@stoprocent/noble 2.1.6 → 2.2.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.
- package/examples/peripheral-explorer-async.js +9 -2
- package/lib/common/include/Emit.h +1 -0
- package/lib/common/src/Emit.cc +8 -0
- package/lib/hci-socket/bindings.js +2 -2
- package/lib/hci-socket/gap.js +0 -5
- package/lib/hci-socket/hci.js +13 -3
- package/lib/mac/src/ble_manager.h +2 -0
- package/lib/mac/src/ble_manager.mm +18 -0
- package/package.json +1 -1
- package/prebuilds/darwin-x64+arm64/@stoprocent+noble.node +0 -0
- package/prebuilds/win32-ia32/@stoprocent+noble.node +0 -0
- package/prebuilds/win32-x64/@stoprocent+noble.node +0 -0
- package/test/lib/hci-socket/bindings.test.js +12 -10
- package/test/lib/hci-socket/gap.test.js +0 -16
|
@@ -13,6 +13,7 @@ try {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const starTime = Date.now();
|
|
16
|
+
let cancelConnectTimeout;
|
|
16
17
|
|
|
17
18
|
let peripheral;
|
|
18
19
|
async function main () {
|
|
@@ -20,12 +21,13 @@ async function main () {
|
|
|
20
21
|
await noble.waitForPoweredOnAsync();
|
|
21
22
|
|
|
22
23
|
// Cancel the connection after 5 seconds if it is still connecting
|
|
23
|
-
setTimeout(() => {
|
|
24
|
+
cancelConnectTimeout = setTimeout(() => {
|
|
24
25
|
noble.cancelConnect(peripheralIdOrAddress);
|
|
25
26
|
}, 5000);
|
|
26
27
|
|
|
27
28
|
if (directConnect === '1') {
|
|
28
29
|
peripheral = await noble.connectAsync(peripheralIdOrAddress, { addressType });
|
|
30
|
+
clearTimeout(cancelConnectTimeout);
|
|
29
31
|
await explore(peripheral);
|
|
30
32
|
} else {
|
|
31
33
|
await noble.startScanningAsync([], false);
|
|
@@ -92,13 +94,18 @@ const explore = async (peripheral) => {
|
|
|
92
94
|
console.log('noble stopped');
|
|
93
95
|
});
|
|
94
96
|
|
|
97
|
+
peripheral.on('mtu', (mtu) => {
|
|
98
|
+
console.log('MTU Updated: ', mtu);
|
|
99
|
+
});
|
|
100
|
+
|
|
95
101
|
if (peripheral.state !== 'connected') {
|
|
96
102
|
await peripheral.connectAsync();
|
|
103
|
+
clearTimeout(cancelConnectTimeout);
|
|
97
104
|
}
|
|
98
105
|
|
|
99
106
|
const rssi = await peripheral.updateRssiAsync();
|
|
100
107
|
console.log('RSSI', rssi);
|
|
101
|
-
|
|
108
|
+
|
|
102
109
|
const services = await peripheral.discoverServicesAsync([]);
|
|
103
110
|
|
|
104
111
|
for (const service of services) {
|
|
@@ -18,6 +18,7 @@ public:
|
|
|
18
18
|
void Scan(const std::string& uuid, int rssi, const Peripheral& peripheral);
|
|
19
19
|
void Connected(const std::string& uuid, const std::string& error = "");
|
|
20
20
|
void Disconnected(const std::string& uuid);
|
|
21
|
+
void MTU(const std::string& uuid, int mtu);
|
|
21
22
|
void RSSI(const std::string& uuid, int rssi, const std::string& error = "");
|
|
22
23
|
void ServicesDiscovered(const std::string& uuid, const std::vector<std::string>& serviceUuids, const std::string& error = "");
|
|
23
24
|
void IncludedServicesDiscovered(const std::string& uuid, const std::string& serviceUuid, const std::vector<std::string>& serviceUuids, const std::string& error = "");
|
package/lib/common/src/Emit.cc
CHANGED
|
@@ -162,6 +162,14 @@ void Emit::Disconnected(const std::string& uuid)
|
|
|
162
162
|
});
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
void Emit::MTU(const std::string& uuid, int mtu)
|
|
166
|
+
{
|
|
167
|
+
mCallback->call([uuid, mtu](Napi::Env env, std::vector<napi_value>& args) {
|
|
168
|
+
// emit('onMtu', deviceUuid, mtu);
|
|
169
|
+
args = { _s("onMtu"), _u(uuid), _n(mtu) };
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
165
173
|
void Emit::RSSI(const std::string& uuid, int rssi, const std::string& error)
|
|
166
174
|
{
|
|
167
175
|
mCallback->call([uuid, rssi, error](Napi::Env env, std::vector<napi_value>& args) {
|
|
@@ -130,7 +130,7 @@ NobleBindings.prototype.connect = function (peripheralUuid, parameters = {}) {
|
|
|
130
130
|
const processNextConnection = () => {
|
|
131
131
|
if (this._connectionQueue.length === 1) {
|
|
132
132
|
const nextConn = this._connectionQueue[0]; // Look at next connection but don't remove yet
|
|
133
|
-
this._hci.createLeConn(nextConn.address, nextConn.addressType, nextConn.params);
|
|
133
|
+
this._hci.createLeConn(nextConn.address, nextConn.addressType, nextConn.params, Object.keys(this._handles).length === 0);
|
|
134
134
|
}
|
|
135
135
|
};
|
|
136
136
|
|
|
@@ -404,7 +404,7 @@ NobleBindings.prototype.onLeConnComplete = function (
|
|
|
404
404
|
// Process next connection in queue if any
|
|
405
405
|
if (this._connectionQueue.length > 0 && !this._isScanning) {
|
|
406
406
|
const nextConn = this._connectionQueue[0];
|
|
407
|
-
this._hci.createLeConn(nextConn.address, nextConn.addressType, nextConn.params);
|
|
407
|
+
this._hci.createLeConn(nextConn.address, nextConn.addressType, nextConn.params, Object.keys(this._handles).length === 0);
|
|
408
408
|
}
|
|
409
409
|
};
|
|
410
410
|
|
package/lib/hci-socket/gap.js
CHANGED
|
@@ -40,11 +40,6 @@ Gap.prototype.setScanParameters = function (interval, window) {
|
|
|
40
40
|
};
|
|
41
41
|
|
|
42
42
|
Gap.prototype.startScanning = function (allowDuplicates) {
|
|
43
|
-
this._hci.once('reset', () => this.startScanningAfterReset(allowDuplicates));
|
|
44
|
-
this._hci.reset();
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
Gap.prototype.startScanningAfterReset = function (allowDuplicates) {
|
|
48
43
|
this._scanState = 'starting';
|
|
49
44
|
this._scanFilterDuplicates = !allowDuplicates;
|
|
50
45
|
|
package/lib/hci-socket/hci.js
CHANGED
|
@@ -508,9 +508,19 @@ Hci.prototype.setScanEnabled = function (enabled, filterDuplicates) {
|
|
|
508
508
|
this._socket.write(cmd);
|
|
509
509
|
};
|
|
510
510
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
511
|
+
// This is a mystery case for me.
|
|
512
|
+
// I don't know why we need to reset the hci socket before creating a LE connection.
|
|
513
|
+
// Issues related to this:
|
|
514
|
+
// https://github.com/stoprocent/noble/issues/12
|
|
515
|
+
// https://github.com/bluez/bluez/issues/1178
|
|
516
|
+
Hci.prototype.createLeConn = function (address, addressType, parameters = {}, reset = true) {
|
|
517
|
+
if (reset) {
|
|
518
|
+
this.once('reset', () => this.createLeConnAfterReset(address, addressType, parameters));
|
|
519
|
+
this.reset();
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
this.createLeConnAfterReset(address, addressType, parameters);
|
|
523
|
+
}
|
|
514
524
|
};
|
|
515
525
|
|
|
516
526
|
Hci.prototype.createLeConnAfterReset = function (address, addressType, parameters = {}) {
|
|
@@ -14,8 +14,10 @@
|
|
|
14
14
|
@property (assign) CBManagerState lastState;
|
|
15
15
|
@property dispatch_queue_t dispatchQueue;
|
|
16
16
|
@property NSMutableDictionary *peripherals;
|
|
17
|
+
@property NSMutableDictionary *mtus;
|
|
17
18
|
@property NSMutableSet *discovered;
|
|
18
19
|
|
|
20
|
+
|
|
19
21
|
- (instancetype)init: (const Napi::Value&) receiver with: (const Napi::Function&) callback;
|
|
20
22
|
- (void)scan: (NSArray<NSString*> *)serviceUUIDs allowDuplicates: (BOOL)allowDuplicates;
|
|
21
23
|
- (void)stopScan;
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
#import <Foundation/Foundation.h>
|
|
6
6
|
|
|
7
|
+
@interface BLEManager ()
|
|
8
|
+
- (void)updateMtuForPeripheral:(CBPeripheral*) peripheral;
|
|
9
|
+
@end
|
|
10
|
+
|
|
7
11
|
@implementation BLEManager
|
|
8
12
|
|
|
9
13
|
- (instancetype)init: (const Napi::Value&) receiver with: (const Napi::Function&) callback
|
|
@@ -16,10 +20,20 @@
|
|
|
16
20
|
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:self.dispatchQueue];
|
|
17
21
|
self.discovered = [NSMutableSet set];
|
|
18
22
|
self.peripherals = [NSMutableDictionary dictionaryWithCapacity:10];
|
|
23
|
+
self.mtus = [NSMutableDictionary dictionaryWithCapacity:10];
|
|
19
24
|
}
|
|
20
25
|
return self;
|
|
21
26
|
}
|
|
22
27
|
|
|
28
|
+
- (void)updateMtuForPeripheral:(CBPeripheral*) peripheral {
|
|
29
|
+
NSUInteger mtu = [peripheral maximumWriteValueLengthForType:CBCharacteristicWriteWithoutResponse];
|
|
30
|
+
NSNumber *mtuNumber = [self.mtus objectForKey: peripheral.identifier];
|
|
31
|
+
if (!mtuNumber || [mtuNumber unsignedIntegerValue] != mtu) {
|
|
32
|
+
emit.MTU(getUuid(peripheral), mtu);
|
|
33
|
+
[self.mtus setObject:[NSNumber numberWithInt:mtu] forKey: peripheral.identifier];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
23
37
|
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
|
|
24
38
|
{
|
|
25
39
|
if (central.state != self.lastState && self.lastState == CBManagerStatePoweredOff && central.state == CBManagerStatePoweredOn) {
|
|
@@ -154,6 +168,8 @@
|
|
|
154
168
|
|
|
155
169
|
std::string uuid = getUuid(peripheral);
|
|
156
170
|
emit.Connected(uuid, "");
|
|
171
|
+
[self updateMtuForPeripheral:peripheral];
|
|
172
|
+
|
|
157
173
|
}
|
|
158
174
|
|
|
159
175
|
- (void) centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
|
|
@@ -213,6 +229,7 @@
|
|
|
213
229
|
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
|
|
214
230
|
std::string uuid = getUuid(peripheral);
|
|
215
231
|
std::vector<std::string> services = getServices(peripheral.services);
|
|
232
|
+
[self updateMtuForPeripheral:peripheral];
|
|
216
233
|
emit.ServicesDiscovered(uuid, services, error ? error.localizedDescription.UTF8String : "");
|
|
217
234
|
}
|
|
218
235
|
|
|
@@ -263,6 +280,7 @@
|
|
|
263
280
|
std::string uuid = getUuid(peripheral);
|
|
264
281
|
std::string serviceUuid = std::string([service.UUID.UUIDString UTF8String]);
|
|
265
282
|
auto characteristics = getCharacteristics(service.characteristics);
|
|
283
|
+
[self updateMtuForPeripheral:peripheral];
|
|
266
284
|
emit.CharacteristicsDiscovered(uuid, serviceUuid, characteristics, error ? error.localizedDescription.UTF8String : "");
|
|
267
285
|
}
|
|
268
286
|
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -239,7 +239,7 @@ describe('hci-socket bindings', () => {
|
|
|
239
239
|
should(bindings._connectionQueue[0].params).eql({ addressType: 'public' });
|
|
240
240
|
|
|
241
241
|
expect(bindings._hci.createLeConn).toHaveBeenCalledTimes(1);
|
|
242
|
-
expect(bindings._hci.createLeConn).toHaveBeenCalledWith('11:22:33:44:55:66', 'public', { addressType: 'public' });
|
|
242
|
+
expect(bindings._hci.createLeConn).toHaveBeenCalledWith('11:22:33:44:55:66', 'public', { addressType: 'public' }, true);
|
|
243
243
|
});
|
|
244
244
|
|
|
245
245
|
it('missing peripheral, no queue, random address', () => {
|
|
@@ -252,7 +252,7 @@ describe('hci-socket bindings', () => {
|
|
|
252
252
|
should(bindings._connectionQueue[0].params).eql({ addressType: 'random' });
|
|
253
253
|
|
|
254
254
|
expect(bindings._hci.createLeConn).toHaveBeenCalledTimes(1);
|
|
255
|
-
expect(bindings._hci.createLeConn).toHaveBeenCalledWith('f3:22:33:44:55:66', 'random', { addressType: 'random' });
|
|
255
|
+
expect(bindings._hci.createLeConn).toHaveBeenCalledWith('f3:22:33:44:55:66', 'random', { addressType: 'random' }, true);
|
|
256
256
|
});
|
|
257
257
|
|
|
258
258
|
it('existing peripheral, no queue', () => {
|
|
@@ -271,7 +271,7 @@ describe('hci-socket bindings', () => {
|
|
|
271
271
|
should(bindings._connectionQueue[0].params).eql('parameters');
|
|
272
272
|
|
|
273
273
|
expect(bindings._hci.createLeConn).toHaveBeenCalledTimes(1);
|
|
274
|
-
expect(bindings._hci.createLeConn).toHaveBeenCalledWith('address', 'addressType', 'parameters');
|
|
274
|
+
expect(bindings._hci.createLeConn).toHaveBeenCalledWith('address', 'addressType', 'parameters', true);
|
|
275
275
|
});
|
|
276
276
|
|
|
277
277
|
it('missing peripheral, with queue', () => {
|
|
@@ -801,8 +801,8 @@ describe('hci-socket bindings', () => {
|
|
|
801
801
|
expect(connectCallback).toHaveBeenCalledTimes(1);
|
|
802
802
|
expect(connectCallback).toHaveBeenCalledWith('112233445566', null);
|
|
803
803
|
expect(Hci.createLeConnSpy).toHaveBeenCalledTimes(2);
|
|
804
|
-
expect(Hci.createLeConnSpy).toHaveBeenCalledWith('112233445566', 'random', { addressType: 'random' });
|
|
805
|
-
expect(Hci.createLeConnSpy).toHaveBeenCalledWith('998877665544', 'public', { addressType: 'public' });
|
|
804
|
+
expect(Hci.createLeConnSpy).toHaveBeenCalledWith('112233445566', 'random', { addressType: 'random' }, true);
|
|
805
|
+
expect(Hci.createLeConnSpy).toHaveBeenCalledWith('998877665544', 'public', { addressType: 'public' }, false);
|
|
806
806
|
|
|
807
807
|
should(bindings._connectionQueue).length(1);
|
|
808
808
|
});
|
|
@@ -822,14 +822,16 @@ describe('hci-socket bindings', () => {
|
|
|
822
822
|
bindings.connect('queuedId_2', { addressType: 'public' });
|
|
823
823
|
bindings.connect('queuedId_3', { addressType: 'random' });
|
|
824
824
|
|
|
825
|
+
bindings.emit('reset');
|
|
826
|
+
|
|
825
827
|
bindings.on('connect', connectCallback);
|
|
826
828
|
bindings.onLeConnComplete(status, handle, role, addressType, address);
|
|
827
829
|
|
|
828
830
|
expect(connectCallback).toHaveBeenCalledTimes(1);
|
|
829
831
|
expect(connectCallback).toHaveBeenCalledWith('112233445566', null);
|
|
830
832
|
expect(Hci.createLeConnSpy).toHaveBeenCalledTimes(2);
|
|
831
|
-
expect(Hci.createLeConnSpy).toHaveBeenCalledWith('112233445566', 'random', { addressType: 'random' });
|
|
832
|
-
expect(Hci.createLeConnSpy).toHaveBeenCalledWith('998877665544', 'public', { addressType: 'public' });
|
|
833
|
+
expect(Hci.createLeConnSpy).toHaveBeenCalledWith('112233445566', 'random', { addressType: 'random' }, true);
|
|
834
|
+
expect(Hci.createLeConnSpy).toHaveBeenCalledWith('998877665544', 'public', { addressType: 'public' }, false);
|
|
833
835
|
expect(bindings._connectionQueue).toHaveLength(2);
|
|
834
836
|
});
|
|
835
837
|
|
|
@@ -865,9 +867,9 @@ describe('hci-socket bindings', () => {
|
|
|
865
867
|
expect(connectCallback).toHaveBeenCalledWith('998877665544', null);
|
|
866
868
|
|
|
867
869
|
expect(Hci.createLeConnSpy).toHaveBeenCalledTimes(3);
|
|
868
|
-
expect(Hci.createLeConnSpy).toHaveBeenCalledWith('112233445566', 'random', { addressType: 'random' });
|
|
869
|
-
expect(Hci.createLeConnSpy).toHaveBeenCalledWith('998877665544', 'public', { addressType: 'public' });
|
|
870
|
-
expect(Hci.createLeConnSpy).toHaveBeenCalledWith('aabbccddeeff', 'random', { addressType: 'random' });
|
|
870
|
+
expect(Hci.createLeConnSpy).toHaveBeenCalledWith('112233445566', 'random', { addressType: 'random' }, true);
|
|
871
|
+
expect(Hci.createLeConnSpy).toHaveBeenCalledWith('998877665544', 'public', { addressType: 'public' }, false);
|
|
872
|
+
expect(Hci.createLeConnSpy).toHaveBeenCalledWith('aabbccddeeff', 'random', { addressType: 'random' }, false);
|
|
871
873
|
expect(bindings._connectionQueue).toHaveLength(1);
|
|
872
874
|
});
|
|
873
875
|
});
|
|
@@ -46,7 +46,6 @@ describe('hci-socket gap', () => {
|
|
|
46
46
|
const hci = {
|
|
47
47
|
on: sinon.spy(),
|
|
48
48
|
once: sinon.spy(),
|
|
49
|
-
reset: sinon.spy(),
|
|
50
49
|
setScanEnabled: sinon.spy(),
|
|
51
50
|
setScanParameters: sinon.spy()
|
|
52
51
|
};
|
|
@@ -54,21 +53,6 @@ describe('hci-socket gap', () => {
|
|
|
54
53
|
const gap = new Gap(hci);
|
|
55
54
|
gap.startScanning(true);
|
|
56
55
|
|
|
57
|
-
assert.callCount(hci.once, 1);
|
|
58
|
-
assert.calledWithExactly(hci.reset);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('startScanningAfterReset', () => {
|
|
62
|
-
const hci = {
|
|
63
|
-
on: sinon.spy(),
|
|
64
|
-
once: sinon.spy(),
|
|
65
|
-
setScanEnabled: sinon.spy(),
|
|
66
|
-
setScanParameters: sinon.spy()
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const gap = new Gap(hci);
|
|
70
|
-
gap.startScanningAfterReset(true);
|
|
71
|
-
|
|
72
56
|
should(gap._scanState).equal('starting');
|
|
73
57
|
should(gap._scanFilterDuplicates).equal(false);
|
|
74
58
|
|