UNPKG

homebridge-touchwand-google

Version:
441 lines 22.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; 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_node_client_1 = require("hap-node-client"); const hap_types_1 = require("./hap-types"); const crypto = __importStar(require("crypto")); const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); const uuid_1 = require("./uuid"); const door_1 = require("./types/door"); 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.evInstances = []; this.evServices = []; 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.deviceNameMap = []; 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.instanceBlacklist || []; this.log.debug('Waiting 15 seconds before starting instance discovery...'); 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.homebridge = new hap_node_client_1.HAPNodeJSClient({ debug: this.config.debug, pin: this.pin, timeout: 10, }); this.homebridge.once('Ready', () => { this.ready = true; this.log.info('Finished instance discovery'); setTimeout(() => { this.requestSync(); }, 15000); }); this.homebridge.on('Ready', () => { this.start(); }); this.homebridge.on('hapEvent', ((event) => { this.handleHapEvent(event); })); }); } start() { return __awaiter(this, void 0, void 0, function* () { yield this.getAccessories(); yield this.buildSyncResponse(); yield this.registerCharacteristicEventHandlers(); }); } buildSyncResponse() { return __awaiter(this, void 0, void 0, function* () { const devices = this.services.map((service) => { return this.types[service.serviceType].sync(service); }); return devices; }); } requestSync() { return __awaiter(this, void 0, void 0, function* () { this.log.info('Sending Sync Request'); this.socket.sendJson({ type: 'request-sync', }); }); } 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.serviceType].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); if (service) { if (this.config.twoFactorAuthPin && this.types[service.serviceType].twoFactorRequired && this.types[service.serviceType].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 { const { payload, states } = this.types[service.serviceType].execute(service, command); yield new Promise((resolve, reject) => { this.homebridge.HAPcontrol(service.instance.ipAddress, service.instance.port, JSON.stringify(payload), (err) => { if (!err) { response.push({ ids: [device.id], status: 'SUCCESS', states, }); } else { this.log.error('Failed to control an accessory. Make sure all your Homebridge instances are using the same PIN.'); this.log.error(err.message); response.push({ ids: [device.id], status: 'ERROR', }); } return resolve(undefined); }); }); } } } } return response; }); } getStatus(service) { return __awaiter(this, void 0, void 0, function* () { const iids = service.characteristics.map(c => c.iid); const body = '?id=' + iids.map(iid => `${service.aid}.${iid}`).join(','); const characteristics = yield new Promise((resolve, reject) => { this.homebridge.HAPstatus(service.instance.ipAddress, service.instance.port, body, (err, status) => { if (err) { return reject(err); } return resolve(status.characteristics); }); }); for (const c of characteristics) { const characteristic = service.characteristics.find(x => x.iid === c.iid); characteristic.value = c.value; } }); } checkInstanceConnection(instance) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve) => { this.homebridge.HAPcontrol(instance.ipAddress, instance.instance.port, JSON.stringify({ characteristics: [{ aid: -1, iid: -1 }] }), (err) => { if (err) { return resolve(false); } return resolve(true); }); }); }); } getAccessories() { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { this.homebridge.HAPaccessories((instances) => __awaiter(this, void 0, void 0, function* () { this.services = []; for (const instance of instances) { if (!(yield this.checkInstanceConnection(instance))) { this.instanceBlacklist.push(instance.instance.txt.id); } if (!this.instanceBlacklist.find(x => x.toLowerCase() === instance.instance.txt.id.toLowerCase())) { yield this.parseAccessories(instance); } else { this.log.debug(`Instance [${instance.instance.txt.id}] on instance blacklist, ignoring.`); } } return resolve(true); })); }); }); } parseAccessories(instance) { return __awaiter(this, void 0, void 0, function* () { instance.accessories.accessories.forEach((accessory) => { for (const service of accessory.services) { service.type = (0, uuid_1.toLongFormUUID)(service.type); for (const characteristic of service.characteristics) { characteristic.type = (0, uuid_1.toLongFormUUID)(characteristic.type); } } const accessoryInformationService = accessory.services.find(x => x.type === hap_types_1.Service.AccessoryInformation); const accessoryInformation = {}; if (accessoryInformationService && accessoryInformationService.characteristics) { accessoryInformationService.characteristics.forEach((c) => { if (c.value) { accessoryInformation[c.description] = c.value; } }); } accessory.services .filter(x => x.type !== hap_types_1.Service.AccessoryInformation) .filter(x => hap_types_1.ServicesTypes[x.type]) .filter(x => Object.prototype.hasOwnProperty.call(this.types, hap_types_1.ServicesTypes[x.type])) .forEach((service) => { service.accessoryInformation = accessoryInformation; service.aid = accessory.aid; service.serviceType = hap_types_1.ServicesTypes[service.type]; service.instance = { ipAddress: instance.ipAddress, port: instance.instance.port, username: instance.instance.txt.id, }; service.uniqueId = crypto.createHash('sha256') .update(`${service.instance.username}${service.aid}${service.iid}${service.type}`) .digest('hex'); const serviceNameCharacteristic = service.characteristics.find(x => [ hap_types_1.Characteristic.Name, hap_types_1.Characteristic.ConfiguredName, ].includes(x.type)); service.serviceName = (serviceNameCharacteristic && serviceNameCharacteristic.value.length) ? serviceNameCharacteristic.value : service.accessoryInformation.Name || service.serviceType; const nameMap = this.deviceNameMap.find(x => x.replace === service.serviceName); if (nameMap) { service.serviceName = nameMap.with; } if ((this.accessoryFilter.includes(service.serviceName) && !this.accessoryFilterInverse) || (!this.accessoryFilter.includes(service.serviceName) && this.accessoryFilterInverse)) { this.log.debug(`Skipping ${service.serviceName} ${service.accessoryInformation['Serial Number']} - matches accessoryFilter`); return; } if (this.accessorySerialFilter.includes(service.accessoryInformation['Serial Number'])) { this.log.debug(`Skipping ${service.serviceName} ${service.accessoryInformation['Serial Number']} - matches accessorySerialFilter'`); return; } if (this.types[service.serviceType].twoFactorRequired && !this.config.twoFactorAuthPin && !this.config.disablePinCodeRequirement) { this.log.warn(`Not registering ${service.serviceName} - Pin cide has not been set and is required for secure ` + `${service.serviceType} accessory types. See https://git.io/JUQWX`); return; } this.services.push(service); }); }); }); } registerCharacteristicEventHandlers() { return __awaiter(this, void 0, void 0, function* () { for (const service of this.services) { const evCharacteristics = service.characteristics.filter(x => x.perms.includes('ev') && this.evTypes.includes(x.type)); if (evCharacteristics.length) { if (!this.evInstances.find(x => x.username === service.instance.username)) { const newInstance = Object.assign({}, service.instance); newInstance.evCharacteristics = []; this.evInstances.push(newInstance); } const instance = this.evInstances.find(x => x.username === service.instance.username); for (const evCharacteristic of evCharacteristics) { if (!instance.evCharacteristics.find(x => x.aid === service.aid && x.iid === evCharacteristic.iid)) { instance.evCharacteristics.push({ aid: service.aid, iid: evCharacteristic.iid, ev: true }); } } } } for (const instance of this.evInstances) { const unregistered = instance.evCharacteristics.filter(x => !x.registered); if (unregistered.length) { this.homebridge.HAPevent(instance.ipAddress, instance.port, JSON.stringify({ characteristics: instance.evCharacteristics.filter(x => !x.registered), }), (err, response) => { if (err) { this.log.error(err.message); this.instanceBlacklist.push(instance.username); this.evInstances.splice(this.evInstances.indexOf(instance), 1); } else { instance.evCharacteristics.forEach((c) => { c.registered = true; }); this.log.debug('HAP Event listeners registered succesfully'); } }); } } }); } handleHapEvent(events) { return __awaiter(this, void 0, void 0, function* () { for (const event of events) { const accessories = this.services.filter(s => s.instance.ipAddress === event.host && s.instance.port === event.port && s.aid === event.aid); const service = accessories.find(x => x.characteristics.find(c => c.iid === event.iid)); if (service) { const characteristic = service.characteristics.find(c => c.iid === event.iid); characteristic.value = event.value; this.reportStateSubject.next(service.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.serviceType].query(service); } return yield this.sendStateReport(states); }); } sendFullStateReport() { return __awaiter(this, void 0, void 0, function* () { const states = {}; if (!this.services.length) { return; } for (const service of this.services) { states[service.uniqueId] = this.types[service.serviceType].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); }); } } exports.Hap = Hap; //# sourceMappingURL=hap.js.map