@stoprocent/noble 1.19.1 → 2.0.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.
Files changed (97) hide show
  1. package/README.md +393 -650
  2. package/examples/advertisement-discovery.js +57 -48
  3. package/examples/connect-address.js +59 -34
  4. package/examples/echo.js +59 -69
  5. package/examples/enter-exit.js +55 -49
  6. package/examples/multiple-bindings.js +53 -0
  7. package/examples/peripheral-explorer-async.js +39 -21
  8. package/examples/peripheral-explorer.ts +52 -0
  9. package/index.d.ts +249 -209
  10. package/index.js +4 -1
  11. package/jest.config.js +4 -0
  12. package/lib/characteristic.js +153 -127
  13. package/lib/{win/src/callbacks.h → common/include/Emit.h} +17 -14
  14. package/lib/common/include/Peripheral.h +31 -0
  15. package/lib/common/include/ThreadSafeCallback.h +95 -0
  16. package/lib/{win/src/callbacks.cc → common/src/Emit.cc} +111 -68
  17. package/lib/descriptor.js +57 -54
  18. package/lib/hci-socket/acl-stream.js +2 -4
  19. package/lib/hci-socket/bindings.js +96 -73
  20. package/lib/hci-socket/gap.js +2 -3
  21. package/lib/hci-socket/gatt.js +2 -5
  22. package/lib/hci-socket/hci.js +19 -7
  23. package/lib/hci-socket/signaling.js +2 -3
  24. package/lib/hci-socket/smp.js +2 -3
  25. package/lib/hci-socket/vs.js +1 -0
  26. package/lib/mac/binding.gyp +5 -7
  27. package/lib/mac/bindings.js +1 -3
  28. package/lib/mac/src/ble_manager.h +1 -8
  29. package/lib/mac/src/ble_manager.mm +87 -44
  30. package/lib/mac/src/napi_objc.h +1 -0
  31. package/lib/mac/src/napi_objc.mm +0 -6
  32. package/lib/mac/src/noble_mac.h +5 -3
  33. package/lib/mac/src/noble_mac.mm +99 -57
  34. package/lib/mac/src/objc_cpp.h +3 -2
  35. package/lib/mac/src/objc_cpp.mm +0 -6
  36. package/lib/noble.js +579 -488
  37. package/lib/peripheral.js +171 -174
  38. package/lib/resolve-bindings.js +37 -30
  39. package/lib/service.js +58 -55
  40. package/lib/win/binding.gyp +4 -11
  41. package/lib/win/bindings.js +1 -3
  42. package/lib/win/src/ble_manager.cc +291 -166
  43. package/lib/win/src/ble_manager.h +11 -13
  44. package/lib/win/src/napi_winrt.cc +1 -7
  45. package/lib/win/src/napi_winrt.h +1 -1
  46. package/lib/win/src/noble_winrt.cc +88 -61
  47. package/lib/win/src/noble_winrt.h +5 -3
  48. package/lib/win/src/notify_map.cc +0 -7
  49. package/lib/win/src/notify_map.h +1 -8
  50. package/lib/win/src/peripheral_winrt.cc +29 -11
  51. package/lib/win/src/peripheral_winrt.h +1 -1
  52. package/lib/win/src/radio_watcher.cc +79 -69
  53. package/lib/win/src/radio_watcher.h +30 -11
  54. package/lib/win/src/winrt_cpp.cc +1 -1
  55. package/lib/win/src/winrt_cpp.h +3 -0
  56. package/package.json +14 -17
  57. package/prebuilds/darwin-x64+arm64/@stoprocent+noble.node +0 -0
  58. package/prebuilds/win32-ia32/@stoprocent+noble.node +0 -0
  59. package/prebuilds/win32-x64/@stoprocent+noble.node +0 -0
  60. package/test/lib/characteristic.test.js +202 -322
  61. package/test/lib/descriptor.test.js +62 -95
  62. package/test/lib/hci-socket/acl-stream.test.js +112 -108
  63. package/test/lib/hci-socket/bindings.test.js +576 -365
  64. package/test/lib/hci-socket/hci.test.js +442 -473
  65. package/test/lib/hci-socket/signaling.test.js +45 -48
  66. package/test/lib/hci-socket/smp.test.js +144 -142
  67. package/test/lib/hci-socket/vs.test.js +193 -18
  68. package/test/lib/peripheral.test.js +492 -322
  69. package/test/lib/resolve-bindings.test.js +207 -82
  70. package/test/lib/service.test.js +79 -88
  71. package/test/noble.test.js +381 -1085
  72. package/.editorconfig +0 -11
  73. package/.nycrc.json +0 -4
  74. package/codecov.yml +0 -5
  75. package/examples/cache-gatt-discovery.js +0 -198
  76. package/examples/cache-gatt-reconnect.js +0 -164
  77. package/examples/ext-advertisement-discovery.js +0 -65
  78. package/examples/peripheral-explorer.js +0 -225
  79. package/examples/pizza/central.js +0 -194
  80. package/examples/pizza/pizza.js +0 -60
  81. package/examples/test/test.custom.js +0 -131
  82. package/examples/uart-bind-params.js +0 -28
  83. package/lib/distributed/bindings.js +0 -326
  84. package/lib/mac/src/callbacks.cc +0 -222
  85. package/lib/mac/src/callbacks.h +0 -84
  86. package/lib/mac/src/peripheral.h +0 -23
  87. package/lib/resolve-bindings-web.js +0 -9
  88. package/lib/webbluetooth/bindings.js +0 -368
  89. package/lib/websocket/bindings.js +0 -321
  90. package/lib/win/src/peripheral.h +0 -23
  91. package/test/lib/distributed/bindings.test.js +0 -918
  92. package/test/lib/webbluetooth/bindings.test.js +0 -190
  93. package/test/lib/websocket/bindings.test.js +0 -456
  94. package/test/mocha.setup.js +0 -0
  95. package/with-bindings.js +0 -5
  96. package/with-custom-binding.js +0 -6
  97. package/ws-slave.js +0 -404
package/README.md CHANGED
@@ -2,27 +2,44 @@
2
2
 
