-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbt.js
350 lines (317 loc) · 12.9 KB
/
bt.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
(function() {
const DevEnum = require('windows.devices.enumeration');
const DevInfo = DevEnum.DeviceInformation;
const BT = require('windows.devices.bluetooth');
const BTDevice = BT.BluetoothDevice;
const Radios = require('windows.devices.radios');
const Radio = Radios.Radio;
const RadioKind = Radios.RadioKind;
const RadioState = Radios.RadioState;
var Bluetooth = {};
/**
* Determines if bluetooth is supported on this device
* by searching for a bluetooth radio.
* @returns {Promise} resolves a boolean representing whether
* or not bluetooth is supported.
*/
Bluetooth.isSupported = isSupported = async function () {
let radios = await _promisify(Radio, Radio.getRadiosAsync)();
radios = radios.first();
while (radios.hasCurrent) {
let radio = radios.current;
if(radio.kind == RadioKind.bluetooth)
return true;
radios.moveNext();
}
return false;
}
/**
* Determines if bluetooth is enabled on this device
* by searching for a bluetooth radio that is enabled.
* @returns {Promise} resolves a boolean representing whether
* or not bluetooth is enabled.
*/
Bluetooth.isEnabled = isEnabled = async function () {
let radios = await _promisify(Radio, Radio.getRadiosAsync)();
radios = radios.first();
while (radios.hasCurrent) {
let radio = radios.current;
if(radio.kind == RadioKind.bluetooth && radio.state == RadioState.on)
return true;
radios.moveNext();
}
return false;
}
/**
* Attempts to turn on bluetooth radios.
* @returns {Promise} resolves if was able to enable any bluetooth radio,
* rejects otherwise.
*/
Bluetooth.enable = enable = function () {
return new Promise((resolve, reject) => {
Radio.getRadiosAsync((err, radios) => {
if (err) {
return reject(err);
}
radios = radios.first();
let promises = [];
while (radios.hasCurrent) {
radio = radios.current;
if(radio.kind == RadioKind.bluetooth) {
promises.push(new Promise((resolve, reject) => {
if (radio.state != RadioState.on) {
radio.setStateAsync(RadioState.on, (err, result) => {
if (result == Radios.RadioAccessStatus.allowed) {
reject(); // Inverted, actually resolves
} else {
resolve(); // Inverted, actually rejects
}
});
} else {
reject(); // Inverted, actually resolves
}
}));
}
radios.moveNext();
}
// All promises are inverted from what they should be,
// so invert again to resolve on the first promise that
// would actually resolve (if uninverted)
Promise.all(promises).then(() => reject('Failed to enable any radios'), resolve);
});
});
}
let pairedQuery = BTDevice.getDeviceSelectorFromPairingState(true);
let unpairedQuery = BTDevice.getDeviceSelectorFromPairingState(false);
/**
* Converts an integer bluetooth address into a hexadecimal string.
* @param {Number} address - integer bluetooth address.
* @returns {String} hexadecimal bluetooth address string.
*/
function _BTAddressToHexString(address) {
if (typeof address !== 'number') {
throw new Error(`Parameter address must be a number between 0 and 0xffffffffffff (281474976710655).`)
}
if (address > 0xffffffffffff || address < 0) { // max bluetooth address value (ff:ff:ff:ff:ff:ff), must be positive
throw new Error(`Address ${address} out of range. Must be between 0 and 0xffffffffffff (281474976710655).`);
}
let hex = address.toString(16);
while (hex.length < 12) {
hex = '0' + hex;
}
address = '';
for(let i = 0; i < hex.length; i += 2) {
if (i > 0) {
address += ':'
}
address += hex.substr(i,2);
}
return address;
}
/**
* Converts a hexadecimal bluetooth address into an integer.
* @param {String} address - hexadecimal bluetooth address string.
* @returns {Number} integer bluetooth address.
*/
function _BTAddressToInt(address) {
if (typeof address !== 'string') {
throw new Error('Parameter address must be a string of twelve hexidecimal digits, optionally separated by a colon (:) every two digits.')
}
if (!address.match(/(?:^[0-9a-f]{12}$)|(?:^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$)/i)) {
throw new Error(`Invalid address string '${address}'. Must be twelve hexidecimal digits, optionally separated by a colon (:) every two digits.`);
}
address = address.replace(/:/g, '');
address = parseInt(address, 16);
return address;
}
/**
* Creates a new object containing information about the bluetooth
* device. Standard output for devices in this library.
* @param {DeviceInformation | BluetoothDevice} devInfo - A DeviceInformation or BluetoothDevice to be parsed.
* @returns {Promise} resolves to representation of a bluetooth device, or rejects with any errors.
*/
async function _parseBTDevice(devInfo) {
let btd = devInfo;
if (devInfo instanceof DevInfo) {
btd = await _promisify(BTDevice, BTDevice.fromIdAsync)(devInfo.id);
} else if (devInfo instanceof BTDevice) {
devInfo = btd.deviceInformation;
} else {
throw new Error('Invalid argument, must be either a DeviceInformation or BluetoothDevice object.');
}
return {
name: devInfo.name,
paired: devInfo.pairing.isPaired,
canPair: devInfo.pairing.canPair,
address: _BTAddressToHexString(btd.bluetoothAddress),
class: {
major: btd.classOfDevice.majorClass,
minor: btd.classOfDevice.minorClass,
raw: btd.classOfDevice.rawValue
},
connected: _parseEnumValue(btd.connectionStatus, BT.BluetoothConnectionStatus) == 'connected',
protection: _parseEnumValue(devInfo.pairing.protectionLevel, DevEnum.DevicePairingProtectionLevel)
};
}
/**
* Retrieves the key name for the enum value of the given enum.
* @param {*} val - the value of the enumeration.
* @param {*} enumeration - the enumeration from which the value comes from.
* @returns {String} the key name for the value given in the enumeration given,
* or null if it does not exist.
*/
function _parseEnumValue(val, enumeration) {
let keys = Object.keys(enumeration);
for (key in keys) {
key = keys[key];
if(enumeration[key] === val) {
return key;
}
}
return null;
}
/**
* Treat the given bluetooth address to ensure that it is a valid bluetooth address
* and is in the correct integer format.
* @param {String | Number} address - the hexadecimal string representation of a
* bluetooth address or the integer representation.
* @returns {Number} the integer representation of the given bluetooth address.
* @throws {Error} if the string is malformated or the address is out of range.
*/
function _treatAddress(address) {
switch (typeof address) {
case 'string':
address = _BTAddressToInt(address);
// No break intended.
case 'number':
if (address > 0xffffffffffff || address < 0) { // max bluetooth address value (ff:ff:ff:ff:ff:ff), must be positive
throw new Error(`Address ${address} out of range. Must be between 0 and #ffffffffffff (281474976710655).`);
}
return address;
default:
throw new Error('Invalid address provided. Must be either string or number.');
}
}
/**
* Turns the given function that takes a callback as the last argument
* into a function that returns a promise.
*/
function _promisify(obj, func) {
return function(...args) {
return new Promise((res, rej) => {
func.call(obj, ...args, (err, data) => {
if (err) {
rej(err);
} else {
res(data);
}
});
});
}
}
/**
* Initiates a scan for unpaired bluetooth devices that are turned on within range.
* @returns {Promise} Resolves an array of bluetooth device objects that are not
* currently paired to this device.
*/
Bluetooth.listUnpaired = listUnpaired = async function() {
let unpaired = [];
let results = await _promisify(DevInfo, DevInfo.findAllAsync)(unpairedQuery);
results = results.first();
while(results.hasCurrent) {
let device = await _parseBTDevice(results.current);
unpaired.push(device);
results.moveNext();
}
return unpaired;
}
/**
* Lists all bluetooth devices paired to this device.
* @returns {Promise} Resolves an array of bluetooth device objects that are
* currently paired to this device.
*/
Bluetooth.listPaired = listPaired = async function() {
let paired = [];
let results = await _promisify(DevInfo, DevInfo.findAllAsync)(pairedQuery);
results = results.first();
while(results.hasCurrent) {
let device = await _parseBTDevice(results.current);
paired.push(device);
results.moveNext();
}
return paired;
}
/**
* Lists all bluetooth devices paired to this device, and all bluetooth devices that
* are not currently paired and are on within range.
* @returns {Promise} Resolves an array of bluetooth device objects that are
* or are not paired to this device.
*/
Bluetooth.listAll = listAll = async function() {
let values = await Promise.all([listPaired(), listUnpaired()]);
return values[0].concat(values[1]);
}
/**
* Creates a bluetooth device object from a bluetooth address (number or hex string).
* @returns {Promise} Resolves a bluetooth device object that represents the device
* with the given address.
*/
Bluetooth.fromAddress = fromAddress = async function(address) {
address = _treatAddress(address);
let btd = await _promisify(BTDevice, BTDevice.fromBluetoothAddressAsync)(address);
return await _parseBTDevice(btd);
}
/**
* Attempts to pair with the device at the given address.
* @returns {Promise} Resolves a result object which contains a status string and a
* bluetooth device object for the device. Rejects with and error
* if the pairing fails.
*/
Bluetooth.pair = pair = async function(address) {
let btd = (await _promisify(BTDevice, BTDevice.fromBluetoothAddressAsync)(_treatAddress(address))).deviceInformation;
let pairing = btd.pairing.custom;
pairing.on('pairingRequested', (custom, request) => {
request.accept();
});
let pairingKinds = DevEnum.DevicePairingKinds;
pairingKinds = pairingKinds.displayPin; // Only one that seems to work at all reliably from library.
// pairingKinds = pairingKinds.confirmOnly | pairingKinds.confirmPinMatch | pairingKinds.displayPin | pairingKinds.providePin;
let result = await _promisify(pairing, pairing.pairAsync)(pairingKinds, btd.pairing.protectionLevel);
let status = _parseEnumValue(result.status, DevEnum.DevicePairingResultStatus);
switch(status) {
case 'paired':
case 'alreadyPaired':
let result = {
status: status,
device: await fromAddress(address)
}
return result;
default:
throw new Error(`Pairing failed: ${status}`);
}
}
/**
* Attempts to unpair the device at the given address.
* @returns {Promise} Resolves a result object which contains a status string and a
* bluetooth device object for the device. Rejects with and error
* if the unpairing fails.
*/
Bluetooth.unpair = unpair = async function(address) {
let btd = (await _promisify(BTDevice, BTDevice.fromBluetoothAddressAsync)(_treatAddress(address))).deviceInformation;
let pairing = btd.pairing;
let result = await _promisify(pairing, pairing.unpairAsync)();
let status = _parseEnumValue(result.status, DevEnum.DeviceUnpairingResultStatus);
switch(status) {
case 'unpaired':
case 'alreadyUnpaired':
let result = {
status: status,
device: await fromAddress(address)
}
return result;
default:
throw new Error(`Unpairing failed: ${status}`);
}
}
module.exports = Bluetooth;
}())