UNPKG

homebridge-evohome

Version:

Honeywell Evohome support for Homebridge: https://github.com/nfarina/homebridge

1,328 lines (1,182 loc) 51.7 kB
// This platform integrates Honeywell Evohome into homebridge // As I only own a few thermostats and no window sensors I have not yet integrated them. // // The configuration is stored inside the ../config.json // { // "platform": "Evohome", // "name" : "Evohome", // "username" : "username/email", // "password" : "password", // "locationIndex" : "locationIndex" // } // "use strict"; var evohome = require("./lib/evohome.js"); var Service, Characteristic; var config; var FakeGatoHistoryService; var inherits = require("util").inherits; const moment = require("moment"); var CustomCharacteristic = {}; module.exports = function (homebridge) { FakeGatoHistoryService = require("fakegato-history")(homebridge); Service = homebridge.hap.Service; Characteristic = homebridge.hap.Characteristic; CustomCharacteristic.ValvePosition = function () { Characteristic.call( this, "Valve position", "E863F12E-079E-48FF-8F27-9C2605A29F52" ); this.setProps({ format: Characteristic.Formats.UINT8, unit: Characteristic.Units.PERCENTAGE, perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY], }); this.value = this.getDefaultValue(); }; inherits(CustomCharacteristic.ValvePosition, Characteristic); CustomCharacteristic.ProgramCommand = function () { Characteristic.call( this, "Program command", "E863F12C-079E-48FF-8F27-9C2605A29F52" ); this.setProps({ format: Characteristic.Formats.DATA, perms: [Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY], }); this.value = this.getDefaultValue(); }; inherits(CustomCharacteristic.ProgramCommand, Characteristic); CustomCharacteristic.ProgramData = function () { Characteristic.call( this, "Program data", "E863F12F-079E-48FF-8F27-9C2605A29F52" ); this.setProps({ format: Characteristic.Formats.DATA, perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY], }); this.value = this.getDefaultValue(); }; inherits(CustomCharacteristic.ProgramData, Characteristic); homebridge.registerPlatform("homebridge-evohome", "Evohome", EvohomePlatform); }; function EvohomePlatform(log, config) { this.sessionObject = null; this.name = config["name"]; this.username = config["username"]; this.password = config["password"]; this.temperatureUnit = config["temperatureUnit"] || "Celsius"; this.locationIndex = config["locationIndex"] || 0; this.switchAway = config["switchAway"]; //set to false to hide this.switchDayOff = config["switchDayOff"]; this.switchEco = config["switchEco"]; this.switchHeatingOff = config["switchHeatingOff"]; this.switchCustom = config["switchCustom"]; this.temperatureAboveAsOff = config["temperatureAboveAsOff"]; this.childBridge = config["childBridge"] || false; this.cache_timeout = 300; // seconds this.interval_setTemperature = 5; // seconds this.interval_getStatus = 60; // seconds this.systemMode = ""; this.log = log; this.updating = false; } EvohomePlatform.prototype = { accessories: function (callback) { this.log("Logging into Evohome..."); var that = this; // create the myAccessories array this.myAccessories = []; evohome .login(that.username, that.password) .then( function (session) { this.log("Logged into Evohome!"); this.sessionObject = session; session .getLocations() .then( function (locations) { this.log( "You have", locations.length, "location(s). This instance will be using Index No", that.locationIndex ); this.log( "You have", locations[that.locationIndex].devices.length, "device(s)." ); session .getThermostats(locations[that.locationIndex].locationID) .then( function (thermostats) { session .getSystemModeStatus( locations[that.locationIndex].locationID ) .then( function (systemModeStatus) { // iterate through the devices for (var deviceID in locations[that.locationIndex] .devices) { for (var thermoId in thermostats) { if ( locations[that.locationIndex].devices[ deviceID ].zoneID == thermostats[thermoId].zoneId ) { // print name of the device this.log( deviceID + ": " + locations[that.locationIndex].devices[ deviceID ].name + " (" + thermostats[thermoId].temperatureStatus .temperature + "°)" ); if ( locations[that.locationIndex].devices[ deviceID ].name == "" ) { // Device name is empty // Probably Hot Water // Do not store this.log( "Found blank device name, probably stored hot water. Ignoring device for now." ); } else { // store device in var var device = locations[that.locationIndex].devices[ deviceID ]; // store thermostat in var var thermostat = thermostats[thermoId]; // store name of device var name = locations[that.locationIndex].devices[ deviceID ].name + " Thermostat"; // timezone offset in minutes var offsetMinutes = locations[that.locationIndex].timeZone .offsetMinutes; // create accessory (only if it is "HeatingZone", "RoundWireless" or "RoundModulation") if ( device.modelType == "HeatingZone" || device.modelType == "RoundWireless" || device.modelType == "RoundModulation" ) { var accessory = new EvohomeThermostatAccessory( that, that.log, name, device, locations[ that.locationIndex ].systemId, deviceID, thermostat, this.temperatureUnit, this.username, this.password, this.interval_setTemperature, offsetMinutes ); // store accessory in myAccessories this.myAccessories.push(accessory); } } } } } this.systemMode = systemModeStatus.mode; if (this.switchAway != false) { var awayAccessory = new EvohomeSwitchAccessory( that, that.log, that.name + " Away Mode", locations[that.locationIndex].systemId, "Away", systemModeStatus.mode == "Away" ? true : false, this.username, this.password ); this.myAccessories.push(awayAccessory); } if (this.switchDayOff != false) { var dayOffAccessory = new EvohomeSwitchAccessory( that, that.log, that.name + " Day Off Mode", locations[that.locationIndex].systemId, "DayOff", systemModeStatus.mode == "DayOff" ? true : false, this.username, this.password ); this.myAccessories.push(dayOffAccessory); } if (this.switchHeatingOff != false) { var heatingOffAccessory = new EvohomeSwitchAccessory( that, that.log, that.name + " Heating Off Mode", locations[that.locationIndex].systemId, "HeatingOff", systemModeStatus.mode == "HeatingOff" ? true : false, this.username, this.password ); this.myAccessories.push(heatingOffAccessory); } if (this.switchEco != false) { var ecoAccessory = new EvohomeSwitchAccessory( that, that.log, that.name + " Eco Mode", locations[that.locationIndex].systemId, "AutoWithEco", systemModeStatus.mode == "AutoWithEco" ? true : false, this.username, this.password ); this.myAccessories.push(ecoAccessory); } if (this.switchCustom != false) { var customAccessory = new EvohomeSwitchAccessory( that, that.log, that.name + " Custom Mode", locations[that.locationIndex].systemId, "Custom", systemModeStatus.mode == "Custom" ? true : false, this.username, this.password ); this.myAccessories.push(customAccessory); } if (locations[that.locationIndex].dhw) { var dhwSwitchAccessory = new EvohomeDhwAccessory( that, that.log, locations[that.locationIndex].dhw["dhwId"], that.username, that.password, that.interval_getStatus ); this.myAccessories.push(dhwSwitchAccessory); } callback(this.myAccessories); setInterval( that.renewSession.bind(this), session.refreshTokenInterval * 1000 ); setInterval( that.periodicUpdate.bind(this), this.cache_timeout * 1000 ); }.bind(this) ) .fail(function (err) { that.log.error( "Error getting system mode status:\n", err ); if (!this.childBridge) { callback([]); } }); }.bind(this) ) .fail(function (err) { that.log.error("Error getting thermostats:\n", err); if (!this.childBridge) { callback([]); } }); }.bind(this) ) .fail(function (err) { that.log.error("Error getting locations:\n", err); if (!this.childBridge) { callback([]); } }); }.bind(this) ) .fail(function (err) { // tell me if login did not work! that.log.error("Error during login:\n", err); if (!this.childBridge) { callback([]); } }); }, }; EvohomePlatform.prototype.renewSession = function () { var that = this; var session = this.sessionObject; session ._renew() .then(function (json) { // renew session token session.sessionId = "bearer " + json.access_token; session.refreshToken = json.refresh_token; that.log.debug("Renewed Honeywell API authentication token!"); }) .fail(function (err) { that.log.error( "Renewing Honeywell API authentication token failed:\n", err ); }); }; EvohomePlatform.prototype.periodicUpdate = function () { if (!this.updating && this.myAccessories) { this.updating = true; var session = this.sessionObject; session .getLocations() .then( function (locations) { session .getThermostats(locations[this.locationIndex].locationID) .then( function (thermostats) { session .getSystemModeStatus(locations[this.locationIndex].locationID) .then( function (systemModeStatus) { this.systemMode = systemModeStatus.mode; var updatedAwayActive = false; var updatedDayOffActive = false; var updatedHeatingOffActive = false; var updatedEcoActive = false; var updatedCustomActive = false; var updatedHotWaterActive = false; for (var deviceID in locations[this.locationIndex] .devices) { for (var thermoId in thermostats) { if ( locations[this.locationIndex].devices[deviceID] .zoneID == thermostats[thermoId].zoneId ) { for ( var i = 0; i < this.myAccessories.length; ++i ) { if ( this.myAccessories[i].device != null && this.myAccessories[i].device.zoneID == locations[this.locationIndex].devices[ deviceID ].zoneID ) { var device = locations[this.locationIndex].devices[ deviceID ]; var thermostat = thermostats[thermoId]; if (device) { // Check if temp has changed var oldCurrentTemp = this.myAccessories[i].thermostat .temperatureStatus.temperature; var newCurrentTemp = thermostat.temperatureStatus.temperature; var oldTargetTemp = this.myAccessories[i].thermostat .setpointStatus.targetHeatTemperature; var newTargetTemp = thermostat.setpointStatus .targetHeatTemperature; // retrieve service, update stored device and thermostat var service = this.myAccessories[i].thermostatService; this.myAccessories[i].device = device; this.myAccessories[i].thermostat = thermostat; if ( oldCurrentTemp != newCurrentTemp && service ) { this.log.debug( "Updating: " + device.name + " currentTempChange from: " + oldCurrentTemp + " to: " + newCurrentTemp ); } if ( oldTargetTemp != newTargetTemp && service ) { this.log( "Updating: " + device.name + " targetTempChange from: " + oldTargetTemp + " to: " + newTargetTemp ); } // notify homebridge of current temp and target because homekit's cached temperature might be wrong if (service) { // updateValue triggers a change event which notifies HomeKit service .getCharacteristic( Characteristic.CurrentTemperature ) .updateValue(Number(newCurrentTemp)); service .getCharacteristic( Characteristic.TargetTemperature ) .updateValue(Number(newTargetTemp)); // if temperature or setpoint changed then CurrentHeatingCoolingState and TargetHeatingCoolingState might have changed too // getValue will update HomeKit if the value is different to homebridge's cached value service .getCharacteristic( Characteristic.CurrentHeatingCoolingState ) .getValue(); service .getCharacteristic( Characteristic.TargetHeatingCoolingState ) .getValue(); } var loggingService = this.myAccessories[i].loggingService; //this.log("populating loggingService: " + loggingService); //this.log(moment().unix() + " " + newCurrentTemp + " " + newTargetTemp); var valvePosition = newCurrentTemp >= newTargetTemp ? 0 : 100; loggingService.addEntry({ time: moment().unix(), currentTemp: newCurrentTemp, setTemp: newTargetTemp, valvePosition: valvePosition, }); } } else if ( !updatedAwayActive && this.myAccessories[i].systemMode == "Away" ) { updatedAwayActive = true; var newAwayActive = systemModeStatus.mode == "Away" ? true : false; if ( this.myAccessories[i].active != newAwayActive ) { this.log( "Updating system mode Away to " + newAwayActive ); this.myAccessories[i].active = newAwayActive; } } else if ( !updatedDayOffActive && this.myAccessories[i].systemMode == "DayOff" ) { updatedDayOffActive = true; var newDayOffActive = systemModeStatus.mode == "DayOff" ? true : false; if ( this.myAccessories[i].active != newDayOffActive ) { this.log( "Updating system mode DayOff to " + newDayOffActive ); this.myAccessories[i].active = newDayOffActive; } } else if ( !updatedHeatingOffActive && this.myAccessories[i].systemMode == "HeatingOff" ) { updatedHeatingOffActive = true; var newHeatingOffActive = systemModeStatus.mode == "HeatingOff" ? true : false; if ( this.myAccessories[i].active != newHeatingOffActive ) { this.log( "Updating system mode HeatingOff to " + newHeatingOffActive ); this.myAccessories[i].active = newHeatingOffActive; } } else if ( !updatedEcoActive && this.myAccessories[i].systemMode == "AutoWithEco" ) { updatedEcoActive = true; var newEcoActive = systemModeStatus.mode == "AutoWithEco" ? true : false; if ( this.myAccessories[i].active != newEcoActive ) { this.log( "Updating system mode Eco to " + newEcoActive ); this.myAccessories[i].active = newEcoActive; } } else if ( !updatedCustomActive && this.myAccessories[i].systemMode == "Custom" ) { updatedCustomActive = true; var newCustomActive = systemModeStatus.mode == "Custom" ? true : false; if ( this.myAccessories[i].active != newCustomActive ) { this.log( "Updating system mode Custom to " + newCustomActive ); this.myAccessories[i].active = newCustomActive; } } else if ( !updatedHotWaterActive && this.myAccessories[i].model == "domesticHotWater" ) { updatedHotWaterActive = true; var loggingService = this.myAccessories[i].loggingService; loggingService.addEntry({ time: moment().unix(), currentTemp: this.myAccessories[i].currentTemperature, setTemp: 60, // TODO, random value valvePosition: this.myAccessories[i] .currentState ? 100 : 0, }); } } } } } }.bind(this) ) .fail(function (err) { this.log.error("Error getting system mode status:\n", err); }); }.bind(this) ) .fail(function (err) { this.log.error("Error getting thermostats:\n", err); }); }.bind(this) ) .fail( function (err) { this.log.error("Error getting locations:\n", err); }.bind(this) ); this.updating = false; } }; // give this function all the parameters needed function EvohomeThermostatAccessory( platform, log, name, device, systemId, deviceID, thermostat, temperatureUnit, username, password, interval_setTemperature, offsetMinutes ) { this.uuid_base = systemId + ":" + deviceID; this.name = name; this.displayName = name; // fakegato this.device = device; this.model = device.modelType; this.serial = deviceID; this.systemId = systemId; this.deviceID = deviceID; this.thermostat = thermostat; this.temperatureUnit = temperatureUnit; this.platform = platform; this.username = username; this.password = password; this.log = log; this.loggingService = new FakeGatoHistoryService("thermo", this, { storage: "fs", }); this.targetTemperateToSet = -1; this.offsetMinutes = offsetMinutes; setInterval( this.periodicCheckSetTemperature.bind(this), interval_setTemperature * 1000 ); } function getNextScheduledTime(log, schedule) { var date = new Date(); var utc = date.getTime() + date.getTimezoneOffset() * 60000; // this was previously used to be independent from system time // but caused problems with daylight saving time so went back // to system time. Keeping this here to revert if needed // -- 2020-05-03 //var correctDate = new Date(utc + (60000 * that.offsetMinutes)); var correctDate = new Date(); var weekdayNumber = correctDate.getDay(); var weekday = new Array(7); weekday[0] = "Sunday"; weekday[1] = "Monday"; weekday[2] = "Tuesday"; weekday[3] = "Wednesday"; weekday[4] = "Thursday"; weekday[5] = "Friday"; weekday[6] = "Saturday"; var currenttime = correctDate.toLocaleTimeString([], { hour12: false, }); log.debug("The current time is", currenttime); var proceed = true; var nextScheduleTime = null; for (var scheduleId in schedule) { if (schedule[scheduleId].dayOfWeek == weekday[weekdayNumber]) { log.debug( "Schedule points for today (" + schedule[scheduleId].dayOfWeek + ")" ); var switchpoints = schedule[scheduleId].switchpoints; for (var switchpointId in switchpoints) { var logline = "- " + switchpoints[switchpointId].timeOfDay; if (proceed == true) { if (currenttime >= switchpoints[switchpointId].timeOfDay) { proceed = true; } else if (currenttime < switchpoints[switchpointId].timeOfDay) { proceed = false; nextScheduleTime = switchpoints[switchpointId].timeOfDay; logline = logline + " -> next change"; } } log.debug(logline); } if (proceed == true) { nextScheduleTime = "00:00:00"; } } } return nextScheduleTime; } EvohomeThermostatAccessory.prototype = { periodicCheckSetTemperature: function () { var that = this; var session = that.platform.sessionObject; var value = that.targetTemperateToSet; if (value != -1) { session .getSchedule(that.device.zoneID, false) .then(function (schedule) { var nextScheduleTime = getNextScheduledTime(that.log, schedule); that.log( "Setting target temperature for", that.name, "to", value + "° until " + nextScheduleTime ); session .setHeatSetpoint(that.device.zoneID, value, nextScheduleTime) .then(function (taskId) { that.log.debug("Successfully changed temperature!"); that.log.debug(taskId); // returns taskId if successful that.targetTemperateToSet = -1; that.thermostat.setpointStatus.targetHeatTemperature = value; // set target temperature here also to prevent from setting temperature two times }); }) .fail(function (err) { that.log.error("Error getting schedule:\n", err); that.targetTemperateToSet = -1; //callback(null, Number(0)); }); } }, getCurrentTemperature: function (callback) { var that = this; // need to refresh data if outdated!! var currentTemperature = this.thermostat.temperatureStatus.temperature; callback(null, Number(currentTemperature)); that.log.debug( "Current temperature of " + this.name + " is " + currentTemperature + "°" ); }, getCurrentHeatingCoolingState: function (callback) { var that = this; // OFF = 0 // HEAT = 1 // COOL = 2 // AUTO = 3 if (this.model == "HeatingZone") { var targetTemp = this.thermostat.setpointStatus.targetHeatTemperature; var currentTemp = this.thermostat.temperatureStatus.temperature; // state is HEAT if there is current call for heat, or OFF var state = currentTemp < targetTemp ? 1 : 0; that.log.debug("Current state of: " + this.name + " is: " + state); } else { var state = 1; // domestic hot water not supported (set to heat by default) } callback(null, Number(state)); }, getName: function (callback) { var that = this; that.log.debug("requesting name of", this.name); callback(this.name); }, setTargetHeatingCooling: function (value, callback) { var that = this; var session = that.platform.sessionObject; // OFF = 0 (Turn heating off, duh) // HEAT = 1 (User intention - Operate normally - change nothing) // COOL = 2 (User intention - Operate normally - change nothing) // AUTO = 3 (Revert to timer/cancel override) // HEAT and COOL could be interpreted as "lock the temperature" but // expected evohome behaviour for this, based on the main panel, // would be to continue the set temperature until the next timer // event. If no new temp is selected this just means do nothing. // // If a new temp is selected then setTargetTemperature gets called // instead of setTargetHeatingCooling, which is correct. // // If temp is previously OFF then the override should be cancelled. if (value == 0) { // OFF that.log.debug("OFF selected, turning heating off"); // set temperature to 5 degrees permanently when heating is "off" session .setHeatSetpoint(that.device.zoneID, 5, null) .then(function (taskId) { that.log("Heating is set off for " + that.name + " (set to 5°)"); that.log.debug(taskId); // returns taskId if successful that.thermostat.setpointStatus.targetHeatTemperature = 5; // set target temperature here also to prevent from setting temperature two times }); } else if (value == 1 || value == 2) { // HEAT or COOL that.getTargetHeatingCooling(function (dummy, currentState) { if (currentState == 0) { that.log.debug( "HEAT or COOL selected, previous state OFF, cancelling override" ); // set thermostat to follow the schedule by passing 0 to the method session .setHeatSetpoint(that.device.zoneID, 0, null) .then(function (taskId) { that.log( "Cancelled override for " + that.name + " (set to follow schedule)" ); that.log.debug(taskId); // returns taskId if successful }); } else { that.log( "HEAT or COOL selected, previous state AUTO, HEAT or COOL. Doing nothing." ); } }); } else { // AUTO that.log.debug("AUTO selected, cancelling overrides"); // set thermostat to follow the schedule by passing 0 to the method session .setHeatSetpoint(that.device.zoneID, 0, null) .then(function (taskId) { that.log( "Cancelled override for " + that.name + " (set to follow schedule)" ); that.log.debug(taskId); // returns taskId if successful }); } callback(null); }, getTargetHeatingCooling: function (callback) { var that = this; // OFF = 0 // HEAT = 1 // COOL = 2 // AUTO = 3 if (this.model == "HeatingZone") { var targetTemp = this.thermostat.setpointStatus.targetHeatTemperature; var currentTemp = this.thermostat.temperatureStatus.temperature; // Sets the heating state of the thermostat to either OFF or HEAT var state = // OFF if targetTemp <= 5 °C targetTemp <= 5 || // OFF if targetTemp below currentTemp AND 'temperatureAboveAsOff' set to true (targetTemp <= currentTemp && that.temperatureAboveAsOff) ? 0 : 1; } else { var state = 1; // domestic hot water not supported (set to heat by default) } callback(null, Number(state)); }, setTargetTemperature: function (value, callback) { var that = this; that.log("Request to set target temperature to " + value); that.targetTemperateToSet = value; callback(null, Number(1)); }, getTargetTemperature: function (callback) { var that = this; // gives back the target temperature of thermostat // crashes the plugin IF there is no value defined (like // with DOMESTIC_HOT_WATER) so we need to check if it // is defined first if ((this.model = "HeatingZone")) { var targetTemperature = this.thermostat.setpointStatus.targetHeatTemperature; that.log.debug( "Target temperature for", this.name, "is", targetTemperature + "°" ); } else { var targetTemperature = 0; that.log.debug( "Will set target temperature for", this.name, "to " + targetTemperature + "°" ); } callback(null, Number(targetTemperature)); }, getTemperatureDisplayUnits: function (callback) { var temperatureUnits = this.temperatureUnit == "Fahrenheit" ? 1 : 0; callback(null, Number(temperatureUnits)); }, setTemperatureDisplayUnits: function (value, callback) { var stringValue = value == 1 ? "Fahrenheit" : "Celsius"; this.log.debug("set temperature units to", stringValue); this.temperatureUnit = stringValue; callback(); }, getValvePosition: function (callback) { if (this.model == "HeatingZone") { var targetTemp = this.thermostat.setpointStatus.targetHeatTemperature; var currentTemp = this.thermostat.temperatureStatus.temperature; // state is HEAT if there is current call for heat, or OFF var state = currentTemp < targetTemp ? 100 : 0; } else { var state = 100; // domestic hot water not supported (set to heat by default) } callback(null, Number(state)); }, setProgramCommand: function (value, callback) { // not implemented callback(); }, getProgramData: function (callback) { // not implemented var data = "12f1130014c717040af6010700fc140c170c11fa24366684ffffffff24366684ffffffff24366684ffffffff24366684ffffffff24366684ffffffff24366684ffffffff24366684fffffffff42422222af3381900001a24366684ffffffff"; var buffer = new Buffer( ("" + data).replace(/[^0-9A-F]/gi, ""), "hex" ).toString("base64"); callback(null, buffer); }, getServices: function () { var that = this; // Information Service var informationService = new Service.AccessoryInformation(); //var serial = 123456 + this.deviceID; var strSerial = this.systemId + "-" + this.serial; this.log.debug("Serial: " + strSerial); informationService .setCharacteristic(Characteristic.Identify, this.name) .setCharacteristic(Characteristic.Manufacturer, "Honeywell") .setCharacteristic(Characteristic.Model, this.model) .setCharacteristic(Characteristic.Name, this.name) .setCharacteristic(Characteristic.SerialNumber, strSerial); // need to stringify the this.serial // Thermostat Service //this.thermostatService = new Service.Thermostat("Honeywell Thermostat"); // Remove the old way above because it creates all devices as "Honeywell Thermostat" so I have no idea which thermostat it is. // This new way creates each thermostat as its own room name, as pulled from Evohome this.thermostatService = new Service.Thermostat(this.name); // Required Characteristics ///////////////////////////////////////////////////////////// // this.addCharacteristic(Characteristic.CurrentHeatingCoolingState); READ this.thermostatService .getCharacteristic(Characteristic.CurrentHeatingCoolingState) .on("get", this.getCurrentHeatingCoolingState.bind(this)); // this.addCharacteristic(Characteristic.TargetHeatingCoolingState); READ WRITE this.thermostatService .getCharacteristic(Characteristic.TargetHeatingCoolingState) .on("get", this.getTargetHeatingCooling.bind(this)) .on("set", this.setTargetHeatingCooling.bind(this)); // this.addCharacteristic(Characteristic.CurrentTemperature); READ this.thermostatService .getCharacteristic(Characteristic.CurrentTemperature) .on("get", this.getCurrentTemperature.bind(this)) .setProps({ minValue: 1, maxValue: 50, minStep: this.device.valueResolution, }); // this.addCharacteristic(Characteristic.TargetTemperature); READ WRITE this.thermostatService .getCharacteristic(Characteristic.TargetTemperature) .on("get", this.getTargetTemperature.bind(this)) .on("set", this.setTargetTemperature.bind(this)) .setProps({ minValue: this.device.minHeatSetpoint, maxValue: this.device.maxHeatSetpoint, minStep: this.device.valueResolution, }); // this.addCharacteristic(Characteristic.TemperatureDisplayUnits); READ WRITE this.thermostatService .getCharacteristic(Characteristic.TemperatureDisplayUnits) .on("get", this.getTemperatureDisplayUnits.bind(this)) .on("set", this.setTemperatureDisplayUnits.bind(this)); // Optional Characteristics ///////////////////////////////////////////////////////////// // this.addOptionalCharacteristic(Characteristic.CurrentRelativeHumidity); // this.addOptionalCharacteristic(Characteristic.TargetRelativeHumidity); // this.addOptionalCharacteristic(Characteristic.CoolingThresholdTemperature); // this.addOptionalCharacteristic(Characteristic.HeatingThresholdTemperature); // this.addOptionalCharacteristic(Characteristic.Name); this.thermostatService.addCharacteristic( CustomCharacteristic.ValvePosition ); this.thermostatService.addCharacteristic( CustomCharacteristic.ProgramCommand ); this.thermostatService.addCharacteristic(CustomCharacteristic.ProgramData); this.thermostatService .getCharacteristic(CustomCharacteristic.ValvePosition) .on("get", this.getValvePosition.bind(this)); this.thermostatService .getCharacteristic(CustomCharacteristic.ProgramCommand) .on("set", this.setProgramCommand.bind(this)); this.thermostatService .getCharacteristic(CustomCharacteristic.ProgramData) .on("get", this.getProgramData.bind(this)); return [informationService, this.thermostatService, this.loggingService]; }, }; // This will be a Temperature sensor with a switch inside it. function EvohomeDhwAccessory( platform, log, dhwId, username, password, interval_getStatus ) { this.uuid_base = dhwId; this.name = platform.name + " Hot Water"; this.displayName = this.name; this.platform = platform; this.dhwId = dhwId; this.log = log; this.model = "domesticHotWater"; this.username = username; this.password = password; this.currentTemperature = -99; this.currentState = true; // Enable logging of temperature this.loggingService = new FakeGatoHistoryService("thermo", this, { storage: "fs", }); setInterval(this.periodicCheckStatus.bind(this), interval_getStatus * 1000); } EvohomeDhwAccessory.prototype = { periodicCheckStatus: function (callback) { var that = this; var session = that.platform.sessionObject; session .getHotWater(this.dhwId) .then(function (dhw) { that.currentTemperature = dhw.temperatureStatus.temperature; that.currentState = dhw.dhwStatus.state == "On" ? true : false; that.log.debug( "Hot Water Temperature " + that.currentTemperature + " and Status " + that.currentState ); }) .fail(function (err) { that.log.error("Failed to load Hot Water:\n", err); callback(err); }); }, getHotWaterTemperature: function (callback) { callback(null, this.currentTemperature); }, getHotWaterStatus: function (callback) { callback(null, this.currentState); }, setHotWaterStatus: function (value, callback) { var that = this; var session = that.platform.sessionObject; session .getSchedule(that.dhwId, true) .then(function (schedule) { var nextScheduleTime = getNextScheduledTime(that.log, schedule); var mode = "PermanentOverride"; var now = new Date(); var untilTime = null; if (nextScheduleTime) { if (nextScheduleTime == "00:00:00") { now.setDate(now.getDate() + 1); } var endDateString = now.toDateString() + " " + nextScheduleTime; untilTime = new Date(Date.parse(endDateString)); mode = "TemporaryOverride"; that.log.debug( "Setting Hot Water to", value + " until " + nextScheduleTime ); } var data = { Mode: mode, State: value ? "On" : "Off", UntilTime: untilTime, }; session .setSystemMode(that.dhwId, data, true) .then(function (taskId) { if (taskId.id) { var msg = "Hot Water is set to: " + value; if (untilTime) { msg += " until " + untilTime; } that.log(msg); that.log.debug(taskId); that.currentState = value; } else { throw taskId; } }) .fail(function (err) { that.log.error("Error setting Hot Water mode:\n", err); callback(err); }); }) .fail(function (err) { that.log.error("Error getting schedule for Hot Water:\n", err); }); }, getServices: function () { var that = this; // Information Service var informationService = new Service.AccessoryInformation(); informationService .setCharacteristic(Characteristic.Identify, this.name) .setCharacteristic(Characteristic.Manufacturer, "Honeywell") .setCharacteristic(Characteristic.Model, this.model) .setCharacteristic(Characteristic.Name, this.name) .setCharacteristic(Characteristic.SerialNumber, this.dhwId); // The Domestic Hot Water will be a Temperature Sensor, and within it // a switch to control on and off until next schedule. this.tempSensor = new Service.TemperatureSensor(); // Read Only this.tempSensor .getCharacteristic(Characteristic.CurrentTemperature) .on("get", this.getHotWaterTemperature.bind(this)); this.tempSensor.setPrimaryService(true); // Switch within the sensor to control it on and off. this.active = new Service.Switch(); // Read Write this.active .getCharacteristic(Characteristic.On) .on("get", this.getHotWaterStatus.bind(this)) .on("set", this.setHotWaterStatus.bind(this)); return [ informationService, this.tempSensor, this.active, this.loggingService, ]; }, }; function EvohomeSwitchAccessory( platform, log, name, systemId, systemMode, active, username, password ) { this.uuid_base = systemId + ":" + systemMode; this.name = name; this.systemId = systemId; this.systemMode = systemMode; this.active = active; this.platform = platform; this.model = "Switch"; this.username = username; this.password = password; this.log = log; } EvohomeSwitchAccessory.prototype = { getActive: function (callback) { var that = this; that.log.debug("System mode " + that.systemMode + " is " + that.active); callback(null, that.active); }, setActive: function (value, callback) { var that = this;