UNPKG

obniz

Version:

obniz sdk for javascript

753 lines (752 loc) 25.1 kB
"use strict"; /** * @packageDocumentation * @module ObnizCore.Components.Ble.Hci */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BleRemotePeripheral = void 0; const eventemitter3_1 = __importDefault(require("eventemitter3")); const ObnizError_1 = require("../../../ObnizError"); const ble_1 = require("./ble"); const bleHelper_1 = __importDefault(require("./bleHelper")); const bleRemoteService_1 = require("./bleRemoteService"); const retry_1 = require("../../utils/retry"); /** * @category Use as Central */ class BleRemotePeripheral { constructor(obnizBle, address) { this.advertisingDataRows = {}; this.scanResponseDataRows = {}; /** * @ignore */ this._connectSetting = {}; /** * Indicating this peripheral is found by scan or set from software. * * @ignore */ this.discoverdOnRemote = undefined; this.keys = [ 'device_type', 'address_type', 'ble_event_type', 'rssi', 'adv_data', 'scan_resp', 'service_data', 'primary_phy', 'secondary_phy', ]; this._extended = false; this.obnizBle = obnizBle; this.address = address; this.connected = false; this.connected_at = null; this.device_type = null; this.address_type = null; this.ble_event_type = null; this.rssi = null; this.primary_phy = null; this.secondary_phy = null; // this.adv_data = null; this.scan_resp = null; this.localName = null; this.manufacturerSpecificData = null; this.manufacturerSpecificDataInScanResponse = null; this.serviceData = null; this.iBeacon = null; this._services = []; this.emitter = new eventemitter3_1.default(); this.service_data = null; } /** * It contains all discovered services in a peripheral as an array. * It is discovered when connection automatically. * * ```javascript * // Javascript Example * * await obniz.ble.initWait(); * var target = { * uuids: ["fff0"], * }; * var peripheral = await obniz.ble.scan.startOneWait(target); * if(!peripheral) { * console.log('no such peripheral') * return; * } * try { * await peripheral.connectWait(); * console.log("connected"); * for (var service of peripheral.services) { * console.log(service.uuid) * } * } catch(e) { * console.error(e); * } * ``` * */ get services() { return this._services; } /** * @ignore * @return {String} json value */ toString() { return JSON.stringify({ address: this.address, addressType: this.address_type, advertisement: this.adv_data, scanResponse: this.scan_resp, rssi: this.rssi, }); } /** * @ignore * @param dic */ setParams(dic) { this.advertise_data_rows = null; for (const key in dic) { // eslint-disable-next-line no-prototype-builtins if (dic.hasOwnProperty(key) && this.keys.includes(key)) { this[key] = dic[key]; } } this.analyseAdvertisement(); } /** * @ignore * @param extendedMode */ setExtendFlg(extendedMode) { this._extended = extendedMode; } /** * @deprecated As of release 3.5.0, replaced by {@link #connectWait()} */ connect(setting) { // noinspection JSIgnoredPromiseFromCall this.connectWait(setting); // background } /** * This connects obniz to the peripheral. * If ble scanning is undergoing, scan will be terminated immidiately. * * It throws when connection establish failed. * * when connection established, all service/characteristics/desriptors will be discovered automatically. * This function will wait until all discovery done. * * About Failures * Connection fails some reasons. You can find reason from thrown error. * Also obniz provide 90 seconds timeout for connection establish. * * ```javascript * // Javascript Example * * await obniz.ble.initWait(); * var target = { * uuids: ["fff0"], * }; * var peripheral = await obniz.ble.scan.startOneWait(target); * if(!peripheral) { * console.log('no such peripheral') * return; * } * try { * await peripheral.connectWait(); * console.log("connected"); * } catch(e) { * console.log("can't connect"); * } * ``` * * There are options for connection * * ```javascript * // Javascript Example * * await obniz.ble.initWait(); * var target = { * uuids: ["fff0"], * }; * var peripheral = await obniz.ble.scan.startOneWait(target); * if(!peripheral) { * console.log('no such peripheral') * return; * } * try { * await peripheral.connectWait({ * * }); * console.log("connected"); * } catch(e) { * console.log("can't connect"); * } * ``` * */ async connectWait(setting) { var _a; if (this.connected && (setting === null || setting === void 0 ? void 0 : setting.forceConnect) === false) return; this._connectSetting = setting || {}; this._connectSetting.autoDiscovery = this._connectSetting.autoDiscovery !== false; this._connectSetting.mtuRequest = this._connectSetting.mtuRequest === undefined ? 256 : this._connectSetting.mtuRequest; if (this._connectSetting.usePyh1m === undefined) { this._connectSetting.usePyh1m = true; } if (this._connectSetting.usePyh2m === undefined) { this._connectSetting.usePyh2m = true; } if (this._connectSetting.usePyhCoded === undefined) { this._connectSetting.usePyhCoded = true; } await this.obnizBle.scan.endWait(); // for only typescript type const mtuRequest = this._connectSetting.mtuRequest; await (0, retry_1.retry)((_a = this._connectSetting.retry) !== null && _a !== void 0 ? _a : 1, async () => { try { if (this._extended) { await this.obnizBle.centralBindings.connectExtendedWait(this.address, mtuRequest, () => { if (this._connectSetting.pairingOption) { this.setPairingOption(this._connectSetting.pairingOption); } }, this._connectSetting.usePyh1m, this._connectSetting.usePyh2m, this._connectSetting.usePyhCoded); } else { await this.obnizBle.centralBindings.connectWait(this.address, mtuRequest, () => { if (this._connectSetting.pairingOption) { this.setPairingOption(this._connectSetting.pairingOption); } }); } } catch (e) { if (e instanceof ObnizError_1.ObnizTimeoutError) { await this.obnizBle.resetWait(); throw new Error(`Connection to device(address=${this.address}) was timedout. ble have been reseted`); } throw e; } this.connected = true; this.connected_at = new Date(); try { if (this._connectSetting.connectionParameterUpdateAccept === false) { this.obnizBle.centralBindings._signalings[this.address].connectionParameterUpdateAccept = false; } if (this._connectSetting.waitUntilPairing && !(await this.isPairingFinishedWait())) { // console.log('waitUntilPairing'); await this.pairingWait(this._connectSetting.pairingOption); // console.log('waitUntilPairing finished'); } if (this._connectSetting.autoDiscovery) { await this.discoverAllHandlesWait(); } } catch (e) { try { await this.disconnectWait(); } catch (e2) { // nothing } throw e; } }, async (err) => { // console.log('connection fail, retry', err); }); this.obnizBle.Obniz._runUserCreatedFunction(this.onconnect); this.emitter.emit('connect'); } /** * @deprecated replaced by {@link #disconnectWait()} */ disconnect() { // noinspection JSIgnoredPromiseFromCall this.disconnectWait(); // background } /** * This disconnects obniz from peripheral. * * It throws when failed * * ```javascript * // Javascript Example * * await obniz.ble.initWait(); * var target = { * uuids: ["fff0"], * }; * var peripheral = await obniz.ble.scan.startOneWait(target); * if(!peripheral) { * console.log('no such peripheral') * return; * } * try { * await peripheral.connectWait(); * console.log("connected"); * await peripheral.disconnectWait(); * console.log("disconnected"); * } catch(e) { * console.log("can't connect / can't disconnect"); * } * ``` */ disconnectWait() { return new Promise((resolve, reject) => { if (!this.connected) { resolve(); return; } const cuttingFailedError = new Error(`cutting connection to peripheral name=${this.localName} address=${this.address} was failed`); this.emitter.once('statusupdate', (params) => { clearTimeout(timeoutTimer); if (params.status === 'disconnected') { resolve(true); // for compatibility } else { reject(cuttingFailedError); } }); const timeoutError = new ObnizError_1.ObnizTimeoutError(`cutting connection to peripheral name=${this.localName} address=${this.address} was failed`); const timeoutTimer = setTimeout(() => { reject(timeoutError); }, 90 * 1000); this.obnizBle.centralBindings.disconnect(this.address); }); } /** * Check the PHY used in the connection * * ```javascript * // Javascript Example * * await obniz.ble.initWait(); * var target = { * uuids: ["fff0"], * }; * var peripheral = await obniz.ble.scan.startOneWait(target); * if(!peripheral) { * console.log('no such peripheral') * return; * } * try { * await peripheral.connectWait(); * console.log("connected"); * const phy = await peripheral.readPhyWait() * console.log(phy) * } catch(e) { * console.error(e); * } * ``` * */ async readPhyWait() { const phyToStr = (phy) => { switch (phy) { case 1: return '1m'; case 2: return '2m'; case 3: return 'coded'; default: throw new Error('decode Phy Error'); } }; const data = await this.obnizBle.centralBindings.readPhyWait(this.address); if (data.status === 0) { return { txPhy: phyToStr(data.txPhy), rxPhy: phyToStr(data.rxPhy) }; } } /** * Check the PHY used in the connection. * Request to change the current PHY * * It will be changed if it corresponds to the PHY set by the other party. * * Changes can be seen on onUpdatePhy * * ```javascript * // Javascript Example * * await obniz.ble.initWait(); * obniz.ble.onUpdatePhy = ((txPhy, rxPhy) => { * console.log("txPhy "+txPhy+" rxPhy "+rxPhy); * }); * var target = { * uuids: ["fff0"], * }; * var peripheral = await obniz.ble.scan.startOneWait(target); * if(!peripheral) { * console.log('no such peripheral') * return; * } * try { * await peripheral.connectWait(); * console.log("connected"); * await peripheral.setPhyWait(false,false,true,true,true);//Request Only PHY Coded * } catch(e) { * console.error(e); * } * ``` * */ async setPhyWait(usePhy1m, usePhy2m, usePhyCoded, useCodedModeS8, useCodedModeS2) { await this.obnizBle.centralBindings.setPhyWait(this.address, usePhy1m, usePhy2m, usePhyCoded, useCodedModeS8, useCodedModeS2); } /** * It returns a service which having specified uuid in [[services]]. * Case is ignored. So aa00 and AA00 are the same. * * ```javascript * // Javascript Example * * await obniz.ble.initWait(); * var target = { * uuids: ["fff0"], * }; * var peripheral = await obniz.ble.scan.startOneWait(target); * if(!peripheral) { * console.log('no such peripheral') * return; * } * try { * await peripheral.connectWait(); * console.log("connected"); * var service = peripheral.getService("1800") * if (!service) { * console.log("service not found") * return; * } * console.log(service.uuid) * } catch(e) { * console.error(e); * } * ``` * * @param uuid */ getService(uuid) { uuid = bleHelper_1.default.uuidFilter(uuid); for (const key in this._services) { if (this._services[key].uuid === uuid) { return this._services[key]; } } return null; } /** * @ignore * @param param */ findService(param) { const serviceUuid = bleHelper_1.default.uuidFilter(param.service_uuid); return this.getService(serviceUuid); } /** * @ignore * @param param */ findCharacteristic(param) { const serviceUuid = bleHelper_1.default.uuidFilter(param.service_uuid); const characteristicUuid = bleHelper_1.default.uuidFilter(param.characteristic_uuid); const s = this.getService(serviceUuid); if (s) { return s.getCharacteristic(characteristicUuid); } return null; } /** * @ignore * @param param */ findDescriptor(param) { const descriptorUuid = bleHelper_1.default.uuidFilter(param.descriptor_uuid); const c = this.findCharacteristic(param); if (c) { return c.getDescriptor(descriptorUuid); } return null; } /** * Discover services. * * If connect setting param 'autoDiscovery' is true(default), * services are automatically disvocer on connection established. * * * ```javascript * // Javascript Example * await obniz.ble.initWait({}); * obniz.ble.scan.onfind = function(peripheral){ * if(peripheral.localName == "my peripheral"){ * peripheral.onconnect = async function(){ * console.log("success"); * await peripheral.discoverAllServicesWait(); //manually discover * let service = peripheral.getService("1800"); * } * peripheral.connectWait({autoDiscovery:false}); * } * } * await obniz.ble.scan.startWait(); * ``` */ async discoverAllServicesWait() { const serviceUuids = await this.obnizBle.centralBindings.discoverServicesWait(this.address); for (const uuid of serviceUuids) { let child = this.getService(uuid); if (!child) { const newService = new bleRemoteService_1.BleRemoteService({ uuid }); newService.parent = this; this._services.push(newService); child = newService; } child.discoverdOnRemote = true; this.obnizBle.Obniz._runUserCreatedFunction(this.ondiscoverservice, child); } const children = this._services.filter((elm) => { return elm.discoverdOnRemote; }); this.obnizBle.Obniz._runUserCreatedFunction(this.ondiscoverservicefinished, children); return children; } /** * @ignore */ async discoverAllHandlesWait() { const ArrayFlat = (array, depth) => { const flattend = []; const flat = (_array, _depth) => { for (const el of _array) { if (Array.isArray(el) && _depth > 0) { flat(el, _depth - 1); } else { flattend.push(el); } } }; flat(array, Math.floor(depth) || 1); return flattend; }; const services = await this.discoverAllServicesWait(); const charsNest = await Promise.all(services.map((s) => s.discoverAllCharacteristicsWait())); const chars = ArrayFlat(charsNest); const descriptorsNest = await Promise.all(chars.map((c) => c.discoverAllDescriptorsWait())); // eslint-disable-next-line @typescript-eslint/no-unused-vars const descriptors = ArrayFlat(descriptorsNest); } /** * @ignore * @param notifyName * @param params */ notifyFromServer(notifyName, params) { this.emitter.emit(notifyName, params); switch (notifyName) { case 'statusupdate': { if (params.status === 'disconnected') { const pre = this.connected; this.connected = false; this.connected_at = null; if (pre) { this.obnizBle.Obniz._runUserCreatedFunction(this.ondisconnect, params.reason); this.emitter.emit('disconnect', params.reason); } } break; } } } /** * @ignore */ advertisementServiceUuids() { const results = []; this._addServiceUuids(results, this.searchTypeVal(0x02), 16); this._addServiceUuids(results, this.searchTypeVal(0x03), 16); this._addServiceUuids(results, this.searchTypeVal(0x04), 32); this._addServiceUuids(results, this.searchTypeVal(0x05), 32); this._addServiceUuids(results, this.searchTypeVal(0x06), 128); this._addServiceUuids(results, this.searchTypeVal(0x07), 128); return results; } /** * Start pairing. * This function return `keys` which you can use next time pairing with same device. * * ```javascript * // Javascript Example * await obniz.ble.initWait({}); * obniz.ble.scan.onfind = function(peripheral){ * if(peripheral.localName == "my peripheral"){ * peripheral.onconnect = async function(){ * console.log("success"); * const keys = await peripheral.pairingWait(); * * // Please store `keys` if you want to bond. * } * await peripheral.connectWait(); * } * } * await obniz.ble.scan.startWait(); * ``` * * * * If you have already keys, please use options.keys * * ```javascript * // Javascript Example * * const keys = "xxxxx"; * await obniz.ble.initWait({}); * obniz.ble.scan.onfind = function(peripheral){ * if(peripheral.localName == "my peripheral"){ * peripheral.onconnect = async function(){ * console.log("success"); * await peripheral.pairingWait({keys}); // pairing with stored keys. * * } * await peripheral.connectWait(); * } * } * await obniz.ble.scan.startWait(); * ``` * * Go to [[BlePairingOptions]] to see more option. * * @param options BlePairingOptions */ async pairingWait(options) { const result = await this.obnizBle.centralBindings.pairingWait(this.address, options); return result; } async getPairingKeysWait() { const result = await this.obnizBle.centralBindings.getPairingKeysWait(this.address); return result; } async isPairingFinishedWait() { const result = await this.obnizBle.centralBindings.isPairingFinishedWait(this.address); return result; } setPairingOption(options) { this.obnizBle.centralBindings.setPairingOption(this.address, options); } analyseAdvertisement() { if (this.advertise_data_rows) return; this.advertise_data_rows = []; if (this.adv_data) { for (let i = 0; i < this.adv_data.length; i++) { const length = this.adv_data[i]; const arr = new Array(length); for (let j = 0; j < length; j++) { arr[j] = this.adv_data[i + j + 1]; } this.advertise_data_rows.push(arr); this.advertisingDataRows[this.adv_data[i + 1]] = this.adv_data.slice(i + 2, i + length + 1); i = i + length; } } if (this.scan_resp) { for (let i = 0; i < this.scan_resp.length; i++) { const length = this.scan_resp[i]; const arr = new Array(length); for (let j = 0; j < length; j++) { arr[j] = this.scan_resp[i + j + 1]; } this.advertise_data_rows.push(arr); this.scanResponseDataRows[this.scan_resp[i + 1]] = this.scan_resp.slice(i + 2, i + length + 1); i = i + length; } } this.setLocalName(); this.setManufacturerSpecificData(); this.setServiceData(); this.setIBeacon(); } searchTypeVal(type, fromScanResponseData = false) { this.analyseAdvertisement(); if (this.advertisingDataRows[type] && !fromScanResponseData) return this.advertisingDataRows[type]; else if (this.scanResponseDataRows[type]) return this.scanResponseDataRows[type]; else return undefined; } setLocalName() { var _a; const data = (_a = this.searchTypeVal(0x09)) !== null && _a !== void 0 ? _a : this.searchTypeVal(0x08); this.localName = data ? String.fromCharCode.apply(null, data) : null; } setManufacturerSpecificData() { var _a, _b; this.manufacturerSpecificData = (_a = this.searchTypeVal(0xff)) !== null && _a !== void 0 ? _a : null; this.manufacturerSpecificDataInScanResponse = (_b = this.searchTypeVal(0xff, true)) !== null && _b !== void 0 ? _b : null; } setServiceData() { var _a; this.serviceData = (_a = this.searchTypeVal(0x16)) !== null && _a !== void 0 ? _a : null; } setIBeacon() { const data = this.manufacturerSpecificData; if (!data || data[0] !== 0x4c || data[1] !== 0x00 || data[2] !== 0x02 || data[3] !== 0x15 || data.length !== 25) { this.iBeacon = null; return; } const uuidData = data.slice(4, 20); let uuid = ''; for (let i = 0; i < uuidData.length; i++) { uuid = uuid + ('00' + uuidData[i].toString(16)).slice(-2); if (i === 4 - 1 || i === 4 + 2 - 1 || i === 4 + 2 * 2 - 1 || i === 4 + 2 * 3 - 1) { uuid += '-'; } } const major = (data[20] << 8) + data[21]; const minor = (data[22] << 8) + data[23]; const power = Buffer.from([data[24]]).readInt8(0); this.iBeacon = { uuid, major, minor, power, rssi: this.rssi, }; } _addServiceUuids(results, data, bit) { if (!data) { return; } const uuidLength = bit / 8; for (let i = 0; i < data.length; i = i + uuidLength) { const one = data.slice(i, i + uuidLength); results.push(ble_1.ObnizBLE._dataArray2uuidHex(one, true)); } } } exports.BleRemotePeripheral = BleRemotePeripheral;