@stoprocent/noble 1.13.6 → 1.14.1
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/README.md +49 -0
- package/examples/connect-address.js +40 -0
- package/index.d.ts +3 -1
- package/lib/hci-socket/bindings.js +34 -3
- package/lib/mac/src/ble_manager.h +1 -0
- package/lib/mac/src/ble_manager.mm +18 -1
- package/lib/noble.js +20 -1
- package/package.json +10 -10
- package/prebuilds/darwin-x64+arm64/@stoprocent+noble.node +0 -0
- package/prebuilds/linux-x64/{node.napi.musl.node → @stoprocent+noble.musl.node} +0 -0
- package/prebuilds/win32-ia32/{node.napi.node → @stoprocent+noble.node} +0 -0
- package/prebuilds/win32-x64/{node.napi.node → @stoprocent+noble.node} +0 -0
- package/test/lib/hci-socket/bindings.test.js +3 -3
- package/examples/pizza/README.md +0 -15
- package/prebuilds/darwin-x64+arm64/node.napi.node +0 -0
- /package/prebuilds/android-arm/{node.napi.armv7.node → @stoprocent+noble.armv7.node} +0 -0
- /package/prebuilds/android-arm64/{node.napi.armv8.node → @stoprocent+noble.armv8.node} +0 -0
- /package/prebuilds/linux-arm/{node.napi.armv6.node → @stoprocent+noble.armv6.node} +0 -0
- /package/prebuilds/linux-arm/{node.napi.armv7.node → @stoprocent+noble.armv7.node} +0 -0
- /package/prebuilds/linux-arm64/{node.napi.armv8.node → @stoprocent+noble.armv8.node} +0 -0
- /package/prebuilds/linux-x64/{node.napi.glibc.node → @stoprocent+noble.glibc.node} +0 -0
package/README.md
CHANGED
|
@@ -245,6 +245,7 @@ API structure:
|
|
|
245
245
|
* [_Event: Scanning started_](#event-scanning-started)
|
|
246
246
|
* [Stop scanning](#stop-scanning)
|
|
247
247
|
* [_Event: Scanning stopped_](#event-scanning-stopped)
|
|
248
|
+
* [Connect by UUID / Address](#connect-by-uuid)
|
|
248
249
|
* [_Event: Peripheral discovered_](#event-peripheral-discovered)
|
|
249
250
|
* [_Event: Warning raised_](#event-warning-raised)
|
|
250
251
|
* [Reset device](#reset-device)
|
|
@@ -355,6 +356,54 @@ The event is emitted when:
|
|
|
355
356
|
* Scanning is stopped
|
|
356
357
|
* Another application stops scanning
|
|
357
358
|
|
|
359
|
+
#### Connect by UUID
|
|
360
|
+
|
|
361
|
+
The `connect` function is used to establish a Bluetooth Low Energy connection to a peripheral device using its UUID. It provides both callback-based and Promise-based interfaces.
|
|
362
|
+
|
|
363
|
+
##### Usage
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
// Callback-based usage
|
|
367
|
+
connect(peripheralUuid: string, options?: object, callback?: (error?: Error, peripheral: Peripheral) => void): void;
|
|
368
|
+
|
|
369
|
+
// Promise-based usage
|
|
370
|
+
connectAsync(peripheralUuid: string, options?: object): Promise<Peripheral>;
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
##### Parameters
|
|
374
|
+
- `peripheralUuid`: The UUID of the peripheral to connect to.
|
|
375
|
+
- `options`: Optional parameters for the connection (this may include connection interval, latency, supervision timeout, etc.).
|
|
376
|
+
- `callback`: An optional callback that returns an error or the connected peripheral object.
|
|
377
|
+
|
|
378
|
+
##### Description
|
|
379
|
+
The `connect` function initiates a connection to a BLE peripheral. The function immediately returns, and the actual connection result is provided asynchronously via the callback or Promise. If the peripheral is successfully connected, a `Peripheral` object representing the connected device is provided.
|
|
380
|
+
|
|
381
|
+
##### Example
|
|
382
|
+
|
|
383
|
+
```javascript
|
|
384
|
+
const noble = require('@stoprocent/noble');
|
|
385
|
+
|
|
386
|
+
// Using callback
|
|
387
|
+
noble.connect('1234567890abcdef', {}, (error, peripheral) => {
|
|
388
|
+
if (error) {
|
|
389
|
+
console.error('Connection error:', error);
|
|
390
|
+
} else {
|
|
391
|
+
console.log('Connected to:', peripheral.uuid);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// Using async/await
|
|
396
|
+
async function connectPeripheral() {
|
|
397
|
+
try {
|
|
398
|
+
const peripheral = await noble.connectAsync('1234567890abcdef');
|
|
399
|
+
console.log('Connected to:', peripheral.uuid);
|
|
400
|
+
} catch (error) {
|
|
401
|
+
console.error('Connection error:', error);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
connectPeripheral();
|
|
405
|
+
```
|
|
406
|
+
|
|
358
407
|
|
|
359
408
|
#### _Event: Peripheral discovered_
|
|
360
409
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const noble = require('../index');
|
|
2
|
+
const direct = require('debug')('connection/direct');
|
|
3
|
+
const scan = require('debug')('connection/scan');
|
|
4
|
+
|
|
5
|
+
function sleep (ms) {
|
|
6
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function run () {
|
|
10
|
+
noble.on('stateChange', async function (state) {
|
|
11
|
+
if (state === 'poweredOn') {
|
|
12
|
+
try {
|
|
13
|
+
direct('connecting');
|
|
14
|
+
// const uuid = 'f1:36:1c:ab:94:cc'.split(':').join(''); // HCI Address UUID
|
|
15
|
+
const uuid = '2561b846d6f83ee27580bca8ed6ec079'; // MacOS UUID
|
|
16
|
+
const peripheral = await noble.connectAsync(uuid);
|
|
17
|
+
direct(`connected ${peripheral.uuid}`);
|
|
18
|
+
await peripheral.disconnectAsync();
|
|
19
|
+
direct('disconnected');
|
|
20
|
+
console.log('sleeping for 2000ms');
|
|
21
|
+
await sleep(2000);
|
|
22
|
+
scan('connecting by scan');
|
|
23
|
+
await noble.startScanningAsync();
|
|
24
|
+
noble.on('discover', async peripheral => {
|
|
25
|
+
if (peripheral.uuid === uuid) {
|
|
26
|
+
await noble.stopScanningAsync();
|
|
27
|
+
await peripheral.connectAsync();
|
|
28
|
+
scan(`connected ${peripheral.uuid}`);
|
|
29
|
+
await peripheral.disconnectAsync();
|
|
30
|
+
scan('disconnected');
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.log(error);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
run();
|
package/index.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export declare function startScanning(serviceUUIDs?: string[], allowDuplicates?:
|
|
|
25
25
|
export declare function startScanningAsync(serviceUUIDs?: string[], allowDuplicates?: boolean): Promise<void>;
|
|
26
26
|
export declare function stopScanning(callback?: () => void): void;
|
|
27
27
|
export declare function stopScanningAsync(): Promise<void>;
|
|
28
|
+
export declare function connect(peripheralUuid: string, options?: object, callback?: (error: Error | undefined, peripheral: Peripheral) => void): void;
|
|
29
|
+
export declare function connectAsync(peripheralUuid: string, options?: object): Promise<Peripheral>;
|
|
28
30
|
export declare function cancelConnect(peripheralUuid: string, options?: object): void;
|
|
29
31
|
export declare function reset(): void;
|
|
30
32
|
|
|
@@ -50,7 +52,7 @@ export declare function removeListener(event: string, listener: Function): event
|
|
|
50
52
|
|
|
51
53
|
export declare function removeAllListeners(event?: string): events.EventEmitter;
|
|
52
54
|
|
|
53
|
-
export
|
|
55
|
+
export var _state: "unknown" | "resetting" | "unsupported" | "unauthorized" | "poweredOff" | "poweredOn";
|
|
54
56
|
|
|
55
57
|
export var _bindings: any;
|
|
56
58
|
|
|
@@ -52,14 +52,20 @@ NobleBindings.prototype.stopScanning = function () {
|
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
NobleBindings.prototype.connect = function (peripheralUuid, parameters) {
|
|
55
|
-
|
|
56
|
-
const addressType = this._addresseTypes[peripheralUuid];
|
|
55
|
+
let address = this._addresses[peripheralUuid];
|
|
56
|
+
const addressType = this._addresseTypes[peripheralUuid] || 'random'; // Default to 'random' if type is not defined
|
|
57
57
|
|
|
58
|
+
// If address is not available, generate it from the UUID using the transformation logic inline
|
|
59
|
+
if (!address) {
|
|
60
|
+
address = peripheralUuid.match(/.{1,2}/g).join(':'); // Converts UUID back to MAC address format
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Manage connection attempts
|
|
58
64
|
if (!this._pendingConnectionUuid) {
|
|
59
65
|
this._pendingConnectionUuid = peripheralUuid;
|
|
60
|
-
|
|
61
66
|
this._hci.createLeConn(address, addressType, parameters);
|
|
62
67
|
} else {
|
|
68
|
+
// If there is already a pending connection, queue this one
|
|
63
69
|
this._connectionQueue.push({ id: peripheralUuid, params: parameters });
|
|
64
70
|
}
|
|
65
71
|
};
|
|
@@ -249,6 +255,31 @@ NobleBindings.prototype.onLeConnComplete = function (
|
|
|
249
255
|
if (status === 0) {
|
|
250
256
|
uuid = address.split(':').join('').toLowerCase();
|
|
251
257
|
|
|
258
|
+
// Check if address is already known
|
|
259
|
+
if (!this._addresses[uuid]) {
|
|
260
|
+
// Simulate discovery if address is not known
|
|
261
|
+
const advertisement = { // Assume structure, adjust as necessary
|
|
262
|
+
serviceUuids: [], // Actual service UUID data needed here
|
|
263
|
+
serviceData: [] // Actual service data needed here
|
|
264
|
+
};
|
|
265
|
+
const rssi = 127;
|
|
266
|
+
const connectable = true; // Assuming the device is connectable
|
|
267
|
+
const scannable = false; // Assuming the device is not scannable
|
|
268
|
+
|
|
269
|
+
this._scanServiceUuids = []; // We have to set this to fake scan
|
|
270
|
+
|
|
271
|
+
// Call onDiscover to simulate device discovery
|
|
272
|
+
this.onDiscover(
|
|
273
|
+
status,
|
|
274
|
+
address,
|
|
275
|
+
addressType,
|
|
276
|
+
connectable,
|
|
277
|
+
advertisement,
|
|
278
|
+
rssi,
|
|
279
|
+
scannable
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
252
283
|
const aclStream = new AclStream(
|
|
253
284
|
this._hci,
|
|
254
285
|
handle,
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
@property (strong) CBCentralManager *centralManager;
|
|
21
21
|
@property dispatch_queue_t dispatchQueue;
|
|
22
22
|
@property NSMutableDictionary *peripherals;
|
|
23
|
+
@property NSMutableSet *discovered;
|
|
23
24
|
|
|
24
25
|
- (instancetype)init: (const Napi::Value&) receiver with: (const Napi::Function&) callback;
|
|
25
26
|
- (void)scan: (NSArray<NSString*> *)serviceUUIDs allowDuplicates: (BOOL)allowDuplicates;
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
self->emit.Wrap(receiver, callback);
|
|
19
19
|
self.dispatchQueue = dispatch_queue_create("CBqueue", 0);
|
|
20
20
|
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:self.dispatchQueue];
|
|
21
|
+
self.discovered = [NSMutableSet set];
|
|
21
22
|
self.peripherals = [NSMutableDictionary dictionaryWithCapacity:10];
|
|
22
23
|
}
|
|
23
24
|
return self;
|
|
@@ -40,11 +41,13 @@
|
|
|
40
41
|
|
|
41
42
|
- (void)stopScan {
|
|
42
43
|
[self.centralManager stopScan];
|
|
44
|
+
[self.discovered removeAllObjects];
|
|
43
45
|
emit.ScanState(false);
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
- (void) centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
|
|
47
49
|
std::string uuid = getUuid(peripheral);
|
|
50
|
+
[self.discovered addObject:getNSUuid(peripheral)];
|
|
48
51
|
|
|
49
52
|
Peripheral p;
|
|
50
53
|
p.address = getAddress(uuid, &p.addressType);
|
|
@@ -93,7 +96,11 @@
|
|
|
93
96
|
- (BOOL)connect:(NSString*) uuid {
|
|
94
97
|
CBPeripheral *peripheral = [self.peripherals objectForKey:uuid];
|
|
95
98
|
if(!peripheral) {
|
|
96
|
-
|
|
99
|
+
NSUUID *identifier = [[NSUUID alloc] initWithUUIDString:uuid];
|
|
100
|
+
if (!identifier) {
|
|
101
|
+
return NO;
|
|
102
|
+
}
|
|
103
|
+
NSArray* peripherals = [self.centralManager retrievePeripheralsWithIdentifiers:@[identifier]];
|
|
97
104
|
peripheral = [peripherals firstObject];
|
|
98
105
|
if(peripheral) {
|
|
99
106
|
peripheral.delegate = self;
|
|
@@ -108,6 +115,16 @@
|
|
|
108
115
|
}
|
|
109
116
|
|
|
110
117
|
- (void) centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
|
|
118
|
+
// Check if peripheral was known
|
|
119
|
+
if ([self.discovered containsObject:getNSUuid(peripheral)] == false) {
|
|
120
|
+
// The peripheral was connected without being discovered by this app instance
|
|
121
|
+
// Optionally simulate discovery using dummy or last known advertisement data and RSSI
|
|
122
|
+
NSDictionary<NSString *, id> *advertisementData = @{ }; // Placeholder, use actual last known data if available
|
|
123
|
+
NSNumber *RSSI = @127; // Placeholder RSSI, use actual last known value if available
|
|
124
|
+
|
|
125
|
+
// Simulate discovery handling
|
|
126
|
+
[self centralManager:central didDiscoverPeripheral:peripheral advertisementData:advertisementData RSSI:RSSI];
|
|
127
|
+
}
|
|
111
128
|
std::string uuid = getUuid(peripheral);
|
|
112
129
|
emit.Connected(uuid, "");
|
|
113
130
|
}
|
package/lib/noble.js
CHANGED
|
@@ -227,15 +227,34 @@ Noble.prototype.onDiscover = function (uuid, address, addressType, connectable,
|
|
|
227
227
|
}
|
|
228
228
|
};
|
|
229
229
|
|
|
230
|
-
Noble.prototype.connect = function (peripheralUuid, parameters) {
|
|
230
|
+
Noble.prototype.connect = function (peripheralUuid, parameters, callback) {
|
|
231
|
+
// Check if callback is a function
|
|
232
|
+
if (typeof callback === 'function') {
|
|
233
|
+
// Create a unique event name using the peripheral UUID
|
|
234
|
+
const eventName = `connect:${peripheralUuid}`;
|
|
235
|
+
|
|
236
|
+
// Add a one-time listener for this specific event
|
|
237
|
+
this.once(eventName, (error) => {
|
|
238
|
+
callback(error, this._peripherals[peripheralUuid]);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Proceed to initiate the connection
|
|
231
243
|
this._bindings.connect(peripheralUuid, parameters);
|
|
232
244
|
};
|
|
245
|
+
Noble.prototype.connectAsync = function (peripheralUuid, parameters) {
|
|
246
|
+
return util.promisify((callback) => this.connect(peripheralUuid, parameters, callback))();
|
|
247
|
+
};
|
|
233
248
|
|
|
234
249
|
Noble.prototype.onConnect = function (peripheralUuid, error) {
|
|
235
250
|
const peripheral = this._peripherals[peripheralUuid];
|
|
236
251
|
|
|
237
252
|
if (peripheral) {
|
|
253
|
+
// Emit a unique connect event for the specific peripheral
|
|
254
|
+
this.emit(`connect:${peripheralUuid}`, error);
|
|
255
|
+
|
|
238
256
|
peripheral.state = error ? 'error' : 'connected';
|
|
257
|
+
// Also emit the general 'connect' event for a peripheral
|
|
239
258
|
peripheral.emit('connect', error);
|
|
240
259
|
} else {
|
|
241
260
|
this.emit('warning', `unknown peripheral ${peripheralUuid} connected!`);
|
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.
|
|
9
|
+
"version": "1.14.1",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
12
|
"url": "https://github.com/stoprocent/noble.git"
|
|
@@ -38,23 +38,23 @@
|
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@semantic-release/exec": "^6.0.3",
|
|
41
|
-
"async": "^3.2.
|
|
41
|
+
"async": "^3.2.5",
|
|
42
42
|
"cross-env": "^7.0.3",
|
|
43
|
-
"eslint": "^8
|
|
43
|
+
"eslint": "^8",
|
|
44
44
|
"eslint-config-semistandard": "^17.0.0",
|
|
45
45
|
"eslint-config-standard": "^17.0.0",
|
|
46
|
-
"eslint-plugin-import": "^2.
|
|
46
|
+
"eslint-plugin-import": "^2.29.1",
|
|
47
47
|
"eslint-plugin-n": "^15.6.0",
|
|
48
|
-
"eslint-plugin-promise": "^6.
|
|
49
|
-
"mocha": "^10.
|
|
48
|
+
"eslint-plugin-promise": "^6.2.0",
|
|
49
|
+
"mocha": "^10.4.0",
|
|
50
50
|
"node-gyp": "^10.0.0",
|
|
51
51
|
"nyc": "^15.1.0",
|
|
52
|
-
"prebuildify": "^
|
|
53
|
-
"prebuildify-cross": "5.
|
|
52
|
+
"prebuildify": "^6.0.1",
|
|
53
|
+
"prebuildify-cross": "^5.1.0",
|
|
54
54
|
"prettier": "^2.8.1",
|
|
55
55
|
"proxyquire": "^2.1.3",
|
|
56
|
-
"semantic-release": "
|
|
57
|
-
"should": "
|
|
56
|
+
"semantic-release": "^23.1.1",
|
|
57
|
+
"should": "^13.2.3",
|
|
58
58
|
"sinon": "^15.0.1",
|
|
59
59
|
"ws": "^8.11.0"
|
|
60
60
|
},
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -135,12 +135,12 @@ describe('hci-socket bindings', () => {
|
|
|
135
135
|
it('missing peripheral, no queue', () => {
|
|
136
136
|
bindings._hci.createLeConn = fake.resolves(null);
|
|
137
137
|
|
|
138
|
-
bindings.connect('
|
|
138
|
+
bindings.connect('112233445566', 'parameters');
|
|
139
139
|
|
|
140
|
-
should(bindings._pendingConnectionUuid).eql('
|
|
140
|
+
should(bindings._pendingConnectionUuid).eql('112233445566');
|
|
141
141
|
|
|
142
142
|
assert.calledOnce(bindings._hci.createLeConn);
|
|
143
|
-
assert.calledWith(bindings._hci.createLeConn,
|
|
143
|
+
assert.calledWith(bindings._hci.createLeConn, '11:22:33:44:55:66', 'random', 'parameters');
|
|
144
144
|
});
|
|
145
145
|
|
|
146
146
|
it('existing peripheral, no queue', () => {
|
package/examples/pizza/README.md
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# BLE Pizza Service
|
|
2
|
-
|
|
3
|
-
This is an example program demonstrating BLE connectivity between a peripheral running bleno, and a central running noble.
|
|
4
|
-
|
|
5
|
-
This central connects to a robotic pizza oven service, with the following characteristics:
|
|
6
|
-
|
|
7
|
-
* crust - read / write. A value representing the type of pizza crust (normal, thin, or deep dish)
|
|
8
|
-
* toppings - read / write. A value representing which toppings to include (pepperoni, mushrooms, extra cheese, etc.)
|
|
9
|
-
* bake - write / notify. The value written is the temperature at which to bake the pizza. When baking is finished, the central is notified with a bake result (half baked, crispy, burnt, etc.)
|
|
10
|
-
|
|
11
|
-
To run the central example:
|
|
12
|
-
|
|
13
|
-
node central
|
|
14
|
-
|
|
15
|
-
And on another computer, start advertising a peripheral with [bleno](https://github.com/sandeepmistry/bleno/tree/master/examples/pizza).
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|