UNPKG

homebridge-homewizard-energy-socket

Version:

This Homebridge plugin exposes your HomeWizard Energy Sockets to Apple HomeKit. So you can use the Home App to switch your Energy Sockets on or off and integrate the Energy Sockets into your Home Automations.

349 lines 18.7 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()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.EnergySocketAccessory = void 0; const undici_1 = __importDefault(require("undici")); const settings_1 = require("./settings"); const types_1 = require("./types"); const utils_1 = require("./utils"); class EnergySocketAccessory { setService(service) { this.service = service; } constructor(platform, accessory, api) { var _a, _b; this.platform = platform; this.accessory = accessory; this.localStatePolling = false; this.pollingTimeout = null; this.longPollErrorCount = 0; this.longPollCrossedThresholdAboveAt = null; this.longPollCrossedThresholdBelowAt = null; this.statePollingErrorCount = 0; const properties = accessory.context.energySocket; this.properties = properties; this.config = properties.config; this.log.debug(`Initializing platform accessory ${JSON.stringify(this.properties)}`); this.energySocketApi = api; const informationService = this.accessory.getService(this.platform.Service.AccessoryInformation); informationService === null || informationService === void 0 ? void 0 : informationService.setCharacteristic(this.platform.Characteristic.Manufacturer, types_1.PLATFORM_MANUFACTURER); informationService === null || informationService === void 0 ? void 0 : informationService.setCharacteristic(this.platform.Characteristic.Model, this.modelName); informationService === null || informationService === void 0 ? void 0 : informationService.setCharacteristic(this.platform.Characteristic.SerialNumber, this.properties.serialNumber); informationService === null || informationService === void 0 ? void 0 : informationService.setCharacteristic(this.platform.Characteristic.FirmwareRevision, this.properties.firmwareVersion); this.informationService = informationService; this.service = this.accessory.getService(this.platform.Service.Outlet) || this.accessory.addService(this.platform.Service.Outlet); this.service.setCharacteristic(this.platform.Characteristic.Name, this.properties.displayName); if (!((_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.outletInUse) === null || _b === void 0 ? void 0 : _b.isActive)) { this.service.setCharacteristic(this.platform.Characteristic.OutletInUse, this.initialIsOutletInUse); } this.service .getCharacteristic(this.platform.Characteristic.On) .onSet(this.handleSetOn.bind(this)) .onGet(this.handleGetOn.bind(this)); this.accessory.on("identify", this.handleIdentify.bind(this)); this.log.info(`OutletInUse initial value is ${this.isOutletInUse ? 'ON' : 'OFF'} (${this.properties.activePower} watt)`); this.longPollData(); this.startStatePolling(); } get log() { const loggerPrefix = `[Energy Socket: ${this.properties.displayName}] -> `; return { info: (...parameters) => { this.platform.log.info(loggerPrefix, ...parameters); }, warn: (...parameters) => { this.platform.log.warn(loggerPrefix, ...parameters); }, error: (...parameters) => { this.platform.log.error(loggerPrefix, ...parameters); }, debug: (...parameters) => { this.platform.log.debug(loggerPrefix, ...parameters); }, }; } get initialIsOutletInUse() { var _a, _b; if (!((_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.outletInUse) === null || _b === void 0 ? void 0 : _b.isActive)) { return true; } return this.getIsActivePowerAboveThreshold(this.properties.activePower); } get firmwareVersion() { var _a; const firmwareVersionString = (_a = this.informationService) === null || _a === void 0 ? void 0 : _a.getCharacteristic(this.platform.Characteristic.FirmwareRevision).value; const firmwareVersion = firmwareVersionString ? Number(firmwareVersionString) : null; return firmwareVersion; } get isOutletInUse() { return this.service.getCharacteristic(this.platform.Characteristic.OutletInUse) .value; } get thresholdDurationInMs() { var _a, _b; return (((_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.outletInUse) === null || _b === void 0 ? void 0 : _b.thresholdDuration) || 0) * 1000; } get isThresholdCrossedAboveAfterDuration() { var _a, _b, _c; if (!((_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.outletInUse) === null || _b === void 0 ? void 0 : _b.isActive)) { return null; } const currentTime = new Date().getTime(); const crossedAboveThresholdTime = (_c = this.longPollCrossedThresholdAboveAt) === null || _c === void 0 ? void 0 : _c.getTime(); if (!crossedAboveThresholdTime) return null; return currentTime - crossedAboveThresholdTime >= this.thresholdDurationInMs; } get isThresholdCrossedBelowAfterDuration() { var _a, _b, _c; if (!((_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.outletInUse) === null || _b === void 0 ? void 0 : _b.isActive)) { return null; } const currentTime = new Date().getTime(); const crossedBelowThresholdTime = (_c = this.longPollCrossedThresholdBelowAt) === null || _c === void 0 ? void 0 : _c.getTime(); if (!crossedBelowThresholdTime) return null; return currentTime - crossedBelowThresholdTime >= this.thresholdDurationInMs; } get isSwitchLockEnabled() { var _a; return ((_a = this.localStateResponse) === null || _a === void 0 ? void 0 : _a.switch_lock) === true; } get modelName() { return `${this.properties.productName} (${this.properties.productType})`; } getCurrentOnState() { return this.service.getCharacteristic(this.platform.Characteristic.On).value; } updateCurrentOnState(value) { this.service.updateCharacteristic(this.platform.Characteristic.On, value); } getIsActivePowerAboveThreshold(activePower) { var _a, _b; return !!(activePower && activePower > (((_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.outletInUse) === null || _b === void 0 ? void 0 : _b.threshold) || 0)); } setOutletInUse(value, activePower) { this.service.setCharacteristic(this.platform.Characteristic.OutletInUse, value); this.longPollCrossedThresholdAboveAt = null; this.longPollCrossedThresholdBelowAt = null; this.log.info(`OutletInUse is changed to ${value ? 'ON' : 'OFF'} (${activePower} watt)`); } setLocalStateResponse(response) { this.localStateResponse = response; } syncOutletInUseStateWithOnState(isOn) { var _a, _b; if (!((_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.outletInUse) === null || _b === void 0 ? void 0 : _b.isActive)) { if (this.isOutletInUse === isOn) { return; } this.log.info(`OutletInUse state is synced to ${isOn ? 'ON' : 'OFF'}`); this.service.setCharacteristic(this.platform.Characteristic.OutletInUse, isOn); } } longPollData() { return __awaiter(this, void 0, void 0, function* () { var _a, _b; if (!((_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.outletInUse) === null || _b === void 0 ? void 0 : _b.isActive)) { this.log.debug('outletInUse.isActive config option is false or not set, not long polling the /data endpoint'); return; } if (this.properties.productType !== 'HWE-SKT') { this.log.debug('Not a Energy Socket, not long polling the /data endpoint'); return; } const polling = this.energySocketApi.polling; polling.getData.start(); polling.getData.on('response', response => { var _a, _b, _c, _d, _e, _f; const threshold = (_a = this.config) === null || _a === void 0 ? void 0 : _a.outletInUse; const thresholdDuration = (_b = this.config) === null || _b === void 0 ? void 0 : _b.outletInUse; const verboseLogging = (_d = (_c = this.config) === null || _c === void 0 ? void 0 : _c.outletInUse) === null || _d === void 0 ? void 0 : _d.verboseLogging; const { active_power_w } = response; if (!(0, utils_1.isNil)(active_power_w)) { this.properties.activePower = active_power_w; } const isActivePowerAboveThreshold = this.getIsActivePowerAboveThreshold(active_power_w); if (isActivePowerAboveThreshold && !this.longPollCrossedThresholdAboveAt) { this.longPollCrossedThresholdAboveAt = new Date(); this.longPollCrossedThresholdBelowAt = null; } if (!isActivePowerAboveThreshold && !this.longPollCrossedThresholdBelowAt) { this.longPollCrossedThresholdBelowAt = new Date(); this.longPollCrossedThresholdAboveAt = null; } if (this.isThresholdCrossedAboveAfterDuration === true && !this.isOutletInUse) { this.log.debug(`OutletInUse threshold crossed above ${threshold} watt for ${thresholdDuration} seconds, set OutletInUse to true`); this.setOutletInUse(true, active_power_w); } if (this.isThresholdCrossedBelowAfterDuration === true && this.isOutletInUse) { this.log.debug(`OutletInUse threshold crossed below ${threshold} watt for ${thresholdDuration} seconds, set OutletInUse to false`); this.setOutletInUse(false, active_power_w); } if (verboseLogging) { this.log.debug(`${active_power_w === null || active_power_w === void 0 ? void 0 : active_power_w.toFixed(3).padStart(8, '0')} watt`, '|', 'Threshold:', `${threshold} watt`, '|', 'OutletInUse:', this.isOutletInUse ? 'Yes' : 'No', '|', 'Above threshold @', ((_e = this.longPollCrossedThresholdAboveAt) === null || _e === void 0 ? void 0 : _e.toLocaleTimeString('nl-NL', { hour: '2-digit', minute: '2-digit', second: '2-digit', })) || 'Never', '|', 'Below threshold @', ((_f = this.longPollCrossedThresholdBelowAt) === null || _f === void 0 ? void 0 : _f.toLocaleTimeString('nl-NL', { hour: '2-digit', minute: '2-digit', second: '2-digit', })) || 'Never', '|', `After duration (${thresholdDuration} sec):`, this.isThresholdCrossedAboveAfterDuration ? 'Yes' : 'No'); } this.longPollErrorCount = 0; }); polling.getData.on('error', error => { let errorMessage = 'A unknown error happened while polling the /data endpoint.'; if (error instanceof Error) { errorMessage = error.message; } const isFirstError = this.longPollErrorCount === 0; const isErrorCountAfterInterval = this.longPollErrorCount > settings_1.SHOW_POLLING_ERRORS_INTERVAL; if (isErrorCountAfterInterval || isFirstError) { this.log.error(`Error during polling the data endpoint: ${errorMessage}.${this.longPollErrorCount ? ` Total errors: ${this.longPollErrorCount}` : ''}`); } if (isErrorCountAfterInterval) { this.longPollErrorCount = 0; } else { this.longPollErrorCount += 1; } }); }); } handleIdentify() { return __awaiter(this, void 0, void 0, function* () { try { const response = yield this.energySocketApi.identify(); return response; } catch (error) { const fallbackErrorMessage = 'A unknown error occurred while identifying the Energy Socket'; throw this.handleAccessoryApiError(error, fallbackErrorMessage); } }); } handleSetOn(value) { return __awaiter(this, void 0, void 0, function* () { try { if (this.isSwitchLockEnabled) { this.log.warn(`This Energy Socket (${this.properties.serialNumber}) is locked. Please enable the "Switch lock" setting in the HomeWizard Energy app for this Energy Socket.`); throw new this.platform.api.hap.HapStatusError(-70412); } this.stopStatePolling(); const response = yield this.energySocketApi.updateState({ power_on: value, }); const isOn = response.power_on; this.log.info(`On state is updated to ${isOn ? 'ON' : 'OFF'}`); this.syncOutletInUseStateWithOnState(isOn); } catch (error) { const fallbackErrorMessage = 'A unknown error occurred while setting the ON state'; throw this.handleAccessoryApiError(error, fallbackErrorMessage); } finally { this.startStatePolling(); } }); } handleGetOn() { return __awaiter(this, void 0, void 0, function* () { var _a, _b; try { const response = yield this.energySocketApi.getState(); this.setLocalStateResponse(response); const isOn = response.power_on; this.log.info(`On state is fetched as ${isOn ? 'ON' : 'OFF'}`); if ((_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.outletInUse) === null || _b === void 0 ? void 0 : _b.isActive) { this.log.info(`Current power consumption is ${this.properties.activePower} watt`); } this.syncOutletInUseStateWithOnState(isOn); return isOn; } catch (error) { const errorMessage = 'A unknown error occurred while getting the ON state'; throw this.handleAccessoryApiError(error, errorMessage); } }); } handleAccessoryApiError(error, fallbackErrorMessage) { let errorMessage = fallbackErrorMessage || 'A unknown error occurred'; if (error instanceof Error) { errorMessage = error.message; } this.log.error(errorMessage); return new this.platform.api.hap.HapStatusError(-70402); } pollState() { return __awaiter(this, void 0, void 0, function* () { if (!this.localStatePolling) { return; } try { const response = yield this.energySocketApi.getState(); this.statePollingErrorCount = 0; this.setLocalStateResponse(response); const currentOnState = this.getCurrentOnState(); if (currentOnState !== response.power_on) { this.log.info(`State polling detected change: ${response.power_on ? 'ON' : 'OFF'}`); this.log.info(`Current On state "${currentOnState}", will be updated to "${response.power_on}"`); this.updateCurrentOnState(response.power_on); this.syncOutletInUseStateWithOnState(response.power_on); } } catch (error) { this.statePollingErrorCount += 1; const isFirstError = this.statePollingErrorCount === 1; const isErrorCountAfterInterval = this.statePollingErrorCount >= settings_1.SHOW_POLLING_ERRORS_INTERVAL; if (isFirstError || isErrorCountAfterInterval) { if (error instanceof undici_1.default.errors.HeadersTimeoutError) { this.log.error(`Error during state polling. Device is probably offline.${this.statePollingErrorCount > 1 ? ` Total errors: ${this.statePollingErrorCount}` : ''}`); } else { this.log.error(`Error polling state:${this.statePollingErrorCount > 1 ? ` Total errors: ${this.statePollingErrorCount}` : ''}`, error); } if (isErrorCountAfterInterval) { this.statePollingErrorCount = 0; } } } this.pollingTimeout = setTimeout(this.pollState.bind(this), settings_1.POLLING_STATE_INTERVAL); }); } startStatePolling() { return __awaiter(this, void 0, void 0, function* () { this.log.debug('Starting state polling'); if (this.localStatePolling) { this.log.debug('State polling already started'); return; } this.localStatePolling = true; yield this.pollState(); }); } stopStatePolling() { this.log.debug('Stopping state polling'); this.localStatePolling = false; if (this.pollingTimeout) { clearTimeout(this.pollingTimeout); this.pollingTimeout = null; } } } exports.EnergySocketAccessory = EnergySocketAccessory; //# sourceMappingURL=energy-socket-accessory.js.map