UNPKG

obniz

Version:

obniz sdk for javascript

408 lines (407 loc) 16.9 kB
"use strict"; /** * @packageDocumentation * * @ignore */ // let debug = require('debug')('gap'); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Gap = void 0; /** * @ignore */ const debug = (message) => { // do nothing. // console.log('gap debug', message); }; const eventemitter3_1 = __importDefault(require("eventemitter3")); const ObnizError_1 = require("../../../../../ObnizError"); const bleHelper_1 = __importDefault(require("../../bleHelper")); const LegacyAdvertisingPduMask = 0b0010000; const LegacyAdvertising_ADV_SCAN_RESP_TO_ADV_IND = 0b0011011; const LegacyAdvertising_ADV_SCAN_RESP_TO_SCAN_IND = 0b0011010; const LegacyAdvertising_ADV_NONCONN_IND = 0b0010000; /** * @ignore */ class Gap extends eventemitter3_1.default { constructor(hci) { super(); this._scanState = null; this._scanFilterDuplicates = null; this._discoveries = {}; this._hci = hci; this._reset(); this._hci.on('leAdvertisingReport', this.onHciLeAdvertisingReport.bind(this)); this._hci.on('leExtendedAdvertisingReport', this.onHciLeExtendedAdvertisingReport.bind(this)); } /** * @ignore * @private */ _reset() { this._scanState = null; this._scanFilterDuplicates = null; this._discoveries = {}; } async startScanningWait(allowDuplicates, activeScan) { this._scanFilterDuplicates = !allowDuplicates; this._discoveries = {}; // Always set scan parameters before scanning // https://www.bluetooth.org/docman/handlers/downloaddoc.ashx?doc_id=229737 // p106 - p107 try { if (this._scanState === 'starting' || this._scanState === 'started') { await this.setScanEnabledWait(false, true); } } catch (e) { if (e instanceof ObnizError_1.ObnizBleScanStartError) { // If not started yet. this error may called. just ignore it. } else { throw e; } } this._scanState = 'starting'; const status = await this._hci.setScanParametersWait(activeScan); if (status !== 0) { throw new ObnizError_1.ObnizBleScanStartError(status, `startScanning Error setting active scan=${activeScan} was failed`); } await new Promise((resolve) => setTimeout(resolve, 1000)); await this.setScanEnabledWait(true, this._scanFilterDuplicates); } async stopScanningWait() { try { if (this._scanState === 'starting' || this._scanState === 'started') { await this.setScanEnabledWait(false, true); } } catch (e) { if (e instanceof ObnizError_1.ObnizBleScanStartError) { // If not started yet. this error may called. just ignore it. } else { throw e; } } } async stopExtendedScanningWait() { try { if (this._scanState === 'starting' || this._scanState === 'started') { await this.setExtendedScanEnabledWait(false, true); } } catch (e) { if (e instanceof ObnizError_1.ObnizBleScanStartError) { // If not started yet. this error may called. just ignore it. } else { throw e; } } } async startExtendedScanningWait(allowDuplicates, activeScan, usePhy1m, usePhyCoded) { this._scanFilterDuplicates = !allowDuplicates; this._discoveries = {}; // Always set scan parameters before scanning // https://www.bluetooth.org/docman/handlers/DownloadDoc.ashx?doc_id=421043 // p2729 try { if (this._scanState === 'starting' || this._scanState === 'started') { await this.setExtendedScanEnabledWait(false, true); } } catch (e) { if (e instanceof ObnizError_1.ObnizBleScanStartError) { // If not started yet. this error may called. just ignore it. } else { throw e; } } this._scanState = 'starting'; const status = await this._hci.setExtendedScanParametersWait(activeScan, usePhy1m, usePhyCoded); if (status !== 0) { throw new ObnizError_1.ObnizBleScanStartError(status, `startExtendedScanning Error setting active scan=${activeScan} was failed`); } await new Promise((resolve) => setTimeout(resolve, 1000)); await this.setExtendedScanEnabledWait(true, this._scanFilterDuplicates); } onHciLeExtendedAdvertisingReport(status, type, address, addressType, eir, rssi, primaryPhy, secondaryPhy, sid, txPower, periodicAdvertisingInterval, directAddressType, directAddress) { debug('onHciLeExtendedAdvertisingReport', type, address, addressType, eir, rssi, primaryPhy, secondaryPhy, sid, txPower, periodicAdvertisingInterval, directAddressType, directAddress); this.onHciLeAdvertisingReport(status, type, address, addressType, eir, rssi, true, primaryPhy, secondaryPhy); } isAdvOrScanResp(type, extended) { if (extended) { if ((type & LegacyAdvertisingPduMask) !== 0) { // legacy advertising PDU if (type === LegacyAdvertising_ADV_SCAN_RESP_TO_ADV_IND || type === LegacyAdvertising_ADV_SCAN_RESP_TO_SCAN_IND) { return 'scanResponse'; } else { return 'advertisement'; } } else { if ((type & 0b00010000) === 0) { return 'advertisement'; } else { return 'scanResponse'; } } } else { if (type === 0x04) { return 'scanResponse'; } else if (type === 0x00 || type === 0x01 || type === 0x02 || type === 0x03) { return 'advertisement'; } } return null; } onHciLeAdvertisingReport(status, type, address, addressType, eir, rssi, extended, primaryPhy, secondaryPhy) { const previouslyDiscovered = !!this._discoveries[address]; const advertisement = previouslyDiscovered ? this._discoveries[address].advertisement : { localName: undefined, txPowerLevel: undefined, manufacturerData: undefined, serviceData: [], serviceUuids: [], solicitationServiceUuids: [], advertisementRaw: [], scanResponseRaw: [], raw: [], }; let discoveryCount = previouslyDiscovered ? this._discoveries[address].count : 0; let hasScanResponse = previouslyDiscovered ? this._discoveries[address].hasScanResponse : false; const advType = this.isAdvOrScanResp(type, extended); if (advType === 'scanResponse') { hasScanResponse = true; if (eir.length > 0) { advertisement.scanResponseRaw = Array.from(eir); } } else { // reset service data every non-scan response event advertisement.serviceData = []; advertisement.serviceUuids = []; advertisement.serviceSolicitationUuids = []; if (eir.length > 0) { advertisement.advertisementRaw = Array.from(eir); } } discoveryCount++; let i = 0; let j = 0; let serviceUuid = null; let serviceSolicitationUuid = null; while (i + 1 < eir.length) { const length = eir.readUInt8(i); if (length < 1) { debug('invalid EIR data, length = ' + length); break; } const eirType = eir.readUInt8(i + 1); // https://www.bluetooth.org/en-us/specification/assigned-numbers/generic-access-profile if (i + length + 1 > eir.length) { debug('invalid EIR data, out of range of buffer length'); break; } const bytes = eir.slice(i + 2).slice(0, length - 1); switch (eirType) { case 0x02: // Incomplete List of 16-bit Service Class UUID case 0x03: // Complete List of 16-bit Service Class UUIDs for (j = 0; j < bytes.length; j += 2) { serviceUuid = bytes.readUInt16LE(j).toString(16); if (advertisement.serviceUuids.indexOf(serviceUuid) === -1) { advertisement.serviceUuids.push(serviceUuid); } } break; case 0x06: // Incomplete List of 128-bit Service Class UUIDs case 0x07: // Complete List of 128-bit Service Class UUIDs for (j = 0; j < bytes.length; j += 16) { serviceUuid = bleHelper_1.default.buffer2reversedHex(bytes.slice(j, j + 16)); if (advertisement.serviceUuids.indexOf(serviceUuid) === -1) { advertisement.serviceUuids.push(serviceUuid); } } break; case 0x08: // Shortened Local Name case 0x09: // Complete Local Name advertisement.localName = bytes.toString('utf8'); break; case 0x0a: { // Tx Power Level advertisement.txPowerLevel = bytes.readInt8(0); break; } case 0x14: { // List of 16 bit solicitation UUIDs for (j = 0; j < bytes.length; j += 2) { serviceSolicitationUuid = bytes.readUInt16LE(j).toString(16); if (advertisement.serviceSolicitationUuids.indexOf(serviceSolicitationUuid) === -1) { advertisement.serviceSolicitationUuids.push(serviceSolicitationUuid); } } break; } case 0x15: { // List of 128 bit solicitation UUIDs for (j = 0; j < bytes.length; j += 16) { serviceSolicitationUuid = bleHelper_1.default.buffer2reversedHex(bytes.slice(j, j + 16)); if (advertisement.serviceSolicitationUuids.indexOf(serviceSolicitationUuid) === -1) { advertisement.serviceSolicitationUuids.push(serviceSolicitationUuid); } } break; } case 0x16: { // 16-bit Service Data, there can be multiple occurences const serviceDataUuid = bleHelper_1.default.buffer2reversedHex(bytes.slice(0, 2)); const serviceData = bytes.slice(2, bytes.length); advertisement.serviceData.push({ uuid: serviceDataUuid, data: serviceData, }); break; } case 0x20: { // 32-bit Service Data, there can be multiple occurences const serviceData32Uuid = bleHelper_1.default.buffer2reversedHex(bytes.slice(0, 4)); const serviceData32 = bytes.slice(4, bytes.length); advertisement.serviceData.push({ uuid: serviceData32Uuid, data: serviceData32, }); break; } case 0x21: { // 128-bit Service Data, there can be multiple occurences const serviceData128Uuid = bleHelper_1.default.buffer2reversedHex(bytes.slice(0, 16)); const serviceData128 = bytes.slice(16, bytes.length); advertisement.serviceData.push({ uuid: serviceData128Uuid, data: serviceData128, }); break; } case 0x1f: // List of 32 bit solicitation UUIDs for (j = 0; j < bytes.length; j += 4) { serviceSolicitationUuid = bytes.readUInt32LE(j).toString(16); if (advertisement.serviceSolicitationUuids.indexOf(serviceSolicitationUuid) === -1) { advertisement.serviceSolicitationUuids.push(serviceSolicitationUuid); } } break; case 0xff: // Manufacturer Specific Data advertisement.manufacturerData = bytes; break; } i += length + 1; } debug('advertisement = ' + JSON.stringify(advertisement, null, 0)); let connectable; if (extended) { if ((type & LegacyAdvertisingPduMask) !== 0) { // legacy advertising PDU if (type === LegacyAdvertising_ADV_SCAN_RESP_TO_ADV_IND || type === LegacyAdvertising_ADV_SCAN_RESP_TO_SCAN_IND) { // legacy scan response if (previouslyDiscovered) { connectable = this._discoveries[address].connectable; } else { connectable = true; // not specified. fallback to true } } else { // legacy advertising connectable = type !== LegacyAdvertising_ADV_NONCONN_IND; } } else { connectable = (type & 0b00000001) !== 0; } } else { if (type === 0x04) { // SCAN_RSP if (previouslyDiscovered) { connectable = this._discoveries[address].connectable; } else { connectable = true; // not specified. fallback to true } } else if (type === 0x03) { // ADV_NONCONN_IND connectable = false; } else { // ADV_IND, ADV_DIRECT_IND, ADV_SCAN_IND connectable = true; } } this._discoveries[address] = { address, addressType, connectable, advertisement, rssi, count: discoveryCount, hasScanResponse, }; this.emit('discover', status, address, addressType, connectable, advertisement, rssi, primaryPhy, secondaryPhy); } async setExtendedScanEnabledWait(enabled, filterDuplicates) { const status = await this._hci.setExtendedScanEnabledWait(enabled, filterDuplicates); // Check the status we got from the command complete function. if (status !== 0) { // If it is non-zero there was an error, and we should not change // our status as a result. throw new ObnizError_1.ObnizBleScanStartError(status, `startExtendedScanning enable=${enabled} was failed. Maybe Connection to a device is under going.`); } else { if (this._scanState === 'starting') { this._scanState = 'started'; } else if (this._scanState === 'stopping') { this._scanState = 'stopped'; } } } async setScanEnabledWait(enabled, filterDuplicates) { const status = await this._hci.setScanEnabledWait(enabled, filterDuplicates); // Check the status we got from the command complete function. if (status !== 0) { // If it is non-zero there was an error, and we should not change // our status as a result. throw new ObnizError_1.ObnizBleScanStartError(status, `startScanning enable=${enabled} was failed. Maybe Connection to a device is under going.`); } else { if (this._scanState === 'starting') { this._scanState = 'started'; } else if (this._scanState === 'stopping') { this._scanState = 'stopped'; } } } } exports.Gap = Gap;