homebridge-touchwand-google
Version:
441 lines • 22.3 kB
JavaScript
;
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