UNPKG

homebridge-evohome

Version:

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

432 lines (387 loc) 11.7 kB
var Q = require("q"); var request = require("request"); var _ = require("lodash"); const BASE_URL = "https://tccna.resideo.com"; const API_URL = `${BASE_URL}/WebAPI/emea/api/v1`; function UserInfo(json) { this.userID = json.userId; this.username = json.username; this.firstname = json.firstname; this.lastname = json.lastname; this.streetAddress = json.streetAddress; this.city = json.city; this.postcode = json.postcode; this.country = json.country; this.language = json.language; } // Private var sessionCredentials = {}; function Session(username, password, json, userinfo) { this.sessionId = "bearer " + json.access_token; this.refreshToken = json.refresh_token; this.refreshTokenInterval = json.expires_in - 30; // refresh token 30 seconds before expiry this.userInfo = new UserInfo(userinfo); sessionCredentials[this.sessionId] = { username: username, password: password, }; } function Location(json) { this.locationID = json.locationID; this.name = json.name; this.streetAddress = json.streetAddress; this.city = json.city; this.country = json.country; this.postcode = json.postcode; this.locationType = json.locationType; this.devices = _.map(json.devices, function (device) { return new Device(device); }); this.daylightSavingTimeEnabled = json.daylightSavingTimeEnabled; this.timeZone = new Timezone(json.timeZone); this.systemId = json.systemId; this.dhw = json.dhw; } function Timezone(json) { this.timeZoneId = json.timeZoneId; this.displayName = json.displayName; this.offsetMinutes = json.offsetMinutes; this.currentOffsetMinutes = json.currentOffsetMinutes; this.supportsDaylightSaving = json.supportsDaylightSaving; } function Device(json) { this.zoneID = json.zoneId; this.zoneType = json.zoneType; this.modelType = json.modelType; this.name = json.name; this.maxHeatSetpoint = json.setpointCapabilities.maxHeatSetpoint; this.minHeatSetpoint = json.setpointCapabilities.minHeatSetpoint; this.valueResolution = json.setpointCapabilities.valueResolution; } function Thermostat(json) { this.zoneId = json.zoneId; this.name = json.name; this.temperatureStatus = new TemperatureStatus(json.temperatureStatus); this.setpointStatus = new SetpointStatus(json.setpointStatus); } function DHW(json) { this.dhwId = json.dhwId; this.temperatureStatus = new TemperatureStatus(json.temperatureStatus); this.dhwStatus = new DHWStatus(json.stateStatus); } function TemperatureStatus(json) { this.temperature = json.temperature; this.isAvailable = json.isAvailable; } function SetpointStatus(json) { this.targetHeatTemperature = json.targetHeatTemperature; this.setpointMode = json.setpointMode; } function DHWStatus(json) { this.state = json.state; this.mode = json.mode; } function Schedule(json) { this.dayOfWeek = json.dayOfWeek; this.switchpoints = _.map(json.switchpoints, function (sw) { return new Switchpoint(sw); }); } function Switchpoint(json) { this.heatSetpoint = json.heatSetpoint; this.timeOfDay = json.timeOfDay; } function SystemModeStatus(json) { this.mode = json.mode; this.isPermanent = json.isPermanent; } Session.prototype.getSchedule = function (zoneId, isHotWater) { var zone_type = isHotWater ? "domesticHotWater" : "temperatureZone"; var url = `${API_URL}/${zone_type}/${zoneId}/schedule`; return this._request(url).then(function (json) { return _.map(json.dailySchedules, function (s) { return new Schedule(s); }); }); }; Session.prototype.getThermostats = function (locationId) { var url = `${API_URL}/location/${locationId}/status?includeTemperatureControlSystems=True`; return this._request(url).then(function (json) { return _.map( json.gateways[0].temperatureControlSystems[0].zones, function (t) { return new Thermostat(t); } ); }); }; Session.prototype.getHotWater = function (dhwId) { var url = `${API_URL}/domesticHotWater/${dhwId}/status?`; return this._request(url).then(function (json) { return new DHW(json) }); }; Session.prototype.getSystemModeStatus = function (locationId) { var url = `${API_URL}/location/${locationId}/status?includeTemperatureControlSystems=True`; return this._request(url).then(function (json) { return new SystemModeStatus( json.gateways[0].temperatureControlSystems[0].systemModeStatus ); }); }; Session.prototype.getLocations = function () { var url = `${API_URL}/location/installationInfo?userId=${this.userInfo.userID}&includeTemperatureControlSystems=True`; return this._request(url).then(function (json) { return _.map(json, function (location) { var data = {}; data.locationID = location.locationInfo.locationId; data.name = location.locationInfo.name; data.streetAddress = location.locationInfo.streetAddress; data.city = location.locationInfo.city; data.country = location.locationInfo.country; data.postcode = location.locationInfo.postcode; data.locationType = location.locationInfo.locationType; data.daylightSavingTimeEnabled = location.locationInfo.useDaylightSaveSwitching; data.timeZone = location.locationInfo.timeZone; data.devices = location.gateways[0].temperatureControlSystems[0].zones; data.dhw = location.gateways[0].temperatureControlSystems[0].dhw; data.systemId = location.gateways[0].temperatureControlSystems[0].systemId; return new Location(data); }); }); }; Session.prototype.setHeatSetpoint = function ( zoneId, targetTemperature, endtime ) { var deferred = Q.defer(); var url = `${API_URL}/temperatureZone/${zoneId}/heatSetpoint`; var now = new Date(); if (endtime != null) { if (endtime == "00:00:00") { now.setDate(now.getDate() + 1); } var endDateString = now.toDateString() + " " + endtime; var endDate = new Date(Date.parse(endDateString)); var body = JSON.stringify({ HeatSetpointValue: targetTemperature, SetpointMode: "TemporaryOverride", TimeUntil: endDate, }); } else if (targetTemperature == 0) { // if target temperature is set to zero then we ask to // follow the schedule instead of setting a temperature var body = JSON.stringify({ HeatSetpointValue: 0.0, SetpointMode: "FollowSchedule", TimeUntil: null, }); } else { // if target temperature is null we ask for a // permanent override of the schedule var body = JSON.stringify({ HeatSetpointValue: targetTemperature, SetpointMode: "PermanentOverride", TimeUntil: null, }); } request( { method: "PUT", url: url, headers: { "Content-Type": "application/json", Authorization: this.sessionId, }, body: body, }, function (err, response) { if (err) { deferred.reject(err); } else { deferred.resolve(JSON.parse(response.body)); } } ); return deferred.promise; }; function getDataForHotWater(dhwId, data) { var url = `${API_URL}/domesticHotWater/${dhwId}/state`; var body = JSON.stringify(data); return [url, body]; } function getDataForSwitch(systemId, systemMode) { var url = `${API_URL}/temperatureControlSystem/${systemId}/mode`; var body = JSON.stringify({ SystemMode: systemMode, TimeUntil: null, Permanent: true, }); return [url, body]; } Session.prototype.setSystemMode = function (systemId, systemMode, isHotWater) { var deferred = Q.defer(); var url, body; [url, body] = isHotWater ? getDataForHotWater(systemId, systemMode) : getDataForSwitch(systemId, systemMode); request( { method: "PUT", url: url, headers: { "Content-Type": "application/json", Authorization: this.sessionId, }, body: body, }, function (err, response) { if (err) { deferred.reject(err); } else { deferred.resolve(JSON.parse(response.body)); } } ); return deferred.promise; }; Session.prototype._renew = function () { var self = this; var deferred = Q.defer(); request( { method: "POST", url: `${BASE_URL}/Auth/OAuth/Token`, headers: { Authorization: "Basic NGEyMzEwODktZDJiNi00MWJkLWE1ZWItMTZhMGE0MjJiOTk5OjFhMTVjZGI4LTQyZGUtNDA3Yi1hZGQwLTA1OWY5MmM1MzBjYg==", "Content-Type": "application/x-www-form-urlencoded", Connection: "Keep-Alive", "Cache-Control": "no-store no-cache", Pragma: "no-cache", }, body: "grant_type=refresh_token&refresh_token=" + self.refreshToken, }, function (err, response) { if (err) { deferred.reject(err); } else { try { deferred.resolve(JSON.parse(response.body)); } catch (e) { deferred.reject(e); } } } ); return deferred.promise; }; Session.prototype._request = function (url) { var deferred = Q.defer(); request( { method: "GET", url: url, headers: { Authorization: this.sessionId, }, }, function (err, response) { if (err) { deferred.reject(err); } else { var json; try { json = JSON.parse(response.body); } catch (ex) { console.error(ex); console.error(response.body); console.error(response); deferred.reject(ex); } if (json) { deferred.resolve(json); } } } ); return deferred.promise; }; function login(username, password) { var deferred = Q.defer(); request( { method: "POST", url: `${BASE_URL}/Auth/OAuth/Token`, headers: { Authorization: "Basic NGEyMzEwODktZDJiNi00MWJkLWE1ZWItMTZhMGE0MjJiOTk5OjFhMTVjZGI4LTQyZGUtNDA3Yi1hZGQwLTA1OWY5MmM1MzBjYg==", "Content-Type": "application/x-www-form-urlencoded", Connection: "Keep-Alive", "Cache-Control": "no-store no-cache", Pragma: "no-cache", }, body: "grant_type=password&scope=EMEA-V1-Basic EMEA-V1-Anonymous EMEA-V1-Get-Current-User-Account&Username=" + encodeURIComponent(username) + "&Password=" + encodeURIComponent(password), }, function (err, response) { if (err) { deferred.reject(err); } else { var json; try { json = JSON.parse(response.body); if (json.error != null) { deferred.reject(json.error); } else if (json.access_token == null) { deferred.reject("No 'access_token' in " + JSON.stringify(json)); } else { deferred.resolve(JSON.parse(response.body)); } } catch (e) { deferred.reject(e); } } } ); return deferred.promise; } function getUserInfo(json) { var deferred = Q.defer(); request( { method: "GET", url: `${API_URL}/userAccount`, headers: { Authorization: "bearer " + json.access_token, }, }, function (err, response) { if (err) { deferred.reject(err); } else { try { deferred.resolve(JSON.parse(response.body)); } catch (e) { deferred.reject(e); } } } ); return deferred.promise; } module.exports = { login: function (username, password) { return login(username, password).then(function (json) { return getUserInfo(json).then(function (userinfojson) { return new Session(username, password, json, userinfojson); }); }); }, };