UNPKG

homebridge-th16ermostat

Version:
275 lines 13.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; const axios_1 = __importDefault(require("axios")); let hap; class TH16ermostatPlugin { // ctor constructor(log, config) { // state this.currTemp = ''; this.targetTemp = 0; this.currRelativeHumidity = ''; this.currentHeatingState = hap.Characteristic.CurrentHeatingCoolingState.OFF; // [0, 1] only this.targetHeatingState = hap.Characteristic.TargetHeatingCoolingState.OFF; // [0, 1, 3] only this.isOffline = false; this.enableHumidity = false; this.minTemp = -25; this.maxTemp = 25; this.deltaTemp = 0.2; this.stepTemp = 0.5; this.tempUnits = 'C'; this.deviceIPAddress = ''; this.deviceStatStatus = '/cm?cmnd=status%208'; this.deviceStatPower = '/cm?cmnd=power'; this.deviceCmndOn = '/cm?cmnd=power%20on'; this.deviceCmndOff = '/cm?cmnd=power%20off'; this.pollingInterval = 60; log.debug('TH16ermostat constructing!'); this.log = log; this.name = config.name; // Config values this.sensorName = config.sensorName; this.deviceIPAddress = config.deviceIPAddress; this.enableHumidity = config.enableHumidity || this.enableHumidity; this.deviceStatStatus = config.deviceStatStatus || this.deviceStatStatus; this.deviceStatPower = config.deviceStatPower || this.deviceStatPower; this.deviceCmndOn = config.deviceCmndOn || this.deviceCmndOn; this.deviceCmndOff = config.deviceCmndOff || this.deviceCmndOff; this.pollingInterval = config.pollingInterval || this.pollingInterval; this.minTemp = config.minTemp || this.minTemp; this.maxTemp = config.maxTemp || this.maxTemp; this.deltaTemp = config.deltaTemp || this.deltaTemp; this.stepTemp = config.StepTemp || this.stepTemp; this.tempUnits = config.tempUnits || this.tempUnits; // Fix config values if necessary // if (this.deltaTemp < 0.1) { // prevent zero or negavite (minimum of 0.1) this.deltaTemp = 0.1; } // init state values this.targetTemp = this.minTemp; // create services this.thermostatService = new hap.Service.Thermostat(this.name); this.informationService = new hap.Service.AccessoryInformation(); this.humidityService = new hap.Service.HumiditySensor(this.name); this.servicesArray = []; } /* * This method is optional to implement. It is called when HomeKit ask to identify the accessory. * Typical this only ever happens at the pairing process. */ identify() { this.log('Sonoff TH16 thermostat'); } /* * This method is called directly after creation of this instance. * It should return all services which should be added to the accessory. */ getServices() { this.log.debug('TH16ermostat initializing!'); // init Humidity Sensor service if (this.enableHumidity) { this.humidityService.getCharacteristic(hap.Characteristic.CurrentRelativeHumidity) .on("get" /* GET */, (callback) => { this.log.info('Get CURRENT relative humidity: ' + this.currRelativeHumidity); callback(undefined, this.currRelativeHumidity); }); } // init Thermostat service this.thermostatService.getCharacteristic(hap.Characteristic.CurrentHeatingCoolingState) .on("get" /* GET */, (callback) => { this.log.info('Get CURRENT heating state: ' + this.heatingStateToStr(this.currentHeatingState)); callback(undefined, this.isOffline ? hap.Characteristic.CurrentHeatingCoolingState.OFF : this.currentHeatingState); }) .on("set" /* SET */, (value, callback) => { this.currentHeatingState = value; this.log.info('Set CURRENT heating state to: ' + this.heatingStateToStr(this.currentHeatingState)); this.pollDeviceStatus(); callback(); }); this.thermostatService.getCharacteristic(hap.Characteristic.TargetHeatingCoolingState) .on("get" /* GET */, (callback) => { this.log.info('Get TARGET heating state: ' + this.heatingStateToStr(this.targetHeatingState)); callback(undefined, this.isOffline ? hap.Characteristic.TargetHeatingCoolingState.OFF : this.targetHeatingState); }) .on("set" /* SET */, (value, callback) => { this.targetHeatingState = value; this.log.info('Set TARGET heating state to: ' + this.heatingStateToStr(this.targetHeatingState)); if (hap.Characteristic.TargetHeatingCoolingState.COOL === value) { this.log.info('COOL state not supported! Setting as OFF'); this.targetHeatingState = hap.Characteristic.TargetHeatingCoolingState.OFF; } this.pollDeviceStatus(); callback(); }) .setProps({ validValues: [0, 1, 3], }); this.thermostatService.getCharacteristic(hap.Characteristic.CurrentTemperature) .on("get" /* GET */, (callback) => { this.log.info('Get CURRENT temperature: ' + this.currTemp); callback(undefined, this.currTemp); }); this.thermostatService.getCharacteristic(hap.Characteristic.TargetTemperature) .on("get" /* GET */, (callback) => { this.log.info('Get TARGET temperature: ' + this.targetTemp); callback(undefined, this.targetTemp); }) .on("set" /* SET */, (value, callback) => { this.targetTemp = value; this.log.info('Set TARGET temperature to: ' + this.targetTemp); this.pollDeviceStatus(); callback(); }) .setProps({ minValue: this.minTemp, maxValue: this.maxTemp, minStep: this.stepTemp, }); this.thermostatService.getCharacteristic(hap.Characteristic.TemperatureDisplayUnits) .on("get" /* GET */, (callback) => { callback(undefined, this.tempUnits); }) .on("set" /* SET */, (value, callback) => { this.log.info('[Ignoring] Set Temperature Units to: ' + this.tempUnits); callback(); }); // init Information service this.informationService .setCharacteristic(hap.Characteristic.Manufacturer, 'Sonoff') .setCharacteristic(hap.Characteristic.Model, 'TH16'); this.log.debug('TH16ermostat finished initializing!'); // Polling service this.log.debug('Polling each ' + this.pollingInterval + ' seconds.'); this.pollingTimer = setInterval(this.pollDeviceStatus.bind(this), this.pollingInterval * 1000); // Get initial state this.pollDeviceStatus(); this.servicesArray = [ this.informationService, this.thermostatService, ]; if (this.enableHumidity) { this.servicesArray.push(this.humidityService); } return this.servicesArray; } setDevicePower(value) { this.log.info('TH16ermostat: Power ' + this.heatingStateToStr(value) + '(Temp: ' + this.currTemp + ' -> ' + this.targetTemp + ')'); const url = 'http://' + this.deviceIPAddress; axios_1.default.get(url + (value === hap.Characteristic.CurrentHeatingCoolingState.HEAT ? this.deviceCmndOn : this.deviceCmndOff)) .then((response) => { this.currentHeatingState = (response.data.POWER === 'ON') ? hap.Characteristic.CurrentHeatingCoolingState.HEAT : hap.Characteristic.CurrentHeatingCoolingState.OFF; }).catch((err) => { this.log.error('Failed to set relay state: cmd=' + this.deviceStatStatus + ' [' + err + ']'); }); } pollDeviceStatus() { const deviceStatus = async () => { let pwr, tmp, hum; const url = 'http://' + this.deviceIPAddress; await axios_1.default.get(url + this.deviceStatStatus, { timeout: 3000 }) .then((response) => { tmp = parseFloat(response.data.StatusSNS[this.sensorName].Temperature); }).catch((err) => { throw new Error('Failed to get status: cmd=' + this.deviceStatStatus + ' [' + err + ']'); }); await axios_1.default.get(url + this.deviceStatStatus, { timeout: 3000 }) .then((response) => { hum = parseFloat(response.data.StatusSNS[this.sensorName].Humidity); }).catch((err) => { throw new Error('Failed to get status: cmd=' + this.deviceStatStatus + ' [' + err + ']'); }); await axios_1.default.get(url + this.deviceStatPower, { timeout: 3000 }) .then((response) => { pwr = (response.data.POWER === 'ON') ? hap.Characteristic.CurrentHeatingCoolingState.HEAT : hap.Characteristic.CurrentHeatingCoolingState.OFF; }).catch((err) => { throw new Error('Failed to get power status: cmd=' + this.deviceStatPower + ' [' + err + ']'); }); return { 'TMP_STAT': (await tmp), 'HUM_STAT': (await hum), 'PWR_STAT': (await pwr) }; }; deviceStatus() .then((response) => { // device online this.isOffline = false; // state values this.currTemp = response['TMP_STAT']; this.currRelativeHumidity = response['HUM_STAT']; this.currentHeatingState = response['PWR_STAT']; this.thermostatService.setCharacteristic(hap.Characteristic.CurrentTemperature, this.currTemp); if (this.enableHumidity) { this.humidityService.setCharacteristic(hap.Characteristic.CurrentRelativeHumidity, this.currRelativeHumidity); } // init target state from current state let targetRelayOn = (this.currentHeatingState === hap.Characteristic.CurrentHeatingCoolingState.HEAT); switch (this.targetHeatingState) { case hap.Characteristic.TargetHeatingCoolingState.AUTO: { // AUTO mode: Compare temperatures if (parseFloat(this.currTemp) >= (this.targetTemp + this.deltaTemp)) { targetRelayOn = false; } else if (parseFloat(this.currTemp) <= (this.targetTemp - this.deltaTemp)) { targetRelayOn = true; } } break; case hap.Characteristic.CurrentHeatingCoolingState.HEAT: targetRelayOn = true; break; case hap.Characteristic.CurrentHeatingCoolingState.OFF: targetRelayOn = false; break; } // Change status if needed (this.currentHeatingState as boolean here) if (targetRelayOn && !this.currentHeatingState) { this.setDevicePower(hap.Characteristic.CurrentHeatingCoolingState.HEAT); } else if (!targetRelayOn && this.currentHeatingState) { this.setDevicePower(hap.Characteristic.CurrentHeatingCoolingState.OFF); } }) .catch((err) => { this.currTemp = '--'; this.thermostatService.setCharacteristic(hap.Characteristic.CurrentTemperature, this.currTemp); this.currRelativeHumidity = '--'; if (this.enableHumidity) { this.humidityService.setCharacteristic(hap.Characteristic.CurrentRelativeHumidity, this.currRelativeHumidity); } // output error only once, do not spam the log on each poll if (!this.isOffline) { this.log.debug('Device offline? ' + err); this.isOffline = true; } }); } // helpers // heatingStateToStr(state) { // assuming we have no 'COOL'! // assiming current and target to have the first 2 values identical! switch (state) { case hap.Characteristic.TargetHeatingCoolingState.OFF: return 'OFF'; case hap.Characteristic.TargetHeatingCoolingState.HEAT: return 'HEAT'; case hap.Characteristic.TargetHeatingCoolingState.AUTO: return 'AUTO'; default: return 'UNKNOWN'; } } } module.exports = (api) => { hap = api.hap; api.registerAccessory('homebridge-th16ermostat', 'TH16ermostat', TH16ermostatPlugin); }; //# sourceMappingURL=index.js.map