homebridge-evohome
Version:
Honeywell Evohome support for Homebridge: https://github.com/nfarina/homebridge
1,328 lines (1,182 loc) • 51.7 kB
JavaScript
// 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;