@stoprocent/noble 1.13.6 → 1.14.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/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, 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 declare var state: string;
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
- const address = this._addresses[peripheralUuid];
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
- NSArray* peripherals = [self.centralManager retrievePeripheralsWithIdentifiers:@[[[NSUUID alloc] initWithUUIDString:uuid]]];
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.13.6",
9
+ "version": "1.14.0",
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.4",
41
+ "async": "^3.2.5",
42
42
  "cross-env": "^7.0.3",
43
- "eslint": "^8.31.0",
43
+ "eslint": "^8",
44
44
  "eslint-config-semistandard": "^17.0.0",
45
45
  "eslint-config-standard": "^17.0.0",
46
- "eslint-plugin-import": "^2.26.0",
46
+ "eslint-plugin-import": "^2.29.1",
47
47
  "eslint-plugin-n": "^15.6.0",
48
- "eslint-plugin-promise": "^6.1.1",
49
- "mocha": "^10.2.0",
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": "^5.0.1",
53
- "prebuildify-cross": "5.0.0",
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": "21.1.0",
57
- "should": "~13.2.3",
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
  },
@@ -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('peripheralUuid', 'parameters');
138
+ bindings.connect('112233445566', 'parameters');
139
139
 
140
- should(bindings._pendingConnectionUuid).eql('peripheralUuid');
140
+ should(bindings._pendingConnectionUuid).eql('112233445566');
141
141
 
142
142
  assert.calledOnce(bindings._hci.createLeConn);
143
- assert.calledWith(bindings._hci.createLeConn, undefined, undefined, 'parameters');
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', () => {
@@ -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).