├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── config.json ├── index.js ├── lib ├── accessories │ ├── alarmsensor.js │ ├── button.js │ ├── outlet.js │ ├── temperaturesensor.js │ ├── thermostat.js │ └── wifi.js ├── accessory.js └── platform.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .vscode/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | deploy: 2 | api_key: 3 | secure: Itc2XdAoNzHM/e51Vb3VuRoJiA/FRDrBE2IGpo/wd8fMwtOTC+gtAJTd4sQkSTTFEqRXbVPVhnT2J2hl5ag7jc55mwQvJgel/s0NNrZ3TBUZjKPwG08c6CVfPEskcHfFqkc6SFtHZCKTEI7yP+nJXIIXArNYgoeIAC47K2kFWvHvgr3Pgh/SxihmYfwsoR7XpiDdSor/8z7wEZGs4HnKEdhREKpU1GX4XIlX72HcWUYar6TRYex/v2h8KtwCqcMBlJ1gem66llEgFaiS27LAM5nB4bvNpw5ZekQIcia8hsJM9Xz4vwmgaQbHCGucwD74rq0TVtauiS5dLCUjnrTlIcRbuPo0gBw/pxrxXLH9370Uh0zTpsJcUH9RDvFitdHwIh7ZGKRuEoQDt2TFla1+eBXzclU9cHKzKrFQOhY7aj0OO2c84jdmYhi8OzdpIP8KsY/UPKE6G17DWmMPhtvhclT+FnSdhq1MgpWShtvxyvQkjTmQ/vidu9j+YlK5zNMJD5rk2vB6kfOT8XdygJOzeFVKTiNUGBUiTEKV77WlMJ3l75k0y7wF7PEFSMEoUEDpQu9B48qa9KQW/wVZPsat+OgmE9qxlZQnX5uq5x42rSTojPQdTs2UQ1IRc5WsRCJ1hE1K963fI/BqYv1w/A6emtkmcR/xEfmTUosrmDiyjkU= 4 | email: cpuidle@gmx.de 5 | provider: npm 6 | language: node_js 7 | sudo: false 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Rasix & 2014 Nischi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # homebridge-fritz 2 | [![NPM Version](https://img.shields.io/npm/v/homebridge-fritz.svg)](https://www.npmjs.com/package/homebridge-fritz) 3 | [![NPM Downloads](https://img.shields.io/npm/dt/homebridge-fritz.svg)](https://www.npmjs.com/package/homebridge-fritz) 4 | [![Build status](https://travis-ci.org/andig/homebridge-fritz.svg?branch=master)](https://travis-ci.org/andig/homebridge-fritz) 5 | [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HGD5E9L28HQHC) 6 | 7 | 8 | Homebridge platform plugin for FRITZ!Box. 9 | 10 | This plugin exposes: 11 | 12 | - WLAN guest access switch 13 | - FRITZ!DECT outlets (200, 210) 14 | - FRITZ!Powerline outlets (510, 540) 15 | - FRITZ!DECT (300, 301) and Comet!DECT thermostats 16 | - FRITZ!DECT (400) buttons 17 | - FRITZ!DECT repeaters as temperature sensor (100) 18 | - Window sensors including HAN FUN devices e.g. of Deutsche Telekom 19 | 20 | ## Installation 21 | 22 | Follow the homebridge installation instructions at [homebridge](https://www.npmjs.com/package/homebridge). 23 | 24 | Install this plugin globally: 25 | 26 | npm install -g homebridge-fritz 27 | 28 | Add platform to `config.json`, for configuration see below. 29 | 30 | ## Configuration 31 | 32 | ```json 33 | { 34 | "platforms": [ 35 | { 36 | "platform": "FRITZ!Box", 37 | "name": "My FRITZ!Box", 38 | "username": "", 39 | "password": "", 40 | "url": "http://fritz.box", 41 | "interval": 60, 42 | "concurrent": true, 43 | "devices": { 44 | "wifi": { 45 | "name": "Guest WLAN", 46 | "display": true 47 | }, 48 | "outlet-1": { 49 | "TemperatureSensor": false 50 | }, 51 | "repeater-1": { 52 | "TemperatureSensor": false 53 | }, 54 | "thermostat-2": { 55 | "ContactSensor": false 56 | }, 57 | "hidden-3": { 58 | "display": false 59 | } 60 | }, 61 | "options": { 62 | "strictSSL": false 63 | } 64 | } 65 | ] 66 | } 67 | 68 | ``` 69 | 70 | The following settings are optional: 71 | 72 | - `url`: FRITZ!Box address 73 | - `interval`: polling interval for updating accessories if state was changed outside homebringe 74 | - `concurrent`: set to `false` to avoid concurrent api requests. May work more stable on older FRITZ!Boxes but has slower performance 75 | - `devices`: detailed configuration for individual devices. To be uniquely addressable, each device uses its `AIN` as key. The guest wifi device is always called `wifi`. Supported device configuration options are: 76 | - `display: false` to disable the device, e.g. useful for main wifi 77 | - `invert: true` to invert open/closed behaviour of `ContactSensor` 78 | - `ContactSensor: false` to disable the thermostat's open window `ContactSensor` 79 | - `TemperatureSensor: false` to disable the temperature sensors for outlets or repeaters 80 | - the `wifi` device additionally supports the `name` option for setting a custom name for the wifi guest access switch 81 | 82 | ## Common Issues / Frequently Asked Questions 83 | 84 | 1. Can't login to the FRITZ!Box 85 | 86 | Some users have reported that logging into the FRITZ!Box internally via `https` fails. This seems to be caused by the FritzApp *occupying* the same port. 87 | In this case you can connect internally via `http` or use the external IP. 88 | 89 | `FRITZ!Box platform login failed` messages can be caused by invalid login data or wrong url. 90 | 91 | Log messages if the form of: 92 | 93 | { error: { [Error: self signed certificate] code: 'DEPTH_ZERO_SELF_SIGNED_CERT' } 94 | 95 | indicate that there are SSL security problems- most likely due to self-signed certificates. Use the `"strictSSL": false` option to disable the respective check. 96 | 97 | 2. Unable to update my thermostat 98 | 99 | Current FRITZ!Box firmwares seem to ignore API updates when the thermostat has been key-locked. 100 | No workaround available- please contact AVM to change this behaviour or don't use the locking mechanism. 101 | 102 | 3. Unable to update thermostat battery charge 103 | 104 | Battery charge is not an API function. That means that the user must have access to FRITZ!Box administration, not only to the SmartHome API in order to use this functionality. 105 | Update your FRITZ!Box user accordingly. 106 | 107 | 4. Can't toggle guest wifi 108 | 109 | Updating guest wifi state requires both a FRITZ!Box username, password and in some cases an https/ssl connection to the FRITZ!Box. If you use the `password only` option (System > FRITZ!Box Users > Login method) of the FRITZ!Box, make sure you provide any random username value at the `"username"` parameter, otherwise `401 - unauthorized` errors may occur. 110 | 111 | 5. Tips for using thermostat with Home App modes and scenes 112 | 113 | When scenes are used in the Home App, a target temperature have to be set. There are the modes Off and On. 114 | - Off - Switches off the thermostat 115 | - On - Set the selected temperature 116 | * Depending on the target and actual temperature, Homekit shows the thermostat as "cooling" or "heating" 117 | 118 | ## Debugging 119 | 120 | If you experience problems with this plugin please provide a homebridge logfile by running homebridge with debugging enabled: 121 | 122 | homebridge -D 123 | 124 | For even more detailed logs set `"debug": true` in the platform configuration. 125 | 126 | 127 | ## Acknowledgements 128 | 129 | - homebridge-fritz is based on the [fritzapi](https://github.com/andig/fritzapi) library 130 | - Original non-working fritz accessory https://github.com/tommasomarchionni/homebridge-FRITZBox 131 | - Platform implementation inspired by https://github.com/rudders/homebridge-platform-wemo. 132 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "platforms": [ 3 | { 4 | "platform": "FRITZ!Box", 5 | "name": "My FRITZ!Box", 6 | "username": "", 7 | "password": "", 8 | "url": "http://fritz.box", 9 | "devices": { 10 | "wifi": { 11 | "name": "Guest WLAN", 12 | "display": true 13 | } 14 | }, 15 | "interval": 60, 16 | "debug": false 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * FRITZ!Box Platform Plugin for HomeBridge (https://github.com/nfarina/homebridge) 3 | * 4 | * @url https://github.com/andig/homebridge-fritz 5 | * @author Andreas Götz 6 | * @license MIT 7 | */ 8 | 9 | /* jslint node: true, laxcomma: true, esversion: 6 */ 10 | "use strict"; 11 | 12 | module.exports = function(homebridge) { 13 | let FritzPlatform = require('./lib/platform')(homebridge); 14 | homebridge.registerPlatform("homebridge-fritz", "FRITZ!Box", FritzPlatform); 15 | }; 16 | -------------------------------------------------------------------------------- /lib/accessories/alarmsensor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * FritzAlarmSensorAccessory 3 | * 4 | * @url https://github.com/andig/homebridge-fritz 5 | * @author Andreas Götz 6 | * @license MIT 7 | */ 8 | 9 | /* jslint node: true, laxcomma: true, esversion: 6 */ 10 | "use strict"; 11 | 12 | const inherits = require('util').inherits; 13 | const extend = require('extend'); 14 | 15 | let Service, Characteristic, FritzPlatform, FritzAccessory; 16 | 17 | module.exports = function(homebridge) { 18 | Service = homebridge.hap.Service; 19 | Characteristic = homebridge.hap.Characteristic; 20 | 21 | FritzPlatform = require('../platform')(homebridge); 22 | FritzAccessory = require('../accessory')(homebridge); 23 | 24 | inherits(FritzAlarmSensorAccessory, FritzAccessory); 25 | return FritzAlarmSensorAccessory; 26 | }; 27 | 28 | function FritzAlarmSensorAccessory(platform, ain) { 29 | FritzAccessory.apply(this, Array.from(arguments).concat("alarm sensor")); 30 | 31 | extend(this.services, { 32 | ContactSensor: new Service.ContactSensor(this.name) 33 | }); 34 | 35 | this.services.ContactSensor.getCharacteristic(Characteristic.ContactSensorState) 36 | .on('get', this.getSensorState.bind(this)) 37 | ; 38 | 39 | this.update(); // execute immediately to get first initial values as fast as possible 40 | setInterval(this.update.bind(this), this.platform.interval); 41 | } 42 | 43 | FritzAlarmSensorAccessory.prototype.getSensorState = function(callback) { 44 | this.platform.log.debug(`Getting ${this.type} ${this.ain} alarm state`); 45 | 46 | callback(null, this.services.ContactSensor.fritzAlarmState); 47 | this.querySensorState(); 48 | }; 49 | 50 | FritzAlarmSensorAccessory.prototype.querySensorState = function() { 51 | this.platform.fritz('getDeviceListFiltered', { identifier: this.ain }).then(function (devices) { 52 | const service = this.services.ContactSensor; 53 | let state = +devices[0].alert.state 54 | ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED 55 | : Characteristic.ContactSensorState.CONTACT_DETECTED; 56 | 57 | // invert if enabled 58 | state = this.platform.deviceConfig(`${this.ain}.invert`, false) 59 | ? 1-state 60 | : state; 61 | 62 | service.fritzAlarmState = state; 63 | service.getCharacteristic(Characteristic.ContactSensorState).updateValue(state); 64 | }.bind(this)); 65 | }; 66 | 67 | FritzAlarmSensorAccessory.prototype.update = function() { 68 | this.platform.log.debug(`Updating ${this.type} ${this.ain}`); 69 | this.querySensorState(); 70 | }; 71 | -------------------------------------------------------------------------------- /lib/accessories/button.js: -------------------------------------------------------------------------------- 1 | /** 2 | * FritzButtonAccessory 3 | * 4 | * @url https://github.com/andig/homebridge-fritz 5 | * @author Andreas Götz 6 | * @license MIT 7 | */ 8 | 9 | /* jslint node: true, laxcomma: true, esversion: 6 */ 10 | "use strict"; 11 | 12 | var inherits = require('util').inherits; 13 | var extend = require('extend'); 14 | 15 | var Service, Characteristic, FritzPlatform, FritzAccessory; 16 | 17 | module.exports = function(homebridge) { 18 | Service = homebridge.hap.Service; 19 | Characteristic = homebridge.hap.Characteristic; 20 | 21 | FritzPlatform = require('../platform')(homebridge); 22 | FritzAccessory = require('../accessory')(homebridge); 23 | 24 | inherits(FritzButtonAccessory, FritzAccessory); 25 | return FritzButtonAccessory; 26 | }; 27 | 28 | function FritzButtonAccessory(platform, ain, index, name) { 29 | FritzAccessory.apply(this, Array.from(arguments).concat("button")); 30 | 31 | this.index = index; // button index of device 32 | this.name = name; // button name for index 33 | 34 | extend(this.services, { 35 | Switch: new Service.Switch(this.name) 36 | }); 37 | 38 | this.services.Switch.getCharacteristic(Characteristic.On) 39 | .on('get', this.getButtonState.bind(this)) 40 | ; 41 | 42 | setInterval(this.update.bind(this), this.platform.interval); 43 | } 44 | 45 | FritzButtonAccessory.prototype.getButtonState = function(callback) { 46 | this.platform.log.debug(`Getting ${this.type} ${this.ain} button state`); 47 | 48 | let service = this.services.Switch; 49 | callback(null, service.fritzButtonState); 50 | 51 | this.platform.fritz('getDeviceListFiltered', { identifier: this.ain }).then(function(devices) { 52 | let lastPressed = +devices[0].button[this.index].lastpressedtimestamp; 53 | 54 | var pressDetected; 55 | if (this.lastPressed === undefined) { 56 | pressDetected = Date.now() - lastPressed * 1000 < this.platform.interval; 57 | } else { 58 | pressDetected = this.lastPressed != lastPressed; 59 | } 60 | 61 | if (this.lastPressed !== undefined && this.lastPressed != lastPressed) { 62 | service.getCharacteristic(Characteristic.On).setValue(true); 63 | setTimeout(function() { 64 | service.getCharacteristic(Characteristic.On).setValue(false); 65 | }, 1000); 66 | } 67 | 68 | this.lastPressed = lastPressed; 69 | }.bind(this)); 70 | }; 71 | 72 | FritzButtonAccessory.prototype.update = function() { 73 | this.platform.log.debug(`Updating ${this.type} ${this.ain}`); 74 | 75 | // Switch 76 | this.getButtonState(function(foo, state) { 77 | this.services.Switch.getCharacteristic(Characteristic.On).setValue(foo, undefined, FritzPlatform.Context); 78 | }.bind(this)); 79 | }; 80 | -------------------------------------------------------------------------------- /lib/accessories/outlet.js: -------------------------------------------------------------------------------- 1 | /** 2 | * FritzOutletAccessory 3 | * 4 | * @url https://github.com/andig/homebridge-fritz 5 | * @author Andreas Götz 6 | * @license MIT 7 | */ 8 | 9 | /* jslint node: true, laxcomma: true, esversion: 6 */ 10 | "use strict"; 11 | 12 | const inherits = require('util').inherits; 13 | const extend = require('extend'); 14 | 15 | let Service, Characteristic, FritzPlatform, FritzAccessory; 16 | 17 | module.exports = function(homebridge) { 18 | Service = homebridge.hap.Service; 19 | Characteristic = homebridge.hap.Characteristic; 20 | 21 | FritzPlatform = require('../platform')(homebridge); 22 | FritzAccessory = require('../accessory')(homebridge); 23 | 24 | inherits(FritzOutletAccessory, FritzAccessory); 25 | return FritzOutletAccessory; 26 | }; 27 | 28 | function FritzOutletAccessory(platform, ain) { 29 | FritzAccessory.apply(this, Array.from(arguments).concat("outlet")); 30 | 31 | extend(this.services, { 32 | Outlet: new Service.Outlet(this.name) 33 | }); 34 | 35 | // Outlet 36 | this.services.Outlet.getCharacteristic(Characteristic.On) 37 | .on('get', this.getOn.bind(this)) 38 | .on('set', this.setOn.bind(this)) 39 | ; 40 | 41 | this.services.Outlet.getCharacteristic(Characteristic.OutletInUse) 42 | .on('getInUse', this.getInUse.bind(this)) 43 | ; 44 | 45 | this.services.Outlet.addCharacteristic(FritzPlatform.PowerUsage) 46 | .on('get', this.getPowerUsage.bind(this)) 47 | ; 48 | 49 | this.services.Outlet.addCharacteristic(FritzPlatform.EnergyConsumption) 50 | .on('get', this.getEnergyConsumption.bind(this)) 51 | ; 52 | 53 | // TemperatureSensor - add only of device supports it 54 | if (this.device.temperature && 55 | this.platform.deviceConfig(`${ain}.TemperatureSensor`, true) 56 | ) { 57 | extend(this.services, { 58 | TemperatureSensor: new Service.TemperatureSensor(this.name) 59 | }); 60 | 61 | this.services.TemperatureSensor.getCharacteristic(Characteristic.CurrentTemperature) 62 | .setProps({minValue: -50}) 63 | .on('get', this.getCurrentTemperature.bind(this)) 64 | ; 65 | 66 | this.services.TemperatureSensor.fritzCurrentTemperature = 20; 67 | } 68 | 69 | this.services.Outlet.fritzState = false; 70 | this.services.Outlet.fritzInUse = false; 71 | this.services.Outlet.fritzPowerUsage = 0; 72 | this.services.Outlet.fritzEnergyConsumption = 0; 73 | 74 | this.update(); // execute immediately to get first initial values as fast as possible 75 | setInterval(this.update.bind(this), this.platform.interval); 76 | } 77 | 78 | FritzOutletAccessory.prototype.getOn = function(callback) { 79 | this.platform.log.debug(`Getting ${this.type} ${this.ain} state`); 80 | 81 | callback(null, this.services.Outlet.fritzState); 82 | 83 | this.queryOn(); 84 | }; 85 | 86 | FritzOutletAccessory.prototype.setOn = function(state, callback) { 87 | this.platform.log(`Switching ${this.type} ${this.ain} to ` + state); 88 | 89 | this.services.Outlet.fritzState = state; 90 | this.platform.fritz(state ? 'setSwitchOn' : 'setSwitchOff', this.ain); 91 | 92 | callback(); 93 | }; 94 | 95 | FritzOutletAccessory.prototype.queryOn = function() { 96 | this.platform.fritz('getSwitchState', this.ain).then(state => { 97 | const service = this.services.Outlet; 98 | service.fritzState = state; 99 | service.getCharacteristic(Characteristic.On).updateValue(state); 100 | }); 101 | }; 102 | 103 | FritzOutletAccessory.prototype.getInUse = function(callback) { 104 | this.platform.log.debug(`Getting ${this.type} ${this.ain} in use`); 105 | 106 | callback(null, this.services.Outlet.fritzInUse); 107 | this.queryPowerUsage(); 108 | }; 109 | 110 | FritzOutletAccessory.prototype.getPowerUsage = function(callback) { 111 | this.platform.log.debug(`Getting ${this.type} ${this.ain} power usage`); 112 | 113 | callback(null, this.services.Outlet.fritzPowerUsage); 114 | this.queryPowerUsage(); 115 | }; 116 | 117 | FritzOutletAccessory.prototype.queryPowerUsage = function() { 118 | this.platform.fritz('getSwitchPower', this.ain).then(power => { 119 | const service = this.services.Outlet; 120 | 121 | service.fritzInUse = power > 0; 122 | service.fritzPowerUsage = power; 123 | 124 | service.getCharacteristic(Characteristic.OutletInUse).updateValue(service.fritzInUse); 125 | service.getCharacteristic(FritzPlatform.PowerUsage).updateValue(power); 126 | }); 127 | }; 128 | 129 | FritzOutletAccessory.prototype.getEnergyConsumption = function(callback) { 130 | this.platform.log.debug(`Getting ${this.type} ${this.ain} energy consumption`); 131 | 132 | callback(null, this.services.Outlet.fritzEnergyConsumption); 133 | this.queryEnergyConsumption(); 134 | }; 135 | 136 | FritzOutletAccessory.prototype.queryEnergyConsumption = function() { 137 | this.platform.fritz('getSwitchEnergy', this.ain).then(energy => { 138 | const service = this.services.Outlet; 139 | 140 | energy = energy / 1000.0; 141 | service.fritzEnergyConsumption = energy; 142 | service.getCharacteristic(FritzPlatform.EnergyConsumption).updateValue(energy); 143 | }); 144 | }; 145 | 146 | FritzOutletAccessory.prototype.update = function() { 147 | this.platform.log.debug(`Updating ${this.type} ${this.ain}`); 148 | 149 | // Outlet 150 | this.queryOn(); 151 | this.queryPowerUsage(); 152 | this.queryEnergyConsumption(); 153 | 154 | // TemperatureSensor 155 | if (this.services.TemperatureSensor) { 156 | this.queryCurrentTemperature(); 157 | } 158 | }; 159 | -------------------------------------------------------------------------------- /lib/accessories/temperaturesensor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * FritzTemperatureSensorAccessory 3 | * 4 | * @url https://github.com/andig/homebridge-fritz 5 | * @author Andreas Götz 6 | * @license MIT 7 | */ 8 | 9 | /* jslint node: true, laxcomma: true, esversion: 6 */ 10 | "use strict"; 11 | 12 | const inherits = require('util').inherits; 13 | const extend = require('extend'); 14 | 15 | let Service, Characteristic, FritzPlatform, FritzAccessory; 16 | 17 | module.exports = function(homebridge) { 18 | Service = homebridge.hap.Service; 19 | Characteristic = homebridge.hap.Characteristic; 20 | 21 | FritzPlatform = require('../platform')(homebridge); 22 | FritzAccessory = require('../accessory')(homebridge); 23 | 24 | inherits(FritzTemperatureSensorAccessory, FritzAccessory); 25 | return FritzTemperatureSensorAccessory; 26 | }; 27 | 28 | function FritzTemperatureSensorAccessory(platform, ain) { 29 | FritzAccessory.apply(this, Array.from(arguments).concat("temperature sensor")); 30 | 31 | extend(this.services, { 32 | TemperatureSensor: new Service.TemperatureSensor(this.name) 33 | }); 34 | 35 | this.services.TemperatureSensor.getCharacteristic(Characteristic.CurrentTemperature) 36 | .setProps({minValue: -50}) 37 | .on('get', this.getCurrentTemperature.bind(this)) 38 | ; 39 | 40 | this.services.TemperatureSensor.fritzCurrentTemperature = 20; 41 | 42 | this.update(); // execute immediately to get first initial values as fast as possible 43 | setInterval(this.update.bind(this), this.platform.interval); 44 | } 45 | 46 | FritzTemperatureSensorAccessory.prototype.update = function() { 47 | this.platform.log.debug(`Updating ${this.type} ${this.ain}`); 48 | this.queryCurrentTemperature(); 49 | }; 50 | -------------------------------------------------------------------------------- /lib/accessories/thermostat.js: -------------------------------------------------------------------------------- 1 | /** 2 | * FritzThermostatAccessory 3 | * 4 | * @url https://github.com/andig/homebridge-fritz 5 | * @author Andreas Götz 6 | * @license MIT 7 | */ 8 | 9 | /* jslint node: true, laxcomma: true, esversion: 6 */ 10 | "use strict"; 11 | 12 | const inherits = require('util').inherits; 13 | const extend = require('extend'); 14 | 15 | let Service, Characteristic, FritzPlatform, FritzAccessory; 16 | 17 | module.exports = function(homebridge) { 18 | Service = homebridge.hap.Service; 19 | Characteristic = homebridge.hap.Characteristic; 20 | 21 | FritzPlatform = require('../platform')(homebridge); 22 | FritzAccessory = require('../accessory')(homebridge); 23 | 24 | inherits(FritzThermostatAccessory, FritzAccessory); 25 | return FritzThermostatAccessory; 26 | }; 27 | 28 | function FritzThermostatAccessory(platform, ain) { 29 | FritzAccessory.apply(this, Array.from(arguments).concat("thermostat")); 30 | 31 | extend(this.services, { 32 | Thermostat: new Service.Thermostat(this.name), 33 | BatteryService: new Service.BatteryService(this.name) 34 | }); 35 | 36 | // Thermostat 37 | this.services.Thermostat.getCharacteristic(Characteristic.CurrentHeatingCoolingState) 38 | .setProps({validValues: [0, 1]}) 39 | .on('get', this.getCurrentHeatingCoolingState.bind(this)) 40 | ; 41 | this.services.Thermostat.getCharacteristic(Characteristic.TargetHeatingCoolingState) 42 | .setProps({validValues: [0, 1]}) // only support "off" and "heating" 43 | .on('get', this.getTargetHeatingCoolingState.bind(this)) 44 | .on('set', this.setTargetHeatingCoolingState.bind(this)) 45 | ; 46 | this.services.Thermostat.getCharacteristic(Characteristic.CurrentTemperature) 47 | .setProps({minValue: -50}) 48 | .on('get', this.getCurrentTemperature.bind(this)) 49 | ; 50 | this.services.Thermostat.getCharacteristic(Characteristic.TargetTemperature) 51 | .setProps({validValueRanges: [8, 28], minValue: 8, maxValue: 28}) // supported temperature range of fritz thermostat 52 | .on('get', this.getTargetTemperature.bind(this)) 53 | .on('set', this.setTargetTemperature.bind(this)) 54 | ; 55 | this.services.Thermostat.getCharacteristic(Characteristic.TemperatureDisplayUnits) 56 | .on('get', this.getTemperatureDisplayUnits.bind(this)) 57 | ; 58 | 59 | // BatteryService 60 | this.services.BatteryService.getCharacteristic(Characteristic.BatteryLevel) 61 | .on('get', this.getBatteryLevel.bind(this)) 62 | ; 63 | this.services.BatteryService.getCharacteristic(Characteristic.ChargingState) 64 | .on('get', this.getChargingState.bind(this)) 65 | ; 66 | this.services.BatteryService.getCharacteristic(Characteristic.StatusLowBattery) 67 | .on('get', this.getStatusLowBattery.bind(this)) 68 | ; 69 | 70 | // init some default values until the first update response arrives 71 | this.services.Thermostat.fritzCurrentTemperature = 20; 72 | this.services.BatteryService.fritzBatteryLevel = 100; 73 | this.services.BatteryService.fritzStatusLowBattery = Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; 74 | this.services.Thermostat.fritzCurrentHeatingCoolingState = Characteristic.CurrentHeatingCoolingState.OFF; 75 | this.services.Thermostat.fritzTargetHeatingCoolingState = Characteristic.TargetHeatingCoolingState.OFF; 76 | this.services.Thermostat.fritzTargetTemperature = 20; 77 | 78 | this.update(); // execute immediately to get first initial values as fast as possible 79 | setInterval(this.update.bind(this), this.platform.interval); 80 | } 81 | 82 | FritzThermostatAccessory.prototype.getCurrentHeatingCoolingState = function(callback) { 83 | this.platform.log.debug(`Getting ${this.type} ${this.ain} current heating state`); 84 | 85 | // current state gets queried in getTargetTemperature 86 | callback(null, this.services.Thermostat.fritzCurrentHeatingCoolingState); 87 | }; 88 | 89 | FritzThermostatAccessory.prototype.getTargetHeatingCoolingState = function(callback) { 90 | this.platform.log.debug(`Getting ${this.type} ${this.ain} target heating state`); 91 | 92 | // target state gets queried in getTargetTemperature 93 | callback(null, this.services.Thermostat.fritzTargetHeatingCoolingState); 94 | }; 95 | 96 | FritzThermostatAccessory.prototype.setTargetHeatingCoolingState = function(state, callback) { 97 | this.platform.log.debug(`Setting ${this.type} ${this.ain} heating state`); 98 | 99 | const service = this.services.Thermostat; 100 | 101 | service.fritzTargetHeatingCoolingState = state; 102 | 103 | let currentState; 104 | if (state === Characteristic.TargetHeatingCoolingState.OFF) { 105 | this.platform.fritz("setTempTarget", this.ain, "off"); 106 | currentState = Characteristic.CurrentHeatingCoolingState.OFF; 107 | } else if (state === Characteristic.TargetHeatingCoolingState.HEAT) { 108 | this.platform.fritz("setTempTarget", this.ain, service.fritzTargetTemperature); 109 | currentState = this.calculateCurrentHeatingCoolingState(); 110 | } 111 | 112 | service.fritzCurrentHeatingCoolingState = currentState; 113 | service.getCharacteristic(Characteristic.CurrentHeatingCoolingState).updateValue(currentState); 114 | 115 | callback(); 116 | }; 117 | 118 | FritzThermostatAccessory.prototype.getTargetTemperature = function(callback) { 119 | this.platform.log.debug(`Getting ${this.type} ${this.ain} target temperature`); 120 | 121 | callback(null, this.services.Thermostat.fritzTargetTemperature); 122 | 123 | this.queryTargetTemperature(); // send query to fritz box; this will also update target/current heating cooling states 124 | }; 125 | 126 | FritzThermostatAccessory.prototype.setTargetTemperature = function(temperature, callback) { 127 | this.platform.log(`Setting ${this.type} ${this.ain} target temperature`); 128 | 129 | const service = this.services.Thermostat; 130 | 131 | service.fritzTargetTemperature = temperature; 132 | service.fritzTargetHeatingCoolingState = Characteristic.TargetHeatingCoolingState.HEAT; 133 | service.fritzCurrentHeatingCoolingState = this.calculateCurrentHeatingCoolingState(); 134 | 135 | service.getCharacteristic(Characteristic.TargetHeatingCoolingState).updateValue(service.fritzTargetHeatingCoolingState); 136 | service.getCharacteristic(Characteristic.CurrentHeatingCoolingState).updateValue(service.fritzCurrentHeatingCoolingState); 137 | 138 | this.platform.fritz('setTempTarget', this.ain, temperature); 139 | 140 | callback(); 141 | }; 142 | 143 | FritzThermostatAccessory.prototype.getTemperatureDisplayUnits = function(callback) { 144 | callback(null, Characteristic.TemperatureDisplayUnits.CELSIUS); 145 | }; 146 | 147 | FritzThermostatAccessory.prototype.queryTargetTemperature = function() { 148 | this.platform.fritz('getTempTarget', this.ain).then(temperature => { 149 | const service = this.services.Thermostat; 150 | 151 | let targetTemperature = temperature; 152 | let currentHeatingCoolingState; 153 | let targetHeatingCoolingState = Characteristic.TargetHeatingCoolingState.HEAT; 154 | 155 | if (temperature === "on") { // change to hap representable value if thermostat is set to on permanently 156 | targetTemperature = service.getCharacteristic(Characteristic.TargetTemperature).props.maxValue; 157 | } 158 | 159 | if (temperature === "off") { 160 | targetTemperature = service.fritzTargetTemperature; 161 | currentHeatingCoolingState = Characteristic.CurrentHeatingCoolingState.OFF; 162 | targetHeatingCoolingState = Characteristic.TargetHeatingCoolingState.OFF; 163 | } else { 164 | currentHeatingCoolingState = this.calculateCurrentHeatingCoolingState(); 165 | } 166 | 167 | service.fritzTargetTemperature = targetTemperature; 168 | service.fritzCurrentHeatingCoolingState = currentHeatingCoolingState; 169 | service.fritzTargetHeatingCoolingState = targetHeatingCoolingState; 170 | 171 | service.getCharacteristic(Characteristic.CurrentHeatingCoolingState).updateValue(currentHeatingCoolingState); 172 | service.getCharacteristic(Characteristic.TargetHeatingCoolingState).updateValue(targetHeatingCoolingState); 173 | service.getCharacteristic(Characteristic.TargetTemperature).updateValue(targetTemperature); 174 | }); 175 | }; 176 | 177 | FritzThermostatAccessory.prototype.calculateCurrentHeatingCoolingState = function() { 178 | const service = this.services.Thermostat; 179 | 180 | // getCurrentTemperature was probably executed before (see #update); so it's enough to use the cached value 181 | const currentTemperature = service.fritzCurrentTemperature; 182 | return currentTemperature <= service.fritzTargetTemperature // we are guessing the current state of the valve 183 | ? Characteristic.CurrentHeatingCoolingState.HEAT 184 | : Characteristic.CurrentHeatingCoolingState.OFF; 185 | }; 186 | 187 | FritzThermostatAccessory.prototype.getBatteryLevel = function(callback) { 188 | this.platform.log.debug(`Getting ${this.type} ${this.ain} battery level`); 189 | 190 | var service = this.services.BatteryService; 191 | callback(null, service.fritzBatteryLevel); 192 | }; 193 | 194 | FritzThermostatAccessory.prototype.getChargingState = function(callback) { 195 | callback(null, Characteristic.ChargingState.NOT_CHARGEABLE); 196 | }; 197 | 198 | FritzThermostatAccessory.prototype.getStatusLowBattery = function(callback) { 199 | this.platform.log.debug(`Getting ${this.type} ${this.ain} battery status`); 200 | 201 | var service = this.services.BatteryService; 202 | callback(null, service.fritzStatusLowBattery); 203 | }; 204 | 205 | FritzThermostatAccessory.prototype.queryBatteryLevel = function() { 206 | this.platform.fritz('getBatteryCharge', this.ain).then(batteryLevel => { 207 | const service = this.services.BatteryService; 208 | 209 | service.fritzBatteryLevel = batteryLevel; 210 | service.fritzStatusLowBattery = batteryLevel < 20 211 | ? Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW 212 | : Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; 213 | 214 | // update internal value; event get only sent when value changes 215 | service.getCharacteristic(Characteristic.BatteryLevel).updateValue(service.fritzBatteryLevel); 216 | service.getCharacteristic(Characteristic.StatusLowBattery).updateValue(service.fritzStatusLowBattery); 217 | }); 218 | }; 219 | 220 | FritzThermostatAccessory.prototype.update = function() { 221 | this.platform.log.debug(`Updating ${this.type} ${this.ain}`); 222 | 223 | this.queryCurrentTemperature(); 224 | this.queryTargetTemperature(); 225 | this.queryBatteryLevel(); 226 | }; 227 | -------------------------------------------------------------------------------- /lib/accessories/wifi.js: -------------------------------------------------------------------------------- 1 | /** 2 | * FritzWifiAccessory 3 | * 4 | * @url https://github.com/andig/homebridge-fritz 5 | * @author Andreas Götz 6 | * @license MIT 7 | */ 8 | 9 | /* jslint node: true, laxcomma: true, esversion: 6 */ 10 | "use strict"; 11 | 12 | const url = require("url"); 13 | const TR064 = require("tr-064-async").Fritzbox; 14 | 15 | let Service, Characteristic, FritzPlatform; 16 | 17 | module.exports = function(homebridge) { 18 | Service = homebridge.hap.Service; 19 | Characteristic = homebridge.hap.Characteristic; 20 | 21 | FritzPlatform = require('../platform')(homebridge); 22 | 23 | return FritzWifiAccessory; 24 | }; 25 | 26 | function FritzWifiAccessory(platform) { 27 | this.platform = platform; 28 | this.name = this.platform.deviceConfig("wifi.name", "Guest WLAN"); 29 | 30 | this.services = { 31 | AccessoryInformation: new Service.AccessoryInformation(), 32 | Switch: new Service.Switch(this.name) 33 | }; 34 | 35 | this.services.AccessoryInformation 36 | .setCharacteristic(Characteristic.Manufacturer, "AVM"); 37 | this.services.AccessoryInformation 38 | .setCharacteristic(Characteristic.Model, "FRITZ!Box"); 39 | 40 | this.platform.fritz('getOSVersion').then(function(version) { 41 | this.services.AccessoryInformation 42 | .setCharacteristic(Characteristic.FirmwareRevision, version); 43 | }.bind(this)); 44 | 45 | if (this.platform.deviceConfig("wifi.tr64Fallback", false)) { 46 | this.fallback = true; 47 | // fritzapi screen scraping 48 | this.services.Switch.getCharacteristic(Characteristic.On) 49 | .on('get', this.getOnFallback.bind(this)) 50 | .on('set', this.setOnFallback.bind(this)) 51 | ; 52 | 53 | this.update(); // execute immediately to get first initial values as fast as possible 54 | } else { 55 | this.platform.log("Using tr64 api for guest Wifi"); 56 | 57 | const box = url.parse(this.platform.options.url); 58 | 59 | const options = { 60 | host: box.host || 'fritz.box', 61 | port: this.platform.deviceConfig("wifi.tr64Port", 49443), 62 | ssl: this.platform.deviceConfig("wifi.tr64Ssl", true), 63 | user: this.platform.config.username, 64 | password: this.platform.config.password 65 | }; 66 | 67 | const tr64 = new TR064(options); 68 | const self = this; 69 | 70 | tr64.initTR064Device().then(() => { 71 | // remember service 72 | let wifiService = "urn:dslforum-org:service:WLANConfiguration"; 73 | self.tr64service = tr64.services[wifiService + ":3"] || tr64.services[wifiService + ":2"]; 74 | 75 | self.services.Switch.getCharacteristic(Characteristic.On) 76 | .on('get', self.getOn.bind(self)) 77 | .on('set', self.setOn.bind(self)) 78 | ; 79 | 80 | this.update(); // execute immediately to get first initial values as fast as possible 81 | }).catch((err) => { 82 | self.platform.log.error(err); 83 | }); 84 | } 85 | 86 | this.services.Switch.fritzState = false; 87 | 88 | setInterval(this.update.bind(this), this.platform.interval); 89 | } 90 | 91 | FritzWifiAccessory.prototype.getServices = function() { 92 | return [this.services.AccessoryInformation, this.services.Switch]; 93 | }; 94 | 95 | FritzWifiAccessory.prototype.getOn = function(callback) { 96 | this.platform.log.debug("Getting guest WLAN state"); 97 | 98 | callback(null, this.services.Switch.fritzState); 99 | this.queryOn(); 100 | }; 101 | 102 | FritzWifiAccessory.prototype.setOn = function(on, callback) { 103 | this.platform.log("Switching guest WLAN to " + on); 104 | 105 | const payload = {'NewEnable':on ? '1' : '0'}; 106 | this.tr64service.actions.SetEnable(payload).then(res => { 107 | this.platform.log.debug("< %s %s", "tr64.SetEnable", JSON.stringify(res)); 108 | // TODO: check GetInfo to see if successful 109 | }).catch(err => { 110 | this.platform.log.error(err); 111 | }); 112 | 113 | callback(); 114 | }; 115 | 116 | FritzWifiAccessory.prototype.queryOn = function() { 117 | if (!this.tr64service) { // tr64 is still not initialized 118 | return; 119 | } 120 | 121 | this.tr64service.actions.GetInfo().then(res => { 122 | this.platform.log.debug("< %s %s", "tr64.GetInfo", JSON.stringify(res)); 123 | 124 | const service = this.services.Switch; 125 | service.fritzState = +res.NewEnable; 126 | service.getCharacteristic(Characteristic.On).updateValue(service.fritzState); 127 | }).catch((err) => { 128 | this.platform.log.error(err); 129 | }); 130 | }; 131 | 132 | FritzWifiAccessory.prototype.getOnFallback = function(callback) { 133 | this.platform.log.debug("Getting guest WLAN state"); 134 | 135 | callback(null, this.services.Switch.fritzState); 136 | this.queryOnFallback(); 137 | }; 138 | 139 | FritzWifiAccessory.prototype.setOnFallback = function(on, callback) { 140 | this.platform.log.debug("Switching guest WLAN to " + on); 141 | 142 | this.platform.fritz('setGuestWlan', !!on); 143 | callback(); 144 | }; 145 | 146 | FritzWifiAccessory.prototype.queryOnFallback = function() { 147 | this.platform.fritz('getGuestWlan').then(res => { 148 | const service = this.services.Switch; 149 | service.fritzState = res.activate_guest_access; 150 | service.getCharacteristic(Characteristic.On).updateValue(res.activate_guest_access); 151 | }); 152 | }; 153 | 154 | FritzWifiAccessory.prototype.update = function() { 155 | this.platform.log.debug("Updating guest WLAN"); 156 | 157 | if (this.fallback) { 158 | this.queryOnFallback(); 159 | } else { 160 | this.queryOn(); 161 | } 162 | }; 163 | -------------------------------------------------------------------------------- /lib/accessory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * FritzAccessory 3 | * 4 | * @url https://github.com/andig/homebridge-fritz 5 | * @author Andreas Götz 6 | * @license MIT 7 | */ 8 | 9 | /* jslint node: true, laxcomma: true, esversion: 6 */ 10 | "use strict"; 11 | 12 | let Service, Characteristic, FritzPlatform; 13 | 14 | module.exports = function(homebridge) { 15 | Service = homebridge.hap.Service; 16 | Characteristic = homebridge.hap.Characteristic; 17 | 18 | FritzPlatform = require('./platform')(homebridge); 19 | 20 | return FritzAccessory; 21 | }; 22 | 23 | function FritzAccessory(platform, ain, type) { 24 | this.platform = platform; 25 | this.ain = ain; 26 | this.type = type; 27 | 28 | // fix duplicate UUID (https://github.com/andig/homebridge-fritz/issues/27) 29 | this.uuid_base = type + ain; 30 | 31 | this.name = this.platform.getName(this.ain); 32 | this.device = this.platform.getDevice(this.ain); 33 | 34 | this.services = { 35 | AccessoryInformation: new Service.AccessoryInformation() 36 | .setCharacteristic(Characteristic.SerialNumber, this.ain) 37 | }; 38 | 39 | // these characteristics will not be present for e.g. device groups 40 | if (this.device.manufacturer) { 41 | this.services.AccessoryInformation 42 | .setCharacteristic(Characteristic.Manufacturer, this.device.manufacturer); 43 | } 44 | if (this.device.productname) { 45 | this.services.AccessoryInformation 46 | .setCharacteristic(Characteristic.Model, this.device.productname); 47 | } 48 | if (this.device.fwversion) { 49 | this.services.AccessoryInformation 50 | .setCharacteristic(Characteristic.FirmwareRevision, this.device.fwversion); 51 | } 52 | } 53 | 54 | FritzAccessory.prototype.getServices = function() { 55 | return Object.keys(this.services).map(function(key) { 56 | return this.services[key]; 57 | }.bind(this)); 58 | }; 59 | 60 | FritzAccessory.prototype.getCurrentTemperature = function(callback) { 61 | this.platform.log.debug(`Getting ${this.type} ${this.ain} temperature`); 62 | 63 | // characteristic CurrentTemperature is part of multiple services 64 | const service = this.services.Thermostat || this.services.TemperatureSensor; 65 | callback(null, service.fritzCurrentTemperature); 66 | }; 67 | 68 | FritzAccessory.prototype.queryCurrentTemperature = function() { 69 | const service = this.services.Thermostat || this.services.TemperatureSensor; 70 | if (service === undefined) { 71 | return; // called accidentally, ignoring request 72 | } 73 | 74 | this.platform.fritz('getTemperature', this.ain).then(temperature => { 75 | service.fritzCurrentTemperature = temperature; 76 | service.getCharacteristic(Characteristic.CurrentTemperature).updateValue(temperature); 77 | }); 78 | }; 79 | -------------------------------------------------------------------------------- /lib/platform.js: -------------------------------------------------------------------------------- 1 | /** 2 | * FritzPlatform 3 | * 4 | * @url https://github.com/andig/homebridge-fritz 5 | * @author Andreas Götz 6 | * @license MIT 7 | */ 8 | 9 | /* jslint node: true, laxcomma: true, esversion: 6 */ 10 | "use strict"; 11 | 12 | var dotProp = require('dot-prop'); 13 | var fritz = require('fritzapi'); 14 | var Promise = require('bluebird'); 15 | var isWebUri = require('valid-url').isWebUri; 16 | var inherits = require('util').inherits; 17 | var Characteristic, Homebridge; 18 | 19 | module.exports = function(homebridge) { 20 | Homebridge = homebridge; 21 | Characteristic = homebridge.hap.Characteristic; 22 | 23 | inherits(FritzPlatform.PowerUsage, Characteristic); 24 | inherits(FritzPlatform.EnergyConsumption, Characteristic); 25 | 26 | return FritzPlatform; 27 | }; 28 | 29 | function FritzPlatform(log, config) { 30 | this.log = log; 31 | this.config = config; 32 | 33 | this.options = this.config.options || {}; 34 | this.interval = 1000 * (this.config.interval || 60); // 1 minute 35 | 36 | this.pending = 0; // pending requests 37 | 38 | // device configuration 39 | this.config.devices = this.config.devices || {}; 40 | if (typeof this.config.hide !== "undefined") { 41 | this.log.warn('Deprecated `hide` setting is ignored. Use `devices` instead'); 42 | } 43 | 44 | // fritz url 45 | var url = this.config.url || 'http://fritz.box'; 46 | if (!isWebUri(url)) this.log.warn("Invalid FRITZ!Box url - forgot http(s)://?"); 47 | // trailing slash 48 | if (url.substr(-1) == "/") url = url.slice(0, -1); 49 | this.options.url = url; 50 | 51 | this.promise = null; 52 | } 53 | 54 | FritzPlatform.PowerUsage = function() { 55 | Characteristic.call(this, 'Power Usage', 'AE48F447-E065-4B31-8050-8FB06DB9E087'); 56 | 57 | this.setProps({ 58 | format: Characteristic.Formats.FLOAT, 59 | unit: 'W', 60 | perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] 61 | }); 62 | 63 | this.value = this.getDefaultValue(); 64 | }; 65 | 66 | FritzPlatform.EnergyConsumption = function() { 67 | Characteristic.call(this, 'Energy Consumption', 'C4805C5B-45B7-4E5B-BFCB-FE43E0FBC1E5'); 68 | 69 | this.setProps({ 70 | format: Characteristic.Formats.FLOAT, 71 | unit: 'kWh', 72 | perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] 73 | }); 74 | 75 | this.value = this.getDefaultValue(); 76 | }; 77 | 78 | FritzPlatform.prototype = { 79 | accessories: function(callback) { 80 | var accessories = []; 81 | var self = this; 82 | 83 | fritz.getSessionID(this.config.username, this.config.password, this.options).then(function(sid) { 84 | self.log("FRITZ!Box platform login successful"); 85 | self.sid = sid; 86 | }) 87 | .then(function() { 88 | self.log("Discovering accessories"); 89 | 90 | // wifi 91 | if (self.deviceConfig("wifi.display", true)) { 92 | let FritzWifiAccessory = require('./accessories/wifi')(Homebridge); 93 | accessories.push(new FritzWifiAccessory(self)); 94 | } 95 | 96 | self.updateDeviceList().then(function(devices) { 97 | var jobs = []; 98 | 99 | // outlets 100 | jobs.push(self.fritz("getSwitchList").then(function(ains) { 101 | self.log("Outlets found: %s", self.getArrayString(ains)); 102 | let FritzOutletAccessory = require('./accessories/outlet')(Homebridge); 103 | 104 | ains.forEach(function(ain) { 105 | if (self.deviceConfig(`${ain}.display`, true)) { 106 | accessories.push(new FritzOutletAccessory(self, ain)); 107 | } 108 | }); 109 | })); 110 | 111 | // thermostats 112 | jobs.push(self.fritz('getThermostatList').then(function(ains) { 113 | self.log("Thermostats found: %s", self.getArrayString(ains)); 114 | let FritzThermostatAccessory = require('./accessories/thermostat')(Homebridge); 115 | 116 | ains.forEach(function(ain) { 117 | if (self.deviceConfig(`${ain}.display`, true)) { 118 | accessories.push(new FritzThermostatAccessory(self, ain)); 119 | } 120 | }); 121 | 122 | // add remaining non-api devices that support temperature, e.g. FRITZ!DECT 100 repeater 123 | var sensors = []; 124 | devices.forEach(function(device) { 125 | if (device.temperature) { 126 | var ain = device.identifier.replace(/\s/g, ''); 127 | if (!accessories.find(function(accessory) { 128 | return accessory.ain && accessory.ain == ain; 129 | })) { 130 | sensors.push(ain); 131 | } 132 | } 133 | }); 134 | 135 | if (sensors.length) { 136 | let FritzTemperatureSensorAccessory = require('./accessories/temperaturesensor')(Homebridge); 137 | 138 | sensors.forEach(function(ain) { 139 | if (self.deviceConfig(`${ain}.display`, true) && 140 | self.deviceConfig(`${ain}.TemperatureSensor`, true) 141 | ) { 142 | accessories.push(new FritzTemperatureSensorAccessory(self, ain)); 143 | } 144 | }); 145 | } 146 | self.log("Sensors found: %s", self.getArrayString(sensors)); 147 | })); 148 | 149 | // alarm sensors 150 | var alarms = []; 151 | devices.forEach(function(device) { 152 | // @TODO deduplicate alarms similar to temp sensors 153 | if (device.alert) { 154 | alarms.push(device.identifier); 155 | } 156 | }); 157 | 158 | if (alarms.length) { 159 | let FritzAlarmSensorAccessory = require('./accessories/alarmsensor')(Homebridge); 160 | 161 | alarms.forEach(function(ain) { 162 | if (self.deviceConfig(`${ain}.display`, true) && 163 | self.deviceConfig(`${ain}.ContactSensor`, true) 164 | ) { 165 | accessories.push(new FritzAlarmSensorAccessory(self, ain)); 166 | } 167 | }); 168 | } 169 | self.log("Alarm sensors found: %s", self.getArrayString(alarms)); 170 | 171 | // buttons 172 | var buttons = []; 173 | devices.forEach(function (device) { 174 | let FritzButtonAccessory = require('./accessories/button')(Homebridge); 175 | 176 | if (device.button) { 177 | var ain = device.identifier.replace(/\s/g, ''); 178 | if (!accessories.find(function (accessory) { 179 | return accessory.ain && accessory.ain == ain; 180 | })) { 181 | buttons.push(ain); 182 | 183 | if (self.deviceConfig(`${ain}.display`, true)) { 184 | device.button.forEach(function(button, index) { 185 | accessories.push(new FritzButtonAccessory(self, ain, index, button.name)); 186 | }); 187 | } 188 | } 189 | } 190 | }); 191 | self.log("Buttons found: %s", self.getArrayString(buttons)); 192 | 193 | Promise.all(jobs).then(function() { 194 | callback(accessories); 195 | }); 196 | }) 197 | .catch(function(error) { 198 | self.log.error("Could not get devices from FRITZ!Box. Please check if device supports the smart home API and user has sufficient privileges."); 199 | callback(accessories); 200 | }); 201 | }) 202 | .catch(function(error) { 203 | self.log.debug(error); 204 | self.log.error("Initializing FRITZ!Box platform accessories failed - wrong user credentials?"); 205 | }); 206 | }, 207 | 208 | deviceConfig: function(key, defaultValue) { 209 | return dotProp.get(this.config.devices, key, defaultValue) 210 | }, 211 | 212 | getArrayString: function(array) { 213 | return array.toString() || "none"; 214 | }, 215 | 216 | updateDeviceList: function() { 217 | return this.fritz("getDeviceList").then(function(devices) { 218 | // cache list of devices in options for reuse by non-API functions 219 | this.deviceList = devices; 220 | return devices; 221 | }.bind(this)); 222 | }, 223 | 224 | getDevice: function(ain) { 225 | var device = this.deviceList.find(function(device) { 226 | return device.identifier.replace(/\s/g, '') == ain; 227 | }); 228 | return device || {}; // safeguard 229 | }, 230 | 231 | getName: function(ain) { 232 | var dev = this.getDevice(ain); 233 | return dev.name || ain; 234 | }, 235 | 236 | fritz: function(func) { 237 | var args = Array.prototype.slice.call(arguments, 1); 238 | var self = this; 239 | 240 | // api call tracking 241 | if (self.config.concurrent !== false) { 242 | this.promise = null; 243 | } 244 | else if ((this.promise || Promise.resolve()).isPending()) { 245 | this.pending++; 246 | this.log.debug('%s pending api calls', this.pending); 247 | } 248 | 249 | this.promise = (this.promise || Promise.resolve()).reflect().then(function() { 250 | self.pending = Math.max(self.pending-1, 0); 251 | 252 | var fritzFunc = fritz[func]; 253 | var funcArgs = [self.sid].concat(args).concat(self.options); 254 | 255 | self.log.debug("> %s (%s)", func, JSON.stringify(funcArgs.slice(0,-1)).slice(1,-1)); 256 | 257 | return fritzFunc.apply(self, funcArgs).catch(function(error) { 258 | if (error.response && error.response.statusCode == 403) { 259 | return fritz.getSessionID(self.config.username, self.config.password, self.options).then(function(sid) { 260 | self.log("FRITZ!Box session renewed"); 261 | self.log("renewed:"+sid); 262 | self.sid = sid; 263 | 264 | funcArgs = [self.sid].concat(args).concat(self.options); 265 | self.log("renewed, now calling:"+funcArgs.toString()); 266 | return fritzFunc.apply(self, funcArgs); 267 | }) 268 | .catch(function(error) { 269 | self.log.warn("FRITZ!Box session renewal failed"); 270 | /* jshint laxbreak:true */ 271 | throw error === "0000000000000000" 272 | ? "Invalid session id" 273 | : error; 274 | }); 275 | } 276 | 277 | throw error; 278 | }); 279 | }) 280 | .catch(function(error) { 281 | self.log.debug(error); 282 | self.log.error("< %s failed", func); 283 | self.promise = null; 284 | 285 | return Promise.reject(func + " failed"); 286 | }); 287 | 288 | // debug result 289 | this.promise.then(function(res) { 290 | self.log.debug("< %s %s", func, JSON.stringify(res)); 291 | return res; 292 | }); 293 | 294 | return this.promise; 295 | }, 296 | 297 | fritzApi: function() { 298 | return fritz; 299 | } 300 | }; 301 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "homebridge-fritz", 3 | "version": "0.8.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ajv": { 8 | "version": "6.10.0", 9 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", 10 | "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", 11 | "requires": { 12 | "fast-deep-equal": "^2.0.1", 13 | "fast-json-stable-stringify": "^2.0.0", 14 | "json-schema-traverse": "^0.4.1", 15 | "uri-js": "^4.2.2" 16 | } 17 | }, 18 | "ansi-styles": { 19 | "version": "3.2.1", 20 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 21 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 22 | "requires": { 23 | "color-convert": "^1.9.0" 24 | } 25 | }, 26 | "array-back": { 27 | "version": "3.1.0", 28 | "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", 29 | "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==" 30 | }, 31 | "asn1": { 32 | "version": "0.2.4", 33 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", 34 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", 35 | "requires": { 36 | "safer-buffer": "~2.1.0" 37 | } 38 | }, 39 | "assert-plus": { 40 | "version": "1.0.0", 41 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 42 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 43 | }, 44 | "async": { 45 | "version": "2.6.2", 46 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", 47 | "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", 48 | "requires": { 49 | "lodash": "^4.17.11" 50 | } 51 | }, 52 | "asynckit": { 53 | "version": "0.4.0", 54 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 55 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 56 | }, 57 | "aws-sign2": { 58 | "version": "0.7.0", 59 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 60 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 61 | }, 62 | "aws4": { 63 | "version": "1.8.0", 64 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", 65 | "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" 66 | }, 67 | "balanced-match": { 68 | "version": "1.0.0", 69 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 70 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 71 | "dev": true 72 | }, 73 | "bcrypt-pbkdf": { 74 | "version": "1.0.2", 75 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 76 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 77 | "requires": { 78 | "tweetnacl": "^0.14.3" 79 | } 80 | }, 81 | "bluebird": { 82 | "version": "3.7.0", 83 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.0.tgz", 84 | "integrity": "sha512-aBQ1FxIa7kSWCcmKHlcHFlT2jt6J/l4FzC7KcPELkOJOsPOb/bccdhmIrKDfXhwFrmc7vDoDrrepFvGqjyXGJg==" 85 | }, 86 | "boolbase": { 87 | "version": "1.0.0", 88 | "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", 89 | "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" 90 | }, 91 | "brace-expansion": { 92 | "version": "1.1.11", 93 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 94 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 95 | "dev": true, 96 | "requires": { 97 | "balanced-match": "^1.0.0", 98 | "concat-map": "0.0.1" 99 | } 100 | }, 101 | "caseless": { 102 | "version": "0.12.0", 103 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 104 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 105 | }, 106 | "chalk": { 107 | "version": "2.4.2", 108 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 109 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 110 | "requires": { 111 | "ansi-styles": "^3.2.1", 112 | "escape-string-regexp": "^1.0.5", 113 | "supports-color": "^5.3.0" 114 | } 115 | }, 116 | "cheerio": { 117 | "version": "0.22.0", 118 | "resolved": "http://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", 119 | "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", 120 | "requires": { 121 | "css-select": "~1.2.0", 122 | "dom-serializer": "~0.1.0", 123 | "entities": "~1.1.1", 124 | "htmlparser2": "^3.9.1", 125 | "lodash.assignin": "^4.0.9", 126 | "lodash.bind": "^4.1.4", 127 | "lodash.defaults": "^4.0.1", 128 | "lodash.filter": "^4.4.0", 129 | "lodash.flatten": "^4.2.0", 130 | "lodash.foreach": "^4.3.0", 131 | "lodash.map": "^4.4.0", 132 | "lodash.merge": "^4.4.0", 133 | "lodash.pick": "^4.2.1", 134 | "lodash.reduce": "^4.4.0", 135 | "lodash.reject": "^4.4.0", 136 | "lodash.some": "^4.4.0" 137 | } 138 | }, 139 | "cli": { 140 | "version": "1.0.1", 141 | "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", 142 | "integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=", 143 | "dev": true, 144 | "requires": { 145 | "exit": "0.1.2", 146 | "glob": "^7.1.1" 147 | } 148 | }, 149 | "color-convert": { 150 | "version": "1.9.3", 151 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 152 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 153 | "requires": { 154 | "color-name": "1.1.3" 155 | } 156 | }, 157 | "color-name": { 158 | "version": "1.1.3", 159 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 160 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 161 | }, 162 | "combined-stream": { 163 | "version": "1.0.7", 164 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", 165 | "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", 166 | "requires": { 167 | "delayed-stream": "~1.0.0" 168 | } 169 | }, 170 | "command-line-args": { 171 | "version": "5.1.1", 172 | "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.1.1.tgz", 173 | "integrity": "sha512-hL/eG8lrll1Qy1ezvkant+trihbGnaKaeEjj6Scyr3DN+RC7iQ5Rz84IeLERfAWDGo0HBSNAakczwgCilDXnWg==", 174 | "requires": { 175 | "array-back": "^3.0.1", 176 | "find-replace": "^3.0.0", 177 | "lodash.camelcase": "^4.3.0", 178 | "typical": "^4.0.0" 179 | } 180 | }, 181 | "command-line-usage": { 182 | "version": "6.0.2", 183 | "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.0.2.tgz", 184 | "integrity": "sha512-Jr9RQM43qWDwpRJOa0lgZw0LhiU8tgOqoR+xxIcb3eT5vFZi69fBWUODMSBtGUYI1qTlElPl3txFQY6rChVuXQ==", 185 | "requires": { 186 | "array-back": "^3.1.0", 187 | "chalk": "^2.4.2", 188 | "table-layout": "^1.0.0", 189 | "typical": "^5.1.0" 190 | }, 191 | "dependencies": { 192 | "typical": { 193 | "version": "5.1.0", 194 | "resolved": "https://registry.npmjs.org/typical/-/typical-5.1.0.tgz", 195 | "integrity": "sha512-t5Ik8UAwBal1P1XzuVE4dc+RYQZicLUGJdvqr/vdqsED7SQECgsGBylldSsfWZL7RQjxT3xhQcKHWhLaVSR6YQ==" 196 | } 197 | } 198 | }, 199 | "concat-map": { 200 | "version": "0.0.1", 201 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 202 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 203 | "dev": true 204 | }, 205 | "console-browserify": { 206 | "version": "1.1.0", 207 | "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", 208 | "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", 209 | "dev": true, 210 | "requires": { 211 | "date-now": "^0.1.4" 212 | } 213 | }, 214 | "core-util-is": { 215 | "version": "1.0.2", 216 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 217 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 218 | }, 219 | "css-select": { 220 | "version": "1.2.0", 221 | "resolved": "http://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", 222 | "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", 223 | "requires": { 224 | "boolbase": "~1.0.0", 225 | "css-what": "2.1", 226 | "domutils": "1.5.1", 227 | "nth-check": "~1.0.1" 228 | } 229 | }, 230 | "css-what": { 231 | "version": "2.1.3", 232 | "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", 233 | "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" 234 | }, 235 | "dashdash": { 236 | "version": "1.14.1", 237 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 238 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 239 | "requires": { 240 | "assert-plus": "^1.0.0" 241 | } 242 | }, 243 | "date-now": { 244 | "version": "0.1.4", 245 | "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", 246 | "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", 247 | "dev": true 248 | }, 249 | "deep-extend": { 250 | "version": "0.6.0", 251 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", 252 | "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" 253 | }, 254 | "delayed-stream": { 255 | "version": "1.0.0", 256 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 257 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 258 | }, 259 | "dom-serializer": { 260 | "version": "0.1.0", 261 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", 262 | "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", 263 | "requires": { 264 | "domelementtype": "~1.1.1", 265 | "entities": "~1.1.1" 266 | }, 267 | "dependencies": { 268 | "domelementtype": { 269 | "version": "1.1.3", 270 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", 271 | "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" 272 | } 273 | } 274 | }, 275 | "domelementtype": { 276 | "version": "1.3.1", 277 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", 278 | "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" 279 | }, 280 | "domhandler": { 281 | "version": "2.4.2", 282 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", 283 | "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", 284 | "requires": { 285 | "domelementtype": "1" 286 | } 287 | }, 288 | "domutils": { 289 | "version": "1.5.1", 290 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", 291 | "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", 292 | "requires": { 293 | "dom-serializer": "0", 294 | "domelementtype": "1" 295 | } 296 | }, 297 | "dot-prop": { 298 | "version": "5.1.0", 299 | "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.1.0.tgz", 300 | "integrity": "sha512-n1oC6NBF+KM9oVXtjmen4Yo7HyAVWV2UUl50dCYJdw2924K6dX9bf9TTTWaKtYlRn0FEtxG27KS80ayVLixxJA==", 301 | "requires": { 302 | "is-obj": "^2.0.0" 303 | } 304 | }, 305 | "ecc-jsbn": { 306 | "version": "0.1.2", 307 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 308 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 309 | "requires": { 310 | "jsbn": "~0.1.0", 311 | "safer-buffer": "^2.1.0" 312 | } 313 | }, 314 | "entities": { 315 | "version": "1.1.2", 316 | "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", 317 | "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" 318 | }, 319 | "escape-string-regexp": { 320 | "version": "1.0.5", 321 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 322 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 323 | }, 324 | "exit": { 325 | "version": "0.1.2", 326 | "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", 327 | "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", 328 | "dev": true 329 | }, 330 | "extend": { 331 | "version": "3.0.2", 332 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 333 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 334 | }, 335 | "extsprintf": { 336 | "version": "1.3.0", 337 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 338 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 339 | }, 340 | "eyes": { 341 | "version": "0.1.8", 342 | "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", 343 | "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" 344 | }, 345 | "fast-deep-equal": { 346 | "version": "2.0.1", 347 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", 348 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" 349 | }, 350 | "fast-json-stable-stringify": { 351 | "version": "2.0.0", 352 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 353 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" 354 | }, 355 | "find-replace": { 356 | "version": "3.0.0", 357 | "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", 358 | "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", 359 | "requires": { 360 | "array-back": "^3.0.1" 361 | } 362 | }, 363 | "forever-agent": { 364 | "version": "0.6.1", 365 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 366 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 367 | }, 368 | "form-data": { 369 | "version": "2.3.3", 370 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 371 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 372 | "requires": { 373 | "asynckit": "^0.4.0", 374 | "combined-stream": "^1.0.6", 375 | "mime-types": "^2.1.12" 376 | } 377 | }, 378 | "fritzapi": { 379 | "version": "0.10.7", 380 | "resolved": "https://registry.npmjs.org/fritzapi/-/fritzapi-0.10.7.tgz", 381 | "integrity": "sha512-Jecjbxxwx/P7gbJ9Lrot8elSBWfCvMhoqouEQalokS8aFt4RhYLK56+JfVFLbNCm6vgZ94wfnx84CXculLz41w==", 382 | "requires": { 383 | "bluebird": "^3.5.5", 384 | "cheerio": "^0.22.0", 385 | "command-line-args": "^5.1.1", 386 | "command-line-usage": "^6.0.2", 387 | "extend": "^3.0.2", 388 | "request": "^2.88.0", 389 | "xml2json-light": "^1.0.6" 390 | } 391 | }, 392 | "fs.realpath": { 393 | "version": "1.0.0", 394 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 395 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 396 | "dev": true 397 | }, 398 | "getpass": { 399 | "version": "0.1.7", 400 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 401 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 402 | "requires": { 403 | "assert-plus": "^1.0.0" 404 | } 405 | }, 406 | "glob": { 407 | "version": "7.1.3", 408 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", 409 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", 410 | "dev": true, 411 | "requires": { 412 | "fs.realpath": "^1.0.0", 413 | "inflight": "^1.0.4", 414 | "inherits": "2", 415 | "minimatch": "^3.0.4", 416 | "once": "^1.3.0", 417 | "path-is-absolute": "^1.0.0" 418 | } 419 | }, 420 | "har-schema": { 421 | "version": "2.0.0", 422 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 423 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 424 | }, 425 | "har-validator": { 426 | "version": "5.1.3", 427 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", 428 | "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", 429 | "requires": { 430 | "ajv": "^6.5.5", 431 | "har-schema": "^2.0.0" 432 | } 433 | }, 434 | "has-flag": { 435 | "version": "3.0.0", 436 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 437 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 438 | }, 439 | "htmlparser2": { 440 | "version": "3.10.1", 441 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", 442 | "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", 443 | "requires": { 444 | "domelementtype": "^1.3.1", 445 | "domhandler": "^2.3.0", 446 | "domutils": "^1.5.1", 447 | "entities": "^1.1.1", 448 | "inherits": "^2.0.1", 449 | "readable-stream": "^3.1.1" 450 | } 451 | }, 452 | "http-signature": { 453 | "version": "1.2.0", 454 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 455 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 456 | "requires": { 457 | "assert-plus": "^1.0.0", 458 | "jsprim": "^1.2.2", 459 | "sshpk": "^1.7.0" 460 | } 461 | }, 462 | "inflight": { 463 | "version": "1.0.6", 464 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 465 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 466 | "dev": true, 467 | "requires": { 468 | "once": "^1.3.0", 469 | "wrappy": "1" 470 | } 471 | }, 472 | "inherits": { 473 | "version": "2.0.3", 474 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 475 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 476 | }, 477 | "is-obj": { 478 | "version": "2.0.0", 479 | "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", 480 | "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" 481 | }, 482 | "is-typedarray": { 483 | "version": "1.0.0", 484 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 485 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 486 | }, 487 | "isarray": { 488 | "version": "0.0.1", 489 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 490 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", 491 | "dev": true 492 | }, 493 | "isstream": { 494 | "version": "0.1.2", 495 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 496 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 497 | }, 498 | "jsbn": { 499 | "version": "0.1.1", 500 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 501 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" 502 | }, 503 | "jshint": { 504 | "version": "2.10.2", 505 | "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.10.2.tgz", 506 | "integrity": "sha512-e7KZgCSXMJxznE/4WULzybCMNXNAd/bf5TSrvVEq78Q/K8ZwFpmBqQeDtNiHc3l49nV4E/+YeHU/JZjSUIrLAA==", 507 | "dev": true, 508 | "requires": { 509 | "cli": "~1.0.0", 510 | "console-browserify": "1.1.x", 511 | "exit": "0.1.x", 512 | "htmlparser2": "3.8.x", 513 | "lodash": "~4.17.11", 514 | "minimatch": "~3.0.2", 515 | "shelljs": "0.3.x", 516 | "strip-json-comments": "1.0.x" 517 | }, 518 | "dependencies": { 519 | "domhandler": { 520 | "version": "2.3.0", 521 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", 522 | "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", 523 | "dev": true, 524 | "requires": { 525 | "domelementtype": "1" 526 | } 527 | }, 528 | "entities": { 529 | "version": "1.0.0", 530 | "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", 531 | "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", 532 | "dev": true 533 | }, 534 | "htmlparser2": { 535 | "version": "3.8.3", 536 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", 537 | "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", 538 | "dev": true, 539 | "requires": { 540 | "domelementtype": "1", 541 | "domhandler": "2.3", 542 | "domutils": "1.5", 543 | "entities": "1.0", 544 | "readable-stream": "1.1" 545 | } 546 | }, 547 | "readable-stream": { 548 | "version": "1.1.14", 549 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", 550 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", 551 | "dev": true, 552 | "requires": { 553 | "core-util-is": "~1.0.0", 554 | "inherits": "~2.0.1", 555 | "isarray": "0.0.1", 556 | "string_decoder": "~0.10.x" 557 | } 558 | }, 559 | "string_decoder": { 560 | "version": "0.10.31", 561 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 562 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", 563 | "dev": true 564 | } 565 | } 566 | }, 567 | "json-schema": { 568 | "version": "0.2.3", 569 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 570 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 571 | }, 572 | "json-schema-traverse": { 573 | "version": "0.4.1", 574 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 575 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 576 | }, 577 | "json-stringify-safe": { 578 | "version": "5.0.1", 579 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 580 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 581 | }, 582 | "jsprim": { 583 | "version": "1.4.1", 584 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 585 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 586 | "requires": { 587 | "assert-plus": "1.0.0", 588 | "extsprintf": "1.3.0", 589 | "json-schema": "0.2.3", 590 | "verror": "1.10.0" 591 | } 592 | }, 593 | "lodash": { 594 | "version": "4.17.15", 595 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", 596 | "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" 597 | }, 598 | "lodash.assignin": { 599 | "version": "4.2.0", 600 | "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", 601 | "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=" 602 | }, 603 | "lodash.bind": { 604 | "version": "4.2.1", 605 | "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", 606 | "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=" 607 | }, 608 | "lodash.camelcase": { 609 | "version": "4.3.0", 610 | "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", 611 | "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" 612 | }, 613 | "lodash.defaults": { 614 | "version": "4.2.0", 615 | "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", 616 | "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" 617 | }, 618 | "lodash.filter": { 619 | "version": "4.6.0", 620 | "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", 621 | "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=" 622 | }, 623 | "lodash.flatten": { 624 | "version": "4.4.0", 625 | "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", 626 | "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" 627 | }, 628 | "lodash.foreach": { 629 | "version": "4.5.0", 630 | "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", 631 | "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" 632 | }, 633 | "lodash.map": { 634 | "version": "4.6.0", 635 | "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", 636 | "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=" 637 | }, 638 | "lodash.merge": { 639 | "version": "4.6.2", 640 | "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 641 | "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" 642 | }, 643 | "lodash.pick": { 644 | "version": "4.4.0", 645 | "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", 646 | "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=" 647 | }, 648 | "lodash.reduce": { 649 | "version": "4.6.0", 650 | "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", 651 | "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" 652 | }, 653 | "lodash.reject": { 654 | "version": "4.6.0", 655 | "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", 656 | "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=" 657 | }, 658 | "lodash.some": { 659 | "version": "4.6.0", 660 | "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", 661 | "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=" 662 | }, 663 | "mime-db": { 664 | "version": "1.38.0", 665 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", 666 | "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==" 667 | }, 668 | "mime-types": { 669 | "version": "2.1.22", 670 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", 671 | "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", 672 | "requires": { 673 | "mime-db": "~1.38.0" 674 | } 675 | }, 676 | "minimatch": { 677 | "version": "3.0.4", 678 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 679 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 680 | "dev": true, 681 | "requires": { 682 | "brace-expansion": "^1.1.7" 683 | } 684 | }, 685 | "nth-check": { 686 | "version": "1.0.2", 687 | "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", 688 | "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", 689 | "requires": { 690 | "boolbase": "~1.0.0" 691 | } 692 | }, 693 | "oauth-sign": { 694 | "version": "0.9.0", 695 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 696 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" 697 | }, 698 | "once": { 699 | "version": "1.4.0", 700 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 701 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 702 | "dev": true, 703 | "requires": { 704 | "wrappy": "1" 705 | } 706 | }, 707 | "path-is-absolute": { 708 | "version": "1.0.1", 709 | "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 710 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 711 | "dev": true 712 | }, 713 | "performance-now": { 714 | "version": "2.1.0", 715 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 716 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 717 | }, 718 | "psl": { 719 | "version": "1.1.31", 720 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", 721 | "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" 722 | }, 723 | "punycode": { 724 | "version": "2.1.1", 725 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 726 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 727 | }, 728 | "qs": { 729 | "version": "6.5.2", 730 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 731 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 732 | }, 733 | "readable-stream": { 734 | "version": "3.4.0", 735 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", 736 | "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", 737 | "requires": { 738 | "inherits": "^2.0.3", 739 | "string_decoder": "^1.1.1", 740 | "util-deprecate": "^1.0.1" 741 | } 742 | }, 743 | "reduce-flatten": { 744 | "version": "2.0.0", 745 | "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", 746 | "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==" 747 | }, 748 | "request": { 749 | "version": "2.88.0", 750 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", 751 | "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", 752 | "requires": { 753 | "aws-sign2": "~0.7.0", 754 | "aws4": "^1.8.0", 755 | "caseless": "~0.12.0", 756 | "combined-stream": "~1.0.6", 757 | "extend": "~3.0.2", 758 | "forever-agent": "~0.6.1", 759 | "form-data": "~2.3.2", 760 | "har-validator": "~5.1.0", 761 | "http-signature": "~1.2.0", 762 | "is-typedarray": "~1.0.0", 763 | "isstream": "~0.1.2", 764 | "json-stringify-safe": "~5.0.1", 765 | "mime-types": "~2.1.19", 766 | "oauth-sign": "~0.9.0", 767 | "performance-now": "^2.1.0", 768 | "qs": "~6.5.2", 769 | "safe-buffer": "^5.1.2", 770 | "tough-cookie": "~2.4.3", 771 | "tunnel-agent": "^0.6.0", 772 | "uuid": "^3.3.2" 773 | } 774 | }, 775 | "safe-buffer": { 776 | "version": "5.1.2", 777 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 778 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 779 | }, 780 | "safer-buffer": { 781 | "version": "2.1.2", 782 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 783 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 784 | }, 785 | "sax": { 786 | "version": "1.2.4", 787 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 788 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" 789 | }, 790 | "shelljs": { 791 | "version": "0.3.0", 792 | "resolved": "http://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", 793 | "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", 794 | "dev": true 795 | }, 796 | "sshpk": { 797 | "version": "1.16.1", 798 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", 799 | "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", 800 | "requires": { 801 | "asn1": "~0.2.3", 802 | "assert-plus": "^1.0.0", 803 | "bcrypt-pbkdf": "^1.0.0", 804 | "dashdash": "^1.12.0", 805 | "ecc-jsbn": "~0.1.1", 806 | "getpass": "^0.1.1", 807 | "jsbn": "~0.1.0", 808 | "safer-buffer": "^2.0.2", 809 | "tweetnacl": "~0.14.0" 810 | } 811 | }, 812 | "string_decoder": { 813 | "version": "1.3.0", 814 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 815 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 816 | "requires": { 817 | "safe-buffer": "~5.2.0" 818 | }, 819 | "dependencies": { 820 | "safe-buffer": { 821 | "version": "5.2.0", 822 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", 823 | "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" 824 | } 825 | } 826 | }, 827 | "strip-json-comments": { 828 | "version": "1.0.4", 829 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", 830 | "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", 831 | "dev": true 832 | }, 833 | "supports-color": { 834 | "version": "5.5.0", 835 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 836 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 837 | "requires": { 838 | "has-flag": "^3.0.0" 839 | } 840 | }, 841 | "table-layout": { 842 | "version": "1.0.0", 843 | "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.0.tgz", 844 | "integrity": "sha512-o8V8u943KXX9gLNK/Ss1n6Nn4YhpyY/RRnp3hKv/zTA+SXYiQnzJQlR8CZQf1RqYqgkiWMJ54Mv+Vq9Kfzxz1A==", 845 | "requires": { 846 | "array-back": "^3.1.0", 847 | "deep-extend": "~0.6.0", 848 | "typical": "^5.0.0", 849 | "wordwrapjs": "^4.0.0" 850 | }, 851 | "dependencies": { 852 | "typical": { 853 | "version": "5.1.0", 854 | "resolved": "https://registry.npmjs.org/typical/-/typical-5.1.0.tgz", 855 | "integrity": "sha512-t5Ik8UAwBal1P1XzuVE4dc+RYQZicLUGJdvqr/vdqsED7SQECgsGBylldSsfWZL7RQjxT3xhQcKHWhLaVSR6YQ==" 856 | } 857 | } 858 | }, 859 | "tough-cookie": { 860 | "version": "2.4.3", 861 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", 862 | "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", 863 | "requires": { 864 | "psl": "^1.1.24", 865 | "punycode": "^1.4.1" 866 | }, 867 | "dependencies": { 868 | "punycode": { 869 | "version": "1.4.1", 870 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 871 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 872 | } 873 | } 874 | }, 875 | "tr-064-async": { 876 | "version": "1.0.0", 877 | "resolved": "https://registry.npmjs.org/tr-064-async/-/tr-064-async-1.0.0.tgz", 878 | "integrity": "sha512-rziYieNFWHNesj4kNEDS987T63qcduaJgU/Pzx/9B+Q3nvsuym5LHFNhZnXkinNvIy4ooFOry6QdwSyqD2W9Kg==", 879 | "requires": { 880 | "async": ">=0.2.0", 881 | "bluebird": "^3.4.6", 882 | "eyes": ">=0.1.0", 883 | "request": ">=2.30", 884 | "underscore": "^1.8.3", 885 | "xml2js": ">=0.4.0", 886 | "xmlbuilder": "^8.2.2" 887 | } 888 | }, 889 | "tunnel-agent": { 890 | "version": "0.6.0", 891 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 892 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 893 | "requires": { 894 | "safe-buffer": "^5.0.1" 895 | } 896 | }, 897 | "tweetnacl": { 898 | "version": "0.14.5", 899 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 900 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" 901 | }, 902 | "typical": { 903 | "version": "4.0.0", 904 | "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", 905 | "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==" 906 | }, 907 | "underscore": { 908 | "version": "1.9.1", 909 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", 910 | "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" 911 | }, 912 | "uri-js": { 913 | "version": "4.2.2", 914 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", 915 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 916 | "requires": { 917 | "punycode": "^2.1.0" 918 | } 919 | }, 920 | "util-deprecate": { 921 | "version": "1.0.2", 922 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 923 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 924 | }, 925 | "uuid": { 926 | "version": "3.3.2", 927 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 928 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" 929 | }, 930 | "valid-url": { 931 | "version": "1.0.9", 932 | "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", 933 | "integrity": "sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA=" 934 | }, 935 | "verror": { 936 | "version": "1.10.0", 937 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 938 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 939 | "requires": { 940 | "assert-plus": "^1.0.0", 941 | "core-util-is": "1.0.2", 942 | "extsprintf": "^1.2.0" 943 | } 944 | }, 945 | "wordwrapjs": { 946 | "version": "4.0.0", 947 | "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.0.tgz", 948 | "integrity": "sha512-Svqw723a3R34KvsMgpjFBYCgNOSdcW3mQFK4wIfhGQhtaFVOJmdYoXgi63ne3dTlWgatVcUc7t4HtQ/+bUVIzQ==", 949 | "requires": { 950 | "reduce-flatten": "^2.0.0", 951 | "typical": "^5.0.0" 952 | }, 953 | "dependencies": { 954 | "typical": { 955 | "version": "5.1.0", 956 | "resolved": "https://registry.npmjs.org/typical/-/typical-5.1.0.tgz", 957 | "integrity": "sha512-t5Ik8UAwBal1P1XzuVE4dc+RYQZicLUGJdvqr/vdqsED7SQECgsGBylldSsfWZL7RQjxT3xhQcKHWhLaVSR6YQ==" 958 | } 959 | } 960 | }, 961 | "wrappy": { 962 | "version": "1.0.2", 963 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 964 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 965 | "dev": true 966 | }, 967 | "xml2js": { 968 | "version": "0.4.19", 969 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", 970 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", 971 | "requires": { 972 | "sax": ">=0.6.0", 973 | "xmlbuilder": "~9.0.1" 974 | }, 975 | "dependencies": { 976 | "xmlbuilder": { 977 | "version": "9.0.7", 978 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", 979 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" 980 | } 981 | } 982 | }, 983 | "xml2json-light": { 984 | "version": "1.0.6", 985 | "resolved": "https://registry.npmjs.org/xml2json-light/-/xml2json-light-1.0.6.tgz", 986 | "integrity": "sha1-ERaHcQorAfvS/hj0GK31Ed5UfzQ=" 987 | }, 988 | "xmlbuilder": { 989 | "version": "8.2.2", 990 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", 991 | "integrity": "sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M=" 992 | } 993 | } 994 | } 995 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "homebridge-fritz", 3 | "version": "0.9.5", 4 | "author": "Andreas Goetz ", 5 | "homepage": "https://github.com/andig/homebridge-fritz#readme", 6 | "description": "Homebridge platform plugin for FRITZ!Box", 7 | "keywords": [ 8 | "homebridge-plugin", 9 | "homebridge", 10 | "FRITZ!Box", 11 | "fritzbox", 12 | "fritz", 13 | "homekit", 14 | "Siri" 15 | ], 16 | "license": "MIT", 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/andig/homebridge-fritz.git" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/andig/homebridge-fritz/issues" 23 | }, 24 | "main": "index.js", 25 | "scripts": { 26 | "test": "node_modules/jshint/bin/jshint index.js" 27 | }, 28 | "engines": { 29 | "node": ">4.0.0", 30 | "homebridge": "^1.0.0" 31 | }, 32 | "dependencies": { 33 | "bluebird": "^3.3.5", 34 | "dot-prop": "^5.1.0", 35 | "extend": "^3.0.0", 36 | "fritzapi": "^0.10.5", 37 | "tr-064-async": "^1.0.0", 38 | "valid-url": "^1.0.9" 39 | }, 40 | "devDependencies": { 41 | "jshint": "^2.10.2" 42 | } 43 | } 44 | --------------------------------------------------------------------------------