UNPKG

homebridge-gsh

Version:
351 lines 16 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Hap = void 0; const hap_client_1 = require("@homebridge/hap-client"); const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); const hap_types_1 = require("./hap-types"); const door_1 = require("./types/door"); const node_crypto_1 = require("node:crypto"); const fan_1 = require("./types/fan"); const fan_v2_1 = require("./types/fan-v2"); const garage_door_opener_1 = require("./types/garage-door-opener"); const heater_cooler_1 = require("./types/heater-cooler"); const humidity_sensor_1 = require("./types/humidity-sensor"); const lightbulb_1 = require("./types/lightbulb"); const lock_mechanism_1 = require("./types/lock-mechanism"); const security_system_1 = require("./types/security-system"); const switch_1 = require("./types/switch"); const television_1 = require("./types/television"); const temperature_sensor_1 = require("./types/temperature-sensor"); const thermostat_1 = require("./types/thermostat"); const window_1 = require("./types/window"); const window_covering_1 = require("./types/window-covering"); class Hap { constructor(socket, log, pin, config) { this.services = []; this.types = { Door: new door_1.Door(), Fan: new fan_1.Fan(), Fanv2: new fan_v2_1.Fanv2(), GarageDoorOpener: new garage_door_opener_1.GarageDoorOpener(), HeaterCooler: new heater_cooler_1.HeaterCooler(this), HumiditySensor: new humidity_sensor_1.HumiditySensor(), Lightbulb: new lightbulb_1.Lightbulb(), LockMechanism: new lock_mechanism_1.LockMechanism(), Outlet: new switch_1.Switch('action.devices.types.OUTLET'), SecuritySystem: new security_system_1.SecuritySystem(), Switch: new switch_1.Switch('action.devices.types.SWITCH'), Television: new television_1.Television(), TemperatureSensor: new temperature_sensor_1.TemperatureSensor(this), Thermostat: new thermostat_1.Thermostat(this), Window: new window_1.Window(), WindowCovering: new window_covering_1.WindowCovering(), }; this.reportStateSubject = new rxjs_1.Subject(); this.pendingStateReport = []; this.evTypes = [ hap_types_1.Characteristic.Active, hap_types_1.Characteristic.On, hap_types_1.Characteristic.CurrentPosition, hap_types_1.Characteristic.TargetPosition, hap_types_1.Characteristic.CurrentDoorState, hap_types_1.Characteristic.TargetDoorState, hap_types_1.Characteristic.Brightness, hap_types_1.Characteristic.HeatingThresholdTemperature, hap_types_1.Characteristic.Hue, hap_types_1.Characteristic.Saturation, hap_types_1.Characteristic.LockCurrentState, hap_types_1.Characteristic.LockTargetState, hap_types_1.Characteristic.TargetHeatingCoolingState, hap_types_1.Characteristic.TargetTemperature, hap_types_1.Characteristic.CoolingThresholdTemperature, hap_types_1.Characteristic.CurrentTemperature, hap_types_1.Characteristic.CurrentRelativeHumidity, hap_types_1.Characteristic.SecuritySystemTargetState, hap_types_1.Characteristic.SecuritySystemCurrentState, ]; this.instanceBlacklist = []; this.accessoryFilter = []; this.accessorySerialFilter = []; this.waitForNoMoreDiscoveries = () => { if (this.discoveryTimeout) { clearTimeout(this.discoveryTimeout); } this.discoveryTimeout = setTimeout(() => { this.log.debug('No more instances discovered, publishing services'); this.hapClient.removeListener('instance-discovered', this.waitForNoMoreDiscoveries); this.start(); this.requestSync(); this.hapClient.on('instance-discovered', this.requestSync.bind(this)); }, 5000); }; this.config = config; this.socket = socket; this.log = log; this.pin = pin; this.accessoryFilter = config.accessoryFilter || []; this.accessoryFilterInverse = config.accessoryFilterInverse || false; this.accessorySerialFilter = config.accessorySerialFilter || []; this.instanceBlacklist = config.instanceDenylist || []; this.log.debug('Waiting 15 seconds before starting instance discovery...'); this.startTimeout = setTimeout(() => { this.discover(); }, 15000); this.reportStateSubject .pipe((0, operators_1.map)((i) => { if (!this.pendingStateReport.includes(i)) { this.pendingStateReport.push(i); } }), (0, operators_1.debounceTime)(1000)) .subscribe((data) => { const pendingStateReport = this.pendingStateReport; this.pendingStateReport = []; this.processPendingStateReports(pendingStateReport); }); } discover() { return __awaiter(this, void 0, void 0, function* () { this.hapClient = new hap_client_1.HapClient({ config: this.config, pin: this.pin, logger: this.log, }); this.waitForNoMoreDiscoveries(); this.hapClient.on('instance-discovered', this.waitForNoMoreDiscoveries); this.hapClient.on('hapEvent', (event) => { this.handleHapEvent(event); }); }); } start() { return __awaiter(this, void 0, void 0, function* () { this.services = yield this.loadAccessories(); this.log.info(`Discovered ${this.services.length} accessories`); this.ready = true; yield this.buildSyncResponse(); const evServices = this.services.filter(x => this.evTypes.some(uuid => x.serviceCharacteristics.find(c => c.uuid === uuid))); this.log.debug(`Monitoring ${evServices.length} services for changes`); const monitor = yield this.hapClient.monitorCharacteristics(evServices); monitor.on('service-update', (services) => { services.map((service) => { this.reportStateSubject.next(service.uniqueId); }); }); }); } buildSyncResponse() { return __awaiter(this, void 0, void 0, function* () { const devices = this.services.map((service) => { if (!this.types[service.type]) { return; } return this.types[service.type].sync(service); }); return devices; }); } requestSync() { return __awaiter(this, void 0, void 0, function* () { if (this.syncTimeout) { clearTimeout(this.syncTimeout); } this.syncTimeout = setTimeout(() => { this.log.info('Sending Sync Request'); this.socket.sendJson({ type: 'request-sync', }); }, 15000); }); } query(devices) { return __awaiter(this, void 0, void 0, function* () { const response = {}; for (const device of devices) { const service = this.services.find(x => x.uniqueId === device.id); if (service) { yield this.getStatus(service); response[device.id] = this.types[service.type].query(service); } else { response[device.id] = {}; } } return response; }); } execute(commands) { return __awaiter(this, void 0, void 0, function* () { const response = []; for (const command of commands) { for (const device of command.devices) { const service = this.services.find(x => x.uniqueId === device.id); this.log.debug(`Processing command ${command.execution[0].command} for ${device.id} and ${service}`); if (service) { if (this.config.twoFactorAuthPin && this.types[service.type].twoFactorRequired && this.types[service.type].is2faRequired(command) && !(command.execution.length && command.execution[0].challenge && command.execution[0].challenge.pin === this.config.twoFactorAuthPin.toString())) { this.log.info('Requesting Two Factor Authentication Pin'); response.push({ ids: [device.id], status: 'ERROR', errorCode: 'challengeNeeded', challengeNeeded: { type: 'pinNeeded', }, }); } else { try { response.push(yield this.types[service.type].execute(service, command)); } catch (error) { this.log.error(`Error executing command: ${error.message}`); response.push({ ids: [device.id], status: 'ERROR', debugString: error.message, }); } } } else { this.log.error(`Device not found: ${device.id}`); response.push({ ids: [device.id], status: 'OFFLINE', errorCode: 'deviceNotFound', }); } } } return response; }); } getStatus(service) { return __awaiter(this, void 0, void 0, function* () { return yield service.refreshCharacteristics(); }); } loadAccessories() { return __awaiter(this, void 0, void 0, function* () { return this.hapClient.getAllServices().then((services) => { services = services.filter(x => this.types[x.type] !== undefined); this.log.debug(`Loaded ${services.length} accessories from Homebridge - pre filter`); if (this.accessoryFilterInverse) { services = services.filter(x => this.accessoryFilter.includes(x.serviceName)); } else { services = services.filter(x => !this.accessoryFilter.includes(x.serviceName)); } services = services.filter(x => !this.accessorySerialFilter.includes(x.accessoryInformation['Serial Number'])); services = services.filter(x => { if (this.types[x.type].twoFactorRequired && !this.config.twoFactorAuthPin && !this.config.disablePinCodeRequirement) { this.log.warn(`Not registering ${x.serviceName} - Pin code has not been set and is required for secure ` + `${x.type} accessory types. See https://git.io/JUQWX`); return false; } else { return true; } }); services = services.map(service => { return Object.assign(Object.assign({}, service), { uniqueId: (0, node_crypto_1.createHash)('sha256') .update(`${service.instance.username}${service.aid}${service.iid}${service.uuid}`) .digest('hex') }); }); this.log.debug(`Returned ${services.length} accessories from Homebridge - post filter`); return services; }).catch((e) => { var _a; if (((_a = e.response) === null || _a === void 0 ? void 0 : _a.status) === 401) { this.log.warn('Homebridge must be running in insecure mode to view and control accessories from this plugin.'); } else { this.log.error(`Failed load accessories from Homebridge: ${e.message}`); } return []; }); }); } handleHapEvent(events) { return __awaiter(this, void 0, void 0, function* () { for (const event of events) { const index = this.services.findIndex(item => item.uniqueId === event.uniqueId); if (index === -1) { this.log.debug(`[handleHapEvent] Service not found in services list ${event}`); return; } else { this.services[index] = event; this.reportStateSubject.next(event.uniqueId); } } }); } processPendingStateReports(pendingStateReport) { return __awaiter(this, void 0, void 0, function* () { const states = {}; for (const uniqueId of pendingStateReport) { const service = this.services.find(x => x.uniqueId === uniqueId); states[service.uniqueId] = this.types[service.type].query(service); } return yield this.sendStateReport(states); }); } sendFullStateReport() { return __awaiter(this, void 0, void 0, function* () { const states = {}; if (!this.services.length) { return; } this.services.map((service) => { if (!this.types[service.type]) { return; } return states[service.uniqueId] = this.types[service.type].query(service); }); return yield this.sendStateReport(states); }); } sendStateReport(states, requestId) { return __awaiter(this, void 0, void 0, function* () { const payload = { requestId, type: 'report-state', body: states, }; this.log.debug('Sending State Report'); this.log.debug(JSON.stringify(payload, null, 2)); this.socket.sendJson(payload); }); } destroy() { return __awaiter(this, void 0, void 0, function* () { if (this.startTimeout) { clearTimeout(this.startTimeout); } if (this.discoveryTimeout) { clearTimeout(this.discoveryTimeout); } if (this.syncTimeout) { clearTimeout(this.syncTimeout); } if (this.hapClient) { this.hapClient.destroy(); } }); } } exports.Hap = Hap; //# sourceMappingURL=hap.js.map