3
3
  [![npm version](https://badgen.net/npm/v/@stoprocent/noble)](https://www.npmjs.com/package/@stoprocent/noble)
4
4
  [![npm downloads](https://badgen.net/npm/dt/@stoprocent/noble)](https://www.npmjs.com/package/@stoprocent/noble)
5
- [![Build Status](https://travis-ci.org/stoprocent/noble.svg?branch=master)](https://travis-ci.org/stoprocent/noble)
5
+
6
6
 
7
7
  A Node.js BLE (Bluetooth Low Energy) central module.
8
8
 
9
9
  Want to implement a peripheral? Check out [@stoprocent/bleno](https://github.com/stoprocent/bleno).
10
10
 
11
+ __NOTE__: Currently, running both noble (central) and bleno (peripheral) together only works with macOS bindings or when using separate HCI/UART dongles. Support for running both on a single HCI adapter (e.g., on Linux systems) will be added in future releases.
12
+
11
13
  ## About This Fork
12
14
 
13
15
  This fork of `noble` was created to introduce several key improvements and new features:
14
16
 
15
- 1. **HCI UART Support**: This version enables HCI UART communication through the `@stoprocent/node-bluetooth-hci-socket` dependency, allowing more flexible use of Bluetooth devices across platforms.
17
+ 1. **Flexible Bluetooth Driver Selection**:
18
+ - This library enables flexible selection of Bluetooth drivers through the new `withBindings()` API. Use native platform bindings (Mac, Windows) or HCI bindings with UART/serial support for hardware dongles, allowing Bluetooth connectivity across various platforms and hardware setups.
16
19
 
17
- 2. **macOS Native Bindings Fix**: I have fixed the native bindings for macOS, ensuring better compatibility and performance on Apple devices.
20
+ 2. **Native Bindings Improvements**:
21
+ - Fixed and optimized native bindings for macOS, ensuring better compatibility and performance on Apple devices
22
+ - Overhauled Windows native bindings with support for `Service Data` from advertisements
23
+ - Aligned behavior across different bindings (macOS, Windows, HCI) for consistent behavior
24
+
25
+ 3. **Modern JavaScript Support**:
26
+ - Added full Promise-based API with async/await support throughout the library
27
+ - Implemented async iterators for device discovery with `for await...of` syntax
28
+ - Refactored codebase to use modern JavaScript patterns and best practices
29
+
30
+ 4. **Enhanced Testing and Reliability**:
31
+ - Migrated tests to Jest for improved coverage and reliability
32
+ - Added comprehensive TypeScript type definitions
33
+ - Fixed numerous edge cases and stability issues
34
+
35
+ 5. **New Features**:
36
+ - A `setAddress(...)` function to set the MAC address of the central device
37
+ - Direct device connection with `connect(...)/connectAsync(...)` without requiring a prior scan
38
+ - `waitForPoweredOnAsync(...)` function to simplify async workflows
39
+ - Support for multiple adapter configurations through the new `withBindings()` API
40
+ - Extended debugging capabilities and error handling
41
+ - Additionally, I plan to add raw L2CAP channel support, enhancing low-level Bluetooth communication capabilities
18
42
 
19
- 3. **Windows Native Bindings Fix**: I have fixed the native bindings for Windows, adding support for `Service Data` from advertisements.
20
-
21
- 4. **New Features**:
22
- - A `setAddress(...)` function has been added, allowing users to set the MAC address of the central device.
23
- - A `connect(...)/connectAsync(...)` function has been added, allowing users to connect directly to specific device by address/identifier without a need to prior scan.
24
- - A `waitForPoweredOn(...)` function to wait for the adapter to be powered on in await/async functions.
25
- - Additionally, I plan to add raw L2CAP channel support, enhancing low-level Bluetooth communication capabilities.
26
43
 
27
44
  If you appreciate these enhancements and the continued development of this project, please consider supporting my work.
28
45
 
@@ -36,53 +53,338 @@ npm install @stoprocent/noble
36
53
 
37
54
  ## Usage
38
55
 
56
+ ### TypeScript (Recommended)
57
+
58
+ ```typescript
59
+ // Auto-select based on platform
60
+ import noble from '@stoprocent/noble';
61
+ // or
62
+ import { withBindings } from '@stoprocent/noble';
63
+ // Auto-select based on platform
64
+ const noble = withBindings('default'); // 'hci', 'win', 'mac'
65
+ ```
66
+
67
+ For more detailed examples and API documentation, see [Binding Types](#Binding-Types) below.
68
+
69
+ ### JavaScript
70
+
39
71
  ```javascript
40
72
  const noble = require('@stoprocent/noble');
73
+ // or
74
+ const { withBindings } = require('@stoprocent/noble');
75
+ const noble = withBindings('default'); // 'hci', 'win', 'mac'
41
76
  ```
42
77
 
43
- ## Documentation
44
-
45
- * [Quick Start Example](#quick-start-example)
46
- * [Installation](#installation)
47
- * [API docs](#api-docs)
48
- * [Advanced usage](#advanced-usage)
49
- * [Common problems](#common-problems)
50
78
 
51
79
  ## Quick Start Example
52
80
 
81
+ ### TypeScript Example (Modern Async/Await)
82
+
83
+ #### Basic Scan
84
+
85
+ ```typescript
86
+ import noble from '@stoprocent/noble';
87
+
88
+ // Discover peripherals as an async generator
89
+ try {
90
+ // Wait for Adapter poweredOn state
91
+ await noble.waitForPoweredOnAsync();
92
+ // Start scanning first
93
+ await noble.startScanningAsync();
94
+
95
+ // Use the async generator with proper boundaries
96
+ for await (const peripheral of noble.discoverAsync()) {
97
+ console.log(`Found device: ${peripheral.advertisement.localName || 'Unknown'}`);
98
+ // Process the peripheral as needed
99
+
100
+ // Optional: stop scanning when a specific device is found
101
+ if (peripheral.advertisement.localName === 'MyDevice') {
102
+ break;
103
+ }
104
+ }
105
+
106
+ // Clean up after discovery
107
+ await noble.stopScanningAsync();
108
+ } catch (error) {
109
+ console.error('Discovery error:', error);
110
+ await noble.stopScanningAsync();
111
+ }
112
+ ```
113
+
114
+ For a more detailed example, please check out [examples/peripheral-explorer.ts](examples/peripheral-explorer.ts)
115
+
116
+ Alternatively, you can still use the legacy event-based API:
117
+
118
+ ``` javascript
119
+ const noble = require('@stoprocent/noble');
120
+
121
+ // State change event is emitted when adapter state changes
122
+ noble.on('stateChange', function (state) {
123
+ if (state === 'poweredOn') {
124
+ // Start scanning when adapter is ready
125
+ noble.startScanning();
126
+ } else {
127
+ // Stop scanning if adapter becomes unavailable
128
+ noble.stopScanning();
129
+ }
130
+ });
131
+
132
+ // Discover event is emitted when a peripheral is found
133
+ noble.on('discover', peripheral => {
134
+ console.log(peripheral);
135
+ // From here you can work with the peripheral:
136
+ // - Connect to it: peripheral.connect()
137
+ // - Check advertisement data: peripheral.advertisement
138
+ // - See signal strength: peripheral.rssi
139
+ });
140
+ ```
141
+
142
+ #### Connecting to the device
143
+
144
+ ``` typescript
145
+ // Stop scan
146
+ await noble.stopScanningAsync();
147
+ // Connect
148
+ await peripheral.connectAsync();
149
+ // Discover
150
+ const { services, characteristics } = await peripheral.discoverAllServicesAndCharacteristicsAsync();
151
+ ```
152
+
153
+ #### Working with Services and Characteristics
154
+
155
+ ```typescript
156
+ async function exploreServices(peripheral) {
157
+ // Discover all services and characteristics at once
158
+ const { services } = await peripheral.discoverAllServicesAndCharacteristicsAsync();
159
+
160
+ const results = [];
161
+
162
+ for (const service of services) {
163
+ const serviceInfo = {
164
+ uuid: service.uuid,
165
+ characteristics: []
166
+ };
167
+
168
+ for (const characteristic of service.characteristics) {
169
+ const characteristicInfo = {
170
+ uuid: characteristic.uuid,
171
+ properties: characteristic.properties
172
+ };
173
+
174
+ // Read the characteristic if it's readable
175
+ if (characteristic.properties.includes('read')) {
176
+ characteristicInfo.value = await characteristic.readAsync();
177
+ }
178
+
179
+ serviceInfo.characteristics.push(characteristicInfo);
180
+ }
181
+
182
+ results.push(serviceInfo);
183
+ }
184
+
185
+ return results;
186
+ }
187
+ ```
188
+
189
+ #### Reading and Writing Data
190
+
191
+ ```typescript
192
+ async function readBatteryLevel(peripheral) {
193
+ // Get battery service (0x180F is the standard UUID for Battery Service)
194
+ const { characteristics } = await peripheral.discoverSomeServicesAndCharacteristicsAsync(
195
+ ['180f'], // Battery Service
196
+ ['2a19'] // Battery Level Characteristic
197
+ );
198
+
199
+ if (characteristics.length > 0) {
200
+ const data = await characteristics[0].readAsync();
201
+ return data[0]; // Battery percentage
202
+ }
203
+
204
+ return null;
205
+ }
206
+
207
+ async function writeCharacteristic(peripheral, serviceUuid, characteristicUuid, data) {
208
+ const { characteristics } = await peripheral.discoverSomeServicesAndCharacteristicsAsync(
209
+ [serviceUuid],
210
+ [characteristicUuid]
211
+ );
212
+
213
+ if (characteristics.length > 0) {
214
+ // false = with response, true = without response
215
+ const requiresResponse = !characteristics[0].properties.includes('writeWithoutResponse');
216
+ await characteristics[0].writeAsync(data, !requiresResponse);
217
+ return true;
218
+ }
219
+
220
+ return false;
221
+ }
222
+ ```
223
+
224
+ ### JavaScript Example (Battery Level)
225
+
53
226
  ```javascript
227
+ const { withBindings } = require('@stoprocent/noble');
228
+
54
229
  // Read the battery level of the first found peripheral exposing the Battery Level characteristic
55
- const noble = require('../');
230
+ async function readBatteryLevel() {
231
+ const noble = withBindings('default');
56
232
 
57
- async function run() {
58
233
  try {
59
- await noble.waitForPoweredOn();
234
+ await noble.waitForPoweredOnAsync();
60
235
  await noble.startScanningAsync(['180f'], false);
236
+
237
+ noble.on('discover', async (peripheral) => {
238
+ await noble.stopScanningAsync();
239
+ await peripheral.connectAsync();
240
+
241
+ const { characteristics } = await peripheral.discoverSomeServicesAndCharacteristicsAsync(['180f'], ['2a19']);
242
+ const batteryLevel = (await characteristics[0].readAsync())[0];
243
+
244
+ console.log(`${peripheral.address} (${peripheral.advertisement.localName}): ${batteryLevel}%`);
245
+
246
+ await peripheral.disconnectAsync();
247
+ process.exit(0);
248
+ });
61
249
  } catch (error) {
62
250
  console.error(error);
63
251
  }
64
252
  }
65
253
 
66
- noble.on('discover', async (peripheral) => {
67
- await noble.stopScanningAsync();
68
- await peripheral.connectAsync();
69
- const {characteristics} = await peripheral.discoverSomeServicesAndCharacteristicsAsync(['180f'], ['2a19']);
70
- const batteryLevel = (await characteristics[0].readAsync())[0];
254
+ readBatteryLevel();
255
+ ```
256
+
257
+ ## API Overview
258
+
259
+ Noble provides both callback-based and Promise-based (Async) APIs:
71
260
 
72
- console.log(`${peripheral.address} (${peripheral.advertisement.localName}): ${batteryLevel}%`);
261
+ ### Binding Types
73
262
 
74
- await peripheral.disconnectAsync();
75
- process.exit(0);
263
+ ```typescript
264
+ // Default binding (automatically selects based on platform)
265
+ import noble from '@stoprocent/noble';
266
+ // or
267
+ import { withBindings } from '@stoprocent/noble';
268
+ const noble = withBindings('default');
269
+
270
+ // Specific bindings
271
+ const nobleHci = withBindings('hci'); // HCI socket binding
272
+ const nobleMac = withBindings('mac'); // macOS binding
273
+ const nobleWin = withBindings('win'); // Windows binding
274
+
275
+ // Custom options for HCI binding (Using UART HCI Dongle)
276
+ const nobleCustom = withBindings('hci', {
277
+ hciDriver: 'uart',
278
+ bindParams: {
279
+ uart: {
280
+ port: '/dev/ttyUSB0',
281
+ baudRate: 1000000
282
+ }
283
+ }
76
284
  });
77
285
 
78
- run();
286
+ // Custom options for HCI binding (Native)
287
+ const nobleCustom = withBindings('hci', {
288
+ hciDriver: 'native',
289
+ deviceId: 0 // This could be also set by env.NOBLE_HCI_DEVICE_ID=0
290
+ });
291
+ ```
292
+
293
+ ### Core Methods
294
+
295
+ ```typescript
296
+ // Wait for adapter to be powered on
297
+ await noble.waitForPoweredOnAsync(timeout?: number);
298
+
299
+ // Start scanning
300
+ await noble.startScanningAsync(serviceUUIDs?: string[], allowDuplicates?: boolean);
79
301
 
302
+ // Stop scanning
303
+ await noble.stopScanningAsync();
304
+
305
+ // Discover peripherals as an async generator
306
+ for await (const peripheral of noble.discoverAsync()) {
307
+ // handle each discovered peripheral
308
+ }
309
+
310
+ // Connect directly to a peripheral by ID or address
311
+ const peripheral = await noble.connectAsync(idOrAddress, options?);
312
+
313
+ // Set adapter address (HCI only on supported devices)
314
+ noble.setAddress('00:11:22:33:44:55');
315
+
316
+ // Reset adapter
317
+ noble.reset();
318
+
319
+ // Stop noble
320
+ noble.stop();
80
321
  ```
81
- ## Use Noble With BLE5 Extended Features With HCI
82
322
 
83
- ```javascript
84
- const noble = require('@stoprocent/noble/with-custom-binding')({extended: true});
323
+ ### Peripheral Methods
324
+
325
+ ```typescript
326
+ // Connect to peripheral
327
+ await peripheral.connectAsync();
328
+
329
+ // Disconnect from peripheral
330
+ await peripheral.disconnectAsync();
331
+
332
+ // Update RSSI
333
+ const rssi = await peripheral.updateRssiAsync();
334
+
335
+ // Discover services
336
+ const services = await peripheral.discoverServicesAsync(['180f']); // Optional service UUIDs
85
337
 
338
+ // Discover all services and characteristics
339
+ const { services, characteristics } = await peripheral.discoverAllServicesAndCharacteristicsAsync();
340
+
341
+ // Discover specific services and characteristics
342
+ const { services, characteristics } = await peripheral.discoverSomeServicesAndCharacteristicsAsync(
343
+ ['180f'], ['2a19']
344
+ );
345
+
346
+ // Read and write handles
347
+ const data = await peripheral.readHandleAsync(handle);
348
+ await peripheral.writeHandleAsync(handle, data, withoutResponse);
349
+ ```
350
+
351
+ ### Service Methods
352
+
353
+ ```typescript
354
+ // Discover included services
355
+ const includedServiceUuids = await service.discoverIncludedServicesAsync([serviceUUIDs]);
356
+
357
+ // Discover characteristics
358
+ const characteristics = await service.discoverCharacteristicsAsync([characteristicUUIDs]);
359
+ ```
360
+
361
+ ### Characteristic Methods
362
+
363
+ ```typescript
364
+ // Read characteristic value
365
+ const data = await characteristic.readAsync();
366
+
367
+ // Write characteristic value
368
+ await characteristic.writeAsync(data, withoutResponse);
369
+
370
+ // Subscribe to notifications
371
+ await characteristic.subscribeAsync();
372
+
373
+ // Unsubscribe from notifications
374
+ await characteristic.unsubscribeAsync();
375
+
376
+ // Discover descriptors
377
+ const descriptors = await characteristic.discoverDescriptorsAsync();
378
+ ```
379
+
380
+ ### Descriptor Methods
381
+
382
+ ```typescript
383
+ // Read descriptor value
384
+ const value = await descriptor.readValueAsync();
385
+
386
+ // Write descriptor value
387
+ await descriptor.writeValueAsync(data);
86
388
  ```
87
389
 
88
390
  ## Installation
@@ -105,37 +407,35 @@ const noble = require('@stoprocent/noble/with-custom-binding')({extended: true})
105
407
 
106
408
  Please refer to [https://github.com/stoprocent/node-bluetooth-hci-socket#uartserial-any-os](https://github.com/stoprocent/node-bluetooth-hci-socket#uartserial-any-os)
107
409
 
108
- ##### Example 1 (UART port spcified as enviromental variable)
109
-
110
- ```bash
111
- $ export BLUETOOTH_HCI_SOCKET_UART_PORT=/dev/tty...
112
- $ export BLUETOOTH_HCI_SOCKET_UART_BAUDRATE=1000000
113
- ```
410
+ __NOTE:__ While environmental variables are still supported for backward compatibility, the recommended approach is to specify driver options directly in the `withBindings()` call as shown below:
114
411
 
115
- __NOTE:__ `BLUETOOTH_HCI_SOCKET_UART_BAUDRATE` defaults to `1000000` so only needed if different.
412
+ ##### Recommended Approach (UART port specified in `bindParams`)
116
413
 
117
- ```javascript
118
- const noble = require('@stoprocent/noble');
414
+ ```typescript
415
+ import { withBindings } from '@stoprocent/noble';
416
+ const noble = withBindings('hci', {
417
+ hciDriver: 'uart',
418
+ bindParams: {
419
+ uart: {
420
+ port: '/dev/ttyUSB0',
421
+ baudRate: 1000000
422
+ }
423
+ }
424
+ });
119
425
  ```
120
426
 
121
- ##### Example 2 (UART port spcified in `bindParams`)
427
+ ##### Legacy Approach (Using environmental variables - not recommended for new implementations)
122
428
 
123
429
  ```bash
124
- $ export BLUETOOTH_HCI_SOCKET_FORCE_UART=1
430
+ $ export BLUETOOTH_HCI_SOCKET_UART_PORT=/dev/tty...
431
+ $ export BLUETOOTH_HCI_SOCKET_UART_BAUDRATE=1000000
125
432
  ```
126
433
 
127
- ```javascript
128
- const noble = require('@stoprocent/noble/with-custom-binding') ( {
129
- bindParams: {
130
- uart: {
131
- port: '/dev/tty...',
132
- baudRate: 1000000
133
- }
134
- }
135
- } );
136
- ```
434
+ __NOTE:__ `BLUETOOTH_HCI_SOCKET_UART_BAUDRATE` defaults to `1000000` so only needed if different.
137
435
 
138
- __NOTE:__ There is a [UART code example](examples/uart-bind-params.js) in the `/examples` directory.
436
+ ```typescript
437
+ import noble from '@stoprocent/noble';
438
+ ```
139
439
 
140
440
  #### OS X
141
441
 
@@ -221,553 +521,7 @@ See [@don](https://github.com/don)'s setup guide on [Bluetooth LE with Node.js a
221
521
 
222
522
  #### Docker
223
523
 
224
- Make sure your container runs with `--network=host` options and all specific environment preriquisites are verified.
225
-
226
- ### Installing and using the package
227
-
228
- ```sh
229
- npm install @stoprocent/noble
230
- ```
231
-
232
- In Windows OS add your custom hci-usb dongle to the process env
233
- ```sh
234
- set BLUETOOTH_HCI_SOCKET_USB_VID=xxx
235
- set BLUETOOTH_HCI_SOCKET_USB_PID=xxx
236
- ```
237
-
238
- ```javascript
239
- const noble = require('@stoprocent/noble');
240
- ```
241
-
242
- ## API docs
243
-
244
- All operations have two API variants – one expecting a callback, one returning a Promise (denoted by `Async` suffix).
245
-
246
- Additionally, there are events corresponding to each operation (and a few global events).
247
-
248
- For example, in case of the "discover services" operation of Peripheral:
249
-
250
- * There's a `discoverServices` method expecting a callback:
251
- ```javascript
252
- peripheral.discoverServices((error, services) => {
253
- // callback - handle error and services
254
- });
255
- ```
256
- * There's a `discoverServicesAsync` method returning a Promise:
257
- ```javascript
258
- try {
259
- const services = await peripheral.discoverServicesAsync();
260
- // handle services
261
- } catch (e) {
262
- // handle error
263
- }
264
- ```
265
- * There's a `servicesDiscover` event emitted after services are discovered:
266
- ```javascript
267
- peripheral.once('servicesDiscover', (services) => {
268
- // handle services
269
- });
270
- ```
271
-
272
- API structure:
273
-
274
- * [Scanning and discovery](#scanning-and-discovery)
275
- * [_Event: Adapter state changed_](#event-adapter-state-changed)
276
- * [Set address](#set-address)
277
- * [Start scanning](#start-scanning)
278
- * [_Event: Scanning started_](#event-scanning-started)
279
- * [Stop scanning](#stop-scanning)
280
- * [_Event: Scanning stopped_](#event-scanning-stopped)
281
- * [Connect by UUID / Address](#connect-by-uuid)
282
- * [_Event: Peripheral discovered_](#event-peripheral-discovered)
283
- * [_Event: Warning raised_](#event-warning-raised)
284
- * [Reset device](#reset-device)
285
- * [Peripheral](#peripheral)
286
- * [Connect](#connect)
287
- * [_Event: Connected_](#event-connected)
288
- * [Cancel a pending connection](#cancel-a-pending-connection)
289
- * [Disconnect](#disconnect)
290
- * [_Event: Disconnected_](#event-disconnected)
291
- * [Update RSSI](#update-rssi)
292
- * [_Event: RSSI updated_](#event-rssi-updated)
293
- * [Discover services](#discover-services)
294
- * [Discover all services and characteristics](#discover-all-services-and-characteristics)
295
- * [Discover some services and characteristics](#discover-some-services-and-characteristics)
296
- * [_Event: Services discovered_](#event-services-discovered)
297
- * [Read handle](#read-handle)
298
- * [_Event: Handle read_](#event-handle-read)
299
- * [Write handle](#write-handle)
300
- * [_Event: Handle written_](#event-handle-written)
301
- * [Service](#service)
302
- * [Discover included services](#discover-included-services)
303
- * [_Event: Included services discovered_](#event-included-services-discovered)
304
- * [Discover characteristics](#discover-characteristics)
305
- * [_Event: Characteristics discovered_](#event-characteristics-discovered)
306
- * [Characteristic](#characteristic)
307
- * [Read](#read)
308
- * [_Event: Data read_](#event-data-read)
309
- * [Write](#write)
310
- * [_Event: Data written_](#event-data-written)
311
- * [Broadcast](#broadcast)
312
- * [_Event: Broadcast sent_](#event-broadcast-sent)
313
- * [Subscribe](#subscribe)
314
- * [_Event: Notification received_](#event-notification-received)
315
- * [Unsubscribe](#unsubscribe)
316
- * [Discover descriptors](#discover-descriptors)
317
- * [_Event: Descriptors discovered_](#event-descriptors-discovered)
318
- * [Descriptor](#descriptor)
319
- * [Read value](#read-value)
320
- * [_Event: Value read_](#event-value-read)
321
- * [Write value](#write-value)
322
- * [_Event: Value written_](#event-value-written)
323
-
324
- ### Scanning and discovery
325
-
326
- #### _Event: Adapter state changed_
327
-
328
- ```javascript
329
- noble.on('stateChange', callback(state));
330
- ```
331
-
332
- `state` can be one of:
333
- * `unknown`
334
- * `resetting`
335
- * `unsupported`
336
- * `unauthorized`
337
- * `poweredOff`
338
- * `poweredOn`
339
-
340
- #### Set address
341
-
342
- ```javascript
343
- noble.setAddress('00:11:22:33:44:55'); // set adapter's mac address
344
- ```
345
- __NOTE:__ Curently this feature is only supported on HCI as it's using vendor specific commands. Source of the commands is based on the [BlueZ bdaddr.c](https://github.com/pauloborges/bluez/blob/master/tools/bdaddr.c).
346
- __NOTE:__ `noble.state` must be `poweredOn` before address can be set. `noble.on('stateChange', callback(state));` can be used to listen for state change events.
347
-
348
- #### Start scanning
349
-
350
- ```javascript
351
- noble.startScanning(); // any service UUID, no duplicates
352
-
353
-
354
- noble.startScanning([], true); // any service UUID, allow duplicates
355
-
356
-
357
- var serviceUUIDs = ['<service UUID 1>', ...]; // default: [] => all
358
- var allowDuplicates = falseOrTrue; // default: false
359
-
360
- noble.startScanning(serviceUUIDs, allowDuplicates[, callback(error)]); // particular UUIDs
361
- ```
362
-
363
- __NOTE:__ `noble.state` must be `poweredOn` before scanning is started. `noble.on('stateChange', callback(state));` can be used to listen for state change events.
364
-
365
- #### _Event: Scanning started_
366
-
367
- ```javascript
368
- noble.on('scanStart', callback);
369
- ```
370
-
371
- The event is emitted when:
372
- * Scanning is started
373
- * Another application enables scanning
374
- * Another application changes scanning settings
375
-
376
- #### Stop scanning
377
-
378
- ```javascript
379
- noble.stopScanning();
380
- ```
381
-
382
- #### _Event: Scanning stopped_
383
-
384
- ```javascript
385
- noble.on('scanStop', callback);
386
- ```
387
-
388
- The event is emitted when:
389
- * Scanning is stopped
390
- * Another application stops scanning
391
-
392
- #### Connect by UUID
393
-
394
- 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.
395
-
396
- ##### Usage
397
-
398
- ```typescript
399
- // Callback-based usage
400
- connect(peripheralUuid: string, options?: object, callback?: (error?: Error, peripheral: Peripheral) => void): void;
401
-
402
- // Promise-based usage
403
- connectAsync(peripheralUuid: string, options?: object): Promise<Peripheral>;
404
- ```
405
-
406
- ##### Parameters
407
- - `peripheralUuid`: The UUID of the peripheral to connect to.
408
- - `options`: Optional parameters for the connection (this may include connection interval, latency, supervision timeout, etc.).
409
- - `callback`: An optional callback that returns an error or the connected peripheral object.
410
-
411
- ##### Description
412
- 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.
413
-
414
- ##### Example
415
-
416
- ```javascript
417
- const noble = require('@stoprocent/noble');
418
-
419
- // Using callback
420
- noble.connect('1234567890abcdef', {}, (error, peripheral) => {
421
- if (error) {
422
- console.error('Connection error:', error);
423
- } else {
424
- console.log('Connected to:', peripheral.uuid);
425
- }
426
- });
427
-
428
- // Using async/await
429
- async function connectPeripheral() {
430
- try {
431
- const peripheral = await noble.connectAsync('1234567890abcdef');
432
- console.log('Connected to:', peripheral.uuid);
433
- } catch (error) {
434
- console.error('Connection error:', error);
435
- }
436
- }
437
- connectPeripheral();
438
- ```
439
-
440
-
441
- #### _Event: Peripheral discovered_
442
-
443
- ```javascript
444
- noble.on('discover', callback(peripheral));
445
- ```
446
-
447
- * `peripheral`:
448
- ```javascript
449
- {
450
- id: '<id>',
451
- address: '<BT address'>, // Bluetooth Address of device, or 'unknown' if not known
452
- addressType: '<BT address type>', // Bluetooth Address type (public, random), or 'unknown' if not known
453
- connectable: trueOrFalseOrUndefined, // true or false, or undefined if not known
454
- advertisement: {
455
- localName: '<name>',
456
- txPowerLevel: someInteger,
457
- serviceUuids: ['<service UUID>', ...],
458
- serviceSolicitationUuid: ['<service solicitation UUID>', ...],
459
- manufacturerData: someBuffer, // a Buffer
460
- serviceData: [
461
- {
462
- uuid: '<service UUID>',
463
- data: someBuffer // a Buffer
464
- },
465
- // ...
466
- ]
467
- },
468
- rssi: integerValue,
469
- mtu: integerValue // MTU will be null, until device is connected and hci-socket is used
470
- };
471
- ```
472
-
473
- __Note:__ On macOS, the address will be set to '' if the device has not been connected previously.
474
-
475
-
476
- #### _Event: Warning raised_
477
-
478
- ```javascript
479
- noble.on('warning', callback(message));
480
- ```
481
-
482
- ### Reset device
483
-
484
- ```javascript
485
- noble.reset()
486
- ```
487
-
488
- ### Peripheral
489
-
490
- #### Connect
491
-
492
- ```javascript
493
- peripheral.connect([callback(error)]);
494
- ```
495
-
496
- Some of the bluetooth devices doesn't connect seamlessly, may be because of bluetooth device firmware or kernel. Do reset the device with noble.reset() API before connect API.
497
-
498
- #### _Event: Connected_
499
-
500
- ```javascript
501
- peripheral.once('connect', callback);
502
- ```
503
-
504
- #### Cancel a pending connection
505
-
506
- ```javascript
507
- peripheral.cancelConnect();
508
- // Will emit a 'connect' event with error
509
- ```
510
-
511
- #### Disconnect
512
-
513
- ```javascript
514
- peripheral.disconnect([callback(error)]);
515
- ```
516
-
517
- #### _Event: Disconnected_
518
-
519
- ```javascript
520
- peripheral.once('disconnect', callback);
521
- ```
522
-
523
- #### Update RSSI
524
-
525
- ```javascript
526
- peripheral.updateRssi([callback(error, rssi)]);
527
- ```
528
-
529
- #### _Event: RSSI updated_
530
-
531
- ```javascript
532
- peripheral.once('rssiUpdate', callback(rssi));
533
- ```
534
-
535
- #### Discover services
536
-
537
- ```javascript
538
- peripheral.discoverServices(); // any service UUID
539
-
540
- var serviceUUIDs = ['<service UUID 1>', ...];
541
- peripheral.discoverServices(serviceUUIDs[, callback(error, services)]); // particular UUIDs
542
- ```
543
-
544
- #### Discover all services and characteristics
545
-
546
- ```javascript
547
- peripheral.discoverAllServicesAndCharacteristics([callback(error, services, characteristics)]);
548
- ```
549
-
550
- #### Discover some services and characteristics
551
-
552
- ```javascript
553
- var serviceUUIDs = ['<service UUID 1>', ...];
554
- var characteristicUUIDs = ['<characteristic UUID 1>', ...];
555
- peripheral.discoverSomeServicesAndCharacteristics(serviceUUIDs, characteristicUUIDs, [callback(error, services, characteristics));
556
- ```
557
-
558
- #### _Event: Services discovered_
559
-
560
- ```javascript
561
- peripheral.once('servicesDiscover', callback(services));
562
- ```
563
-
564
- #### Read handle
565
-
566
- ```javascript
567
- peripheral.readHandle(handle, callback(error, data));
568
- ```
569
-
570
- #### _Event: Handle read_
571
-
572
- ```javascript
573
- peripheral.once('handleRead<handle>', callback(data)); // data is a Buffer
574
- ```
575
-
576
- `<handle>` is the handle identifier.
577
-
578
- #### Write handle
579
-
580
- ```javascript
581
- peripheral.writeHandle(handle, data, withoutResponse, callback(error));
582
- ```
583
-
584
- #### _Event: Handle written_
585
-
586
- ```javascript
587
- peripheral.once('handleWrite<handle>', callback());
588
- ```
589
-
590
- `<handle>` is the handle identifier.
591
-
592
- ### Service
593
-
594
- #### Discover included services
595
-
596
- ```javascript
597
- service.discoverIncludedServices(); // any service UUID
598
-
599
- var serviceUUIDs = ['<service UUID 1>', ...];
600
- service.discoverIncludedServices(serviceUUIDs[, callback(error, includedServiceUuids)]); // particular UUIDs
601
- ```
602
-
603
- #### _Event: Included services discovered_
604
-
605
- ```javascript
606
- service.once('includedServicesDiscover', callback(includedServiceUuids));
607
- ```
608
-
609
- #### Discover characteristics
610
-
611
- ```javascript
612
- service.discoverCharacteristics() // any characteristic UUID
613
-
614
- var characteristicUUIDs = ['<characteristic UUID 1>', ...];
615
- service.discoverCharacteristics(characteristicUUIDs[, callback(error, characteristics)]); // particular UUIDs
616
- ```
617
-
618
- #### _Event: Characteristics discovered_
619
-
620
- ```javascript
621
- service.once('characteristicsDiscover', callback(characteristics));
622
- ```
623
-
624
- * `characteristics`
625
- ```javascript
626
- {
627
- uuid: '<uuid>',
628
- properties: ['...'] // 'broadcast', 'read', 'writeWithoutResponse', 'write', 'notify', 'indicate', 'authenticatedSignedWrites', 'extendedProperties'
629
- };
630
- ```
631
-
632
- ### Characteristic
633
-
634
- #### Read
635
-
636
- ```javascript
637
- characteristic.read([callback(error, data)]);
638
- ```
639
-
640
- #### _Event: Data read_
641
-
642
- ```javascript
643
- characteristic.on('data', callback(data, isNotification));
644
-
645
- characteristic.once('read', callback(data, isNotification)); // legacy
646
- ```
647
-
648
- Emitted when:
649
- * Characteristic read has completed, result of `characteristic.read(...)`
650
- * Characteristic value has been updated by peripheral via notification or indication, after having been enabled with `characteristic.notify(true[, callback(error)])`
651
-
652
- **Note:** `isNotification` event parameter value MAY be `undefined` depending on platform. The parameter is **deprecated** after version 1.8.1, and not supported on macOS High Sierra and later.
653
-
654
- #### Write
655
-
656
- ```javascript
657
- characteristic.write(data, withoutResponse[, callback(error)]); // data is a Buffer, withoutResponse is true|false
658
- ```
659
-
660
- * `withoutResponse`:
661
- * `false`: send a write request, used with "write" characteristic property
662
- * `true`: send a write command, used with "write without response" characteristic property
663
-
664
-
665
- #### _Event: Data written_
666
-
667
- ```javascript
668
- characteristic.once('write', withoutResponse, callback());
669
- ```
670
-
671
- Emitted when characteristic write has completed, result of `characteristic.write(...)`.
672
-
673
- #### Broadcast
674
-
675
- ```javascript
676
- characteristic.broadcast(broadcast[, callback(error)]); // broadcast is true|false
677
- ```
678
-
679
- #### _Event: Broadcast sent_
680
-
681
- ```javascript
682
- characteristic.once('broadcast', callback(state));
683
- ```
684
-
685
- Emitted when characteristic broadcast state changes, result of `characteristic.broadcast(...)`.
686
-
687
- #### Subscribe
688
-
689
- ```javascript
690
- characteristic.subscribe([callback(error)]);
691
- ```
692
-
693
- Subscribe to a characteristic.
694
-
695
- Triggers `data` events when peripheral sends a notification or indication. Use for characteristics with "notify" or "indicate" properties.
696
-
697
- #### _Event: Notification received_
698
-
699
- ```javascript
700
- characteristic.once('notify', callback(state));
701
- ```
702
-
703
- Emitted when characteristic notification state changes, result of `characteristic.notify(...)`.
704
-
705
- #### Unsubscribe
706
-
707
- ```javascript
708
- characteristic.unsubscribe([callback(error)]);
709
- ```
710
-
711
- Unsubscribe from a characteristic.
712
-
713
- Use for characteristics with "notify" or "indicate" properties
714
-
715
- #### Discover descriptors
716
-
717
- ```javascript
718
- characteristic.discoverDescriptors([callback(error, descriptors)]);
719
- ```
720
-
721
- #### _Event: Descriptors discovered_
722
-
723
- ```javascript
724
- characteristic.once('descriptorsDiscover', callback(descriptors));
725
- ```
726
- * `descriptors`:
727
- ```javascript
728
- [
729
- {
730
- uuid: '<uuid>'
731
- },
732
- // ...
733
- ]
734
- ```
735
-
736
- ### Descriptor
737
-
738
- #### Read value
739
-
740
- ```javascript
741
- descriptor.readValue([callback(error, data)]);
742
- ```
743
-
744
- #### _Event: Value read_
745
-
746
- ```javascript
747
- descriptor.once('valueRead', data); // data is a Buffer
748
- ```
749
-
750
- #### Write value
751
-
752
- ```javascript
753
- descriptor.writeValue(data[, callback(error)]); // data is a Buffer
754
- ```
755
-
756
- #### _Event: Value written_
757
-
758
- ```javascript
759
- descriptor.once('valueWrite');
760
- ```
761
-
762
- ## Advanced usage
763
-
764
- ### Override default bindings
765
-
766
- By default, noble will select appropriate Bluetooth device bindings based on your platform. You can provide custom bindings using the `with-bindings` module.
767
-
768
- ```javascript
769
- var noble = require('@stoprocent/noble/with-bindings')(require('./my-custom-bindings'));
770
- ```
524
+ Make sure your container runs with `--network=host` options and all specific environment prerequisites are verified.
771
525
 
772
526
  ### Running without root/sudo (Linux-specific)
773
527
 
@@ -789,7 +543,23 @@ It can be installed the following way:
789
543
 
790
544
  `hci0` is used by default.
791
545
 
792
- To override, set the `NOBLE_HCI_DEVICE_ID` environment variable to the interface number.
546
+ You can specify which HCI adapter to use in two ways:
547
+
548
+ #### 1. Using `withBindings` (Recommended)
549
+
550
+ ```typescript
551
+ import { withBindings } from '@stoprocent/noble';
552
+
553
+ // Specify HCI adapter in code
554
+ const noble = withBindings('hci', {
555
+ hciDriver: 'native',
556
+ deviceId: 1 // Using hci1
557
+ });
558
+ ```
559
+
560
+ #### 2. Using environment variable
561
+
562
+ To override using environment variables, set the `NOBLE_HCI_DEVICE_ID` environment variable to the interface number.
793
563
 
794
564
  For example, to specify `hci1`:
795
565
 
@@ -799,19 +569,22 @@ sudo NOBLE_HCI_DEVICE_ID=1 node <your file>.js
799
569
 
800
570
  If you are using multiple HCI devices in one setup you can run two instances of noble with different binding configurations by initializing them seperatly in code:
801
571
 
802
- ```
803
- const HCIBindings = require('@stoprocent/noble/lib/hci-socket/bindings');
804
- const Noble = require('@stoprocent/noble/lib/noble');
572
+ ``` typescript
573
+ import { withBindings } from '@stoprocent/noble';
805
574
 
806
- const params = {
807
- deviceId: 0,
808
- userChannel: true,
809
- extended: false //ble5 extended features
810
- };
575
+ // Create two noble instances with different HCI adapters
576
+ const nobleAdapter0 = withBindings('hci', {
577
+ hciDriver: 'native',
578
+ deviceId: 0 // Using hci0
579
+ });
811
580
 
812
- const noble = new Noble(new HCIBindings(params));
581
+ const nobleAdapter1 = withBindings('hci', {
582
+ hciDriver: 'native',
583
+ deviceId: 1 // Using hci1
584
+ });
813
585
  ```
814
586
 
587
+
815
588
  ### Reporting all HCI events (Linux-specific)
816
589
 
817
590
  By default, noble waits for both the advertisement data and scan response data for each Bluetooth address. If your device does not use scan response, the `NOBLE_REPORT_ALL_HCI_EVENTS` environment variable can be used to bypass it.
@@ -820,45 +593,15 @@ By default, noble waits for both the advertisement data and scan response data f
820
593
  sudo NOBLE_REPORT_ALL_HCI_EVENTS=1 node <your file>.js
821
594
  ```
822
595
 
823
- ### bleno compatibility (Linux-specific)
824
-
825
- By default, noble will respond with an error whenever a GATT request message is received. If your intention is to use bleno in tandem with noble, the `NOBLE_MULTI_ROLE` environment variable can be used to bypass this behaviour.
826
-
827
- __Note:__ this requires a Bluetooth 4.1 adapter.
828
-
829
- ```sh
830
- sudo NOBLE_MULTI_ROLE=1 node <your file>.js
831
- ```
832
-
833
- ## Common problems
834
-
835
- ### Maximum simultaneous connections
836
-
837
- This limit is imposed by the Bluetooth adapter hardware as well as its firmware.
838
-
839
- | Platform | |
840
- | :-------------------------------- | --------------------- |
841
- | OS X 10.11 (El Capitan) | 6 |
842
- | Linux/Windows - Adapter-dependent | 5 (CSR based adapter) |
843
-
844
- ### Sandboxed terminal
845
-
846
- On newer versions of OSX, the terminal app is sandboxed to not allow bluetooth connections by default. If you run a script that tries to access it, you will get an `Abort trap: 6` error.
847
-
848
- To enable bluetooth, go to "System Preferences" —> "Security & Privacy" —> "Bluetooth" -> Add your terminal into allowed apps.
849
-
850
- ### Adapter-specific known issues
851
-
852
- Some BLE adapters cannot connect to a peripheral while they are scanning (examples below). You will get the following messages when trying to connect:
853
-
854
- Sena UD-100 (Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)): `Error: Command disallowed`
855
-
856
- Intel Dual Band Wireless-AC 7260 (Intel Corporation Wireless 7260 (rev 73)): `Error: Connection Rejected due to Limited Resources (0xd)`
596
+ ## Environment Variables
857
597
 
858
- You need to stop scanning before trying to connect in order to solve this issue.
598
+ The following environment variables can configure noble's behavior:
859
599
 
860
- ## Useful links
600
+ | Variable | Purpose | Default | Example |
601
+ |----------|---------|---------|---------|
602
+ | NOBLE_HCI_DEVICE_ID | Specify which HCI adapter to use | 0 | `export NOBLE_HCI_DEVICE_ID=1` |
603
+ | NOBLE_REPORT_ALL_HCI_EVENTS | Report HCI events without waiting for scan response | false | `export NOBLE_REPORT_ALL_HCI_EVENTS=1` |
604
+ | BLUETOOTH_HCI_SOCKET_UART_PORT | UART port for HCI communication | none | `export BLUETOOTH_HCI_SOCKET_UART_PORT=/dev/ttyUSB0` |
605
+ | BLUETOOTH_HCI_SOCKET_UART_BAUDRATE | UART baudrate | 1000000 | `export BLUETOOTH_HCI_SOCKET_UART_BAUDRATE=1000000` |
861
606
 
862
- * [Bluetooth Development Portal](http://developer.bluetooth.org)
863
- * [GATT Specifications](https://www.bluetooth.com/specifications/gatt/)
864
- * [Bluetooth: ATT and GATT](http://epx.com.br/artigos/bluetooth_gatt.php)
607
+ **Note:** The preferred method for configuration is now using the `withBindings()` API rather than environment variables.