@stoprocent/noble 2.5.0 → 2.5.2
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/lib/dbus/bindings.js
CHANGED
|
@@ -49,6 +49,13 @@ function loadDbus () {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
function normalizeId (id) {
|
|
53
|
+
// BlueZ emits MACs uppercase; noble's id form is colon-stripped lowercase.
|
|
54
|
+
// Accept either, plus mixed case, so external callers don't have to care.
|
|
55
|
+
if (id == null) return id;
|
|
56
|
+
return String(id).replace(/:/g, '').toLowerCase();
|
|
57
|
+
}
|
|
58
|
+
|
|
52
59
|
function unwrapVariant (variant) {
|
|
53
60
|
if (variant && typeof variant === 'object' && 'value' in variant && 'signature' in variant) {
|
|
54
61
|
return variant.value;
|
|
@@ -102,7 +109,8 @@ function buildAdvertisement (deviceProps) {
|
|
|
102
109
|
const entries = Object.entries(deviceProps.ManufacturerData);
|
|
103
110
|
if (entries.length > 0) {
|
|
104
111
|
const buffers = [];
|
|
105
|
-
for (const [companyId,
|
|
112
|
+
for (const [companyId, rawPayload] of entries) {
|
|
113
|
+
const payload = unwrapVariant(rawPayload);
|
|
106
114
|
const id = Number(companyId) & 0xffff;
|
|
107
115
|
const header = Buffer.from([id & 0xff, (id >> 8) & 0xff]);
|
|
108
116
|
const data = Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
|
|
@@ -113,7 +121,8 @@ function buildAdvertisement (deviceProps) {
|
|
|
113
121
|
}
|
|
114
122
|
|
|
115
123
|
if (deviceProps.ServiceData && typeof deviceProps.ServiceData === 'object') {
|
|
116
|
-
for (const [uuid,
|
|
124
|
+
for (const [uuid, rawPayload] of Object.entries(deviceProps.ServiceData)) {
|
|
125
|
+
const payload = unwrapVariant(rawPayload);
|
|
117
126
|
advertisement.serviceData.push({
|
|
118
127
|
uuid: normalizeUuid(uuid),
|
|
119
128
|
data: Buffer.isBuffer(payload) ? payload : Buffer.from(payload)
|
|
@@ -485,6 +494,7 @@ class DbusBindings extends EventEmitter {
|
|
|
485
494
|
// ---- Connect / disconnect ----
|
|
486
495
|
|
|
487
496
|
connect (peripheralUuid, _parameters) {
|
|
497
|
+
peripheralUuid = normalizeId(peripheralUuid);
|
|
488
498
|
this._connect(peripheralUuid).catch(err => {
|
|
489
499
|
this.emit('connect', peripheralUuid, err);
|
|
490
500
|
});
|
|
@@ -518,10 +528,11 @@ class DbusBindings extends EventEmitter {
|
|
|
518
528
|
}
|
|
519
529
|
|
|
520
530
|
cancelConnect (peripheralUuid, _parameters) {
|
|
521
|
-
this.disconnect(peripheralUuid);
|
|
531
|
+
this.disconnect(normalizeId(peripheralUuid));
|
|
522
532
|
}
|
|
523
533
|
|
|
524
534
|
disconnect (peripheralUuid) {
|
|
535
|
+
peripheralUuid = normalizeId(peripheralUuid);
|
|
525
536
|
this._disconnect(peripheralUuid).catch(err => {
|
|
526
537
|
this.emit('warning', `disconnect failed: ${err.message}`);
|
|
527
538
|
});
|
|
@@ -540,6 +551,7 @@ class DbusBindings extends EventEmitter {
|
|
|
540
551
|
}
|
|
541
552
|
|
|
542
553
|
updateRssi (peripheralUuid) {
|
|
554
|
+
peripheralUuid = normalizeId(peripheralUuid);
|
|
543
555
|
const device = this._devices.get(peripheralUuid);
|
|
544
556
|
if (!device || !device.path) {
|
|
545
557
|
this.emit('rssiUpdate', peripheralUuid, 0, new Error('unknown peripheral'));
|
|
@@ -596,6 +608,7 @@ class DbusBindings extends EventEmitter {
|
|
|
596
608
|
}
|
|
597
609
|
|
|
598
610
|
discoverServices (peripheralUuid, uuids) {
|
|
611
|
+
peripheralUuid = normalizeId(peripheralUuid);
|
|
599
612
|
const wanted = (uuids || []).map(normalizeUuid);
|
|
600
613
|
const found = this._findServicesForDevice(peripheralUuid);
|
|
601
614
|
const filtered = wanted.length === 0 ? found : found.filter(s => wanted.includes(s.uuid));
|
|
@@ -605,11 +618,13 @@ class DbusBindings extends EventEmitter {
|
|
|
605
618
|
}
|
|
606
619
|
|
|
607
620
|
discoverIncludedServices (peripheralUuid, serviceUuid, _serviceUuids) {
|
|
621
|
+
peripheralUuid = normalizeId(peripheralUuid);
|
|
608
622
|
// BlueZ does not expose included services directly via D-Bus.
|
|
609
623
|
this.emit('includedServicesDiscover', peripheralUuid, serviceUuid, []);
|
|
610
624
|
}
|
|
611
625
|
|
|
612
626
|
discoverCharacteristics (peripheralUuid, serviceUuid, characteristicUuids) {
|
|
627
|
+
peripheralUuid = normalizeId(peripheralUuid);
|
|
613
628
|
const services = this._findServicesForDevice(peripheralUuid);
|
|
614
629
|
const service = services.find(s => s.uuid === normalizeUuid(serviceUuid));
|
|
615
630
|
if (!service) {
|
|
@@ -642,6 +657,7 @@ class DbusBindings extends EventEmitter {
|
|
|
642
657
|
}
|
|
643
658
|
|
|
644
659
|
read (peripheralUuid, serviceUuid, characteristicUuid) {
|
|
660
|
+
peripheralUuid = normalizeId(peripheralUuid);
|
|
645
661
|
this._readChar(peripheralUuid, serviceUuid, characteristicUuid).catch(err => {
|
|
646
662
|
this.emit('read', peripheralUuid, serviceUuid, characteristicUuid, null, false, err);
|
|
647
663
|
});
|
|
@@ -658,6 +674,7 @@ class DbusBindings extends EventEmitter {
|
|
|
658
674
|
}
|
|
659
675
|
|
|
660
676
|
write (peripheralUuid, serviceUuid, characteristicUuid, data, withoutResponse) {
|
|
677
|
+
peripheralUuid = normalizeId(peripheralUuid);
|
|
661
678
|
this._writeChar(peripheralUuid, serviceUuid, characteristicUuid, data, withoutResponse).catch(err => {
|
|
662
679
|
this.emit('write', peripheralUuid, serviceUuid, characteristicUuid, err);
|
|
663
680
|
});
|
|
@@ -676,11 +693,13 @@ class DbusBindings extends EventEmitter {
|
|
|
676
693
|
}
|
|
677
694
|
|
|
678
695
|
broadcast (peripheralUuid, serviceUuid, characteristicUuid, _broadcast) {
|
|
696
|
+
peripheralUuid = normalizeId(peripheralUuid);
|
|
679
697
|
this.emit('warning', 'broadcast is not supported on the dbus backend');
|
|
680
698
|
this.emit('broadcast', peripheralUuid, serviceUuid, characteristicUuid, false);
|
|
681
699
|
}
|
|
682
700
|
|
|
683
701
|
notify (peripheralUuid, serviceUuid, characteristicUuid, notify) {
|
|
702
|
+
peripheralUuid = normalizeId(peripheralUuid);
|
|
684
703
|
this._setNotify(peripheralUuid, serviceUuid, characteristicUuid, notify).catch(err => {
|
|
685
704
|
this.emit('notify', peripheralUuid, serviceUuid, characteristicUuid, false, err);
|
|
686
705
|
});
|
|
@@ -724,6 +743,7 @@ class DbusBindings extends EventEmitter {
|
|
|
724
743
|
}
|
|
725
744
|
|
|
726
745
|
discoverDescriptors (peripheralUuid, serviceUuid, characteristicUuid) {
|
|
746
|
+
peripheralUuid = normalizeId(peripheralUuid);
|
|
727
747
|
const charPath = this._findCharacteristicPath(peripheralUuid, serviceUuid, characteristicUuid);
|
|
728
748
|
if (!charPath) {
|
|
729
749
|
this.emit('descriptorsDiscover', peripheralUuid, serviceUuid, characteristicUuid, [], new Error('characteristic not found'));
|
|
@@ -734,6 +754,7 @@ class DbusBindings extends EventEmitter {
|
|
|
734
754
|
}
|
|
735
755
|
|
|
736
756
|
readValue (peripheralUuid, serviceUuid, characteristicUuid, descriptorUuid) {
|
|
757
|
+
peripheralUuid = normalizeId(peripheralUuid);
|
|
737
758
|
this._readDesc(peripheralUuid, serviceUuid, characteristicUuid, descriptorUuid).catch(err => {
|
|
738
759
|
this.emit('valueRead', peripheralUuid, serviceUuid, characteristicUuid, descriptorUuid, null, err);
|
|
739
760
|
});
|
|
@@ -750,6 +771,7 @@ class DbusBindings extends EventEmitter {
|
|
|
750
771
|
}
|
|
751
772
|
|
|
752
773
|
writeValue (peripheralUuid, serviceUuid, characteristicUuid, descriptorUuid, data) {
|
|
774
|
+
peripheralUuid = normalizeId(peripheralUuid);
|
|
753
775
|
this._writeDesc(peripheralUuid, serviceUuid, characteristicUuid, descriptorUuid, data).catch(err => {
|
|
754
776
|
this.emit('valueWrite', peripheralUuid, serviceUuid, characteristicUuid, descriptorUuid, err);
|
|
755
777
|
});
|
|
@@ -766,11 +788,13 @@ class DbusBindings extends EventEmitter {
|
|
|
766
788
|
}
|
|
767
789
|
|
|
768
790
|
readHandle (peripheralUuid, handle) {
|
|
791
|
+
peripheralUuid = normalizeId(peripheralUuid);
|
|
769
792
|
const err = new Error('readHandle is not supported on the dbus backend (BlueZ exposes UUIDs only)');
|
|
770
793
|
this.emit('handleRead', peripheralUuid, handle, null, err);
|
|
771
794
|
}
|
|
772
795
|
|
|
773
796
|
writeHandle (peripheralUuid, handle, _data, _withoutResponse) {
|
|
797
|
+
peripheralUuid = normalizeId(peripheralUuid);
|
|
774
798
|
const err = new Error('writeHandle is not supported on the dbus backend (BlueZ exposes UUIDs only)');
|
|
775
799
|
this.emit('handleWrite', peripheralUuid, handle, err);
|
|
776
800
|
}
|
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": "2.5.
|
|
9
|
+
"version": "2.5.2",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
12
|
"url": "https://github.com/stoprocent/noble.git"
|
|
@@ -36,6 +36,14 @@
|
|
|
36
36
|
"optionalDependencies": {
|
|
37
37
|
"@stoprocent/bluetooth-hci-socket": "^2.2.6"
|
|
38
38
|
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"dbus-next": "^0.10.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"dbus-next": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
39
47
|
"devDependencies": {
|
|
40
48
|
"@babel/eslint-parser": "^7.27.0",
|
|
41
49
|
"@commitlint/cli": "^19.3.0",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -270,6 +270,44 @@ describe('dbus/bindings', () => {
|
|
|
270
270
|
expect(advertisement.serviceUuids).toEqual(['180d']);
|
|
271
271
|
});
|
|
272
272
|
|
|
273
|
+
test('InterfacesAdded unwraps Variant-wrapped ManufacturerData and ServiceData payloads', async () => {
|
|
274
|
+
const bindings = new DbusBindings();
|
|
275
|
+
const discoveries = [];
|
|
276
|
+
bindings.on('discover', (...args) => discoveries.push(args));
|
|
277
|
+
|
|
278
|
+
bindings.start();
|
|
279
|
+
await flush();
|
|
280
|
+
|
|
281
|
+
// BlueZ exposes ManufacturerData as a{qv} and ServiceData as a{sv}; dbus-next
|
|
282
|
+
// surfaces each inner value as a Variant, not the raw bytes.
|
|
283
|
+
const mfgPayload = Buffer.from([0xde, 0xad, 0xbe, 0xef]);
|
|
284
|
+
const svcPayload = Buffer.from([0x01, 0x02, 0x03]);
|
|
285
|
+
|
|
286
|
+
const om = state.rootProxy.getInterface('org.freedesktop.DBus.ObjectManager');
|
|
287
|
+
om.emit('InterfacesAdded', '/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF', {
|
|
288
|
+
'org.bluez.Device1': wrapDict({
|
|
289
|
+
Address: 'AA:BB:CC:DD:EE:FF',
|
|
290
|
+
AddressType: 'public',
|
|
291
|
+
ManufacturerData: { 0x004c: v('ay', mfgPayload) },
|
|
292
|
+
ServiceData: { '0000180d-0000-1000-8000-00805f9b34fb': v('ay', svcPayload) }
|
|
293
|
+
})
|
|
294
|
+
});
|
|
295
|
+
await flush();
|
|
296
|
+
|
|
297
|
+
expect(discoveries.length).toBe(1);
|
|
298
|
+
const advertisement = discoveries[0][4];
|
|
299
|
+
|
|
300
|
+
// Manufacturer: 2-byte little-endian company id (0x004c => Apple) + payload
|
|
301
|
+
expect(Buffer.isBuffer(advertisement.manufacturerData)).toBe(true);
|
|
302
|
+
expect(advertisement.manufacturerData.equals(
|
|
303
|
+
Buffer.concat([Buffer.from([0x4c, 0x00]), mfgPayload])
|
|
304
|
+
)).toBe(true);
|
|
305
|
+
|
|
306
|
+
expect(advertisement.serviceData).toEqual([
|
|
307
|
+
{ uuid: '180d', data: svcPayload }
|
|
308
|
+
]);
|
|
309
|
+
});
|
|
310
|
+
|
|
273
311
|
test('discoverServices/Characteristics/Descriptors walk the cached object tree', async () => {
|
|
274
312
|
const tree = adapterTree({
|
|
275
313
|
'/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF': {
|
|
@@ -409,4 +447,75 @@ describe('dbus/bindings', () => {
|
|
|
409
447
|
const bindings = new DbusBindings();
|
|
410
448
|
expect(bindings.addressToId('AA:BB:CC:DD:EE:FF')).toBe('aabbccddeeff');
|
|
411
449
|
});
|
|
450
|
+
|
|
451
|
+
describe('peripheral id normalization on public methods', () => {
|
|
452
|
+
const tree = () => adapterTree({
|
|
453
|
+
'/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF': {
|
|
454
|
+
'org.bluez.Device1': { Address: 'AA:BB:CC:DD:EE:FF', AddressType: 'public', Connected: false }
|
|
455
|
+
},
|
|
456
|
+
'/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF/service0001': {
|
|
457
|
+
'org.bluez.GattService1': { UUID: '0000180d-0000-1000-8000-00805f9b34fb', Primary: true }
|
|
458
|
+
},
|
|
459
|
+
'/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF/service0001/char0002': {
|
|
460
|
+
'org.bluez.GattCharacteristic1': {
|
|
461
|
+
UUID: '00002a37-0000-1000-8000-00805f9b34fb',
|
|
462
|
+
Flags: ['read', 'notify']
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
const variants = [
|
|
468
|
+
['canonical id', 'aabbccddeeff'],
|
|
469
|
+
['uppercase id', 'AABBCCDDEEFF'],
|
|
470
|
+
['mixed-case id', 'AaBbCcDdEeFf'],
|
|
471
|
+
['colon MAC uppercase', 'AA:BB:CC:DD:EE:FF'],
|
|
472
|
+
['colon MAC lowercase', 'aa:bb:cc:dd:ee:ff'],
|
|
473
|
+
['colon MAC mixed', 'Aa:Bb:Cc:Dd:Ee:Ff']
|
|
474
|
+
];
|
|
475
|
+
|
|
476
|
+
test.each(variants)('discoverServices accepts %s and emits canonical id', async (_label, input) => {
|
|
477
|
+
resetState(tree());
|
|
478
|
+
const bindings = new DbusBindings();
|
|
479
|
+
bindings.start();
|
|
480
|
+
await flush();
|
|
481
|
+
|
|
482
|
+
const services = [];
|
|
483
|
+
bindings.on('servicesDiscover', (...a) => services.push(a));
|
|
484
|
+
|
|
485
|
+
bindings.discoverServices(input, []);
|
|
486
|
+
|
|
487
|
+
expect(services[0]).toEqual(['aabbccddeeff', ['180d']]);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
test.each(variants)('read accepts %s and emits canonical id', async (_label, input) => {
|
|
491
|
+
resetState(tree());
|
|
492
|
+
const bindings = new DbusBindings();
|
|
493
|
+
bindings.start();
|
|
494
|
+
await flush();
|
|
495
|
+
|
|
496
|
+
const reads = [];
|
|
497
|
+
bindings.on('read', (...a) => reads.push(a));
|
|
498
|
+
|
|
499
|
+
bindings.read(input, '180d', '2a37');
|
|
500
|
+
await flush();
|
|
501
|
+
|
|
502
|
+
expect(reads.length).toBe(1);
|
|
503
|
+
expect(reads[0][0]).toBe('aabbccddeeff');
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
test.each(variants)('readHandle (unsupported) emits canonical id for %s', (_label, input) => {
|
|
507
|
+
resetState(tree());
|
|
508
|
+
const bindings = new DbusBindings();
|
|
509
|
+
const events = [];
|
|
510
|
+
bindings.on('handleRead', (...a) => events.push(a));
|
|
511
|
+
bindings.readHandle(input, 0x42);
|
|
512
|
+
expect(events[0][0]).toBe('aabbccddeeff');
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
test('null/undefined peripheralUuid does not throw', () => {
|
|
516
|
+
const bindings = new DbusBindings();
|
|
517
|
+
expect(() => bindings.discoverServices(undefined, [])).not.toThrow();
|
|
518
|
+
expect(() => bindings.discoverServices(null, [])).not.toThrow();
|
|
519
|
+
});
|
|
520
|
+
});
|
|
412
521
|
});
|