@stoprocent/bleno 0.7.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/.jshintrc +5 -0
- package/LICENSE +20 -0
- package/README.md +409 -0
- package/binding.gyp +17 -0
- package/examples/battery-service/README.md +14 -0
- package/examples/battery-service/battery-level-characteristic.js +45 -0
- package/examples/battery-service/battery-service.js +16 -0
- package/examples/battery-service/main.js +28 -0
- package/examples/battery-service/package-lock.json +1314 -0
- package/examples/battery-service/package.json +20 -0
- package/examples/blink1/README.md +44 -0
- package/examples/blink1/blink1-fade-rgb-characteristic.js +42 -0
- package/examples/blink1/blink1-rgb-characteristic.js +38 -0
- package/examples/blink1/blink1-service.js +19 -0
- package/examples/blink1/device-information-service.js +19 -0
- package/examples/blink1/hardware-revision-characteristic.js +32 -0
- package/examples/blink1/main.js +32 -0
- package/examples/blink1/serial-number-characteristic.js +21 -0
- package/examples/echo/characteristic.js +45 -0
- package/examples/echo/main.js +33 -0
- package/examples/pizza/README.md +16 -0
- package/examples/pizza/peripheral.js +57 -0
- package/examples/pizza/pizza-bake-characteristic.js +40 -0
- package/examples/pizza/pizza-crust-characteristic.js +52 -0
- package/examples/pizza/pizza-service.js +20 -0
- package/examples/pizza/pizza-toppings-characteristic.js +41 -0
- package/examples/pizza/pizza.js +58 -0
- package/examples/uart/main.js +23 -0
- package/index.d.ts +153 -0
- package/index.js +1 -0
- package/lib/bleno.js +231 -0
- package/lib/characteristic.js +91 -0
- package/lib/descriptor.js +17 -0
- package/lib/hci-socket/acl-stream.js +37 -0
- package/lib/hci-socket/bindings.js +219 -0
- package/lib/hci-socket/crypto.js +74 -0
- package/lib/hci-socket/gap.js +212 -0
- package/lib/hci-socket/gatt.js +1028 -0
- package/lib/hci-socket/hci-status.json +67 -0
- package/lib/hci-socket/hci.js +796 -0
- package/lib/hci-socket/mgmt.js +89 -0
- package/lib/hci-socket/smp.js +160 -0
- package/lib/hci-socket/vs.js +156 -0
- package/lib/mac/binding.gyp +39 -0
- package/lib/mac/bindings.js +12 -0
- package/lib/mac/src/ble_peripheral_manager.h +32 -0
- package/lib/mac/src/ble_peripheral_manager.mm +241 -0
- package/lib/mac/src/bleno_mac.h +26 -0
- package/lib/mac/src/bleno_mac.mm +167 -0
- package/lib/mac/src/callbacks.h +76 -0
- package/lib/mac/src/callbacks.mm +124 -0
- package/lib/mac/src/napi_objc.h +30 -0
- package/lib/mac/src/napi_objc.mm +286 -0
- package/lib/mac/src/noble_mac.h +34 -0
- package/lib/mac/src/noble_mac.mm +260 -0
- package/lib/mac/src/objc_cpp.h +27 -0
- package/lib/mac/src/objc_cpp.mm +144 -0
- package/lib/mac/src/peripheral.h +23 -0
- package/lib/primary-service.js +19 -0
- package/lib/resolve-bindings.js +19 -0
- package/lib/uuid-util.js +7 -0
- package/package.json +77 -0
- package/prebuilds/android-arm/node.napi.armv7.node +0 -0
- package/prebuilds/android-arm64/node.napi.armv8.node +0 -0
- package/prebuilds/darwin-x64+arm64/node.napi.node +0 -0
- package/prebuilds/linux-arm/node.napi.armv6.node +0 -0
- package/prebuilds/linux-arm/node.napi.armv7.node +0 -0
- package/prebuilds/linux-arm64/node.napi.armv8.node +0 -0
- package/prebuilds/linux-x64/node.napi.glibc.node +0 -0
- package/prebuilds/linux-x64/node.napi.musl.node +0 -0
- package/prebuilds/win32-ia32/node.napi.node +0 -0
- package/prebuilds/win32-x64/node.napi.node +0 -0
- package/test/characteristic.test.js +174 -0
- package/test/descriptor.test.js +46 -0
- package/test/mocha.setup.js +0 -0
- package/with-bindings.js +5 -0
- package/with-custom-binding.js +6 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
const debug = require('debug')('bindings');
|
|
2
|
+
|
|
3
|
+
const { EventEmitter } = require('events');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
|
|
6
|
+
const AclStream = require('./acl-stream');
|
|
7
|
+
const Hci = require('./hci');
|
|
8
|
+
const Gap = require('./gap');
|
|
9
|
+
const Gatt = require('./gatt');
|
|
10
|
+
|
|
11
|
+
class BlenoBindings extends EventEmitter {
|
|
12
|
+
constructor (options) {
|
|
13
|
+
super();
|
|
14
|
+
|
|
15
|
+
this._state = null;
|
|
16
|
+
|
|
17
|
+
this._advertising = false;
|
|
18
|
+
|
|
19
|
+
this._hci = new Hci(options);
|
|
20
|
+
this._gap = new Gap(this._hci);
|
|
21
|
+
this._gatt = new Gatt(this._hci);
|
|
22
|
+
|
|
23
|
+
this._address = null;
|
|
24
|
+
this._handle = null;
|
|
25
|
+
this._aclStream = null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setAddress (address) {
|
|
29
|
+
this._hci.setAddress(address);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
startAdvertising (name, serviceUuids) {
|
|
33
|
+
this._advertising = true;
|
|
34
|
+
|
|
35
|
+
this._gap.startAdvertising(name, serviceUuids);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
startAdvertisingIBeacon (data) {
|
|
39
|
+
this._advertising = true;
|
|
40
|
+
|
|
41
|
+
this._gap.startAdvertisingIBeacon(data);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
startAdvertisingWithEIRData (advertisementData, scanData) {
|
|
45
|
+
this._advertising = true;
|
|
46
|
+
|
|
47
|
+
this._gap.startAdvertisingWithEIRData(advertisementData, scanData);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
stopAdvertising () {
|
|
51
|
+
this._advertising = false;
|
|
52
|
+
|
|
53
|
+
this._gap.stopAdvertising();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
setServices (services) {
|
|
57
|
+
this._gatt.setServices(services);
|
|
58
|
+
|
|
59
|
+
this.emit('servicesSet');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
disconnect () {
|
|
63
|
+
if (this._handle) {
|
|
64
|
+
debug('disconnect by server');
|
|
65
|
+
|
|
66
|
+
this._hci.disconnect(this._handle);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
updateRssi () {
|
|
71
|
+
if (this._handle) {
|
|
72
|
+
this._hci.readRssi(this._handle);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
init () {
|
|
77
|
+
this.onSigIntBinded = this.onSigInt.bind(this);
|
|
78
|
+
|
|
79
|
+
process.on('SIGINT', this.onSigIntBinded);
|
|
80
|
+
process.on('exit', this.onExit.bind(this));
|
|
81
|
+
|
|
82
|
+
this._gap.on('advertisingStart', this.onAdvertisingStart.bind(this));
|
|
83
|
+
this._gap.on('advertisingStop', this.onAdvertisingStop.bind(this));
|
|
84
|
+
|
|
85
|
+
this._gatt.on('mtuChange', this.onMtuChange.bind(this));
|
|
86
|
+
|
|
87
|
+
this._hci.on('stateChange', this.onStateChange.bind(this));
|
|
88
|
+
this._hci.on('addressChange', this.onAddressChange.bind(this));
|
|
89
|
+
this._hci.on('readLocalVersion', this.onReadLocalVersion.bind(this));
|
|
90
|
+
|
|
91
|
+
this._hci.on('leConnComplete', this.onLeConnComplete.bind(this));
|
|
92
|
+
this._hci.on('leConnUpdateComplete', this.onLeConnUpdateComplete.bind(this));
|
|
93
|
+
this._hci.on('rssiRead', this.onRssiRead.bind(this));
|
|
94
|
+
this._hci.on('disconnComplete', this.onDisconnComplete.bind(this));
|
|
95
|
+
this._hci.on('encryptChange', this.onEncryptChange.bind(this));
|
|
96
|
+
this._hci.on('leLtkNegReply', this.onLeLtkNegReply.bind(this));
|
|
97
|
+
this._hci.on('aclDataPkt', this.onAclDataPkt.bind(this));
|
|
98
|
+
|
|
99
|
+
this.emit('platform', os.platform());
|
|
100
|
+
|
|
101
|
+
this._hci.init();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
onStateChange (state) {
|
|
105
|
+
if (this._state === state) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
this._state = state;
|
|
109
|
+
|
|
110
|
+
if (state === 'unauthorized') {
|
|
111
|
+
console.log('Bleno warning: adapter state unauthorized, please run as root or with sudo');
|
|
112
|
+
console.log(' or see README for information on running without root/sudo:');
|
|
113
|
+
console.log(' https://github.com/abandonware/bleno#running-on-linux');
|
|
114
|
+
} else if (state === 'unsupported') {
|
|
115
|
+
console.log('Bleno warning: adapter does not support Bluetooth Low Energy (BLE, Bluetooth Smart).');
|
|
116
|
+
console.log(' Try to run with environment variable:');
|
|
117
|
+
console.log(' [sudo] BLENO_HCI_DEVICE_ID=x node ...');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
this.emit('stateChange', state);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
onAddressChange (address) {
|
|
124
|
+
this.emit('addressChange', address);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
onReadLocalVersion (hciVer, hciRev, lmpVer, manufacturer, lmpSubVer) {
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
onAdvertisingStart (error) {
|
|
131
|
+
this.emit('advertisingStart', error);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
onAdvertisingStop () {
|
|
135
|
+
this.emit('advertisingStop');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
onLeConnComplete (status, handle, role, addressType, address, interval, latency, supervisionTimeout, masterClockAccuracy) {
|
|
139
|
+
if (role !== 1) {
|
|
140
|
+
// not slave, ignore
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
this._address = address;
|
|
145
|
+
this._handle = handle;
|
|
146
|
+
this._aclStream = new AclStream(this._hci, handle, this._hci.addressType, this._hci.address, addressType, address);
|
|
147
|
+
this._gatt.setAclStream(this._aclStream);
|
|
148
|
+
|
|
149
|
+
this.emit('accept', address);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
onLeConnUpdateComplete (handle, interval, latency, supervisionTimeout) {
|
|
153
|
+
// no-op
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
onDisconnComplete (handle, reason) {
|
|
157
|
+
if (this._aclStream) {
|
|
158
|
+
this._aclStream.push(null, null);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const address = this._address;
|
|
162
|
+
|
|
163
|
+
this._address = null;
|
|
164
|
+
this._handle = null;
|
|
165
|
+
this._aclStream = null;
|
|
166
|
+
|
|
167
|
+
if (address) {
|
|
168
|
+
this.emit('disconnect', address); // TODO: use reason
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (this._advertising) {
|
|
172
|
+
this._gap.restartAdvertising();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
onEncryptChange (handle, encrypt) {
|
|
177
|
+
if (this._handle === handle && this._aclStream) {
|
|
178
|
+
this._aclStream.pushEncrypt(encrypt);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
onLeLtkNegReply (handle) {
|
|
183
|
+
if (this._handle === handle && this._aclStream) {
|
|
184
|
+
this._aclStream.pushLtkNegReply();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
onMtuChange (mtu) {
|
|
189
|
+
this.emit('mtuChange', mtu);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
onRssiRead (handle, rssi) {
|
|
193
|
+
this.emit('rssiUpdate', rssi);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
onAclDataPkt (handle, cid, data) {
|
|
197
|
+
if (this._handle === handle && this._aclStream) {
|
|
198
|
+
this._aclStream.push(cid, data);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
onSigInt () {
|
|
203
|
+
const sigIntListeners = process.listeners('SIGINT');
|
|
204
|
+
|
|
205
|
+
if (sigIntListeners[sigIntListeners.length - 1] === this.onSigIntBinded) {
|
|
206
|
+
// we are the last listener, so exit
|
|
207
|
+
// this will trigger onExit, and clean up
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
onExit () {
|
|
213
|
+
this._gap.stopAdvertising();
|
|
214
|
+
|
|
215
|
+
this.disconnect();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
module.exports = BlenoBindings;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
|
|
3
|
+
function r () {
|
|
4
|
+
return crypto.randomBytes(16);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function c1 (k, r, pres, preq, iat, ia, rat, ra) {
|
|
8
|
+
const p1 = Buffer.concat([
|
|
9
|
+
iat,
|
|
10
|
+
rat,
|
|
11
|
+
preq,
|
|
12
|
+
pres
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
const p2 = Buffer.concat([
|
|
16
|
+
ra,
|
|
17
|
+
ia,
|
|
18
|
+
Buffer.from('00000000', 'hex')
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
let res = xor(r, p1);
|
|
22
|
+
res = e(k, res);
|
|
23
|
+
res = xor(res, p2);
|
|
24
|
+
res = e(k, res);
|
|
25
|
+
|
|
26
|
+
return res;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function s1 (k, r1, r2) {
|
|
30
|
+
return e(k, Buffer.concat([
|
|
31
|
+
r2.slice(0, 8),
|
|
32
|
+
r1.slice(0, 8)
|
|
33
|
+
]));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function e (key, data) {
|
|
37
|
+
key = swap(key);
|
|
38
|
+
data = swap(data);
|
|
39
|
+
|
|
40
|
+
const cipher = crypto.createCipheriv('aes-128-ecb', key, '');
|
|
41
|
+
cipher.setAutoPadding(false);
|
|
42
|
+
|
|
43
|
+
return swap(Buffer.concat([
|
|
44
|
+
cipher.update(data),
|
|
45
|
+
cipher.final()
|
|
46
|
+
]));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function xor (b1, b2) {
|
|
50
|
+
const result = Buffer.alloc(b1.length);
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < b1.length; i++) {
|
|
53
|
+
result[i] = b1[i] ^ b2[i];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function swap (input) {
|
|
60
|
+
const output = Buffer.alloc(input.length);
|
|
61
|
+
|
|
62
|
+
for (let i = 0; i < output.length; i++) {
|
|
63
|
+
output[i] = input[input.length - i - 1];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return output;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = {
|
|
70
|
+
r,
|
|
71
|
+
c1,
|
|
72
|
+
s1,
|
|
73
|
+
e
|
|
74
|
+
};
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
const debug = require('debug')('gap');
|
|
2
|
+
|
|
3
|
+
const { EventEmitter } = require('events');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
|
|
6
|
+
const Hci = require('./hci');
|
|
7
|
+
|
|
8
|
+
const isLinux = (os.platform() === 'linux');
|
|
9
|
+
const isIntelEdison = isLinux && (os.release().includes('edison'));
|
|
10
|
+
const isYocto = isLinux && (os.release().includes('yocto'));
|
|
11
|
+
|
|
12
|
+
class Gap extends EventEmitter {
|
|
13
|
+
constructor (hci) {
|
|
14
|
+
super();
|
|
15
|
+
|
|
16
|
+
this._hci = hci;
|
|
17
|
+
|
|
18
|
+
this._advertiseState = null;
|
|
19
|
+
|
|
20
|
+
this._hci.on('error', this.onHciError.bind(this));
|
|
21
|
+
|
|
22
|
+
this._hci.on('leAdvertisingParametersSet', this.onHciLeAdvertisingParametersSet.bind(this));
|
|
23
|
+
this._hci.on('leAdvertisingDataSet', this.onHciLeAdvertisingDataSet.bind(this));
|
|
24
|
+
this._hci.on('leScanResponseDataSet', this.onHciLeScanResponseDataSet.bind(this));
|
|
25
|
+
this._hci.on('leAdvertiseEnableSet', this.onHciLeAdvertiseEnableSet.bind(this));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
startAdvertising (name, serviceUuids) {
|
|
29
|
+
debug('startAdvertising: name = ' + name + ', serviceUuids = ' + JSON.stringify(serviceUuids, null, 2));
|
|
30
|
+
|
|
31
|
+
let advertisementDataLength = 3;
|
|
32
|
+
let scanDataLength = 0;
|
|
33
|
+
|
|
34
|
+
const serviceUuids16bit = [];
|
|
35
|
+
const serviceUuids128bit = [];
|
|
36
|
+
|
|
37
|
+
if (name && name.length) {
|
|
38
|
+
scanDataLength += 2 + name.length;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (serviceUuids && serviceUuids.length) {
|
|
42
|
+
for (let i = 0; i < serviceUuids.length; i++) {
|
|
43
|
+
const serviceUuid = Buffer.from(serviceUuids[i].match(/.{1,2}/g).reverse().join(''), 'hex');
|
|
44
|
+
|
|
45
|
+
if (serviceUuid.length === 2) {
|
|
46
|
+
serviceUuids16bit.push(serviceUuid);
|
|
47
|
+
} else if (serviceUuid.length === 16) {
|
|
48
|
+
serviceUuids128bit.push(serviceUuid);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (serviceUuids16bit.length) {
|
|
54
|
+
advertisementDataLength += 2 + 2 * serviceUuids16bit.length;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (serviceUuids128bit.length) {
|
|
58
|
+
advertisementDataLength += 2 + 16 * serviceUuids128bit.length;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const advertisementData = Buffer.alloc(advertisementDataLength);
|
|
62
|
+
const scanData = Buffer.alloc(scanDataLength);
|
|
63
|
+
|
|
64
|
+
// flags
|
|
65
|
+
advertisementData.writeUInt8(2, 0);
|
|
66
|
+
advertisementData.writeUInt8(0x01, 1);
|
|
67
|
+
advertisementData.writeUInt8(0x06, 2);
|
|
68
|
+
|
|
69
|
+
let advertisementDataOffset = 3;
|
|
70
|
+
|
|
71
|
+
if (serviceUuids16bit.length) {
|
|
72
|
+
advertisementData.writeUInt8(1 + 2 * serviceUuids16bit.length, advertisementDataOffset);
|
|
73
|
+
advertisementDataOffset++;
|
|
74
|
+
|
|
75
|
+
advertisementData.writeUInt8(0x03, advertisementDataOffset);
|
|
76
|
+
advertisementDataOffset++;
|
|
77
|
+
|
|
78
|
+
for (let i = 0; i < serviceUuids16bit.length; i++) {
|
|
79
|
+
serviceUuids16bit[i].copy(advertisementData, advertisementDataOffset);
|
|
80
|
+
advertisementDataOffset += serviceUuids16bit[i].length;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (serviceUuids128bit.length) {
|
|
85
|
+
advertisementData.writeUInt8(1 + 16 * serviceUuids128bit.length, advertisementDataOffset);
|
|
86
|
+
advertisementDataOffset++;
|
|
87
|
+
|
|
88
|
+
advertisementData.writeUInt8(0x06, advertisementDataOffset);
|
|
89
|
+
advertisementDataOffset++;
|
|
90
|
+
|
|
91
|
+
for (let i = 0; i < serviceUuids128bit.length; i++) {
|
|
92
|
+
serviceUuids128bit[i].copy(advertisementData, advertisementDataOffset);
|
|
93
|
+
advertisementDataOffset += serviceUuids128bit[i].length;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// name
|
|
98
|
+
if (name && name.length) {
|
|
99
|
+
const nameBuffer = Buffer.from(name);
|
|
100
|
+
|
|
101
|
+
scanData.writeUInt8(1 + nameBuffer.length, 0);
|
|
102
|
+
scanData.writeUInt8(0x08, 1);
|
|
103
|
+
nameBuffer.copy(scanData, 2);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
this.startAdvertisingWithEIRData(advertisementData, scanData);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
startAdvertisingIBeacon (data) {
|
|
110
|
+
debug('startAdvertisingIBeacon: data = ' + data.toString('hex'));
|
|
111
|
+
|
|
112
|
+
const dataLength = data.length;
|
|
113
|
+
const manufacturerDataLength = 4 + dataLength;
|
|
114
|
+
const advertisementDataLength = 5 + manufacturerDataLength;
|
|
115
|
+
// const scanDataLength = 0;
|
|
116
|
+
|
|
117
|
+
const advertisementData = Buffer.alloc(advertisementDataLength);
|
|
118
|
+
const scanData = Buffer.alloc(0);
|
|
119
|
+
|
|
120
|
+
// flags
|
|
121
|
+
advertisementData.writeUInt8(2, 0);
|
|
122
|
+
advertisementData.writeUInt8(0x01, 1);
|
|
123
|
+
advertisementData.writeUInt8(0x06, 2);
|
|
124
|
+
|
|
125
|
+
advertisementData.writeUInt8(manufacturerDataLength + 1, 3);
|
|
126
|
+
advertisementData.writeUInt8(0xff, 4);
|
|
127
|
+
advertisementData.writeUInt16LE(0x004c, 5); // Apple Company Identifier LE (16 bit)
|
|
128
|
+
advertisementData.writeUInt8(0x02, 7); // type, 2 => iBeacon
|
|
129
|
+
advertisementData.writeUInt8(dataLength, 8);
|
|
130
|
+
|
|
131
|
+
data.copy(advertisementData, 9);
|
|
132
|
+
|
|
133
|
+
this.startAdvertisingWithEIRData(advertisementData, scanData);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
startAdvertisingWithEIRData (advertisementData, scanData) {
|
|
137
|
+
advertisementData = advertisementData || Buffer.alloc(0);
|
|
138
|
+
scanData = scanData || Buffer.alloc(0);
|
|
139
|
+
|
|
140
|
+
debug('startAdvertisingWithEIRData: advertisement data = ' + advertisementData.toString('hex') + ', scan data = ' + scanData.toString('hex'));
|
|
141
|
+
|
|
142
|
+
let error = null;
|
|
143
|
+
|
|
144
|
+
if (advertisementData.length > 31) {
|
|
145
|
+
error = new Error('Advertisement data is over maximum limit of 31 bytes');
|
|
146
|
+
} else if (scanData.length > 31) {
|
|
147
|
+
error = new Error('Scan data is over maximum limit of 31 bytes');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (error) {
|
|
151
|
+
this.emit('advertisingStart', error);
|
|
152
|
+
} else {
|
|
153
|
+
this._advertiseState = 'starting';
|
|
154
|
+
|
|
155
|
+
if (isIntelEdison || isYocto) {
|
|
156
|
+
// work around for Intel Edison
|
|
157
|
+
debug('skipping first set of scan response and advertisement data');
|
|
158
|
+
} else {
|
|
159
|
+
this._hci.setScanResponseData(scanData);
|
|
160
|
+
this._hci.setAdvertisingData(advertisementData);
|
|
161
|
+
}
|
|
162
|
+
this._hci.setAdvertiseEnable(true);
|
|
163
|
+
this._hci.setScanResponseData(scanData);
|
|
164
|
+
this._hci.setAdvertisingData(advertisementData);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
restartAdvertising () {
|
|
169
|
+
this._advertiseState = 'restarting';
|
|
170
|
+
|
|
171
|
+
this._hci.setAdvertiseEnable(true);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
stopAdvertising () {
|
|
175
|
+
this._advertiseState = 'stopping';
|
|
176
|
+
|
|
177
|
+
this._hci.setAdvertiseEnable(false);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
onHciError (error) {
|
|
181
|
+
this.emit('error', error);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
onHciLeAdvertisingParametersSet (status) {
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
onHciLeAdvertisingDataSet (status) {
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
onHciLeScanResponseDataSet (status) {
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
onHciLeAdvertiseEnableSet (status) {
|
|
194
|
+
if (this._advertiseState === 'starting') {
|
|
195
|
+
this._advertiseState = 'started';
|
|
196
|
+
|
|
197
|
+
let error = null;
|
|
198
|
+
|
|
199
|
+
if (status) {
|
|
200
|
+
error = new Error(Hci.STATUS_MAPPER[status] || ('Unknown (' + status + ')'));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
this.emit('advertisingStart', error);
|
|
204
|
+
} else if (this._advertiseState === 'stopping') {
|
|
205
|
+
this._advertiseState = 'stopped';
|
|
206
|
+
|
|
207
|
+
this.emit('advertisingStop');
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
module.exports = Gap;
|