From 049ff0b1b5a77bf9be345346ba3b7ece284cdc16 Mon Sep 17 00:00:00 2001 From: Adrian Cable Date: Fri, 19 Jul 2019 11:57:09 -0700 Subject: [PATCH] Refactored to support native Nest API --- .editorconfig | 2 +- .eslintrc.js | 319 +------ README.md | 110 ++- index.js | 437 +++++----- lib/nest-cam-accessory.js | 56 +- lib/nest-connection.js | 384 ++++++--- lib/nest-device-accessory.js | 278 +++---- lib/nest-protect-accessory.js | 204 ++--- lib/nest-thermostat-accessory.js | 626 +++++++------- package-lock.json | 1323 ------------------------------ package.json | 56 +- 11 files changed, 1195 insertions(+), 2600 deletions(-) delete mode 100644 package-lock.json diff --git a/.editorconfig b/.editorconfig index fa8ffc6..baf3842 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,7 +3,7 @@ root = true [*] end_of_line = lf insert_final_newline = true -indent_size = 2 +indent_size = 4 indent_style = space [*.js] diff --git a/.eslintrc.js b/.eslintrc.js index 7faf7b9..dc413e5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,296 +1,33 @@ module.exports = { - "env": { - "es6": true, - "node": true + 'env': { + 'es6': true, + 'node': true }, - "extends": "eslint:recommended", - "rules": { - "accessor-pairs": "error", - "array-bracket-newline": "error", - "array-bracket-spacing": [ - "error", - "never" - ], - "array-callback-return": "off", - "array-element-newline": "off", - "arrow-body-style": "error", - "arrow-parens": "error", - "arrow-spacing": "error", - "block-scoped-var": "error", - "block-spacing": [ - "error", - "always" - ], - "brace-style": [ - "error", - "1tbs", - { - "allowSingleLine": true - } - ], - "callback-return": "off", - "camelcase": [ - "error", - { - "properties": "never" - } - ], - "capitalized-comments": "off", - "class-methods-use-this": "error", - "comma-dangle": "error", - "comma-spacing": [ - "error", - { - "after": true, - "before": false - } - ], - "comma-style": [ - "error", - "last" - ], - "complexity": "error", - "computed-property-spacing": [ - "error", - "never" - ], - "consistent-return": "off", - "consistent-this": "off", - "curly": "off", - "default-case": "off", - "dot-location": [ - "error", - "property" - ], - "dot-notation": [ - "error", - { - "allowKeywords": true - } - ], - "eol-last": "error", - "eqeqeq": "off", - "for-direction": "error", - "func-call-spacing": "error", - "func-name-matching": "error", - "func-names": [ - "error", - "never" - ], - "func-style": "off", - "function-paren-newline": "off", - "generator-star-spacing": "error", - "getter-return": "error", - "global-require": "error", - "guard-for-in": "off", - "handle-callback-err": "error", - "id-blacklist": "error", - "id-length": "off", - "id-match": "error", - "implicit-arrow-linebreak": "error", - "indent": ["error", 2], - "indent-legacy": "off", - "init-declarations": "off", - "jsx-quotes": "error", - "key-spacing": "error", - "keyword-spacing": [ - "error", - { - "after": true, - "before": true - } - ], - //"line-comment-position": "error", - "linebreak-style": [ - "error", - "unix" - ], - "lines-around-comment": "error", - "lines-around-directive": "error", - "lines-between-class-members": "error", - "max-depth": "error", - "max-len": "off", - // "max-lines": "error", - "max-nested-callbacks": "error", - "max-params": "off", - "max-statements": "off", - "max-statements-per-line": "error", - "multiline-comment-style": "error", - "new-cap": "error", - "new-parens": "error", - "newline-after-var": "off", - "newline-before-return": "off", - "newline-per-chained-call": "error", - "no-alert": "error", - "no-array-constructor": "error", - "no-await-in-loop": "error", - "no-bitwise": "off", - "no-buffer-constructor": "error", - "no-caller": "error", - "no-catch-shadow": "error", - "no-confusing-arrow": "error", - // "no-continue": "error", - "no-div-regex": "error", - "no-duplicate-imports": "error", - "no-else-return": "off", - "no-empty-function": "error", - "no-eq-null": "error", - "no-eval": "error", - "no-extend-native": "error", - "no-extra-bind": "error", - "no-extra-label": "error", - "no-extra-parens": "off", - "no-floating-decimal": "error", - "no-implicit-coercion": "error", - "no-implicit-globals": "error", - "no-implied-eval": "error", - //"no-inline-comments": "error", - "no-inner-declarations": [ - "error", - "functions" - ], - "no-invalid-this": "error", - "no-iterator": "error", - "no-label-var": "error", - "no-labels": "error", - "no-lone-blocks": "error", - "no-lonely-if": "error", - "no-loop-func": "error", - "no-magic-numbers": "off", - "no-mixed-operators": "error", - "no-mixed-requires": "error", - "no-multi-assign": "error", - "no-multi-spaces": "error", - "no-multi-str": "error", - "no-multiple-empty-lines": "error", - "no-native-reassign": "error", - "no-negated-condition": "off", - "no-negated-in-lhs": "error", - "no-nested-ternary": "error", - "no-new": "error", - "no-new-func": "error", - "no-new-object": "error", - "no-new-require": "error", - "no-new-wrappers": "error", - "no-octal-escape": "error", - "no-param-reassign": "off", - "no-path-concat": "error", - "no-plusplus": "error", - "no-process-env": "error", - "no-process-exit": "error", - "no-proto": "error", - "no-prototype-builtins": "error", - "no-restricted-globals": "error", - "no-restricted-imports": "error", - "no-restricted-modules": "error", - "no-restricted-properties": "error", - "no-restricted-syntax": "error", - "no-return-assign": [ - "error", - "except-parens" - ], - "no-return-await": "error", - "no-script-url": "error", - "no-self-compare": "error", - "no-sequences": "error", - "no-shadow": "error", - "no-shadow-restricted-names": "error", - "no-spaced-func": "error", - "no-sync": "error", - "no-tabs": "off", - "no-template-curly-in-string": "error", - "no-ternary": "off", - "no-throw-literal": "error", - "no-trailing-spaces": "error", - "no-undef-init": "error", - // "no-undefined": "error", - "no-underscore-dangle": "error", - "no-unmodified-loop-condition": "error", - "no-unneeded-ternary": "off", - "no-unused-expressions": "off", - "no-unused-vars": ["error", { "vars": "all", "args": "none", "ignoreRestSiblings": true }], - "no-use-before-define": "off", - "no-useless-call": "error", - "no-useless-computed-key": "error", - //"no-useless-concat": "error", - "no-useless-constructor": "error", - "no-useless-rename": "error", - "no-useless-return": "error", - "no-var": "error", - //"no-void": "error", - "no-warning-comments": "warn", - "no-whitespace-before-property": "error", - "no-with": "error", - "nonblock-statement-body-position": "error", - "object-curly-newline": "off", - "object-curly-spacing": "error", - "object-property-newline": "error", - "object-shorthand": "off", - "one-var": "off", - "one-var-declaration-per-line": [ - "error", - "initializations" - ], - "operator-assignment": [ - "error", - "always" - ], - "operator-linebreak": "error", - "padded-blocks": "off", - "padding-line-between-statements": "error", - "prefer-arrow-callback": "off", - "prefer-const": ["error", {"destructuring": "all"}], - "prefer-destructuring": "off", - "prefer-numeric-literals": "error", - "prefer-promise-reject-errors": "error", - "prefer-reflect": "off", - //"prefer-rest-params": "error", - "prefer-spread": "error", - "prefer-template": "off", - "quote-props": "off", - "quotes": "off", - "radix": "error", - "require-await": "error", - "require-jsdoc": "off", - "rest-spread-spacing": "error", - "semi": "error", - "semi-spacing": "error", - "semi-style": [ - "error", - "last" - ], - "sort-imports": "error", - "sort-keys": "off", - "sort-vars": "off", - "space-before-blocks": "off", - "space-before-function-paren": "off", - "space-in-parens": "off", - "space-infix-ops": "error", - "space-unary-ops": "error", - "spaced-comment": [ - "error", - "always" - ], - "strict": [ - "error", - "never" - ], - "switch-colon-spacing": "error", - "symbol-description": "error", - "template-curly-spacing": "error", - "template-tag-spacing": "error", - "unicode-bom": [ - "error", - "never" - ], - "valid-jsdoc": "error", - "vars-on-top": "off", - "wrap-iife": "error", - "wrap-regex": "error", - "yield-star-spacing": "error", - "yoda": [ - "error", - "never" + 'extends': 'eslint:recommended', + 'globals': { + 'Atomics': 'readonly', + 'SharedArrayBuffer': 'readonly' + }, + 'parserOptions': { + 'ecmaVersion': 2018, + 'sourceType': 'module' + }, + 'rules': { + 'indent': [ + 'error', + 4 + ], + 'linebreak-style': [ + 'error', + 'unix' + ], + 'quotes': [ + 'error', + 'single' + ], + 'semi': [ + 'error', + 'always' ] } }; diff --git a/README.md b/README.md index 26e0f40..2148059 100644 --- a/README.md +++ b/README.md @@ -1,84 +1,70 @@ -# NEED NEW OWNER - -After Nest's latest API announcements I've decided to switch to an ecobee. If you would like to take over this repo and npm package, please file an issue requesting ownership. - # homebridge-nest -Nest plugin for [HomeBridge](https://github.com/nfarina/homebridge) +Nest plug-in for [Homebridge](https://github.com/nfarina/homebridge) using the native Nest API. + +Integrate your Nest Thermostat and Nest Protect devices into your HomeKit system. **homebridge-nest no longer uses the 'Works With Nest' API and will be unaffected by its shutdown in August 2019.** -This repository contains the Nest plugin for homebridge that was previously bundled in the main `homebridge` repository. +Currently, homebridge-nest supports Nest Thermostat and Nest Protect devices. Camera and Nest Secure support may come later. (I don't currently own those devices.) # Installation 1. Install homebridge using: `npm install -g homebridge` -2. Install this plugin using: `npm install -g homebridge-nest` +1. Install this plug-in using: `npm install -g homebridge-nest` 3. Update your configuration file. See `sample-config.json` snippet below. -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. It's a simple process and if you specify that it is for Individual, then you are auto approved (at least in my experience). - -_Note: The name of the device matches the name displayed in the Nest app. In my case, I originally configured the Nest app so 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._ - - -## How to Setup New API - -1. Go to [https://developer.nest.com](https://developer.nest.com) -2. Choose **Sign In** -3. Use your normal account to sign in -4. Fill in you info in 'Step 1' -5. In 'Step 2' set: - * **Company Name**: _HomeBridge-Nest_ - * **Company URL**: _https://github.com/chrisjshull/homebridge-nest_ - * **Country**: _[Your Country]_ - * **Size of Company**: _Individual_ -6. Then just agree to the terms and submit -7. Go to **Products** and create a new product -8. Fill in: - * **Product Name**: _HomeBridge_ + your name (must be unique) - * **Description**: _Open source project to provide HomeKit integration_ - * **Categories**: _Home Automation_ - * **Users**: _Individual_ - * **Support URL**: _https://github.com/chrisjshull/homebridge-nest_ - * **Redirect URL**: _[LEAVE BLANK]_ - * **Permissions (minimum)**: - * Enable **Thermostat** with **read/write** - * Enable **Away** with **read/write** - * Enable **Smoke+CO alarm** with **read** (if you ever might want Nest Protect) - * Enable **Camera** with **read** (if you ever might want Nest Cam, motion detection only) - * Permission description: fill in anything -9. Now you should have a product. Now locate the id/secret section on the right of your product's page -10. Copy the **Product ID** to your HomeBridge config as the **clientId** in the Nest config -11. Copy the **Product Secret** to your HomeBridge config as the **clientSecret** in the Nest config -12. Navigate to the **Authorization URL** -13. Accept the terms and copy the **Pin Code** to your HomeBridge config as the **code** in the Nest config -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 -15. You should be able to **restart HomeBridge** and it should succeed with the new token. - -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), but you can keep them around if you wish, they will be ignored. - - - +You will need your Nest account email address and password - the same credentials you use with the Nest app. A 'Works With Nest' developer account and tokens are not required. # Configuration -Configuration sample: +Configuration sample (edit ~/.homebridge/config.json): - ``` +``` "platforms": [ { "platform": "Nest", - "clientId": "developer Product ID from Nest", - "clientSecret": "developer Product Secret from Nest", - "code": "your Pincode from Nest" + "email": "your Nest account email address", + "password": "your Nest account password" } ], - ``` Fields: * "platform": Must always be "Nest" (required) -* "clientId": developer Product ID (see instructions) -* "clientSecret": developer Product Secret (see instructions) -* "code": your Pincode from Nest (see instructions) -* "token": The only (and final) authentication piece you need to use the new API (see instructions) -* "structureId": "your structure's ID" // optional structureId to filter to (see logs on first run for each device's structureId) -* "disable": [] // optional list of features to disable ("Thermostat.Fan", "Thermostat.Home", "Thermostat.Eco") +* "email": Your Nest account email address (required) +* "password": Your Nest account password (required) +* "pin": "number" // PIN code sent to your mobile device for 2-factor authentication - see below (optional) +* "structureId": "your structure's ID" // optional structureId to filter to (see logs on first run for each device's structureId) - Nest "structures" are equivalent to HomeKit "homes" +* "disable": [] // optional list of features to disable ("Thermostat.Fan", "Thermostat.Home", "Thermostat.Eco", "Protect.Home") + +Note: The name of the device that appears in HomeKit matches the name displayed in the Nest app. This plug-in creates 4 accessories for each Thermostat (temperature, home/away mode, eco mode, fan) and 2 accessories for each Protect (smoke and CO sensors). If you have multiple thermostats or Protects, you should assign them a Label in the Nest app so you know which is which (select your device in the app, then go to Settings, then go to Where, then set a Label). You can also change the names of the individual accessories in the Apple Home app if you wish. + +# Two-Factor Authentication + +Two-factor authentication is supported if enabled in your Nest account. On starting Homebridge, you will be prompted to enter a PIN code which will be sent to the mobile device number registered to your Nest account. + +If you are running Homebridge as a service, you cannot manually enter the PIN in the console. In this case, when you start Homebridge and receive the PIN code, edit config.json and add the PIN received under "pin" (see 'Configuration' above). Then, restart Homebridge. Using 2FA is not recommended if Homebridge is run as a service, because if the connection to the Nest service is interrupted for any reason, homebridge-nest-native will not be able to automatically reconnect. + +# HomeKit Accessory Types + +## Nest Thermostat + +* *Thermostat* accessory with ambient temperature and humidity sensors, mode control (heat/cool/auto/off), and target temperature control +* *Switch* accessory (Home Occupied) indicating detected Home/Away state - can be manually changed. Disable by adding "Thermostat.Home" to "disable" field in config.json +* *Switch* accessory (Eco Mode) indicating current eco mode state - can be manually changed. Disable by adding "Thermostat.Eco" to "disable" field in config.json +* *Fan* accessory indicating whether the fan is running - can be manually changed. Disable by adding "Thermostat.Fan" to "disable" field in config.json + +## Nest Protect + +* *SmokeSensor* accessory (Smoke) indicating smoke detected +* *CarbonMonoxideSensor* accessory (Carbon Monoxide) indicating CO detected +* *OccupancySensor* accessory (Home Occupied) indicating detected occupancy (Home/Away) state. Disable by adding "Protect.Home" to "disable" field in config.json - you will want to do this if your home has both a Thermostat and Protects to avoid a duplicate home/away accessory + +# Donate to Support homebridge-nest + +homebridge-nest is a labour of love. It's provided under the ISC licence and is completely free to do whatever you want with. But if you'd like to show your appreciation for its continued development, please consider [clicking here to make a small donation](https://paypal.me/adriancable586) or send me a thank-you card: + +Adrian Cable +PO Box 370365 +Montara, CA 94037 + +I appreciate your feedback and support in whatever form! \ No newline at end of file diff --git a/index.js b/index.js index 0d7fe23..f7c4c43 100644 --- a/index.js +++ b/index.js @@ -7,272 +7,273 @@ let ThermostatAccessory, ProtectAccessory, CamAccessory; // DeviceAccessory, let Away, EcoMode, FanTimerActive, FanTimerDuration, HasLeaf, ManualTestActive, SunlightCorrectionEnabled, SunlightCorrectionActive, UsingEmergencyHeat; module.exports = function (homebridge) { - Service = homebridge.hap.Service; - Characteristic = homebridge.hap.Characteristic; - Accessory = homebridge.hap.Accessory; - uuid = homebridge.hap.uuid; + Service = homebridge.hap.Service; + Characteristic = homebridge.hap.Characteristic; + Accessory = homebridge.hap.Accessory; + uuid = homebridge.hap.uuid; - // Define custom characteristics + // Define custom characteristics - /* + /* * Characteristic "Away" */ - Away = function () { - Characteristic.call(this, 'Away', 'D6D47D29-4638-4F44-B53C-D84015DAEBDB'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - }; - inherits(Away, Characteristic); + Away = function () { + Characteristic.call(this, 'Away', 'D6D47D29-4638-4F44-B53C-D84015DAEBDB'); + this.setProps({ + format: Characteristic.Formats.BOOL, + perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + }; + inherits(Away, Characteristic); - /* + /* * Characteristic "EcoMode" */ - EcoMode = function () { - Characteristic.call(this, 'Eco Mode', 'D6D47D29-4639-4F44-B53C-D84015DAEBDB'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - }; - inherits(EcoMode, Characteristic); + EcoMode = function () { + Characteristic.call(this, 'Eco Mode', 'D6D47D29-4639-4F44-B53C-D84015DAEBDB'); + this.setProps({ + format: Characteristic.Formats.BOOL, + perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + }; + inherits(EcoMode, Characteristic); - /* + /* * Characteristic "FanTimerActive" */ - FanTimerActive = function () { - Characteristic.call(this, 'Fan Timer Active', 'D6D47D29-4640-4F44-B53C-D84015DAEBDB'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - }; - inherits(FanTimerActive, Characteristic); + FanTimerActive = function () { + Characteristic.call(this, 'Fan Timer Active', 'D6D47D29-4640-4F44-B53C-D84015DAEBDB'); + this.setProps({ + format: Characteristic.Formats.BOOL, + perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + }; + inherits(FanTimerActive, Characteristic); - /* + /* * Characteristic "FanTimerDuration" */ - FanTimerDuration = function () { - Characteristic.call(this, 'Fan Timer Duraton', 'D6D47D29-4641-4F44-B53C-D84015DAEBDB'); - this.setProps({ - format: Characteristic.Formats.UINT8, - unit: Characteristic.Units.MINUTES, - maxValue: 60, - minValue: 15, - minStep: 15, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - }; - inherits(FanTimerDuration, Characteristic); + FanTimerDuration = function () { + Characteristic.call(this, 'Fan Timer Duration', 'D6D47D29-4641-4F44-B53C-D84015DAEBDB'); + this.setProps({ + format: Characteristic.Formats.UINT8, + unit: Characteristic.Units.MINUTES, + maxValue: 60, + minValue: 15, + minStep: 15, + perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + }; + inherits(FanTimerDuration, Characteristic); - /* + /* * Characteristic "HasLeaf" */ - HasLeaf = function () { - Characteristic.call(this, 'Has Leaf', 'D6D47D29-4642-4F44-B53C-D84015DAEBDB'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - }; - inherits(HasLeaf, Characteristic); + HasLeaf = function () { + Characteristic.call(this, 'Has Leaf', 'D6D47D29-4642-4F44-B53C-D84015DAEBDB'); + this.setProps({ + format: Characteristic.Formats.BOOL, + perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + }; + inherits(HasLeaf, Characteristic); - /* + /* * Characteristic "ManualTestActive" */ - ManualTestActive = function () { - Characteristic.call(this, 'Manual Test Active', 'D6D47D29-4643-4F44-B53C-D84015DAEBDB'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - }; - inherits(ManualTestActive, Characteristic); + ManualTestActive = function () { + Characteristic.call(this, 'Manual Test Active', 'D6D47D29-4643-4F44-B53C-D84015DAEBDB'); + this.setProps({ + format: Characteristic.Formats.BOOL, + perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + }; + inherits(ManualTestActive, Characteristic); - /* + /* * Characteristic "SunlightCorrectionEnabled" */ - SunlightCorrectionEnabled = function () { - Characteristic.call(this, 'Sunlight Correction Enabled', 'D6D47D29-4644-4F44-B53C-D84015DAEBDB'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - }; - inherits(SunlightCorrectionEnabled, Characteristic); + SunlightCorrectionEnabled = function () { + Characteristic.call(this, 'Sunlight Correction Enabled', 'D6D47D29-4644-4F44-B53C-D84015DAEBDB'); + this.setProps({ + format: Characteristic.Formats.BOOL, + perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + }; + inherits(SunlightCorrectionEnabled, Characteristic); - /* + /* * Characteristic "SunlightCorrectionActive" */ - SunlightCorrectionActive = function () { - Characteristic.call(this, 'Sunlight Correction Active', 'D6D47D29-4645-4F44-B53C-D84015DAEBDB'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - }; - inherits(SunlightCorrectionActive, Characteristic); + SunlightCorrectionActive = function () { + Characteristic.call(this, 'Sunlight Correction Active', 'D6D47D29-4645-4F44-B53C-D84015DAEBDB'); + this.setProps({ + format: Characteristic.Formats.BOOL, + perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + }; + inherits(SunlightCorrectionActive, Characteristic); - /* + /* * Characteristic "UsingEmergencyHeat" */ - UsingEmergencyHeat = function () { - Characteristic.call(this, 'Using Emergency Heat', 'D6D47D29-4646-4F44-B53C-D84015DAEBDB'); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] - }); - this.value = this.getDefaultValue(); - }; - inherits(UsingEmergencyHeat, Characteristic); + UsingEmergencyHeat = function () { + Characteristic.call(this, 'Using Emergency Heat', 'D6D47D29-4646-4F44-B53C-D84015DAEBDB'); + this.setProps({ + format: Characteristic.Formats.BOOL, + perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] + }); + this.value = this.getDefaultValue(); + }; + inherits(UsingEmergencyHeat, Characteristic); - const exportedTypes = { - Accessory: Accessory, - Service: Service, - Characteristic: Characteristic, - uuid: uuid, - Away: Away, - EcoMode: EcoMode, - FanTimerActive: FanTimerActive, - FanTimerDuration: FanTimerDuration, - HasLeaf: HasLeaf, - ManualTestActive: ManualTestActive, - SunlightCorrectionEnabled: SunlightCorrectionEnabled, - SunlightCorrectionActive: SunlightCorrectionActive, - UsingEmergencyHeat: UsingEmergencyHeat - }; + const exportedTypes = { + Accessory: Accessory, + Service: Service, + Characteristic: Characteristic, + uuid: uuid, + Away: Away, + EcoMode: EcoMode, + FanTimerActive: FanTimerActive, + FanTimerDuration: FanTimerDuration, + HasLeaf: HasLeaf, + ManualTestActive: ManualTestActive, + SunlightCorrectionEnabled: SunlightCorrectionEnabled, + SunlightCorrectionActive: SunlightCorrectionActive, + UsingEmergencyHeat: UsingEmergencyHeat + }; - // DeviceAccessory = - require('./lib/nest-device-accessory.js')(exportedTypes); // eslint-disable-line global-require - ThermostatAccessory = require('./lib/nest-thermostat-accessory.js')(exportedTypes); // eslint-disable-line global-require - ProtectAccessory = require('./lib/nest-protect-accessory.js')(exportedTypes); // eslint-disable-line global-require - CamAccessory = require('./lib/nest-cam-accessory.js')(exportedTypes); // eslint-disable-line global-require + // DeviceAccessory = + require('./lib/nest-device-accessory.js')(exportedTypes); // eslint-disable-line global-require + ThermostatAccessory = require('./lib/nest-thermostat-accessory.js')(exportedTypes); // eslint-disable-line global-require + ProtectAccessory = require('./lib/nest-protect-accessory.js')(exportedTypes); // eslint-disable-line global-require + CamAccessory = require('./lib/nest-cam-accessory.js')(exportedTypes); // eslint-disable-line global-require - homebridge.registerPlatform("homebridge-nest", "Nest", NestPlatform); + homebridge.registerPlatform('homebridge-nest', 'Nest', NestPlatform); }; function NestPlatform(log, config) { - // auth info - this.config = config; + // auth info + this.config = config; - this.log = log; - this.accessoryLookup = {}; + this.log = log; + this.accessoryLookup = {}; } const setupConnection = function(config, log) { - return new Promise(function (resolve, reject) { - const token = config.token; - const clientId = config.clientId; - const clientSecret = config.clientSecret; - const code = config.code; - const authURL = clientId ? "https://home.nest.com/login/oauth2?client_id=" + clientId + "&state=STATE" : null; + return new Promise(function (resolve, reject) { + const email = config.email; + const password = config.password; + const pin = config.pin; + const token = ''; - let err; - if (!token && !clientId && !clientSecret && !code) { - err = "You did not specify {'token'} or {'clientId','clientSecret','code'}, one set of which is required for the new API"; - } else if (!token && clientId && clientSecret && !code) { - err = "You are missing the one-time-use 'code' param. Should be able to obtain from " + authURL; - } else if (!token && (!clientId || !clientSecret || !code)) { - err = "If you are going to use {'clientId','clientSecret','code'} then you must specify all three, otherwise use {'token'}"; - } - if (err) { - reject(new Error(err)); - return; - } + let err; + if (!email || !password) { + err = 'You did not specify your Nest app {\'email\',\'password\'} in config.json'; + } + if (err) { + reject(new Error(err)); + return; + } - const conn = new NestConnection(token, log); - if (token) { - resolve(conn); - } else { - conn.auth(clientId, clientSecret, code) - .then(function(newToken) { - if (log) log.warn("CODE IS ONLY VALID ONCE! Update config to use {'token':'" + newToken + "'} instead."); - resolve(conn); - }) - .catch(function(authError){ - reject(authError); - 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); - }); - } - }); + const conn = new NestConnection(token, log); + if (token) { + resolve(conn); + } else { + conn.auth(email, password, pin) + .then(() => { + resolve(conn); + }) + .catch(function(authError){ + if (log) { + if (authError.code == 400) { + log.warn('Auth failed: email/password is not valid. Check you are using the correct email/password for your Nest account'); + } else if (authError.code == 429) { + log.warn('Auth failed: rate limit exceeded. Please try again in 60 minutes'); + } else if (authError.code == '2fa_error') { + log.warn('Auth failed: 2FA PIN was rejected'); + } else { + log.warn('Auth failed: could not connect to Nest service. Check your Internet connection'); + } + } + reject(authError); + }); + } + }); }; NestPlatform.prototype = { - shouldEnableFeature: function (key) { - return !this.config.disable || !this.config.disable.includes(key); - }, - accessories: function (callback) { - this.log("Fetching Nest devices."); + shouldEnableFeature: function (key) { + return !this.config.disable || !this.config.disable.includes(key); + }, + accessories: function (callback) { + this.log('Fetching Nest devices.'); - const that = this; + const that = this; - const generateAccessories = function(data) { - const foundAccessories = []; + const generateAccessories = function(data) { + const foundAccessories = []; - const loadDevices = function(DeviceType) { - const devices = (data.devices && data.devices[DeviceType.deviceGroup]) || {}; - for (const deviceId of Object.keys(devices)) { - const device = devices[deviceId]; - const structureId = device.structure_id; - if (this.config.structureId && this.config.structureId !== structureId) { - this.log("Skipping device " + deviceId + " because it is not in the required structure. Has " + structureId + ", looking for " + this.config.structureId + "."); - continue; - } - const structure = data.structures[structureId]; - const accessory = new DeviceType(this.conn, this.log, device, structure, this); - this.accessoryLookup[deviceId] = accessory; - foundAccessories.push(accessory); - } - }.bind(this); + const loadDevices = function(DeviceType) { + const devices = (data.devices && data.devices[DeviceType.deviceGroup]) || {}; + for (const deviceId of Object.keys(devices)) { + const device = devices[deviceId]; + const structureId = device.structure_id; + if (this.config.structureId && this.config.structureId !== structureId) { + this.log('Skipping device ' + deviceId + ' because it is not in the required structure. Has ' + structureId + ', looking for ' + this.config.structureId + '.'); + continue; + } + const structure = data.structures[structureId]; + const accessory = new DeviceType(this.conn, this.log, device, structure, this); + this.accessoryLookup[deviceId] = accessory; + foundAccessories.push(accessory); + } + }.bind(this); - loadDevices(ThermostatAccessory); - loadDevices(ProtectAccessory); - loadDevices(CamAccessory); + loadDevices(ThermostatAccessory); + loadDevices(ProtectAccessory); + loadDevices(CamAccessory); - return foundAccessories; - }.bind(this); + return foundAccessories; + }.bind(this); - const updateAccessories = function(data, accList) { - accList.map(function(acc) { - const device = data.devices[acc.deviceGroup][acc.deviceId]; - const structureId = device.structure_id; - const structure = data.structures[structureId]; - acc.updateData(device, structure); - }); - }; + const updateAccessories = function(data, accList) { + accList.map(function(acc) { + const device = data.devices[acc.deviceGroup][acc.deviceId]; + const structureId = device.structure_id; + const structure = data.structures[structureId]; + acc.updateData(device, structure); + }); + }; - const handleUpdates = function(data){ - updateAccessories(data, that.accessoryLookup); - }; - setupConnection(this.config, this.log) - .then(function(conn){ - that.conn = conn; - return that.conn.open(); - }) - .then(function(){ - return that.conn.subscribe(handleUpdates); - }) - .then(function(data) { - that.accessoryLookup = generateAccessories(data); - if (callback) { - const copy = Array.from(that.accessoryLookup); - callback(copy); - } - }) - .catch(function(err) { - that.log.error(err); - if (callback) { - callback([]); - } - }); - } + const handleUpdates = function(data){ + updateAccessories(data, that.accessoryLookup); + }; + setupConnection(this.config, this.log) + .then(function(conn){ + that.conn = conn; + return that.conn.subscribe(handleUpdates); + }) + .then(function(data) { + that.accessoryLookup = generateAccessories(data); + if (callback) { + const copy = Array.from(that.accessoryLookup); + callback(copy); + } + }) + .catch(function(err) { + that.log.error(err); + if (callback) { + callback([]); + } + }); + } }; diff --git a/lib/nest-cam-accessory.js b/lib/nest-cam-accessory.js index f393a32..aec8ccc 100644 --- a/lib/nest-cam-accessory.js +++ b/lib/nest-cam-accessory.js @@ -9,50 +9,50 @@ const NestDeviceAccessory = require('./nest-device-accessory')(); 'use strict'; module.exports = function(exportedTypes) { - if (exportedTypes && !Service) { - Service = exportedTypes.Service; - Characteristic = exportedTypes.Characteristic; - - const acc = NestCamAccessory.prototype; - inherits(NestCamAccessory, NestDeviceAccessory); - NestCamAccessory.prototype.parent = NestDeviceAccessory.prototype; - for (const mn in acc) { - NestCamAccessory.prototype[mn] = acc[mn]; + if (exportedTypes && !Service) { + Service = exportedTypes.Service; + Characteristic = exportedTypes.Characteristic; + + const acc = NestCamAccessory.prototype; + inherits(NestCamAccessory, NestDeviceAccessory); + NestCamAccessory.prototype.parent = NestDeviceAccessory.prototype; + for (const mn in acc) { + NestCamAccessory.prototype[mn] = acc[mn]; + } + + NestCamAccessory.deviceType = 'cam'; + NestCamAccessory.deviceGroup = 'cameras'; + NestCamAccessory.prototype.deviceType = NestCamAccessory.deviceType; + NestCamAccessory.prototype.deviceGroup = NestCamAccessory.deviceGroup; } - - NestCamAccessory.deviceType = 'cam'; - NestCamAccessory.deviceGroup = 'cameras'; - NestCamAccessory.prototype.deviceType = NestCamAccessory.deviceType; - NestCamAccessory.prototype.deviceGroup = NestCamAccessory.deviceGroup; - } - return NestCamAccessory; + return NestCamAccessory; }; function NestCamAccessory(conn, log, device, structure, platform) { - NestDeviceAccessory.call(this, conn, log, device, structure, platform); + NestDeviceAccessory.call(this, conn, log, device, structure, platform); - const motionSvc = this.addService(Service.MotionSensor); - this.bindCharacteristic(motionSvc, Characteristic.MotionDetected, "Motion", - getMotionDetectionState.bind(this), null, formatMotionDetectionState.bind(this)); + const motionSvc = this.addService(Service.MotionSensor); + this.bindCharacteristic(motionSvc, Characteristic.MotionDetected, 'Motion', + getMotionDetectionState.bind(this), null, formatMotionDetectionState.bind(this)); - this.addAwayCharacteristic(motionSvc); + this.addAwayCharacteristic(motionSvc); - this.updateData(); + this.updateData(); } // --- MotionDetectionState --- const getMotionDetectionState = function() { - return this.device.last_event && + return this.device.last_event && this.device.last_event.has_motion && !this.device.last_event.end_time; }; const formatMotionDetectionState = function(val) { - if (val) { - return "detected"; - } else { - return "not detected"; - } + if (val) { + return 'detected'; + } else { + return 'not detected'; + } }; diff --git a/lib/nest-connection.js b/lib/nest-connection.js index 14fed99..025b0c9 100644 --- a/lib/nest-connection.js +++ b/lib/nest-connection.js @@ -1,132 +1,308 @@ /** - * Created by kraigm on 12/15/15. + * Created by Adrian Cable on 7/16/19. */ const Promise = require('bluebird'); const rp = require('request-promise'); -const Firebase = require("firebase"); +const Prompt = require('prompt-promise'); 'use strict'; module.exports = Connection; -const logPrefix = "[NestFirebase]"; +let API_PENDING = 0; + +// Amount of time to run the fan when accessory is turned on +const FAN_DURATION_MINUTES = 15; + +// Interval between Nest status update polls +const API_POLL_SECONDS = 5; + +// We want to look like a browser +const USER_AGENT_STRING = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'; function Connection(token, log) { - this.token = token; - this.log = function(info) { - log.info(logPrefix, info); - }; - this.debug = function(info) { - log.debug(logPrefix, info); - }; - this.error = function(info) { - log.error(logPrefix, info); - }; + this.token = token; + this.log = function(info) { + log.info(info); + }; + this.debug = function(info) { + log.debug(info); + }; + this.error = function(info) { + log.error(info); + }; } -Connection.prototype.auth = function(clientId, clientSecret, code) { - return rp({ - method: 'POST', - uri: 'https://api.home.nest.com/oauth2/access_token', - form: { - client_id: clientId, - client_secret: clientSecret, - code: code, - grant_type: "authorization_code" - } - }) - .then(function (parsedBody) { - const body = JSON.parse(parsedBody); - this.token = body.access_token; - return this.token; - }.bind(this)); -}; +Connection.prototype.auth = function(email, password, forcePIN) { + API_PENDING++; + return new Promise((resolve, reject) => { + rp({ + method: 'POST', + uri: 'https://home.nest.com/session', + headers: { + 'Authorization': 'Basic', + 'User-Agent': USER_AGENT_STRING + }, + body: { + email: email, + password: password + }, + json: true + }).then(body => { + this.token = body.access_token; + this.transport_url = body.urls.transport_url; + this.userid = body.userid; + this.loggedin_email = email; + this.loggedin_password = password; + resolve(this.token); + }).catch(error => { + if (error.statusCode == 401) { + // 2FA required + let getPIN; -const authAsync = function() { - return Promise.fromCallback(this.conn.authWithCustomToken.bind(this.conn, this.token)); + this.log('Your Nest account has 2-factor authentication enabled.'); + if (forcePIN) { + this.log('Using PIN ' + forcePIN + ' from config.json.'); + this.log('If authentication fails, check this matches the 6-digit PIN sent to your phone number ending ' + error.response.body.truncated_phone_number + '.'); + getPIN = Promise.resolve(forcePIN); + } else { + this.log('Please enter the 6-digit PIN sent to your phone number ending ' + error.response.body.truncated_phone_number + '.'); + getPIN = Prompt('PIN: '); + } + getPIN.then(pin => { + return rp({ + method: 'POST', + uri: 'https://home.nest.com/api/0.1/2fa/verify_pin', + body: { + pin: pin, + '2fa_token': error.response.body['2fa_token'] + }, + json: true + }); + }).then(result => { + return rp({ + method: 'GET', + uri: 'https://home.nest.com/session', + headers: { + 'Authorization': 'Basic ' + result.access_token, + 'User-Agent': USER_AGENT_STRING + }, + json: true + }); + }).then(body => { + this.token = body.access_token; + this.transport_url = body.urls.transport_url; + this.userid = body.userid; + this.loggedin_email = email; + this.loggedin_password = password; + resolve(this.token); + }).catch(() => { + reject({ code: '2fa_error' }); + }); + } else { + reject({ code: error.statusCode }); + } + }).finally(() => API_PENDING--); + }); }; -// Create a callback which logs the current auth state -const authDataCallback = function(authData) { - if (authData) { - this.debug("User " + authData.uid + " is logged in with " + authData.provider); - } else { - this.debug("User is logged out"); - reauthAsync.bind(this)(); - } -}; +Connection.prototype.updateData = function() { + if (API_PENDING) { + // Don't get a data update if we are in the middle of pushing updated settings to the device + Promise.resolve(null); + } -const reauthAsync = function() { - // If already reauthorizing, return existing - if (this.authTask) return this.authTask; - - const self = this; - - // Loop that continues until connection is successful - const reauthLoopAsync = function(err) { - if (err) self.error("Reauthorizing error : " + (err.stack || err.message || err)); - self.debug("Delaying next reauthorization attempt (5s)"); - return Promise.delay(5000) - .then(function() { - // Attempts to reauthorize Firebase connection - self.error("Reauthorizing connection"); - return authAsync.call(self); - }) - .catch(reauthLoopAsync); - }; - - // Create loop and clean up when complete - return self.authTask || (self.authTask = reauthLoopAsync() - .finally(function() { self.authTask = null; })); + let data = {}; + + return rp({ + method: 'GET', + uri: this.transport_url +'/v2/mobile/user.' + this.userid, + headers: { + 'User-Agent': USER_AGENT_STRING, + 'Authorization': 'Basic ' + this.token, + 'X-nl-user-id': this.userid, + 'X-nl-protocol-version': 1 + }, + json: true + }) + .then(body => { + data.devices = {}; + data.devices['thermostats'] = {}; + data.devices['smoke_co_alarms'] = {}; + + let structures = body.structure || {}; + let shared = body.shared || {}; + let topaz = body.topaz || {}; + let device = body.device || {}; + + for (let i = 0; i < Object.keys(structures).length; i++) { + let structureId = Object.keys(structures)[i]; + let thisStructure = structures[structureId]; + + thisStructure.structure_id = structureId; + thisStructure.away = thisStructure.away ? 'away' : 'home'; + + let swarm = thisStructure.swarm; + swarm.map(unit => unit.split('.')).forEach(unit => { + let deviceType = unit[0]; + let deviceId = unit[1]; + + if (deviceType == 'device') { + // Detected thermostat + + data.devices['thermostats'][deviceId] = device[deviceId]; + let thisDevice = data.devices['thermostats'][deviceId]; + + for (let j = 0; j < Object.keys(shared[deviceId]).length; j++) { + let sKey = Object.keys(shared[deviceId])[j]; + thisDevice[sKey] = shared[deviceId][sKey]; + } + + thisDevice.device_id = deviceId; + thisDevice.structure_id = structureId; + thisDevice.name = thisDevice.name || 'Nest Thermostat'; + thisDevice.fan_timer_active = thisDevice.fan_timer_timeout > 0; + thisDevice.hvac_mode = (thisDevice.eco.mode == 'manual-eco' || thisDevice.eco.mode == 'auto-eco') ? 'eco' : (thisDevice.target_temperature_type == 'range' ? 'heat-cool' : thisDevice.target_temperature_type.toLowerCase()); + thisDevice.previous_hvac_mode = 'eco-off'; + thisDevice.humidity = thisDevice.current_humidity; + thisDevice.software_version = thisDevice.current_version; + thisDevice.hvac_state = (thisDevice.can_heat && thisDevice.hvac_heater_state) ? 'heating' : (thisDevice.can_cool && thisDevice.hvac_ac_state ? 'cooling' : 'off'); + + temperatureUnitMirror(thisDevice, 'current_temperature', 'ambient_temperature'); + temperatureUnitMirror(thisDevice, 'target_temperature'); + temperatureUnitMirror(thisDevice, 'target_temperature_high'); + temperatureUnitMirror(thisDevice, 'target_temperature_low'); + temperatureUnitMirror(thisDevice, 'eco_temperature_high'); + temperatureUnitMirror(thisDevice, 'eco_temperature_low'); + temperatureUnitMirror(thisDevice, 'away_temperature_high'); + temperatureUnitMirror(thisDevice, 'away_temperature_low'); + } else if (deviceType == 'topaz') { + // Detected Nest Protect + + data.devices['smoke_co_alarms'][deviceId] = topaz[deviceId]; + let thisDevice = data.devices['smoke_co_alarms'][deviceId]; + thisDevice.device_id = deviceId; + thisDevice.name = thisDevice.description || 'Nest Protect'; + thisDevice.smoke_alarm_state = (thisDevice.smoke_status == 0) ? 'ok' : 'emergency'; + thisDevice.co_alarm_state = (thisDevice.co_status == 0) ? 'ok' : 'emergency'; + thisDevice.battery_health = (thisDevice.battery_health_state == 0) ? 'ok' : 'low'; + thisDevice.is_online = thisDevice.component_wifi_test_passed; + } + }); + } + + data.structures = structures; + return data; + }).catch(error => { + this.log('Nest API call to update status returned an error: ' + error.statusCode); + }); }; -Connection.prototype.open = function() { - if (!this.token) { - return Promise.reject(new Error("You must provide a token or auth before you can open a connection.")); - } - - this.conn = new Firebase('wss://developer-api.nest.com', new Firebase.Context()); - return authAsync.call(this) - .then(function() { - // Register the callback to be fired every time auth state changes - this.conn.onAuth(authDataCallback.bind(this)); - return this; - }.bind(this)); -}; +Connection.prototype.dataTimerLoop = function(resolve, handler) { + var notify = resolve || handler; -Connection.prototype.isOpen = function() { - return this.conn ? true : false; + this.updateData().then(data => { + if (data) { + notify(data); + } + }).catch(error => { + console.log('Nest_API_error', error); + if (error.statusCode == 401 || error.statusCode == 403) { + // Token has probably expired - re-authenticate + this.log('Reauthenticating on Nest service ...'); + this.auth(this.loggedin_email, this.loggedin_password); + } + }).finally(() => { + setTimeout(() => this.dataTimerLoop(null, handler), API_POLL_SECONDS * 1000); + }); }; Connection.prototype.subscribe = function(handler) { - const self = this; - return new Promise(function (resolve, reject) { - if (!handler){ - reject(new Error("You must specify a handler")); - } else { - let notify = resolve || handler; - this.conn.on('value', function (snapshot) { - const data = snapshot.val(); - // self.debug(JSON.stringify({data})); - if (data) { - notify(data); - notify = handler; - } else { - self.error("Disconnect Detected"); - } - }); - } - }.bind(this)); + return new Promise(resolve => { + this.dataTimerLoop(resolve, handler); + }); }; Connection.prototype.update = function(path, data) { - const self = this; - const child = this.conn.child(path); - return Promise.fromCallback(function (cb) { - child.set(data, function (error) { - self.debug(...arguments); - cb(...arguments); - }); - }); + let body, url, serviceType; + + let splitPath = path.split('/'); + if (splitPath[0] == 'structures') { + serviceType = 'structure'; + + if (splitPath[2] == 'away') { + body = { away: data == 'away', away_timestamp: getUnixTime(), away_setter: 0 }; + } + } else if (splitPath[0] == 'devices') { + serviceType = 'shared'; + + if (splitPath[1] == 'thermostats') { + if (splitPath[3] == 'hvac_mode') { + if (['eco', 'eco-off'].includes(data)) { + serviceType = 'device'; + body = { eco: { mode: data == 'eco' ? 'manual-eco' : 'schedule' }}; + } else if (data == 'heat-cool') { + body = { target_temperature_type: 'range' }; + } else if (['heat', 'cool', 'off'].includes(data)) { + body = { target_temperature_type: data }; + } + } else if (splitPath[3] == 'temperature_scale' ) { + serviceType = 'device'; + body = { temperature_scale: data }; + } else if (splitPath[3] == 'target_temperature_f' ) { + body = { target_temperature: fahrenheitToCelsius(data) }; + } else if (splitPath[3] == 'target_temperature_c' ) { + body = { target_temperature: Number(data) }; + } else if (splitPath[3] == 'fan_timer_active' ) { + serviceType = 'device'; + body = { fan_timer_timeout: data ? getUnixTime() + (FAN_DURATION_MINUTES * 60) : 0 }; + } + } + } + + if (serviceType && body) { + url = this.transport_url + '/v2/put/' + serviceType + '.' + splitPath[serviceType == 'structure' ? 1 : 2]; + API_PENDING++; + return Promise.resolve(rp({ + method: 'POST', + uri: url, + headers: { + 'User-Agent': USER_AGENT_STRING, + 'Authorization': 'Basic ' + this.token, + 'X-nl-protocol-version': 1 + }, + body: body, + json: true + })) + .catch(error => { + this.log('Nest API call to change device settings returned an error: ' + error.statusCode); + }) + .finally(() => API_PENDING--); + } else { + return Promise.reject(new Error('not_supported')); + } }; + +function celsiusToFahrenheit(temperature) { + return (temperature * 1.8) + 32; +} + +function fahrenheitToCelsius(temperature) { + return (temperature - 32) / 1.8; +} + +function getUnixTime() { + return Math.floor(Date.now() / 1000); +} + +function temperatureUnitMirror(obj, apiKey, wwnKey) { + if (!wwnKey) { + wwnKey = apiKey; + } + if (obj[apiKey]) { + obj[wwnKey + '_c'] = obj[apiKey]; + obj[wwnKey + '_f'] = celsiusToFahrenheit(obj[apiKey]); + } +} + diff --git a/lib/nest-device-accessory.js b/lib/nest-device-accessory.js index 11f45b0..ab4d66c 100644 --- a/lib/nest-device-accessory.js +++ b/lib/nest-device-accessory.js @@ -10,236 +10,236 @@ let Away, EcoMode, FanTimerActive, FanTimerDuration, HasLeaf, ManualTestActive, 'use strict'; function toTitleCase(str) { - return str.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()); + return str.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()); } module.exports = function(exportedTypes) { - if (exportedTypes && !Accessory) { - Accessory = exportedTypes.Accessory; - Service = exportedTypes.Service; - Characteristic = exportedTypes.Characteristic; - uuid = exportedTypes.uuid; - Away = exportedTypes.Away; - EcoMode = exportedTypes.EcoMode; - FanTimerActive = exportedTypes.FanTimerActive; - FanTimerDuration = exportedTypes.FanTimerDuration; - HasLeaf = exportedTypes.HasLeaf; - ManualTestActive = exportedTypes.ManualTestActive; - SunlightCorrectionEnabled = exportedTypes.SunlightCorrectionEnabled; - SunlightCorrectionActive = exportedTypes.SunlightCorrectionActive; - UsingEmergencyHeat = exportedTypes.UsingEmergencyHeat; - - const acc = NestDeviceAccessory.prototype; - inherits(NestDeviceAccessory, Accessory); - NestDeviceAccessory.prototype.parent = Accessory.prototype; - for (const mn in acc) { - NestDeviceAccessory.prototype[mn] = acc[mn]; + if (exportedTypes && !Accessory) { + Accessory = exportedTypes.Accessory; + Service = exportedTypes.Service; + Characteristic = exportedTypes.Characteristic; + uuid = exportedTypes.uuid; + Away = exportedTypes.Away; + EcoMode = exportedTypes.EcoMode; + FanTimerActive = exportedTypes.FanTimerActive; + FanTimerDuration = exportedTypes.FanTimerDuration; + HasLeaf = exportedTypes.HasLeaf; + ManualTestActive = exportedTypes.ManualTestActive; + SunlightCorrectionEnabled = exportedTypes.SunlightCorrectionEnabled; + SunlightCorrectionActive = exportedTypes.SunlightCorrectionActive; + UsingEmergencyHeat = exportedTypes.UsingEmergencyHeat; + + const acc = NestDeviceAccessory.prototype; + inherits(NestDeviceAccessory, Accessory); + NestDeviceAccessory.prototype.parent = Accessory.prototype; + for (const mn in acc) { + NestDeviceAccessory.prototype[mn] = acc[mn]; + } } - } - return NestDeviceAccessory; + return NestDeviceAccessory; }; // Base type for Nest devices function NestDeviceAccessory(conn, log, device, structure, platform) { - // device info - this.conn = conn; - this.name = device.name_long || device.name; - this.deviceId = device.device_id; - this.log = log; - this.device = device; - this.structure = structure; - this.structureId = structure.structure_id; - this.platform = platform; - - this.log('initing ' + this.deviceType + ' "' + this.name + '":', 'deviceId:', this.deviceId, 'structureId:', this.structureId); - this.log.debug(this.device); - - const id = uuid.generate('nest' + '.' + this.deviceType + '.' + this.deviceId); - Accessory.call(this, this.name, id); - this.uuid_base = id; - - this.getService(Service.AccessoryInformation) - .setCharacteristic(Characteristic.FirmwareRevision, this.device.software_version.toUpperCase()) - .setCharacteristic(Characteristic.Manufacturer, "Nest") - .setCharacteristic(Characteristic.Model, `${toTitleCase(this.deviceType)}`) - .setCharacteristic(Characteristic.Name, this.name) - .setCharacteristic(Characteristic.SerialNumber, this.device.name); - - this.boundCharacteristics = []; - - this.updateData(); + // device info + this.conn = conn; + this.name = device.name_long || device.name; + this.deviceId = device.device_id; + this.log = log; + this.device = device; + this.structure = structure; + this.structureId = structure.structure_id; + this.platform = platform; + + this.log('initing ' + this.deviceType + ' "' + this.name + '":', 'deviceId:', this.deviceId, 'structureId:', this.structureId); + this.log.debug(this.device); + + const id = uuid.generate('nest' + '.' + this.deviceType + '.' + this.deviceId); + Accessory.call(this, this.name, id); + this.uuid_base = id; + + this.getService(Service.AccessoryInformation) + .setCharacteristic(Characteristic.FirmwareRevision, this.device.software_version.toUpperCase()) + .setCharacteristic(Characteristic.Manufacturer, 'Nest') + .setCharacteristic(Characteristic.Model, `${toTitleCase(this.deviceType)}`) + .setCharacteristic(Characteristic.Name, this.name) + .setCharacteristic(Characteristic.SerialNumber, this.device.name); + + this.boundCharacteristics = []; + + this.updateData(); } NestDeviceAccessory.prototype.getServices = function () { - return this.services; + return this.services; }; NestDeviceAccessory.prototype.bindCharacteristic = function (service, characteristic, desc, getFunc, setFunc, format) { - const actual = service.getCharacteristic(characteristic) - .on('get', function (callback) { - const val = getFunc.bind(this)(); - if (callback) callback(null, val); - }.bind(this)) - .on('change', function (change) { - let disp = change.newValue; - if (format && disp !== null) { - disp = format(disp); - } - this.log.debug(desc + " for " + this.name + " is: " + disp); - }.bind(this)); - if (setFunc) { - actual.on('set', setFunc.bind(this)); - } - this.boundCharacteristics.push([service, characteristic]); + const actual = service.getCharacteristic(characteristic) + .on('get', function (callback) { + const val = getFunc.bind(this)(); + if (callback) callback(null, val); + }.bind(this)) + .on('change', function (change) { + let disp = change.newValue; + if (format && disp !== null) { + disp = format(disp); + } + this.log.debug(desc + ' for ' + this.name + ' is: ' + disp); + }.bind(this)); + if (setFunc) { + actual.on('set', setFunc.bind(this)); + } + this.boundCharacteristics.push([service, characteristic]); }; NestDeviceAccessory.prototype.updateData = function (device, structure) { - if (device) { - this.device = device; - } - if (structure) { - this.structure = structure; - } - this.boundCharacteristics.map(function (c) { - c[0].getCharacteristic(c[1]).getValue(); - }); + if (device) { + this.device = device; + } + if (structure) { + this.structure = structure; + } + this.boundCharacteristics.map(function (c) { + c[0].getCharacteristic(c[1]).getValue(); + }); }; NestDeviceAccessory.prototype.getDevicePropertyPath = function(property) { - return 'devices/' + this.deviceGroup + '/' + this.deviceId + '/' + property; + return 'devices/' + this.deviceGroup + '/' + this.deviceId + '/' + property; }; NestDeviceAccessory.prototype.updateDevicePropertyAsync = function(property, value, propertyDescription, valueDescription) { - propertyDescription = propertyDescription || property; - valueDescription = valueDescription || value; - this.log.debug("Setting " + propertyDescription + " for " + this.name + " to: " + valueDescription); - return this.conn.update(this.getDevicePropertyPath(property), value) - .return(value); + propertyDescription = propertyDescription || property; + valueDescription = valueDescription || value; + this.log.debug('Setting ' + propertyDescription + ' for ' + this.name + ' to: ' + valueDescription); + return this.conn.update(this.getDevicePropertyPath(property), value) + .return(value); }; NestDeviceAccessory.prototype.getStructurePropertyPath = function(property) { - return 'structures/' + this.structureId + '/' + property; + return 'structures/' + this.structureId + '/' + property; }; NestDeviceAccessory.prototype.isAway = function () { - switch (this.structure.away) { - case "home": - return false; - case "away": - return true; - case "auto-away": - return true; - default: - return false; - } + switch (this.structure.away) { + case 'home': + return false; + case 'away': + return true; + case 'auto-away': + return true; + default: + return false; + } }; NestDeviceAccessory.prototype.isEcoMode = function () { - return (this.device.hvac_mode === "eco"); + return (this.device.hvac_mode === 'eco'); }; NestDeviceAccessory.prototype.isFanTimerActive = function () { - return this.device.fan_timer_active; + return this.device.fan_timer_active; }; NestDeviceAccessory.prototype.isFanTimerDuration = function () { - return this.device.fan_timer_duration; + return this.device.fan_timer_duration; }; NestDeviceAccessory.prototype.isHasLeaf = function () { - return this.device.has_leaf; + return this.device.has_leaf; }; NestDeviceAccessory.prototype.isManualTestActive = function () { - return this.device.is_manual_test_active; + return this.device.is_manual_test_active; }; NestDeviceAccessory.prototype.isSunlightCorrectionEnabled = function () { - return this.device.sunlight_correction_enabled; + return this.device.sunlight_correction_enabled; }; NestDeviceAccessory.prototype.isSunlightCorrectionActive = function () { - return this.device.sunlight_correction_active; + return this.device.sunlight_correction_active; }; NestDeviceAccessory.prototype.isUsingEmergencyHeat = function () { - return this.device.is_using_emergency_heat; + return this.device.is_using_emergency_heat; }; NestDeviceAccessory.prototype.setAway = function (away, callback) { - const val = away ? 'away' : 'home'; - this.log.info("Setting Away for " + this.name + " to: " + val); - const promise = this.conn.update(this.getStructurePropertyPath("away"), val); - return promise - .return(away) - .asCallback(callback); + const val = away ? 'away' : 'home'; + this.log.info('Setting Away for ' + this.name + ' to: ' + val); + const promise = this.conn.update(this.getStructurePropertyPath('away'), val); + return promise + .return(away) + .asCallback(callback); }; NestDeviceAccessory.prototype.setEcoMode = function (eco, callback) { - const val = eco ? 'eco' : this.device.previous_hvac_mode; - this.log.info("Setting Eco Mode for " + this.name + " to: " + val); - return this.updateDevicePropertyAsync("hvac_mode", val, "target heating cooling") - .asCallback(callback); + const val = eco ? 'eco' : this.device.previous_hvac_mode; + this.log.info('Setting Eco Mode for ' + this.name + ' to: ' + val); + return this.updateDevicePropertyAsync('hvac_mode', val, 'target heating cooling') + .asCallback(callback); }; NestDeviceAccessory.prototype.setFanTimerActive = function (fan, callback) { - const val = fan; - this.log.info("Setting Fan Timer Active for " + this.name + " to: " + val); - if (this.device.hvac_mode !== "off" && this.device.hvac_state === "off") { - return this.updateDevicePropertyAsync("fan_timer_active", val, "fan timer active") - .asCallback(callback); - } + const val = fan; + this.log.info('Setting Fan Timer Active for ' + this.name + ' to: ' + val); + if (this.device.hvac_mode !== 'off' && this.device.hvac_state === 'off') { + return this.updateDevicePropertyAsync('fan_timer_active', val, 'fan timer active') + .asCallback(callback); + } }; NestDeviceAccessory.prototype.setFanTimerDuration = function (timer, callback) { - const val = timer; - this.log.info("Setting Fan Timer Duration for " + this.name + " to: " + val); - return this.updateDevicePropertyAsync("fan_timer_duration", val, "fan timer duration") - .asCallback(callback); + const val = timer; + this.log.info('Setting Fan Timer Duration for ' + this.name + ' to: ' + val); + return this.updateDevicePropertyAsync('fan_timer_duration', val, 'fan timer duration') + .asCallback(callback); }; NestDeviceAccessory.prototype.addAwayCharacteristic = function(service) { - service.addCharacteristic(Away); - this.bindCharacteristic(service, Away, "Away", this.isAway, this.setAway); + service.addCharacteristic(Away); + this.bindCharacteristic(service, Away, 'Away', this.isAway, this.setAway); }; NestDeviceAccessory.prototype.addEcoModeCharacteristic = function(service) { - service.addCharacteristic(EcoMode); - this.bindCharacteristic(service, EcoMode, "Eco Mode", this.isEcoMode, this.setEcoMode); + service.addCharacteristic(EcoMode); + this.bindCharacteristic(service, EcoMode, 'Eco Mode', this.isEcoMode, this.setEcoMode); }; NestDeviceAccessory.prototype.addFanTimerActiveCharacteristic = function(service) { - service.addCharacteristic(FanTimerActive); - this.bindCharacteristic(service, FanTimerActive, "Fan Timer Active", this.isFanTimerActive, this.setFanTimerActive); + service.addCharacteristic(FanTimerActive); + this.bindCharacteristic(service, FanTimerActive, 'Fan Timer Active', this.isFanTimerActive, this.setFanTimerActive); }; NestDeviceAccessory.prototype.addFanTimerDurationCharacteristic = function(service) { - service.addCharacteristic(FanTimerDuration); - this.bindCharacteristic(service, FanTimerDuration, "Fan Timer Duration", this.isFanTimerDuration, this.setFanTimerDuration); + service.addCharacteristic(FanTimerDuration); + this.bindCharacteristic(service, FanTimerDuration, 'Fan Timer Duration', this.isFanTimerDuration, this.setFanTimerDuration); }; NestDeviceAccessory.prototype.addHasLeafCharacteristic = function(service) { - service.addCharacteristic(HasLeaf); - this.bindCharacteristic(service, HasLeaf, "Has Leaf", this.isHasLeaf); + service.addCharacteristic(HasLeaf); + this.bindCharacteristic(service, HasLeaf, 'Has Leaf', this.isHasLeaf); }; NestDeviceAccessory.prototype.addManualTestActiveCharacteristic = function(service) { - service.addCharacteristic(ManualTestActive); - this.bindCharacteristic(service, ManualTestActive, "Manual Test Active", this.isManualTestActive); + service.addCharacteristic(ManualTestActive); + this.bindCharacteristic(service, ManualTestActive, 'Manual Test Active', this.isManualTestActive); }; NestDeviceAccessory.prototype.addSunlightCorrectionEnabledCharacteristic = function(service) { - service.addCharacteristic(SunlightCorrectionEnabled); - this.bindCharacteristic(service, SunlightCorrectionEnabled, "Sunlight Correction Enabled", this.isSunlightCorrectionEnabled); + service.addCharacteristic(SunlightCorrectionEnabled); + this.bindCharacteristic(service, SunlightCorrectionEnabled, 'Sunlight Correction Enabled', this.isSunlightCorrectionEnabled); }; NestDeviceAccessory.prototype.addSunlightCorrectionActiveCharacteristic = function(service) { - service.addCharacteristic(SunlightCorrectionActive); - this.bindCharacteristic(service, SunlightCorrectionActive, "Sunlight Correction Active", this.isSunlightCorrectionActive); + service.addCharacteristic(SunlightCorrectionActive); + this.bindCharacteristic(service, SunlightCorrectionActive, 'Sunlight Correction Active', this.isSunlightCorrectionActive); }; NestDeviceAccessory.prototype.addUsingEmergencyHeatCharacteristic = function(service) { - service.addCharacteristic(UsingEmergencyHeat); - this.bindCharacteristic(service, UsingEmergencyHeat, "Using Emergency Heat", this.isUsingEmergencyHeat); + service.addCharacteristic(UsingEmergencyHeat); + this.bindCharacteristic(service, UsingEmergencyHeat, 'Using Emergency Heat', this.isUsingEmergencyHeat); }; diff --git a/lib/nest-protect-accessory.js b/lib/nest-protect-accessory.js index b231f9c..0f87314 100644 --- a/lib/nest-protect-accessory.js +++ b/lib/nest-protect-accessory.js @@ -7,150 +7,162 @@ let Accessory, Service, Characteristic; // , Away, uuid; const NestDeviceAccessory = require('./nest-device-accessory')(); const AlarmState = { - ok: 1, - warning: 2, - emergency: 3 + ok: 1, + warning: 2, + emergency: 3 }; 'use strict'; module.exports = function(exportedTypes) { - if (exportedTypes && !Accessory) { - Accessory = exportedTypes.Accessory; - Service = exportedTypes.Service; - Characteristic = exportedTypes.Characteristic; + if (exportedTypes && !Accessory) { + Accessory = exportedTypes.Accessory; + Service = exportedTypes.Service; + Characteristic = exportedTypes.Characteristic; - /* + /* * uuid = exportedTypes.uuid; * Away = exportedTypes.Away; */ - const acc = NestProtectAccessory.prototype; - inherits(NestProtectAccessory, NestDeviceAccessory); - NestProtectAccessory.prototype.parent = NestDeviceAccessory.prototype; - for (const mn in acc) { - NestProtectAccessory.prototype[mn] = acc[mn]; + const acc = NestProtectAccessory.prototype; + inherits(NestProtectAccessory, NestDeviceAccessory); + NestProtectAccessory.prototype.parent = NestDeviceAccessory.prototype; + for (const mn in acc) { + NestProtectAccessory.prototype[mn] = acc[mn]; + } + + NestProtectAccessory.deviceType = 'protect'; + NestProtectAccessory.deviceGroup = 'smoke_co_alarms'; + NestProtectAccessory.prototype.deviceType = NestProtectAccessory.deviceType; + NestProtectAccessory.prototype.deviceGroup = NestProtectAccessory.deviceGroup; } - - NestProtectAccessory.deviceType = 'protect'; - NestProtectAccessory.deviceGroup = 'smoke_co_alarms'; - NestProtectAccessory.prototype.deviceType = NestProtectAccessory.deviceType; - NestProtectAccessory.prototype.deviceGroup = NestProtectAccessory.deviceGroup; - } - return NestProtectAccessory; + return NestProtectAccessory; }; function NestProtectAccessory(conn, log, device, structure, platform) { - NestDeviceAccessory.call(this, conn, log, device, structure, platform); - - const smokeSvc = this.addService(Service.SmokeSensor) - .setCharacteristic(Characteristic.Name, this.device.name + " " + "Smoke"); - this.bindCharacteristic(smokeSvc, Characteristic.SmokeDetected, "Smoke", - getSmokeAlarmState.bind(this), null, formatSmokeAlarmState.bind(this)); - this.bindCharacteristic(smokeSvc, Characteristic.StatusLowBattery, "Battery status (Smoke)", - getBatteryHealth.bind(this), null, formatStatusLowBattery.bind(this)); - this.bindCharacteristic(smokeSvc, Characteristic.StatusActive, "Online status (Smoke)", - getOnlineStatus.bind(this), null, formatOnlineStatus.bind(this)); - - const coSvc = this.addService(Service.CarbonMonoxideSensor) - .setCharacteristic(Characteristic.Name, this.device.name + " " + "Carbon Monoxide"); - this.bindCharacteristic(coSvc, Characteristic.CarbonMonoxideDetected, "Carbon Monoxide", - getCarbonMonoxideAlarmState.bind(this), null, formatCarbonMonoxideAlarmState.bind(this)); - this.bindCharacteristic(coSvc, Characteristic.StatusLowBattery, "Battery status (CO)", - getBatteryHealth.bind(this), null, formatStatusLowBattery.bind(this)); - this.bindCharacteristic(coSvc, Characteristic.StatusActive, "Online status (CO)", - getOnlineStatus.bind(this), null, formatOnlineStatus.bind(this)); - - this.addManualTestActiveCharacteristic(smokeSvc); - - this.addManualTestActiveCharacteristic(coSvc); - - this.updateData(); + NestDeviceAccessory.call(this, conn, log, device, structure, platform); + + const smokeSvc = this.addService(Service.SmokeSensor) + .setCharacteristic(Characteristic.Name, this.device.name + ' ' + 'Smoke'); + this.bindCharacteristic(smokeSvc, Characteristic.SmokeDetected, 'Smoke', + getSmokeAlarmState.bind(this), null, formatSmokeAlarmState.bind(this)); + this.bindCharacteristic(smokeSvc, Characteristic.StatusLowBattery, 'Battery status (Smoke)', + getBatteryHealth.bind(this), null, formatStatusLowBattery.bind(this)); + this.bindCharacteristic(smokeSvc, Characteristic.StatusActive, 'Online status (Smoke)', + getOnlineStatus.bind(this), null, formatOnlineStatus.bind(this)); + + const coSvc = this.addService(Service.CarbonMonoxideSensor) + .setCharacteristic(Characteristic.Name, this.device.name + ' ' + 'Carbon Monoxide'); + this.bindCharacteristic(coSvc, Characteristic.CarbonMonoxideDetected, 'Carbon Monoxide', + getCarbonMonoxideAlarmState.bind(this), null, formatCarbonMonoxideAlarmState.bind(this)); + this.bindCharacteristic(coSvc, Characteristic.StatusLowBattery, 'Battery status (CO)', + getBatteryHealth.bind(this), null, formatStatusLowBattery.bind(this)); + this.bindCharacteristic(coSvc, Characteristic.StatusActive, 'Online status (CO)', + getOnlineStatus.bind(this), null, formatOnlineStatus.bind(this)); + + if (this.platform.shouldEnableFeature('Protect.Home')) { + const homeService = this.addService(Service.OccupancySensor, 'Home Occupied', 'home_occupied'); + this.bindCharacteristic(homeService, Characteristic.OccupancyDetected, 'Home Occupied', getHomeStatus.bind(this), null); + } + + this.addManualTestActiveCharacteristic(smokeSvc); + + this.addManualTestActiveCharacteristic(coSvc); + + this.updateData(); } // --- SmokeAlarmState --- const getSmokeAlarmState = function() { - switch (AlarmState[this.device.smoke_alarm_state]) { - case AlarmState.ok: - return Characteristic.SmokeDetected.SMOKE_NOT_DETECTED; - default: - return Characteristic.SmokeDetected.SMOKE_DETECTED; - } + switch (AlarmState[this.device.smoke_alarm_state]) { + case AlarmState.ok: + return Characteristic.SmokeDetected.SMOKE_NOT_DETECTED; + default: + return Characteristic.SmokeDetected.SMOKE_DETECTED; + } }; const formatSmokeAlarmState = function(val) { - switch (val) { - case Characteristic.SmokeDetected.SMOKE_NOT_DETECTED: - return "not detected"; - case Characteristic.SmokeDetected.SMOKE_DETECTED: - return "detected"; - default: - return "unknown (" + val + ")"; - } + switch (val) { + case Characteristic.SmokeDetected.SMOKE_NOT_DETECTED: + return 'not detected'; + case Characteristic.SmokeDetected.SMOKE_DETECTED: + return 'detected'; + default: + return 'unknown (' + val + ')'; + } }; // --- CarbonMonoxideAlarmState --- const getCarbonMonoxideAlarmState = function() { - switch (AlarmState[this.device.co_alarm_state]) { - case AlarmState.ok: - return Characteristic.CarbonMonoxideDetected.CO_LEVELS_NORMAL; - default: - return Characteristic.CarbonMonoxideDetected.CO_LEVELS_ABNORMAL; - } + switch (AlarmState[this.device.co_alarm_state]) { + case AlarmState.ok: + return Characteristic.CarbonMonoxideDetected.CO_LEVELS_NORMAL; + default: + return Characteristic.CarbonMonoxideDetected.CO_LEVELS_ABNORMAL; + } }; const formatCarbonMonoxideAlarmState = function(val) { - switch (val) { - case Characteristic.CarbonMonoxideDetected.CO_LEVELS_NORMAL: - return "normal"; - case Characteristic.CarbonMonoxideDetected.CO_LEVELS_ABNORMAL: - return "abnormal"; - default: - return "unknown (" + val + ")"; - } + switch (val) { + case Characteristic.CarbonMonoxideDetected.CO_LEVELS_NORMAL: + return 'normal'; + case Characteristic.CarbonMonoxideDetected.CO_LEVELS_ABNORMAL: + return 'abnormal'; + default: + return 'unknown (' + val + ')'; + } }; // --- BatteryHealth --- const getBatteryHealth = function () { - switch (this.device.battery_health) { - case 'ok': - return Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - default: - return Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW; - } + switch (this.device.battery_health) { + case 'ok': + return Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; + default: + return Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW; + } }; const formatStatusLowBattery = function (val) { - switch (val) { - case Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL: - return 'normal'; - case Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW: - return 'low'; - default: - return 'unknown (' + val + ')'; - } + switch (val) { + case Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL: + return 'normal'; + case Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW: + return 'low'; + default: + return 'unknown (' + val + ')'; + } }; // --- OnlineStatus --- const getOnlineStatus = function () { - return this.device.is_online; + return this.device.is_online; }; const formatOnlineStatus = function (val) { - switch (val) { - case true: - return 'online'; - case false: - return 'offline'; - default: - return 'unknown (' + val + ')'; - } + switch (val) { + case true: + return 'online'; + case false: + return 'offline'; + default: + return 'unknown (' + val + ')'; + } }; + +// --- getHomeStatus --- + +const getHomeStatus = function () { + return !(this.structure.topaz_away); +}; + diff --git a/lib/nest-thermostat-accessory.js b/lib/nest-thermostat-accessory.js index e99c291..8fc3340 100644 --- a/lib/nest-thermostat-accessory.js +++ b/lib/nest-thermostat-accessory.js @@ -11,434 +11,434 @@ const NestDeviceAccessory = require('./nest-device-accessory')(); 'use strict'; module.exports = function(exportedTypes) { - if (exportedTypes && !Accessory) { - Accessory = exportedTypes.Accessory; - Service = exportedTypes.Service; - Characteristic = exportedTypes.Characteristic; - // uuid = exportedTypes.uuid; - - const acc = NestThermostatAccessory.prototype; - inherits(NestThermostatAccessory, NestDeviceAccessory); - NestThermostatAccessory.prototype.parent = NestDeviceAccessory.prototype; - for (const mn in acc) { - NestThermostatAccessory.prototype[mn] = acc[mn]; + if (exportedTypes && !Accessory) { + Accessory = exportedTypes.Accessory; + Service = exportedTypes.Service; + Characteristic = exportedTypes.Characteristic; + // uuid = exportedTypes.uuid; + + const acc = NestThermostatAccessory.prototype; + inherits(NestThermostatAccessory, NestDeviceAccessory); + NestThermostatAccessory.prototype.parent = NestDeviceAccessory.prototype; + for (const mn in acc) { + NestThermostatAccessory.prototype[mn] = acc[mn]; + } + + NestThermostatAccessory.deviceType = 'thermostat'; + NestThermostatAccessory.deviceGroup = 'thermostats'; + NestThermostatAccessory.prototype.deviceType = NestThermostatAccessory.deviceType; + NestThermostatAccessory.prototype.deviceGroup = NestThermostatAccessory.deviceGroup; } - - NestThermostatAccessory.deviceType = 'thermostat'; - NestThermostatAccessory.deviceGroup = 'thermostats'; - NestThermostatAccessory.prototype.deviceType = NestThermostatAccessory.deviceType; - NestThermostatAccessory.prototype.deviceGroup = NestThermostatAccessory.deviceGroup; - } - return NestThermostatAccessory; + return NestThermostatAccessory; }; function NestThermostatAccessory(conn, log, device, structure, platform) { - NestDeviceAccessory.call(this, conn, log, device, structure, platform); - - const thermostatService = this.addService(Service.Thermostat); - - const formatAsDisplayTemperature = function(t) { - return t + " °C and " + celsiusToFahrenheit(t) + " °F"; - }; - - const formatCurrentHeatingCoolingState = function (val) { - switch (val) { - case Characteristic.CurrentHeatingCoolingState.OFF: - return "Off"; - case Characteristic.CurrentHeatingCoolingState.HEAT: - return "Heating"; - case Characteristic.CurrentHeatingCoolingState.COOL: - return "Cooling"; - } - }; + NestDeviceAccessory.call(this, conn, log, device, structure, platform); - const formatTargetHeatingCoolingState = function (val) { - switch (val) { - case Characteristic.TargetHeatingCoolingState.OFF: - return "Off"; - case Characteristic.TargetHeatingCoolingState.HEAT: - return "Heat"; - case Characteristic.TargetHeatingCoolingState.COOL: - return "Cool"; - case Characteristic.TargetHeatingCoolingState.AUTO: - return "Auto"; - } - }; + const thermostatService = this.addService(Service.Thermostat); - const bindCharacteristic = function (characteristic, desc, getFunc, setFunc, format) { - this.bindCharacteristic(thermostatService, characteristic, desc, getFunc, setFunc, format); - }.bind(this); + const formatAsDisplayTemperature = function(t) { + return t + ' °C and ' + celsiusToFahrenheit(t) + ' °F'; + }; - bindCharacteristic(Characteristic.TemperatureDisplayUnits, "Temperature unit", this.getTemperatureUnits, this.setTemperatureUnits, function (val) { - return val == Characteristic.TemperatureDisplayUnits.FAHRENHEIT ? "Fahrenheit" : "Celsius"; - }); + const formatCurrentHeatingCoolingState = function (val) { + switch (val) { + case Characteristic.CurrentHeatingCoolingState.OFF: + return 'Off'; + case Characteristic.CurrentHeatingCoolingState.HEAT: + return 'Heating'; + case Characteristic.CurrentHeatingCoolingState.COOL: + return 'Cooling'; + } + }; + + const formatTargetHeatingCoolingState = function (val) { + switch (val) { + case Characteristic.TargetHeatingCoolingState.OFF: + return 'Off'; + case Characteristic.TargetHeatingCoolingState.HEAT: + return 'Heat'; + case Characteristic.TargetHeatingCoolingState.COOL: + return 'Cool'; + case Characteristic.TargetHeatingCoolingState.AUTO: + return 'Auto'; + } + }; + + const bindCharacteristic = function (characteristic, desc, getFunc, setFunc, format) { + this.bindCharacteristic(thermostatService, characteristic, desc, getFunc, setFunc, format); + }.bind(this); - bindCharacteristic(Characteristic.CurrentTemperature, "Current temperature", this.getCurrentTemperature, null, formatAsDisplayTemperature); - bindCharacteristic(Characteristic.CurrentHeatingCoolingState, "Current heating/cooling state", this.getCurrentHeatingCooling, null, formatCurrentHeatingCoolingState); - bindCharacteristic(Characteristic.CurrentRelativeHumidity, "Current humidity", this.getCurrentRelativeHumidity, null, function(val) { - return val + "%"; - }); + bindCharacteristic(Characteristic.TemperatureDisplayUnits, 'Temperature unit', this.getTemperatureUnits, this.setTemperatureUnits, function (val) { + return val == Characteristic.TemperatureDisplayUnits.FAHRENHEIT ? 'Fahrenheit' : 'Celsius'; + }); + + bindCharacteristic(Characteristic.CurrentTemperature, 'Current temperature', this.getCurrentTemperature, null, formatAsDisplayTemperature); + bindCharacteristic(Characteristic.CurrentHeatingCoolingState, 'Current heating/cooling state', this.getCurrentHeatingCooling, null, formatCurrentHeatingCoolingState); + bindCharacteristic(Characteristic.CurrentRelativeHumidity, 'Current humidity', this.getCurrentRelativeHumidity, null, function(val) { + return val + '%'; + }); - /* + /* * Only allow 0.5 increments for Celsius temperatures. HomeKit is already limited to 1-degree increments in Fahrenheit, * and setting this value for Fahrenheit will cause HomeKit to incorrectly round values when converting from °F to °C and back. */ - if (!this.usesFahrenheit()) { - thermostatService.getCharacteristic(Characteristic.CurrentTemperature) - .setProps({ - minStep: 0.5 - }); - thermostatService.getCharacteristic(Characteristic.TargetTemperature) - .setProps({ - minStep: 0.5 - }); - thermostatService.getCharacteristic(Characteristic.CoolingThresholdTemperature) - .setProps({ - minStep: 0.5 - }); - thermostatService.getCharacteristic(Characteristic.HeatingThresholdTemperature) - .setProps({ - minStep: 0.5 - }); - } - - if (!this.device.can_cool) { - thermostatService.getCharacteristic(Characteristic.TargetHeatingCoolingState).setProps({validValues: [Characteristic.TargetHeatingCoolingState.OFF, Characteristic.TargetHeatingCoolingState.HEAT]}); - } else if (!this.device.can_heat) { - thermostatService.getCharacteristic(Characteristic.TargetHeatingCoolingState).setProps({validValues: [Characteristic.TargetHeatingCoolingState.OFF, Characteristic.TargetHeatingCoolingState.COOL]}); - } - - bindCharacteristic(Characteristic.TargetTemperature, "Target temperature", this.getTargetTemperature, this.setTargetTemperature, formatAsDisplayTemperature); - bindCharacteristic(Characteristic.TargetHeatingCoolingState, "Target heating/cooling state", this.getTargetHeatingCooling, this.setTargetHeatingCooling, formatTargetHeatingCoolingState); - - bindCharacteristic(Characteristic.CoolingThresholdTemperature, "Cooling threshold temperature", this.getCoolingThresholdTemperature, this.setCoolingThresholdTemperature, formatAsDisplayTemperature); - bindCharacteristic(Characteristic.HeatingThresholdTemperature, "Heating threshold temperature", this.getHeatingThresholdTemperature, this.setHeatingThresholdTemperature, formatAsDisplayTemperature); - - if (this.device.has_fan && this.platform.shouldEnableFeature("Thermostat.Fan")) { - const thermostatFanService = this.addService(Service.Fan); - const formatFanState = function (val) { - if (val) { - return "On"; - } - return "Off"; - }; - this.bindCharacteristic(thermostatFanService, Characteristic.On, "Fan State", this.getFanState, this.setFanState, formatFanState); - } + if (!this.usesFahrenheit()) { + thermostatService.getCharacteristic(Characteristic.CurrentTemperature) + .setProps({ + minStep: 0.5 + }); + thermostatService.getCharacteristic(Characteristic.TargetTemperature) + .setProps({ + minStep: 0.5 + }); + thermostatService.getCharacteristic(Characteristic.CoolingThresholdTemperature) + .setProps({ + minStep: 0.5 + }); + thermostatService.getCharacteristic(Characteristic.HeatingThresholdTemperature) + .setProps({ + minStep: 0.5 + }); + } - if (this.platform.shouldEnableFeature("Thermostat.Home")) { - const homeService = this.addService(Service.Switch, "Home Occupied", "home_occupied"); - this.bindCharacteristic(homeService, Characteristic.On, "Home Occupied", this.getHome, this.setHome); - } + if (!this.device.can_cool) { + thermostatService.getCharacteristic(Characteristic.TargetHeatingCoolingState).setProps({validValues: [Characteristic.TargetHeatingCoolingState.OFF, Characteristic.TargetHeatingCoolingState.HEAT]}); + } else if (!this.device.can_heat) { + thermostatService.getCharacteristic(Characteristic.TargetHeatingCoolingState).setProps({validValues: [Characteristic.TargetHeatingCoolingState.OFF, Characteristic.TargetHeatingCoolingState.COOL]}); + } - if (this.platform.shouldEnableFeature("Thermostat.Eco")) { - const thermostatEcoModeService = this.addService(Service.Switch, "Eco Mode", "eco_mode"); - this.bindCharacteristic(thermostatEcoModeService, Characteristic.On, "Eco Mode", this.getEcoMode, this.setEcoMode); - } + bindCharacteristic(Characteristic.TargetTemperature, 'Target temperature', this.getTargetTemperature, this.setTargetTemperature, formatAsDisplayTemperature); + bindCharacteristic(Characteristic.TargetHeatingCoolingState, 'Target heating/cooling state', this.getTargetHeatingCooling, this.setTargetHeatingCooling, formatTargetHeatingCoolingState); + + bindCharacteristic(Characteristic.CoolingThresholdTemperature, 'Cooling threshold temperature', this.getCoolingThresholdTemperature, this.setCoolingThresholdTemperature, formatAsDisplayTemperature); + bindCharacteristic(Characteristic.HeatingThresholdTemperature, 'Heating threshold temperature', this.getHeatingThresholdTemperature, this.setHeatingThresholdTemperature, formatAsDisplayTemperature); + + if (this.device.has_fan && this.platform.shouldEnableFeature('Thermostat.Fan')) { + const thermostatFanService = this.addService(Service.Fan); + const formatFanState = function (val) { + if (val) { + return 'On'; + } + return 'Off'; + }; + this.bindCharacteristic(thermostatFanService, Characteristic.On, 'Fan State', this.getFanState, this.setFanState, formatFanState); + } - // Add custom characteristics + if (this.platform.shouldEnableFeature('Thermostat.Home')) { + const homeService = this.addService(Service.Switch, 'Home Occupied', 'home_occupied'); + this.bindCharacteristic(homeService, Characteristic.On, 'Home Occupied', this.getHome, this.setHome); + } - this.addAwayCharacteristic(thermostatService); // legacy: now exposed as a Switch - this.addEcoModeCharacteristic(thermostatService); // legacy: now exposed as a Switch + if (this.platform.shouldEnableFeature('Thermostat.Eco')) { + const thermostatEcoModeService = this.addService(Service.Switch, 'Eco Mode', 'eco_mode'); + this.bindCharacteristic(thermostatEcoModeService, Characteristic.On, 'Eco Mode', this.getEcoMode, this.setEcoMode); + } - if (this.device.has_fan === true) { // legacy: now exposed as a Fan - this.addFanTimerActiveCharacteristic(thermostatService); - this.addFanTimerDurationCharacteristic(thermostatService); - } + // Add custom characteristics - this.addHasLeafCharacteristic(thermostatService); + this.addAwayCharacteristic(thermostatService); // legacy: now exposed as a Switch + this.addEcoModeCharacteristic(thermostatService); // legacy: now exposed as a Switch - this.addSunlightCorrectionEnabledCharacteristic(thermostatService); + if (this.device.has_fan === true) { // legacy: now exposed as a Fan + this.addFanTimerActiveCharacteristic(thermostatService); + this.addFanTimerDurationCharacteristic(thermostatService); + } + + this.addHasLeafCharacteristic(thermostatService); - this.addSunlightCorrectionActiveCharacteristic(thermostatService); + this.addSunlightCorrectionEnabledCharacteristic(thermostatService); - this.addUsingEmergencyHeatCharacteristic(thermostatService); + this.addSunlightCorrectionActiveCharacteristic(thermostatService); - this.updateData(); + this.addUsingEmergencyHeatCharacteristic(thermostatService); + + this.updateData(); } function fahrenheitToCelsius(temperature) { - return (temperature - 32) / 1.8; + return (temperature - 32) / 1.8; } function celsiusToFahrenheit(temperature) { - return (temperature * 1.8) + 32; + return (temperature * 1.8) + 32; } NestThermostatAccessory.prototype.usesFahrenheit = function () { - return this.getTemperatureUnits() === Characteristic.TemperatureDisplayUnits.FAHRENHEIT; + return this.getTemperatureUnits() === Characteristic.TemperatureDisplayUnits.FAHRENHEIT; }; // can't trust the *_c values all the time. see https://github.com/chrisjshull/homebridge-nest/issues/15 NestThermostatAccessory.prototype.getTemperatureValueInCelsius = function (key) { - key += (this.usesFahrenheit() ? "f" : "c"); - let value = this.device[key]; - if (this.usesFahrenheit()) { - value = fahrenheitToCelsius(value); - } - return value; + key += (this.usesFahrenheit() ? 'f' : 'c'); + let value = this.device[key]; + if (this.usesFahrenheit()) { + value = fahrenheitToCelsius(value); + } + return value; }; NestThermostatAccessory.prototype.getCurrentHeatingCooling = function () { - switch (this.device.hvac_state) { - case "heating": - return Characteristic.CurrentHeatingCoolingState.HEAT; - case "cooling": - return Characteristic.CurrentHeatingCoolingState.COOL; - case "off": - default: - return Characteristic.CurrentHeatingCoolingState.OFF; - } + switch (this.device.hvac_state) { + case 'heating': + return Characteristic.CurrentHeatingCoolingState.HEAT; + case 'cooling': + return Characteristic.CurrentHeatingCoolingState.COOL; + case 'off': + default: + return Characteristic.CurrentHeatingCoolingState.OFF; + } }; NestThermostatAccessory.prototype.getTargetHeatingCooling = function () { - switch (this.device.hvac_mode) { - case "heat": - return Characteristic.TargetHeatingCoolingState.HEAT; - case "cool": - return Characteristic.TargetHeatingCoolingState.COOL; - case "heat-cool": - case "eco": - if (!this.device.can_cool) { - return Characteristic.TargetHeatingCoolingState.HEAT; - } else if (!this.device.can_heat) { - return Characteristic.TargetHeatingCoolingState.COOL; + switch (this.device.hvac_mode) { + case 'heat': + return Characteristic.TargetHeatingCoolingState.HEAT; + case 'cool': + return Characteristic.TargetHeatingCoolingState.COOL; + case 'heat-cool': + case 'eco': + if (!this.device.can_cool) { + return Characteristic.TargetHeatingCoolingState.HEAT; + } else if (!this.device.can_heat) { + return Characteristic.TargetHeatingCoolingState.COOL; + } + return Characteristic.TargetHeatingCoolingState.AUTO; + case 'off': + default: + return Characteristic.TargetHeatingCoolingState.OFF; } - return Characteristic.TargetHeatingCoolingState.AUTO; - case "off": - default: - return Characteristic.TargetHeatingCoolingState.OFF; - } }; NestThermostatAccessory.prototype.getCurrentTemperature = function () { - return this.getTemperatureValueInCelsius('ambient_temperature_'); + return this.getTemperatureValueInCelsius('ambient_temperature_'); }; NestThermostatAccessory.prototype.getCurrentRelativeHumidity = function () { - return this.device.humidity; + return this.device.humidity; }; // Siri will use this even when in AUTO mode NestThermostatAccessory.prototype.getTargetTemperature = function () { - switch (this.device.hvac_mode) { - case "heat-cool": - case "eco": - if (!this.device.can_cool) { - return this.getHeatingThresholdTemperature(); - } else if (!this.device.can_heat) { - return this.getCoolingThresholdTemperature(); - } - - switch (this.device.hvac_state) { - case "heating": - return this.getHeatingThresholdTemperature(); - case "cooling": - return this.getCoolingThresholdTemperature(); - case "off": + switch (this.device.hvac_mode) { + case 'heat-cool': + case 'eco': + if (!this.device.can_cool) { + return this.getHeatingThresholdTemperature(); + } else if (!this.device.can_heat) { + return this.getCoolingThresholdTemperature(); + } + + switch (this.device.hvac_state) { + case 'heating': + return this.getHeatingThresholdTemperature(); + case 'cooling': + return this.getCoolingThresholdTemperature(); + case 'off': + default: + return this.getCurrentTemperature(); + } default: - return this.getCurrentTemperature(); + return this.getTemperatureValueInCelsius('target_temperature_'); } - default: - return this.getTemperatureValueInCelsius('target_temperature_'); - } }; NestThermostatAccessory.prototype.getCoolingThresholdTemperature = function () { - switch (this.device.hvac_mode) { - case "eco": + switch (this.device.hvac_mode) { + case 'eco': // away_temperature deprecated in v5. in v6 use eco_temperature but if undefined, fallback to away_temperature - return this.getTemperatureValueInCelsius('eco_temperature_high_') || this.getTemperatureValueInCelsius('away_temperature_high_'); - case "heat-cool": - default: - return this.getTemperatureValueInCelsius('target_temperature_high_'); - } + return this.getTemperatureValueInCelsius('eco_temperature_high_') || this.getTemperatureValueInCelsius('away_temperature_high_'); + case 'heat-cool': + default: + return this.getTemperatureValueInCelsius('target_temperature_high_'); + } }; NestThermostatAccessory.prototype.getHeatingThresholdTemperature = function () { - switch (this.device.hvac_mode) { - case "eco": + switch (this.device.hvac_mode) { + case 'eco': // away_temperature deprecated in v5. in v6 use eco_temperature but if undefined, fallback to away_temperature - return this.getTemperatureValueInCelsius('eco_temperature_low_') || this.getTemperatureValueInCelsius('away_temperature_low_'); - case "heat-cool": - default: - return this.getTemperatureValueInCelsius('target_temperature_low_'); - } + return this.getTemperatureValueInCelsius('eco_temperature_low_') || this.getTemperatureValueInCelsius('away_temperature_low_'); + case 'heat-cool': + default: + return this.getTemperatureValueInCelsius('target_temperature_low_'); + } }; NestThermostatAccessory.prototype.getTemperatureUnits = function () { - switch (this.device.temperature_scale) { - case "C": - return Characteristic.TemperatureDisplayUnits.CELSIUS; - case "F": - return Characteristic.TemperatureDisplayUnits.FAHRENHEIT; - default: - return Characteristic.TemperatureDisplayUnits.CELSIUS; - } + switch (this.device.temperature_scale) { + case 'C': + return Characteristic.TemperatureDisplayUnits.CELSIUS; + case 'F': + return Characteristic.TemperatureDisplayUnits.FAHRENHEIT; + default: + return Characteristic.TemperatureDisplayUnits.CELSIUS; + } }; NestThermostatAccessory.prototype.setTemperatureUnits = function (temperatureUnits, callback) { - let val = null; - - switch (temperatureUnits) { - case Characteristic.TemperatureDisplayUnits.CELSIUS: - val = 'C'; - break; - case Characteristic.TemperatureDisplayUnits.FAHRENHEIT: - val = 'F'; - break; - default: - val = 'C'; - break; - } - - return this.updateDevicePropertyAsync("temperature_scale", val, "temperature display units") - .asCallback(callback); + let val = null; + + switch (temperatureUnits) { + case Characteristic.TemperatureDisplayUnits.CELSIUS: + val = 'C'; + break; + case Characteristic.TemperatureDisplayUnits.FAHRENHEIT: + val = 'F'; + break; + default: + val = 'C'; + break; + } + + return this.updateDevicePropertyAsync('temperature_scale', val, 'temperature display units') + .asCallback(callback); }; NestThermostatAccessory.prototype.setTargetHeatingCooling = function (targetHeatingCooling, callback) { - let val = "off"; - - switch (targetHeatingCooling) { - case Characteristic.TargetHeatingCoolingState.HEAT: - val = 'heat'; - break; - case Characteristic.TargetHeatingCoolingState.COOL: - val = 'cool'; - break; - case Characteristic.TargetHeatingCoolingState.AUTO: - val = 'heat-cool'; - break; - case Characteristic.TargetHeatingCoolingState.OFF: - default: - break; - } - - return this.updateDevicePropertyAsync("hvac_mode", val, "target heating cooling").asCallback(callback); + let val = 'off'; + + switch (targetHeatingCooling) { + case Characteristic.TargetHeatingCoolingState.HEAT: + val = 'heat'; + break; + case Characteristic.TargetHeatingCoolingState.COOL: + val = 'cool'; + break; + case Characteristic.TargetHeatingCoolingState.AUTO: + val = 'heat-cool'; + break; + case Characteristic.TargetHeatingCoolingState.OFF: + default: + break; + } + + return this.updateDevicePropertyAsync('hvac_mode', val, 'target heating cooling').asCallback(callback); }; // Note: HomeKit is not smart enough to avoid sending every temp change while waiting for callback to invoke NestThermostatAccessory.prototype.setTargetTemperature = function(targetTemperature, callback) { - this.log('Queuing to set temperature ' + targetTemperature); - this.setTargetTemperatureDebounced(targetTemperature, function(error) { - this.log('Temperature set to ' + targetTemperature, error); - }.bind(this)); - callback(); + this.log('Queuing to set temperature ' + targetTemperature); + this.setTargetTemperatureDebounced(targetTemperature, function(error) { + this.log('Temperature set to ' + targetTemperature, error); + }.bind(this)); + callback(); }; NestThermostatAccessory.prototype.setCoolingThresholdTemperature = function(targetTemperature, callback) { - this.log('Queuing to set cooling threshold temperature ' + targetTemperature); - this.setCoolingThresholdTemperatureDebounced(targetTemperature, function(error) { - this.log('Cooling threshold temperature set to ' + targetTemperature, error); - }.bind(this)); - callback(); + this.log('Queuing to set cooling threshold temperature ' + targetTemperature); + this.setCoolingThresholdTemperatureDebounced(targetTemperature, function(error) { + this.log('Cooling threshold temperature set to ' + targetTemperature, error); + }.bind(this)); + callback(); }; NestThermostatAccessory.prototype.setHeatingThresholdTemperature = function(targetTemperature, callback) { - this.log('Queuing to set heating threshold temperature ' + targetTemperature); - this.setHeatingThresholdTemperatureDebounced(targetTemperature, function(error) { - this.log('Heating threshold temperature set to ' + targetTemperature, error); - }.bind(this)); - callback(); + this.log('Queuing to set heating threshold temperature ' + targetTemperature); + this.setHeatingThresholdTemperatureDebounced(targetTemperature, function(error) { + this.log('Heating threshold temperature set to ' + targetTemperature, error); + }.bind(this)); + callback(); }; // todo: why does this sometimes reset while dragging? (change event coming in while new change queued?) NestThermostatAccessory.prototype.setTargetTemperatureDebounced = debounce(function (targetTemperature, callback) { - const ambient = this.getCurrentTemperature(); - let setting = "target_temperature_"; - let mode = this.device.hvac_mode; - let promise = Promise.resolve(); - - if (mode === 'eco') { - mode = this.device.previous_hvac_mode; - promise = promise.then(() => this.updateDevicePropertyAsync("hvac_mode", mode, "target heating cooling")); - } - - if (mode === 'off') { - if (ambient < targetTemperature) { - promise = promise.then(() => this.updateDevicePropertyAsync("hvac_mode", 'heat', "target heating cooling")); - } else if (ambient > targetTemperature) { - promise = promise.then(() => this.updateDevicePropertyAsync("hvac_mode", 'cool', "target heating cooling")); - } else { - return void callback(); + const ambient = this.getCurrentTemperature(); + let setting = 'target_temperature_'; + let mode = this.device.hvac_mode; + let promise = Promise.resolve(); + + if (mode === 'eco') { + mode = this.device.previous_hvac_mode; + promise = promise.then(() => this.updateDevicePropertyAsync('hvac_mode', mode, 'target heating cooling')); } - } else if (mode === 'heat-cool') { - const targetHigh = this.getCoolingThresholdTemperature(); - const targetLow = this.getHeatingThresholdTemperature(); - if (targetTemperature < targetHigh && targetTemperature > targetLow) { - if (ambient < targetTemperature) { - setting = 'target_temperature_low_'; - } else if (ambient > targetTemperature) { - setting = 'target_temperature_high_'; - } - } else if (targetTemperature > targetHigh) { - setting = 'target_temperature_high_'; - } else if (targetTemperature < targetLow) { - setting = 'target_temperature_low_'; - } else { - return void callback(); + + if (mode === 'off') { + if (ambient < targetTemperature) { + promise = promise.then(() => this.updateDevicePropertyAsync('hvac_mode', 'heat', 'target heating cooling')); + } else if (ambient > targetTemperature) { + promise = promise.then(() => this.updateDevicePropertyAsync('hvac_mode', 'cool', 'target heating cooling')); + } else { + return void callback(); + } + } else if (mode === 'heat-cool') { + const targetHigh = this.getCoolingThresholdTemperature(); + const targetLow = this.getHeatingThresholdTemperature(); + if (targetTemperature < targetHigh && targetTemperature > targetLow) { + if (ambient < targetTemperature) { + setting = 'target_temperature_low_'; + } else if (ambient > targetTemperature) { + setting = 'target_temperature_high_'; + } + } else if (targetTemperature > targetHigh) { + setting = 'target_temperature_high_'; + } else if (targetTemperature < targetLow) { + setting = 'target_temperature_low_'; + } else { + return void callback(); + } } - } - return promise.then(() => this.updateTargetWithCorrectUnitsAsync(setting, targetTemperature, "target temperature")).asCallback(callback); + return promise.then(() => this.updateTargetWithCorrectUnitsAsync(setting, targetTemperature, 'target temperature')).asCallback(callback); }, 5000); NestThermostatAccessory.prototype.setCoolingThresholdTemperatureDebounced = debounce(function (targetTemperature, callback) { - const setting = this.device.hvac_mode === 'heat-cool' ? 'target_temperature_high_' : 'target_temperature_'; - return this.updateTargetWithCorrectUnitsAsync(setting, targetTemperature, "cooling threshold temperature").asCallback(callback); + const setting = this.device.hvac_mode === 'heat-cool' ? 'target_temperature_high_' : 'target_temperature_'; + return this.updateTargetWithCorrectUnitsAsync(setting, targetTemperature, 'cooling threshold temperature').asCallback(callback); }, 5000); NestThermostatAccessory.prototype.setHeatingThresholdTemperatureDebounced = debounce(function (targetTemperature, callback) { - const setting = this.device.hvac_mode === 'heat-cool' ? 'target_temperature_low_' : 'target_temperature_'; - this.updateTargetWithCorrectUnitsAsync(setting, targetTemperature, "heating threshold temperature").asCallback(callback); + const setting = this.device.hvac_mode === 'heat-cool' ? 'target_temperature_low_' : 'target_temperature_'; + this.updateTargetWithCorrectUnitsAsync(setting, targetTemperature, 'heating threshold temperature').asCallback(callback); }, 5000); NestThermostatAccessory.prototype.updateTargetWithCorrectUnitsAsync = function (key, celsius, str) { - const usesFahrenheit = this.usesFahrenheit(); - key += (usesFahrenheit ? "f" : "c"); - const targetTemperature = usesFahrenheit ? Math.round(celsiusToFahrenheit(celsius)) : celsius; - return this.updateDevicePropertyAsync(key, targetTemperature, str); + const usesFahrenheit = this.usesFahrenheit(); + key += (usesFahrenheit ? 'f' : 'c'); + const targetTemperature = usesFahrenheit ? Math.round(celsiusToFahrenheit(celsius)) : celsius; + return this.updateDevicePropertyAsync(key, targetTemperature, str); }; NestThermostatAccessory.prototype.getFanState = function () { - return this.device.fan_timer_active; + return this.device.fan_timer_active; }; NestThermostatAccessory.prototype.setFanState = function (targetFanState, callback) { - this.log("Setting target fan state for " + this.name + " to: " + targetFanState); + this.log('Setting target fan state for ' + this.name + ' to: ' + targetFanState); - this.updateDevicePropertyAsync("fan_timer_active", Boolean(targetFanState), "fan enable/disable") - .asCallback(function () { - setTimeout(callback, 3000, ...arguments); // fan seems to "flicker" when you first enable it - }); + this.updateDevicePropertyAsync('fan_timer_active', Boolean(targetFanState), 'fan enable/disable') + .asCallback(function () { + setTimeout(callback, 3000, ...arguments); // fan seems to "flicker" when you first enable it + }); }; NestThermostatAccessory.prototype.getHome = function () { - switch (this.structure.away) { - case "home": - return true; - case "away": - default: - return false; - } + switch (this.structure.away) { + case 'home': + return true; + case 'away': + default: + return false; + } }; NestDeviceAccessory.prototype.setHome = function (home, callback) { - const val = !home ? 'away' : 'home'; - this.log.info("Setting Home for " + this.name + " to: " + val); - const promise = this.conn.update(this.getStructurePropertyPath("away"), val); - return promise - .return(home) - .asCallback(callback); + const val = !home ? 'away' : 'home'; + this.log.info('Setting Home for ' + this.name + ' to: ' + val); + const promise = this.conn.update(this.getStructurePropertyPath('away'), val); + return promise + .return(home) + .asCallback(callback); }; NestThermostatAccessory.prototype.getEcoMode = function () { - return (this.device.hvac_mode === "eco"); + return (this.device.hvac_mode === 'eco'); }; NestThermostatAccessory.prototype.setEcoMode = function (eco, callback) { - const val = eco ? 'eco' : this.device.previous_hvac_mode; - this.log.info("Setting Eco Mode for " + this.name + " to: " + val); - return this.updateDevicePropertyAsync("hvac_mode", val, "target heating cooling") - .asCallback(callback); + const val = eco ? 'eco' : this.device.previous_hvac_mode; + this.log.info('Setting Eco Mode for ' + this.name + ' to: ' + val); + return this.updateDevicePropertyAsync('hvac_mode', val, 'target heating cooling') + .asCallback(callback); }; diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 6691455..0000000 --- a/package-lock.json +++ /dev/null @@ -1,1323 +0,0 @@ -{ - "name": "homebridge-nest", - "version": "2.1.4", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "acorn": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.0.tgz", - "integrity": "sha512-MW/FjM+IvU9CgBzjO3UIPCE2pyEwUsoFl+VGdczOPEdxfGFjuKny/gN54mOuX7Qxmb9Rg9MCn2oKiSUeW+pjrw==", - "dev": true - }, - "acorn-jsx": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", - "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", - "dev": true - }, - "ajv": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.1.tgz", - "integrity": "sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==", - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "bluebird": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", - "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "callsites": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", - "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, - "requires": { - "restore-cursor": "^2.0.0" - } - }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "combined-stream": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", - "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "eslint": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.14.0.tgz", - "integrity": "sha512-jrOhiYyENRrRnWlMYANlGZTqb89r2FuRT+615AabBoajhNjeh9ywDNlh2LU9vTqf0WYN+L3xdXuIi7xuj/tK9w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.9.1", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "eslint-scope": "^4.0.0", - "eslint-utils": "^1.3.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^5.0.1", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.7.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "inquirer": "^6.2.2", - "js-yaml": "^3.12.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.11", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^5.5.1", - "strip-ansi": "^4.0.0", - "strip-json-comments": "^2.0.1", - "table": "^5.2.3", - "text-table": "^0.2.0" - } - }, - "eslint-scope": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", - "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", - "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", - "dev": true - }, - "eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", - "dev": true - }, - "espree": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", - "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", - "dev": true, - "requires": { - "acorn": "^6.0.7", - "acorn-jsx": "^5.0.0", - "eslint-visitor-keys": "^1.0.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", - "dev": true, - "requires": { - "estraverse": "^4.0.0" - } - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "requires": { - "estraverse": "^4.1.0" - } - }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "external-editor": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", - "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", - "dev": true, - "requires": { - "flat-cache": "^2.0.1" - } - }, - "firebase": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/firebase/-/firebase-2.4.2.tgz", - "integrity": "sha1-ThEZ7AOWylYdinrL/xYw/qxsCjE=", - "requires": { - "faye-websocket": ">=0.6.0" - }, - "dependencies": { - "faye-websocket": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.9.3.tgz", - "integrity": "sha1-SCpQWw3wrmJrlphm0710DNuWLoM=", - "requires": { - "websocket-driver": ">=0.5.1" - }, - "dependencies": { - "websocket-driver": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.5.2.tgz", - "integrity": "sha1-jHyF2gcTtAYFVrTXHAF3XuEmnrk=", - "requires": { - "websocket-extensions": ">=0.1.1" - }, - "dependencies": { - "websocket-extensions": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.1.tgz", - "integrity": "sha1-domUmcGEtu91Q3fC27DNbLVdKec=" - } - } - } - } - } - } - }, - "flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", - "dev": true, - "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - } - }, - "flatted": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz", - "integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==", - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "globals": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", - "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==", - "dev": true - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "import-fresh": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", - "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "inquirer": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.2.tgz", - "integrity": "sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA==", - "dev": true, - "requires": { - "ansi-escapes": "^3.2.0", - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^2.0.0", - "lodash": "^4.17.11", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^6.4.0", - "string-width": "^2.1.0", - "strip-ansi": "^5.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-regex": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", - "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", - "dev": true - }, - "strip-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", - "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", - "dev": true, - "requires": { - "ansi-regex": "^4.0.0" - } - } - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.1.tgz", - "integrity": "sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" - }, - "mime-db": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", - "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" - }, - "mime-types": { - "version": "2.1.21", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", - "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", - "requires": { - "mime-db": "~1.37.0" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "parent-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.0.tgz", - "integrity": "sha512-8Mf5juOMmiE4FcmzYc4IaiS9L3+9paz2KOiXzkRviCP6aDmN49Hz6EMWz0lGNp9pX80GvvAuLADtyGfW/Em3TA==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "psl": { - "version": "1.1.31", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", - "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", - "dev": true - }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - } - } - } - }, - "request-promise": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.4.tgz", - "integrity": "sha512-8wgMrvE546PzbR5WbYxUQogUnUDfM0S7QIFZMID+J73vdFARkFy+HElj4T+MWYhpXwlLp0EQ8Zoj8xUA0he4Vg==", - "requires": { - "bluebird": "^3.5.0", - "request-promise-core": "1.1.2", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, - "request-promise-core": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", - "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", - "requires": { - "lodash": "^4.17.11" - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } - }, - "rxjs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", - "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, - "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "table": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/table/-/table-5.2.3.tgz", - "integrity": "sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ==", - "dev": true, - "requires": { - "ajv": "^6.9.1", - "lodash": "^4.17.11", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", - "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", - "dev": true - }, - "string-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.0.0.tgz", - "integrity": "sha512-rr8CUxBbvOZDUvc5lNIJ+OC1nPVpz+Siw9VBtUjB9b6jZehZLFt0JMCZzShFHIsI8cbhm0EsNIfWJMFV3cu3Ew==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.0.0" - } - }, - "strip-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", - "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", - "dev": true, - "requires": { - "ansi-regex": "^4.0.0" - } - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", - "dev": true - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "requires": { - "punycode": "^2.1.0" - } - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - } - } -} diff --git a/package.json b/package.json index 30a3b53..2f9ccca 100644 --- a/package.json +++ b/package.json @@ -1,34 +1,40 @@ { - "name": "homebridge-nest", - "description": "Nest plugin for homebridge", - "version": "2.1.4", - "repository": { - "type": "git", - "url": "git://github.com/chrisjshull/homebridge-nest.git" + "bundleDependencies": false, + "dependencies": { + "bluebird": "^3.5.4", + "eslint": "^5.16.0", + "lodash.debounce": "^4.0.8", + "prompt-promise": "^1.0.3", + "request-promise": "^1.0.2" }, - "scripts": { - "lint": "eslint lib *.js", - "prepublishOnly": "npm run lint", - "preversion": "npm run lint" + "deprecated": false, + "description": "Nest native API plugin for homebridge", + "engines": { + "homebridge": ">=0.2.5", + "node": ">=7.0.0" }, - "license": "ISC", - "preferGlobal": true, + "homepage": "https://github.com/adriancable/homebridge-nest-native#readme", "keywords": [ "homebridge-plugin", "nest" ], - "engines": { - "node": ">=7.0.0", - "homebridge": ">=0.2.5" - }, - "dependencies": { - "bluebird": "^3.2.2", - "firebase": "^2.3.2", - "lodash.debounce": "^4.0.8", - "request": "^2.88.0", - "request-promise": "^4.2.4" + "license": "ISC", + "name": "homebridge-nest", + "preferGlobal": true, + "scripts": { + "lint": "eslint lib *.js", + "prepublishOnly": "npm run lint", + "preversion": "npm run lint" }, - "devDependencies": { - "eslint": "^5.14.0" - } + "version": "3.0.0", + "warnings": [ + { + "code": "ENOTSUP", + "required": { + "node": ">=7.0.0", + "homebridge": ">=0.2.5" + }, + "pkgid": "homebridge-nest@3.0.0" + } + ] }