diff --git a/README.md b/README.md index a0cb30a..e13cdf8 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,37 @@ -# homebridge-verisure - -This is a plugin for [homebridge](https://github.com/nfarina/homebridge). It's a -working implementation for several Verisure devices: - -- [x] __HUMIDITY1__ - Temperature -- [x] __SIREN1__ - Temperature -- [x] __SMARTPLUG__ - State, on, off -- [x] __SMOKE2__ - Temperature -- [x] __VOICEBOX1__ - Temperature - -## Installation - -```bash -npm install -g homebridge-verisure -``` - -Now you can update your configuration file to enable the plugin, see sample -snippet below. - -## Configuration - -As part of your configuration, add an object with your Verisure credentials to -your array (list) of enabled platform plugins. - -```json -"platforms": [ - { - "platform" : "verisure", - "name" : "Verisure", - "email": "your@email.com", - "password": "yourT0p5ecre7Passw0rd" - } -] -``` +# homebridge-verisure + +This is a plugin for [homebridge](https://github.com/nfarina/homebridge). It's a +working implementation for several Verisure devices: + +- [x] __DOORLOCK__ - Yale Doorman Lock/Unlock +- [x] __HUMIDITY1__ - Temperature +- [x] __SIREN1__ - Temperature +- [x] __SMARTPLUG__ - State, on, off +- [x] __SMOKE2__ - Temperature +- [x] __VOICEBOX1__ - Temperature + +## Installation + +```bash +npm install -g homebridge-verisure +``` + +Now you can update your configuration file to enable the plugin, see sample +snippet below. + +## Configuration + +As part of your configuration, add an object with your Verisure credentials to +your array (list) of enabled platform plugins. + +```json +"platforms": [ + { + "platform" : "verisure", + "name" : "Verisure", + "email": "your@email.com", + "password": "yourT0p5ecre7Passw0rd", + "doorcode": "000000" + } +] +``` diff --git a/index.js b/index.js index 88edc87..cad8798 100644 --- a/index.js +++ b/index.js @@ -13,6 +13,7 @@ const PLATFORM_NAME = 'verisure'; const MANUFACTURER = 'Verisure'; const DEVICE_TYPES = { + 'DOORLOCK': 'Yale Doorman', 'HUMIDITY1': 'Klimatdetektor', 'SIREN1': 'Siren', 'SMARTPLUG': 'Smart plug', @@ -27,6 +28,7 @@ let VERISURE_DEVICE_NAMES = [] const getVerisureInstallation = function(config, callback) { + verisure.auth(config.email, config.password, function(err, token) { if(err) return callback(err); VERISURE_TOKEN = token; @@ -82,7 +84,6 @@ const VerisurePlatform = function(log, config, api) { const platform = this; this.log = log; this.config = config; - this.accessories = function(callback) { getVerisureInstallation(config, function(err) { if(err) return log.error(err); @@ -108,6 +109,20 @@ const VerisurePlatform = function(log, config, api) { }); })); + if (overview && overview.doorLockStatusList){ + let locks = overview.doorLockStatusList.map(function(device){ + return new VerisureAccessory(log, { + name: getUniqueName(`${device.area}`), + model: 'DOORLOCK', + serialNumber: device.deviceLabel, + value: device.lockedState==='LOCKED' ? 1 : 0, + doorcode: config.doorcode, + category: 6 // Hardcoded from Accessory.Categories in Accessory.js of hap-nodejs + }); + }); + devices = devices.concat(locks); + } + callback(devices); }); }) @@ -123,6 +138,7 @@ const VerisureAccessory = function(log, config) { this.model = config.model; this.serialNumber = config.serialNumber; this.value = config.value; + this.service = null; } @@ -175,11 +191,125 @@ VerisureAccessory.prototype = { }, callback); }, + _getCurrentLockState: function(callback){ + this.log(`${this.name} (${this.serialNumber}): GETTING CURRENT LOCK STATE`); + verisure._apiClient({ + method: 'GET', + uri: `/installation/${VERISURE_INSTALLATION.giid}/doorlockstate/search`, + headers: { + 'Cookie': `vid=${VERISURE_TOKEN}`, + 'Accept': 'application/json, text/javascript, */*; q=0.01' + } + }, function (callback, error, response){ + this.log(`**** Response: getCurrentLockState: ${JSON.stringify(response)}`); + if (error) callback(error); + if (response && response.statusCode == 200){ + let body = JSON.parse(response.body); + for (let doorlock of body){ + if (doorlock.deviceLabel == this.serialNumber){ + if (doorlock.motorJam){ + this.value=Characteristic.LockCurrentState.JAMMED; + callback(null, this.value); + break; + } else { + this.value = doorlock.currentLockState == "UNLOCKED" ? Characteristic.LockCurrentState.UNSECURED : Characteristic.LockCurrentState.SECURED ; + callback(null, this.value); + break; + } + } + } + } + }.bind(this, callback)); + }, + + _getTargetLockState: function(callback){ + this.log(`${this.name} (${this.serialNumber}): GETTING TARGET LOCK STATE.`); + + verisure._apiClient({ + method: 'GET', + uri: `/installation/${VERISURE_INSTALLATION.giid}/doorlockstate/search`, + headers: { + 'Cookie': `vid=${VERISURE_TOKEN}`, + 'Accept': 'application/json, text/javascript, */*; q=0.01' + } + }, function(callback, error, response){ + this.log(`**** Response: getTargetLockState: ${JSON.stringify(response)}`); + if (error) callback(error); + if (response && response.statusCode == 200){ + let body = JSON.parse(response.body); + for (let doorlock of body){ + if (doorlock.deviceLabel == this.serialNumber) { + let targetLockState = doorlock.pendingLockState == "NONE" ? doorlock.currentLockState : doorlock.pendingLockState; + callback(error, targetLockState == "UNLOCKED" ? Characteristic.LockTargetState.UNSECURED : Characteristic.LockTargetState.SECURED); + } + } + } + + }.bind(this, callback)); + }, + + _setTargetLockState: function(value, callback){ + this.log(`${this.name} (${this.serialNumber}): Setting TARGET LOCK STATE to "${value}"`); + let actionValue = value ? "lock":"unlock"; + verisure._apiClient({ + method: 'PUT', + uri: `/installation/${VERISURE_INSTALLATION.giid}/device/${this.serialNumber}/${actionValue}`, + headers: { + 'Cookie': `vid=${VERISURE_TOKEN}`, + 'Accept': 'application/json, text/javascript, */*; q=0.01' + }, + json: + { + "code": this.config.doorcode + } + }, function(value, callback, error, response){ + this.log(`***** Response from ${actionValue}-operation: ${JSON.stringify(response)}`); + if (error != null) callback(error, response); + if (response && response.statusCode != 200) { + if (response.statusCode == 400 && response.body && response.body.errorCode == "VAL_00819"){ + this.service.setCharacteristic(Characteristic.LockCurrentState, value) + this.value = value; + callback (null); + } else { + callback(response); + } + } else { + this._waitForLockStatusChangeResult(value, callback, response); + } + }.bind(this,value,callback)) + }, + + _waitForLockStatusChangeResult: function(value, callback, response){ + setTimeout(function(value, callback, response){ + verisure._apiClient({ + method: 'GET', + uri: `/installation/${VERISURE_INSTALLATION.giid}/doorlockstate/change/result/${response.body.doorLockStateChangeTransactionId}`, + headers: { + 'Cookie': `vid=${VERISURE_TOKEN}`, + 'Accept': 'application/json, text/javascript, */*; q=0.01' + } + }, function(value, origResponse, callback,error,response){ + if (error != null) + this.log(`**** ERROR: ${JSON.stringify(error)}`); + + this.log(`**** Response: Doorlockstate: ${JSON.stringify(response)}`); + let body = JSON.parse(response.body); + if (body.result == "NO_DATA"){ + this._waitForLockStatusChangeResult(value, callback, origResponse); + } else { + this.service.setCharacteristic(Characteristic.LockCurrentState, value) + this.value = value; + callback (null); + } + }.bind(this, value, response, callback)); + }.bind(this, value, callback, response) + ,200); + }, getServices: function() { const accessoryInformation = new Service.AccessoryInformation(); accessoryInformation .setCharacteristic(Characteristic.Manufacturer, MANUFACTURER) - .setCharacteristic(Characteristic.Model, this.model) + .setCharacteristic(Characteristic.Model, DEVICE_TYPES[this.model] || this.model) .setCharacteristic(Characteristic.SerialNumber, this.serialNumber) let service = null; @@ -193,6 +323,20 @@ VerisureAccessory.prototype = { .value = this.value; } + if (['DOORLOCK'].includes(this.model)){ + service = new Service.LockMechanism(this.name); + service + .getCharacteristic(Characteristic.LockCurrentState) + .on('get', this._getCurrentLockState.bind(this)); + + service + .getCharacteristic(Characteristic.LockTargetState) + .on('get', this._getTargetLockState.bind(this)) + .on('set', this._setTargetLockState.bind(this)); + + this.service = service; + } + if(['HUMIDITY1', 'SIREN1', 'SMOKE2', 'VOICEBOX1'].includes(this.model)) { service = new Service.TemperatureSensor(this.name); service