UNPKG

hap-controller

Version:

Library to implement a HAP (HomeKit) controller

208 lines 7.24 kB
"use strict"; /** * BLE discovery wrappers for finding HAP devices. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DiscoveryPairingStatusFlags = void 0; const events_1 = require("events"); const gatt_client_1 = __importDefault(require("./gatt-client")); const debug_1 = __importDefault(require("debug")); let noble = null; try { noble = require('@stoprocent/noble'); if (typeof (noble === null || noble === void 0 ? void 0 : noble.on) !== 'function') { // The following commit broke the default exported instance of noble: // https://github.com/abandonware/noble/commit/b67eea246f719947fc45b1b52b856e61637a8a8e noble = noble({ extended: false }); } } catch (error) { // Ignore } const debug = (0, debug_1.default)('hap-controller:gatt-client'); /** * See Table 7-43 */ const DiscoveryPairingStatusFlags = { AccessoryNotPaired: 0x01, }; exports.DiscoveryPairingStatusFlags = DiscoveryPairingStatusFlags; /** * Handle discovery of IP devices * * @fires BLEDiscovery#serviceUp * @fires BLEDiscovery#serviceChanged */ class BLEDiscovery extends events_1.EventEmitter { constructor() { super(); this.scanEnabled = false; this.allowDuplicates = false; this.services = new Map(); this.handleStateChange = this._handleStateChange.bind(this); this.handleDiscover = this._handleDiscover.bind(this); this.handleScanStart = this._handleScanStart.bind(this); this.handleScanStop = this._handleScanStop.bind(this); } /** * Start searching for BLE HAP devices. * * @param {boolean} allowDuplicates - Deprecated, use new serviceChanged event instead. * Allow duplicate serviceUp events. This * is needed for disconnected events, where the GSN is * updated in the advertisement. */ start(allowDuplicates = false) { if (!noble) { throw new Error('BLE could not be enabled or no device found'); } this.scanEnabled = true; this.allowDuplicates = allowDuplicates; noble.on('stateChange', this.handleStateChange); noble.on('scanStart', this.handleScanStart); noble.on('scanStop', this.handleScanStop); noble.on('discover', this.handleDiscover); // Only manually start if powered on already. Otherwise, wait for state // change and handle it there. if (noble._state === 'poweredOn') { noble.startScanning([], true); } } /** * Get PairMethod to use for pairing from the data received during discovery * * @param {HapServiceBle} service Discovered service object to check * @returns {Promise<number>} Promise which resolves with the PairMethod to use */ async getPairMethod(service) { const client = new gatt_client_1.default(service.DeviceID, service.peripheral); return client.getPairingMethod(); } /** * List the currently known services. * * @returns {Object[]} Array of services */ list() { return Array.from(this.services.values()); } /** * Stop an ongoing discovery process. */ stop() { if (!noble) { throw new Error('BLE could not be enabled or no device found'); } this.scanEnabled = false; noble.stopScanning(); noble.removeListener('stateChange', this.handleStateChange); noble.removeListener('scanStart', this.handleScanStart); noble.removeListener('scanStop', this.handleScanStop); noble.removeListener('discover', this.handleDiscover); } _handleStateChange(state) { if (!noble) { return; } if (state === 'poweredOn' && this.scanEnabled) { noble.startScanning([], true); } else { noble.stopScanning(); } } _handleScanStart() { if (!noble) { return; } if (!this.scanEnabled) { noble.stopScanning(); } } _handleScanStop() { if (!noble) { return; } if (this.scanEnabled && noble._state === 'poweredOn') { noble.startScanning([], true); } } _handleDiscover(peripheral) { const advertisement = peripheral.advertisement; const manufacturerData = advertisement.manufacturerData; if (!advertisement || !advertisement.localName || !manufacturerData || manufacturerData.length < 17) { return; } // See Chapter 6.4.2.2 const localName = advertisement.localName; const CoID = manufacturerData.readUInt16LE(0); const TY = manufacturerData.readUInt8(2); const AIL = manufacturerData.readUInt8(3); const SF = manufacturerData.readUInt8(4); const deviceID = manufacturerData.slice(5, 11); const ACID = manufacturerData.readUInt16LE(11); const GSN = manufacturerData.readUInt16LE(13); const CN = manufacturerData.readUInt8(15); const CV = manufacturerData.readUInt8(16); // const SH = manufacturerData.length > 17 ? manufacturerData.slice(17, 21) : Buffer.alloc(0); if (TY === 0x11) { debug(`Encrypted Broadcast detected ... ignoring for now: ${manufacturerData}`); } if (CoID !== 0x4c || TY !== 0x06 || CV !== 0x02) { return; } let formattedId = ''; for (const b of deviceID) { formattedId += `${b.toString(16).padStart(2, '0')}:`; } formattedId = formattedId.substr(0, 17); const service = { name: localName, CoID, TY, AIL, SF, DeviceID: formattedId, ACID, GSN, CN, CV, peripheral, // SH, 'c#': CN, id: formattedId, ci: ACID, availableToPair: !!(SF & DiscoveryPairingStatusFlags.AccessoryNotPaired), }; const formerService = this.services.get(service.DeviceID); this.services.set(service.DeviceID, service); if (formerService && !this.allowDuplicates) { for (const el of Object.keys(service)) { if (el !== 'peripheral' && el !== 'name' && formerService[el] !== service[el]) { /** * Device data changed event * * @event BLEDiscovery#serviceChanged * @type HapServiceBle */ this.emit('serviceChanged', service); break; } } } else { /** * New device discovered event * * @event BLEDiscovery#serviceUp * @type HapServiceBle */ this.emit('serviceUp', service); } } } exports.default = BLEDiscovery; //# sourceMappingURL=ble-discovery.js.map