@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,809 @@
|
|
|
1
|
+
const debug = require('debug')('att');
|
|
2
|
+
|
|
3
|
+
const events = require('events');
|
|
4
|
+
const util = require('util');
|
|
5
|
+
|
|
6
|
+
/* eslint-disable no-unused-vars */
|
|
7
|
+
const ATT_OP_ERROR = 0x01;
|
|
8
|
+
const ATT_OP_MTU_REQ = 0x02;
|
|
9
|
+
const ATT_OP_MTU_RESP = 0x03;
|
|
10
|
+
const ATT_OP_FIND_INFO_REQ = 0x04;
|
|
11
|
+
const ATT_OP_FIND_INFO_RESP = 0x05;
|
|
12
|
+
const ATT_OP_READ_BY_TYPE_REQ = 0x08;
|
|
13
|
+
const ATT_OP_READ_BY_TYPE_RESP = 0x09;
|
|
14
|
+
const ATT_OP_READ_REQ = 0x0a;
|
|
15
|
+
const ATT_OP_READ_RESP = 0x0b;
|
|
16
|
+
const ATT_OP_READ_BLOB_REQ = 0x0c;
|
|
17
|
+
const ATT_OP_READ_BLOB_RESP = 0x0d;
|
|
18
|
+
const ATT_OP_READ_BY_GROUP_REQ = 0x10;
|
|
19
|
+
const ATT_OP_READ_BY_GROUP_RESP = 0x11;
|
|
20
|
+
const ATT_OP_WRITE_REQ = 0x12;
|
|
21
|
+
const ATT_OP_WRITE_RESP = 0x13;
|
|
22
|
+
const ATT_OP_PREPARE_WRITE_REQ = 0x16;
|
|
23
|
+
const ATT_OP_PREPARE_WRITE_RESP = 0x17;
|
|
24
|
+
const ATT_OP_EXECUTE_WRITE_REQ = 0x18;
|
|
25
|
+
const ATT_OP_EXECUTE_WRITE_RESP = 0x19;
|
|
26
|
+
const ATT_OP_HANDLE_NOTIFY = 0x1b;
|
|
27
|
+
const ATT_OP_HANDLE_IND = 0x1d;
|
|
28
|
+
const ATT_OP_HANDLE_CNF = 0x1e;
|
|
29
|
+
const ATT_OP_WRITE_CMD = 0x52;
|
|
30
|
+
|
|
31
|
+
const ATT_ECODE_SUCCESS = 0x00;
|
|
32
|
+
const ATT_ECODE_INVALID_HANDLE = 0x01;
|
|
33
|
+
const ATT_ECODE_READ_NOT_PERM = 0x02;
|
|
34
|
+
const ATT_ECODE_WRITE_NOT_PERM = 0x03;
|
|
35
|
+
const ATT_ECODE_INVALID_PDU = 0x04;
|
|
36
|
+
const ATT_ECODE_AUTHENTICATION = 0x05;
|
|
37
|
+
const ATT_ECODE_REQ_NOT_SUPP = 0x06;
|
|
38
|
+
const ATT_ECODE_INVALID_OFFSET = 0x07;
|
|
39
|
+
const ATT_ECODE_AUTHORIZATION = 0x08;
|
|
40
|
+
const ATT_ECODE_PREP_QUEUE_FULL = 0x09;
|
|
41
|
+
const ATT_ECODE_ATTR_NOT_FOUND = 0x0a;
|
|
42
|
+
const ATT_ECODE_ATTR_NOT_LONG = 0x0b;
|
|
43
|
+
const ATT_ECODE_INSUFF_ENCR_KEY_SIZE = 0x0c;
|
|
44
|
+
const ATT_ECODE_INVAL_ATTR_VALUE_LEN = 0x0d;
|
|
45
|
+
const ATT_ECODE_UNLIKELY = 0x0e;
|
|
46
|
+
const ATT_ECODE_INSUFF_ENC = 0x0f;
|
|
47
|
+
const ATT_ECODE_UNSUPP_GRP_TYPE = 0x10;
|
|
48
|
+
const ATT_ECODE_INSUFF_RESOURCES = 0x11;
|
|
49
|
+
|
|
50
|
+
const GATT_PRIM_SVC_UUID = 0x2800;
|
|
51
|
+
const GATT_INCLUDE_UUID = 0x2802;
|
|
52
|
+
const GATT_CHARAC_UUID = 0x2803;
|
|
53
|
+
|
|
54
|
+
const GATT_CLIENT_CHARAC_CFG_UUID = 0x2902;
|
|
55
|
+
const GATT_SERVER_CHARAC_CFG_UUID = 0x2903;
|
|
56
|
+
|
|
57
|
+
const ATT_CID = 0x0004;
|
|
58
|
+
/* eslint-enable no-unused-vars */
|
|
59
|
+
|
|
60
|
+
const Gatt = function (address, aclStream) {
|
|
61
|
+
this._address = address;
|
|
62
|
+
this._aclStream = aclStream;
|
|
63
|
+
|
|
64
|
+
this._services = {};
|
|
65
|
+
this._characteristics = {};
|
|
66
|
+
this._descriptors = {};
|
|
67
|
+
|
|
68
|
+
this._currentCommand = null;
|
|
69
|
+
this._commandQueue = [];
|
|
70
|
+
|
|
71
|
+
this._mtu = 23;
|
|
72
|
+
this._desired_mtu = 256;
|
|
73
|
+
this._security = 'low';
|
|
74
|
+
|
|
75
|
+
this.onAclStreamDataBinded = this.onAclStreamData.bind(this);
|
|
76
|
+
this.onAclStreamEncryptBinded = this.onAclStreamEncrypt.bind(this);
|
|
77
|
+
this.onAclStreamEncryptFailBinded = this.onAclStreamEncryptFail.bind(this);
|
|
78
|
+
this.onAclStreamEndBinded = this.onAclStreamEnd.bind(this);
|
|
79
|
+
|
|
80
|
+
this._aclStream.on('data', this.onAclStreamDataBinded);
|
|
81
|
+
this._aclStream.on('encrypt', this.onAclStreamEncryptBinded);
|
|
82
|
+
this._aclStream.on('encryptFail', this.onAclStreamEncryptFailBinded);
|
|
83
|
+
this._aclStream.on('end', this.onAclStreamEndBinded);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
util.inherits(Gatt, events.EventEmitter);
|
|
87
|
+
|
|
88
|
+
Gatt.prototype.onAclStreamData = function (cid, data) {
|
|
89
|
+
if (cid !== ATT_CID) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (this._currentCommand && (data.toString('hex') === this._currentCommand.buffer.toString('hex'))) {
|
|
94
|
+
debug(`${this._address}: echo ... echo ... echo ...`);
|
|
95
|
+
} else if (data[0] % 2 === 0) {
|
|
96
|
+
if (process.env.NOBLE_MULTI_ROLE) {
|
|
97
|
+
debug(`${this._address}: multi-role flag in use, ignoring command meant for peripheral role.`);
|
|
98
|
+
} else {
|
|
99
|
+
const requestType = data[0];
|
|
100
|
+
if (requestType === ATT_OP_MTU_REQ) {
|
|
101
|
+
debug(`${this._address}: replying to MTU request`);
|
|
102
|
+
this.writeAtt(this.mtuResponse(this._desired_mtu));
|
|
103
|
+
} else {
|
|
104
|
+
debug(`${this._address}: replying with REQ_NOT_SUPP to 0x${requestType.toString(16)}`);
|
|
105
|
+
this.writeAtt(this.errorResponse(requestType, 0x0000, ATT_ECODE_REQ_NOT_SUPP));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} else if (data[0] === ATT_OP_HANDLE_NOTIFY || data[0] === ATT_OP_HANDLE_IND) {
|
|
109
|
+
const valueHandle = data.readUInt16LE(1);
|
|
110
|
+
const valueData = data.slice(3);
|
|
111
|
+
|
|
112
|
+
this.emit('handleNotify', this._address, valueHandle, valueData);
|
|
113
|
+
|
|
114
|
+
if (data[0] === ATT_OP_HANDLE_IND) {
|
|
115
|
+
this._queueCommand(this.handleConfirmation(), null, () => {
|
|
116
|
+
this.emit('handleConfirmation', this._address, valueHandle);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
for (const serviceUuid in this._services) {
|
|
121
|
+
for (const characteristicUuid in this._characteristics[serviceUuid]) {
|
|
122
|
+
if (this._characteristics[serviceUuid][characteristicUuid].valueHandle === valueHandle) {
|
|
123
|
+
this.emit('notification', this._address, serviceUuid, characteristicUuid, valueData);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} else if (!this._currentCommand) {
|
|
128
|
+
debug(`${this._address}: uh oh, no current command`);
|
|
129
|
+
} else {
|
|
130
|
+
if (data[0] === ATT_OP_ERROR &&
|
|
131
|
+
(data[4] === ATT_ECODE_AUTHENTICATION || data[4] === ATT_ECODE_AUTHORIZATION || data[4] === ATT_ECODE_INSUFF_ENC) &&
|
|
132
|
+
this._security !== 'medium') {
|
|
133
|
+
this._aclStream.encrypt();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (data[0] === ATT_OP_ERROR && data[4] === ATT_ECODE_INVALID_PDU) {
|
|
138
|
+
debug('Error: can\'t change MTU, invalid PDU');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
debug(`${this._address}: read: ${data.toString('hex')}`);
|
|
143
|
+
|
|
144
|
+
this._currentCommand.callback(data);
|
|
145
|
+
|
|
146
|
+
this._currentCommand = null;
|
|
147
|
+
|
|
148
|
+
while (this._commandQueue.length) {
|
|
149
|
+
this._currentCommand = this._commandQueue.shift();
|
|
150
|
+
|
|
151
|
+
this.writeAtt(this._currentCommand.buffer);
|
|
152
|
+
|
|
153
|
+
if (this._currentCommand.callback) {
|
|
154
|
+
break;
|
|
155
|
+
} else if (this._currentCommand.writeCallback) {
|
|
156
|
+
this._currentCommand.writeCallback();
|
|
157
|
+
|
|
158
|
+
this._currentCommand = null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
Gatt.prototype.onAclStreamEncrypt = function (encrypt) {
|
|
165
|
+
if (encrypt) {
|
|
166
|
+
this._security = 'medium';
|
|
167
|
+
|
|
168
|
+
this.writeAtt(this._currentCommand.buffer);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
Gatt.prototype.onAclStreamEncryptFail = function () {
|
|
173
|
+
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
Gatt.prototype.onAclStreamEnd = function () {
|
|
177
|
+
this._aclStream.removeListener('data', this.onAclStreamDataBinded);
|
|
178
|
+
this._aclStream.removeListener('encrypt', this.onAclStreamEncryptBinded);
|
|
179
|
+
this._aclStream.removeListener('encryptFail', this.onAclStreamEncryptFailBinded);
|
|
180
|
+
this._aclStream.removeListener('end', this.onAclStreamEndBinded);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
Gatt.prototype.writeAtt = function (data) {
|
|
184
|
+
debug(`${this._address}: write: ${data.toString('hex')}`);
|
|
185
|
+
|
|
186
|
+
this._aclStream.write(ATT_CID, data);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
Gatt.prototype.errorResponse = function (opcode, handle, status) {
|
|
190
|
+
const buf = Buffer.alloc(5);
|
|
191
|
+
|
|
192
|
+
buf.writeUInt8(ATT_OP_ERROR, 0);
|
|
193
|
+
buf.writeUInt8(opcode, 1);
|
|
194
|
+
buf.writeUInt16LE(handle, 2);
|
|
195
|
+
buf.writeUInt8(status, 4);
|
|
196
|
+
|
|
197
|
+
return buf;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
Gatt.prototype._queueCommand = function (buffer, callback, writeCallback) {
|
|
201
|
+
this._commandQueue.push({
|
|
202
|
+
buffer: buffer,
|
|
203
|
+
callback: callback,
|
|
204
|
+
writeCallback: writeCallback
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
if (this._currentCommand === null) {
|
|
208
|
+
while (this._commandQueue.length) {
|
|
209
|
+
this._currentCommand = this._commandQueue.shift();
|
|
210
|
+
|
|
211
|
+
this.writeAtt(this._currentCommand.buffer);
|
|
212
|
+
|
|
213
|
+
if (this._currentCommand.callback) {
|
|
214
|
+
break;
|
|
215
|
+
} else if (this._currentCommand.writeCallback) {
|
|
216
|
+
this._currentCommand.writeCallback();
|
|
217
|
+
|
|
218
|
+
this._currentCommand = null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
Gatt.prototype.mtuRequest = function (mtu) {
|
|
225
|
+
const buf = Buffer.alloc(3);
|
|
226
|
+
|
|
227
|
+
buf.writeUInt8(ATT_OP_MTU_REQ, 0);
|
|
228
|
+
buf.writeUInt16LE(mtu, 1);
|
|
229
|
+
|
|
230
|
+
return buf;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
Gatt.prototype.mtuResponse = function (mtu) {
|
|
234
|
+
const buf = Buffer.alloc(3);
|
|
235
|
+
|
|
236
|
+
buf.writeUInt8(ATT_OP_MTU_RESP, 0);
|
|
237
|
+
buf.writeUInt16LE(mtu, 1);
|
|
238
|
+
|
|
239
|
+
return buf;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
Gatt.prototype.readByGroupRequest = function (startHandle, endHandle, groupUuid) {
|
|
243
|
+
const buf = Buffer.alloc(7);
|
|
244
|
+
|
|
245
|
+
buf.writeUInt8(ATT_OP_READ_BY_GROUP_REQ, 0);
|
|
246
|
+
buf.writeUInt16LE(startHandle, 1);
|
|
247
|
+
buf.writeUInt16LE(endHandle, 3);
|
|
248
|
+
buf.writeUInt16LE(groupUuid, 5);
|
|
249
|
+
|
|
250
|
+
return buf;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
Gatt.prototype.readByTypeRequest = function (startHandle, endHandle, groupUuid) {
|
|
254
|
+
const buf = Buffer.alloc(7);
|
|
255
|
+
|
|
256
|
+
buf.writeUInt8(ATT_OP_READ_BY_TYPE_REQ, 0);
|
|
257
|
+
buf.writeUInt16LE(startHandle, 1);
|
|
258
|
+
buf.writeUInt16LE(endHandle, 3);
|
|
259
|
+
buf.writeUInt16LE(groupUuid, 5);
|
|
260
|
+
|
|
261
|
+
return buf;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
Gatt.prototype.readRequest = function (handle) {
|
|
265
|
+
const buf = Buffer.alloc(3);
|
|
266
|
+
|
|
267
|
+
buf.writeUInt8(ATT_OP_READ_REQ, 0);
|
|
268
|
+
buf.writeUInt16LE(handle, 1);
|
|
269
|
+
|
|
270
|
+
return buf;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
Gatt.prototype.readBlobRequest = function (handle, offset) {
|
|
274
|
+
const buf = Buffer.alloc(5);
|
|
275
|
+
|
|
276
|
+
buf.writeUInt8(ATT_OP_READ_BLOB_REQ, 0);
|
|
277
|
+
buf.writeUInt16LE(handle, 1);
|
|
278
|
+
buf.writeUInt16LE(offset, 3);
|
|
279
|
+
|
|
280
|
+
return buf;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
Gatt.prototype.findInfoRequest = function (startHandle, endHandle) {
|
|
284
|
+
const buf = Buffer.alloc(5);
|
|
285
|
+
|
|
286
|
+
buf.writeUInt8(ATT_OP_FIND_INFO_REQ, 0);
|
|
287
|
+
buf.writeUInt16LE(startHandle, 1);
|
|
288
|
+
buf.writeUInt16LE(endHandle, 3);
|
|
289
|
+
|
|
290
|
+
return buf;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
Gatt.prototype.writeRequest = function (handle, data, withoutResponse) {
|
|
294
|
+
const buf = Buffer.alloc(3 + data.length);
|
|
295
|
+
|
|
296
|
+
buf.writeUInt8(withoutResponse ? ATT_OP_WRITE_CMD : ATT_OP_WRITE_REQ, 0);
|
|
297
|
+
buf.writeUInt16LE(handle, 1);
|
|
298
|
+
|
|
299
|
+
for (let i = 0; i < data.length; i++) {
|
|
300
|
+
buf.writeUInt8(data.readUInt8(i), i + 3);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return buf;
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
Gatt.prototype.prepareWriteRequest = function (handle, offset, data) {
|
|
307
|
+
const buf = Buffer.alloc(5 + data.length);
|
|
308
|
+
|
|
309
|
+
buf.writeUInt8(ATT_OP_PREPARE_WRITE_REQ, 0);
|
|
310
|
+
buf.writeUInt16LE(handle, 1);
|
|
311
|
+
buf.writeUInt16LE(offset, 3);
|
|
312
|
+
|
|
313
|
+
for (let i = 0; i < data.length; i++) {
|
|
314
|
+
buf.writeUInt8(data.readUInt8(i), i + 5);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return buf;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
Gatt.prototype.executeWriteRequest = function (handle, cancelPreparedWrites) {
|
|
321
|
+
const buf = Buffer.alloc(2);
|
|
322
|
+
|
|
323
|
+
buf.writeUInt8(ATT_OP_EXECUTE_WRITE_REQ, 0);
|
|
324
|
+
buf.writeUInt8(cancelPreparedWrites ? 0 : 1, 1);
|
|
325
|
+
|
|
326
|
+
return buf;
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
Gatt.prototype.handleConfirmation = function () {
|
|
330
|
+
const buf = Buffer.alloc(1);
|
|
331
|
+
|
|
332
|
+
buf.writeUInt8(ATT_OP_HANDLE_CNF, 0);
|
|
333
|
+
|
|
334
|
+
return buf;
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
Gatt.prototype.exchangeMtu = function () {
|
|
338
|
+
this._queueCommand(this.mtuRequest(this._desired_mtu), (data) => {
|
|
339
|
+
const opcode = data[0];
|
|
340
|
+
|
|
341
|
+
if (opcode === ATT_OP_MTU_RESP) {
|
|
342
|
+
const newMtu = data.readUInt16LE(1);
|
|
343
|
+
|
|
344
|
+
debug(`${this._address}: new MTU is ${newMtu}`);
|
|
345
|
+
|
|
346
|
+
this._mtu = newMtu;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
this.emit('mtu', this._address, this._mtu);
|
|
350
|
+
});
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
Gatt.prototype.addService = function (service) {
|
|
354
|
+
this._services[service.uuid] = service;
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
Gatt.prototype.discoverServices = function (uuids) {
|
|
358
|
+
const services = [];
|
|
359
|
+
|
|
360
|
+
const callback = (data) => {
|
|
361
|
+
const opcode = data[0];
|
|
362
|
+
let i = 0;
|
|
363
|
+
|
|
364
|
+
if (opcode === ATT_OP_READ_BY_GROUP_RESP) {
|
|
365
|
+
const type = data[1];
|
|
366
|
+
const num = (data.length - 2) / type;
|
|
367
|
+
|
|
368
|
+
for (i = 0; i < num; i++) {
|
|
369
|
+
const offset = 2 + i * type;
|
|
370
|
+
services.push({
|
|
371
|
+
startHandle: data.readUInt16LE(offset),
|
|
372
|
+
endHandle: data.readUInt16LE(offset + 2),
|
|
373
|
+
uuid: (type === 6) ? data.readUInt16LE(offset + 4).toString(16) : data.slice(offset + 4).slice(0, 16).toString('hex').match(/.{1,2}/g).reverse().join('')
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if ((opcode !== ATT_OP_READ_BY_GROUP_RESP && opcode !== ATT_OP_ERROR) || services[services.length - 1].endHandle === 0xffff) {
|
|
379
|
+
const serviceUuids = [];
|
|
380
|
+
for (i = 0; i < services.length; i++) {
|
|
381
|
+
const uuid = services[i].uuid.trim();
|
|
382
|
+
if ((uuids.length === 0 || uuids.indexOf(uuid) !== -1) && serviceUuids.indexOf(uuid) === -1) {
|
|
383
|
+
serviceUuids.push(uuid);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
this._services[services[i].uuid] = services[i];
|
|
387
|
+
}
|
|
388
|
+
this.emit('servicesDiscovered', this._address, JSON.parse(JSON.stringify(services)) /* services */);
|
|
389
|
+
this.emit('servicesDiscover', this._address, serviceUuids);
|
|
390
|
+
} else {
|
|
391
|
+
this._queueCommand(this.readByGroupRequest(services[services.length - 1].endHandle + 1, 0xffff, GATT_PRIM_SVC_UUID), callback);
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
this._queueCommand(this.readByGroupRequest(0x0001, 0xffff, GATT_PRIM_SVC_UUID), callback);
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
Gatt.prototype.discoverIncludedServices = function (serviceUuid, uuids) {
|
|
399
|
+
const service = this._services[serviceUuid];
|
|
400
|
+
const includedServices = [];
|
|
401
|
+
|
|
402
|
+
const callback = (data) => {
|
|
403
|
+
const opcode = data[0];
|
|
404
|
+
let i = 0;
|
|
405
|
+
|
|
406
|
+
if (opcode === ATT_OP_READ_BY_TYPE_RESP) {
|
|
407
|
+
const type = data[1];
|
|
408
|
+
const num = (data.length - 2) / type;
|
|
409
|
+
|
|
410
|
+
for (i = 0; i < num; i++) {
|
|
411
|
+
const offset = 2 + i * type;
|
|
412
|
+
includedServices.push({
|
|
413
|
+
endHandle: data.readUInt16LE(offset),
|
|
414
|
+
startHandle: data.readUInt16LE(offset + 2),
|
|
415
|
+
uuid: (type === 8) ? data.readUInt16LE(offset + 6).toString(16) : data.slice(offset + 6).slice(0, 16).toString('hex').match(/.{1,2}/g).reverse().join('')
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (opcode !== ATT_OP_READ_BY_TYPE_RESP || includedServices[includedServices.length - 1].endHandle === service.endHandle) {
|
|
421
|
+
const includedServiceUuids = [];
|
|
422
|
+
|
|
423
|
+
for (i = 0; i < includedServices.length; i++) {
|
|
424
|
+
if (uuids.length === 0 || uuids.indexOf(includedServices[i].uuid) !== -1) {
|
|
425
|
+
includedServiceUuids.push(includedServices[i].uuid);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
this.emit('includedServicesDiscover', this._address, service.uuid, includedServiceUuids);
|
|
430
|
+
} else {
|
|
431
|
+
this._queueCommand(this.readByTypeRequest(includedServices[includedServices.length - 1].endHandle + 1, service.endHandle, GATT_INCLUDE_UUID), callback);
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
this._queueCommand(this.readByTypeRequest(service.startHandle, service.endHandle, GATT_INCLUDE_UUID), callback);
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
Gatt.prototype.addCharacteristics = function (serviceUuid, characteristics) {
|
|
439
|
+
this._characteristics[serviceUuid] = this._characteristics[serviceUuid] || {};
|
|
440
|
+
this._descriptors[serviceUuid] = this._descriptors[serviceUuid] || {};
|
|
441
|
+
|
|
442
|
+
for (let i = 0; i < characteristics.length; i++) {
|
|
443
|
+
this._characteristics[serviceUuid][characteristics[i].uuid] = characteristics[i];
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
Gatt.prototype.discoverCharacteristics = function (serviceUuid, characteristicUuids) {
|
|
448
|
+
const service = this._services[serviceUuid];
|
|
449
|
+
const characteristics = [];
|
|
450
|
+
|
|
451
|
+
this._characteristics[serviceUuid] = this._characteristics[serviceUuid] || {};
|
|
452
|
+
this._descriptors[serviceUuid] = this._descriptors[serviceUuid] || {};
|
|
453
|
+
|
|
454
|
+
const callback = (data) => {
|
|
455
|
+
const opcode = data[0];
|
|
456
|
+
let i = 0;
|
|
457
|
+
|
|
458
|
+
if (opcode === ATT_OP_READ_BY_TYPE_RESP) {
|
|
459
|
+
const type = data[1];
|
|
460
|
+
const num = (data.length - 2) / type;
|
|
461
|
+
|
|
462
|
+
for (i = 0; i < num; i++) {
|
|
463
|
+
const offset = 2 + i * type;
|
|
464
|
+
characteristics.push({
|
|
465
|
+
startHandle: data.readUInt16LE(offset),
|
|
466
|
+
properties: data.readUInt8(offset + 2),
|
|
467
|
+
valueHandle: data.readUInt16LE(offset + 3),
|
|
468
|
+
uuid: (type === 7) ? data.readUInt16LE(offset + 5).toString(16) : data.slice(offset + 5).slice(0, 16).toString('hex').match(/.{1,2}/g).reverse().join('')
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (opcode !== ATT_OP_READ_BY_TYPE_RESP || characteristics[characteristics.length - 1].valueHandle === service.endHandle) {
|
|
474
|
+
const characteristicsDiscovered = [];
|
|
475
|
+
for (i = 0; i < characteristics.length; i++) {
|
|
476
|
+
const properties = characteristics[i].properties;
|
|
477
|
+
|
|
478
|
+
const characteristic = {
|
|
479
|
+
properties: [],
|
|
480
|
+
uuid: characteristics[i].uuid
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
// work around name-clash of numeric vs. string-array properties field:
|
|
484
|
+
characteristics[i].propsDecoded = characteristic.properties;
|
|
485
|
+
characteristics[i].rawProps = properties;
|
|
486
|
+
|
|
487
|
+
if (i !== 0) {
|
|
488
|
+
characteristics[i - 1].endHandle = characteristics[i].startHandle - 1;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (i === (characteristics.length - 1)) {
|
|
492
|
+
characteristics[i].endHandle = service.endHandle;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
this._characteristics[serviceUuid][characteristics[i].uuid] = characteristics[i];
|
|
496
|
+
|
|
497
|
+
if (properties & 0x01) {
|
|
498
|
+
characteristic.properties.push('broadcast');
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (properties & 0x02) {
|
|
502
|
+
characteristic.properties.push('read');
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (properties & 0x04) {
|
|
506
|
+
characteristic.properties.push('writeWithoutResponse');
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (properties & 0x08) {
|
|
510
|
+
characteristic.properties.push('write');
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (properties & 0x10) {
|
|
514
|
+
characteristic.properties.push('notify');
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (properties & 0x20) {
|
|
518
|
+
characteristic.properties.push('indicate');
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (properties & 0x40) {
|
|
522
|
+
characteristic.properties.push('authenticatedSignedWrites');
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (properties & 0x80) {
|
|
526
|
+
characteristic.properties.push('extendedProperties');
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (characteristicUuids.length === 0 || characteristicUuids.indexOf(characteristic.uuid) !== -1) {
|
|
530
|
+
characteristicsDiscovered.push(characteristic);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
this.emit('characteristicsDiscovered', this._address, serviceUuid, characteristics);
|
|
535
|
+
this.emit('characteristicsDiscover', this._address, serviceUuid, characteristicsDiscovered);
|
|
536
|
+
} else {
|
|
537
|
+
this._queueCommand(this.readByTypeRequest(characteristics[characteristics.length - 1].valueHandle + 1, service.endHandle, GATT_CHARAC_UUID), callback);
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
this._queueCommand(this.readByTypeRequest(service.startHandle, service.endHandle, GATT_CHARAC_UUID), callback);
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
Gatt.prototype.read = function (serviceUuid, characteristicUuid) {
|
|
545
|
+
const characteristic = this._characteristics[serviceUuid][characteristicUuid];
|
|
546
|
+
|
|
547
|
+
let readData = Buffer.alloc(0);
|
|
548
|
+
|
|
549
|
+
const callback = (data) => {
|
|
550
|
+
const opcode = data[0];
|
|
551
|
+
|
|
552
|
+
if (opcode === ATT_OP_READ_RESP || opcode === ATT_OP_READ_BLOB_RESP) {
|
|
553
|
+
readData = Buffer.from(`${readData.toString('hex')}${data.slice(1).toString('hex')}`, 'hex');
|
|
554
|
+
|
|
555
|
+
if (data.length === this._mtu) {
|
|
556
|
+
this._queueCommand(this.readBlobRequest(characteristic.valueHandle, readData.length), callback);
|
|
557
|
+
} else {
|
|
558
|
+
this.emit('read', this._address, serviceUuid, characteristicUuid, readData);
|
|
559
|
+
}
|
|
560
|
+
} else {
|
|
561
|
+
this.emit('read', this._address, serviceUuid, characteristicUuid, readData);
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
this._queueCommand(this.readRequest(characteristic.valueHandle), callback);
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
Gatt.prototype.write = function (serviceUuid, characteristicUuid, data, withoutResponse) {
|
|
569
|
+
const characteristic = this._characteristics[serviceUuid][characteristicUuid];
|
|
570
|
+
|
|
571
|
+
if (withoutResponse) {
|
|
572
|
+
this._queueCommand(this.writeRequest(characteristic.valueHandle, data, true), null, () => {
|
|
573
|
+
this.emit('write', this._address, serviceUuid, characteristicUuid);
|
|
574
|
+
});
|
|
575
|
+
} else if (data.length + 3 > this._mtu) {
|
|
576
|
+
return this.longWrite(serviceUuid, characteristicUuid, data, withoutResponse);
|
|
577
|
+
} else {
|
|
578
|
+
this._queueCommand(this.writeRequest(characteristic.valueHandle, data, false), data => {
|
|
579
|
+
const opcode = data[0];
|
|
580
|
+
|
|
581
|
+
if (opcode === ATT_OP_WRITE_RESP) {
|
|
582
|
+
this.emit('write', this._address, serviceUuid, characteristicUuid);
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
/* Perform a "long write" as described Bluetooth Spec section 4.9.4 "Write Long Characteristic Values" */
|
|
589
|
+
Gatt.prototype.longWrite = function (serviceUuid, characteristicUuid, data, withoutResponse) {
|
|
590
|
+
const characteristic = this._characteristics[serviceUuid][characteristicUuid];
|
|
591
|
+
const limit = this._mtu - 5;
|
|
592
|
+
|
|
593
|
+
const prepareWriteCallback = (dataChunk) => {
|
|
594
|
+
return (resp) => {
|
|
595
|
+
const opcode = resp[0];
|
|
596
|
+
|
|
597
|
+
if (opcode !== ATT_OP_PREPARE_WRITE_RESP) {
|
|
598
|
+
debug(`${this._address}: unexpected reply opcode %d (expecting ATT_OP_PREPARE_WRITE_RESP)`, opcode);
|
|
599
|
+
} else {
|
|
600
|
+
const expectedLength = dataChunk.length + 5;
|
|
601
|
+
|
|
602
|
+
if (resp.length !== expectedLength) {
|
|
603
|
+
/* the response should contain the data packet echoed back to the caller */
|
|
604
|
+
debug(`${this._address}: unexpected prepareWriteResponse length %d (expecting %d)`, resp.length, expectedLength);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
/* split into prepare-write chunks and queue them */
|
|
611
|
+
let offset = 0;
|
|
612
|
+
|
|
613
|
+
while (offset < data.length) {
|
|
614
|
+
const end = offset + limit;
|
|
615
|
+
const chunk = data.slice(offset, end);
|
|
616
|
+
this._queueCommand(this.prepareWriteRequest(characteristic.valueHandle, offset, chunk), prepareWriteCallback(chunk));
|
|
617
|
+
offset = end;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/* queue the execute command with a callback to emit the write signal when done */
|
|
621
|
+
this._queueCommand(this.executeWriteRequest(characteristic.valueHandle), resp => {
|
|
622
|
+
const opcode = resp[0];
|
|
623
|
+
|
|
624
|
+
if (opcode === ATT_OP_EXECUTE_WRITE_RESP && !withoutResponse) {
|
|
625
|
+
this.emit('write', this._address, serviceUuid, characteristicUuid);
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
Gatt.prototype.broadcast = function (serviceUuid, characteristicUuid, broadcast) {
|
|
631
|
+
const characteristic = this._characteristics[serviceUuid][characteristicUuid];
|
|
632
|
+
|
|
633
|
+
this._queueCommand(this.readByTypeRequest(characteristic.startHandle, characteristic.endHandle, GATT_SERVER_CHARAC_CFG_UUID), data => {
|
|
634
|
+
const opcode = data[0];
|
|
635
|
+
if (opcode === ATT_OP_READ_BY_TYPE_RESP) {
|
|
636
|
+
const handle = data.readUInt16LE(2);
|
|
637
|
+
let value = data.readUInt16LE(4);
|
|
638
|
+
|
|
639
|
+
if (broadcast) {
|
|
640
|
+
value |= 0x0001;
|
|
641
|
+
} else {
|
|
642
|
+
value &= 0xfffe;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const valueBuffer = Buffer.alloc(2);
|
|
646
|
+
valueBuffer.writeUInt16LE(value, 0);
|
|
647
|
+
|
|
648
|
+
this._queueCommand(this.writeRequest(handle, valueBuffer, false), data => {
|
|
649
|
+
const opcode = data[0];
|
|
650
|
+
|
|
651
|
+
if (opcode === ATT_OP_WRITE_RESP) {
|
|
652
|
+
this.emit('broadcast', this._address, serviceUuid, characteristicUuid, broadcast);
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
Gatt.prototype.notify = function (serviceUuid, characteristicUuid, notify) {
|
|
660
|
+
const characteristic = this._characteristics[serviceUuid][characteristicUuid];
|
|
661
|
+
|
|
662
|
+
this._queueCommand(this.readByTypeRequest(characteristic.startHandle, characteristic.endHandle, GATT_CLIENT_CHARAC_CFG_UUID), data => {
|
|
663
|
+
const opcode = data[0];
|
|
664
|
+
if (opcode === ATT_OP_READ_BY_TYPE_RESP) {
|
|
665
|
+
const handle = data.readUInt16LE(2);
|
|
666
|
+
let value = data.readUInt16LE(4);
|
|
667
|
+
|
|
668
|
+
const useNotify = characteristic.properties & 0x10;
|
|
669
|
+
const useIndicate = characteristic.properties & 0x20;
|
|
670
|
+
|
|
671
|
+
if (notify) {
|
|
672
|
+
if (useNotify) {
|
|
673
|
+
value |= 0x0001;
|
|
674
|
+
} else if (useIndicate) {
|
|
675
|
+
value |= 0x0002;
|
|
676
|
+
}
|
|
677
|
+
} else {
|
|
678
|
+
if (useNotify) {
|
|
679
|
+
value &= 0xfffe;
|
|
680
|
+
} else if (useIndicate) {
|
|
681
|
+
value &= 0xfffd;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
const valueBuffer = Buffer.alloc(2);
|
|
686
|
+
valueBuffer.writeUInt16LE(value, 0);
|
|
687
|
+
|
|
688
|
+
this._queueCommand(this.writeRequest(handle, valueBuffer, false), data => {
|
|
689
|
+
const opcode = data[0];
|
|
690
|
+
|
|
691
|
+
if (opcode === ATT_OP_WRITE_RESP) {
|
|
692
|
+
this.emit('notify', this._address, serviceUuid, characteristicUuid, notify);
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
Gatt.prototype.discoverDescriptors = function (serviceUuid, characteristicUuid) {
|
|
700
|
+
const characteristic = this._characteristics[serviceUuid][characteristicUuid];
|
|
701
|
+
const descriptors = [];
|
|
702
|
+
|
|
703
|
+
this._descriptors[serviceUuid][characteristicUuid] = {};
|
|
704
|
+
|
|
705
|
+
const callback = data => {
|
|
706
|
+
const opcode = data[0];
|
|
707
|
+
let i = 0;
|
|
708
|
+
|
|
709
|
+
if (opcode === ATT_OP_FIND_INFO_RESP) {
|
|
710
|
+
const format = data[1];
|
|
711
|
+
const elen = 2 + (format === 0x01 ? 2 : 16);
|
|
712
|
+
const num = (data.length - 2) / (elen);
|
|
713
|
+
for (i = 0; i < num; i++) {
|
|
714
|
+
const offset = 2 + (i * elen);
|
|
715
|
+
descriptors.push({
|
|
716
|
+
handle: data.readUInt16LE(offset + 0),
|
|
717
|
+
uuid: (format === 0x01) ? data.readUInt16LE(offset + 2).toString(16) : data.slice(offset + 2).slice(0, 16).toString('hex').match(/.{1,2}/g).reverse().join('')
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
if (opcode !== ATT_OP_FIND_INFO_RESP || descriptors[descriptors.length - 1].handle === characteristic.endHandle) {
|
|
723
|
+
const descriptorUuids = [];
|
|
724
|
+
for (i = 0; i < descriptors.length; i++) {
|
|
725
|
+
descriptorUuids.push(descriptors[i].uuid);
|
|
726
|
+
|
|
727
|
+
this._descriptors[serviceUuid][characteristicUuid][descriptors[i].uuid] = descriptors[i];
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
this.emit('descriptorsDiscover', this._address, serviceUuid, characteristicUuid, descriptorUuids);
|
|
731
|
+
} else {
|
|
732
|
+
this._queueCommand(this.findInfoRequest(descriptors[descriptors.length - 1].handle + 1, characteristic.endHandle), callback);
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
this._queueCommand(this.findInfoRequest(characteristic.valueHandle + 1, characteristic.endHandle), callback);
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
Gatt.prototype.readValue = function (serviceUuid, characteristicUuid, descriptorUuid) {
|
|
740
|
+
const descriptor = this._descriptors[serviceUuid][characteristicUuid][descriptorUuid];
|
|
741
|
+
|
|
742
|
+
let readData = Buffer.alloc(0);
|
|
743
|
+
|
|
744
|
+
const callback = (data) => {
|
|
745
|
+
const opcode = data[0];
|
|
746
|
+
if (opcode === ATT_OP_READ_RESP || opcode === ATT_OP_READ_BLOB_RESP) {
|
|
747
|
+
readData = Buffer.from(`${readData.toString('hex')}${data.slice(1).toString('hex')}`, 'hex');
|
|
748
|
+
if (data.length === this._mtu) {
|
|
749
|
+
this._queueCommand(this.readBlobRequest(descriptor.handle, readData.length), callback);
|
|
750
|
+
} else {
|
|
751
|
+
this.emit('valueRead', this._address, serviceUuid, characteristicUuid, descriptorUuid, readData);
|
|
752
|
+
}
|
|
753
|
+
} else {
|
|
754
|
+
this.emit('valueRead', this._address, serviceUuid, characteristicUuid, descriptorUuid, readData);
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
this._queueCommand(this.readRequest(descriptor.handle), callback);
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
Gatt.prototype.writeValue = function (serviceUuid, characteristicUuid, descriptorUuid, data) {
|
|
762
|
+
const descriptor = this._descriptors[serviceUuid][characteristicUuid][descriptorUuid];
|
|
763
|
+
|
|
764
|
+
this._queueCommand(this.writeRequest(descriptor.handle, data, false), data => {
|
|
765
|
+
const opcode = data[0];
|
|
766
|
+
|
|
767
|
+
if (opcode === ATT_OP_WRITE_RESP) {
|
|
768
|
+
this.emit('valueWrite', this._address, serviceUuid, characteristicUuid, descriptorUuid);
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
};
|
|
772
|
+
|
|
773
|
+
Gatt.prototype.readHandle = function (handle) {
|
|
774
|
+
let readData = Buffer.alloc(0);
|
|
775
|
+
|
|
776
|
+
const callback = (data) => {
|
|
777
|
+
const opcode = data[0];
|
|
778
|
+
if (opcode === ATT_OP_READ_RESP || opcode === ATT_OP_READ_BLOB_RESP) {
|
|
779
|
+
readData = Buffer.from(`${readData.toString('hex')}${data.slice(1).toString('hex')}`, 'hex');
|
|
780
|
+
if (data.length === this._mtu) {
|
|
781
|
+
this._queueCommand(this.readBlobRequest(handle, readData.length), callback);
|
|
782
|
+
} else {
|
|
783
|
+
this.emit('handleRead', this._address, handle, readData);
|
|
784
|
+
}
|
|
785
|
+
} else {
|
|
786
|
+
this.emit('handleRead', this._address, handle, readData);
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
this._queueCommand(this.readRequest(handle), callback);
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
Gatt.prototype.writeHandle = function (handle, data, withoutResponse) {
|
|
794
|
+
if (withoutResponse) {
|
|
795
|
+
this._queueCommand(this.writeRequest(handle, data, true), null, () => {
|
|
796
|
+
this.emit('handleWrite', this._address, handle);
|
|
797
|
+
});
|
|
798
|
+
} else {
|
|
799
|
+
this._queueCommand(this.writeRequest(handle, data, false), data => {
|
|
800
|
+
const opcode = data[0];
|
|
801
|
+
|
|
802
|
+
if (opcode === ATT_OP_WRITE_RESP) {
|
|
803
|
+
this.emit('handleWrite', this._address, handle);
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
|
|
809
|
+
module.exports = Gatt;
|