From a47c8dd4fd167975530cb447c5bb9a42f1e0b1e0 Mon Sep 17 00:00:00 2001 From: Chloe Stars Date: Fri, 10 Jul 2015 23:50:31 -0700 Subject: [PATCH 1/6] Added preliminary support for the Nest platform. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some things are missing or may not be working correctly but the main things like checking the current temperature, the target temperature, the humidity, setting the target temperature and etc.. I don’t think the current heating mode is correct. --- platforms/Nest.js | 321 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 321 insertions(+) create mode 100644 platforms/Nest.js diff --git a/platforms/Nest.js b/platforms/Nest.js new file mode 100644 index 0000000..3d5d41a --- /dev/null +++ b/platforms/Nest.js @@ -0,0 +1,321 @@ +var types = require("HAP-NodeJS/accessories/types.js"); +var nest = require('unofficial-nest-api'); + +function NestPlatform(log, config){ + // auth info + this.username = config["username"]; + this.password = config["password"]; + + this.log = log; +} + +NestPlatform.prototype = { + accessories: function(callback) { + this.log("Fetching Nest devices."); + + var that = this; + var foundAccessories = []; + + nest.login(this.username, this.password, function (err, data) { + if (err) { + that.log("There was a problem authenticating with Nest."); + } + else { + nest.fetchStatus(function (data) { + for (var deviceId in data.device) { + if (data.device.hasOwnProperty(deviceId)) { + device = data.device[deviceId]; + // it's a thermostat, adjust this to detect other accessories + if (data.shared[deviceId].hasOwnProperty('current_temperature')) + { + name = data.shared[deviceId].name + accessory = new NestThermostatAccessory(that.log, name, device, deviceId); + foundAccessories.push(accessory); + } + } + } + callback(foundAccessories) + }); + } + }); + } +} + +function NestThermostatAccessory(log, name, device, deviceId) { + // device info + this.name = name; + this.model = device.model_version; + this.serial = device.serial_number; + this.deviceId = deviceId; + this.log = log; +} + +NestThermostatAccessory.prototype = { + getCurrentHeatingCooling: function(callback){ + + var that = this; + + this.log("Checking current heating cooling for: " + this.name); + nest.fetchStatus(function (data) { + device = data.device[that.deviceId]; + that.log("Current healing cooling for " + that.name + " is: " + device.current_schedule_mode) + currentHeatingCooling = 0; + switch(device.current_schedule_mode) { + case "HEAT": + currentHeatingCooling = 1; + break; + // this is a guess, I don't have AC to test out the response + case "COOL": + currentHeatingCooling = 2; + break; + default: + currentHeatingCooling = 0; + } + callback(currentHeatingCooling); + }); + + + }, + + getCurrentTemperature: function(callback){ + + var that = this; + + nest.fetchStatus(function (data) { + device = data.shared[that.deviceId]; + that.log("Current temperature for " + this.name + " is: " + device.current_temperature); + callback(device.current_temperature); + }); + + + }, + + getTargetTemperature: function(callback){ + + var that = this; + + nest.fetchStatus(function (data) { + device = data.shared[that.deviceId]; + that.log("Target temperature for " + this.name + " is: " + device.target_temperature); + callback(device.target_temperature); + }); + + + }, + + getTemperatureUnits: function(callback){ + + var that = this; + + nest.fetchStatus(function (data) { + device = data.device[that.deviceId]; + temperatureUnits = 0; + switch(device.temperature_scale) { + case "F": + that.log("Tempature unit for " + this.name + " is: " + "Fahrenheit"); + temperatureUnits = 1; + break; + case "C": + that.log("Tempature unit for " + this.name + " is: " + "Celsius"); + temperatureUnits = 0; + break; + default: + temperatureUnits = 0; + } + + callback(temperatureUnits); + }); + + + }, + + getCurrentRelativeHumidity: function(callback){ + + var that = this; + + nest.fetchStatus(function (data) { + device = data.device[that.deviceId]; + that.log("Humidity for " + this.name + " is: " + device.current_humidity); + callback(device.current_humidity); + }) + + + }, + + setTargetTemperature: function(targetTemperature){ + + var that = this; + + this.log("Setting target temperature for " + this.name + " to: " + targetTemperature); + nest.setTemperature(this.deviceId, targetTemperature); + + + }, + + getServices: function() { + var that = this; + return [{ + sType: types.ACCESSORY_INFORMATION_STYPE, + characteristics: [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of the accessory", + designedMaxLength: 255 + },{ + cType: types.MANUFACTURER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: "Nest", + supportEvents: false, + supportBonjour: false, + manfDescription: "Manufacturer", + designedMaxLength: 255 + },{ + cType: types.MODEL_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.model, + supportEvents: false, + supportBonjour: false, + manfDescription: "Model", + designedMaxLength: 255 + },{ + cType: types.SERIAL_NUMBER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.serial, + supportEvents: false, + supportBonjour: false, + manfDescription: "SN", + designedMaxLength: 255 + },{ + cType: types.IDENTIFY_CTYPE, + onUpdate: null, + perms: ["pw"], + format: "bool", + initialValue: false, + supportEvents: false, + supportBonjour: false, + manfDescription: "Identify Accessory", + designedMaxLength: 1 + }] + },{ + sType: types.THERMOSTAT_STYPE, + characteristics: [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of thermostat", + designedMaxLength: 255 + },{ + cType: types.CURRENTHEATINGCOOLING_CTYPE, + onUpdate: null, + onRead: function(callback) { + that.getCurrentHeatingCooling(function(coolingType){ + callback(coolingType); + }); + }, + perms: ["pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Mode", + designedMaxLength: 1, + designedMinValue: 0, + designedMaxValue: 2, + designedMinStep: 1, + },{ + cType: types.TARGETHEATINGCOOLING_CTYPE, + onUpdate: null, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Target Mode", + designedMinValue: 0, + designedMaxValue: 3, + designedMinStep: 1, + },{ + cType: types.CURRENT_TEMPERATURE_CTYPE, + onUpdate: null, + onRead: function(callback) { + that.getCurrentTemperature(function(currentTemperature){ + callback(currentTemperature); + }); + }, + perms: ["pr","ev"], + format: "int", + initialValue: 20, + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Temperature", + unit: "celsius" + },{ + cType: types.TARGET_TEMPERATURE_CTYPE, + onUpdate: function(value) { + that.setTargetTemperature(value); + }, + onRead: function(callback) { + that.getTargetTemperature(function(targetTemperature){ + callback(targetTemperature); + }); + }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 20, + supportEvents: false, + supportBonjour: false, + manfDescription: "Target Temperature", + designedMinValue: 16, + designedMaxValue: 38, + designedMinStep: 1, + unit: "celsius" + },{ + cType: types.TEMPERATURE_UNITS_CTYPE, + onUpdate: null, + onRead: function(callback) { + that.getTemperatureUnits(function(temperatureUnits){ + callback(temperatureUnits); + }); + }, + perms: ["pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Unit", + },{ + cType: types.CURRENT_RELATIVE_HUMIDITY_CTYPE, + onUpdate: null, + onRead: function(callback) { + that.getCurrentRelativeHumidity(function(currentRelativeHumidity){ + callback(currentRelativeHumidity); + }); + }, + perms: ["pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Humidity", + }] + }]; + } +} + +module.exports.accessory = NestThermostatAccessory; +module.exports.platform = NestPlatform; \ No newline at end of file From 81cc689b7e8f3f0f92d7bfc64e7d155d422a413a Mon Sep 17 00:00:00 2001 From: Chloe Stars Date: Fri, 10 Jul 2015 23:53:57 -0700 Subject: [PATCH 2/6] Add the required module to package.json. --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index da1e577..e06a339 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "wink-js": "0.0.5", "elkington": "kevinohara80/elkington", "node-milight-promise": "0.0.2", - "telldus-live" : "0.2.x" + "telldus-live" : "0.2.x", + "unofficial-nest-api": "0.1.x" } } From 4aafdaea9b319a241a96c091ea34e944f040e8bc Mon Sep 17 00:00:00 2001 From: Chloe Stars Date: Fri, 10 Jul 2015 23:58:41 -0700 Subject: [PATCH 3/6] Update the sample config file with Nest platform. --- config-sample.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/config-sample.json b/config-sample.json index 201c8f1..01764f2 100644 --- a/config-sample.json +++ b/config-sample.json @@ -2,6 +2,12 @@ "description": "This is an example configuration file with all supported devices. You can use this as a template for creating your own configuration file containing devices you actually own.", "platforms": [ + { + "platform" : "Nest", + "name" : "Nest", + "username" : "username", + "password" : "password" + }, { "platform" : "TelldusLive", "name" : "Telldus Live!", From 91602e4f9983b8b30750be10fe73e299579e5acb Mon Sep 17 00:00:00 2001 From: Chloe Stars Date: Mon, 13 Jul 2015 14:58:39 -0700 Subject: [PATCH 4/6] Fix heating and cooling. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It works but the API doesn’t reflect the change soon enough for HomeKit to be aware it worked. Not sure what to do here. --- platforms/Nest.js | 83 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 5 deletions(-) diff --git a/platforms/Nest.js b/platforms/Nest.js index 3d5d41a..22d47da 100644 --- a/platforms/Nest.js +++ b/platforms/Nest.js @@ -2,6 +2,7 @@ var types = require("HAP-NodeJS/accessories/types.js"); var nest = require('unofficial-nest-api'); function NestPlatform(log, config){ + // auth info this.username = config["username"]; this.password = config["password"]; @@ -58,25 +59,61 @@ NestThermostatAccessory.prototype = { this.log("Checking current heating cooling for: " + this.name); nest.fetchStatus(function (data) { device = data.device[that.deviceId]; - that.log("Current healing cooling for " + that.name + " is: " + device.current_schedule_mode) + currentHeatingCooling = 0; switch(device.current_schedule_mode) { + case "OFF": + targetHeatingCooling = 0; + break; case "HEAT": currentHeatingCooling = 1; break; - // this is a guess, I don't have AC to test out the response case "COOL": currentHeatingCooling = 2; break; + case "RANGE": + currentHeatingCooling = 3; + break; default: currentHeatingCooling = 0; } + that.log("Current heating for " + this.name + "is: " + currentHeatingCooling); callback(currentHeatingCooling); }); }, + getTargetHeatingCoooling: function(callback){ + + var that = this; + + this.log("Checking target heating cooling for: " + this.name); + nest.fetchStatus(function (data) { + device = data.device[that.deviceId]; + + targetHeatingCooling = 0; + switch(device.target_temperature_type) { + case "off": + targetHeatingCooling = 0; + break; + case "heat": + targetHeatingCooling = 1; + break; + case "cool": + targetHeatingCooling = 2; + break; + case "range": + targetHeatingCooling = 3; + break; + default: + targetHeatingCooling = 0; + } + that.log("Current target heating for " + this.name + " is: " + targetHeatingCooling); + callback(targetHeatingCooling); + }); + }, + getCurrentTemperature: function(callback){ var that = this; @@ -140,6 +177,35 @@ NestThermostatAccessory.prototype = { }) + }, + + setTargetHeatingCooling: function(targetHeatingCooling){ + + var that = this; + + targetTemperatureType = 'off'; + switch(targetHeatingCooling) { + case 0: + // this will crash unnofficial-node-api, it needs to be forked to accept the input + targetTemperatureType = 'off'; + break; + case 1: + targetTemperatureType = 'heat'; + break; + case 2: + targetTemperatureType = 'cool'; + break; + case 3: + targetTemperatureType = 'range'; + break; + default: + targetTemperatureType = 'off'; + } + + this.log("Setting target heating cooling for " + this.name + " to: " + targetTemperatureType); + nest.setTargetTemperatureType(this.deviceId, targetTemperatureType); + + }, setTargetTemperature: function(targetTemperature){ @@ -223,8 +289,8 @@ NestThermostatAccessory.prototype = { cType: types.CURRENTHEATINGCOOLING_CTYPE, onUpdate: null, onRead: function(callback) { - that.getCurrentHeatingCooling(function(coolingType){ - callback(coolingType); + that.getCurrentHeatingCooling(function(currentHeatingCooling){ + callback(currentHeatingCooling); }); }, perms: ["pr","ev"], @@ -239,7 +305,14 @@ NestThermostatAccessory.prototype = { designedMinStep: 1, },{ cType: types.TARGETHEATINGCOOLING_CTYPE, - onUpdate: null, + onUpdate: function(value) { + that.setTargetHeatingCooling(value); + }, + onRead: function(callback) { + that.getTargetHeatingCoooling(function(targetHeatingCooling){ + callback(targetHeatingCooling); + }); + }, perms: ["pw","pr","ev"], format: "int", initialValue: 0, From c4f9e81828171119c4396bad24457d17820524a0 Mon Sep 17 00:00:00 2001 From: Chloe Stars Date: Fri, 17 Jul 2015 01:08:50 -0700 Subject: [PATCH 5/6] Oops, made a rookie mistake. Make sure I'm using local variables instead of global variables. --- platforms/Nest.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/platforms/Nest.js b/platforms/Nest.js index 22d47da..117e4b8 100644 --- a/platforms/Nest.js +++ b/platforms/Nest.js @@ -25,12 +25,12 @@ NestPlatform.prototype = { nest.fetchStatus(function (data) { for (var deviceId in data.device) { if (data.device.hasOwnProperty(deviceId)) { - device = data.device[deviceId]; + var device = data.device[deviceId]; // it's a thermostat, adjust this to detect other accessories if (data.shared[deviceId].hasOwnProperty('current_temperature')) { - name = data.shared[deviceId].name - accessory = new NestThermostatAccessory(that.log, name, device, deviceId); + var name = data.shared[deviceId].name + var accessory = new NestThermostatAccessory(that.log, name, device, deviceId); foundAccessories.push(accessory); } } @@ -58,9 +58,9 @@ NestThermostatAccessory.prototype = { this.log("Checking current heating cooling for: " + this.name); nest.fetchStatus(function (data) { - device = data.device[that.deviceId]; + var device = data.device[that.deviceId]; - currentHeatingCooling = 0; + var currentHeatingCooling = 0; switch(device.current_schedule_mode) { case "OFF": targetHeatingCooling = 0; @@ -90,9 +90,9 @@ NestThermostatAccessory.prototype = { this.log("Checking target heating cooling for: " + this.name); nest.fetchStatus(function (data) { - device = data.device[that.deviceId]; + var device = data.device[that.deviceId]; - targetHeatingCooling = 0; + var targetHeatingCooling = 0; switch(device.target_temperature_type) { case "off": targetHeatingCooling = 0; @@ -119,7 +119,7 @@ NestThermostatAccessory.prototype = { var that = this; nest.fetchStatus(function (data) { - device = data.shared[that.deviceId]; + var device = data.shared[that.deviceId]; that.log("Current temperature for " + this.name + " is: " + device.current_temperature); callback(device.current_temperature); }); @@ -132,7 +132,7 @@ NestThermostatAccessory.prototype = { var that = this; nest.fetchStatus(function (data) { - device = data.shared[that.deviceId]; + var device = data.shared[that.deviceId]; that.log("Target temperature for " + this.name + " is: " + device.target_temperature); callback(device.target_temperature); }); @@ -145,8 +145,8 @@ NestThermostatAccessory.prototype = { var that = this; nest.fetchStatus(function (data) { - device = data.device[that.deviceId]; - temperatureUnits = 0; + var device = data.device[that.deviceId]; + var temperatureUnits = 0; switch(device.temperature_scale) { case "F": that.log("Tempature unit for " + this.name + " is: " + "Fahrenheit"); @@ -171,7 +171,7 @@ NestThermostatAccessory.prototype = { var that = this; nest.fetchStatus(function (data) { - device = data.device[that.deviceId]; + var device = data.device[that.deviceId]; that.log("Humidity for " + this.name + " is: " + device.current_humidity); callback(device.current_humidity); }) @@ -183,7 +183,7 @@ NestThermostatAccessory.prototype = { var that = this; - targetTemperatureType = 'off'; + var targetTemperatureType = 'off'; switch(targetHeatingCooling) { case 0: // this will crash unnofficial-node-api, it needs to be forked to accept the input From 511b1873f753bd79b3e2c1a507410398c8c90f48 Mon Sep 17 00:00:00 2001 From: Chloe Stars Date: Fri, 17 Jul 2015 18:29:23 -0700 Subject: [PATCH 6/6] I forked unofficial-nest-api and added support for 'off'. For now the package location has been changed to reflect this. I've submitted a pull request but it doesn't look like it's been touched in a while so I'm not sure it's going to get merged. --- package.json | 2 +- platforms/Nest.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index bd18348..b55bbd6 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "request": "2.49.x", "sonos": "0.8.x", "telldus-live": "0.2.x", - "unofficial-nest-api": "0.1.x", + "unofficial-nest-api": "git+https://github.com/hachidorii/unofficial_nodejs_nest.git#d8d48edc952b049ff6320ef99afa7b2f04cdee98", "wemo": "0.2.x", "wink-js": "0.0.5", "xml2js": "0.4.x", diff --git a/platforms/Nest.js b/platforms/Nest.js index 117e4b8..414fcef 100644 --- a/platforms/Nest.js +++ b/platforms/Nest.js @@ -186,7 +186,6 @@ NestThermostatAccessory.prototype = { var targetTemperatureType = 'off'; switch(targetHeatingCooling) { case 0: - // this will crash unnofficial-node-api, it needs to be forked to accept the input targetTemperatureType = 'off'; break; case 1: