├── .gitignore ├── package.json ├── lib ├── nest-cam-accessory.js ├── nest-connection.js ├── nest-protect-accessory.js ├── nest-device-accessory.js └── nest-thermostat-accessory.js ├── README.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | *.swp 4 | *.rej 5 | *.orig 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "homebridge-nest", 3 | "description": "Nest plugin for homebridge", 4 | "version": "1.1.2", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/kraigm/homebridge-nest.git" 8 | }, 9 | "license": "ISC", 10 | "preferGlobal": true, 11 | "keywords": [ 12 | "homebridge-plugin", 13 | "nest" 14 | ], 15 | "engines": { 16 | "node": ">=0.12.0", 17 | "homebridge": ">=0.2.5" 18 | }, 19 | "dependencies": { 20 | "firebase": "^2.3.2", 21 | "bluebird": "^3.2.2", 22 | "request-promise": "^1.0.2", 23 | "unofficial-nest-api": "git+https://github.com/kraigm/unofficial_nodejs_nest.git#3cbd337adc32fab3b481659b38d86f9fcd6a9c02" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/nest-cam-accessory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kraig on 3/11/16. 3 | */ 4 | 5 | var inherits = require('util').inherits; 6 | var Service, Characteristic; 7 | var NestDeviceAccessory = require('./nest-device-accessory')(); 8 | 9 | 'use strict'; 10 | 11 | module.exports = function(exportedTypes) { 12 | if (exportedTypes && !Service) { 13 | Service = exportedTypes.Service; 14 | Characteristic = exportedTypes.Characteristic; 15 | 16 | var acc = NestCamAccessory.prototype; 17 | inherits(NestCamAccessory, NestDeviceAccessory); 18 | NestCamAccessory.prototype.parent = NestDeviceAccessory.prototype; 19 | for (var mn in acc) { 20 | NestCamAccessory.prototype[mn] = acc[mn]; 21 | } 22 | 23 | NestCamAccessory.deviceType = 'cam'; 24 | NestCamAccessory.deviceGroup = 'cameras'; 25 | NestCamAccessory.prototype.deviceType = NestCamAccessory.deviceType; 26 | NestCamAccessory.prototype.deviceGroup = NestCamAccessory.deviceGroup; 27 | } 28 | return NestCamAccessory; 29 | }; 30 | 31 | function NestCamAccessory(conn, log, device, structure) { 32 | NestDeviceAccessory.call(this, conn, log, device, structure); 33 | 34 | var motionSvc = this.addService(Service.MotionSensor); 35 | this.bindCharacteristic(motionSvc, Characteristic.MotionDetected, "Motion", 36 | getMotionDetectionState.bind(this), null, formatMotionDetectionState.bind(this)); 37 | 38 | this.addAwayCharacteristic(motionSvc); 39 | 40 | this.updateData(); 41 | } 42 | 43 | 44 | // --- MotionDetectionState --- 45 | 46 | var getMotionDetectionState = function() { 47 | return this.device.last_event && 48 | this.device.last_event.has_motion && 49 | !this.device.last_event.end_time; 50 | }; 51 | 52 | var formatMotionDetectionState = function(val) { 53 | if (val) { 54 | return "detected"; 55 | } else { 56 | return "not detected"; 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /lib/nest-connection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kraigm on 12/15/15. 3 | */ 4 | 5 | var Promise = require('bluebird'); 6 | var rp = require('request-promise'); 7 | var Firebase = require("firebase"); 8 | 9 | 'use strict'; 10 | 11 | module.exports = Connection; 12 | 13 | var logPrefix = "[NestFirebase] "; 14 | 15 | function Connection(token, log) { 16 | this.token = token; 17 | this.log = function(info) { 18 | log(logPrefix + info); 19 | }; 20 | this.debug = function(info) { 21 | log.debug(logPrefix + info); 22 | }; 23 | this.error = function(info) { 24 | log.error(logPrefix + info); 25 | }; 26 | } 27 | 28 | Connection.prototype.auth = function(clientId, clientSecret, code) { 29 | return rp({ 30 | method: 'POST', 31 | uri: 'https://api.home.nest.com/oauth2/access_token', 32 | form: { 33 | client_id: clientId, 34 | client_secret: clientSecret, 35 | code: code, 36 | grant_type: "authorization_code" 37 | } 38 | }) 39 | .then(function (parsedBody) { 40 | var body = JSON.parse(parsedBody); 41 | this.token = body.access_token; 42 | return this.token; 43 | }.bind(this)); 44 | }; 45 | 46 | var authAsync = function() { 47 | return Promise.fromCallback(this.conn.authWithCustomToken.bind(this.conn, this.token)); 48 | }; 49 | 50 | // Create a callback which logs the current auth state 51 | var authDataCallback = function(authData) { 52 | if (authData) { 53 | this.debug("User " + authData.uid + " is logged in with " + authData.provider); 54 | } else { 55 | this.debug("User is logged out"); 56 | reauthAsync.bind(this)(); 57 | } 58 | }; 59 | 60 | var reauthAsync = function() { 61 | // If already reauthorizing, return existing 62 | if (this.authTask) return this.authTask; 63 | 64 | var self = this; 65 | 66 | // Loop that continues until connection is successful 67 | var reauthLoopAsync = function(err) { 68 | if (err) self.error("Reauthorizing error : " + (err.stack || err.message || err)); 69 | self.debug("Delaying next reauthorization attempt (5s)"); 70 | return Promise.delay(5000) 71 | .then(function() { 72 | // Attempts to reauthorize Firebase connection 73 | self.log("Reauthorizing connection"); 74 | return authAsync.call(self); 75 | }) 76 | .catch(reauthLoopAsync); 77 | }; 78 | 79 | // Create loop and clean up when complete 80 | return self.authTask || (self.authTask = reauthLoopAsync() 81 | .finally(function() { self.authTask = null; })); 82 | }; 83 | 84 | Connection.prototype.open = function() { 85 | if (!this.token) { 86 | return Promise.reject(new Error("You must provide a token or auth before you can open a connection.")); 87 | } 88 | 89 | this.conn = new Firebase('wss://developer-api.nest.com', new Firebase.Context()); 90 | return authAsync.call(this) 91 | .then(function() { 92 | // Register the callback to be fired every time auth state changes 93 | this.conn.onAuth(authDataCallback.bind(this)); 94 | return this; 95 | }.bind(this)); 96 | }; 97 | 98 | Connection.prototype.isOpen = function() { 99 | return this.conn ? true : false; 100 | }; 101 | 102 | Connection.prototype.subscribe = function(handler) { 103 | var self = this; 104 | return new Promise(function (resolve, reject) { 105 | if (!handler){ 106 | reject(new Error("You must specify a handler")) 107 | } else { 108 | var notify = resolve || handler; 109 | this.conn.on('value', function (snapshot) { 110 | var data = snapshot.val(); 111 | if (data) { 112 | notify(data); 113 | notify = handler; 114 | } else { 115 | self.log("Disconnect Detected"); 116 | } 117 | }); 118 | } 119 | }.bind(this)); 120 | }; 121 | 122 | Connection.prototype.update = function(path, data) { 123 | var child = this.conn.child(path); 124 | return Promise.fromCallback(child.set.bind(child, data)); 125 | }; 126 | -------------------------------------------------------------------------------- /lib/nest-protect-accessory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kraig on 3/11/16. 3 | */ 4 | 5 | var inherits = require('util').inherits; 6 | var Accessory, Service, Characteristic, Away, uuid; 7 | var NestDeviceAccessory = require('./nest-device-accessory')(); 8 | 9 | var AlarmState = { 10 | ok: 1, 11 | warning: 2, 12 | emergency: 3 13 | }; 14 | 15 | 'use strict'; 16 | 17 | module.exports = function(exportedTypes) { 18 | if (exportedTypes && !Accessory) { 19 | Accessory = exportedTypes.Accessory; 20 | Service = exportedTypes.Service; 21 | Characteristic = exportedTypes.Characteristic; 22 | uuid = exportedTypes.uuid; 23 | Away = exportedTypes.Away; 24 | 25 | var acc = NestProtectAccessory.prototype; 26 | inherits(NestProtectAccessory, NestDeviceAccessory); 27 | NestProtectAccessory.prototype.parent = NestDeviceAccessory.prototype; 28 | for (var mn in acc) { 29 | NestProtectAccessory.prototype[mn] = acc[mn]; 30 | } 31 | 32 | NestProtectAccessory.deviceType = 'protect'; 33 | NestProtectAccessory.deviceGroup = 'smoke_co_alarms'; 34 | NestProtectAccessory.prototype.deviceType = NestProtectAccessory.deviceType; 35 | NestProtectAccessory.prototype.deviceGroup = NestProtectAccessory.deviceGroup; 36 | } 37 | return NestProtectAccessory; 38 | }; 39 | 40 | function NestProtectAccessory(conn, log, device, structure) { 41 | NestDeviceAccessory.call(this, conn, log, device, structure); 42 | 43 | var smokeSvc = this.addService(Service.SmokeSensor); 44 | this.bindCharacteristic(smokeSvc, Characteristic.SmokeDetected, "Smoke", 45 | getSmokeAlarmState.bind(this), null, formatSmokeAlarmState.bind(this)); 46 | this.bindCharacteristic(smokeSvc, Characteristic.StatusLowBattery, "Battery status (Smoke)", 47 | getBatteryHealth.bind(this), null, formatStatusLowBattery.bind(this)); 48 | 49 | var coSvc = this.addService(Service.CarbonMonoxideSensor); 50 | this.bindCharacteristic(coSvc, Characteristic.CarbonMonoxideDetected, "Carbon Monoxide", 51 | getCarbonMonoxideAlarmState.bind(this), null, formatCarbonMonoxideAlarmState.bind(this)); 52 | this.bindCharacteristic(coSvc, Characteristic.StatusLowBattery, "Battery status (CO)", 53 | getBatteryHealth.bind(this), null, formatStatusLowBattery.bind(this)); 54 | 55 | this.updateData(); 56 | } 57 | 58 | 59 | // --- SmokeAlarmState --- 60 | 61 | var getSmokeAlarmState = function() { 62 | switch (AlarmState[this.device.smoke_alarm_state]) { 63 | case AlarmState.ok: 64 | return Characteristic.SmokeDetected.SMOKE_NOT_DETECTED; 65 | default: 66 | return Characteristic.SmokeDetected.SMOKE_DETECTED; 67 | } 68 | }; 69 | 70 | var formatSmokeAlarmState = function(val) { 71 | switch (val) { 72 | case Characteristic.SmokeDetected.SMOKE_NOT_DETECTED: 73 | return "not detected"; 74 | case Characteristic.SmokeDetected.SMOKE_DETECTED: 75 | return "detected"; 76 | default: 77 | return "unknown (" + val + ")"; 78 | } 79 | }; 80 | 81 | 82 | // --- CarbonMonoxideAlarmState --- 83 | 84 | var getCarbonMonoxideAlarmState = function() { 85 | switch (AlarmState[this.device.co_alarm_state]) { 86 | case AlarmState.ok: 87 | return Characteristic.CarbonMonoxideDetected.CO_LEVELS_NORMAL; 88 | default: 89 | return Characteristic.CarbonMonoxideDetected.CO_LEVELS_ABNORMAL; 90 | } 91 | }; 92 | 93 | var formatCarbonMonoxideAlarmState = function(val) { 94 | switch (val) { 95 | case Characteristic.CarbonMonoxideDetected.CO_LEVELS_NORMAL: 96 | return "normal"; 97 | case Characteristic.CarbonMonoxideDetected.CO_LEVELS_ABNORMAL: 98 | return "abnormal"; 99 | default: 100 | return "unknown (" + val + ")"; 101 | } 102 | }; 103 | 104 | 105 | // --- BatteryHealth --- 106 | 107 | var getBatteryHealth = function () { 108 | switch (this.device.battery_health) { 109 | case 'ok': 110 | return Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; 111 | default: 112 | return Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW; 113 | } 114 | }; 115 | 116 | var formatStatusLowBattery = function (val) { 117 | switch (val) { 118 | case Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL: 119 | return 'normal'; 120 | case Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW: 121 | return 'low'; 122 | default: 123 | return 'unknown (' + val + ')'; 124 | } 125 | }; 126 | -------------------------------------------------------------------------------- /lib/nest-device-accessory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kraig on 3/11/16. 3 | */ 4 | 5 | var inherits = require('util').inherits; 6 | var Promise = require('bluebird'); 7 | var Accessory, Service, Characteristic, Away, uuid; 8 | 9 | 'use strict'; 10 | 11 | module.exports = function(exportedTypes) { 12 | if (exportedTypes && !Accessory) { 13 | Accessory = exportedTypes.Accessory; 14 | Service = exportedTypes.Service; 15 | Characteristic = exportedTypes.Characteristic; 16 | uuid = exportedTypes.uuid; 17 | Away = exportedTypes.Away; 18 | 19 | var acc = NestDeviceAccessory.prototype; 20 | inherits(NestDeviceAccessory, Accessory); 21 | NestDeviceAccessory.prototype.parent = Accessory.prototype; 22 | for (var mn in acc) { 23 | NestDeviceAccessory.prototype[mn] = acc[mn]; 24 | } 25 | } 26 | return NestDeviceAccessory; 27 | }; 28 | 29 | // Base type for Nest devices 30 | function NestDeviceAccessory(conn, log, device, structure) { 31 | 32 | // device info 33 | this.conn = conn; 34 | this.name = device.name_long || device.name; 35 | this.deviceId = device.device_id; 36 | this.log = log; 37 | this.device = device; 38 | this.structure = structure; 39 | this.structureId = structure.structure_id; 40 | 41 | var id = uuid.generate('nest.' + this.deviceType + '.' + this.deviceId); 42 | Accessory.call(this, this.name, id); 43 | this.uuid_base = id; 44 | 45 | var infoSvc = this.getService(Service.AccessoryInformation) 46 | .setCharacteristic(Characteristic.Model, "Nest " + this.deviceType) 47 | .setCharacteristic(Characteristic.SerialNumber, " ") 48 | .setCharacteristic(Characteristic.Manufacturer, "Nest"); 49 | 50 | this.boundCharacteristics = []; 51 | 52 | this.bindCharacteristic(infoSvc, Characteristic.SoftwareRevision, "Software version", function() { 53 | return this.device.software_version; 54 | }); 55 | 56 | this.updateData(); 57 | } 58 | 59 | NestDeviceAccessory.prototype.getServices = function () { 60 | return this.services; 61 | }; 62 | 63 | NestDeviceAccessory.prototype.bindCharacteristic = function (service, characteristic, desc, getFunc, setFunc, format) { 64 | var actual = service.getCharacteristic(characteristic) 65 | .on('get', function (callback) { 66 | var val = getFunc.bind(this)(); 67 | if (callback) callback(null, val); 68 | }.bind(this)) 69 | .on('change', function (change) { 70 | var disp = change.newValue; 71 | if (format && disp != null) { 72 | disp = format(disp); 73 | } 74 | this.log(desc + " for " + this.name + " is: " + disp); 75 | }.bind(this)); 76 | if (setFunc) { 77 | actual.on('set', setFunc.bind(this)); 78 | } 79 | this.boundCharacteristics.push([service, characteristic]); 80 | }; 81 | 82 | NestDeviceAccessory.prototype.updateData = function (device, structure) { 83 | if (device) { 84 | this.device = device; 85 | } 86 | if (structure) { 87 | this.structure = structure; 88 | } 89 | this.boundCharacteristics.map(function (c) { 90 | c[0].getCharacteristic(c[1]).getValue(); 91 | }); 92 | }; 93 | 94 | NestDeviceAccessory.prototype.getDevicePropertyPath = function(property) { 95 | return 'devices/' + this.deviceGroup + '/' + this.deviceId + '/' + property; 96 | }; 97 | 98 | NestDeviceAccessory.prototype.updateDevicePropertyAsync = function(property, value, propertyDescription, valueDescription) { 99 | propertyDescription = propertyDescription || property; 100 | valueDescription = valueDescription || value; 101 | this.log("Setting " + propertyDescription + " for " + this.name + " to: " + valueDescription); 102 | return this.conn.update(this.getDevicePropertyPath(property), value) 103 | .return(value); 104 | }; 105 | 106 | NestDeviceAccessory.prototype.getStructurePropertyPath = function(property) { 107 | return 'structures/' + this.structureId + '/' + property; 108 | }; 109 | 110 | NestDeviceAccessory.prototype.isAway = function () { 111 | switch (this.structure.away) { 112 | case "home": 113 | return Away.HOME; 114 | case "away": 115 | case "auto-away": 116 | return Away.AWAY; 117 | default: 118 | return Away.HOME; 119 | } 120 | }; 121 | 122 | NestDeviceAccessory.prototype.isAutoAway = function () { 123 | return this.structure.away == "auto-away"; 124 | }; 125 | 126 | NestDeviceAccessory.prototype.cancelAutoAway = function () { 127 | return this.isAutoAway() ? this.setAway(false) : Promise.resolve(); 128 | }; 129 | 130 | NestDeviceAccessory.prototype.setAway = function (away, callback) { 131 | var val = away ? 'away' : 'home'; 132 | this.log("Setting Away for " + this.name + " to: " + val); 133 | var promise = this.conn.update(this.getStructurePropertyPath("away"), val); 134 | return promise 135 | .return(away) 136 | .asCallback(callback); 137 | }; 138 | 139 | NestDeviceAccessory.prototype.addAwayCharacteristic = function(service) { 140 | service.addCharacteristic(Away); 141 | this.bindCharacteristic(service, Away, "Away", this.isAway, this.setAway); 142 | }; 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # homebridge-nest 2 | Nest plugin for [HomeBridge](https://github.com/nfarina/homebridge) 3 | 4 | This repository contains the Nest plugin for homebridge that was previously bundled in the main `homebridge` repository. 5 | 6 | # Installation 7 | 8 | 9 | 1. Install homebridge using: npm install -g homebridge 10 | 2. Install this plugin using: npm install -g homebridge-nest 11 | 3. Update your configuration file. See sample-config.json snippet below. 12 | 13 | It is **Strongly advised that you switch to the new API** but it is not required at the moment. It will fall back to the old API, but **no new development will be done on the old API**. 14 | 15 | Until an alternative is determined (like Nest Weave which hasn't been released yet or setting up a website for generating tokens specifically for HomeBridge-Nest), you will have to setup an developer account for Nest. Its a simple process and if you specify that it is for Individual, then you are auto approved (at least in my experience). 16 | 17 | _WARNING: Switching to the new API means it will show up as brand new device. This is do to the fact that the unofficial API used a different device id and we have no way to link it to the official cloud device id. This means any configurations, alarms, scenes, etc to which the Nest was associated will have need to be updated with the new Nest device._ 18 | 19 | _Note: The name of the device will change as well. It matches the name displayed in the Nest app. In my case, I originally configured the Nest app so the the "Where" of my Nest was "Hallway" and I also added a label which was "Nest", so the display was "Hallway (Nest)". To fix the name to say "Nest", you can use the Nest app and blank out the "Label" and use the custom "Where" of "Nest". Anther option to fix the name is through HomeKit. HomeKit allows you to rename Accessories and Services, but it requires an app like [Insteon+](https://itunes.apple.com/us/app/insteon+/id919270334?uo=2&at=11la2C) that has the ability to change the name._ 20 | 21 | 22 | ## How to Setup New API 23 | 24 | 1. Go to [https://developer.nest.com](https://developer.nest.com) 25 | 2. Choose **Sign In** 26 | 3. Use your normal account to sign in 27 | 4. Fill in you info in 'Step 1' 28 | 5. In 'Step 2' set: 29 | * **Company Name**: _HomeBridge-Nest_ 30 | * **Company URL**: _https://github.com/kraigm/homebridge-nest_ 31 | * **Country**: _[Your Country]_ 32 | * **Size of Company**: _Individual_ 33 | 6. Then just agree to the terms and submit 34 | 7. Go to **Products** and create a new product 35 | 8. Fill in: 36 | * **Product Name**: _HomeBridge_ 37 | * **Description**: _Open source project to provide HomeKit integration_ 38 | * **Categories**: _HomeAutomation_ 39 | * **Support URL**: _https://github.com/kraigm/homebridge-nest_ 40 | * **Redirect URL**: _[LEAVE BLANK]_ 41 | * **Permissions (minimum)**: 42 | * Enable **Thermostat** with **read/write v4** 43 | * Enable **Away** with **read/write v2** 44 | * Enable **Smoke+CO alarm** with **read v4** (if you ever might want Nest Protect) 45 | * Enable **Camera** with **read v2** (if you ever might want Nest Cam, motion detection only) 46 | 9. Now you should have a product. Now locate the **Keys** section on the right of your product's page 47 | 10. Copy the **Product ID** to your HomeBridge config as the **clientId** in the Nest config 48 | 11. Copy the **Product Secret** to your HomeBridge config as the **clientSecret** in the Nest config 49 | 12. Navigate to the **Authorization URL** 50 | 13. Accept the terms and copy the **Pin Code** to your HomeBridge config as the **code** in the Nest config 51 | 14. Run HomeBridge once _(do not include the **token** in the config at this time)_ and you should find a log that says something like _"CODE IS ONLY VALID ONCE! Update config to use {'token':'c.5ABsTpo88k5yfNIxZlh...'} instead."_ Copy the **_c.5ABsTpo88k5yfNIxZlh..._** portion to your HomeBridge config as the **token** in the Nest config 52 | 15. You should be able to **restart HomeBridge** and it should succeed with the new token. 53 | 54 | After that you will be **FINALLY** done (Huzzah!). If the token is working correctly, you no longer NEED the other three configs (clientId, clientSecret, and code) nor the original username and password from the legacy system (but you can keep them around if you wish, they will be ignored). 55 | 56 | 57 | 58 | 59 | # Configuration 60 | 61 | Configuration sample: 62 | 63 | ``` 64 | "platforms": [ 65 | { 66 | "platform": "Nest", 67 | 68 | "token" : "c.5ABsTpo88k5yfNIxZlh...", 69 | 70 | "clientId": "developer client id", 71 | "clientSecret": "developer client secret.", 72 | "code": "Pin Code", 73 | 74 | "username" : "username", 75 | "password" : "password" 76 | } 77 | ], 78 | 79 | ``` 80 | 81 | Fields: 82 | 83 | * "platform": Must always be "Nest" (required) 84 | * "token": The only (and final) authentication piece you need to use the new API (required for new api, after determined) 85 | 86 | * "clientId": Can be anything (required for new api, if token not yet determined) 87 | * "clientSecret": Can be anything (required for new api, if token not yet determined) 88 | * "code": Can be anything (required for new api if trying to determine token) 89 | 90 | 91 | Legacy Fields: 92 | 93 | * "username": Nest login username, same as app (required for legacy api) 94 | * "password": Nest login password, same as app (required for legacy api) 95 | 96 | -------------------------------------------------------------------------------- /lib/nest-thermostat-accessory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kraigm on 12/15/15. 3 | */ 4 | 5 | var inherits = require('util').inherits; 6 | var Accessory, Service, Characteristic, uuid; 7 | var NestDeviceAccessory = require('./nest-device-accessory')(); 8 | 9 | 'use strict'; 10 | 11 | module.exports = function(exportedTypes) { 12 | if (exportedTypes && !Accessory) { 13 | Accessory = exportedTypes.Accessory; 14 | Service = exportedTypes.Service; 15 | Characteristic = exportedTypes.Characteristic; 16 | uuid = exportedTypes.uuid; 17 | 18 | var acc = NestThermostatAccessory.prototype; 19 | inherits(NestThermostatAccessory, NestDeviceAccessory); 20 | NestThermostatAccessory.prototype.parent = NestDeviceAccessory.prototype; 21 | for (var mn in acc) { 22 | NestThermostatAccessory.prototype[mn] = acc[mn]; 23 | } 24 | 25 | NestThermostatAccessory.deviceType = 'thermostat'; 26 | NestThermostatAccessory.deviceGroup = 'thermostats'; 27 | NestThermostatAccessory.prototype.deviceType = NestThermostatAccessory.deviceType; 28 | NestThermostatAccessory.prototype.deviceGroup = NestThermostatAccessory.deviceGroup; 29 | } 30 | return NestThermostatAccessory; 31 | }; 32 | 33 | function NestThermostatAccessory(conn, log, device, structure) { 34 | 35 | NestDeviceAccessory.call(this, conn, log, device, structure); 36 | 37 | var thermostatService = this.addService(Service.Thermostat); 38 | 39 | var formatAsDisplayTemperature = function(t) { 40 | return this.usesFahrenheit() ? celsiusToFahrenheit(t) + " F" : t + " C"; 41 | }.bind(this); 42 | 43 | var formatHeatingCoolingState = function (val) { 44 | switch (val) { 45 | case Characteristic.CurrentHeatingCoolingState.OFF: 46 | return "Off"; 47 | case Characteristic.CurrentHeatingCoolingState.HEAT: 48 | return "Heating"; 49 | case Characteristic.CurrentHeatingCoolingState.COOL: 50 | return "Cooling"; 51 | case Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL: 52 | return "Heating/Cooling"; 53 | } 54 | }; 55 | 56 | var bindCharacteristic = function (characteristic, desc, getFunc, setFunc, format) { 57 | this.bindCharacteristic(thermostatService, characteristic, desc, getFunc, setFunc, format); 58 | }.bind(this); 59 | 60 | bindCharacteristic(Characteristic.TemperatureDisplayUnits, "Temperature unit", this.getTemperatureUnits, null, function (val) { 61 | return val == Characteristic.TemperatureDisplayUnits.FAHRENHEIT ? "Fahrenheit" : "Celsius"; 62 | }); 63 | 64 | bindCharacteristic(Characteristic.CurrentTemperature, "Current temperature", this.getCurrentTemperature, null, formatAsDisplayTemperature); 65 | bindCharacteristic(Characteristic.CurrentHeatingCoolingState, "Current heating", this.getCurrentHeatingCooling, null, formatHeatingCoolingState); 66 | bindCharacteristic(Characteristic.CurrentRelativeHumidity, "Current humidity", this.getCurrentRelativeHumidity, null, function(val) { 67 | return val + "%"; 68 | }); 69 | 70 | bindCharacteristic(Characteristic.TargetTemperature, "Target temperature", this.getTargetTemperature, this.setTargetTemperature, formatAsDisplayTemperature); 71 | bindCharacteristic(Characteristic.TargetHeatingCoolingState, "Target heating", this.getTargetHeatingCooling, this.setTargetHeatingCooling, formatHeatingCoolingState); 72 | 73 | this.addAwayCharacteristic(thermostatService); 74 | 75 | this.updateData(); 76 | } 77 | 78 | NestThermostatAccessory.prototype.getCurrentHeatingCooling = function () { 79 | switch (this.device.hvac_state) { 80 | case "off": 81 | return Characteristic.CurrentHeatingCoolingState.OFF; 82 | case "heating": 83 | return Characteristic.CurrentHeatingCoolingState.HEAT; 84 | case "cooling": 85 | return Characteristic.CurrentHeatingCoolingState.COOL; 86 | default: 87 | return Characteristic.CurrentHeatingCoolingState.OFF; 88 | } 89 | }; 90 | 91 | NestThermostatAccessory.prototype.getTargetHeatingCooling = function () { 92 | switch (this.device.hvac_mode) { 93 | case "off": 94 | return Characteristic.CurrentHeatingCoolingState.OFF; 95 | case "heat": 96 | return Characteristic.CurrentHeatingCoolingState.HEAT; 97 | case "cool": 98 | return Characteristic.CurrentHeatingCoolingState.COOL; 99 | case "heat-cool": 100 | return Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL; 101 | default: 102 | return Characteristic.CurrentHeatingCoolingState.OFF; 103 | } 104 | }; 105 | 106 | NestThermostatAccessory.prototype.getCurrentTemperature = function () { 107 | if (this.usesFahrenheit()) { 108 | return fahrenheitToCelsius(this.device.ambient_temperature_f); 109 | } else { 110 | return this.device.ambient_temperature_c; 111 | } 112 | }; 113 | 114 | NestThermostatAccessory.prototype.getCurrentRelativeHumidity = function () { 115 | return this.device.humidity; 116 | }; 117 | 118 | NestThermostatAccessory.prototype.getTargetTemperature = function () { 119 | switch (this.getTargetHeatingCooling()) { 120 | case Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL: 121 | // Choose closest target as single target 122 | var high, low; 123 | if (this.usesFahrenheit()) { 124 | high = fahrenheitToCelsius(this.device.target_temperature_high_f); 125 | low = fahrenheitToCelsius(this.device.target_temperature_low_f); 126 | } else { 127 | high = this.device.target_temperature_high_c; 128 | low = this.device.target_temperature_low_c; 129 | } 130 | var cur = this.getCurrentTemperature(); 131 | return Math.abs(high - cur) < Math.abs(cur - low) ? high : low; 132 | default: 133 | if (this.usesFahrenheit()) { 134 | return fahrenheitToCelsius(this.device.target_temperature_f); 135 | } else { 136 | return this.device.target_temperature_c; 137 | } 138 | } 139 | }; 140 | 141 | NestThermostatAccessory.prototype.getTemperatureUnits = function () { 142 | switch (this.device.temperature_scale) { 143 | case "F": 144 | return Characteristic.TemperatureDisplayUnits.FAHRENHEIT; 145 | case "C": 146 | return Characteristic.TemperatureDisplayUnits.CELSIUS; 147 | default: 148 | return Characteristic.TemperatureDisplayUnits.CELSIUS; 149 | } 150 | }; 151 | 152 | function fahrenheitToCelsius(temperature) { 153 | return (temperature - 32) / 1.8; 154 | } 155 | 156 | function celsiusToFahrenheit(temperature) { 157 | return (temperature * 1.8) + 32; 158 | } 159 | 160 | NestThermostatAccessory.prototype.setTargetHeatingCooling = function (targetHeatingCooling, callback) { 161 | var val = null; 162 | 163 | switch (targetHeatingCooling) { 164 | case Characteristic.CurrentHeatingCoolingState.HEAT: 165 | val = 'heat'; 166 | break; 167 | case Characteristic.CurrentHeatingCoolingState.COOL: 168 | val = 'cool'; 169 | break; 170 | case Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL: 171 | val = 'heat-cool'; 172 | break; 173 | default: 174 | val = 'off'; 175 | break; 176 | } 177 | 178 | return this.updateDevicePropertyAsync("hvac_mode", val, "target heating cooling") 179 | .asCallback(callback); 180 | }; 181 | 182 | NestThermostatAccessory.prototype.setTargetTemperature = function (targetTemperature, callback) { 183 | var usesFahrenheit = this.usesFahrenheit(); 184 | if (usesFahrenheit) { 185 | // Convert to Fahrenheit and round to nearest integer 186 | targetTemperature = Math.round(celsiusToFahrenheit(targetTemperature)); 187 | } else { 188 | // Celsius value has to be in half point increments 189 | targetTemperature = Math.round( targetTemperature * 2 ) / 2; 190 | } 191 | 192 | var key = "target_temperature_"; 193 | var prop = ""; 194 | if (this.getTargetHeatingCooling() == (Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL)) { 195 | // Choose closest target as single target 196 | var high, low; 197 | if (usesFahrenheit) { 198 | high = fahrenheitToCelsius(this.device.target_temperature_high_f); 199 | low = fahrenheitToCelsius(this.device.target_temperature_low_f); 200 | } else { 201 | high = this.device.target_temperature_high_c; 202 | low = this.device.target_temperature_low_c; 203 | } 204 | var cur = this.getCurrentTemperature(); 205 | var isHighTemp = Math.abs(high - cur) < Math.abs(cur - low); 206 | prop = isHighTemp ? "high" : "low"; 207 | key += prop + "_"; 208 | prop += " "; 209 | } 210 | 211 | key += (usesFahrenheit ? "f" : "c"); 212 | 213 | return this.cancelAutoAway() 214 | .then(this.updateDevicePropertyAsync.bind(this, key, targetTemperature, prop + "target temperature")) 215 | .asCallback(callback); 216 | }; 217 | 218 | NestThermostatAccessory.prototype.usesFahrenheit = function () { 219 | return this.getTemperatureUnits() == Characteristic.TemperatureDisplayUnits.FAHRENHEIT; 220 | }; 221 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var nest = require('unofficial-nest-api'); 2 | var NestConnection = require('./lib/nest-connection.js'); 3 | var inherits = require('util').inherits; 4 | 5 | var Service, Characteristic, Accessory, uuid, Away; 6 | var DeviceAccessory, ThermostatAccessory, ProtectAccessory, CamAccessory; 7 | 8 | module.exports = function (homebridge) { 9 | Service = homebridge.hap.Service; 10 | Characteristic = homebridge.hap.Characteristic; 11 | Accessory = homebridge.hap.Accessory; 12 | uuid = homebridge.hap.uuid; 13 | 14 | /** 15 | * Characteristic "Away" 16 | */ 17 | Away = function () { 18 | Characteristic.call(this, 'Away', 'D6D47D29-4639-4F44-B53C-D84015DAEBDB'); 19 | this.setProps({ 20 | format: Characteristic.Formats.BOOL, 21 | perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] 22 | }); 23 | this.value = this.getDefaultValue(); 24 | }; 25 | inherits(Away, Characteristic); 26 | Away.HOME = 0; 27 | Away.AWAY = 1; 28 | 29 | var exportedTypes = { 30 | Accessory: Accessory, 31 | Service: Service, 32 | Characteristic: Characteristic, 33 | uuid: uuid, 34 | Away: Away 35 | }; 36 | 37 | DeviceAccessory = require('./lib/nest-device-accessory.js')(exportedTypes); 38 | ThermostatAccessory = require('./lib/nest-thermostat-accessory.js')(exportedTypes); 39 | ProtectAccessory = require('./lib/nest-protect-accessory.js')(exportedTypes); 40 | CamAccessory = require('./lib/nest-cam-accessory.js')(exportedTypes); 41 | 42 | var acc = NestThermostatAccessory.prototype; 43 | inherits(NestThermostatAccessory, Accessory); 44 | NestThermostatAccessory.prototype.parent = Accessory.prototype; 45 | for (var mn in acc) { 46 | NestThermostatAccessory.prototype[mn] = acc[mn]; 47 | } 48 | 49 | homebridge.registerPlatform("homebridge-nest", "Nest", NestPlatform); 50 | }; 51 | 52 | function NestPlatform(log, config) { 53 | // auth info 54 | this.username = config["username"]; 55 | this.password = config["password"]; 56 | this.config = config; 57 | 58 | this.log = log; 59 | this.accessoryLookup = {}; 60 | this.accessoryLookupByStructureId = {}; 61 | } 62 | 63 | var setupConnection = function(config, log) { 64 | return new Promise(function (resolve, reject) { 65 | var token = config["token"]; 66 | var clientId = config["clientId"]; 67 | var clientSecret = config["clientSecret"]; 68 | var code = config["code"]; 69 | var authURL = clientId ? "https://home.nest.com/login/oauth2?client_id=" + clientId + "&state=STATE" : null; 70 | 71 | var err; 72 | if (!token && !clientId && !clientSecret && !code) { 73 | err = "You did not specify {'token'} or {'clientId','clientSecret','code'}, one set of which is required for the new API"; 74 | } else if (!token && clientId && clientSecret && !code) { 75 | err = "You are missing the one-time-use 'code' param. Should be able to obtain from " + authURL; 76 | } else if (!token && (!clientId || !clientSecret || !code)) { 77 | err = "If you are going to use {'clientId','clientSecret','code'} then you must specify all three, otherwise use {'token'}"; 78 | } 79 | if (err) { 80 | reject(new Error(err)); 81 | return; 82 | } 83 | 84 | var conn = new NestConnection(token, log); 85 | if (token) { 86 | resolve(conn) 87 | } else { 88 | conn.auth(clientId, clientSecret, code) 89 | .then(function(token) { 90 | if (log) log.warn("CODE IS ONLY VALID ONCE! Update config to use {'token':'" + token + "'} instead."); 91 | resolve(conn); 92 | }) 93 | .catch(function(err){ 94 | reject(err); 95 | if (log) log.warn("Auth failed which likely means the code is no longer valid. Should be able to generate a new one at " + authURL); 96 | }); 97 | } 98 | }); 99 | }; 100 | 101 | NestPlatform.prototype = { 102 | accessories: function (callback) { 103 | this.log("Fetching Nest devices."); 104 | 105 | var that = this; 106 | 107 | var generateAccessories = function(data) { 108 | var foundAccessories = []; 109 | 110 | var loadDevices = function(DeviceType) { 111 | var list = data.devices && data.devices[DeviceType.deviceGroup]; 112 | for (var deviceId in list) { 113 | if (list.hasOwnProperty(deviceId)) { 114 | var device = list[deviceId]; 115 | var structureId = device['structure_id']; 116 | var structure = data.structures[structureId]; 117 | var accessory = new DeviceType(this.conn, this.log, device, structure); 118 | that.accessoryLookup[deviceId] = accessory; 119 | foundAccessories.push(accessory); 120 | } 121 | } 122 | }.bind(this); 123 | 124 | loadDevices(ThermostatAccessory); 125 | loadDevices(ProtectAccessory); 126 | loadDevices(CamAccessory); 127 | 128 | return foundAccessories; 129 | }.bind(this); 130 | 131 | var updateAccessories = function(data, accList) { 132 | accList.map(function(acc) { 133 | var device = data.devices[acc.deviceGroup][acc.deviceId]; 134 | var structureId = device['structure_id']; 135 | var structure = data.structures[structureId]; 136 | acc.updateData(device, structure); 137 | }.bind(this)); 138 | }; 139 | 140 | var handleUpdates = function(data){ 141 | updateAccessories(data, that.accessoryLookup); 142 | }; 143 | setupConnection(this.config, this.log) 144 | .then(function(conn){ 145 | that.conn = conn; 146 | return that.conn.open(); 147 | }) 148 | .then(function(){ 149 | return that.conn.subscribe(handleUpdates); 150 | }) 151 | .then(function(data) { 152 | that.accessoryLookup = generateAccessories(data); 153 | if (callback) { 154 | var copy = that.accessoryLookup.map(function (a) { return a; }); 155 | callback(copy); 156 | } 157 | }) 158 | .catch(function(err) { 159 | that.log.error(err); 160 | if (that.username && that.password) { 161 | that.oldaccessories(callback); 162 | } else if (callback) { 163 | callback([]); 164 | } 165 | }); 166 | }, 167 | oldaccessories: function (callback) { 168 | this.log.warn("Falling back to legacy API."); 169 | 170 | var that = this; 171 | var foundAccessories = []; 172 | 173 | nest.login(this.username, this.password, function (err, data) { 174 | if (err) { 175 | that.log("There was a problem authenticating with Nest."); 176 | } else { 177 | nest.fetchStatus(function (data) { 178 | for (var deviceId in data.device) { 179 | if (data.device.hasOwnProperty(deviceId)) { 180 | var device = data.device[deviceId]; 181 | // it's a thermostat, adjust this to detect other accessories 182 | if (data.shared[deviceId].hasOwnProperty('current_temperature')) { 183 | var initialData = data.shared[deviceId]; 184 | var structureId = data.link[deviceId]['structure'].replace('structure.', ''); 185 | var structure = data.structure[structureId]; 186 | var name = initialData.name; 187 | var accessory = new NestThermostatAccessory( 188 | that.log, name, 189 | device, deviceId, initialData, 190 | structure, structureId); 191 | that.accessoryLookup[deviceId] = accessory; 192 | that.accessoryLookupByStructureId[structureId] = accessory; 193 | foundAccessories.push(accessory); 194 | } 195 | } 196 | } 197 | function subscribe() { 198 | nest.subscribe(subscribeDone, ['device', 'shared', 'structure']); 199 | } 200 | 201 | function subscribeDone(id, data, type) { 202 | // data if set, is also stored here: nest.lastStatus.shared[thermostatID] 203 | if (id && type != undefined && data && (that.accessoryLookup[id] || that.accessoryLookupByStructureId[id])) { 204 | that.log('Update to Device: ' + id + " type: " + type); 205 | var accessory = that.accessoryLookup[id] || that.accessoryLookupByStructureId[id]; 206 | if (accessory) { 207 | switch (type) { 208 | case 'shared': 209 | accessory.updateData(data); 210 | break; 211 | case 'device': 212 | accessory.device = data; 213 | accessory.updateData(); 214 | break; 215 | case 'structure': 216 | accessory.structure = data; 217 | accessory.updateData(); 218 | break; 219 | } 220 | } 221 | 222 | } 223 | setTimeout(subscribe, 2000); 224 | } 225 | 226 | subscribe(); 227 | callback(foundAccessories) 228 | }); 229 | } 230 | }); 231 | } 232 | } 233 | 234 | function NestThermostatAccessory(log, name, device, deviceId, initialData, structure, structureId) { 235 | // device info 236 | this.name = name || ("Nest" + device.serial_number); 237 | this.deviceId = deviceId; 238 | this.log = log; 239 | this.device = device; 240 | 241 | var id = uuid.generate('nest.thermostat.' + deviceId); 242 | Accessory.call(this, this.name, id); 243 | this.uuid_base = id; 244 | 245 | this.currentData = initialData; 246 | 247 | this.structureId = structureId; 248 | this.structure = structure; 249 | 250 | this.getService(Service.AccessoryInformation) 251 | .setCharacteristic(Characteristic.Manufacturer, "Nest") 252 | .setCharacteristic(Characteristic.Model, device.model_version) 253 | .setCharacteristic(Characteristic.SerialNumber, device.serial_number); 254 | 255 | this.addService(Service.Thermostat, name); 256 | 257 | this.getService(Service.Thermostat) 258 | .addCharacteristic(Away) 259 | .on('get', function (callback) { 260 | var away = this.isAway(); 261 | this.log("Away for " + this.name + " is: " + away); 262 | if (callback) callback(null, away); 263 | }.bind(this)) 264 | .on('set', this.setAway.bind(this)); 265 | 266 | this.getService(Service.Thermostat) 267 | .getCharacteristic(Characteristic.TemperatureDisplayUnits) 268 | .on('get', function (callback) { 269 | var units = this.getTemperatureUnits(); 270 | var unitsName = units == Characteristic.TemperatureDisplayUnits.FAHRENHEIT ? "Fahrenheit" : "Celsius"; 271 | this.log("Temperature unit for " + this.name + " is: " + unitsName); 272 | if (callback) callback(null, units); 273 | }.bind(this)); 274 | 275 | this.getService(Service.Thermostat) 276 | .getCharacteristic(Characteristic.CurrentTemperature) 277 | .on('get', function (callback) { 278 | var curTemp = this.getCurrentTemperature(); 279 | this.log("Current temperature for " + this.name + " is: " + curTemp); 280 | if (callback) callback(null, curTemp); 281 | }.bind(this)); 282 | 283 | this.getService(Service.Thermostat) 284 | .getCharacteristic(Characteristic.CurrentHeatingCoolingState) 285 | .on('get', function (callback) { 286 | var curHeatingCooling = this.getCurrentHeatingCooling(); 287 | this.log("Current heating for " + this.name + " is: " + curHeatingCooling); 288 | if (callback) callback(null, curHeatingCooling); 289 | }.bind(this)); 290 | 291 | this.getService(Service.Thermostat) 292 | .getCharacteristic(Characteristic.CurrentRelativeHumidity) 293 | .on('get', function (callback) { 294 | var curHumidity = this.getCurrentRelativeHumidity(); 295 | this.log("Current humidity for " + this.name + " is: " + curHumidity); 296 | if (callback) callback(null, curHumidity); 297 | }.bind(this)); 298 | 299 | this.getService(Service.Thermostat) 300 | .getCharacteristic(Characteristic.TargetTemperature) 301 | .on('get', function (callback) { 302 | var targetTemp = this.getTargetTemperature(); 303 | this.log("Target temperature for " + this.name + " is: " + targetTemp); 304 | if (callback) callback(null, targetTemp); 305 | }.bind(this)) 306 | .on('set', this.setTargetTemperature.bind(this)); 307 | 308 | this.getService(Service.Thermostat) 309 | .getCharacteristic(Characteristic.TargetHeatingCoolingState) 310 | .on('get', function (callback) { 311 | var targetHeatingCooling = this.getTargetHeatingCooling(); 312 | this.log("Target heating for " + this.name + " is: " + targetHeatingCooling); 313 | if (callback) callback(null, targetHeatingCooling); 314 | }.bind(this)) 315 | .on('set', this.setTargetHeatingCooling.bind(this)); 316 | 317 | this.updateData(initialData); 318 | } 319 | 320 | NestThermostatAccessory.prototype.getServices = function () { 321 | return this.services; 322 | }; 323 | 324 | NestThermostatAccessory.prototype.updateData = function (data) { 325 | if (data != undefined) { 326 | this.currentData = data; 327 | } 328 | var thermostat = this.getService(Service.Thermostat); 329 | thermostat.getCharacteristic(Away).getValue(); 330 | thermostat.getCharacteristic(Characteristic.TemperatureDisplayUnits).getValue(); 331 | thermostat.getCharacteristic(Characteristic.CurrentTemperature).getValue(); 332 | thermostat.getCharacteristic(Characteristic.CurrentHeatingCoolingState).getValue(); 333 | thermostat.getCharacteristic(Characteristic.CurrentRelativeHumidity).getValue(); 334 | thermostat.getCharacteristic(Characteristic.TargetHeatingCoolingState).getValue(); 335 | thermostat.getCharacteristic(Characteristic.TargetTemperature).getValue(); 336 | }; 337 | 338 | NestThermostatAccessory.prototype.getCurrentHeatingCooling = function () { 339 | var current = this.getCurrentTemperature(); 340 | var state = this.getTargetHeatingCooling(); 341 | 342 | var isRange = state == (Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL); 343 | var high = isRange ? this.currentData.target_temperature_high : this.currentData.target_temperature; 344 | var low = isRange ? this.currentData.target_temperature_low : this.currentData.target_temperature; 345 | 346 | // Add threshold 347 | var threshold = .2; 348 | high += threshold; 349 | low -= threshold; 350 | 351 | if ((state & Characteristic.CurrentHeatingCoolingState.COOL) && this.currentData.can_cool && high < current) { 352 | return Characteristic.CurrentHeatingCoolingState.COOL; 353 | } 354 | if ((state & Characteristic.CurrentHeatingCoolingState.HEAT) && this.currentData.can_heat && low > current) { 355 | return Characteristic.CurrentHeatingCoolingState.HEAT; 356 | } 357 | return Characteristic.CurrentHeatingCoolingState.OFF; 358 | }; 359 | 360 | NestThermostatAccessory.prototype.getTargetHeatingCooling = function () { 361 | switch (this.currentData.target_temperature_type) { 362 | case "off": 363 | return Characteristic.CurrentHeatingCoolingState.OFF; 364 | case "heat": 365 | return Characteristic.CurrentHeatingCoolingState.HEAT; 366 | case "cool": 367 | return Characteristic.CurrentHeatingCoolingState.COOL; 368 | case "range": 369 | return Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL; 370 | default: 371 | return Characteristic.CurrentHeatingCoolingState.OFF; 372 | } 373 | }; 374 | 375 | NestThermostatAccessory.prototype.isAway = function () { 376 | return this.structure.away; 377 | }; 378 | 379 | NestThermostatAccessory.prototype.getCurrentTemperature = function () { 380 | return this.currentData.current_temperature; 381 | }; 382 | 383 | NestThermostatAccessory.prototype.getCurrentRelativeHumidity = function () { 384 | return this.device.current_humidity; 385 | }; 386 | 387 | NestThermostatAccessory.prototype.getTargetTemperature = function () { 388 | switch (this.getTargetHeatingCooling()) { 389 | case Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL: 390 | // Choose closest target as single target 391 | var high = this.currentData.target_temperature_high; 392 | var low = this.currentData.target_temperature_low; 393 | var cur = this.currentData.current_temperature; 394 | return Math.abs(high - cur) < Math.abs(cur - low) ? high : low; 395 | default: 396 | return this.currentData.target_temperature; 397 | } 398 | }; 399 | 400 | NestThermostatAccessory.prototype.getTemperatureUnits = function () { 401 | switch (this.device.temperature_scale) { 402 | case "F": 403 | return Characteristic.TemperatureDisplayUnits.FAHRENHEIT; 404 | case "C": 405 | return Characteristic.TemperatureDisplayUnits.CELSIUS; 406 | default: 407 | return Characteristic.TemperatureDisplayUnits.CELSIUS; 408 | } 409 | }; 410 | 411 | NestThermostatAccessory.prototype.setTargetHeatingCooling = function (targetHeatingCooling, callback) { 412 | var targetTemperatureType = null; 413 | 414 | switch (targetHeatingCooling) { 415 | case Characteristic.CurrentHeatingCoolingState.HEAT: 416 | targetTemperatureType = 'heat'; 417 | break; 418 | case Characteristic.CurrentHeatingCoolingState.COOL: 419 | targetTemperatureType = 'cool'; 420 | break; 421 | case Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL: 422 | targetTemperatureType = 'range'; 423 | break; 424 | default: 425 | targetTemperatureType = 'off'; 426 | break; 427 | } 428 | 429 | this.log("Setting target heating cooling for " + this.name + " to: " + targetTemperatureType); 430 | nest.setTargetTemperatureType(this.deviceId, targetTemperatureType); 431 | 432 | if (callback) callback(null, targetTemperatureType); 433 | }; 434 | 435 | NestThermostatAccessory.prototype.setTargetTemperature = function (targetTemperature, callback) { 436 | 437 | switch (this.getTargetHeatingCooling()) { 438 | case Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL: 439 | // Choose closest target as single target 440 | var high = this.currentData.target_temperature_high; 441 | var low = this.currentData.target_temperature_low; 442 | var cur = this.currentData.current_temperature; 443 | var isHighTemp = Math.abs(high - cur) < Math.abs(cur - low); 444 | if (isHighTemp) { 445 | high = targetTemperature; 446 | } else { 447 | low = targetTemperature; 448 | } 449 | this.log("Setting " + (isHighTemp ? "high" : "low") + " target temperature for " + this.name + " to: " + targetTemperature); 450 | nest.setTemperatureRange(this.deviceId, low, high); 451 | break; 452 | default: 453 | this.log("Setting target temperature for " + this.name + " to: " + targetTemperature); 454 | nest.setTemperature(this.deviceId, targetTemperature); 455 | break; 456 | } 457 | 458 | if (callback) callback(null, targetTemperature); 459 | }; 460 | 461 | NestThermostatAccessory.prototype.setAway = function (away, callback) { 462 | this.log("Setting Away for " + this.name + " to: " + away); 463 | nest.setAway(Boolean(away), this.structureId); 464 | if (callback) callback(null, away); 465 | } 466 | --------------------------------------------------------------------------------