@stoprocent/noble 1.15.1 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,17 +1,25 @@
1
1
  const noble = require('../');
2
2
 
3
- const peripheralIdOrAddress = process.argv[2].toLowerCase();
3
+ const directConnect = process.argv[2].toLowerCase();
4
+ const peripheralIdOrAddress = process.argv[3].toLowerCase();
5
+
6
+ const starTime = Date.now();
4
7
 
5
8
  noble.on('stateChange', async (state) => {
6
9
  if (state === 'poweredOn') {
7
- await noble.startScanningAsync([], false);
10
+ if (directConnect === '1') {
11
+ await noble.stopScanningAsync();
12
+ await noble.connectAsync(peripheralIdOrAddress.replace(/:/g, ''))
13
+ } else {
14
+ await noble.startScanningAsync();
15
+ }
8
16
  }
9
17
  });
10
18
 
11
19
  noble.on('discover', async (peripheral) => {
12
20
  if ([peripheral.id, peripheral.address].includes(peripheralIdOrAddress)) {
13
21
  await noble.stopScanningAsync();
14
-
22
+
15
23
  console.log(`Peripheral with ID ${peripheral.id} found`);
16
24
  const advertisement = peripheral.advertisement;
17
25
 
@@ -59,7 +67,9 @@ const explore = async (peripheral) => {
59
67
  process.exit(0);
60
68
  });
61
69
 
62
- await peripheral.connectAsync();
70
+ if (peripheral.state !== 'connected') {
71
+ await peripheral.connectAsync();
72
+ }
63
73
 
64
74
  const services = await peripheral.discoverServicesAsync([]);
65
75
 
@@ -114,7 +124,9 @@ const explore = async (peripheral) => {
114
124
  }
115
125
  }
116
126
 
127
+ console.log(`Time taken: ${Date.now() - starTime}ms`);
117
128
  await peripheral.disconnectAsync();
129
+
118
130
  };
119
131
 
