@stoprocent/noble 1.15.2 → 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 () {
@@ -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
@@ -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
  }
@@ -369,6 +364,20 @@ Hci.prototype.setLeEventMask = function () {
369
364
  this._socket.write(cmd);
370
365
  };
371
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
+
372
381
  Hci.prototype.readLeBufferSize = function () {
373
382
  const cmd = Buffer.alloc(4);
374
383
 
@@ -917,6 +926,22 @@ Hci.prototype.processCmdCompleteEvent = function (cmd, status, result) {
917
926
  this.setLeEventMask();
918
927
  this.readLocalVersion();
919
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
+ }
920
945
  } else if (cmd === READ_LE_HOST_SUPPORTED_CMD) {
921
946
  if (status === 0) {
922
947
  const le = result.readUInt8(0);
@@ -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.2",
9
+ "version": "1.16.0",
10
10
  "repository": {
11
11
  "type": "git",
12
12
  "url": "https://github.com/stoprocent/noble.git"
@@ -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', () => {
@@ -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', () => {