UNPKG

homebridge-virtual-accessories

Version:
441 lines 22.2 kB
/* eslint-disable brace-style */ /* eslint-disable max-len */ import { Accessory } from './accessory.js'; import { InvalidSensorValueType, SensorValueUpdateNotAllowed } from '../errors.js'; /** * HeaterCooler - Accessory implementation */ export class HeaterCooler extends Accessory { static ACCESSORY_TYPE_NAME = 'HeaterCooler'; static CURRENTLY_INACTIVE = 0; // Characteristic.CurrentHeaterCoolerState.INACTIVE static CURRENTLY_IDLE = 1; // Characteristic.CurrentHeaterCoolerState.IDLE static CURRENTLY_HEATING = 2; // Characteristic.CurrentHeaterCoolerState.HEATING static CURRENTLY_COOLING = 3; // Characteristic.CurrentHeaterCoolerState.COOLING static AUTO = 0; // Characteristic.TargetHeaterCoolerState.AUTO static HEAT = 1; // Characteristic.TargetHeaterCoolerState.HEAT static COOL = 2; // Characteristic.TargetHeaterCoolerState.COOL static INACTIVE = 0; // Characteristic.Active.INACTIVE static ACTIVE = 1; // Characteristic.Active.ACTIVE static CELSIUS = 0; // Characteristic.TemperatureDisplayUnits.CELSIUS static FAHRENHEIT = 1; // Characteristic.TemperatureDisplayUnits.FAHRENHEIT stateStorageKey = 'HeaterCoolerActive'; targetStateStorageKey = 'HeaterCoolerTargetState'; heatingThresholdStorageKey = 'HeatingThreshold'; coolingThresholdStorageKey = 'CoolingThreshold'; temperatureDisplayUnitsStorageKey = 'TemperatureDisplayUnits'; deviceType; states = { HeaterCoolerActive: HeaterCooler.INACTIVE, HeaterCoolerCurrentState: HeaterCooler.CURRENTLY_INACTIVE, HeaterCoolerTargetState: HeaterCooler.AUTO, // HomeKit units are in celsius HeatingThreshold: 18, // 18ºC considered a minimum for health and safety CoolingThreshold: 27, // 27ºC CurrentTemperature: 22, // This value comes from sensor, set to 22ºC for now - room temperature TemperatureDisplayUnits: HeaterCooler.CELSIUS, }; constructor(platform, accessory, accessoryConfiguration) { super(platform, accessory, accessoryConfiguration); // First configure the device based on the accessory details this.states.HeaterCoolerActive = HeaterCooler.INACTIVE; this.states.HeaterCoolerCurrentState = HeaterCooler.CURRENTLY_INACTIVE; this.states.TemperatureDisplayUnits = this.accessoryConfiguration.heaterCooler.temperatureDisplayUnits === 'celsius' ? HeaterCooler.CELSIUS : HeaterCooler.FAHRENHEIT; this.states.HeatingThreshold = this.toCelsius(this.accessoryConfiguration.heaterCooler.getHeatingThreshold()); this.states.CoolingThreshold = this.toCelsius(this.accessoryConfiguration.heaterCooler.getCoolingThreshold()); // set to 22ºC or 71ºF this.states.CurrentTemperature = (this.states.TemperatureDisplayUnits === HeaterCooler.CELSIUS) ? 22 : this.toCelsius(71); this.deviceType = this.accessoryConfiguration.heaterCooler.type; this.states.HeaterCoolerTargetState = HeaterCooler.AUTO; // If the accessory is stateful retrieve stored state if (this.accessoryConfiguration.accessoryIsStateful) { const accessoryState = this.loadAccessoryState(this.storagePath); const cachedState = accessoryState[this.stateStorageKey]; const cachedTargetState = accessoryState[this.targetStateStorageKey]; const cachedTemperatureDisplayUnits = accessoryState[this.temperatureDisplayUnitsStorageKey]; if (cachedState !== undefined) { this.states.HeaterCoolerActive = cachedState; } if (cachedTargetState !== undefined) { this.states.HeaterCoolerTargetState = cachedTargetState; } if (cachedTemperatureDisplayUnits !== undefined) { this.states.TemperatureDisplayUnits = cachedTemperatureDisplayUnits; } if (this.cools()) { const cachedCoolingThreshold = accessoryState[this.coolingThresholdStorageKey]; if (cachedCoolingThreshold !== undefined) { this.states.CoolingThreshold = cachedCoolingThreshold; } } if (this.heats()) { const cachedHeatingThreshold = accessoryState[this.heatingThresholdStorageKey]; if (cachedHeatingThreshold !== undefined) { this.states.HeatingThreshold = cachedHeatingThreshold; } } } this.setDeviceOperationalCondition(); // get the HeaterCooler service if it exists, otherwise create a new LightBulb service this.service = this.accessory.getService(this.platform.Service.HeaterCooler) || this.accessory.addService(this.platform.Service.HeaterCooler); // These characteristics will be added back as needed this.service.removeCharacteristic(this.service.getCharacteristic(this.platform.Characteristic.CoolingThresholdTemperature)); this.service.removeCharacteristic(this.service.getCharacteristic(this.platform.Characteristic.HeatingThresholdTemperature)); this.setHeaterCoolerServiceProperties(this.service); // set the service name, this is what is displayed as the default name on the Home app this.service.setCharacteristic(this.platform.Characteristic.Name, this.accessoryConfiguration.accessoryName); // Update the initial state of the accessory this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Setting Heater/Cooler Current State: ${HeaterCooler.getCurrentStateName(this.states.HeaterCoolerCurrentState)}`); this.service.updateCharacteristic(this.platform.Characteristic.CurrentHeaterCoolerState, (this.states.HeaterCoolerCurrentState)); this.service.updateCharacteristic(this.platform.Characteristic.TargetHeaterCoolerState, (this.states.HeaterCoolerTargetState)); // register handlers this.service.getCharacteristic(this.platform.Characteristic.Active) .onSet(this.setActive.bind(this)) .onGet(this.getActive.bind(this)); this.service.getCharacteristic(this.platform.Characteristic.CurrentHeaterCoolerState) .onGet(this.getCurrentHeaterCoolerState.bind(this)); this.service.getCharacteristic(this.platform.Characteristic.TargetHeaterCoolerState) .onSet(this.setTargetHeaterCoolerState.bind(this)) .onGet(this.getTargetHeaterCoolerState.bind(this)); this.service.getCharacteristic(this.platform.Characteristic.CurrentTemperature) .onGet(this.getCurrentTemperature.bind(this)); if (this.cools()) { // Characteristic was removed when adding the Service this.service.addCharacteristic(this.platform.Characteristic.CoolingThresholdTemperature) .onSet(this.setCoolingThresholdTemperature.bind(this)) .onGet(this.getCoolingThresholdTemperature.bind(this)); } if (this.heats()) { // Characteristic was removed when adding the Service this.service.addCharacteristic(this.platform.Characteristic.HeatingThresholdTemperature) .onSet(this.setHeatingThresholdTemperature.bind(this)) .onGet(this.getHeatingThresholdTemperature.bind(this)); } this.service.getCharacteristic(this.platform.Characteristic.TemperatureDisplayUnits) .onSet(this.setTemperatureDisplayUnits.bind(this)) .onGet(this.getTemperatureDisplayUnits.bind(this)); const characteristics = this.service.characteristics.map(characteristic => characteristic.displayName); this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Characteristics: ${characteristics.join(', ')}`); } // Handlers async setActive(value) { this.states.HeaterCoolerActive = value; this.setDeviceOperationalCondition(); this.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Active: ${HeaterCooler.getActiveName(this.states.HeaterCoolerActive)}`); } async getActive() { const heaterCoolerActive = this.states.HeaterCoolerActive; this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Active: ${HeaterCooler.getActiveName(heaterCoolerActive)}`); return heaterCoolerActive; } async getCurrentHeaterCoolerState() { const heaterCoolerCurrentState = this.states.HeaterCoolerCurrentState; this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Current Heater Cooler State: ${HeaterCooler.getCurrentStateName(heaterCoolerCurrentState)}`); return heaterCoolerCurrentState; } async setTargetHeaterCoolerState(value) { this.states.HeaterCoolerTargetState = value; this.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Target Heater Cooler State: ${HeaterCooler.getTargetStateName(this.states.HeaterCoolerTargetState)}`); this.setDeviceOperationalCondition(); this.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Current Heater Cooler State: ${HeaterCooler.getCurrentStateName(this.states.HeaterCoolerCurrentState)}`); } async getTargetHeaterCoolerState() { const heaterCoolerTargetState = this.states.HeaterCoolerTargetState; this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Target Heater Cooler State: ${HeaterCooler.getTargetStateName(heaterCoolerTargetState)}`); return heaterCoolerTargetState; } async getCurrentTemperature() { const currentTemperature = this.states.CurrentTemperature; this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Current Temperature: ${this.displayTemperature(currentTemperature)}${this.getDegreeUnits()}`); return currentTemperature; } async setCoolingThresholdTemperature(value) { this.states.CoolingThreshold = value; this.setDeviceOperationalCondition(); this.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Cooling Threshold Temperature: ${this.displayTemperature(this.states.CoolingThreshold)}${this.getDegreeUnits()}`); } async getCoolingThresholdTemperature() { const coolingThreshold = this.states.CoolingThreshold; this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Cooling Threshold Temperature: ${this.displayTemperature(coolingThreshold)}${this.getDegreeUnits()}`); return coolingThreshold; } async setHeatingThresholdTemperature(value) { this.states.HeatingThreshold = value; this.setDeviceOperationalCondition(); this.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Heating Threshold Temperature: ${this.displayTemperature(this.states.HeatingThreshold)}${this.getDegreeUnits()}`); } async getHeatingThresholdTemperature() { const heatingThreshold = this.states.HeatingThreshold; this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Heating Threshold Temperature: ${this.displayTemperature(heatingThreshold)}${this.getDegreeUnits()}`); return heatingThreshold; } async setTemperatureDisplayUnits(value) { this.states.TemperatureDisplayUnits = value; this.storeState(); this.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Temperature Display Units: ${HeaterCooler.getTemperatureDisplayUnitsName(this.states.TemperatureDisplayUnits)}`); } async getTemperatureDisplayUnits() { const temperatureDisplayUnits = this.states.TemperatureDisplayUnits; this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Temperature Display Units: ${HeaterCooler.getTemperatureDisplayUnitsName(temperatureDisplayUnits)}`); return temperatureDisplayUnits; } getJsonState() { const json = JSON.stringify({ [this.stateStorageKey]: this.states.HeaterCoolerActive, [this.targetStateStorageKey]: this.states.HeaterCoolerTargetState, [this.coolingThresholdStorageKey]: this.states.CoolingThreshold, [this.heatingThresholdStorageKey]: this.states.HeatingThreshold, [this.temperatureDisplayUnitsStorageKey]: this.states.TemperatureDisplayUnits, }); return json; } getAccessoryTypeName() { return HeaterCooler.ACCESSORY_TYPE_NAME; } isHeater() { return ['heater'].includes(this.deviceType); } isCooler() { return ['cooler'].includes(this.deviceType); } heats() { return ['auto', 'heater'].includes(this.deviceType); } cools() { return ['auto', 'cooler'].includes(this.deviceType); } setDeviceOperationalCondition() { if (this.states.HeaterCoolerActive === HeaterCooler.INACTIVE) { this.states.HeaterCoolerCurrentState = HeaterCooler.CURRENTLY_INACTIVE; } else { // (this.states.HeaterCoolerActive === HeaterCooler.ACTIVE) if (this.states.HeaterCoolerTargetState === HeaterCooler.HEAT) { this.states.HeaterCoolerCurrentState = HeaterCooler.CURRENTLY_HEATING; } else if (this.states.HeaterCoolerTargetState === HeaterCooler.COOL) { this.states.HeaterCoolerCurrentState = HeaterCooler.CURRENTLY_COOLING; } else { // (this.states.HeaterCoolerTargetState === HeaterCooler.AUTO) if (this.states.CurrentTemperature < this.states.HeatingThreshold) { if (this.heats()) { this.states.HeaterCoolerCurrentState = HeaterCooler.CURRENTLY_HEATING; } } else if (this.states.CurrentTemperature > this.states.CoolingThreshold) { if (this.cools()) { this.states.HeaterCoolerCurrentState = HeaterCooler.CURRENTLY_COOLING; } } else { this.states.HeaterCoolerCurrentState = HeaterCooler.CURRENTLY_IDLE; } } } this.service?.setCharacteristic(this.platform.Characteristic.CurrentHeaterCoolerState, (this.states.HeaterCoolerCurrentState)); this.storeState(); this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Heater/Cooler current state: ${HeaterCooler.getCurrentStateName(this.states.HeaterCoolerCurrentState)}`); } static getActiveName(status) { let activeName; switch (status) { case undefined: { activeName = 'undefined'; break; } case HeaterCooler.INACTIVE: { activeName = 'INACTIVE'; break; } case HeaterCooler.ACTIVE: { activeName = 'ACTIVE'; break; } default: { activeName = status.toString(); } } return activeName; } static getCurrentStateName(state) { let stateName; switch (state) { case undefined: { stateName = 'undefined'; break; } case HeaterCooler.CURRENTLY_INACTIVE: { stateName = 'INACTIVE'; break; } case HeaterCooler.CURRENTLY_IDLE: { stateName = 'IDLE'; break; } case HeaterCooler.CURRENTLY_HEATING: { stateName = 'HEATING'; break; } case HeaterCooler.CURRENTLY_COOLING: { stateName = 'COOLING'; break; } default: { stateName = state.toString(); } } return stateName; } static getTargetStateName(state) { let stateName; switch (state) { case undefined: { stateName = 'undefined'; break; } case HeaterCooler.AUTO: { stateName = 'AUTO'; break; } case HeaterCooler.HEAT: { stateName = 'HEAT'; break; } case HeaterCooler.COOL: { stateName = 'COOL'; break; } default: { stateName = state.toString(); } } return stateName; } static getTemperatureDisplayUnitsName(state) { let unitsName; switch (state) { case undefined: { unitsName = 'undefined'; break; } case HeaterCooler.CELSIUS: { unitsName = 'CELSIUS'; break; } case HeaterCooler.FAHRENHEIT: { unitsName = 'FAHRENHEIT'; break; } default: { unitsName = state.toString(); } } return unitsName; } /** * Ensure all the property values are set, then remove as required */ setHeaterCoolerServiceProperties(service) { const CurrentHeaterCoolerState = this.platform.Characteristic.CurrentHeaterCoolerState; const TargetHeaterCoolerState = this.platform.Characteristic.TargetHeaterCoolerState; const currentStateValues = new Set([ CurrentHeaterCoolerState.INACTIVE, CurrentHeaterCoolerState.IDLE, CurrentHeaterCoolerState.CurrentHeaterCoolerState.HEATING, CurrentHeaterCoolerState.CurrentHeaterCoolerState.COOLING, ]); const targetStateValues = new Set([ TargetHeaterCoolerState.AUTO, TargetHeaterCoolerState.HEAT, TargetHeaterCoolerState.COOL, ]); if (this.isHeater()) { currentStateValues.delete(CurrentHeaterCoolerState.COOLING); targetStateValues.delete(TargetHeaterCoolerState.COOL); this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Is a Heater`); } else if (this.isCooler()) { currentStateValues.delete(CurrentHeaterCoolerState.HEATING); targetStateValues.delete(TargetHeaterCoolerState.HEAT); this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Is a Cooler`); } else { this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Is a Heater/Cooler`); } if (currentStateValues.size > 0) { this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Setting Current State values: ${this.getCurrentStateLabels(currentStateValues)}`); service.getCharacteristic(CurrentHeaterCoolerState) .setProps({ validValues: Array.from(currentStateValues), }); this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Current State Props: ${JSON.stringify(service.getCharacteristic(CurrentHeaterCoolerState).props)}`); } if (targetStateValues.size > 0) { this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Setting Target State values: ${this.getTargetStateLabels(targetStateValues)}`); service.getCharacteristic(TargetHeaterCoolerState) .setProps({ validValues: Array.from(targetStateValues), }); this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Target State Props: ${JSON.stringify(service.getCharacteristic(TargetHeaterCoolerState).props)}`); } } getCurrentStateLabels(values) { const labels = []; values.forEach(value => { labels.push(HeaterCooler.getCurrentStateName(value)); }); return labels; } getTargetStateLabels(values) { const labels = []; values.forEach(value => { labels.push(HeaterCooler.getTargetStateName(value)); }); return labels; } toCelsius(temperature) { const temperatureCelsius = (this.states.TemperatureDisplayUnits === HeaterCooler.CELSIUS) ? temperature : (temperature - 32) * 5 / 9; return Math.round(temperatureCelsius * 10) / 10; } displayTemperature(temperature) { const displayTemperature = (this.states.TemperatureDisplayUnits === HeaterCooler.CELSIUS) ? temperature : (temperature * 9 / 5) + 32; return Math.round(displayTemperature * 10) / 10; } getDegreeUnits() { let units; switch (this.states.TemperatureDisplayUnits) { case undefined: { units = 'º'; break; } case HeaterCooler.CELSIUS: { units = 'ºC'; break; } case HeaterCooler.FAHRENHEIT: { units = 'ºF'; break; } default: { units = 'º'; } } return units; } // Updatable Sensor interface updateSensor(value, accessoryId) { this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Request update temperature sensor to ${value}${this.getDegreeUnits()}`); if (accessoryId !== this.accessoryConfiguration.accessoryID) { this.log.error(`[${this.accessoryConfiguration.accessoryName}] Accessory Id ${accessoryId} is not valid for this accessory`); throw new SensorValueUpdateNotAllowed(`Invalid accessory id: ${accessoryId}`); } else if (typeof value !== 'number') { this.log.error(`[${this.accessoryConfiguration.accessoryName}] Value ${value} is not valid for Heater/Cooler sensor`); throw new InvalidSensorValueType(`Invalid sensor value: ${value}`); } else { this.log.debug(`[${this.accessoryConfiguration.accessoryName}] Updating temperature sensor to ${value}${this.getDegreeUnits()}`); this.states.CurrentTemperature = this.toCelsius(value); this.setDeviceOperationalCondition(); } } } //# sourceMappingURL=virtualAccessoryHeaterCooler.js.map