@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.
- package/README.md +393 -650
- package/examples/advertisement-discovery.js +57 -48
- package/examples/connect-address.js +59 -34
- package/examples/echo.js +59 -69
- package/examples/enter-exit.js +55 -49
- package/examples/multiple-bindings.js +53 -0
- package/examples/peripheral-explorer-async.js +39 -21
- package/examples/peripheral-explorer.ts +52 -0
- package/index.d.ts +249 -209
- package/index.js +4 -1
- package/jest.config.js +4 -0
- package/lib/characteristic.js +153 -127
- package/lib/{win/src/callbacks.h → common/include/Emit.h} +17 -14
- package/lib/common/include/Peripheral.h +31 -0
- package/lib/common/include/ThreadSafeCallback.h +95 -0
- package/lib/{win/src/callbacks.cc → common/src/Emit.cc} +111 -68
- package/lib/descriptor.js +57 -54
- package/lib/hci-socket/acl-stream.js +2 -4
- package/lib/hci-socket/bindings.js +96 -73
- package/lib/hci-socket/gap.js +2 -3
- package/lib/hci-socket/gatt.js +2 -5
- package/lib/hci-socket/hci.js +19 -7
- package/lib/hci-socket/signaling.js +2 -3
- package/lib/hci-socket/smp.js +2 -3
- package/lib/hci-socket/vs.js +1 -0
- package/lib/mac/binding.gyp +5 -7
- package/lib/mac/bindings.js +1 -3
- package/lib/mac/src/ble_manager.h +1 -8
- package/lib/mac/src/ble_manager.mm +87 -44
- package/lib/mac/src/napi_objc.h +1 -0
- package/lib/mac/src/napi_objc.mm +0 -6
- package/lib/mac/src/noble_mac.h +5 -3
- package/lib/mac/src/noble_mac.mm +99 -57
- package/lib/mac/src/objc_cpp.h +3 -2
- package/lib/mac/src/objc_cpp.mm +0 -6
- package/lib/noble.js +579 -488
- package/lib/peripheral.js +171 -174
- package/lib/resolve-bindings.js +37 -30
- package/lib/service.js +58 -55
- package/lib/win/binding.gyp +4 -11
- package/lib/win/bindings.js +1 -3
- package/lib/win/src/ble_manager.cc +291 -166
- package/lib/win/src/ble_manager.h +11 -13
- package/lib/win/src/napi_winrt.cc +1 -7
- package/lib/win/src/napi_winrt.h +1 -1
- package/lib/win/src/noble_winrt.cc +88 -61
- package/lib/win/src/noble_winrt.h +5 -3
- package/lib/win/src/notify_map.cc +0 -7
- package/lib/win/src/notify_map.h +1 -8
- package/lib/win/src/peripheral_winrt.cc +29 -11
- package/lib/win/src/peripheral_winrt.h +1 -1
- package/lib/win/src/radio_watcher.cc +79 -69
- package/lib/win/src/radio_watcher.h +30 -11
- package/lib/win/src/winrt_cpp.cc +1 -1
- package/lib/win/src/winrt_cpp.h +3 -0
- package/package.json +14 -17
- package/prebuilds/darwin-x64+arm64/@stoprocent+noble.node +0 -0
- package/prebuilds/win32-ia32/@stoprocent+noble.node +0 -0
- package/prebuilds/win32-x64/@stoprocent+noble.node +0 -0
- package/test/lib/characteristic.test.js +202 -322
- package/test/lib/descriptor.test.js +62 -95
- package/test/lib/hci-socket/acl-stream.test.js +112 -108
- package/test/lib/hci-socket/bindings.test.js +576 -365
- package/test/lib/hci-socket/hci.test.js +442 -473
- package/test/lib/hci-socket/signaling.test.js +45 -48
- package/test/lib/hci-socket/smp.test.js +144 -142
- package/test/lib/hci-socket/vs.test.js +193 -18
- package/test/lib/peripheral.test.js +492 -322
- package/test/lib/resolve-bindings.test.js +207 -82
- package/test/lib/service.test.js +79 -88
- package/test/noble.test.js +381 -1085
- package/.editorconfig +0 -11
- package/.nycrc.json +0 -4
- package/codecov.yml +0 -5
- package/examples/cache-gatt-discovery.js +0 -198
- package/examples/cache-gatt-reconnect.js +0 -164
- package/examples/ext-advertisement-discovery.js +0 -65
- package/examples/peripheral-explorer.js +0 -225
- package/examples/pizza/central.js +0 -194
- package/examples/pizza/pizza.js +0 -60
- package/examples/test/test.custom.js +0 -131
- package/examples/uart-bind-params.js +0 -28
- package/lib/distributed/bindings.js +0 -326
- package/lib/mac/src/callbacks.cc +0 -222
- package/lib/mac/src/callbacks.h +0 -84
- package/lib/mac/src/peripheral.h +0 -23
- package/lib/resolve-bindings-web.js +0 -9
- package/lib/webbluetooth/bindings.js +0 -368
- package/lib/websocket/bindings.js +0 -321
- package/lib/win/src/peripheral.h +0 -23
- package/test/lib/distributed/bindings.test.js +0 -918
- package/test/lib/webbluetooth/bindings.test.js +0 -190
- package/test/lib/websocket/bindings.test.js +0 -456
- package/test/mocha.setup.js +0 -0
- package/with-bindings.js +0 -5
- package/with-custom-binding.js +0 -6
- package/ws-slave.js +0 -404
package/README.md
CHANGED
|
@@ -2,27 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@stoprocent/noble)
|
|
4
4
|
[](https://www.npmjs.com/package/@stoprocent/noble)
|
|
5
|
-
|
|
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. **
|
|
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. **
|
|
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
|
-
|
|
230
|
+
async function readBatteryLevel() {
|
|
231
|
+
const noble = withBindings('default');
|
|
56
232
|
|
|
57
|
-
async function run() {
|
|
58
233
|
try {
|
|
59
|
-
await noble.
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
254
|
+
readBatteryLevel();
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## API Overview
|
|
258
|
+
|
|
259
|
+
Noble provides both callback-based and Promise-based (Async) APIs:
|
|
71
260
|
|
|
72
|
-
|
|
261
|
+
### Binding Types
|
|
73
262
|
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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
|
-
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
|
|
412
|
+
##### Recommended Approach (UART port specified in `bindParams`)
|
|
116
413
|
|
|
117
|
-
```
|
|
118
|
-
|
|
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
|
-
#####
|
|
427
|
+
##### Legacy Approach (Using environmental variables - not recommended for new implementations)
|
|
122
428
|
|
|
123
429
|
```bash
|
|
124
|
-
$ export
|
|
430
|
+
$ export BLUETOOTH_HCI_SOCKET_UART_PORT=/dev/tty...
|
|
431
|
+
$ export BLUETOOTH_HCI_SOCKET_UART_BAUDRATE=1000000
|
|
125
432
|
```
|
|
126
433
|
|
|
127
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
804
|
-
const Noble = require('@stoprocent/noble/lib/noble');
|
|
572
|
+
``` typescript
|
|
573
|
+
import { withBindings } from '@stoprocent/noble';
|
|
805
574
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
598
|
+
The following environment variables can configure noble's behavior:
|
|
859
599
|
|
|
860
|
-
|
|
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
|
-
|
|
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.
|