@stoprocent/noble 1.9.2-16
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/.editorconfig +11 -0
- package/.eslintrc.js +25 -0
- package/.github/FUNDING.yml +2 -0
- package/.github/workflows/fediverse-action.yml +16 -0
- package/.github/workflows/nodepackage.yml +77 -0
- package/.github/workflows/npm-publish.yml +26 -0
- package/.github/workflows/prebuild.yml +65 -0
- package/.nycrc.json +4 -0
- package/CHANGELOG.md +119 -0
- package/LICENSE +20 -0
- package/MAINTAINERS.md +1 -0
- package/README.md +833 -0
- package/assets/noble-logo.png +0 -0
- package/assets/noble-logo.svg +13 -0
- package/binding.gyp +19 -0
- package/codecov.yml +5 -0
- package/examples/advertisement-discovery.js +65 -0
- package/examples/cache-gatt-discovery.js +198 -0
- package/examples/cache-gatt-reconnect.js +164 -0
- package/examples/echo.js +104 -0
- package/examples/enter-exit.js +78 -0
- package/examples/peripheral-explorer-async.js +133 -0
- package/examples/peripheral-explorer.js +225 -0
- package/examples/pizza/README.md +15 -0
- package/examples/pizza/central.js +194 -0
- package/examples/pizza/pizza.js +60 -0
- package/index.d.ts +203 -0
- package/index.js +6 -0
- package/lib/characteristic.js +161 -0
- package/lib/characteristics.json +449 -0
- package/lib/descriptor.js +72 -0
- package/lib/descriptors.json +47 -0
- package/lib/distributed/bindings.js +326 -0
- package/lib/hci-socket/acl-stream.js +60 -0
- package/lib/hci-socket/bindings.js +788 -0
- package/lib/hci-socket/crypto.js +74 -0
- package/lib/hci-socket/gap.js +432 -0
- package/lib/hci-socket/gatt.js +809 -0
- package/lib/hci-socket/hci-status.json +71 -0
- package/lib/hci-socket/hci.js +1264 -0
- package/lib/hci-socket/signaling.js +76 -0
- package/lib/hci-socket/smp.js +140 -0
- package/lib/hci-uart/bindings.js +569 -0
- package/lib/hci-uart/hci-serial-parser.js +70 -0
- package/lib/hci-uart/hci.js +1336 -0
- package/lib/mac/binding.gyp +26 -0
- package/lib/mac/bindings.js +11 -0
- package/lib/mac/src/ble_manager.h +41 -0
- package/lib/mac/src/ble_manager.mm +435 -0
- package/lib/mac/src/callbacks.cc +222 -0
- package/lib/mac/src/callbacks.h +84 -0
- package/lib/mac/src/napi_objc.h +12 -0
- package/lib/mac/src/napi_objc.mm +50 -0
- package/lib/mac/src/noble_mac.h +34 -0
- package/lib/mac/src/noble_mac.mm +264 -0
- package/lib/mac/src/objc_cpp.h +26 -0
- package/lib/mac/src/objc_cpp.mm +126 -0
- package/lib/mac/src/peripheral.h +23 -0
- package/lib/manufacture.js +48 -0
- package/lib/noble.js +593 -0
- package/lib/peripheral.js +219 -0
- package/lib/resolve-bindings-web.js +9 -0
- package/lib/resolve-bindings.js +44 -0
- package/lib/service.js +72 -0
- package/lib/services.json +92 -0
- package/lib/webbluetooth/bindings.js +368 -0
- package/lib/websocket/bindings.js +321 -0
- package/lib/win/binding.gyp +23 -0
- package/lib/win/bindings.js +11 -0
- package/lib/win/src/ble_manager.cc +802 -0
- package/lib/win/src/ble_manager.h +77 -0
- package/lib/win/src/callbacks.cc +274 -0
- package/lib/win/src/callbacks.h +33 -0
- package/lib/win/src/napi_winrt.cc +76 -0
- package/lib/win/src/napi_winrt.h +12 -0
- package/lib/win/src/noble_winrt.cc +308 -0
- package/lib/win/src/noble_winrt.h +34 -0
- package/lib/win/src/notify_map.cc +62 -0
- package/lib/win/src/notify_map.h +50 -0
- package/lib/win/src/peripheral.h +23 -0
- package/lib/win/src/peripheral_winrt.cc +296 -0
- package/lib/win/src/peripheral_winrt.h +82 -0
- package/lib/win/src/radio_watcher.cc +125 -0
- package/lib/win/src/radio_watcher.h +61 -0
- package/lib/win/src/winrt_cpp.cc +82 -0
- package/lib/win/src/winrt_cpp.h +11 -0
- package/lib/win/src/winrt_guid.cc +12 -0
- package/lib/win/src/winrt_guid.h +13 -0
- package/misc/nrf52840dk.hex +6921 -0
- package/misc/prj.conf +43 -0
- package/package.json +96 -0
- package/test/lib/characteristic.test.js +791 -0
- package/test/lib/descriptor.test.js +249 -0
- package/test/lib/distributed/bindings.test.js +918 -0
- package/test/lib/hci-socket/acl-stream.test.js +188 -0
- package/test/lib/hci-socket/bindings.test.js +1756 -0
- package/test/lib/hci-socket/crypto.test.js +55 -0
- package/test/lib/hci-socket/gap.test.js +1089 -0
- package/test/lib/hci-socket/gatt.test.js +2392 -0
- package/test/lib/hci-socket/hci.test.js +1891 -0
- package/test/lib/hci-socket/signaling.test.js +94 -0
- package/test/lib/hci-socket/smp.test.js +268 -0
- package/test/lib/manufacture.test.js +77 -0
- package/test/lib/peripheral.test.js +623 -0
- package/test/lib/resolve-bindings.test.js +102 -0
- package/test/lib/service.test.js +195 -0
- package/test/lib/webbluetooth/bindings.test.js +190 -0
- package/test/lib/websocket/bindings.test.js +456 -0
- package/test/noble.test.js +1565 -0
- package/test.js +131 -0
- package/with-bindings.js +5 -0
- package/ws-slave.js +404 -0
|
@@ -0,0 +1,1264 @@
|
|
|
1
|
+
const debug = require('debug')('hci');
|
|
2
|
+
|
|
3
|
+
const events = require('events');
|
|
4
|
+
const util = require('util');
|
|
5
|
+
|
|
6
|
+
const BluetoothHciSocket = require('@abandonware/bluetooth-hci-socket');
|
|
7
|
+
|
|
8
|
+
const HCI_COMMAND_PKT = 0x01;
|
|
9
|
+
const HCI_ACLDATA_PKT = 0x02;
|
|
10
|
+
const HCI_EVENT_PKT = 0x04;
|
|
11
|
+
|
|
12
|
+
const ACL_START_NO_FLUSH = 0x00;
|
|
13
|
+
const ACL_CONT = 0x01;
|
|
14
|
+
const ACL_START = 0x02;
|
|
15
|
+
|
|
16
|
+
const EVT_DISCONN_COMPLETE = 0x05;
|
|
17
|
+
const EVT_ENCRYPT_CHANGE = 0x08;
|
|
18
|
+
const EVT_CMD_COMPLETE = 0x0e;
|
|
19
|
+
const EVT_CMD_STATUS = 0x0f;
|
|
20
|
+
const EVT_NUMBER_OF_COMPLETED_PACKETS = 0x13;
|
|
21
|
+
const EVT_LE_META_EVENT = 0x3e;
|
|
22
|
+
|
|
23
|
+
const EVT_LE_CONN_COMPLETE = 0x01;
|
|
24
|
+
const EVT_LE_ADVERTISING_REPORT = 0x02;
|
|
25
|
+
const EVT_LE_ENHANCED_CONN_COMPLETE = 0x0a;
|
|
26
|
+
const EVT_LE_EXTENDED_ADVERTISING_REPORT = 0x0d;
|
|
27
|
+
const EVT_LE_CONN_UPDATE_COMPLETE = 0x03;
|
|
28
|
+
|
|
29
|
+
const OGF_LINK_CTL = 0x01;
|
|
30
|
+
const OCF_DISCONNECT = 0x0006;
|
|
31
|
+
|
|
32
|
+
const OGF_HOST_CTL = 0x03;
|
|
33
|
+
const OCF_SET_EVENT_MASK = 0x0001;
|
|
34
|
+
const OCF_RESET = 0x0003;
|
|
35
|
+
const OCF_SET_RANDOM_MAC = 0x0005;
|
|
36
|
+
const OCF_SET_PHY = 0x0031;
|
|
37
|
+
const OCF_READ_LE_HOST_SUPPORTED = 0x006c;
|
|
38
|
+
const OCF_WRITE_LE_HOST_SUPPORTED = 0x006d;
|
|
39
|
+
|
|
40
|
+
const OGF_INFO_PARAM = 0x04;
|
|
41
|
+
const OCF_READ_LOCAL_VERSION = 0x0001;
|
|
42
|
+
const OCF_READ_SUPPORTED_COMMANDS = 0x0002;
|
|
43
|
+
const OCF_READ_BUFFER_SIZE = 0x0005;
|
|
44
|
+
const OCF_READ_BD_ADDR = 0x0009;
|
|
45
|
+
|
|
46
|
+
const OGF_STATUS_PARAM = 0x05;
|
|
47
|
+
const OCF_READ_RSSI = 0x0005;
|
|
48
|
+
|
|
49
|
+
const OGF_LE_CTL = 0x08;
|
|
50
|
+
const OCF_LE_SET_EVENT_MASK = 0x0001;
|
|
51
|
+
const OCF_LE_READ_BUFFER_SIZE = 0x0002;
|
|
52
|
+
const OCF_LE_SET_EXTENDED_SCAN_PARAMETERS = 0x0041;
|
|
53
|
+
const OCF_LE_SET_EXTENDED_SCAN_ENABLE = 0x0042;
|
|
54
|
+
const OCF_LE_SET_SCAN_PARAMETERS = 0x000b;
|
|
55
|
+
const OCF_LE_SET_SCAN_ENABLE = 0x000c;
|
|
56
|
+
const OCF_LE_CREATE_CONN = 0x000d;
|
|
57
|
+
const OCF_LE_CREATE_EXTENDED_CONN = 0x0043;
|
|
58
|
+
const OCF_LE_CANCEL_CONN = 0x000e;
|
|
59
|
+
const OCF_LE_CONN_UPDATE = 0x0013;
|
|
60
|
+
const OCF_LE_START_ENCRYPTION = 0x0019;
|
|
61
|
+
const DISCONNECT_CMD = OCF_DISCONNECT | (OGF_LINK_CTL << 10);
|
|
62
|
+
|
|
63
|
+
const SET_EVENT_MASK_CMD = OCF_SET_EVENT_MASK | (OGF_HOST_CTL << 10);
|
|
64
|
+
const RESET_CMD = OCF_RESET | (OGF_HOST_CTL << 10);
|
|
65
|
+
const READ_LE_HOST_SUPPORTED_CMD =
|
|
66
|
+
OCF_READ_LE_HOST_SUPPORTED | (OGF_HOST_CTL << 10);
|
|
67
|
+
const WRITE_LE_HOST_SUPPORTED_CMD =
|
|
68
|
+
OCF_WRITE_LE_HOST_SUPPORTED | (OGF_HOST_CTL << 10);
|
|
69
|
+
|
|
70
|
+
const READ_LOCAL_VERSION_CMD = OCF_READ_LOCAL_VERSION | (OGF_INFO_PARAM << 10);
|
|
71
|
+
const READ_SUPPORTED_COMMANDS_CMD =
|
|
72
|
+
OCF_READ_SUPPORTED_COMMANDS | (OGF_INFO_PARAM << 10);
|
|
73
|
+
const READ_BUFFER_SIZE_CMD = OCF_READ_BUFFER_SIZE | (OGF_INFO_PARAM << 10);
|
|
74
|
+
const READ_BD_ADDR_CMD = OCF_READ_BD_ADDR | (OGF_INFO_PARAM << 10);
|
|
75
|
+
|
|
76
|
+
const READ_RSSI_CMD = OCF_READ_RSSI | (OGF_STATUS_PARAM << 10);
|
|
77
|
+
|
|
78
|
+
const LE_SET_EVENT_MASK_CMD = OCF_LE_SET_EVENT_MASK | (OGF_LE_CTL << 10);
|
|
79
|
+
const LE_READ_BUFFER_SIZE_CMD = OCF_LE_READ_BUFFER_SIZE | (OGF_LE_CTL << 10);
|
|
80
|
+
const LE_SET_EXTENDED_SCAN_PARAMETERS_CMD =
|
|
81
|
+
OCF_LE_SET_EXTENDED_SCAN_PARAMETERS | (OGF_LE_CTL << 10);
|
|
82
|
+
const LE_SET_EXTENDED_SCAN_ENABLE_CMD =
|
|
83
|
+
OCF_LE_SET_EXTENDED_SCAN_ENABLE | (OGF_LE_CTL << 10);
|
|
84
|
+
const LE_SET_SCAN_PARAMETERS_CMD =
|
|
85
|
+
OCF_LE_SET_SCAN_PARAMETERS | (OGF_LE_CTL << 10);
|
|
86
|
+
const LE_SET_SCAN_ENABLE_CMD = OCF_LE_SET_SCAN_ENABLE | (OGF_LE_CTL << 10);
|
|
87
|
+
const LE_CREATE_CONN_CMD = OCF_LE_CREATE_CONN | (OGF_LE_CTL << 10);
|
|
88
|
+
const LE_CREATE_EXTENDED_CONN_CMD =
|
|
89
|
+
OCF_LE_CREATE_EXTENDED_CONN | (OGF_LE_CTL << 10);
|
|
90
|
+
const LE_CONN_UPDATE_CMD = OCF_LE_CONN_UPDATE | (OGF_LE_CTL << 10);
|
|
91
|
+
const LE_CANCEL_CONN_CMD = OCF_LE_CANCEL_CONN | (OGF_LE_CTL << 10);
|
|
92
|
+
const LE_START_ENCRYPTION_CMD = OCF_LE_START_ENCRYPTION | (OGF_LE_CTL << 10);
|
|
93
|
+
const HCI_OE_USER_ENDED_CONNECTION = 0x13;
|
|
94
|
+
|
|
95
|
+
const STATUS_MAPPER = require('./hci-status');
|
|
96
|
+
|
|
97
|
+
const Hci = function (options) {
|
|
98
|
+
options = options || {};
|
|
99
|
+
this._socket = new BluetoothHciSocket();
|
|
100
|
+
this._isDevUp = null;
|
|
101
|
+
this._isExtended = 'extended' in options && options.extended;
|
|
102
|
+
this._state = null;
|
|
103
|
+
|
|
104
|
+
this._handleBuffers = {};
|
|
105
|
+
|
|
106
|
+
this._aclBuffers = undefined;
|
|
107
|
+
this._resolveAclBuffers = undefined;
|
|
108
|
+
const aclBuffersPromise = new Promise((resolve) => {
|
|
109
|
+
this._resolveAclBuffers = resolve;
|
|
110
|
+
});
|
|
111
|
+
this.getAclBuffers = async function () {
|
|
112
|
+
if (this._aclBuffers) return this._aclBuffers;
|
|
113
|
+
return await aclBuffersPromise;
|
|
114
|
+
}.bind(this);
|
|
115
|
+
this.setAclBuffers = function (length, num) {
|
|
116
|
+
if (this._aclBuffers) {
|
|
117
|
+
this._aclBuffers.length = length;
|
|
118
|
+
this._aclBuffers.num = num;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
this._aclBuffers = {
|
|
122
|
+
length,
|
|
123
|
+
num
|
|
124
|
+
};
|
|
125
|
+
this._resolveAclBuffers(this._aclBuffers);
|
|
126
|
+
}.bind(this);
|
|
127
|
+
|
|
128
|
+
this._aclConnections = new Map();
|
|
129
|
+
|
|
130
|
+
this._aclQueue = [];
|
|
131
|
+
|
|
132
|
+
this._deviceId = options.deviceId != null
|
|
133
|
+
? parseInt(options.deviceId, 10)
|
|
134
|
+
: process.env.NOBLE_HCI_DEVICE_ID
|
|
135
|
+
? parseInt(process.env.NOBLE_HCI_DEVICE_ID, 10)
|
|
136
|
+
: undefined;
|
|
137
|
+
|
|
138
|
+
this._userChannel =
|
|
139
|
+
(typeof options.userChannel === 'undefined' && options.userChannel) ||
|
|
140
|
+
process.env.HCI_CHANNEL_USER;
|
|
141
|
+
|
|
142
|
+
this.on('stateChange', this.onStateChange.bind(this));
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
util.inherits(Hci, events.EventEmitter);
|
|
146
|
+
|
|
147
|
+
Hci.STATUS_MAPPER = STATUS_MAPPER;
|
|
148
|
+
|
|
149
|
+
Hci.prototype.init = function (options) {
|
|
150
|
+
this._socket.on('data', this.onSocketData.bind(this));
|
|
151
|
+
this._socket.on('error', this.onSocketError.bind(this));
|
|
152
|
+
|
|
153
|
+
if (this._userChannel) {
|
|
154
|
+
this._socket.bindUser(this._deviceId);
|
|
155
|
+
this._socket.start();
|
|
156
|
+
|
|
157
|
+
this.reset();
|
|
158
|
+
} else {
|
|
159
|
+
if (!this._bound) {
|
|
160
|
+
this._socket.bindRaw(this._deviceId);
|
|
161
|
+
this._bound = true;
|
|
162
|
+
}
|
|
163
|
+
this._socket.start();
|
|
164
|
+
|
|
165
|
+
this.pollIsDevUp();
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
Hci.prototype.pollIsDevUp = function () {
|
|
170
|
+
const isDevUp = this._socket.isDevUp();
|
|
171
|
+
|
|
172
|
+
if (this._isDevUp !== isDevUp) {
|
|
173
|
+
if (isDevUp) {
|
|
174
|
+
if (this._state === 'poweredOff') {
|
|
175
|
+
this._socket.removeAllListeners();
|
|
176
|
+
this._state = null;
|
|
177
|
+
this.init();
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (this._isExtended) {
|
|
182
|
+
this.setCodedPhySupport();
|
|
183
|
+
}
|
|
184
|
+
this.setSocketFilter();
|
|
185
|
+
this.setEventMask();
|
|
186
|
+
this.setLeEventMask();
|
|
187
|
+
this.readLocalVersion();
|
|
188
|
+
this.writeLeHostSupported();
|
|
189
|
+
this.readLeHostSupported();
|
|
190
|
+
this.readLeBufferSize();
|
|
191
|
+
this.readBdAddr();
|
|
192
|
+
} else {
|
|
193
|
+
this.emit('stateChange', 'poweredOff');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
this._isDevUp = isDevUp;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
setTimeout(this.pollIsDevUp.bind(this), 1000);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
Hci.prototype.setCodedPhySupport = function () {
|
|
203
|
+
const cmd = Buffer.alloc(7);
|
|
204
|
+
|
|
205
|
+
// header
|
|
206
|
+
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
|
|
207
|
+
cmd.writeUInt16LE(OCF_SET_PHY | (OGF_LE_CTL << 10), 1);
|
|
208
|
+
|
|
209
|
+
// length
|
|
210
|
+
cmd.writeUInt8(0x03, 3);
|
|
211
|
+
|
|
212
|
+
// data
|
|
213
|
+
cmd.writeUInt8(0x00, 4); // all phy prefs
|
|
214
|
+
cmd.writeUInt8(0x05, 5); // tx phy: 0x01 - LE 1M, 0x03 - LE 1M + LE 2M, 0x05 - LE 1M + LE CODED, 0x07 - LE 1M + LE 2M + LE CODED
|
|
215
|
+
cmd.writeUInt8(0x05, 6); // rx phy: 0x01 - LE 1M, 0x03 - LE 1M + LE 2M, 0x05 - LE 1M + LE CODED, 0x07 - LE 1M + LE 2M + LE CODED
|
|
216
|
+
|
|
217
|
+
debug(`set all phys supporting - writing: ${cmd.toString('hex')}`);
|
|
218
|
+
this._socket.write(cmd);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
Hci.prototype.setRandomMAC = function () {
|
|
222
|
+
const cmd = Buffer.alloc(10);
|
|
223
|
+
|
|
224
|
+
// header
|
|
225
|
+
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
|
|
226
|
+
cmd.writeUInt16LE(OCF_SET_RANDOM_MAC | (OGF_LE_CTL << 10), 1);
|
|
227
|
+
|
|
228
|
+
// length
|
|
229
|
+
cmd.writeUInt8(0x06, 3);
|
|
230
|
+
|
|
231
|
+
// data
|
|
232
|
+
cmd.writeUInt8(0x05, 4); // mac 6 byte
|
|
233
|
+
cmd.writeUInt8(0x04, 5); // mac 5 byte
|
|
234
|
+
cmd.writeUInt8(0x03, 6); // mac 4 byte
|
|
235
|
+
cmd.writeUInt8(0x02, 7); // mac 3 byte
|
|
236
|
+
cmd.writeUInt8(0x01, 8); // mac 2 byte
|
|
237
|
+
cmd.writeUInt8(0x00, 9); // mac 1 byte
|
|
238
|
+
|
|
239
|
+
debug(`set random mac address - writing: ${cmd.toString('hex')}`);
|
|
240
|
+
this._socket.write(cmd);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
Hci.prototype.setSocketFilter = function () {
|
|
244
|
+
const filter = Buffer.alloc(14);
|
|
245
|
+
const typeMask =
|
|
246
|
+
(1 << HCI_COMMAND_PKT) | (1 << HCI_EVENT_PKT) | (1 << HCI_ACLDATA_PKT);
|
|
247
|
+
const eventMask1 =
|
|
248
|
+
(1 << EVT_DISCONN_COMPLETE) |
|
|
249
|
+
(1 << EVT_ENCRYPT_CHANGE) |
|
|
250
|
+
(1 << EVT_CMD_COMPLETE) |
|
|
251
|
+
(1 << EVT_CMD_STATUS) |
|
|
252
|
+
(1 << EVT_NUMBER_OF_COMPLETED_PACKETS);
|
|
253
|
+
const eventMask2 = 1 << (EVT_LE_META_EVENT - 32);
|
|
254
|
+
const opcode = 0;
|
|
255
|
+
|
|
256
|
+
filter.writeUInt32LE(typeMask, 0);
|
|
257
|
+
filter.writeUInt32LE(eventMask1, 4);
|
|
258
|
+
filter.writeUInt32LE(eventMask2, 8);
|
|
259
|
+
filter.writeUInt16LE(opcode, 12);
|
|
260
|
+
|
|
261
|
+
debug(`setting filter to: ${filter.toString('hex')}`);
|
|
262
|
+
this._socket.setFilter(filter);
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
Hci.prototype.setEventMask = function () {
|
|
266
|
+
const cmd = Buffer.alloc(12);
|
|
267
|
+
const eventMask = Buffer.from('fffffbff07f8bf3d', 'hex');
|
|
268
|
+
|
|
269
|
+
// header
|
|
270
|
+
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
|
|
271
|
+
cmd.writeUInt16LE(SET_EVENT_MASK_CMD, 1);
|
|
272
|
+
|
|
273
|
+
// length
|
|
274
|
+
cmd.writeUInt8(eventMask.length, 3);
|
|
275
|
+
|
|
276
|
+
eventMask.copy(cmd, 4);
|
|
277
|
+
|
|
278
|
+
debug(`set event mask - writing: ${cmd.toString('hex')}`);
|
|
279
|
+
this._socket.write(cmd);
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
Hci.prototype.reset = function () {
|
|
283
|
+
const cmd = Buffer.alloc(4);
|
|
284
|
+
|
|
285
|
+
// header
|
|
286
|
+
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
|
|
287
|
+
cmd.writeUInt16LE(RESET_CMD, 1);
|
|
288
|
+
|
|
289
|
+
// length
|
|
290
|
+
cmd.writeUInt8(0x00, 3);
|
|
291
|
+
|
|
292
|
+
debug(`reset - writing: ${cmd.toString('hex')}`);
|
|
293
|
+
this._socket.write(cmd);
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
Hci.prototype.readSupportedCommands = function () {
|
|
297
|
+
const cmd = Buffer.alloc(4);
|
|
298
|
+
|
|
299
|
+
// header
|
|
300
|
+
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
|
|
301
|
+
cmd.writeUInt16LE(READ_SUPPORTED_COMMANDS_CMD, 1);
|
|
302
|
+
|
|
303
|
+
// length
|
|
304
|
+
cmd.writeUInt8(0x0, 3);
|
|
305
|
+
|
|
306
|
+
debug(`read supported commands - writing: ${cmd.toString('hex')}`);
|
|
307
|
+
this._socket.write(cmd);
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
Hci.prototype.readLocalVersion = function () {
|
|
311
|
+
const cmd = Buffer.alloc(4);
|
|
312
|
+
|
|
313
|
+
// header
|
|
314
|
+
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
|
|
315
|
+
cmd.writeUInt16LE(READ_LOCAL_VERSION_CMD, 1);
|
|
316
|
+
|
|
317
|
+
// length
|
|
318
|
+
cmd.writeUInt8(0x0, 3);
|
|
319
|
+
|
|
320
|
+
debug(`read local version - writing: ${cmd.toString('hex')}`);
|
|
321
|
+
this._socket.write(cmd);
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
Hci.prototype.readBufferSize = function () {
|
|
325
|
+
const cmd = Buffer.alloc(4);
|
|
326
|
+
|
|
327
|
+
// header
|
|
328
|
+
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
|
|
329
|
+
cmd.writeUInt16LE(READ_BUFFER_SIZE_CMD, 1);
|
|
330
|
+
|
|
331
|
+
// length
|
|
332
|
+
cmd.writeUInt8(0x0, 3);
|
|
333
|
+
|
|
334
|
+
debug(`read buffer size - writing: ${cmd.toString('hex')}`);
|
|
335
|
+
this._socket.write(cmd);
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
Hci.prototype.readBdAddr = function () {
|
|
339
|
+
const cmd = Buffer.alloc(4);
|
|
340
|
+
|
|
341
|
+
// header
|
|
342
|
+
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
|
|
343
|
+
cmd.writeUInt16LE(READ_BD_ADDR_CMD, 1);
|
|
344
|
+
|
|
345
|
+
// length
|
|
346
|
+
cmd.writeUInt8(0x0, 3);
|
|
347
|
+
|
|
348
|
+
debug(`read bd addr - writing: ${cmd.toString('hex')}`);
|
|
349
|
+
this._socket.write(cmd);
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
Hci.prototype.setLeEventMask = function () {
|
|
353
|
+
const cmd = Buffer.alloc(12);
|
|
354
|
+
const leEventMask = this._isExtended
|
|
355
|
+
? Buffer.from('1fff000000000000', 'hex')
|
|
356
|
+
: Buffer.from('1f00000000000000', 'hex');
|
|
357
|
+
|
|
358
|
+
// header
|
|
359
|
+
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
|
|
360
|
+
cmd.writeUInt16LE(LE_SET_EVENT_MASK_CMD, 1);
|
|
361
|
+
|
|
362
|
+
// length
|
|
363
|
+
cmd.writeUInt8(leEventMask.length, 3);
|
|
364
|
+
|
|
365
|
+
leEventMask.copy(cmd, 4);
|
|
366
|
+
|
|
367
|
+
debug(`set le event mask - writing: ${cmd.toString('hex')}`);
|
|
368
|
+
this._socket.write(cmd);
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
Hci.prototype.readLeBufferSize = function () {
|
|
372
|
+
const cmd = Buffer.alloc(4);
|
|
373
|
+
|
|
374
|
+
// header
|
|
375
|
+
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
|
|
376
|
+
cmd.writeUInt16LE(LE_READ_BUFFER_SIZE_CMD, 1);
|
|
377
|
+
|
|
378
|
+
// length
|
|
379
|
+
cmd.writeUInt8(0x0, 3);
|
|
380
|
+
|
|
381
|
+
debug(`le read buffer size - writing: ${cmd.toString('hex')}`);
|
|
382
|
+
this._socket.write(cmd);
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
Hci.prototype.readLeHostSupported = function () {
|
|
386
|
+
const cmd = Buffer.alloc(4);
|
|
387
|
+
|
|
388
|
+
// header
|
|
389
|
+
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
|
|
390
|
+
cmd.writeUInt16LE(READ_LE_HOST_SUPPORTED_CMD, 1);
|
|
391
|
+
|
|
392
|
+
// length
|
|
393
|
+
cmd.writeUInt8(0x00, 3);
|
|
394
|
+
|
|
395
|
+
debug(`read LE host supported - writing: ${cmd.toString('hex')}`);
|
|
396
|
+
this._socket.write(cmd);
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
Hci.prototype.writeLeHostSupported = function () {
|
|
400
|
+
const cmd = Buffer.alloc(6);
|
|
401
|
+
|
|
402
|
+
// header
|
|
403
|
+
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
|
|
404
|
+
cmd.writeUInt16LE(WRITE_LE_HOST_SUPPORTED_CMD, 1);
|
|
405
|
+
|
|
406
|
+
// length
|
|
407
|
+
cmd.writeUInt8(0x02, 3);
|
|
408
|
+
|
|
409
|
+
// data
|
|
410
|
+
cmd.writeUInt8(0x01, 4); // le
|
|
411
|
+
cmd.writeUInt8(0x00, 5); // simul
|
|
412
|
+
|
|
413
|
+
debug(`write LE host supported - writing: ${cmd.toString('hex')}`);
|
|
414
|
+
this._socket.write(cmd);
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
Hci.prototype.setScanParameters = function (
|
|
418
|
+
interval = 0x0012,
|
|
419
|
+
window = 0x0012
|
|
420
|
+
) {
|
|
421
|
+
const cmd = Buffer.alloc(this._isExtended ? 17 : 11);
|
|
422
|
+
|
|
423
|
+
// header
|
|
424
|
+
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
|
|
425
|
+
cmd.writeUInt16LE(
|
|
426
|
+
this._isExtended
|
|
427
|
+
? LE_SET_EXTENDED_SCAN_PARAMETERS_CMD
|
|
428
|
+
: LE_SET_SCAN_PARAMETERS_CMD,
|
|
429
|
+
1
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
if (this._isExtended) {
|
|
433
|
+
// length
|
|
434
|
+
cmd.writeUInt8(0x0d, 3);
|
|
435
|
+
|
|
436
|
+
// data
|
|
437
|
+
cmd.writeUInt8(0x00, 4); // own address type: 0 -> public, 1 -> random
|
|
438
|
+
cmd.writeUInt8(0x00, 5); // filter: 0 -> all event types
|
|
439
|
+
cmd.writeUInt8(0x05, 6); // phy: LE 1M + LE CODED
|
|
440
|
+
// phy 1M
|
|
441
|
+
cmd.writeUInt8(0x01, 7); // type: 0 -> passive, 1 -> active
|
|
442
|
+
cmd.writeUInt16LE(interval, 8); // interval, ms * 1.6
|
|
443
|
+
cmd.writeUInt16LE(window, 10); // window, ms * 1.6
|
|
444
|
+
// phy coded
|
|
445
|
+
cmd.writeUInt8(0x01, 12); // type: 0 -> passive, 1 -> active
|
|
446
|
+
cmd.writeUInt16LE(interval, 13); // interval, ms * 1.6
|
|
447
|
+
cmd.writeUInt16LE(window, 15); // window, ms * 1.6
|
|
448
|
+
} else {
|
|
449
|
+
// length
|
|
450
|
+
cmd.writeUInt8(0x07, 3);
|
|
451
|
+
|
|
452
|
+
// data
|
|
453
|
+
cmd.writeUInt8(0x01, 4); // type: 0 -> passive, 1 -> active
|
|
454
|
+
cmd.writeUInt16LE(interval, 5); // interval, ms * 1.6
|
|
455
|
+
cmd.writeUInt16LE(window, 7); // window, ms * 1.6
|
|
456
|
+
cmd.writeUInt8(0x00, 9); // own address type: 0 -> public, 1 -> random
|
|
457
|
+
cmd.writeUInt8(0x00, 10); // filter: 0 -> all event types
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
debug(`set scan parameters - writing: ${cmd.toString('hex')}`);
|
|
461
|
+
this._socket.write(cmd);
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
Hci.prototype.setScanEnabled = function (enabled, filterDuplicates) {
|
|
465
|
+
const cmd = Buffer.alloc(this._isExtended ? 10 : 6);
|
|
466
|
+
|
|
467
|
+
// header
|
|
468
|
+
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
|
|
469
|
+
cmd.writeUInt16LE(
|
|
470
|
+
this._isExtended ? LE_SET_EXTENDED_SCAN_ENABLE_CMD : LE_SET_SCAN_ENABLE_CMD,
|
|
471
|
+
1
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
if (this._isExtended) {
|
|
475
|
+
// length
|
|
476
|
+
cmd.writeUInt8(0x06, 3);
|
|
477
|
+
|
|
478
|
+
// data
|
|
479
|
+
cmd.writeUInt8(enabled ? 0x01 : 0x00, 4); // enable: 0 -> disabled, 1 -> enabled
|
|
480
|
+
cmd.writeUInt8(filterDuplicates ? 0x01 : 0x00, 5); // duplicates: 0 -> duplicates, 1 -> all
|
|
481
|
+
cmd.writeUInt16LE(0x0000, 6); // duration
|
|
482
|
+
cmd.writeUInt16LE(0x0000, 8); // period
|
|
483
|
+
} else {
|
|
484
|
+
// length
|
|
485
|
+
cmd.writeUInt8(0x02, 3);
|
|
486
|
+
|
|
487
|
+
// data
|
|
488
|
+
cmd.writeUInt8(enabled ? 0x01 : 0x00, 4); // enable: 0 -> disabled, 1 -> enabled
|
|
489
|
+
cmd.writeUInt8(filterDuplicates ? 0x01 : 0x00, 5); // duplicates: 0 -> duplicates, 0 -> duplicates
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
debug(`set scan enabled - writing: ${cmd.toString('hex')}`);
|
|
493
|
+
this._socket.write(cmd);
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
Hci.prototype.createLeConn = function (address, addressType, parameters = {}) {
|
|
497
|
+
const {
|
|
498
|
+
minInterval = 0x0006,
|
|
499
|
+
maxInterval = 0x0012,
|
|
500
|
+
latency = 0x0000,
|
|
501
|
+
timeout = 0x002a
|
|
502
|
+
} = parameters;
|
|
503
|
+
|
|
504
|
+
const cmd = Buffer.alloc(this._isExtended ? 46 : 29);
|
|
505
|
+
|
|
506
|
+
// header
|
|
507
|
+
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
|
|
508
|
+
cmd.writeUInt16LE(
|
|
509
|
+
this._isExtended
|
|
510
|
+
? LE_CREATE_EXTENDED_CONN_CMD
|
|
511
|
+
: LE_CREATE_CONN_CMD,
|
|
512
|
+
1
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
if (this._isExtended) {
|
|
516
|
+
// length
|
|
517
|
+
cmd.writeUInt8(0x2a, 3);
|
|
518
|
+
|
|
519
|
+
// data
|
|
520
|
+
cmd.writeUInt8(0x00, 4); // filter policy: white list is not used
|
|
521
|
+
cmd.writeUInt8(0x00, 5); // own address type
|
|
522
|
+
cmd.writeUInt8(addressType === 'random' ? 0x01 : 0x00, 6); // peer address type
|
|
523
|
+
Buffer.from(address.split(':').reverse().join(''), 'hex').copy(cmd, 7); // peer address
|
|
524
|
+
cmd.writeUInt8(0x05, 13); // initiating PHYs: LE 1M + LE Coded
|
|
525
|
+
|
|
526
|
+
// LE 1M PHY
|
|
527
|
+
cmd.writeUInt16LE(0x0060, 14); // scan interval 60 msec
|
|
528
|
+
cmd.writeUInt16LE(0x0060, 16); // scan window 60 msec
|
|
529
|
+
cmd.writeUInt16LE(minInterval, 18); // min interval
|
|
530
|
+
cmd.writeUInt16LE(maxInterval, 20); // max interval
|
|
531
|
+
cmd.writeUInt16LE(latency, 22); // latency
|
|
532
|
+
cmd.writeUInt16LE(timeout, 24); // supervision timeout
|
|
533
|
+
cmd.writeUInt16LE(0x0000, 26); // min ce length
|
|
534
|
+
cmd.writeUInt16LE(0x0000, 28); // max ce length
|
|
535
|
+
|
|
536
|
+
// LE Coded PHY
|
|
537
|
+
cmd.writeUInt16LE(0x0060, 30); // scan interval 60 msec
|
|
538
|
+
cmd.writeUInt16LE(0x0060, 32); // scan window 60 msec
|
|
539
|
+
cmd.writeUInt16LE(minInterval, 34); // min interval
|
|
540
|
+
cmd.writeUInt16LE(maxInterval, 36); // max interval
|
|
541
|
+
cmd.writeUInt16LE(latency, 38); // latency
|
|
542
|
+
cmd.writeUInt16LE(timeout, 40); // supervision timeout
|
|
543
|
+
cmd.writeUInt16LE(0x0000, 42); // min ce length
|
|
544
|
+
cmd.writeUInt16LE(0x0000, 44); // max ce length
|
|
545
|
+
} else {
|
|
546
|
+
// length
|
|
547
|
+
cmd.writeUInt8(0x19, 3);
|
|
548
|
+
|
|
549
|
+
// data
|
|
550
|
+
cmd.writeUInt16LE(0x0060, 4); // interval
|
|
551
|
+
cmd.writeUInt16LE(0x0030, 6); // window
|
|
552
|
+
cmd.writeUInt8(0x00, 8); // initiator filter
|
|
553
|
+
|
|
554
|
+
cmd.writeUInt8(addressType === 'random' ? 0x01 : 0x00, 9); // peer address type
|
|
555
|
+
Buffer.from(address.split(':').reverse().join(''), 'hex').copy(cmd, 10); // peer address
|
|
556
|
+
|
|
557
|
+
cmd.writeUInt8(0x00, 16); // own address type
|
|
558
|
+
|
|
559
|
+
cmd.writeUInt16LE(minInterval, 17); // min interval
|
|
560
|
+
cmd.writeUInt16LE(maxInterval, 19); // max interval
|
|
561
|
+
cmd.writeUInt16LE(latency, 21); // latency
|
|
562
|
+
cmd.writeUInt16LE(timeout, 23); // supervision timeout
|
|
563
|
+
cmd.writeUInt16LE(0x0004, 25); // min ce length
|
|
564
|
+
cmd.writeUInt16LE(0x0006, 27); // max ce length
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
debug(`create le conn - writing: ${cmd.toString('hex')}`);
|
|
568
|
+
this._socket.write(cmd);
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
Hci.prototype.connUpdateLe = function (
|
|
572
|
+
handle,
|
|
573
|
+
minInterval,
|
|
574
|
+
maxInterval,
|
|
575
|
+
latency,
|
|
576
|
+
supervisionTimeout
|
|
577
|
+
) {
|
|
578
|
+
const cmd = Buffer.alloc(18);
|
|
579
|
+
|
|
580
|
+
// header
|
|
581
|
+
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
|
|
582
|
+
cmd.writeUInt16LE(LE_CONN_UPDATE_CMD, 1);
|
|
583
|
+
|
|
584
|
+
// length
|
|
585
|
+
cmd.writeUInt8(0x0e, 3);
|
|
586
|
+
|
|
587
|
+
// data
|
|
588
|
+
cmd.writeUInt16LE(handle, 4);
|
|
589
|
+
cmd.writeUInt16LE(Math.floor(minInterval / 1.25), 6); // min interval
|
|
590
|
+
cmd.writeUInt16LE(Math.floor(maxInterval / 1.25), 8); // max interval
|
|
591
|
+
cmd.writeUInt16LE(latency, 10); // latency
|
|
592
|
+
cmd.writeUInt16LE(Math.floor(supervisionTimeout / 10), 12); // supervision timeout
|
|
593
|
+
cmd.writeUInt16LE(0x0000, 14); // min ce length
|
|
594
|
+
cmd.writeUInt16LE(0x0000, 16); // max ce length
|
|
595
|
+
|
|
596
|
+
debug(`conn update le - writing: ${cmd.toString('hex')}`);
|
|
597
|
+
this._socket.write(cmd);
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
Hci.prototype.cancelConnect = function () {
|
|
601
|
+
const cmd = Buffer.alloc(4);
|
|
602
|
+
|
|
603
|
+
// header
|
|
604
|
+
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
|
|
605
|
+
cmd.writeUInt16LE(LE_CANCEL_CONN_CMD, 1);
|
|
606
|
+
|
|
607
|
+
// length
|
|
608
|
+
cmd.writeUInt8(0x0, 3);
|
|
609
|
+
|
|
610
|
+
debug('cancel le conn - writing: ' + cmd.toString('hex'));
|
|
611
|
+
this._socket.write(cmd);
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
Hci.prototype.startLeEncryption = function (handle, random, diversifier, key) {
|
|
615
|
+
const cmd = Buffer.alloc(32);
|
|
616
|
+
|
|
617
|
+
// header
|
|
618
|
+
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
|
|
619
|
+
cmd.writeUInt16LE(LE_START_ENCRYPTION_CMD, 1);
|
|
620
|
+
|
|
621
|
+
// length
|
|
622
|
+
cmd.writeUInt8(0x1c, 3);
|
|
623
|
+
|
|
624
|
+
// data
|
|
625
|
+
cmd.writeUInt16LE(handle, 4); // handle
|
|
626
|
+
random.copy(cmd, 6);
|
|
627
|
+
diversifier.copy(cmd, 14);
|
|
628
|
+
key.copy(cmd, 16);
|
|
629
|
+
|
|
630
|
+
debug(`start le encryption - writing: ${cmd.toString('hex')}`);
|
|
631
|
+
this._socket.write(cmd);
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
Hci.prototype.disconnect = function (handle, reason) {
|
|
635
|
+
const cmd = Buffer.alloc(7);
|
|
636
|
+
|
|
637
|
+
reason = reason || HCI_OE_USER_ENDED_CONNECTION;
|
|
638
|
+
|
|
639
|
+
// header
|
|
640
|
+
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
|
|
641
|
+
cmd.writeUInt16LE(DISCONNECT_CMD, 1);
|
|
642
|
+
|
|
643
|
+
// length
|
|
644
|
+
cmd.writeUInt8(0x03, 3);
|
|
645
|
+
|
|
646
|
+
// data
|
|
647
|
+
cmd.writeUInt16LE(handle, 4); // handle
|
|
648
|
+
cmd.writeUInt8(reason, 6); // reason
|
|
649
|
+
|
|
650
|
+
debug(`disconnect - writing: ${cmd.toString('hex')}`);
|
|
651
|
+
this._socket.write(cmd);
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
Hci.prototype.readRssi = function (handle) {
|
|
655
|
+
const cmd = Buffer.alloc(6);
|
|
656
|
+
|
|
657
|
+
// header
|
|
658
|
+
cmd.writeUInt8(HCI_COMMAND_PKT, 0);
|
|
659
|
+
cmd.writeUInt16LE(READ_RSSI_CMD, 1);
|
|
660
|
+
|
|
661
|
+
// length
|
|
662
|
+
cmd.writeUInt8(0x02, 3);
|
|
663
|
+
|
|
664
|
+
// data
|
|
665
|
+
cmd.writeUInt16LE(handle, 4); // handle
|
|
666
|
+
|
|
667
|
+
debug(`read rssi - writing: ${cmd.toString('hex')}`);
|
|
668
|
+
this._socket.write(cmd);
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
Hci.prototype.writeAclDataPkt = async function (handle, cid, data) {
|
|
672
|
+
const l2capLength = 4 /* l2cap header */ + data.length;
|
|
673
|
+
|
|
674
|
+
const aclBuffers = await this.getAclBuffers();
|
|
675
|
+
const aclLength = Math.min(l2capLength, aclBuffers.length);
|
|
676
|
+
|
|
677
|
+
const minFirstLength = Math.max(aclLength + 5 /* acl header */, 9 /* prevent buffer overflow */);
|
|
678
|
+
const first = Buffer.alloc(minFirstLength);
|
|
679
|
+
|
|
680
|
+
// acl header
|
|
681
|
+
first.writeUInt8(HCI_ACLDATA_PKT, 0);
|
|
682
|
+
first.writeUInt16LE(handle | (ACL_START_NO_FLUSH << 12), 1);
|
|
683
|
+
first.writeUInt16LE(aclLength, 3);
|
|
684
|
+
|
|
685
|
+
// l2cap header
|
|
686
|
+
first.writeUInt16LE(data.length, 5);
|
|
687
|
+
first.writeUInt16LE(cid, 7);
|
|
688
|
+
|
|
689
|
+
data.copy(first, 9);
|
|
690
|
+
data = data.slice(first.length - 9);
|
|
691
|
+
|
|
692
|
+
debug(`push to acl queue: ${first.toString('hex')}`);
|
|
693
|
+
this._aclQueue.push({ handle, packet: first });
|
|
694
|
+
|
|
695
|
+
while (data.length > 0) {
|
|
696
|
+
const fragAclLength = Math.min(data.length, aclBuffers.length);
|
|
697
|
+
const frag = Buffer.alloc(fragAclLength + 5 /* acl header */);
|
|
698
|
+
// acl header
|
|
699
|
+
frag.writeUInt8(HCI_ACLDATA_PKT, 0);
|
|
700
|
+
frag.writeUInt16LE(handle | (ACL_CONT << 12), 1);
|
|
701
|
+
frag.writeUInt16LE(fragAclLength, 3);
|
|
702
|
+
|
|
703
|
+
data.copy(frag, 5);
|
|
704
|
+
data = data.slice(frag.length - 5);
|
|
705
|
+
|
|
706
|
+
debug(`push fragment to acl queue: ${frag.toString('hex')}`);
|
|
707
|
+
this._aclQueue.push({ handle, packet: frag });
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
this.flushAcl();
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
Hci.prototype.flushAcl = async function () {
|
|
714
|
+
const pendingPackets = () => {
|
|
715
|
+
let totalPending = 0;
|
|
716
|
+
for (const { pending } of this._aclConnections.values()) {
|
|
717
|
+
totalPending += pending;
|
|
718
|
+
}
|
|
719
|
+
return totalPending;
|
|
720
|
+
};
|
|
721
|
+
|
|
722
|
+
debug(
|
|
723
|
+
`flush - pending: ${pendingPackets()} queue length: ${
|
|
724
|
+
this._aclQueue.length
|
|
725
|
+
}`
|
|
726
|
+
);
|
|
727
|
+
|
|
728
|
+
const aclBuffers = await this.getAclBuffers();
|
|
729
|
+
while (this._aclQueue.length > 0 && pendingPackets() < aclBuffers.num) {
|
|
730
|
+
const { handle, packet } = this._aclQueue.shift();
|
|
731
|
+
this._aclConnections.get(handle).pending++;
|
|
732
|
+
debug(`write acl data packet - writing: ${packet.toString('hex')}`);
|
|
733
|
+
this._socket.write(packet);
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
Hci.prototype.onSocketData = function (data) {
|
|
738
|
+
debug(`onSocketData: ${data.toString('hex')}`);
|
|
739
|
+
|
|
740
|
+
const eventType = data.readUInt8(0);
|
|
741
|
+
let handle;
|
|
742
|
+
let cmd;
|
|
743
|
+
let status;
|
|
744
|
+
|
|
745
|
+
debug(`\tevent type = ${eventType}`);
|
|
746
|
+
|
|
747
|
+
if (HCI_EVENT_PKT === eventType) {
|
|
748
|
+
const subEventType = data.readUInt8(1);
|
|
749
|
+
|
|
750
|
+
debug(`\tsub event type = ${subEventType}`);
|
|
751
|
+
|
|
752
|
+
if (subEventType === EVT_DISCONN_COMPLETE) {
|
|
753
|
+
handle = data.readUInt16LE(4);
|
|
754
|
+
const reason = data.readUInt8(6);
|
|
755
|
+
|
|
756
|
+
debug(`\t\thandle = ${handle}`);
|
|
757
|
+
debug(`\t\treason = ${reason}`);
|
|
758
|
+
|
|
759
|
+
this._aclQueue = this._aclQueue.filter((acl) => acl.handle !== handle);
|
|
760
|
+
this._aclConnections.delete(handle);
|
|
761
|
+
this.flushAcl();
|
|
762
|
+
|
|
763
|
+
this.emit('disconnComplete', handle, reason);
|
|
764
|
+
} else if (subEventType === EVT_ENCRYPT_CHANGE) {
|
|
765
|
+
handle = data.readUInt16LE(4);
|
|
766
|
+
const encrypt = data.readUInt8(6);
|
|
767
|
+
|
|
768
|
+
debug(`\t\thandle = ${handle}`);
|
|
769
|
+
debug(`\t\tencrypt = ${encrypt}`);
|
|
770
|
+
|
|
771
|
+
this.emit('encryptChange', handle, encrypt);
|
|
772
|
+
} else if (subEventType === EVT_CMD_COMPLETE) {
|
|
773
|
+
cmd = data.readUInt16LE(4);
|
|
774
|
+
status = data.readUInt8(6);
|
|
775
|
+
const result = data.slice(7);
|
|
776
|
+
|
|
777
|
+
debug(`\t\tcmd = ${cmd}`);
|
|
778
|
+
debug(`\t\tstatus = ${status}`);
|
|
779
|
+
debug(`\t\tresult = ${result.toString('hex')}`);
|
|
780
|
+
|
|
781
|
+
this.processCmdCompleteEvent(cmd, status, result);
|
|
782
|
+
} else if (subEventType === EVT_CMD_STATUS) {
|
|
783
|
+
status = data.readUInt8(3);
|
|
784
|
+
cmd = data.readUInt16LE(5);
|
|
785
|
+
|
|
786
|
+
debug(`\t\tstatus = ${status}`);
|
|
787
|
+
debug(`\t\tcmd = ${cmd}`);
|
|
788
|
+
|
|
789
|
+
this.processCmdStatusEvent(cmd, status);
|
|
790
|
+
} else if (subEventType === EVT_LE_META_EVENT) {
|
|
791
|
+
const leMetaEventLength = data.readUInt8(2);
|
|
792
|
+
const leMetaEventType = data.readUInt8(3);
|
|
793
|
+
const leMetaEventNumReports = data.readUInt8(4);
|
|
794
|
+
const leMetaEventData = data.slice(5);
|
|
795
|
+
|
|
796
|
+
debug(`\t\tLE meta event type = ${leMetaEventType}`);
|
|
797
|
+
debug(`\t\tLE meta event data length = ${leMetaEventLength}`);
|
|
798
|
+
debug(`\t\tLE meta event num reports = ${leMetaEventNumReports}`);
|
|
799
|
+
debug(`\t\tLE meta event data = ${leMetaEventData.toString('hex')}`);
|
|
800
|
+
|
|
801
|
+
this.processLeMetaEvent(
|
|
802
|
+
leMetaEventType,
|
|
803
|
+
leMetaEventNumReports,
|
|
804
|
+
leMetaEventData
|
|
805
|
+
);
|
|
806
|
+
} else if (subEventType === EVT_NUMBER_OF_COMPLETED_PACKETS) {
|
|
807
|
+
const handles = data.readUInt8(3);
|
|
808
|
+
for (let h = 0; h < handles; h++) {
|
|
809
|
+
const handle = data.readUInt16LE(4 + h * 4);
|
|
810
|
+
const pkts = data.readUInt16LE(6 + h * 4);
|
|
811
|
+
debug(`\thandle = ${handle}`);
|
|
812
|
+
debug(`\t\tcompleted = ${pkts}`);
|
|
813
|
+
|
|
814
|
+
if (!this._aclConnections.has(handle)) {
|
|
815
|
+
debug('\t\tclosed');
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
const connection = this._aclConnections.get(handle);
|
|
820
|
+
|
|
821
|
+
connection.pending -= pkts;
|
|
822
|
+
|
|
823
|
+
if (connection.pending < 0) {
|
|
824
|
+
connection.pending = 0;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
this.flushAcl();
|
|
828
|
+
}
|
|
829
|
+
} else if (HCI_ACLDATA_PKT === eventType) {
|
|
830
|
+
const flags = data.readUInt16LE(1) >> 12;
|
|
831
|
+
handle = data.readUInt16LE(1) & 0x0fff;
|
|
832
|
+
|
|
833
|
+
if (ACL_START === flags) {
|
|
834
|
+
const cid = data.readUInt16LE(7);
|
|
835
|
+
|
|
836
|
+
const length = data.readUInt16LE(5);
|
|
837
|
+
const pktData = data.slice(9);
|
|
838
|
+
|
|
839
|
+
debug(`\t\tcid = ${cid}`);
|
|
840
|
+
|
|
841
|
+
if (length === pktData.length) {
|
|
842
|
+
debug(`\t\thandle = ${handle}`);
|
|
843
|
+
debug(`\t\tdata = ${pktData.toString('hex')}`);
|
|
844
|
+
|
|
845
|
+
this.emit('aclDataPkt', handle, cid, pktData);
|
|
846
|
+
} else {
|
|
847
|
+
this._handleBuffers[handle] = {
|
|
848
|
+
length: length,
|
|
849
|
+
cid: cid,
|
|
850
|
+
data: pktData
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
} else if (ACL_CONT === flags) {
|
|
854
|
+
if (!this._handleBuffers[handle] || !this._handleBuffers[handle].data) {
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
this._handleBuffers[handle].data = Buffer.concat([
|
|
859
|
+
this._handleBuffers[handle].data,
|
|
860
|
+
data.slice(5)
|
|
861
|
+
]);
|
|
862
|
+
|
|
863
|
+
if (
|
|
864
|
+
this._handleBuffers[handle].data.length ===
|
|
865
|
+
this._handleBuffers[handle].length
|
|
866
|
+
) {
|
|
867
|
+
this.emit(
|
|
868
|
+
'aclDataPkt',
|
|
869
|
+
handle,
|
|
870
|
+
this._handleBuffers[handle].cid,
|
|
871
|
+
this._handleBuffers[handle].data
|
|
872
|
+
);
|
|
873
|
+
|
|
874
|
+
delete this._handleBuffers[handle];
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
} else if (HCI_COMMAND_PKT === eventType) {
|
|
878
|
+
cmd = data.readUInt16LE(1);
|
|
879
|
+
const len = data.readUInt8(3);
|
|
880
|
+
|
|
881
|
+
debug(`\t\tcmd = ${cmd}`);
|
|
882
|
+
debug(`\t\tdata len = ${len}`);
|
|
883
|
+
|
|
884
|
+
if (
|
|
885
|
+
cmd === LE_SET_SCAN_ENABLE_CMD ||
|
|
886
|
+
cmd === LE_SET_EXTENDED_SCAN_ENABLE_CMD
|
|
887
|
+
) {
|
|
888
|
+
const enable = data.readUInt8(4) === 0x1;
|
|
889
|
+
const filterDuplicates = data.readUInt8(5) === 0x1;
|
|
890
|
+
|
|
891
|
+
debug('\t\t\tLE enable scan command');
|
|
892
|
+
debug(`\t\t\tenable scanning = ${enable}`);
|
|
893
|
+
debug(`\t\t\tfilter duplicates = ${filterDuplicates}`);
|
|
894
|
+
|
|
895
|
+
this.emit('leScanEnableSetCmd', enable, filterDuplicates);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
|
|
900
|
+
Hci.prototype.onSocketError = function (error) {
|
|
901
|
+
debug(`onSocketError: ${error.message}`);
|
|
902
|
+
|
|
903
|
+
if (error.code === 'EPERM') {
|
|
904
|
+
this.emit('stateChange', 'unauthorized');
|
|
905
|
+
} else if (error.message === 'Network is down') {
|
|
906
|
+
// no-op
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
|
|
910
|
+
Hci.prototype.processCmdCompleteEvent = function (cmd, status, result) {
|
|
911
|
+
if (cmd === RESET_CMD) {
|
|
912
|
+
if (this._isExtended) {
|
|
913
|
+
this.setCodedPhySupport();
|
|
914
|
+
}
|
|
915
|
+
this.setEventMask();
|
|
916
|
+
this.setLeEventMask();
|
|
917
|
+
this.readLocalVersion();
|
|
918
|
+
this.readBdAddr();
|
|
919
|
+
} else if (cmd === READ_LE_HOST_SUPPORTED_CMD) {
|
|
920
|
+
if (status === 0) {
|
|
921
|
+
const le = result.readUInt8(0);
|
|
922
|
+
const simul = result.readUInt8(1);
|
|
923
|
+
|
|
924
|
+
debug(`\t\t\tle = ${le}`);
|
|
925
|
+
debug(`\t\t\tsimul = ${simul}`);
|
|
926
|
+
}
|
|
927
|
+
} else if (cmd === READ_LOCAL_VERSION_CMD) {
|
|
928
|
+
const hciVer = result.readUInt8(0);
|
|
929
|
+
const hciRev = result.readUInt16LE(1);
|
|
930
|
+
const lmpVer = result.readInt8(3);
|
|
931
|
+
const manufacturer = result.readUInt16LE(4);
|
|
932
|
+
const lmpSubVer = result.readUInt16LE(6);
|
|
933
|
+
|
|
934
|
+
if (hciVer < 0x06) {
|
|
935
|
+
this.emit('stateChange', 'unsupported');
|
|
936
|
+
} else if (this._state !== 'poweredOn') {
|
|
937
|
+
this.setScanEnabled(false, true);
|
|
938
|
+
this.setScanParameters();
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
this.emit(
|
|
942
|
+
'readLocalVersion',
|
|
943
|
+
hciVer,
|
|
944
|
+
hciRev,
|
|
945
|
+
lmpVer,
|
|
946
|
+
manufacturer,
|
|
947
|
+
lmpSubVer
|
|
948
|
+
);
|
|
949
|
+
} else if (cmd === READ_SUPPORTED_COMMANDS_CMD) {
|
|
950
|
+
const extendedScanParameters = result.readUInt8(37) & 0x10; // LE Set Extended Scan Parameters (Octet 37 - Bit 5)
|
|
951
|
+
const extendedScan = result.readUInt8(37) & 0x20; // LE Set Extended Scan Enable (Octet 37 - Bit 6)
|
|
952
|
+
|
|
953
|
+
debug(
|
|
954
|
+
`Extended advertising features: parameters = ${
|
|
955
|
+
extendedScanParameters ? 'true' : 'false'
|
|
956
|
+
}, num = ${extendedScan ? 'true' : 'false'}`
|
|
957
|
+
);
|
|
958
|
+
|
|
959
|
+
this._isExtended = extendedScanParameters && extendedScan;
|
|
960
|
+
} else if (cmd === READ_BD_ADDR_CMD) {
|
|
961
|
+
this.addressType = 'public';
|
|
962
|
+
this.address = result
|
|
963
|
+
.toString('hex')
|
|
964
|
+
.match(/.{1,2}/g)
|
|
965
|
+
.reverse()
|
|
966
|
+
.join(':');
|
|
967
|
+
|
|
968
|
+
debug(`address = ${this.address}`);
|
|
969
|
+
|
|
970
|
+
this.emit('addressChange', this.address);
|
|
971
|
+
} else if (
|
|
972
|
+
cmd === LE_SET_SCAN_PARAMETERS_CMD ||
|
|
973
|
+
cmd === LE_SET_EXTENDED_SCAN_PARAMETERS_CMD
|
|
974
|
+
) {
|
|
975
|
+
this.emit('stateChange', 'poweredOn');
|
|
976
|
+
|
|
977
|
+
this.emit('leScanParametersSet');
|
|
978
|
+
} else if (
|
|
979
|
+
cmd === LE_SET_SCAN_ENABLE_CMD ||
|
|
980
|
+
cmd === LE_SET_EXTENDED_SCAN_ENABLE_CMD
|
|
981
|
+
) {
|
|
982
|
+
this.emit('leScanEnableSet', status);
|
|
983
|
+
} else if (cmd === READ_RSSI_CMD) {
|
|
984
|
+
const handle = result.readUInt16LE(0);
|
|
985
|
+
const rssi = result.readInt8(2);
|
|
986
|
+
|
|
987
|
+
debug(`\t\t\thandle = ${handle}`);
|
|
988
|
+
debug(`\t\t\trssi = ${rssi}`);
|
|
989
|
+
|
|
990
|
+
this.emit('rssiRead', handle, rssi);
|
|
991
|
+
} else if (cmd === LE_READ_BUFFER_SIZE_CMD) {
|
|
992
|
+
if (status === 0) {
|
|
993
|
+
const aclLength = result.readUInt16LE(0);
|
|
994
|
+
const aclNum = result.readUInt8(2);
|
|
995
|
+
|
|
996
|
+
/* Spec Vol 4 Part E.7.8
|
|
997
|
+
/* No dedicated LE Buffer exists. Use the HCI_Read_Buffer_Size command. */
|
|
998
|
+
if (aclLength === 0 || aclNum === 0) {
|
|
999
|
+
debug('using br/edr buffer size');
|
|
1000
|
+
this.readBufferSize();
|
|
1001
|
+
} else {
|
|
1002
|
+
debug(`le buffer size: length = ${aclLength}, num = ${aclNum}`);
|
|
1003
|
+
this.setAclBuffers(aclLength, aclNum);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
} else if (cmd === READ_BUFFER_SIZE_CMD) {
|
|
1007
|
+
const aclLength = result.readUInt16LE(0);
|
|
1008
|
+
const aclNum = result.readUInt16LE(3);
|
|
1009
|
+
|
|
1010
|
+
debug(`buffer size: length = ${aclLength}, num = ${aclNum}`);
|
|
1011
|
+
this.setAclBuffers(aclLength, aclNum);
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
|
|
1015
|
+
Hci.prototype.processLeMetaEvent = function (eventType, numReports, data) {
|
|
1016
|
+
if (eventType === EVT_LE_CONN_COMPLETE) {
|
|
1017
|
+
this.processLeConnComplete(numReports, data);
|
|
1018
|
+
} else if (eventType === EVT_LE_ENHANCED_CONN_COMPLETE) {
|
|
1019
|
+
this.processLeEnhancedConnComplete(numReports, data);
|
|
1020
|
+
} else if (eventType === EVT_LE_ADVERTISING_REPORT) {
|
|
1021
|
+
this.processLeAdvertisingReport(numReports, data);
|
|
1022
|
+
} else if (eventType === EVT_LE_EXTENDED_ADVERTISING_REPORT) {
|
|
1023
|
+
this.processLeExtendedAdvertisingReport(numReports, data);
|
|
1024
|
+
} else if (eventType === EVT_LE_CONN_UPDATE_COMPLETE) {
|
|
1025
|
+
this.processLeConnUpdateComplete(numReports, data);
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
|
|
1029
|
+
Hci.prototype.processLeConnComplete = function (status, data) {
|
|
1030
|
+
const handle = data.readUInt16LE(0);
|
|
1031
|
+
const role = data.readUInt8(2);
|
|
1032
|
+
const addressType = data.readUInt8(3) === 0x01 ? 'random' : 'public';
|
|
1033
|
+
const address = data
|
|
1034
|
+
.slice(4, 10)
|
|
1035
|
+
.toString('hex')
|
|
1036
|
+
.match(/.{1,2}/g)
|
|
1037
|
+
.reverse()
|
|
1038
|
+
.join(':');
|
|
1039
|
+
const interval = data.readUInt16LE(10) * 1.25;
|
|
1040
|
+
const latency = data.readUInt16LE(12); // TODO: multiplier?
|
|
1041
|
+
const supervisionTimeout = data.readUInt16LE(14) * 10;
|
|
1042
|
+
const masterClockAccuracy = data.readUInt8(16); // TODO: multiplier?
|
|
1043
|
+
|
|
1044
|
+
debug(`\t\t\thandle = ${handle}`);
|
|
1045
|
+
debug(`\t\t\trole = ${role}`);
|
|
1046
|
+
debug(`\t\t\taddress type = ${addressType}`);
|
|
1047
|
+
debug(`\t\t\taddress = ${address}`);
|
|
1048
|
+
debug(`\t\t\tinterval = ${interval}`);
|
|
1049
|
+
debug(`\t\t\tlatency = ${latency}`);
|
|
1050
|
+
debug(`\t\t\tsupervision timeout = ${supervisionTimeout}`);
|
|
1051
|
+
debug(`\t\t\tmaster clock accuracy = ${masterClockAccuracy}`);
|
|
1052
|
+
|
|
1053
|
+
this._aclConnections.set(handle, { pending: 0 });
|
|
1054
|
+
|
|
1055
|
+
this.emit(
|
|
1056
|
+
'leConnComplete',
|
|
1057
|
+
status,
|
|
1058
|
+
handle,
|
|
1059
|
+
role,
|
|
1060
|
+
addressType,
|
|
1061
|
+
address,
|
|
1062
|
+
interval,
|
|
1063
|
+
latency,
|
|
1064
|
+
supervisionTimeout,
|
|
1065
|
+
masterClockAccuracy
|
|
1066
|
+
);
|
|
1067
|
+
};
|
|
1068
|
+
|
|
1069
|
+
Hci.prototype.processLeEnhancedConnComplete = function (status, data) {
|
|
1070
|
+
const handle = data.readUInt16LE(0);
|
|
1071
|
+
const role = data.readUInt8(2);
|
|
1072
|
+
const addressType = data.readUInt8(3) === 0x01 ? 'random' : 'public';
|
|
1073
|
+
const address = data
|
|
1074
|
+
.slice(4, 10)
|
|
1075
|
+
.toString('hex')
|
|
1076
|
+
.match(/.{1,2}/g)
|
|
1077
|
+
.reverse()
|
|
1078
|
+
.join(':');
|
|
1079
|
+
const localResolvablePrivateAddress = data
|
|
1080
|
+
.slice(10, 16)
|
|
1081
|
+
.toString('hex')
|
|
1082
|
+
.match(/.{1,2}/g)
|
|
1083
|
+
.reverse()
|
|
1084
|
+
.join(':');
|
|
1085
|
+
const peerResolvablePrivateAddress = data
|
|
1086
|
+
.slice(16, 22)
|
|
1087
|
+
.toString('hex')
|
|
1088
|
+
.match(/.{1,2}/g)
|
|
1089
|
+
.reverse()
|
|
1090
|
+
.join(':');
|
|
1091
|
+
const interval = data.readUInt16LE(22) * 1.25;
|
|
1092
|
+
const latency = data.readUInt16LE(24); // TODO: multiplier?
|
|
1093
|
+
const supervisionTimeout = data.readUInt16LE(26) * 10;
|
|
1094
|
+
const masterClockAccuracy = data.readUInt8(28); // TODO: multiplier?
|
|
1095
|
+
|
|
1096
|
+
debug(`\t\t\thandle = ${handle}`);
|
|
1097
|
+
debug(`\t\t\trole = ${role}`);
|
|
1098
|
+
debug(`\t\t\taddress type = ${addressType}`);
|
|
1099
|
+
debug(`\t\t\taddress = ${address}`);
|
|
1100
|
+
debug(
|
|
1101
|
+
`\t\t\tLocal resolvable private address = ${localResolvablePrivateAddress}`
|
|
1102
|
+
);
|
|
1103
|
+
debug(
|
|
1104
|
+
`\t\t\tPeer resolvable private address = ${peerResolvablePrivateAddress}`
|
|
1105
|
+
);
|
|
1106
|
+
debug(`\t\t\tinterval = ${interval}`);
|
|
1107
|
+
debug(`\t\t\tlatency = ${latency}`);
|
|
1108
|
+
debug(`\t\t\tsupervision timeout = ${supervisionTimeout}`);
|
|
1109
|
+
debug(`\t\t\tmaster clock accuracy = ${masterClockAccuracy}`);
|
|
1110
|
+
|
|
1111
|
+
this._aclConnections.set(handle, { pending: 0 });
|
|
1112
|
+
|
|
1113
|
+
this.emit(
|
|
1114
|
+
'leConnComplete',
|
|
1115
|
+
status,
|
|
1116
|
+
handle,
|
|
1117
|
+
role,
|
|
1118
|
+
addressType,
|
|
1119
|
+
address,
|
|
1120
|
+
interval,
|
|
1121
|
+
latency,
|
|
1122
|
+
supervisionTimeout,
|
|
1123
|
+
masterClockAccuracy
|
|
1124
|
+
);
|
|
1125
|
+
};
|
|
1126
|
+
|
|
1127
|
+
Hci.prototype.processLeAdvertisingReport = function (numReports, data) {
|
|
1128
|
+
try {
|
|
1129
|
+
for (let i = 0; i < numReports; i++) {
|
|
1130
|
+
const type = data.readUInt8(0);
|
|
1131
|
+
const addressType = data.readUInt8(1) === 0x01 ? 'random' : 'public';
|
|
1132
|
+
const address = data
|
|
1133
|
+
.slice(2, 8)
|
|
1134
|
+
.toString('hex')
|
|
1135
|
+
.match(/.{1,2}/g)
|
|
1136
|
+
.reverse()
|
|
1137
|
+
.join(':');
|
|
1138
|
+
const eirLength = data.readUInt8(8);
|
|
1139
|
+
const eir = data.slice(9, eirLength + 9);
|
|
1140
|
+
const rssi = data.readInt8(eirLength + 9);
|
|
1141
|
+
|
|
1142
|
+
debug(`\t\t\ttype = ${type}`);
|
|
1143
|
+
debug(`\t\t\taddress = ${address}`);
|
|
1144
|
+
debug(`\t\t\taddress type = ${addressType}`);
|
|
1145
|
+
debug(`\t\t\teir = ${eir.toString('hex')}`);
|
|
1146
|
+
debug(`\t\t\trssi = ${rssi}`);
|
|
1147
|
+
|
|
1148
|
+
this.emit(
|
|
1149
|
+
'leAdvertisingReport',
|
|
1150
|
+
0,
|
|
1151
|
+
type,
|
|
1152
|
+
address,
|
|
1153
|
+
addressType,
|
|
1154
|
+
eir,
|
|
1155
|
+
rssi
|
|
1156
|
+
);
|
|
1157
|
+
|
|
1158
|
+
data = data.slice(eirLength + 10);
|
|
1159
|
+
}
|
|
1160
|
+
} catch (e) {
|
|
1161
|
+
console.warn(
|
|
1162
|
+
`processLeAdvertisingReport: Caught illegal packet (buffer overflow): ${e}`
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
|
|
1167
|
+
Hci.prototype.processLeExtendedAdvertisingReport = function (numReports, data) {
|
|
1168
|
+
try {
|
|
1169
|
+
for (let i = 0; i < numReports; i++) {
|
|
1170
|
+
const type = data.readUInt16LE(0);
|
|
1171
|
+
const addressType = data.readUInt8(2) === 0x01 ? 'random' : 'public';
|
|
1172
|
+
const address = data
|
|
1173
|
+
.slice(3, 9)
|
|
1174
|
+
.toString('hex')
|
|
1175
|
+
.match(/.{1,2}/g)
|
|
1176
|
+
.reverse()
|
|
1177
|
+
.join(':');
|
|
1178
|
+
const primaryPHY = data.readUInt8(9);
|
|
1179
|
+
const secondaryPHY = data.readUInt8(10);
|
|
1180
|
+
const sid = data.readUInt8(11);
|
|
1181
|
+
const txpower = data.readUInt8(12);
|
|
1182
|
+
const rssi = data.readInt8(13);
|
|
1183
|
+
const periodicAdvInterval = data.readUInt16LE(14);
|
|
1184
|
+
const directAddressType =
|
|
1185
|
+
data.readUInt8(16) === 0x01 ? 'random' : 'public';
|
|
1186
|
+
const directAddress = data
|
|
1187
|
+
.slice(17, 23)
|
|
1188
|
+
.toString('hex')
|
|
1189
|
+
.match(/.{1,2}/g)
|
|
1190
|
+
.reverse()
|
|
1191
|
+
.join(':');
|
|
1192
|
+
const eirLength = data.readUInt8(23);
|
|
1193
|
+
const eir = data.slice(24);
|
|
1194
|
+
|
|
1195
|
+
debug(`\t\t\ttype = ${type}`);
|
|
1196
|
+
debug(`\t\t\taddress = ${address}`);
|
|
1197
|
+
debug(`\t\t\taddress type = ${addressType}`);
|
|
1198
|
+
debug(`\t\t\tprimary phy = ${primaryPHY.toString(16)}`);
|
|
1199
|
+
debug(`\t\t\tsecondary phy = ${secondaryPHY.toString(16)}`);
|
|
1200
|
+
debug(`\t\t\tSID = ${sid.toString(16)}`);
|
|
1201
|
+
debug(`\t\t\tTX power = ${txpower}`);
|
|
1202
|
+
debug(`\t\t\tRSSI = ${rssi}`);
|
|
1203
|
+
debug(
|
|
1204
|
+
`\t\t\tperiodic advertising interval = ${periodicAdvInterval} msec`
|
|
1205
|
+
);
|
|
1206
|
+
debug(`\t\t\tdirect address type = ${directAddressType}`);
|
|
1207
|
+
debug(`\t\t\tdirect address = ${directAddress}`);
|
|
1208
|
+
debug(`\t\t\teir length = ${eirLength}`);
|
|
1209
|
+
debug(`\t\t\teir = ${eir.toString('hex')}`);
|
|
1210
|
+
|
|
1211
|
+
this.emit(
|
|
1212
|
+
'leExtendedAdvertisingReport',
|
|
1213
|
+
0,
|
|
1214
|
+
type,
|
|
1215
|
+
address,
|
|
1216
|
+
addressType,
|
|
1217
|
+
txpower,
|
|
1218
|
+
rssi,
|
|
1219
|
+
eir
|
|
1220
|
+
);
|
|
1221
|
+
|
|
1222
|
+
data = data.slice(eirLength + 24);
|
|
1223
|
+
}
|
|
1224
|
+
} catch (e) {
|
|
1225
|
+
console.warn(
|
|
1226
|
+
`processLeExtendedAdvertisingReport: Caught illegal packet (buffer overflow): ${e}`
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
};
|
|
1230
|
+
|
|
1231
|
+
Hci.prototype.processLeConnUpdateComplete = function (status, data) {
|
|
1232
|
+
const handle = data.readUInt16LE(0);
|
|
1233
|
+
const interval = data.readUInt16LE(2) * 1.25;
|
|
1234
|
+
const latency = data.readUInt16LE(4); // TODO: multiplier?
|
|
1235
|
+
const supervisionTimeout = data.readUInt16LE(6) * 10;
|
|
1236
|
+
|
|
1237
|
+
debug(`\t\t\thandle = ${handle}`);
|
|
1238
|
+
debug(`\t\t\tinterval = ${interval}`);
|
|
1239
|
+
debug(`\t\t\tlatency = ${latency}`);
|
|
1240
|
+
debug(`\t\t\tsupervision timeout = ${supervisionTimeout}`);
|
|
1241
|
+
|
|
1242
|
+
this.emit(
|
|
1243
|
+
'leConnUpdateComplete',
|
|
1244
|
+
status,
|
|
1245
|
+
handle,
|
|
1246
|
+
interval,
|
|
1247
|
+
latency,
|
|
1248
|
+
supervisionTimeout
|
|
1249
|
+
);
|
|
1250
|
+
};
|
|
1251
|
+
|
|
1252
|
+
Hci.prototype.processCmdStatusEvent = function (cmd, status) {
|
|
1253
|
+
if (cmd === LE_CREATE_CONN_CMD || cmd === LE_CREATE_EXTENDED_CONN_CMD) {
|
|
1254
|
+
if (status !== 0) {
|
|
1255
|
+
this.emit('leConnComplete', status);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
};
|
|
1259
|
+
|
|
1260
|
+
Hci.prototype.onStateChange = function (state) {
|
|
1261
|
+
this._state = state;
|
|
1262
|
+
};
|
|
1263
|
+
|
|
1264
|
+
module.exports = Hci;
|