120
132
  process.on('SIGINT', function () {
package/index.d.ts CHANGED
@@ -29,6 +29,7 @@ export declare function connect(peripheralUuid: string, options?: object, callba
29
29
  export declare function connectAsync(peripheralUuid: string, options?: object): Promise<Peripheral>;
30
30
  export declare function cancelConnect(peripheralUuid: string, options?: object): void;
31
31
  export declare function reset(): void;
32
+ export declare function stop(): void;
32
33
 
33
34
  export declare function setAddress(address: string): void;
34
35
 
@@ -53,11 +53,12 @@ NobleBindings.prototype.stopScanning = function () {
53
53
 
54
54
  NobleBindings.prototype.connect = function (peripheralUuid, parameters) {
55
55
  let address = this._addresses[peripheralUuid];
56
- const addressType = this._addresseTypes[peripheralUuid] || 'random'; // Default to 'random' if type is not defined
57
-
56
+ let addressType = this._addresseTypes[peripheralUuid] || 'random'; // Default to 'random' if type is not defined
57
+
58
58
  // If address is not available, generate it from the UUID using the transformation logic inline
59
59
  if (!address) {
60
60
  address = peripheralUuid.match(/.{1,2}/g).join(':'); // Converts UUID back to MAC address format
61
+ addressType = (parseInt(address.slice(0, 2).slice(0,2), 16) & 0x02) === 0 ? 'public' : 'random';
61
62
  }
62
63
 
63
64
  // Manage connection attempts
@@ -113,7 +114,7 @@ NobleBindings.prototype.init = function () {
113
114
  is present it can throw an exception - in which case we don't
114
115
  want to try and clear up afterwards (issue #502) */
115
116
  process.on('SIGINT', this.onSigIntBinded);
116
- process.on('exit', this.onExit.bind(this));
117
+ process.on('exit', this.stop.bind(this));
117
118
  };
118
119
 
119
120
  NobleBindings.prototype.onSigInt = function () {
@@ -126,12 +127,13 @@ NobleBindings.prototype.onSigInt = function () {
126
127
  }
127
128
  };
128
129
 
129
- NobleBindings.prototype.onExit = function () {
130
+ NobleBindings.prototype.stop = function () {
130
131
  this.stopScanning();
131
-
132
132
  for (const handle in this._aclStreams) {
133
133
  this._hci.disconnect(handle);
134
134
  }
135
+ this._hci.reset();
136
+ this._hci.stop();
135
137
  };
136
138
 
137
139
  NobleBindings.prototype.onStateChange = function (state) {
@@ -49,6 +49,7 @@ const OCF_READ_RSSI = 0x0005;
49
49
  const OGF_LE_CTL = 0x08;
50
50
  const OCF_LE_SET_EVENT_MASK = 0x0001;
51
51
  const OCF_LE_READ_BUFFER_SIZE = 0x0002;
52
+ const OCF_LE_READ_LOCAL_SUPPORTED_FEATURES = 0x0003;
52
53
  const OCF_LE_SET_EXTENDED_SCAN_PARAMETERS = 0x0041;
53
54
  const OCF_LE_SET_EXTENDED_SCAN_ENABLE = 0x0042;
54
55
  const OCF_LE_SET_SCAN_PARAMETERS = 0x000b;
@@ -77,6 +78,7 @@ const READ_RSSI_CMD = OCF_READ_RSSI | (OGF_STATUS_PARAM << 10);
77
78
 
78
79
  const LE_SET_EVENT_MASK_CMD = OCF_LE_SET_EVENT_MASK | (OGF_LE_CTL << 10);
79
80
  const LE_READ_BUFFER_SIZE_CMD = OCF_LE_READ_BUFFER_SIZE | (OGF_LE_CTL << 10);
81
+ const LE_READ_LOCAL_SUPPORTED_FEATURES = OCF_LE_READ_LOCAL_SUPPORTED_FEATURES | (OGF_LE_CTL << 10);
80
82
  const LE_SET_EXTENDED_SCAN_PARAMETERS_CMD =
81
83
  OCF_LE_SET_EXTENDED_SCAN_PARAMETERS | (OGF_LE_CTL << 10);
82
84
  const LE_SET_EXTENDED_SCAN_ENABLE_CMD =
@@ -164,7 +166,8 @@ Hci.prototype.init = function (options) {
164
166
  this._bound = true;
165
167
  }
166
168
  this._socket.start();
167
-
169
+
170
+ this.reset();
168
171
  this.pollIsDevUp();
169
172
  }
170
173
  };
@@ -181,17 +184,9 @@ Hci.prototype.pollIsDevUp = function () {
181
184
  return;
182
185
  }
183
186
 
184
- if (this._isExtended) {
185
- this.setCodedPhySupport();
186
- }
187
187
  this.setSocketFilter();
188
- this.setEventMask();
189
- this.setLeEventMask();
190
- this.readLocalVersion();
191
- this.writeLeHostSupported();
192
- this.readLeHostSupported();
193
- this.readLeBufferSize();
194
- this.readBdAddr();
188
+ this.readLeSupportedFeatures();
189
+ // Subsequent calls moved to processCmdCompleteEvent for LE_READ_LOCAL_SUPPORTED_FEATURES
195
190
  } else {
196
191
  this.emit('stateChange', 'poweredOff');
197
192
  }
@@ -290,6 +285,10 @@ Hci.prototype.reset = function () {
290
285
  this._socket.write(cmd);
291
286
  };
292
287
 
288
+ Hci.prototype.stop = function () {
289
+ this._socket.stop();
290
+ };
291
+
293
292
  Hci.prototype.readSupportedCommands = function () {
294
293
  const cmd = Buffer.alloc(4);
295
294
 
@@ -365,6 +364,20 @@ Hci.prototype.setLeEventMask = function () {
365
364
  this._socket.write(cmd);
366
365
  };
367
366
 
367
+ Hci.prototype.readLeSupportedFeatures = function () {
368
+ const cmd = Buffer.alloc(4);
369
+
370
+ // header
371
+ cmd.writeUInt8(HCI_COMMAND_PKT, 0);
372
+ cmd.writeUInt16LE(LE_READ_LOCAL_SUPPORTED_FEATURES, 1);
373
+
374
+ // length
375
+ cmd.writeUInt8(0x0, 3);
376
+
377
+ debug(`le read supported feature - writing: ${cmd.toString('hex')}`);
378
+ this._socket.write(cmd);
379
+ };
380
+
368
381
  Hci.prototype.readLeBufferSize = function () {
369
382
  const cmd = Buffer.alloc(4);
370
383
 
@@ -913,6 +926,22 @@ Hci.prototype.processCmdCompleteEvent = function (cmd, status, result) {
913
926
  this.setLeEventMask();
914
927
  this.readLocalVersion();
915
928
  this.readBdAddr();
929
+ } else if (cmd === LE_READ_LOCAL_SUPPORTED_FEATURES) {
930
+ if (status === 0) {
931
+ const featuresLow = result.readUInt32LE(0);
932
+ this._isExtended = (featuresLow & (1 << 6)) !== 0;
933
+
934
+ if (this._isExtended) {
935
+ this.setCodedPhySupport();
936
+ }
937
+ this.setEventMask();
938
+ this.setLeEventMask();
939
+ this.readLocalVersion();
940
+ this.writeLeHostSupported();
941
+ this.readLeHostSupported();
942
+ this.readLeBufferSize();
943
+ this.readBdAddr();
944
+ }
916
945
  } else if (cmd === READ_LE_HOST_SUPPORTED_CMD) {
917
946
  if (status === 0) {
918
947
  const le = result.readUInt8(0);
package/lib/noble.js CHANGED
@@ -190,7 +190,15 @@ Noble.prototype.onScanStop = function () {
190
190
  };
191
191
 
192
192
  Noble.prototype.reset = function () {
193
- this._bindings.reset();
193
+ if (this._bindings.reset) {
194
+ this._bindings.reset();
195
+ }
196
+ };
197
+
198
+ Noble.prototype.stop = function () {
199
+ if (this._bindings.stop) {
200
+ this._bindings.stop();
201
+ }
194
202
  };
195
203
 
196
204
  Noble.prototype.onDiscover = function (uuid, address, addressType, connectable, advertisement, rssi, scannable) {
@@ -35,7 +35,7 @@
35
35
  'msvs_target_platform_minversion':'10.0.18362.0',
36
36
  'conditions': [
37
37
  ['OS=="win"', {
38
- 'defines': [ '_HAS_EXCEPTIONS=1' ]
38
+ 'defines': [ '_HAS_EXCEPTIONS=1', 'NAPI_CPP_EXCEPTIONS' ]
39
39
  }]
40
40
  ],
41
41
  }
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "license": "MIT",
7
7
  "name": "@stoprocent/noble",
8
8
  "description": "A Node.js BLE (Bluetooth Low Energy) central library.",
9
- "version": "1.15.1",
9
+ "version": "1.16.0",
10
10
  "repository": {
11
11
  "type": "git",
12
12
  "url": "https://github.com/stoprocent/noble.git"
@@ -34,7 +34,7 @@
34
34
  "node-gyp-build": "^4.8.1"
35
35
  },
36
36
  "optionalDependencies": {
37
- "@stoprocent/bluetooth-hci-socket": "^1.4.1"
37
+ "@stoprocent/bluetooth-hci-socket": "^1.4.2"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@commitlint/cli": "^19.3.0",
@@ -52,7 +52,7 @@
52
52
  "mocha": "^10.7.0",
53
53
  "nyc": "^17.0.0",
54
54
  "prebuildify": "^6.0.1",
55
- "prebuildify-cross": "thegecko/prebuildify-cross#fix-docker",
55
+ "prebuildify-cross": "^5.1.1",
56
56
  "semantic-release": "^24.1.1",
57
57
  "jshint": "^2.13.6",
58
58
  "prettier": "^3.3.3",
@@ -132,7 +132,7 @@ describe('hci-socket bindings', () => {
132
132
  });
133
133
 
134
134
  describe('connect', () => {
135
- it('missing peripheral, no queue', () => {
135
+ it('missing peripheral, no queue, public address', () => {
136
136
  bindings._hci.createLeConn = fake.resolves(null);
137
137
 
138
138
  bindings.connect('112233445566', 'parameters');
@@ -140,7 +140,18 @@ describe('hci-socket bindings', () => {
140
140
  should(bindings._pendingConnectionUuid).eql('112233445566');
141
141
 
142
142
  assert.calledOnce(bindings._hci.createLeConn);
143
- assert.calledWith(bindings._hci.createLeConn, '11:22:33:44:55:66', 'random', 'parameters');
143
+ assert.calledWith(bindings._hci.createLeConn, '11:22:33:44:55:66', 'public', 'parameters');
144
+ });
145
+
146
+ it('missing peripheral, no queue, random address', () => {
147
+ bindings._hci.createLeConn = fake.resolves(null);
148
+
149
+ bindings.connect('f32233445566', 'parameters');
150
+
151
+ should(bindings._pendingConnectionUuid).eql('f32233445566');
152
+
153
+ assert.calledOnce(bindings._hci.createLeConn);
154
+ assert.calledWith(bindings._hci.createLeConn, 'f3:22:33:44:55:66', 'random', 'parameters');
144
155
  });
145
156
 
146
157
  it('existing peripheral, no queue', () => {
@@ -268,22 +279,28 @@ describe('hci-socket bindings', () => {
268
279
  assert.calledTwice(process.on);
269
280
  });
270
281
 
271
- describe('onExit', () => {
282
+ describe('stop', () => {
272
283
  it('no handles', () => {
273
284
  bindings._gap.stopScanning = fake.resolves(null);
285
+ bindings._hci.reset = fake.resolves(null);
286
+ bindings._hci.stop = fake.resolves(null);
274
287
 
275
- bindings.onExit();
288
+ bindings.stop();
276
289
 
277
290
  assert.calledOnce(bindings._gap.stopScanning);
291
+ assert.calledOnce(bindings._hci.reset);
292
+ assert.calledOnce(bindings._hci.stop);
278
293
  });
279
294
 
280
295
  it('with handles', () => {
281
296
  bindings._gap.stopScanning = fake.resolves(null);
282
297
  bindings._hci.disconnect = fake.resolves(null);
298
+ bindings._hci.reset = fake.resolves(null);
299
+ bindings._hci.stop = fake.resolves(null);
283
300
 
284
301
  bindings._aclStreams = [1, 2, 3];
285
302
 
286
- bindings.onExit();
303
+ bindings.stop();
287
304
 
288
305
  assert.calledOnce(bindings._gap.stopScanning);
289
306
  assert.calledThrice(bindings._hci.disconnect);
@@ -109,6 +109,7 @@ describe('hci-socket hci', () => {
109
109
  hci.readLeBufferSize = sinon.spy();
110
110
  hci.readBdAddr = sinon.spy();
111
111
  hci.init = sinon.spy();
112
+ hci.readLeSupportedFeatures = sinon.spy();
112
113
  hci.setCodedPhySupport = sinon.spy();
113
114
 
114
115
  hci.on('stateChange', callback);
@@ -170,13 +171,7 @@ describe('hci-socket hci', () => {
170
171
  hci.pollIsDevUp();
171
172
 
172
173
  assert.calledOnceWithExactly(hci.setSocketFilter);
173
- assert.calledOnceWithExactly(hci.setEventMask);
174
- assert.calledOnceWithExactly(hci.setLeEventMask);
175
- assert.calledOnceWithExactly(hci.readLocalVersion);
176
- assert.calledOnceWithExactly(hci.writeLeHostSupported);
177
- assert.calledOnceWithExactly(hci.readLeHostSupported);
178
- assert.calledOnceWithExactly(hci.readLeBufferSize);
179
- assert.calledOnceWithExactly(hci.readBdAddr);
174
+ assert.calledOnceWithExactly(hci.readLeSupportedFeatures);
180
175
 
181
176
  assert.notCalled(hci.setCodedPhySupport);
182
177
  assert.notCalled(hci._socket.removeAllListeners);
@@ -193,14 +188,7 @@ describe('hci-socket hci', () => {
193
188
  hci.pollIsDevUp();
194
189
 
195
190
  assert.calledOnceWithExactly(hci.setSocketFilter);
196
- assert.calledOnceWithExactly(hci.setEventMask);
197
- assert.calledOnceWithExactly(hci.setLeEventMask);
198
- assert.calledOnceWithExactly(hci.readLocalVersion);
199
- assert.calledOnceWithExactly(hci.writeLeHostSupported);
200
- assert.calledOnceWithExactly(hci.readLeHostSupported);
201
- assert.calledOnceWithExactly(hci.readLeBufferSize);
202
- assert.calledOnceWithExactly(hci.readBdAddr);
203
- assert.calledOnceWithExactly(hci.setCodedPhySupport);
191
+ assert.calledOnceWithExactly(hci.readLeSupportedFeatures);
204
192
 
205
193
  assert.notCalled(hci._socket.removeAllListeners);
206
194
  assert.notCalled(hci.init);
@@ -698,6 +686,81 @@ describe('hci-socket hci', () => {
698
686
  should(hci._aclConnections).have.keys(4660, 4661);
699
687
  should(hci._aclConnections.get(4660)).deepEqual({ pending: 3 });
700
688
  should(hci._aclConnections.get(4661)).deepEqual({ pending: 2 });
689
+
690
+ describe('LE_READ_LOCAL_SUPPORTED_FEATURES', () => {
691
+ beforeEach(() => {
692
+ hci.setCodedPhySupport = sinon.spy();
693
+ hci.setEventMask = sinon.spy();
694
+ hci.setLeEventMask = sinon.spy();
695
+ hci.readLocalVersion = sinon.spy();
696
+ hci.writeLeHostSupported = sinon.spy();
697
+ hci.readLeHostSupported = sinon.spy();
698
+ hci.readLeBufferSize = sinon.spy();
699
+ hci.readBdAddr = sinon.spy();
700
+ });
701
+
702
+ it('should not process on error status', () => {
703
+ const cmd = 8195;
704
+ const status = 1;
705
+ const result = Buffer.from([0x00, 0x00, 0x00, 0x00]);
706
+
707
+ hci.processCmdCompleteEvent(cmd, status, result);
708
+
709
+ // Verify no methods were called
710
+ assert.notCalled(hci.setCodedPhySupport);
711
+ assert.notCalled(hci.setEventMask);
712
+ assert.notCalled(hci.setLeEventMask);
713
+ assert.notCalled(hci.readLocalVersion);
714
+ assert.notCalled(hci.writeLeHostSupported);
715
+ assert.notCalled(hci.readLeHostSupported);
716
+ assert.notCalled(hci.readLeBufferSize);
717
+ assert.notCalled(hci.readBdAddr);
718
+
719
+ should(hci._isExtended).equal(false);
720
+ });
721
+
722
+ it('should process without extended features', () => {
723
+ const cmd = 8195;
724
+ const status = 0;
725
+ const result = Buffer.from([0x00, 0x00, 0x00, 0x00]); // No bits set
726
+
727
+ hci.processCmdCompleteEvent(cmd, status, result);
728
+
729
+ // Verify extended-specific method not called
730
+ assert.notCalled(hci.setCodedPhySupport);
731
+
732
+ // Verify other methods were called
733
+ assert.calledOnce(hci.setEventMask);
734
+ assert.calledOnce(hci.setLeEventMask);
735
+ assert.calledOnce(hci.readLocalVersion);
736
+ assert.calledOnce(hci.writeLeHostSupported);
737
+ assert.calledOnce(hci.readLeHostSupported);
738
+ assert.calledOnce(hci.readLeBufferSize);
739
+ assert.calledOnce(hci.readBdAddr);
740
+
741
+ should(hci._isExtended).equal(false);
742
+ });
743
+
744
+ it('should process with extended features', () => {
745
+ const cmd = 8195;
746
+ const status = 0;
747
+ const result = Buffer.from([0x40, 0x00, 0x00, 0x00]); // Bit 6 set for extended features
748
+
749
+ hci.processCmdCompleteEvent(cmd, status, result);
750
+
751
+ // Verify all methods were called including extended-specific
752
+ assert.calledOnce(hci.setCodedPhySupport);
753
+ assert.calledOnce(hci.setEventMask);
754
+ assert.calledOnce(hci.setLeEventMask);
755
+ assert.calledOnce(hci.readLocalVersion);
756
+ assert.calledOnce(hci.writeLeHostSupported);
757
+ assert.calledOnce(hci.readLeHostSupported);
758
+ assert.calledOnce(hci.readLeBufferSize);
759
+ assert.calledOnce(hci.readBdAddr);
760
+
761
+ should(hci._isExtended).equal(true);
762
+ });
763
+ });
701
764
  });
702
765
 
703
766
  it('should only processCmdStatusEvent - HCI_EVENT_PKT / EVT_CMD_STATUS', () => {