├── .gitignore ├── tests ├── js │ ├── runner.js │ ├── issue-70.js │ ├── issue-48.js │ ├── switchBinary.js │ ├── issue-72.js │ ├── update-without-change.js │ └── issue-69.js └── data │ ├── issue-48.json │ └── issue-70.json ├── LICENSE ├── package.json ├── README.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /tests/js/runner.js: -------------------------------------------------------------------------------- 1 | var qunit = require("qunit"); 2 | homebridge = require("../../node_modules/homebridge/lib/api.js"); 3 | 4 | qunit.options.coverage = { dir: "/tmp/" }; 5 | 6 | qunit.run({ 7 | code : "index.js", 8 | tests : [ 9 | 'switchBinary', 10 | 'issue-48.js', 11 | 'issue-69.js', 12 | 'issue-72.js', 13 | 'issue-70.js', 14 | 'update-without-change.js' 15 | ].map(function (v) { return './tests/js/' + v; }) 16 | }); 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Robert Blake 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 6 | -------------------------------------------------------------------------------- /tests/js/issue-70.js: -------------------------------------------------------------------------------- 1 | var homebridge = require("../../node_modules/homebridge/lib/api.js"); 2 | 3 | var api = new homebridge.API(); 4 | _initializer(api); 5 | var testPlatform = new platform({}, console.log); 6 | var Service = api.hap.Service; 7 | var Characteristic = api.hap.Characteristic; 8 | 9 | test("Issue 70 hang", function() { 10 | var devicesJson = require('../data/issue-70.json'); 11 | var foundAccessories = testPlatform.buildAccessoriesFromJson(devicesJson); 12 | assert.equal(foundAccessories.length, 23, "buildAccessoriesFromJson must find 23 accessories."); 13 | var accessories = [], dnames = {}; 14 | for(var i = 0; i < foundAccessories.length; i++){ 15 | accessories[i] = new accessory(foundAccessories[i].name, foundAccessories[i].devDesc, testPlatform); 16 | var dname = accessories[i].name; 17 | assert.ok(!dnames[dname], "No duplicate displayName " + foundAccessories[i].displayName + ""); 18 | dnames[dname] = accessories[i]; 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "homebridge-zway", 3 | "version": "0.6.0-alpha0", 4 | "description": "homebridge-plugin for ZWay Server and RaZBerry", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node tests/js/runner.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/SphtKr/homebridge-zway.git" 12 | }, 13 | "preferGlobal": true, 14 | "keywords": [ 15 | "homebridge-plugin", 16 | "Z-Way", 17 | "RaZBerry", 18 | "Z-Wave", 19 | "HomeKit" 20 | ], 21 | "engines": { 22 | "node": ">=0.12.0", 23 | "homebridge": ">=0.2.5" 24 | }, 25 | "dependencies": { 26 | "request": "2.74.0", 27 | "q": "1.4.x", 28 | "debug": "^2.2.0", 29 | "tough-cookie": "^2.2.0" 30 | }, 31 | "devDependencies": { 32 | "qunit": "^0.9.1", 33 | "homebridge": ">=0.2.5", 34 | "istanbul": ">=0.4.5" 35 | }, 36 | "author": "S'pht'Kr (https://github.com/SphtKr)", 37 | "license": "ISC", 38 | "bugs": { 39 | "url": "https://github.com/SphtKr/homebridge-zway/issues" 40 | }, 41 | "homepage": "https://github.com/SphtKr/homebridge-zway#readme" 42 | } 43 | -------------------------------------------------------------------------------- /tests/js/issue-48.js: -------------------------------------------------------------------------------- 1 | var homebridge = require("../../node_modules/homebridge/lib/api.js"); 2 | 3 | var api = new homebridge.API(); 4 | _initializer(api); 5 | var testPlatform = new platform({}, console.log); 6 | var Service = api.hap.Service; 7 | var Characteristic = api.hap.Characteristic; 8 | var ZWayServerPlatform = platform; 9 | 10 | test("Issue 48 published devices", function() { 11 | var devicesJson = require('../data/issue-48.json'); 12 | var foundAccessories = testPlatform.buildAccessoriesFromJson(devicesJson); 13 | assert.equal(foundAccessories.length, 1, "buildAccessoriesFromJson must find 1 accessory."); 14 | var acc = new accessory(foundAccessories[0].name, foundAccessories[0].devDesc, testPlatform); 15 | var services = acc.getServices(); 16 | assert.equal(services.length, 4, "getServices must return four services."); 17 | console.log(JSON.stringify(services, null, 5)); 18 | 19 | var fsvcs, cxs; 20 | 21 | fsvcs = services.filter(function(service){ return service.UUID === Service.Switch.UUID; }); 22 | assert.equal(fsvcs.length, 1, "getServices must return exactly one Switch service"); 23 | var cxs = fsvcs[0].characteristics.filter(function(cx){ return cx.UUID === Characteristic.On.UUID; }); 24 | assert.equal(cxs.length, 1, "The Switch service must have exactly one On Characteristic"); 25 | 26 | fsvcs = services.filter(function(service){ return service.UUID === Service.TemperatureSensor.UUID; }); 27 | assert.equal(fsvcs.length, 1, "getServices must return exactly one TemperatureSensor service"); 28 | var cxs = fsvcs[0].characteristics.filter(function(cx){ return cx.UUID === Characteristic.CurrentTemperature.UUID; }); 29 | assert.equal(cxs.length, 1, "The TemperatureSensor service must have exactly one CurrentTemperature Characteristic"); 30 | 31 | fsvcs = services.filter(function(service){ return service.UUID === Service.Outlet.UUID; }); 32 | assert.equal(fsvcs.length, 1, "getServices must return exactly one Outlet service"); 33 | var cxs = fsvcs[0].characteristics.filter(function(cx){ return cx.UUID === Characteristic.On.UUID; }); 34 | assert.equal(cxs.length, 1, "The Outlet service must have exactly one On Characteristic"); 35 | var cxs = fsvcs[0].characteristics.filter(function(cx){ return cx.UUID === ZWayServerPlatform.CurrentPowerConsumption.UUID; }); 36 | assert.equal(cxs.length, 1, "The Outlet service must have exactly one CurrentPowerConsumption Characteristic"); 37 | var cxs = fsvcs[0].characteristics.filter(function(cx){ return cx.UUID === ZWayServerPlatform.TotalPowerConsumption.UUID; }); 38 | assert.equal(cxs.length, 1, "The Outlet service must have exactly one TotalPowerConsumption Characteristic"); 39 | }); 40 | -------------------------------------------------------------------------------- /tests/js/switchBinary.js: -------------------------------------------------------------------------------- 1 | var homebridge = require("../../node_modules/homebridge/lib/api.js"); 2 | 3 | var api = new homebridge.API(); 4 | _initializer(api); 5 | var testPlatform = new platform({}, console.log); 6 | var Service = api.hap.Service; 7 | var Characteristic = api.hap.Characteristic; 8 | 9 | test("switchBinary", function() { 10 | var devicesJson = {"data":{"structureChanged":true,"updateTime":1483686127,"devices":[ 11 | { 12 | "creationTime": 1453783771, 13 | "creatorId": 2, 14 | "deviceType": "switchBinary", 15 | "h": 1078449915, 16 | "hasHistory": true, 17 | "id": "ZWayVDev_zway_2-0-37", 18 | "location": 3, 19 | "metrics": { 20 | "icon": "switch", 21 | "title": "Test Lamps", 22 | "level": "off" 23 | }, 24 | "permanently_hidden": false, 25 | "probeType": "", 26 | "tags": [], 27 | "visibility": true, 28 | "updateTime": 1483864350 29 | } 30 | ]},"code":200,"message":"200 OK","error":null}; 31 | var foundAccessories = testPlatform.buildAccessoriesFromJson(devicesJson); 32 | assert.equal(foundAccessories.length, 1, "buildAccessoriesFromJson must find exactly one accessory."); 33 | var acc = new accessory(foundAccessories[0].name, foundAccessories[0].devDesc, testPlatform); 34 | var services = acc.getServices(); 35 | assert.equal(services.length, 2, "getServices must return two services."); 36 | //console.log(JSON.stringify(services[1].characteristics[1], null, 4)); 37 | assert.equal(services[1].characteristics[1].UUID, api.hap.Characteristic.On.UUID, 'services[1].characteristics[1] should be an "On" characteristic'); 38 | }); 39 | 40 | test("switchBinary update", function(){ 41 | var devicesJson = {"data":{"structureChanged":true,"updateTime":1483686137,"devices":[ 42 | { 43 | "creationTime": 1453783771, 44 | "creatorId": 2, 45 | "deviceType": "switchBinary", 46 | "h": 1078449915, 47 | "hasHistory": true, 48 | "id": "ZWayVDev_zway_2-0-37", 49 | "location": 3, 50 | "metrics": { 51 | "icon": "switch", 52 | "title": "Test Lamps", 53 | "level": "on" 54 | }, 55 | "permanently_hidden": false, 56 | "probeType": "", 57 | "tags": [], 58 | "visibility": true, 59 | "updateTime": 1483686136 60 | } 61 | ]},"code":200,"message":"200 OK","error":null}; 62 | testPlatform.processPollUpdate(devicesJson); 63 | var cxs = testPlatform.cxVDevMap[devicesJson.data.devices[0].id]; 64 | var oncx = cxs.filter(function(cx){ return cx.UUID === Characteristic.On.UUID; })[0]; 65 | assert.strictEqual(oncx.value, true, "Characteristic On updates to value boolean true"); 66 | }) 67 | -------------------------------------------------------------------------------- /tests/js/issue-72.js: -------------------------------------------------------------------------------- 1 | var homebridge = require("../../node_modules/homebridge/lib/api.js"); 2 | 3 | var api = new homebridge.API(); 4 | _initializer(api); 5 | var testPlatform = new platform({}, console.log); 6 | var Service = api.hap.Service; 7 | var Characteristic = api.hap.Characteristic; 8 | 9 | test("Issue 72 maxValue", function() { 10 | var devicesJson = {"data":{"structureChanged":true,"updateTime":1483686127,"devices":[ 11 | { 12 | "creationTime":1481040700, 13 | "creatorId":1, 14 | "deviceType":"switchMultilevel", 15 | "h":732948197, 16 | "hasHistory":false, 17 | "id":"ZWayVDev_zway_180-0-38", 18 | "location":1, 19 | "metrics":{ 20 | "icon":"blinds", 21 | "title":"Blind (180.0)", 22 | "level":0 23 | }, 24 | "permanently_hidden":false, 25 | "probeType":"motor", 26 | "tags":[], 27 | "visibility":true, 28 | "updateTime":1483686091 29 | } 30 | ]},"code":200,"message":"200 OK","error":null}; 31 | var foundAccessories = testPlatform.buildAccessoriesFromJson(devicesJson); 32 | assert.equal(foundAccessories.length, 1, "buildAccessoriesFromJson must find exactly one accessory."); 33 | var acc = new accessory(foundAccessories[0].name, foundAccessories[0].devDesc, testPlatform); 34 | var services = acc.getServices(); 35 | assert.equal(services.length, 2, "getServices must return two services."); 36 | //console.log(JSON.stringify(services[1].characteristics, null, 4)); 37 | var wcServices = services.filter(function(service){ return service.UUID === Service.WindowCovering.UUID; }); 38 | assert.equal(wcServices.length, 1, "getServices must return exactly one WindowCovering service"); 39 | var tpCxs = wcServices[0].characteristics.filter(function(cx){ return cx.UUID === Characteristic.TargetPosition.UUID; }); 40 | assert.equal(tpCxs.length, 1, "The WindowCovering service must have exactly one TargetPosition Characteristic"); 41 | assert.strictEqual(tpCxs[0].props['maxValue'], 100, "The TargetPosition Characteristic must equal numeric 100"); 42 | }); 43 | 44 | test("Issue 72 update", function() { 45 | var devicesJson = {"data":{"structureChanged":true,"updateTime":1483686137,"devices":[ 46 | { 47 | "creationTime":1481040700, 48 | "creatorId":1, 49 | "deviceType":"switchMultilevel", 50 | "h":732948197, 51 | "hasHistory":false, 52 | "id":"ZWayVDev_zway_180-0-38", 53 | "location":1, 54 | "metrics":{ 55 | "icon":"blinds", 56 | "title":"Blind (180.0)", 57 | "level":100 58 | }, 59 | "permanently_hidden":false, 60 | "probeType":"motor", 61 | "tags":[], 62 | "visibility":true, 63 | "updateTime":1483686136 64 | } 65 | ]},"code":200,"message":"200 OK","error":null}; 66 | testPlatform.processPollUpdate(devicesJson); 67 | var cxs = testPlatform.cxVDevMap[devicesJson.data.devices[0].id]; 68 | var tpcx = cxs.filter(function(cx){ return cx.UUID === Characteristic.TargetPosition.UUID; })[0]; 69 | assert.equal(tpcx.value, 100, "Characteristic TargetPosition updates to value 100"); 70 | var cpcx = cxs.filter(function(cx){ return cx.UUID === Characteristic.CurrentPosition.UUID; })[0]; 71 | assert.equal(cpcx.value, 100, "Characteristic CurrentPosition updates to value 100"); 72 | }); 73 | -------------------------------------------------------------------------------- /tests/js/update-without-change.js: -------------------------------------------------------------------------------- 1 | var homebridge = require("../../node_modules/homebridge/lib/api.js"); 2 | 3 | var api = new homebridge.API(); 4 | _initializer(api); 5 | var testPlatform = new platform({}, console.log); 6 | var Service = api.hap.Service; 7 | var Characteristic = api.hap.Characteristic; 8 | 9 | test("Change in updateTime (only) causes two change events from ContactSensors", function() { 10 | var devicesJson = {"data":{"structureChanged":true,"updateTime":1488002598,"devices":[ 11 | { 12 | "creationTime": 1482335001, 13 | "creatorId": 2, 14 | "deviceType": "sensorBinary", 15 | "h": -1774790026, 16 | "hasHistory": true, 17 | "id": "ZWayVDev_zway_41-0-48-1", 18 | "location": 12, 19 | "metrics": { 20 | "probeTitle": "General purpose", 21 | "scaleTitle": "", 22 | "icon": "motion", 23 | "level": "off", 24 | "title": "Mailbox Sensor" 25 | }, 26 | "permanently_hidden": false, 27 | "probeType": "general_purpose", 28 | "tags": [], 29 | "visibility": true, 30 | "updateTime": 1487924946 31 | } 32 | ]},"code":200,"message":"200 OK","error":null}; 33 | var foundAccessories = testPlatform.buildAccessoriesFromJson(devicesJson); 34 | assert.equal(foundAccessories.length, 1, "buildAccessoriesFromJson must find exactly one accessory."); 35 | var acc = new accessory(foundAccessories[0].name, foundAccessories[0].devDesc, testPlatform); 36 | var services = acc.getServices(); 37 | assert.equal(services.length, 2, "getServices must return two services."); 38 | //console.log(JSON.stringify(services[1].characteristics, null, 4)); 39 | var csServices = services.filter(function(service){ return service.UUID === Service.ContactSensor.UUID; }); 40 | assert.equal(csServices.length, 1, "getServices must return exactly one ContactSensor service"); 41 | var cssCxs = csServices[0].characteristics.filter(function(cx){ return cx.UUID === Characteristic.ContactSensorState.UUID; }); 42 | assert.equal(cssCxs.length, 1, "The ContactSensor service must have exactly one ContactSensorState Characteristic"); 43 | var cssCx = cssCxs[0]; 44 | assert.strictEqual(cssCx.value, Characteristic.ContactSensorState.CONTACT_DETECTED, "Characteristic ContactSensorState must have value CONTACT_DETECTED"); 45 | 46 | var updateJson = {"data":{"structureChanged":true,"updateTime":1488002600,"devices":[ 47 | { 48 | "creationTime": 1482335001, 49 | "creatorId": 2, 50 | "deviceType": "sensorBinary", 51 | "h": -1774790026, 52 | "hasHistory": true, 53 | "id": "ZWayVDev_zway_41-0-48-1", 54 | "location": 12, 55 | "metrics": { 56 | "probeTitle": "General purpose", 57 | "scaleTitle": "", 58 | "icon": "motion", 59 | "level": "off", 60 | "title": "Mailbox Sensor" 61 | }, 62 | "permanently_hidden": false, 63 | "probeType": "general_purpose", 64 | "tags": [], 65 | "visibility": true, 66 | "updateTime": 1488002599 67 | } 68 | ]},"code":200,"message":"200 OK","error":null}; 69 | 70 | //var changedToOpen = assert.async(); 71 | //var changedToClosed = assert.async(); 72 | var hasChangedToOpen = false; 73 | cssCx.on('change', function(ev){ 74 | if(ev.newValue == Characteristic.ContactSensorState.CONTACT_NOT_DETECTED){ 75 | assert.ok(!hasChangedToOpen, "A change event to CONTACT_NOT_DETECTED was observed before a change to CONTACT_DETECTED was observed."); 76 | hasChangedToOpen = true; 77 | //changedToOpen(); 78 | start(); 79 | } 80 | if(ev.newValue == Characteristic.ContactSensorState.CONTACT_DETECTED){ 81 | assert.ok(hasChangedToOpen, "A change event to CONTACT_DETECTED was observed after a change to CONTACT_NOT_DETECTED was observed."); 82 | //changedToClosed(); 83 | start(); 84 | } 85 | }); 86 | 87 | testPlatform.processPollUpdate(devicesJson); 88 | expect(7); 89 | stop(); 90 | }) 91 | -------------------------------------------------------------------------------- /tests/data/issue-48.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "structureChanged": true, 4 | "updateTime": 1473941669, 5 | "devices": [ 6 | { 7 | "creationTime": 1473256750, 8 | "creatorId": 1, 9 | "deviceType": "switchBinary", 10 | "h": -1617577652, 11 | "hasHistory": true, 12 | "id": "ZWayVDev_zway_14-0-37", 13 | "location": 2, 14 | "metrics": { 15 | "icon": "switch", 16 | "title": "Весь свет", 17 | "level": "on", 18 | "modificationTime": 1473885980, 19 | "lastLevel": "on" 20 | }, 21 | "permanently_hidden": false, 22 | "probeType": "", 23 | "tags": [], 24 | "visibility": true, 25 | "updateTime": 1473931187 26 | }, 27 | { 28 | "creationTime": 1473256755, 29 | "creatorId": 1, 30 | "deviceType": "sensorMultilevel", 31 | "h": 286070737, 32 | "hasHistory": true, 33 | "id": "ZWayVDev_zway_14-0-49-1", 34 | "location": 2, 35 | "metrics": { 36 | "probeTitle": "Temperature", 37 | "scaleTitle": "°C", 38 | "level": 43.9900032, 39 | "icon": "temperature", 40 | "title": "Температура в шкафу", 41 | "modificationTime": 1473940801, 42 | "lastLevel": 43.9900032 43 | }, 44 | "permanently_hidden": false, 45 | "probeType": "temperature", 46 | "tags": [], 47 | "visibility": true, 48 | "updateTime": 1473940801 49 | }, 50 | { 51 | "creationTime": 1473256753, 52 | "creatorId": 1, 53 | "deviceType": "sensorMultilevel", 54 | "h": 286091878, 55 | "hasHistory": true, 56 | "id": "ZWayVDev_zway_14-0-50-0", 57 | "location": 2, 58 | "metrics": { 59 | "probeTitle": "Electric", 60 | "scaleTitle": "kWh", 61 | "level": 29.3309984, 62 | "icon": "meter", 63 | "title": "кВт/ч", 64 | "modificationTime": 1473941623, 65 | "lastLevel": 29.3309984 66 | }, 67 | "permanently_hidden": false, 68 | "probeType": "meterElectric_kilowatt_per_hour", 69 | "tags": [ 70 | "Homebridge.Include" 71 | ], 72 | "visibility": true, 73 | "updateTime": 1473941623 74 | }, 75 | { 76 | "creationTime": 1473256753, 77 | "creatorId": 1, 78 | "deviceType": "sensorMultilevel", 79 | "h": 286091880, 80 | "hasHistory": true, 81 | "id": "ZWayVDev_zway_14-0-50-2", 82 | "location": 2, 83 | "metrics": { 84 | "probeTitle": "Electric", 85 | "scaleTitle": "W", 86 | "level": 85.608, 87 | "icon": "meter", 88 | "title": "Потребляемая мощность", 89 | "modificationTime": 1473941191, 90 | "lastLevel": 85.608 91 | }, 92 | "permanently_hidden": false, 93 | "probeType": "meterElectric_watt", 94 | "tags": [ 95 | "Homebridge.Include" 96 | ], 97 | "visibility": true, 98 | "updateTime": 1473941191 99 | }, 100 | { 101 | "creationTime": 1473256754, 102 | "creatorId": 1, 103 | "deviceType": "sensorMultilevel", 104 | "h": 286091882, 105 | "hasHistory": true, 106 | "id": "ZWayVDev_zway_14-0-50-4", 107 | "location": 2, 108 | "metrics": { 109 | "probeTitle": "Electric", 110 | "scaleTitle": "V", 111 | "level": 226.1619968, 112 | "icon": "meter", 113 | "title": "Вольметр", 114 | "modificationTime": 1473940801, 115 | "lastLevel": 226.1619968 116 | }, 117 | "permanently_hidden": false, 118 | "probeType": "meterElectric_voltage", 119 | "tags": [], 120 | "visibility": true, 121 | "updateTime": 1473940801 122 | }, 123 | { 124 | "creationTime": 1473256754, 125 | "creatorId": 1, 126 | "deviceType": "sensorMultilevel", 127 | "h": 286091883, 128 | "hasHistory": true, 129 | "id": "ZWayVDev_zway_14-0-50-5", 130 | "location": 2, 131 | "metrics": { 132 | "probeTitle": "Electric", 133 | "scaleTitle": "A", 134 | "level": 0.025, 135 | "icon": "meter", 136 | "title": "Амперметр", 137 | "modificationTime": 1473940801, 138 | "lastLevel": 0.025 139 | }, 140 | "permanently_hidden": false, 141 | "probeType": "meterElectric_ampere", 142 | "tags": [], 143 | "visibility": true, 144 | "updateTime": 1473940801 145 | } 146 | ] 147 | }, 148 | "code": 200, 149 | "message": "200 OK", 150 | "error": null 151 | } 152 | -------------------------------------------------------------------------------- /tests/js/issue-69.js: -------------------------------------------------------------------------------- 1 | var homebridge = require("../../node_modules/homebridge/lib/api.js"); 2 | 3 | var api, testPlatform, Service, Characteristic; 4 | 5 | api = new homebridge.API(); 6 | _initializer(api); 7 | testPlatform = new platform({}, console.log); 8 | Service = api.hap.Service; 9 | Characteristic = api.hap.Characteristic; 10 | 11 | test("Issue 69: sensorBinary.alarm_door can create a Door accessory", function() { 12 | var devicesJson = {"data":{"structureChanged":true,"updateTime":1483686127,"devices":[ 13 | { 14 | "creationTime": 1479323835, 15 | "creatorId": 1, 16 | "deviceType": "sensorBinary", 17 | "h": 1594949884, 18 | "hasHistory": false, 19 | "id": "ZWayVDev_zway_13-0-113-6-Door-A", 20 | "location": 5, 21 | "metrics": { 22 | "icon": "door", 23 | "level": "off", 24 | "title": "Fenster Bad" 25 | }, 26 | "permanently_hidden": false, 27 | "probeType": "alarm_door", 28 | "tags": [], 29 | "visibility": true, 30 | "updateTime": 1480462200 31 | }, 32 | { 33 | "creationTime": 1479323835, 34 | "creatorId": 1, 35 | "deviceType": "sensorBinary", 36 | "h": -2105364498, 37 | "hasHistory": false, 38 | "id": "ZWayVDev_zway_13-0-113-7-3-A", 39 | "location": 5, 40 | "metrics": { 41 | "icon": "smoke", 42 | "level": "on", 43 | "title": "Fibaro Burglar Alarm (13.0.113.7.3)" 44 | }, 45 | "permanently_hidden": true, 46 | "probeType": "alarm_burglar", 47 | "tags": [], 48 | "visibility": true, 49 | "updateTime": 1480462200 50 | }, 51 | { 52 | "creationTime": 1479323835, 53 | "creatorId": 1, 54 | "deviceType": "sensorBinary", 55 | "h": -2103519378, 56 | "hasHistory": false, 57 | "id": "ZWayVDev_zway_13-0-113-9-1-A", 58 | "location": 5, 59 | "metrics": { 60 | "icon": "alarm", 61 | "level": "off", 62 | "title": "Fibaro System Alarm (13.0.113.9.1)" 63 | }, 64 | "permanently_hidden": true, 65 | "probeType": "alarm_system", 66 | "tags": [], 67 | "visibility": true, 68 | "updateTime": 1480461223 69 | }, 70 | { 71 | "creationTime": 1479323842, 72 | "creatorId": 1, 73 | "deviceType": "sensorBinary", 74 | "h": 146374528, 75 | "hasHistory": false, 76 | "id": "ZWayVDev_zway_13-0-156-0-A", 77 | "location": 5, 78 | "metrics": { 79 | "icon": "alarm", 80 | "level": "on", 81 | "title": "Fibaro General Purpose alarm Alarm (13.0.156.0)" 82 | }, 83 | "permanently_hidden": true, 84 | "probeType": "alarmSensor_general_purpose", 85 | "tags": [], 86 | "visibility": true, 87 | "updateTime": 1480498020 88 | } 89 | ]},"code":200,"message":"200 OK","error":null}; 90 | var foundAccessories = testPlatform.buildAccessoriesFromJson(devicesJson); 91 | assert.equal(foundAccessories.length, 1, "buildAccessoriesFromJson must find exactly one accessory."); 92 | var acc = new accessory(foundAccessories[0].name, foundAccessories[0].devDesc, testPlatform); 93 | var services = acc.getServices(); 94 | assert.equal(services.length, 2, "getServices must return two services."); 95 | //console.log(JSON.stringify(services[1].characteristics, null, 4)); 96 | var dServices = services.filter(function(service){ return service.UUID === Service.Door.UUID; }); 97 | assert.equal(dServices.length, 1, "getServices must return exactly one Door service"); 98 | var cpCxs = dServices[0].characteristics.filter(function(cx){ return cx.UUID === Characteristic.CurrentPosition.UUID; }); 99 | assert.equal(cpCxs.length, 1, "The Door service must have exactly one CurrentPosition Characteristic"); 100 | assert.strictEqual(cpCxs[0].props['maxValue'], 100, "The CurrentPosition Characteristic must equal numeric 100"); 101 | }); 102 | 103 | api = new homebridge.API(); 104 | _initializer(api); 105 | testPlatform = new platform({}, console.log); 106 | Service = api.hap.Service; 107 | Characteristic = api.hap.Characteristic; 108 | 109 | test("Issue 69: sensorBinary.door-window can create a Door accessory (regression)", function() { 110 | var devicesJson = {"data":{"structureChanged":true,"updateTime":1483686137,"devices":[ 111 | { 112 | "creationTime": 1483802176, 113 | "creatorId": 1, 114 | "deviceType": "sensorBinary", 115 | "h": 1303282175, 116 | "hasHistory": false, 117 | "id": "ZWayVDev_zway_2-0-48-1", 118 | "location": 1, 119 | "metrics": { 120 | "probeTitle": "General purpose", 121 | "scaleTitle": "", 122 | "icon": "motion", 123 | "level": "on", 124 | "title": "c" 125 | }, 126 | "permanently_hidden": false, 127 | "probeType": "general_purpose", 128 | "tags": [], 129 | "visibility": true, 130 | "updateTime": 1483811173 131 | }, 132 | { 133 | "creationTime": 1483802185, 134 | "creatorId": 1, 135 | "deviceType": "sensorBinary", 136 | "h": -613749302, 137 | "hasHistory": false, 138 | "id": "ZWayVDev_zway_2-0-113-6-Door-A", 139 | "location": 1, 140 | "metrics": { 141 | "icon": "door", 142 | "level": "off", 143 | "title": "Fibaro Access Control Alarm (2.0.113.6.Door)" 144 | }, 145 | "permanently_hidden": false, 146 | "probeType": "alarm_door", 147 | "tags": [], 148 | "visibility": true, 149 | "updateTime": 1483864001 150 | }, 151 | { 152 | "creationTime": 1483802185, 153 | "creatorId": 1, 154 | "deviceType": "sensorBinary", 155 | "h": -1995004448, 156 | "hasHistory": false, 157 | "id": "ZWayVDev_zway_2-0-113-7-3-A", 158 | "location": 1, 159 | "metrics": { 160 | "icon": "smoke", 161 | "level": "off", 162 | "title": "Fibaro Burglar Alarm (2.0.113.7.3)" 163 | }, 164 | "permanently_hidden": false, 165 | "probeType": "alarm_burglar", 166 | "tags": [], 167 | "visibility": true, 168 | "updateTime": 1483811173 169 | }, 170 | { 171 | "creationTime": 1483802190, 172 | "creatorId": 1, 173 | "deviceType": "sensorBinary", 174 | "h": 1129728498, 175 | "hasHistory": false, 176 | "id": "ZWayVDev_zway_2-0-156-0-A", 177 | "location": 1, 178 | "metrics": { 179 | "icon": "alarm", 180 | "level": "on", 181 | "title": "Fibaro General Purpose alarm Alarm (2.0.156.0)" 182 | }, 183 | "permanently_hidden": false, 184 | "probeType": "alarmSensor_general_purpose", 185 | "tags": [], 186 | "visibility": true, 187 | "updateTime": 1483811173 188 | } 189 | ]},"code":200,"message":"200 OK","error":null}; 190 | var foundAccessories = testPlatform.buildAccessoriesFromJson(devicesJson); 191 | assert.equal(foundAccessories.length, 1, "buildAccessoriesFromJson must find exactly one accessory."); 192 | var acc = new accessory(foundAccessories[0].name, foundAccessories[0].devDesc, testPlatform); 193 | var services = acc.getServices(); 194 | assert.equal(services.length, 2, "getServices must return two services."); 195 | //console.log(JSON.stringify(services[1].characteristics, null, 4)); 196 | var dServices = services.filter(function(service){ return service.UUID === Service.Door.UUID; }); 197 | assert.equal(dServices.length, 1, "getServices must return exactly one Door service"); 198 | var cpCxs = dServices[0].characteristics.filter(function(cx){ return cx.UUID === Characteristic.CurrentPosition.UUID; }); 199 | assert.equal(cpCxs.length, 1, "The Door service must have exactly one CurrentPosition Characteristic"); 200 | assert.strictEqual(cpCxs[0].props['maxValue'], 100, "The CurrentPosition Characteristic must equal numeric 100"); 201 | }); 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # homebridge-zway 2 | 3 | [![npm version](https://badge.fury.io/js/homebridge-zway.svg)](https://badge.fury.io/js/homebridge-zway) 4 | 5 | ...is a Homebridge module for [Z-Way Server](http://razberry.z-wave.me/index.php?id=24). 6 | 7 | This platform lets you bridge a Z-Way Server instance (for example, running on [RaZBerry](http://razberry.z-wave.me) hardware or with a [UZB1](http://www.z-wave.me/index.php?id=28)) to HomeKit using Homebridge. 8 | 9 | Homebridge requires Z-Way Server version 2.0.1 or greater. It is currently tested against 2.2 though it is expected to still work with 2.0.1. 10 | 11 | 12 | 13 | - [homebridge-zway](#homebridge-zway) 14 | - [Quick Start](#quick-start) 15 | - [Supported Devices](#supported-devices) 16 | - [Problems/Troubleshooting](#problemstroubleshooting) 17 | - [Getting `/accessories`](#getting-accessories) 18 | - [Getting `/ZAutomation/api/v1/devices`](#getting-zautomationapiv1devices) 19 | - [Configuration](#configuration) 20 | - [Required](#required) 21 | - [Optional](#optional) 22 | - [Tags](#tags) 23 | - [Homebridge.Skip](#homebridgeskip) 24 | - [Homebridge.Include](#homebridgeinclude) 25 | - [Homebridge.IsPrimary](#homebridgeisprimary) 26 | - [Homebridge.Accessory.Id:*value*](#homebridgeaccessoryidvalue) 27 | - [Homebridge.Characteristic.Description:*value*](#homebridgecharacteristicdescriptionvalue) 28 | - [Homebridge.Service.Type:*value*](#homebridgeservicetypevalue) 29 | - [Make a Switch show as a `Lightbulb`](#make-a-switch-show-as-a-lightbulb) 30 | - [Change a Dimmer to a Switch](#change-a-dimmer-to-a-switch) 31 | - [Specify a switch to be an `Outlet`](#specify-a-switch-to-be-an-outlet) 32 | - [`sensorBinary` as Contact Sensor, Motion sensor, or Leak sensor](#sensorbinary-as-contact-sensor-motion-sensor-or-leak-sensor) 33 | - [Door/Window Sensor Service](#doorwindow-sensor-service) 34 | - [Dimmer or other `switchMultilevel` as `WindowCovering`](#dimmer-or-other-switchmultilevel-as-windowcovering) 35 | - [More](#more) 36 | - [Homebridge.Characteristic.Type:*value*](#homebridgecharacteristictypevalue) 37 | - [Homebridge.Interlock](#homebridgeinterlock) 38 | - [Homebridge.ContactSensorState.Invert](#homebridgecontactsensorstateinvert) 39 | - [Homebridge.OutletInUse.Level:*value*](#homebridgeoutletinuselevelvalue) 40 | - [Technical Detail](#technical-detail) 41 | - [Bridging and Mapping Logic](#bridging-and-mapping-logic) 42 | - [Recombination](#recombination) 43 | - [Primary Devices](#primary-devices) 44 | - [Service Type](#service-type) 45 | - [Required and Optional Characteristics](#required-and-optional-characteristics) 46 | - [Additional Services](#additional-services) 47 | 48 | 49 | ## Quick Start 50 | 51 | 1. `sudo npm install -g homebridge`, See the [Homebridge](https://github.com/nfarina/homebridge) project site for more information, and to configure Homebridge 52 | 2. `sudo npm install -g homebridge-zway` 53 | 3. Edit `~/.homebridge/config.json` and add the following: 54 | 55 | ``` 56 | "platforms": [ 57 | { 58 | "platform": "ZWayServer", 59 | "url": "http://your.ip.goes.here:8083/", 60 | "login": "[admin]", 61 | "password": "[password]" 62 | } 63 | ] 64 | ``` 65 | 66 | Then see the [Configuration](#configuration) and [Tags](#tags) sections below to to customize the bridge for your environment or devices. 67 | 68 | ## Supported Devices 69 | 70 | Support is currently designed around Z-Wave devices. The bridge uses the `VDev` interface, so other device types (such as EnOcean) should also work, but this has not been tested. 71 | 72 | Generally speaking, the following types of devices will work: 73 | 74 | * On/off switches 75 | * Dimmers 76 | * RGB bulbs (e.g. Aeon Labs') 77 | * Thermostats (heating only for now!) 78 | * Temperature sensors 79 | * Door/window sensors 80 | * Contact sensors 81 | * Light sensors (needs work) 82 | * Motion sensors 83 | * Door Locks (e.g. Danalock) 84 | * Relative Humidity sensors (e.g. Aeon Labs Multisensor 6...needs testing!) 85 | * Window Coverings 86 | * Water leak sensors 87 | * Smoke detectors 88 | 89 | Additional devices in progress: 90 | 91 | * Remotes/buttons (Initial implementation available in trunk!) 92 | 93 | ## Problems/Troubleshooting 94 | 95 | If you have a problem with the Z-Way Server bridge or with a particular device, please create an Issue and attach the contents of `/accessories` from your Homebridge instance, and of `/ZAutomation/api/v1/devices` from your Z-Way Server instance (or at least those parts that pertain to your problem). 96 | 97 | ### Getting `/accessories` 98 | 99 | Current versions of homebridge will--by default--only allow communication with encrypted paired devices. To manually retrieve the `accessories` JSON data, you will need to run Homebridge in insecure mode by adding the `-I` switch (e.g. `homebridge -I`). Then, point your browser at your Homebridge IP and port and hit the accessories URL endpoint, e.g. `http://127.0.0.1:51826/accessories`. 100 | 101 | ### Getting `/ZAutomation/api/v1/devices` 102 | 103 | To retrieve your `devices` JSON from Z-Way, first connect to the Home Automation interface (usually running on port 8083) with your browser and log in with your username and password. After successfully logging in, go to the following address in the same browser window: `http://your.ip.goes.here:8083/ZAutomation/api/v1/devices`, substituting your Z-Way server's IP address and port as appropriate. 104 | 105 | # Configuration 106 | 107 | ## Required 108 | 109 | The minimum configuration includes the following 3 parameters 110 | 111 | { 112 | "platform": "ZWayServer", 113 | "url": "http://192.168.1.100:8083/", 114 | "login": "[admin]", 115 | "password": "[password]" 116 | } 117 | 118 | | Key | Description | 119 | | --- | --- | 120 | | `url` | The base URL of your Z-Way Server installation. For instance, the URL above would be the right pattern if your Z-Way web UI is accessed at `http://192.168.1.100:8083/smarthome/#/elements`. Since the protocol, address, port, and path are all used, you're covered if you change any of them (i.e. if you're running Z-Way Server behind a reverse-proxy). | 121 | | `login` | A username with permissions in Z-Way. Using `admin` is actually not recommended, but you can to start with it if you want to make sure everything's working. It's best to create another user so you don't have your admin password sitting in the configuration file. | 122 | | `password` | The password for the user specified in `login`. | 123 | 124 | ## Optional 125 | 126 | The following additional configuration options are supported 127 | 128 | | Key | Default | Description | 129 | | --- | :---: | --- | 130 | | `poll_interval` | `2` | The time in seconds between polls to Z-Way Server for updates. 2 seconds is what the Z-Way web UI uses, so this should probably be sufficient for most cases. | 131 | | `battery_low_level` | `15` | For devices that report a battery percentage, this will be used to set the `BatteryLow` Characteristic to `true`. | 132 | | `dimmer_off_threshold` | `5` | In some cases (especially older versions of Z-Way) dimmers would never ramp all the way down to zero when switching off. This value determines what threshold to use to consider the dimmer "off". At or below this level, the `Switch` will report as "off", but the `Brightness` value will remain at the actual reported value. This has become less necessary with newer versions of Z-Way, and in the future the default will be changed to `0`. Set the value to `0` to always only report dimmers as "off" when the `Brightness` reaches `0`. | 133 | | `outlet_in_use_level` | `2` | For `Outlet` devices (currently only available when designated with the tag `Homebridge.Service.Type:Outlet`), sets the level that a Watt meter device must rise above to trigger the `OutletInUse` value to "true". | 134 | | `blink_time` | `0.5` | For some devices and some use cases, a change may occur and be reversed in a device so quickly that the changed value does not get captured in polling. Mainly this happens with button devices, but may also occur with a contact sensor that opens and closes quickly, such as if you are monitoring a mailbox door. Such an event does change the `updateTime` of the Z-Way device however, so Homebridge-ZWay will capture that change and simulate the missed event by flipping the value of the HomeKit device and then flipping it back again. This `blink_time` is the amount of time in seconds the device will stay in the inverted state before flipping back. The default of 0.5 seconds seems to work well, but you can adjust it with this config setting. It is recommended to keep this value below half of the `poll_interval`. 135 | | `split_services` | `true` (after 0.4.0) | **DEPRECATED** This setting affects how Characteristics are organized within an accessory. If set to "true", for instance the `BatteryLevel` and `StatusLowBattery` Characteristics are put into a `BatteryService`, where `false` causes them to be simply added as additional Characteristics on the main Service. This was done mainly to support the Eve app better, which made separate Services appear the same as whole different Accessories. The Eve app now groups services in the same accessory. This has been changed to default to `true` in 0.4.0 and will later be removed entirely. | 136 | | `opt_in` | `false` | If this is set to `true`, only devices tagged with `Homebridge.Include` will be bridged. This is mainly useful for development or troubleshooting purposes, or if you really only want to include a few accessories from your Z-Way server. | 137 | 138 | ## Tags 139 | 140 | You can change the default behavior of Homebridge by adding certain tags to your devices in the Z-Way web UI. Sometimes this may be necessary to get certain devices to be bridged properly, as there are a large number of Z-Way devices and sometimes the "guessing" that Homebridge does to get one device right may be the wrong answer for a different device. 141 | 142 | Tags are case sensitive. Some tags allow you to specify a value, and these have the value after a `:` character (everything after the `:` is the value). Tags without a value are boolean, and are tested for presence or absence. 143 | 144 | ### Homebridge.Skip 145 | 146 | Any devices with this tag will not be bridged, and will be excluded from the logic used by Homebridge to try and translate Z-Way devices to HomeKit devices. 147 | 148 | ### Homebridge.Include 149 | 150 | This tag has two different but related purposes. Essentially, it lets you include a device on the bridge that would have otherwise been excluded. 151 | 152 | 1. If in the Z-Way GUI you set "Permanently hide this element", then it will not be bridged by Homebridge by default. If you want it to be hidden in Z-Way yet visible in Homebridge, you can use the `Homebridge.Include` tag to override this behavior. 153 | 154 | 2. Used in conjunction with the `opt_in` configuration option above, this marks a device to be included in opt-in mode, while all devices without the tag are skipped. 155 | 156 | Note that if both `Homebridge.Skip` and `Homebridge.Include` are specified on the same device that `Homebridge.Skip` wins--_unless_ you have set the `opt_in` configuration option. This is useful for troubleshooting or development: you can have a "production" instance of Homebridge running that skips a troublesome device (with `opt_in` false) and a second instance for testing running with `opt_in` true that will pick up the device regardless of the `Skip` tag. 157 | 158 | ### Homebridge.IsPrimary 159 | 160 | This overrides or supplements Homebridge's logic for figuring out what kind of Accessory or Services to build from your Z-Way devices, and sometimes how to use the multiple devices within an Accessory. This is particularly useful if Homebridge gets it wrong by default, or the components of your device are unusual or ambiguous. 161 | 162 | For instance, if you have a Devolo Door/Window sensor but are primarily using it for a temperature sensor, you could tag the temperature sensor device with `Homebridge.IsPrimary`, which would change the way the device is reported to HomeKit. 163 | 164 | For another example, the Aeon Labs RGB Bulb has three dimmers: one for the color LEDs, one for "Cold" white and one for "Soft" white. It's not obvious to Homebridge which of the dimmers controls the color LEDs, but it can figure out that it's an RGB bulb. So, you should put `Homebridge.IsPrimary` on the dimmer for the color LEDs, and the other dimmer devices will be treated as extras. 165 | 166 | ### Homebridge.Accessory.Id:*value* 167 | 168 | Manually specifies the Accessory identifier to use for this device. This has the effect of allowing you to split or merge devices that would be grouped differently by default by Homebridge's translation logic. 169 | 170 | For instance, many Z-Wave devices include a temperature sensor that has nothing to do with their primary function (such as an outlet switch), so you could give that temperature sensor a different Accessory Id, and it would appear to HomeKit as a separate Accessory. Or, if you have a Danfoss Living Connect thermostat (which does not report the room temperature via Z-Wave) and a temperature monitoring device in the same room, you could give them the same ID and Homebridge would bridge them as a single device on the HomeKit side. 171 | 172 | ### Homebridge.Characteristic.Description:*value* 173 | 174 | This tag lets you override the description for the Characteristic(s) created from this device. This may affect the way the Characteristic is displayed in your HomeKit app. 175 | 176 | ### Homebridge.Service.Type:*value* 177 | 178 | This tag allows you to explicitly specify what kind of HomeKit Service to create for an accessory. This is only supported for a specific set of cases, and even then may break your bridge! It will only be effective on the primary device of a Service, so either on the primary device of an Accessory or on a device like the Aeon Labs RGB bulb which has multiple dimmers which have to be split off into their own Services. 179 | 180 | #### Make a Switch show as a `Lightbulb` 181 | 182 | Tagging a device with `Homebridge.Service.Type:Lightbulb` allows you to explicitly report a `switchBinary` as a HomeKit `Lightbulb` (normally only `switchMultilevel`s will be automatically bridged as lights). This means that if you ask Siri to "turn off the lights" in a room, the marked device should be included. 183 | 184 | #### Change a Dimmer to a Switch 185 | 186 | Somewhat the opposite of above, you can specify `Homebridge.Service.Type:Switch` on a dimmer to treat that device as a standard switch instead of a dimmer. Besides doing this just out of preference, this is handy on the aforementioned Aeon Labs RGB bulb's extra "white" dimmers, because the primary (color) dimmer actually controls the dimming of the two whites. 187 | 188 | #### Specify a switch to be an `Outlet` 189 | 190 | Tagging a device with `Homebridge.Service.Type:Outlet` makes a `switchBinary` into an `Outlet` service instead of a switch. The main functional reason you would want to do this is when a device also has a Watt meter, it will add an `OutletInUse` Characteristic that will become "true" once the wattage consumed rises above a specified level (the default is `2` Watts, see also the tag `Homebridge.OutletInUse.Level:*value*` below). This, for example, would let you put your bedside phone charger on a Watt meter, and when you plug your phone in for the night, a HomeKit trigger could set your "Good Night" scene. 191 | 192 | #### Button as `StatefulProgrammableSwitch` 193 | 194 | By default, `toggleButton` devices are bridged as `StatelessProgrammableSwitch` services, which is best for triggering scenes. However, if you want to control a single device with a button, it may be easier to specify the `Homebridge.Service.Type:StatefulProgrammableSwitch` tag for your device, which gives the button a state value that you can more easily build automations against. 195 | 196 | Note that the switch's state is always set to `0` at Homebridge startup, since it doesn't know what to pull its initial state from. The state value only persists as long as Homebridge is running. 197 | 198 | #### `sensorBinary` as Contact Sensor, Motion sensor, or Leak sensor 199 | 200 | Many sensor devices will only be reported by Z-Way as a `sensorBinary` or `sensorBinary.general_purpose`, which is too vague to determine its real purpose. In this case you must specify either `Homebridge.Service.Type:MotionSensor`, `Homebridge.Service.Type:ContactSensor` or `Homebridge.Service.Type:LeakSensor` so that the bridge will know how to bridge the device. See also `Homebridge.Characteristic.Type` below—though in many cases, once you specify the Service type the bridge can figure out how to properly report the Characteristic. 201 | 202 | #### Door/Window Sensor Service 203 | 204 | There is not really a direct analogue to a `Door/Window` sensor in HomeKit--the possibilities are `GarageDoor`, `Door`, `Window`, and `ContactSensor`. The first three are really designed for door or window *controls* instead of sensors, and then the last one is a very generic type of sensor. Homebridge now lets you choose which of these four to use for your sensor by specifying one of the following values in this tag: 205 | 206 | * `GarageDoor`: This is the old default, and may make the most sense if you have an apartment with a single door. It reports the door as "Open" or "Closed" in most apps, and you get iOS notifications when the door opens or closes. However, if you have an actual garage door opener that is controllable by Siri, when you say "Open the Garage Door," Siri will try to also open your door sensor...which will fail, and she will complain, which can be annoying. It also adds a required "ObstructionDetected" sensor, which does nothing. 207 | * `Door`: This is the new default and avoids the "multiple" Garage Door problem above. However it reports position in percent (always 0% for closed or 100% for open), and still generates iOS notifications when the door opens and closes. It will also have a "PositionState" characteristic, which is always "Stopped". 208 | * `Window`: This is identical to `Door`, but may be categorized differently by Siri or in apps. 209 | * `ContactSensor`: This treats the `Door/Window` sensor as a simple contact sensor (on or off). This has two main advantages: 210 | a. It's the simplest option, and doesn't have any superfluous, non-working characteristics. 211 | b. You don't get any iOS notifications for state changes, so pick this if you find those annoying. 212 | But, you won't be able to ask Siri about the state of the "Door", and app support for this characteristic has been historically lacking (Eve works great now). Also note that you can invert the value with `Homebridge.ContactSensorState.Invert`, which may result in a more intuitive value being shown. 213 | 214 | #### Dimmer or other `switchMultilevel` as `WindowCovering` 215 | 216 | If you have a shutter/drapery control that uses a percentage value for open/closed, but is not automatically recognized as such (for instance it is instead shown as a light dimmer), you can specify `Homebridge.Service.Type:WindowCovering` to force it to be recognized correctly. This should not be necessary in most cases. 217 | 218 | #### More 219 | 220 | There will be additional devices and use cases where this will be used. If you think you have a good use case for this that is not supported by the current code, please submit an issue with the guidelines above. 221 | 222 | ### Homebridge.Characteristic.Type:*value* 223 | 224 | Like [`Homebridge.Service.Type`](#homebridgeservicetypevalue), this allows you to explicitly define the type of Characteristic that will be created for a given device. This will override the bridge's own logic for selecting which Characteristic(s) to build from a device, so use it with caution! 225 | 226 | This tag is particularly useful for scenarios where the physical device is reported ambiguously by Z-Way. For instance, the Vision ZP 3012 motion sensor is presented by Z-Way merely as two `sensorBinary` devices (plus a temperature sensor), one of which is the actual motion sensor and the other is a tampering detector. The `sensorBinary` designation (with no accompanying `probeTitle`) is too ambiguous for the bridge to work with, so it will be ignored. To make this device work, you can tag the motion sensor device in Z-Way with `Homebridge.Characteristic.Type:MotionDetected` and (optionally) the tamper detector with `Homebridge.Characteristic.Type:StatusTampered`. (Note that for this device you will also need to tag the motion sensor with `Homebridge.Service.Type:MotionSensor` and `Homebridge.IsPrimary`, otherwise the more recognizable temperature sensor will take precedence.) 227 | 228 | ### Homebridge.Interlock 229 | 230 | Adding the tag `Homebridge.Interlock` to the primary device will add an additional `Switch` service named "Interlock", defaulted to "on". When this switch is engaged, you will not be able to set the characteristics of any other devices in the accessory! You will be required to turn off the Interlock switch before changing/setting other values. This is a kind of a "safety" switch, so that you (or Siri) does not turn something on or off that you did not intend. A use case might be if you had your cable modem or router plugged into a power outlet switch so that you could power cycle it remotely: you would not want to turn this off accidentally, so add an Interlock switch. **Do NOT rely on this capability for health or life safety purposes--it is a convenience and is not designed or intended to be a robust safety feature.** 231 | 232 | ### Homebridge.ContactSensorState.Invert 233 | 234 | If you have a `ContactSensor`, this will invert the state reported to HomeKit. This is useful if you are using the `ContactSensor` Service type for a `Door/Window` sensor, and you want it to show "Yes" when open and "No" when closed, which may be more intuitive. The default for a `ContactSensor` is to show "Yes" when there is contact (in the case of a door, when it's closed) and "No" when there is no contact (which for a door is when it's open). 235 | 236 | ### Homebridge.OutletInUse.Level:*value* 237 | 238 | This can be used in conjunction with the `Homebridge.Service.Type:Outlet` tag and lets you change the threshold value that changes the `OutletInUse` value to true for a particular device. The main use case is if you have a USB charger or transformer that always consumes a given amount of power, but you want events to trigger when the consumption rises above that level (e.g. when a device is plugged into the USB charger and draws more power). You could also adjust this to trigger only when the higher settings on a 3-way lamp are used, when a fan is turned to high speed, or other creative purposes. 239 | 240 | # Technical Detail 241 | 242 | ## Bridging and Mapping Logic 243 | 244 | As with any bridge, the devices on the The Z-Way Server side do not necessarily map perfectly to the HomeKit device model. So, the Z-Way bridge does a lot of guesswork and generalization to make the devices make sense on the HomeKit side. 245 | 246 | ### Recombination 247 | 248 | A Z-Wave device usually supports multiple Command Classes for different kinds of controls and sensors, and Z-Way splits all of those devices up into separate "Virtual Devices". This is the most flexible way to deal with composite devices, but having all those individual components in a HomeKit app as separate Accessories would be very clumsy, and sometimes impossible (for instance, a HomeKit Thermostat requires a temperature sensor device in the same Accessory and Service, so it can't be bridged by itself). So, Homebridge attempts to recombine those Virtual Devices back into a single composite device. 249 | 250 | ### Primary Devices 251 | 252 | After combining the sub-devices back into a single composite device, it then tries to make sense of what kind of Accessory that composite device should be in HomeKit. It does this by trying to determine which sub-device should be treated as the "primary" device. 253 | 254 | This can be tricky, since devices may contain any combination of sensors and controls. The bridge tries to pick more unusual or specific sub-devices out first. For example, many many devices include a temperature sensor, but a Door/Window sensor is far less common, and if present probably indicates the device's intended main purpose. 255 | 256 | ### Service Type 257 | 258 | Once Homebridge decides on the primary device, it chooses a Service type that corresponds to that device and attempts to configure that Service. For example, if it finds a thermostat device in Z-Way, it will create a Thermostat Service in HomeKit and look for a temperature sensor device in the same composite device. 259 | 260 | ### Required and Optional Characteristics 261 | 262 | Many Services in HomeKit have a set of Characteristics that must appear in the Service to be in compliance--for example the aforementioned Thermostat, which requires a thermostat device and a temperature sensor. Homebridge will attempt to fill all the requirements from the composite device. If it fails, it will not bridge that Service (and likely nothing else in the device)! You may see messages to this effect at the console on startup. 263 | 264 | After fulfilling the required Characteristics, the bridge will look at the remaining sub-devices and--if it understands them and knows what Characteristic(s) it can build from them, it will tack those Characteristics onto the primary service. HomeKit seems fairly flexible about this sort of tack-on extras ability, so if you have a device that is an outlet switch with a temperature sensor in it, you'll get a Switch with a TemperatureSensor in HomeKit as well. 265 | 266 | ### Additional Services 267 | 268 | Because HomeKit does not support more than one of the same Characteristic in the same Service, if the bridge encounters a composite device with this composition (say, multiple dimmers) it will in some cases build additional services to contain the additional Characteristics. 269 | 270 | *NOTE: At the moment, the only scenario where this happens is RGBW bulbs.* 271 | 272 | If this happens, you may see ambiguously titled controls in your HomeKit app (multiple Brightness controls, for instance). 273 | -------------------------------------------------------------------------------- /tests/data/issue-70.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "structureChanged": true, 4 | "updateTime": 1483469807, 5 | "devices": [{ 6 | "creationTime": 1478079528, 7 | "creatorId": 43, 8 | "deviceType": "switchBinary", 9 | "h": 1620899615, 10 | "hasHistory": true, 11 | "id": "Presence_presence_43", 12 | "location": 0, 13 | "metrics": { 14 | "title": "Presence", 15 | "level": "on", 16 | "mode": "home", 17 | "icon": "/ZAutomation/api/v1/load/modulemedia/Presence/presence_on.png" 18 | }, 19 | "permanently_hidden": false, 20 | "probeType": "presence", 21 | "tags": [], 22 | "visibility": true, 23 | "updateTime": 1483419600 24 | }, { 25 | "creationTime": 1478079528, 26 | "creatorId": 43, 27 | "deviceType": "switchBinary", 28 | "h": 1079840253, 29 | "hasHistory": true, 30 | "id": "Presence_vacation_43", 31 | "location": 0, 32 | "metrics": { 33 | "title": "Vacation", 34 | "level": "off", 35 | "mode": "home", 36 | "icon": "/ZAutomation/api/v1/load/modulemedia/Presence/vacation_off.png" 37 | }, 38 | "permanently_hidden": false, 39 | "probeType": "vacation", 40 | "tags": [], 41 | "visibility": true, 42 | "updateTime": 1483419600 43 | }, { 44 | "creationTime": 1478079528, 45 | "creatorId": 43, 46 | "deviceType": "switchBinary", 47 | "h": -676182, 48 | "hasHistory": true, 49 | "id": "Presence_night_43", 50 | "location": 0, 51 | "metrics": { 52 | "title": "Night", 53 | "level": "off", 54 | "mode": "home", 55 | "icon": "/ZAutomation/api/v1/load/modulemedia/Presence/night_off.png", 56 | "modificationTime": 1483419600, 57 | "lastLevel": "off" 58 | }, 59 | "permanently_hidden": false, 60 | "probeType": "night", 61 | "tags": [], 62 | "visibility": true, 63 | "updateTime": 1483419600 64 | }, { 65 | "creationTime": 1478079526, 66 | "creatorId": "current_37", 67 | "deviceType": "sensorMultilevel", 68 | "h": -1759605314, 69 | "hasHistory": true, 70 | "id": "WeatherUnderground_current_37", 71 | "location": 0, 72 | "metrics": { 73 | "probeTitle": "WeatherUndergoundCurrent", 74 | "scaleTitle": "°C", 75 | "title": "Current Condition", 76 | "temperature_list": [-0.6, -0.3, 0.1], 77 | "temperatureChange": "rise", 78 | "conditiongroup": "neutral", 79 | "condition": "mostlycloudy", 80 | "level": -0.6, 81 | "modificationTime": 1483468111, 82 | "lastLevel": -0.6, 83 | "temperature": -0.6, 84 | "icon": "http://icons.wxug.com/i/c/k/nt_mostlycloudy.gif", 85 | "feelslike": -1, 86 | "weather": "Mostly Cloudy", 87 | "pop": 70, 88 | "high": 0, 89 | "low": -3, 90 | "raw": { 91 | "image": { 92 | "url": "http://icons.wxug.com/graphics/wu2/logo_130x80.png", 93 | "title": "Weather Underground", 94 | "link": "http://www.wunderground.com" 95 | }, 96 | "display_location": { 97 | "full": "Dachau, Germany", 98 | "city": "Dachau", 99 | "state": "OB", 100 | "state_name": "Germany", 101 | "country": "DL", 102 | "country_iso3166": "DE", 103 | "zip": "00000", 104 | "magic": "183", 105 | "wmo": "10858", 106 | "latitude": "48.25999832", 107 | "longitude": "11.43000031", 108 | "elevation": "474.9" 109 | }, 110 | "observation_location": { 111 | "full": "Friedrich-Dürr-Straße, Dachau, ", 112 | "city": "Friedrich-Dürr-Straße, Dachau", 113 | "state": "", 114 | "country": "DE", 115 | "country_iso3166": "DE", 116 | "latitude": "48.246189", 117 | "longitude": "11.444480", 118 | "elevation": "1584 ft" 119 | }, 120 | "estimated": {}, 121 | "station_id": "IDACHAU7", 122 | "observation_time": "Last Updated on January 3, 7:18 PM CET", 123 | "observation_time_rfc822": "Tue, 03 Jan 2017 19:18:52 +0100", 124 | "observation_epoch": "1483467532", 125 | "local_time_rfc822": "Tue, 03 Jan 2017 19:28:31 +0100", 126 | "local_epoch": "1483468111", 127 | "local_tz_short": "CET", 128 | "local_tz_long": "Europe/Berlin", 129 | "local_tz_offset": "+0100", 130 | "weather": "Mostly Cloudy", 131 | "temperature_string": "30.9 F (-0.6 C)", 132 | "temp_f": 30.9, 133 | "temp_c": -0.6, 134 | "relative_humidity": "83%", 135 | "wind_string": "From the SW at 1.9 MPH Gusting to 3.1 MPH", 136 | "wind_dir": "SW", 137 | "wind_degrees": 216, 138 | "wind_mph": 1.9, 139 | "wind_gust_mph": "3.1", 140 | "wind_kph": 3.1, 141 | "wind_gust_kph": "5.0", 142 | "pressure_mb": "1023", 143 | "pressure_in": "30.21", 144 | "pressure_trend": "0", 145 | "dewpoint_string": "26 F (-3 C)", 146 | "dewpoint_f": 26, 147 | "dewpoint_c": -3, 148 | "heat_index_string": "NA", 149 | "heat_index_f": "NA", 150 | "heat_index_c": "NA", 151 | "windchill_string": "31 F (-1 C)", 152 | "windchill_f": "31", 153 | "windchill_c": "-1", 154 | "feelslike_string": "31 F (-1 C)", 155 | "feelslike_f": "31", 156 | "feelslike_c": "-1", 157 | "visibility_mi": "N/A", 158 | "visibility_km": "N/A", 159 | "solarradiation": "--", 160 | "UV": "-1", 161 | "precip_1hr_string": "-999.00 in ( 0 mm)", 162 | "precip_1hr_in": "-999.00", 163 | "precip_1hr_metric": " 0", 164 | "precip_today_string": "-999.00 in (-25375 mm)", 165 | "precip_today_in": "-999.00", 166 | "precip_today_metric": "--", 167 | "icon": "mostlycloudy", 168 | "icon_url": "http://icons.wxug.com/i/c/k/nt_mostlycloudy.gif", 169 | "forecast_url": "http://www.wunderground.com/global/stations/10858.html", 170 | "history_url": "http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=IDACHAU7", 171 | "ob_url": "http://www.wunderground.com/cgi-bin/findweather/getForecast?query=48.246189,11.444480", 172 | "nowcast": "" 173 | }, 174 | "timestamp": 1483468111759, 175 | "percipintensity": 0, 176 | "uv": -1, 177 | "solarradiation": null 178 | }, 179 | "permanently_hidden": false, 180 | "probeType": "condition", 181 | "tags": [], 182 | "visibility": true, 183 | "updateTime": 1483468112 184 | }, { 185 | "creationTime": 1478079526, 186 | "creatorId": "forecast_37", 187 | "deviceType": "sensorMultilevel", 188 | "h": -1959135532, 189 | "hasHistory": true, 190 | "id": "WeatherUnderground_forecast_37", 191 | "location": 0, 192 | "metrics": { 193 | "probeTitle": "WeatherUndergoundForecast", 194 | "scaleTitle": "°C", 195 | "title": "Forecast", 196 | "conditiongroup": "snow", 197 | "condition": "snow", 198 | "level": "-3 - 1", 199 | "modificationTime": 1483468112, 200 | "lastLevel": "-3 - 1", 201 | "icon": "http://icons.wxug.com/i/c/k/snow.gif", 202 | "pop": 90, 203 | "weather": "Snow", 204 | "high": 1, 205 | "low": -3, 206 | "raw": [{ 207 | "date": { 208 | "epoch": "1483466400", 209 | "pretty": "7:00 PM CET on January 03, 2017", 210 | "day": 3, 211 | "month": 1, 212 | "year": 2017, 213 | "yday": 2, 214 | "hour": 19, 215 | "min": "00", 216 | "sec": 0, 217 | "isdst": "0", 218 | "monthname": "January", 219 | "monthname_short": "Jan", 220 | "weekday_short": "Tue", 221 | "weekday": "Tuesday", 222 | "ampm": "PM", 223 | "tz_short": "CET", 224 | "tz_long": "Europe/Berlin" 225 | }, 226 | "period": 1, 227 | "high": { 228 | "fahrenheit": "33", 229 | "celsius": "0" 230 | }, 231 | "low": { 232 | "fahrenheit": "27", 233 | "celsius": "-3" 234 | }, 235 | "conditions": "Snow", 236 | "icon": "snow", 237 | "icon_url": "http://icons.wxug.com/i/c/k/snow.gif", 238 | "skyicon": "", 239 | "pop": 70, 240 | "qpf_allday": { 241 | "in": 0.06, 242 | "mm": 2 243 | }, 244 | "qpf_day": { 245 | "in": null, 246 | "mm": null 247 | }, 248 | "qpf_night": { 249 | "in": 0.06, 250 | "mm": 2 251 | }, 252 | "snow_allday": { 253 | "in": 0.6, 254 | "cm": 1.5 255 | }, 256 | "snow_day": { 257 | "in": null, 258 | "cm": null 259 | }, 260 | "snow_night": { 261 | "in": 0.6, 262 | "cm": 1.5 263 | }, 264 | "maxwind": { 265 | "mph": 10, 266 | "kph": 17, 267 | "dir": "WNW", 268 | "degrees": 0 269 | }, 270 | "avewind": { 271 | "mph": 5, 272 | "kph": 9, 273 | "dir": "WSW", 274 | "degrees": 0 275 | }, 276 | "avehumidity": 81, 277 | "maxhumidity": 0, 278 | "minhumidity": 0 279 | }, { 280 | "date": { 281 | "epoch": "1483552800", 282 | "pretty": "7:00 PM CET on January 04, 2017", 283 | "day": 4, 284 | "month": 1, 285 | "year": 2017, 286 | "yday": 3, 287 | "hour": 19, 288 | "min": "00", 289 | "sec": 0, 290 | "isdst": "0", 291 | "monthname": "January", 292 | "monthname_short": "Jan", 293 | "weekday_short": "Wed", 294 | "weekday": "Wednesday", 295 | "ampm": "PM", 296 | "tz_short": "CET", 297 | "tz_long": "Europe/Berlin" 298 | }, 299 | "period": 2, 300 | "high": { 301 | "fahrenheit": "34", 302 | "celsius": "1" 303 | }, 304 | "low": { 305 | "fahrenheit": "26", 306 | "celsius": "-3" 307 | }, 308 | "conditions": "Snow", 309 | "icon": "snow", 310 | "icon_url": "http://icons.wxug.com/i/c/k/snow.gif", 311 | "skyicon": "", 312 | "pop": 90, 313 | "qpf_allday": { 314 | "in": 0.31, 315 | "mm": 8 316 | }, 317 | "qpf_day": { 318 | "in": 0.21, 319 | "mm": 5 320 | }, 321 | "qpf_night": { 322 | "in": 0.1, 323 | "mm": 3 324 | }, 325 | "snow_allday": { 326 | "in": 3.1, 327 | "cm": 7.9 328 | }, 329 | "snow_day": { 330 | "in": 2.1, 331 | "cm": 5.3 332 | }, 333 | "snow_night": { 334 | "in": 1, 335 | "cm": 2.5 336 | }, 337 | "maxwind": { 338 | "mph": 30, 339 | "kph": 48, 340 | "dir": "W", 341 | "degrees": 268 342 | }, 343 | "avewind": { 344 | "mph": 23, 345 | "kph": 37, 346 | "dir": "W", 347 | "degrees": 268 348 | }, 349 | "avehumidity": 93, 350 | "maxhumidity": 0, 351 | "minhumidity": 0 352 | }, { 353 | "date": { 354 | "epoch": "1483639200", 355 | "pretty": "7:00 PM CET on January 05, 2017", 356 | "day": 5, 357 | "month": 1, 358 | "year": 2017, 359 | "yday": 4, 360 | "hour": 19, 361 | "min": "00", 362 | "sec": 0, 363 | "isdst": "0", 364 | "monthname": "January", 365 | "monthname_short": "Jan", 366 | "weekday_short": "Thu", 367 | "weekday": "Thursday", 368 | "ampm": "PM", 369 | "tz_short": "CET", 370 | "tz_long": "Europe/Berlin" 371 | }, 372 | "period": 3, 373 | "high": { 374 | "fahrenheit": "26", 375 | "celsius": "-3" 376 | }, 377 | "low": { 378 | "fahrenheit": "6", 379 | "celsius": "-14" 380 | }, 381 | "conditions": "Overcast", 382 | "icon": "cloudy", 383 | "icon_url": "http://icons.wxug.com/i/c/k/cloudy.gif", 384 | "skyicon": "", 385 | "pop": 20, 386 | "qpf_allday": { 387 | "in": 0.02, 388 | "mm": 1 389 | }, 390 | "qpf_day": { 391 | "in": 0, 392 | "mm": 0 393 | }, 394 | "qpf_night": { 395 | "in": 0.02, 396 | "mm": 1 397 | }, 398 | "snow_allday": { 399 | "in": 0.3, 400 | "cm": 0.8 401 | }, 402 | "snow_day": { 403 | "in": 0, 404 | "cm": 0 405 | }, 406 | "snow_night": { 407 | "in": 0.3, 408 | "cm": 0.8 409 | }, 410 | "maxwind": { 411 | "mph": 15, 412 | "kph": 24, 413 | "dir": "NNW", 414 | "degrees": 337 415 | }, 416 | "avewind": { 417 | "mph": 11, 418 | "kph": 18, 419 | "dir": "NNW", 420 | "degrees": 337 421 | }, 422 | "avehumidity": 88, 423 | "maxhumidity": 0, 424 | "minhumidity": 0 425 | }, { 426 | "date": { 427 | "epoch": "1483725600", 428 | "pretty": "7:00 PM CET on January 06, 2017", 429 | "day": 6, 430 | "month": 1, 431 | "year": 2017, 432 | "yday": 5, 433 | "hour": 19, 434 | "min": "00", 435 | "sec": 0, 436 | "isdst": "0", 437 | "monthname": "January", 438 | "monthname_short": "Jan", 439 | "weekday_short": "Fri", 440 | "weekday": "Friday", 441 | "ampm": "PM", 442 | "tz_short": "CET", 443 | "tz_long": "Europe/Berlin" 444 | }, 445 | "period": 4, 446 | "high": { 447 | "fahrenheit": "17", 448 | "celsius": "-8" 449 | }, 450 | "low": { 451 | "fahrenheit": "-1", 452 | "celsius": "-18" 453 | }, 454 | "conditions": "Partly Cloudy", 455 | "icon": "partlycloudy", 456 | "icon_url": "http://icons.wxug.com/i/c/k/partlycloudy.gif", 457 | "skyicon": "", 458 | "pop": 10, 459 | "qpf_allday": { 460 | "in": 0, 461 | "mm": 0 462 | }, 463 | "qpf_day": { 464 | "in": 0, 465 | "mm": 0 466 | }, 467 | "qpf_night": { 468 | "in": 0, 469 | "mm": 0 470 | }, 471 | "snow_allday": { 472 | "in": 0, 473 | "cm": 0 474 | }, 475 | "snow_day": { 476 | "in": 0, 477 | "cm": 0 478 | }, 479 | "snow_night": { 480 | "in": 0, 481 | "cm": 0 482 | }, 483 | "maxwind": { 484 | "mph": 5, 485 | "kph": 8, 486 | "dir": "NNW", 487 | "degrees": 334 488 | }, 489 | "avewind": { 490 | "mph": 3, 491 | "kph": 5, 492 | "dir": "NNW", 493 | "degrees": 334 494 | }, 495 | "avehumidity": 93, 496 | "maxhumidity": 0, 497 | "minhumidity": 0 498 | }] 499 | }, 500 | "permanently_hidden": false, 501 | "probeType": "forecast_range", 502 | "tags": [], 503 | "visibility": true, 504 | "updateTime": 1483468112 505 | }, { 506 | "creationTime": 1478079526, 507 | "creatorId": "humidity_37", 508 | "deviceType": "sensorMultilevel", 509 | "h": 1804069148, 510 | "hasHistory": true, 511 | "id": "WeatherUnderground_humidity_37", 512 | "location": 0, 513 | "metrics": { 514 | "probeTitle": "", 515 | "scaleTitle": "%", 516 | "icon": "/ZAutomation/api/v1/load/modulemedia/WeatherUnderground/humidity.png", 517 | "title": "Humidity", 518 | "level": 83, 519 | "modificationTime": 1483468112, 520 | "lastLevel": 83 521 | }, 522 | "permanently_hidden": false, 523 | "probeType": "humidity", 524 | "tags": [], 525 | "visibility": true, 526 | "updateTime": 1483468112 527 | }, { 528 | "creationTime": 1478079526, 529 | "creatorId": "wind_37", 530 | "deviceType": "sensorMultilevel", 531 | "h": 620818343, 532 | "hasHistory": true, 533 | "id": "WeatherUnderground_wind_37", 534 | "location": 0, 535 | "metrics": { 536 | "probeTitle": "", 537 | "scaleTitle": "km/h", 538 | "title": "Wind", 539 | "icon": "/ZAutomation/api/v1/load/modulemedia/WeatherUnderground/wind1.png", 540 | "dir": "SW", 541 | "wind": 3.1, 542 | "windgust": 5, 543 | "winddregrees": 216, 544 | "beaufort": 1, 545 | "list": [3, 2, 3], 546 | "current": 2.6666666666666665, 547 | "level": 2.6666666666666665, 548 | "modificationTime": 1483464512, 549 | "lastLevel": 2.6666666666666665 550 | }, 551 | "permanently_hidden": false, 552 | "probeType": "wind", 553 | "tags": [], 554 | "visibility": true, 555 | "updateTime": 1483468112 556 | }, { 557 | "creationTime": 1478079526, 558 | "creatorId": "uv_37", 559 | "deviceType": "sensorMultilevel", 560 | "h": -1953900178, 561 | "hasHistory": true, 562 | "id": "WeatherUnderground_uv_37", 563 | "location": 0, 564 | "metrics": { 565 | "probeTitle": "", 566 | "scaleTitle": "", 567 | "icon": "ultraviolet", 568 | "title": "UV Index", 569 | "list": [-1, -1, -1], 570 | "current": -1, 571 | "level": -1, 572 | "modificationTime": 1483468112, 573 | "lastLevel": -1 574 | }, 575 | "permanently_hidden": false, 576 | "probeType": "ultraviolet", 577 | "tags": [], 578 | "visibility": true, 579 | "updateTime": 1483468112 580 | }, { 581 | "creationTime": 1478079526, 582 | "creatorId": "solar_37", 583 | "deviceType": "sensorMultilevel", 584 | "h": 359859574, 585 | "hasHistory": true, 586 | "id": "WeatherUnderground_solar_37", 587 | "location": 0, 588 | "metrics": { 589 | "probeTitle": "", 590 | "scaleTitle": "Watt/m²", 591 | "icon": "ultraviolet", 592 | "title": "Solar intensity", 593 | "list": [null, null, null], 594 | "current": null, 595 | "level": null, 596 | "modificationTime": 1483468112, 597 | "lastLevel": null 598 | }, 599 | "permanently_hidden": false, 600 | "probeType": "solar", 601 | "tags": [], 602 | "visibility": true, 603 | "updateTime": 1483468112 604 | }, { 605 | "creationTime": 1478079526, 606 | "creatorId": "barometer_37", 607 | "deviceType": "sensorMultilevel", 608 | "h": 1186483050, 609 | "hasHistory": true, 610 | "id": "WeatherUnderground_barometer_37", 611 | "location": 0, 612 | "metrics": { 613 | "probeTitle": "", 614 | "scaleTitle": "hPa", 615 | "icon": "/ZAutomation/api/v1/load/modulemedia/WeatherUnderground/barometer0.png", 616 | "title": "Barometer", 617 | "level": 1023, 618 | "modificationTime": 1483464512, 619 | "lastLevel": 1023, 620 | "trend": "0" 621 | }, 622 | "permanently_hidden": false, 623 | "probeType": "barometer", 624 | "tags": [], 625 | "visibility": true, 626 | "updateTime": 1483468112 627 | }, { 628 | "creationTime": 1474362461, 629 | "creatorId": 8, 630 | "deviceType": "battery", 631 | "h": -592588977, 632 | "hasHistory": false, 633 | "id": "BatteryPolling_8", 634 | "location": 0, 635 | "metrics": { 636 | "probeTitle": "Battery", 637 | "scaleTitle": "%", 638 | "title": "Battery digest 8", 639 | "level": 68, 640 | "modificationTime": 1482859850, 641 | "lastLevel": 68 642 | }, 643 | "permanently_hidden": false, 644 | "probeType": "", 645 | "tags": [], 646 | "visibility": true, 647 | "updateTime": 1483469640 648 | }, { 649 | "creationTime": 1482018308, 650 | "creatorId": 48, 651 | "deviceType": "switchBinary", 652 | "h": 958164197, 653 | "hasHistory": true, 654 | "id": "GroupDevices_48", 655 | "location": 1, 656 | "metrics": { 657 | "level": "on", 658 | "icon": "", 659 | "title": "Sideboard LED", 660 | "modificationTime": 1483458468, 661 | "lastLevel": "on" 662 | }, 663 | "permanently_hidden": false, 664 | "probeType": "", 665 | "tags": [], 666 | "visibility": true, 667 | "updateTime": 1483458468 668 | }, { 669 | "creationTime": 1482439055, 670 | "creatorId": 49, 671 | "deviceType": "switchBinary", 672 | "h": 958164198, 673 | "hasHistory": true, 674 | "id": "GroupDevices_49", 675 | "location": 1, 676 | "metrics": { 677 | "level": "on", 678 | "icon": "", 679 | "title": "Schranklichter unten", 680 | "modificationTime": 1483458468, 681 | "lastLevel": "on" 682 | }, 683 | "permanently_hidden": false, 684 | "probeType": "", 685 | "tags": [], 686 | "visibility": true, 687 | "updateTime": 1483458468 688 | }, { 689 | "creationTime": 1482441436, 690 | "creatorId": 50, 691 | "deviceType": "switchBinary", 692 | "h": 958164220, 693 | "hasHistory": true, 694 | "id": "GroupDevices_50", 695 | "location": 1, 696 | "metrics": { 697 | "level": "on", 698 | "icon": "", 699 | "title": "Schranklichter oben", 700 | "modificationTime": 1483458468, 701 | "lastLevel": "on" 702 | }, 703 | "permanently_hidden": false, 704 | "probeType": "", 705 | "tags": [], 706 | "visibility": true, 707 | "updateTime": 1483458468 708 | }, { 709 | "creationTime": 1478079528, 710 | "creatorId": 46, 711 | "deviceType": "switchBinary", 712 | "h": 612354165, 713 | "hasHistory": true, 714 | "id": "SecurityZone_46", 715 | "location": 0, 716 | "metrics": { 717 | "securityType": "intrusion", 718 | "triggeredDevices": [], 719 | "level": "off", 720 | "state": "off", 721 | "title": "Security Zones Intrusion", 722 | "icon": "/ZAutomation/api/v1/load/modulemedia/SecurityZone/icon.png" 723 | }, 724 | "permanently_hidden": false, 725 | "probeType": "controller_security", 726 | "tags": [], 727 | "visibility": true, 728 | "updateTime": 1482859711 729 | }, { 730 | "creationTime": 1478079528, 731 | "creatorId": 44, 732 | "deviceType": "sensorBinary", 733 | "h": -1656742165, 734 | "hasHistory": true, 735 | "id": "Rain_44", 736 | "location": 0, 737 | "metrics": { 738 | "title": "Virtual Rain Sensor", 739 | "level": "on", 740 | "rain": "on", 741 | "sources": ["WeatherUnderground_current_37/metrics:percipintensity"], 742 | "lastRain": 1483469323, 743 | "icon": "/ZAutomation/api/v1/load/modulemedia/Rain/icon.png", 744 | "modificationTime": 1483453722, 745 | "lastLevel": "on", 746 | "change": 1483453722, 747 | "pop": 70 748 | }, 749 | "permanently_hidden": false, 750 | "probeType": "rain", 751 | "tags": [], 752 | "visibility": true, 753 | "updateTime": 1483469323 754 | }, { 755 | "creationTime": 1478079527, 756 | "creatorId": 39, 757 | "deviceType": "sensorMultilevel", 758 | "h": 640146536, 759 | "hasHistory": true, 760 | "id": "Astronomy_39_altitude", 761 | "location": 0, 762 | "metrics": { 763 | "scaleTitle": "°", 764 | "icon": "/ZAutomation/api/v1/load/modulemedia/Astronomy/altitude_night.png", 765 | "title": "Solar altitude", 766 | "level": -32.74662994281291, 767 | "modificationTime": 1483469793, 768 | "lastLevel": -32.74662994281291, 769 | "azimuth": 271.6955027580881, 770 | "altitude": -32.74662994281291, 771 | "sunrise": "2017-01-03T07:06:18.652Z", 772 | "sunriseEnd": "2017-01-03T07:10:12.777Z", 773 | "goldenHourEnd": "2017-01-03T07:59:36.609Z", 774 | "solarNoon": "2017-01-03T11:19:55.893Z", 775 | "goldenHour": "2017-01-03T14:40:15.177Z", 776 | "sunsetStart": "2017-01-03T15:29:39.009Z", 777 | "sunset": "2017-01-03T15:33:33.134Z", 778 | "dusk": "2017-01-03T16:09:51.255Z", 779 | "nauticalDusk": "2017-01-03T16:49:27.804Z", 780 | "night": "2017-01-03T17:27:18.930Z", 781 | "nadir": "2017-01-02T23:19:55.893Z", 782 | "nightEnd": "2017-01-03T05:12:32.856Z", 783 | "nauticalDawn": "2017-01-03T05:50:23.982Z", 784 | "dawn": "2017-01-03T06:30:00.531Z" 785 | }, 786 | "permanently_hidden": false, 787 | "probeType": "astronomy_sun_altitude", 788 | "tags": [], 789 | "visibility": true, 790 | "updateTime": 1483469793 791 | }, { 792 | "creationTime": 1478079527, 793 | "creatorId": 39, 794 | "deviceType": "sensorMultilevel", 795 | "h": 411419366, 796 | "hasHistory": true, 797 | "id": "Astronomy_39_azimuth", 798 | "location": 0, 799 | "metrics": { 800 | "scaleTitle": "°", 801 | "icon": "/ZAutomation/api/v1/load/modulemedia/Astronomy/azimuth_night.png", 802 | "title": "Solar azimuth", 803 | "level": 271.6955027580881, 804 | "modificationTime": 1483469793, 805 | "lastLevel": 271.6955027580881, 806 | "azimuth": 271.6955027580881, 807 | "altitude": -32.74662994281291 808 | }, 809 | "permanently_hidden": false, 810 | "probeType": "astronomy_sun_azimuth", 811 | "tags": [], 812 | "visibility": true, 813 | "updateTime": 1483469793 814 | }, { 815 | "creationTime": 1476422183, 816 | "creatorId": 1, 817 | "deviceType": "sensorMultilevel", 818 | "h": 221426609, 819 | "hasHistory": true, 820 | "id": "ZWayVDev_zway_25-0-49-1", 821 | "location": 6, 822 | "metrics": { 823 | "probeTitle": "Temperature", 824 | "scaleTitle": "°C", 825 | "level": 21.3, 826 | "icon": "temperature", 827 | "title": "Temperatur Arbeitszimmer", 828 | "modificationTime": 1483469106, 829 | "lastLevel": 21.3 830 | }, 831 | "permanently_hidden": false, 832 | "probeType": "temperature", 833 | "tags": ["influx"], 834 | "visibility": true, 835 | "updateTime": 1483469106 836 | }, { 837 | "creationTime": 1476422183, 838 | "creatorId": 1, 839 | "deviceType": "thermostat", 840 | "h": 221484269, 841 | "hasHistory": true, 842 | "id": "ZWayVDev_zway_25-0-67-1", 843 | "location": 6, 844 | "metrics": { 845 | "scaleTitle": "°C", 846 | "level": 22, 847 | "min": 5, 848 | "max": 40, 849 | "icon": "thermostat", 850 | "title": "Heizung Arbeitszimmer", 851 | "modificationTime": 1483452999, 852 | "lastLevel": 22 853 | }, 854 | "permanently_hidden": false, 855 | "probeType": "thermostat_set_point", 856 | "tags": [], 857 | "visibility": true, 858 | "updateTime": 1483469105 859 | }, { 860 | "creationTime": 1476422182, 861 | "creatorId": 1, 862 | "deviceType": "battery", 863 | "h": -269954961, 864 | "hasHistory": false, 865 | "id": "ZWayVDev_zway_25-0-128", 866 | "location": 0, 867 | "metrics": { 868 | "probeTitle": "Battery", 869 | "scaleTitle": "%", 870 | "level": 80, 871 | "icon": "battery", 872 | "title": "Danfoss Battery (25.0)", 873 | "modificationTime": 1483452999, 874 | "lastLevel": 80 875 | }, 876 | "permanently_hidden": false, 877 | "probeType": "", 878 | "tags": [], 879 | "visibility": true, 880 | "updateTime": 1483469105 881 | }, { 882 | "creationTime": 1476422322, 883 | "creatorId": 1, 884 | "deviceType": "thermostat", 885 | "h": 1964294604, 886 | "hasHistory": true, 887 | "id": "ZWayVDev_zway_26-0-67-1", 888 | "location": 7, 889 | "metrics": { 890 | "scaleTitle": "°C", 891 | "level": 20, 892 | "min": 5, 893 | "max": 40, 894 | "icon": "thermostat", 895 | "title": "Heizung WC", 896 | "modificationTime": 1483035948, 897 | "lastLevel": 20 898 | }, 899 | "permanently_hidden": false, 900 | "probeType": "thermostat_set_point", 901 | "tags": [], 902 | "visibility": true, 903 | "updateTime": 1483469634 904 | }, { 905 | "creationTime": 1476422320, 906 | "creatorId": 1, 907 | "deviceType": "battery", 908 | "h": 617548720, 909 | "hasHistory": false, 910 | "id": "ZWayVDev_zway_26-0-128", 911 | "location": 0, 912 | "metrics": { 913 | "probeTitle": "Battery", 914 | "scaleTitle": "%", 915 | "level": 81, 916 | "icon": "battery", 917 | "title": "Danfoss Battery (26.0)", 918 | "modificationTime": 1483468747, 919 | "lastLevel": 81 920 | }, 921 | "permanently_hidden": false, 922 | "probeType": "", 923 | "tags": [], 924 | "visibility": true, 925 | "updateTime": 1483469634 926 | }, { 927 | "creationTime": 1479889820, 928 | "creatorId": 1, 929 | "deviceType": "switchBinary", 930 | "h": 42913106, 931 | "hasHistory": true, 932 | "id": "ZWayVDev_zway_30-0-37", 933 | "location": 2, 934 | "metrics": { 935 | "icon": "switch", 936 | "title": "Boiler Schalter", 937 | "level": "on", 938 | "modificationTime": 1483462282, 939 | "lastLevel": "on" 940 | }, 941 | "permanently_hidden": false, 942 | "probeType": "", 943 | "tags": [], 944 | "visibility": true, 945 | "updateTime": 1483462282 946 | }, { 947 | "creationTime": 1479889820, 948 | "creatorId": 1, 949 | "deviceType": "sensorMultilevel", 950 | "h": -1710144934, 951 | "hasHistory": true, 952 | "id": "ZWayVDev_zway_30-0-49-4", 953 | "location": 2, 954 | "metrics": { 955 | "probeTitle": "Power", 956 | "scaleTitle": "W", 957 | "level": 0.3, 958 | "icon": "energy", 959 | "title": "Boiler Strom", 960 | "modificationTime": 1483469731, 961 | "lastLevel": 0.3 962 | }, 963 | "permanently_hidden": false, 964 | "probeType": "energy", 965 | "tags": [], 966 | "visibility": true, 967 | "updateTime": 1483469731 968 | }, { 969 | "creationTime": 1479889821, 970 | "creatorId": 1, 971 | "deviceType": "sensorMultilevel", 972 | "h": -1710123796, 973 | "hasHistory": true, 974 | "id": "ZWayVDev_zway_30-0-50-0", 975 | "location": 2, 976 | "metrics": { 977 | "probeTitle": "Electric", 978 | "scaleTitle": "kWh", 979 | "level": 10.03, 980 | "icon": "meter", 981 | "title": "Boiler Meter 1", 982 | "modificationTime": 1483467775, 983 | "lastLevel": 10.03 984 | }, 985 | "permanently_hidden": false, 986 | "probeType": "meterElectric_kilowatt_hour", 987 | "tags": ["influx"], 988 | "visibility": true, 989 | "updateTime": 1483467775 990 | }, { 991 | "creationTime": 1479889821, 992 | "creatorId": 1, 993 | "deviceType": "sensorMultilevel", 994 | "h": -1710123794, 995 | "hasHistory": true, 996 | "id": "ZWayVDev_zway_30-0-50-2", 997 | "location": 2, 998 | "metrics": { 999 | "probeTitle": "Electric", 1000 | "scaleTitle": "W", 1001 | "level": 0.3, 1002 | "icon": "meter", 1003 | "title": "Boiler Metet 2", 1004 | "modificationTime": 1483392334, 1005 | "lastLevel": 0.3 1006 | }, 1007 | "permanently_hidden": false, 1008 | "probeType": "meterElectric_watt", 1009 | "tags": ["influx"], 1010 | "visibility": true, 1011 | "updateTime": 1483392334 1012 | }, { 1013 | "creationTime": 1479892030, 1014 | "creatorId": 1, 1015 | "deviceType": "sensorMultilevel", 1016 | "h": 32665398, 1017 | "hasHistory": true, 1018 | "id": "ZWayVDev_zway_31-0-49-1", 1019 | "location": 4, 1020 | "metrics": { 1021 | "probeTitle": "Temperature", 1022 | "scaleTitle": "°C", 1023 | "level": 19.78, 1024 | "icon": "temperature", 1025 | "title": "Temperatur Bad", 1026 | "modificationTime": 1483468711, 1027 | "lastLevel": 19.78 1028 | }, 1029 | "permanently_hidden": false, 1030 | "probeType": "temperature", 1031 | "tags": ["influx"], 1032 | "visibility": true, 1033 | "updateTime": 1483468711 1034 | }, { 1035 | "creationTime": 1479892016, 1036 | "creatorId": 1, 1037 | "deviceType": "thermostat", 1038 | "h": 32723058, 1039 | "hasHistory": true, 1040 | "id": "ZWayVDev_zway_31-0-67-1", 1041 | "location": 4, 1042 | "metrics": { 1043 | "scaleTitle": "°C", 1044 | "level": 20, 1045 | "min": 5, 1046 | "max": 40, 1047 | "icon": "thermostat", 1048 | "title": "Heizung Bad", 1049 | "modificationTime": 1483033998, 1050 | "lastLevel": 20 1051 | }, 1052 | "permanently_hidden": false, 1053 | "probeType": "thermostat_set_point", 1054 | "tags": [], 1055 | "visibility": true, 1056 | "updateTime": 1483468711 1057 | }, { 1058 | "creationTime": 1479892016, 1059 | "creatorId": 1, 1060 | "deviceType": "battery", 1061 | "h": -2077159350, 1062 | "hasHistory": false, 1063 | "id": "ZWayVDev_zway_31-0-128", 1064 | "location": 0, 1065 | "metrics": { 1066 | "probeTitle": "Battery", 1067 | "scaleTitle": "%", 1068 | "level": 69, 1069 | "icon": "battery", 1070 | "title": "Danfoss Battery (31.0)", 1071 | "modificationTime": 1483367050, 1072 | "lastLevel": 69 1073 | }, 1074 | "permanently_hidden": false, 1075 | "probeType": "", 1076 | "tags": [], 1077 | "visibility": true, 1078 | "updateTime": 1483468711 1079 | }, { 1080 | "creationTime": 1479893618, 1081 | "creatorId": 1, 1082 | "deviceType": "sensorMultilevel", 1083 | "h": 1775475733, 1084 | "hasHistory": true, 1085 | "id": "ZWayVDev_zway_32-0-49-1", 1086 | "location": 3, 1087 | "metrics": { 1088 | "probeTitle": "Temperature", 1089 | "scaleTitle": "°C", 1090 | "level": 18.39, 1091 | "icon": "temperature", 1092 | "title": "Temperatur Schlazimmer", 1093 | "modificationTime": 1483469112, 1094 | "lastLevel": 18.39 1095 | }, 1096 | "permanently_hidden": false, 1097 | "probeType": "temperature", 1098 | "tags": ["influx"], 1099 | "visibility": true, 1100 | "updateTime": 1483469112 1101 | }, { 1102 | "creationTime": 1479893618, 1103 | "creatorId": 1, 1104 | "deviceType": "thermostat", 1105 | "h": 1775533393, 1106 | "hasHistory": true, 1107 | "id": "ZWayVDev_zway_32-0-67-1", 1108 | "location": 3, 1109 | "metrics": { 1110 | "scaleTitle": "°C", 1111 | "level": 6, 1112 | "min": 5, 1113 | "max": 40, 1114 | "icon": "thermostat", 1115 | "title": "Heizung Schlazimmer", 1116 | "modificationTime": 1483446392, 1117 | "lastLevel": 6 1118 | }, 1119 | "permanently_hidden": false, 1120 | "probeType": "thermostat_set_point", 1121 | "tags": [], 1122 | "visibility": true, 1123 | "updateTime": 1483469112 1124 | }, { 1125 | "creationTime": 1479893618, 1126 | "creatorId": 1, 1127 | "deviceType": "battery", 1128 | "h": -1189655669, 1129 | "hasHistory": false, 1130 | "id": "ZWayVDev_zway_32-0-128", 1131 | "location": 0, 1132 | "metrics": { 1133 | "probeTitle": "Battery", 1134 | "scaleTitle": "%", 1135 | "level": 68, 1136 | "icon": "battery", 1137 | "title": "Danfoss Battery (32.0)", 1138 | "modificationTime": 1482054430, 1139 | "lastLevel": 68 1140 | }, 1141 | "permanently_hidden": false, 1142 | "probeType": "", 1143 | "tags": [], 1144 | "visibility": true, 1145 | "updateTime": 1483469112 1146 | }, { 1147 | "creationTime": 1479894104, 1148 | "creatorId": 1, 1149 | "deviceType": "switchMultilevel", 1150 | "h": 157429711, 1151 | "hasHistory": true, 1152 | "id": "ZWayVDev_zway_34-0-38", 1153 | "location": 1, 1154 | "metrics": { 1155 | "icon": "multilevel", 1156 | "title": "Fibaro Dimmer (34.0)", 1157 | "level": 0, 1158 | "modificationTime": 1482691176, 1159 | "lastLevel": 0 1160 | }, 1161 | "permanently_hidden": false, 1162 | "probeType": "multilevel", 1163 | "tags": [], 1164 | "visibility": true, 1165 | "updateTime": 1483397524 1166 | }, { 1167 | "creationTime": 1479894078, 1168 | "creatorId": 1, 1169 | "deviceType": "sensorMultilevel", 1170 | "h": 966129110, 1171 | "hasHistory": true, 1172 | "id": "ZWayVDev_zway_34-0-49-4", 1173 | "location": 1, 1174 | "metrics": { 1175 | "probeTitle": "Power", 1176 | "scaleTitle": "W", 1177 | "level": 0, 1178 | "icon": "energy", 1179 | "title": "Fibaro Power (34.0.49.4)", 1180 | "modificationTime": 1482523201, 1181 | "lastLevel": 0 1182 | }, 1183 | "permanently_hidden": false, 1184 | "probeType": "energy", 1185 | "tags": [], 1186 | "visibility": true, 1187 | "updateTime": 1483392331 1188 | }, { 1189 | "creationTime": 1479894084, 1190 | "creatorId": 1, 1191 | "deviceType": "sensorMultilevel", 1192 | "h": 966150248, 1193 | "hasHistory": true, 1194 | "id": "ZWayVDev_zway_34-0-50-0", 1195 | "location": 1, 1196 | "metrics": { 1197 | "probeTitle": "Electric", 1198 | "scaleTitle": "kWh", 1199 | "level": 0.29, 1200 | "icon": "meter", 1201 | "title": "Fibaro Electric Meter (34.0.50.0)", 1202 | "modificationTime": 1482523202, 1203 | "lastLevel": 0.29 1204 | }, 1205 | "permanently_hidden": false, 1206 | "probeType": "meterElectric_kilowatt_hour", 1207 | "tags": [], 1208 | "visibility": true, 1209 | "updateTime": 1483392335 1210 | }, { 1211 | "creationTime": 1479894084, 1212 | "creatorId": 1, 1213 | "deviceType": "sensorMultilevel", 1214 | "h": 966150250, 1215 | "hasHistory": true, 1216 | "id": "ZWayVDev_zway_34-0-50-2", 1217 | "location": 1, 1218 | "metrics": { 1219 | "probeTitle": "Electric", 1220 | "scaleTitle": "W", 1221 | "level": 0, 1222 | "icon": "meter", 1223 | "title": "Fibaro Electric Meter (34.0.50.2)", 1224 | "modificationTime": 1482447623, 1225 | "lastLevel": 0 1226 | }, 1227 | "permanently_hidden": false, 1228 | "probeType": "meterElectric_watt", 1229 | "tags": [], 1230 | "visibility": true, 1231 | "updateTime": 1483392335 1232 | }, { 1233 | "creationTime": 1479894085, 1234 | "creatorId": 1, 1235 | "deviceType": "sensorBinary", 1236 | "h": 882311017, 1237 | "hasHistory": true, 1238 | "id": "ZWayVDev_zway_34-0-113-4-2-A", 1239 | "location": 1, 1240 | "metrics": { 1241 | "icon": "alarm", 1242 | "level": "off", 1243 | "title": "Fibaro Heat Alarm (34.0.113.4.2)" 1244 | }, 1245 | "permanently_hidden": false, 1246 | "probeType": "alarm_heat", 1247 | "tags": [], 1248 | "visibility": true, 1249 | "updateTime": 1482859713 1250 | }, { 1251 | "creationTime": 1479894085, 1252 | "creatorId": 1, 1253 | "deviceType": "sensorBinary", 1254 | "h": 886007023, 1255 | "hasHistory": true, 1256 | "id": "ZWayVDev_zway_34-0-113-8-4-A", 1257 | "location": 1, 1258 | "metrics": { 1259 | "icon": "alarm", 1260 | "level": "off", 1261 | "title": "Fibaro Power Management Alarm (34.0.113.8.4)" 1262 | }, 1263 | "permanently_hidden": false, 1264 | "probeType": "alarm_power", 1265 | "tags": [], 1266 | "visibility": true, 1267 | "updateTime": 1482859713 1268 | }, { 1269 | "creationTime": 1479894085, 1270 | "creatorId": 1, 1271 | "deviceType": "sensorBinary", 1272 | "h": 886007984, 1273 | "hasHistory": true, 1274 | "id": "ZWayVDev_zway_34-0-113-8-5-A", 1275 | "location": 1, 1276 | "metrics": { 1277 | "icon": "alarm", 1278 | "level": "off", 1279 | "title": "Fibaro Power Management Alarm (34.0.113.8.5)" 1280 | }, 1281 | "permanently_hidden": false, 1282 | "probeType": "alarm_power", 1283 | "tags": [], 1284 | "visibility": true, 1285 | "updateTime": 1482859713 1286 | }, { 1287 | "creationTime": 1479894085, 1288 | "creatorId": 1, 1289 | "deviceType": "sensorBinary", 1290 | "h": 886008945, 1291 | "hasHistory": true, 1292 | "id": "ZWayVDev_zway_34-0-113-8-6-A", 1293 | "location": 1, 1294 | "metrics": { 1295 | "icon": "alarm", 1296 | "level": "off", 1297 | "title": "Fibaro Power Management Alarm (34.0.113.8.6)" 1298 | }, 1299 | "permanently_hidden": false, 1300 | "probeType": "alarm_power", 1301 | "tags": [], 1302 | "visibility": true, 1303 | "updateTime": 1482859713 1304 | }, { 1305 | "creationTime": 1479894085, 1306 | "creatorId": 1, 1307 | "deviceType": "sensorBinary", 1308 | "h": 886010867, 1309 | "hasHistory": true, 1310 | "id": "ZWayVDev_zway_34-0-113-8-8-A", 1311 | "location": 1, 1312 | "metrics": { 1313 | "icon": "alarm", 1314 | "level": "off", 1315 | "title": "Fibaro Power Management Alarm (34.0.113.8.8)" 1316 | }, 1317 | "permanently_hidden": false, 1318 | "probeType": "alarm_power", 1319 | "tags": [], 1320 | "visibility": true, 1321 | "updateTime": 1482859713 1322 | }, { 1323 | "creationTime": 1479894085, 1324 | "creatorId": 1, 1325 | "deviceType": "sensorBinary", 1326 | "h": 886011828, 1327 | "hasHistory": true, 1328 | "id": "ZWayVDev_zway_34-0-113-8-9-A", 1329 | "location": 1, 1330 | "metrics": { 1331 | "icon": "alarm", 1332 | "level": "off", 1333 | "title": "Fibaro Power Management Alarm (34.0.113.8.9)" 1334 | }, 1335 | "permanently_hidden": false, 1336 | "probeType": "alarm_power", 1337 | "tags": [], 1338 | "visibility": true, 1339 | "updateTime": 1482859713 1340 | }, { 1341 | "creationTime": 1479894085, 1342 | "creatorId": 1, 1343 | "deviceType": "sensorBinary", 1344 | "h": 886927661, 1345 | "hasHistory": true, 1346 | "id": "ZWayVDev_zway_34-0-113-9-1-A", 1347 | "location": 1, 1348 | "metrics": { 1349 | "icon": "alarm", 1350 | "level": "off", 1351 | "title": "Fibaro System Alarm (34.0.113.9.1)" 1352 | }, 1353 | "permanently_hidden": false, 1354 | "probeType": "alarm_system", 1355 | "tags": [], 1356 | "visibility": true, 1357 | "updateTime": 1482859713 1358 | }, { 1359 | "creationTime": 1479894124, 1360 | "creatorId": 1, 1361 | "deviceType": "switchMultilevel", 1362 | "h": 157459502, 1363 | "hasHistory": true, 1364 | "id": "ZWayVDev_zway_34-1-38", 1365 | "location": 1, 1366 | "metrics": { 1367 | "icon": "multilevel", 1368 | "title": "Fibaro Dimmer (34.1)", 1369 | "level": 0, 1370 | "modificationTime": 1483395213, 1371 | "lastLevel": 0 1372 | }, 1373 | "permanently_hidden": false, 1374 | "probeType": "multilevel", 1375 | "tags": [], 1376 | "visibility": true, 1377 | "updateTime": 1483397524 1378 | }, { 1379 | "creationTime": 1479894254, 1380 | "creatorId": 1, 1381 | "deviceType": "sensorMultilevel", 1382 | "h": 994758261, 1383 | "hasHistory": true, 1384 | "id": "ZWayVDev_zway_34-1-49-4", 1385 | "location": 1, 1386 | "metrics": { 1387 | "probeTitle": "Power", 1388 | "scaleTitle": "W", 1389 | "level": 0, 1390 | "icon": "energy", 1391 | "title": "Fibaro Power (34.1.49.4)", 1392 | "modificationTime": 1483395220, 1393 | "lastLevel": 0 1394 | }, 1395 | "permanently_hidden": false, 1396 | "probeType": "energy", 1397 | "tags": [], 1398 | "visibility": true, 1399 | "updateTime": 1483467816 1400 | }, { 1401 | "creationTime": 1479894114, 1402 | "creatorId": 1, 1403 | "deviceType": "switchMultilevel", 1404 | "h": 157489293, 1405 | "hasHistory": true, 1406 | "id": "ZWayVDev_zway_34-2-38", 1407 | "location": 1, 1408 | "metrics": { 1409 | "icon": "multilevel", 1410 | "title": "Fibaro Dimmer (34.2)", 1411 | "level": 0, 1412 | "modificationTime": 1481992062, 1413 | "lastLevel": 0 1414 | }, 1415 | "permanently_hidden": false, 1416 | "probeType": "multilevel", 1417 | "tags": [], 1418 | "visibility": true, 1419 | "updateTime": 1482859713 1420 | }, { 1421 | "creationTime": 1479895465, 1422 | "creatorId": 1, 1423 | "deviceType": "switchMultilevel", 1424 | "h": 186058862, 1425 | "hasHistory": true, 1426 | "id": "ZWayVDev_zway_35-0-38", 1427 | "location": 1, 1428 | "metrics": { 1429 | "icon": "multilevel", 1430 | "title": "Fibaro Dimmer (35.0)", 1431 | "level": 99, 1432 | "modificationTime": 1483458472, 1433 | "lastLevel": 99 1434 | }, 1435 | "permanently_hidden": false, 1436 | "probeType": "multilevel", 1437 | "tags": ["Homebridge.Skip"], 1438 | "visibility": true, 1439 | "updateTime": 1483458474 1440 | }, { 1441 | "creationTime": 1479895465, 1442 | "creatorId": 1, 1443 | "deviceType": "sensorMultilevel", 1444 | "h": -1586027851, 1445 | "hasHistory": true, 1446 | "id": "ZWayVDev_zway_35-0-49-4", 1447 | "location": 1, 1448 | "metrics": { 1449 | "probeTitle": "Power", 1450 | "scaleTitle": "W", 1451 | "level": 1, 1452 | "icon": "energy", 1453 | "title": "Fibaro Power (35.0.49.4)", 1454 | "modificationTime": 1483458474, 1455 | "lastLevel": 1 1456 | }, 1457 | "permanently_hidden": false, 1458 | "probeType": "energy", 1459 | "tags": [], 1460 | "visibility": true, 1461 | "updateTime": 1483458474 1462 | }, { 1463 | "creationTime": 1479895466, 1464 | "creatorId": 1, 1465 | "deviceType": "sensorMultilevel", 1466 | "h": -1586006713, 1467 | "hasHistory": true, 1468 | "id": "ZWayVDev_zway_35-0-50-0", 1469 | "location": 1, 1470 | "metrics": { 1471 | "probeTitle": "Electric", 1472 | "scaleTitle": "kWh", 1473 | "level": "0.1", 1474 | "icon": "meter", 1475 | "title": "Fibaro Electric Meter (35.0.50.0)", 1476 | "modificationTime": 1483392927, 1477 | "lastLevel": "0.1" 1478 | }, 1479 | "permanently_hidden": false, 1480 | "probeType": "meterElectric_kilowatt_hour", 1481 | "tags": [], 1482 | "visibility": true, 1483 | "updateTime": 1483392927 1484 | }, { 1485 | "creationTime": 1479895466, 1486 | "creatorId": 1, 1487 | "deviceType": "sensorMultilevel", 1488 | "h": -1586006711, 1489 | "hasHistory": true, 1490 | "id": "ZWayVDev_zway_35-0-50-2", 1491 | "location": 1, 1492 | "metrics": { 1493 | "probeTitle": "Electric", 1494 | "scaleTitle": "W", 1495 | "level": 1, 1496 | "icon": "meter", 1497 | "title": "Fibaro Electric Meter (35.0.50.2)", 1498 | "modificationTime": 1483392334, 1499 | "lastLevel": 1 1500 | }, 1501 | "permanently_hidden": false, 1502 | "probeType": "meterElectric_watt", 1503 | "tags": [], 1504 | "visibility": true, 1505 | "updateTime": 1483392334 1506 | }, { 1507 | "creationTime": 1479895467, 1508 | "creatorId": 1, 1509 | "deviceType": "switchRGBW", 1510 | "h": 561929125, 1511 | "hasHistory": false, 1512 | "id": "ZWayVDev_zway_35-0-51-rgb", 1513 | "location": 1, 1514 | "metrics": { 1515 | "icon": "multilevel", 1516 | "title": "Fibaro Color (35.0.51)", 1517 | "color": { 1518 | "r": 0, 1519 | "g": 0, 1520 | "b": 0 1521 | }, 1522 | "level": "off", 1523 | "modificationTime": 1481992945, 1524 | "lastLevel": "off", 1525 | "rgbColors": "rgb(0,0,0)" 1526 | }, 1527 | "permanently_hidden": false, 1528 | "probeType": "switchColor_rgb", 1529 | "tags": ["Homebridge.Skip"], 1530 | "visibility": true, 1531 | "updateTime": 1482859713 1532 | }, { 1533 | "creationTime": 1479895467, 1534 | "creatorId": 1, 1535 | "deviceType": "switchMultilevel", 1536 | "h": -1586005752, 1537 | "hasHistory": true, 1538 | "id": "ZWayVDev_zway_35-0-51-0", 1539 | "location": 1, 1540 | "metrics": { 1541 | "icon": "multilevel", 1542 | "title": "Fibaro Soft White (35.0.51.0)", 1543 | "level": 0, 1544 | "modificationTime": 1479895467, 1545 | "lastLevel": 0 1546 | }, 1547 | "permanently_hidden": false, 1548 | "probeType": "switchColor_soft_white", 1549 | "tags": ["Homebridge.Skip"], 1550 | "visibility": true, 1551 | "updateTime": 1482859713 1552 | }, { 1553 | "creationTime": 1479895466, 1554 | "creatorId": 1, 1555 | "deviceType": "switchMultilevel", 1556 | "h": 186088653, 1557 | "hasHistory": true, 1558 | "id": "ZWayVDev_zway_35-1-38", 1559 | "location": 1, 1560 | "metrics": { 1561 | "icon": "multilevel", 1562 | "title": "Fibaro Dimmer (35.1)", 1563 | "level": 99, 1564 | "modificationTime": 1483458472, 1565 | "lastLevel": 99 1566 | }, 1567 | "permanently_hidden": false, 1568 | "probeType": "multilevel", 1569 | "tags": ["Homebridge.Skip"], 1570 | "visibility": true, 1571 | "updateTime": 1483458474 1572 | }, { 1573 | "creationTime": 1479895466, 1574 | "creatorId": 1, 1575 | "deviceType": "switchMultilevel", 1576 | "h": 186118444, 1577 | "hasHistory": true, 1578 | "id": "ZWayVDev_zway_35-2-38", 1579 | "location": 1, 1580 | "metrics": { 1581 | "icon": "multilevel", 1582 | "title": "LifeSideboard", 1583 | "level": 99, 1584 | "modificationTime": 1483458471, 1585 | "lastLevel": 99 1586 | }, 1587 | "permanently_hidden": false, 1588 | "probeType": "multilevel", 1589 | "tags": ["Homebridge.Skip"], 1590 | "visibility": true, 1591 | "updateTime": 1483458474 1592 | }, { 1593 | "creationTime": 1479895466, 1594 | "creatorId": 1, 1595 | "deviceType": "switchMultilevel", 1596 | "h": 186148235, 1597 | "hasHistory": true, 1598 | "id": "ZWayVDev_zway_35-3-38", 1599 | "location": 1, 1600 | "metrics": { 1601 | "icon": "multilevel", 1602 | "title": "Sideboard LED Links", 1603 | "level": 99, 1604 | "modificationTime": 1483458471, 1605 | "lastLevel": 99 1606 | }, 1607 | "permanently_hidden": false, 1608 | "probeType": "multilevel", 1609 | "tags": ["Homebridge.Skip"], 1610 | "visibility": true, 1611 | "updateTime": 1483458471 1612 | }, { 1613 | "creationTime": 1479895466, 1614 | "creatorId": 1, 1615 | "deviceType": "switchMultilevel", 1616 | "h": 186178026, 1617 | "hasHistory": true, 1618 | "id": "ZWayVDev_zway_35-4-38", 1619 | "location": 1, 1620 | "metrics": { 1621 | "icon": "multilevel", 1622 | "title": "Fibaro Dimmer (35.4)", 1623 | "level": 0, 1624 | "modificationTime": 1481992979, 1625 | "lastLevel": 0 1626 | }, 1627 | "permanently_hidden": false, 1628 | "probeType": "multilevel", 1629 | "tags": ["Homebridge.Skip"], 1630 | "visibility": true, 1631 | "updateTime": 1482859714 1632 | }, { 1633 | "creationTime": 1479895466, 1634 | "creatorId": 1, 1635 | "deviceType": "switchMultilevel", 1636 | "h": 186207817, 1637 | "hasHistory": true, 1638 | "id": "ZWayVDev_zway_35-5-38", 1639 | "location": 1, 1640 | "metrics": { 1641 | "icon": "multilevel", 1642 | "title": "Fibaro Dimmer (35.5)", 1643 | "level": 0, 1644 | "modificationTime": 1481992979, 1645 | "lastLevel": 0 1646 | }, 1647 | "permanently_hidden": false, 1648 | "probeType": "multilevel", 1649 | "tags": ["Homebridge.Skip"], 1650 | "visibility": true, 1651 | "updateTime": 1482859714 1652 | }, { 1653 | "creationTime": 1481104053, 1654 | "creatorId": 1, 1655 | "deviceType": "sensorBinary", 1656 | "h": 1090245229, 1657 | "hasHistory": true, 1658 | "id": "ZWayVDev_zway_39-0-48-1", 1659 | "location": 1, 1660 | "metrics": { 1661 | "probeTitle": "General purpose", 1662 | "scaleTitle": "", 1663 | "icon": "motion", 1664 | "level": "off", 1665 | "title": "Bewegung Wohnzimmer Tür", 1666 | "modificationTime": 1481742503, 1667 | "lastLevel": "off" 1668 | }, 1669 | "permanently_hidden": false, 1670 | "probeType": "general_purpose", 1671 | "tags": [], 1672 | "visibility": true, 1673 | "updateTime": 1483395720 1674 | }, { 1675 | "creationTime": 1481104063, 1676 | "creatorId": 1, 1677 | "deviceType": "sensorBinary", 1678 | "h": 1238364216, 1679 | "hasHistory": true, 1680 | "id": "ZWayVDev_zway_39-0-113-6-Door-A", 1681 | "location": 1, 1682 | "metrics": { 1683 | "icon": "door", 1684 | "level": "off", 1685 | "title": "Tür Wohnzimmer", 1686 | "modificationTime": 1483438951, 1687 | "lastLevel": "off" 1688 | }, 1689 | "permanently_hidden": false, 1690 | "probeType": "alarm_door", 1691 | "tags": ["influx"], 1692 | "visibility": true, 1693 | "updateTime": 1483438951 1694 | }, { 1695 | "creationTime": 1481104063, 1696 | "creatorId": 1, 1697 | "deviceType": "sensorBinary", 1698 | "h": -581932494, 1699 | "hasHistory": true, 1700 | "id": "ZWayVDev_zway_39-0-113-7-3-A", 1701 | "location": 1, 1702 | "metrics": { 1703 | "icon": "smoke", 1704 | "level": "off", 1705 | "title": "Fibaro Burglar Alarm (39.0.113.7.3)" 1706 | }, 1707 | "permanently_hidden": false, 1708 | "probeType": "alarm_burglar", 1709 | "tags": [], 1710 | "visibility": true, 1711 | "updateTime": 1482859714 1712 | }, { 1713 | "creationTime": 1481104052, 1714 | "creatorId": 1, 1715 | "deviceType": "battery", 1716 | "h": 727902802, 1717 | "hasHistory": false, 1718 | "id": "ZWayVDev_zway_39-0-128", 1719 | "location": 0, 1720 | "metrics": { 1721 | "probeTitle": "Battery", 1722 | "scaleTitle": "%", 1723 | "level": 100, 1724 | "icon": "battery", 1725 | "title": "Fibaro Battery (39.0)", 1726 | "modificationTime": 1481104052, 1727 | "lastLevel": 100 1728 | }, 1729 | "permanently_hidden": false, 1730 | "probeType": "", 1731 | "tags": [], 1732 | "visibility": true, 1733 | "updateTime": 1482859715 1734 | }, { 1735 | "creationTime": 1481104069, 1736 | "creatorId": 1, 1737 | "deviceType": "sensorBinary", 1738 | "h": -1787233596, 1739 | "hasHistory": true, 1740 | "id": "ZWayVDev_zway_39-0-156-0-A", 1741 | "location": 1, 1742 | "metrics": { 1743 | "icon": "alarm", 1744 | "level": "on", 1745 | "title": "Fibaro General Purpose alarm Alarm (39.0.156.0)", 1746 | "modificationTime": 1481104069, 1747 | "lastLevel": "on" 1748 | }, 1749 | "permanently_hidden": false, 1750 | "probeType": "alarmSensor_general_purpose", 1751 | "tags": [], 1752 | "visibility": true, 1753 | "updateTime": 1483395720 1754 | }, { 1755 | "creationTime": 1481104430, 1756 | "creatorId": 1, 1757 | "deviceType": "sensorBinary", 1758 | "h": 777366935, 1759 | "hasHistory": false, 1760 | "id": "ZWayVDev_zway_40-0-48-1", 1761 | "location": 1, 1762 | "metrics": { 1763 | "probeTitle": "General purpose", 1764 | "scaleTitle": "", 1765 | "icon": "motion", 1766 | "level": "on", 1767 | "title": "Tamper Eye", 1768 | "modificationTime": 1481299566, 1769 | "lastLevel": "on" 1770 | }, 1771 | "permanently_hidden": true, 1772 | "probeType": "general_purpose", 1773 | "tags": [], 1774 | "visibility": true, 1775 | "updateTime": 1483392330 1776 | }, { 1777 | "creationTime": 1481104432, 1778 | "creatorId": 1, 1779 | "deviceType": "sensorMultilevel", 1780 | "h": 777367896, 1781 | "hasHistory": true, 1782 | "id": "ZWayVDev_zway_40-0-49-1", 1783 | "location": 1, 1784 | "metrics": { 1785 | "probeTitle": "Temperature", 1786 | "scaleTitle": "°C", 1787 | "level": 21.9, 1788 | "icon": "temperature", 1789 | "title": "Temperatur Wohzimmer Eye", 1790 | "modificationTime": 1481313841, 1791 | "lastLevel": 21.9 1792 | }, 1793 | "permanently_hidden": false, 1794 | "probeType": "temperature", 1795 | "tags": ["influx"], 1796 | "visibility": true, 1797 | "updateTime": 1482859715 1798 | }, { 1799 | "creationTime": 1481104432, 1800 | "creatorId": 1, 1801 | "deviceType": "sensorMultilevel", 1802 | "h": 777367898, 1803 | "hasHistory": true, 1804 | "id": "ZWayVDev_zway_40-0-49-3", 1805 | "location": 1, 1806 | "metrics": { 1807 | "probeTitle": "Luminiscence", 1808 | "scaleTitle": "Lux", 1809 | "level": 14, 1810 | "icon": "luminosity", 1811 | "title": "Helligkeit Wohzimmer Eye", 1812 | "modificationTime": 1481313841, 1813 | "lastLevel": 14 1814 | }, 1815 | "permanently_hidden": false, 1816 | "probeType": "luminosity", 1817 | "tags": ["influx"], 1818 | "visibility": true, 1819 | "updateTime": 1482859715 1820 | }, { 1821 | "creationTime": 1481104430, 1822 | "creatorId": 1, 1823 | "deviceType": "battery", 1824 | "h": -1221852696, 1825 | "hasHistory": false, 1826 | "id": "ZWayVDev_zway_40-0-128", 1827 | "location": 0, 1828 | "metrics": { 1829 | "probeTitle": "Battery", 1830 | "scaleTitle": "%", 1831 | "level": 100, 1832 | "icon": "battery", 1833 | "title": "Fibaro Battery (40.0)", 1834 | "modificationTime": 1481104430, 1835 | "lastLevel": 100 1836 | }, 1837 | "permanently_hidden": false, 1838 | "probeType": "", 1839 | "tags": [], 1840 | "visibility": true, 1841 | "updateTime": 1482859715 1842 | }, { 1843 | "creationTime": 1482358501, 1844 | "creatorId": 1, 1845 | "deviceType": "battery", 1846 | "h": -334349015, 1847 | "hasHistory": false, 1848 | "id": "ZWayVDev_zway_41-0-128", 1849 | "location": 0, 1850 | "metrics": { 1851 | "probeTitle": "Battery", 1852 | "scaleTitle": "%", 1853 | "level": 100, 1854 | "icon": "battery", 1855 | "title": "Devolo Battery (41.0)", 1856 | "modificationTime": 1482860167, 1857 | "lastLevel": 100 1858 | }, 1859 | "permanently_hidden": false, 1860 | "probeType": "", 1861 | "tags": [], 1862 | "visibility": true, 1863 | "updateTime": 1483468606 1864 | }, { 1865 | "creationTime": 1482438786, 1866 | "creatorId": 1, 1867 | "deviceType": "switchMultilevel", 1868 | "h": 987675090, 1869 | "hasHistory": false, 1870 | "id": "ZWayVDev_zway_42-0-38", 1871 | "location": 1, 1872 | "metrics": { 1873 | "icon": "multilevel", 1874 | "title": "Fibaro Dimmer (42.0)", 1875 | "level": 99, 1876 | "modificationTime": 1483458472, 1877 | "lastLevel": 99 1878 | }, 1879 | "permanently_hidden": true, 1880 | "probeType": "multilevel", 1881 | "tags": [], 1882 | "visibility": true, 1883 | "updateTime": 1483458474 1884 | }, { 1885 | "creationTime": 1482438786, 1886 | "creatorId": 1, 1887 | "deviceType": "sensorMultilevel", 1888 | "h": -31978727, 1889 | "hasHistory": true, 1890 | "id": "ZWayVDev_zway_42-0-49-4", 1891 | "location": 1, 1892 | "metrics": { 1893 | "probeTitle": "Power", 1894 | "scaleTitle": "W", 1895 | "level": 11, 1896 | "icon": "energy", 1897 | "title": "Fibaro Power (42.0.49.4)", 1898 | "modificationTime": 1483459766, 1899 | "lastLevel": 11 1900 | }, 1901 | "permanently_hidden": false, 1902 | "probeType": "energy", 1903 | "tags": [], 1904 | "visibility": true, 1905 | "updateTime": 1483459766 1906 | }, { 1907 | "creationTime": 1482438787, 1908 | "creatorId": 1, 1909 | "deviceType": "sensorMultilevel", 1910 | "h": -31957589, 1911 | "hasHistory": true, 1912 | "id": "ZWayVDev_zway_42-0-50-0", 1913 | "location": 1, 1914 | "metrics": { 1915 | "probeTitle": "Electric", 1916 | "scaleTitle": "kWh", 1917 | "level": 0.4, 1918 | "icon": "meter", 1919 | "title": "Fibaro Electric Meter (42.0.50.0)", 1920 | "modificationTime": 1483392337, 1921 | "lastLevel": 0.4 1922 | }, 1923 | "permanently_hidden": false, 1924 | "probeType": "meterElectric_kilowatt_hour", 1925 | "tags": [], 1926 | "visibility": true, 1927 | "updateTime": 1483392337 1928 | }, { 1929 | "creationTime": 1482438787, 1930 | "creatorId": 1, 1931 | "deviceType": "sensorMultilevel", 1932 | "h": -31957587, 1933 | "hasHistory": true, 1934 | "id": "ZWayVDev_zway_42-0-50-2", 1935 | "location": 1, 1936 | "metrics": { 1937 | "probeTitle": "Electric", 1938 | "scaleTitle": "W", 1939 | "level": 9.3, 1940 | "icon": "meter", 1941 | "title": "Fibaro Electric Meter (42.0.50.2)", 1942 | "modificationTime": 1483392337, 1943 | "lastLevel": 9.3 1944 | }, 1945 | "permanently_hidden": false, 1946 | "probeType": "meterElectric_watt", 1947 | "tags": [], 1948 | "visibility": true, 1949 | "updateTime": 1483392337 1950 | }, { 1951 | "creationTime": 1482438788, 1952 | "creatorId": 1, 1953 | "deviceType": "switchRGBW", 1954 | "h": -645481719, 1955 | "hasHistory": false, 1956 | "id": "ZWayVDev_zway_42-0-51-rgb", 1957 | "location": 1, 1958 | "metrics": { 1959 | "icon": "multilevel", 1960 | "title": "Fibaro Color (42.0.51)", 1961 | "color": { 1962 | "r": 0, 1963 | "g": 0, 1964 | "b": 0 1965 | }, 1966 | "level": "off", 1967 | "modificationTime": 1482438788, 1968 | "lastLevel": "off", 1969 | "rgbColors": "rgb(0,0,0)" 1970 | }, 1971 | "permanently_hidden": true, 1972 | "probeType": "switchColor_rgb", 1973 | "tags": [], 1974 | "visibility": true, 1975 | "updateTime": 1482859715 1976 | }, { 1977 | "creationTime": 1482438788, 1978 | "creatorId": 1, 1979 | "deviceType": "switchMultilevel", 1980 | "h": -31956628, 1981 | "hasHistory": false, 1982 | "id": "ZWayVDev_zway_42-0-51-0", 1983 | "location": 1, 1984 | "metrics": { 1985 | "icon": "multilevel", 1986 | "title": "Fibaro Soft White (42.0.51.0)", 1987 | "level": 0, 1988 | "modificationTime": 1482438788, 1989 | "lastLevel": 0 1990 | }, 1991 | "permanently_hidden": true, 1992 | "probeType": "switchColor_soft_white", 1993 | "tags": [], 1994 | "visibility": true, 1995 | "updateTime": 1482859715 1996 | }, { 1997 | "creationTime": 1482438787, 1998 | "creatorId": 1, 1999 | "deviceType": "switchMultilevel", 2000 | "h": 987704881, 2001 | "hasHistory": true, 2002 | "id": "ZWayVDev_zway_42-1-38", 2003 | "location": 1, 2004 | "metrics": { 2005 | "icon": "multilevel", 2006 | "title": "Linker Schrank", 2007 | "level": 99, 2008 | "modificationTime": 1483458472, 2009 | "lastLevel": 99 2010 | }, 2011 | "permanently_hidden": false, 2012 | "probeType": "multilevel", 2013 | "tags": [], 2014 | "visibility": true, 2015 | "updateTime": 1483458474 2016 | }, { 2017 | "creationTime": 1482438787, 2018 | "creatorId": 1, 2019 | "deviceType": "switchMultilevel", 2020 | "h": 987734672, 2021 | "hasHistory": true, 2022 | "id": "ZWayVDev_zway_42-2-38", 2023 | "location": 1, 2024 | "metrics": { 2025 | "icon": "multilevel", 2026 | "title": "Linkes Schranklicht Mitte", 2027 | "level": 99, 2028 | "modificationTime": 1483458470, 2029 | "lastLevel": 99 2030 | }, 2031 | "permanently_hidden": false, 2032 | "probeType": "multilevel", 2033 | "tags": ["Homebridge.Skip"], 2034 | "visibility": true, 2035 | "updateTime": 1483458471 2036 | }, { 2037 | "creationTime": 1482438787, 2038 | "creatorId": 1, 2039 | "deviceType": "switchMultilevel", 2040 | "h": 987764463, 2041 | "hasHistory": true, 2042 | "id": "ZWayVDev_zway_42-3-38", 2043 | "location": 1, 2044 | "metrics": { 2045 | "icon": "multilevel", 2046 | "title": "Linkes Schranklicht Oben", 2047 | "level": 99, 2048 | "modificationTime": 1483458470, 2049 | "lastLevel": 99 2050 | }, 2051 | "permanently_hidden": false, 2052 | "probeType": "multilevel", 2053 | "tags": ["Homebridge.Skip"], 2054 | "visibility": true, 2055 | "updateTime": 1483458471 2056 | }, { 2057 | "creationTime": 1482438787, 2058 | "creatorId": 1, 2059 | "deviceType": "switchMultilevel", 2060 | "h": 987794254, 2061 | "hasHistory": true, 2062 | "id": "ZWayVDev_zway_42-4-38", 2063 | "location": 1, 2064 | "metrics": { 2065 | "icon": "multilevel", 2066 | "title": "Linkes Schranklicht Rechts", 2067 | "level": 99, 2068 | "modificationTime": 1483458472, 2069 | "lastLevel": 99 2070 | }, 2071 | "permanently_hidden": false, 2072 | "probeType": "multilevel", 2073 | "tags": ["Homebridge.Skip"], 2074 | "visibility": true, 2075 | "updateTime": 1483458473 2076 | }, { 2077 | "creationTime": 1482438787, 2078 | "creatorId": 1, 2079 | "deviceType": "switchMultilevel", 2080 | "h": 987824045, 2081 | "hasHistory": true, 2082 | "id": "ZWayVDev_zway_42-5-38", 2083 | "location": 1, 2084 | "metrics": { 2085 | "icon": "multilevel", 2086 | "title": "Linkes Schranklicht Links", 2087 | "level": 99, 2088 | "modificationTime": 1483458471, 2089 | "lastLevel": 99 2090 | }, 2091 | "permanently_hidden": false, 2092 | "probeType": "multilevel", 2093 | "tags": ["Homebridge.Skip"], 2094 | "visibility": true, 2095 | "updateTime": 1483458472 2096 | }, { 2097 | "creationTime": 1482439758, 2098 | "creatorId": 1, 2099 | "deviceType": "switchMultilevel", 2100 | "h": 1016304241, 2101 | "hasHistory": false, 2102 | "id": "ZWayVDev_zway_43-0-38", 2103 | "location": 1, 2104 | "metrics": { 2105 | "icon": "multilevel", 2106 | "title": "Fibaro Dimmer (43.0)", 2107 | "level": 62, 2108 | "modificationTime": 1483458471, 2109 | "lastLevel": 62 2110 | }, 2111 | "permanently_hidden": true, 2112 | "probeType": "multilevel", 2113 | "tags": [], 2114 | "visibility": true, 2115 | "updateTime": 1483458474 2116 | }, { 2117 | "creationTime": 1482439758, 2118 | "creatorId": 1, 2119 | "deviceType": "sensorMultilevel", 2120 | "h": 1710831608, 2121 | "hasHistory": true, 2122 | "id": "ZWayVDev_zway_43-0-49-4", 2123 | "location": 1, 2124 | "metrics": { 2125 | "probeTitle": "Power", 2126 | "scaleTitle": "W", 2127 | "level": 7, 2128 | "icon": "energy", 2129 | "title": "Fibaro Power (43.0.49.4)", 2130 | "modificationTime": 1483469768, 2131 | "lastLevel": 7 2132 | }, 2133 | "permanently_hidden": false, 2134 | "probeType": "energy", 2135 | "tags": [], 2136 | "visibility": true, 2137 | "updateTime": 1483469768 2138 | }, { 2139 | "creationTime": 1482439759, 2140 | "creatorId": 1, 2141 | "deviceType": "sensorMultilevel", 2142 | "h": 1710852746, 2143 | "hasHistory": true, 2144 | "id": "ZWayVDev_zway_43-0-50-0", 2145 | "location": 1, 2146 | "metrics": { 2147 | "probeTitle": "Electric", 2148 | "scaleTitle": "kWh", 2149 | "level": 0.49, 2150 | "icon": "meter", 2151 | "title": "Fibaro Electric Meter (43.0.50.0)", 2152 | "modificationTime": 1483392338, 2153 | "lastLevel": 0.49 2154 | }, 2155 | "permanently_hidden": false, 2156 | "probeType": "meterElectric_kilowatt_hour", 2157 | "tags": [], 2158 | "visibility": true, 2159 | "updateTime": 1483392338 2160 | }, { 2161 | "creationTime": 1482439759, 2162 | "creatorId": 1, 2163 | "deviceType": "sensorMultilevel", 2164 | "h": 1710852748, 2165 | "hasHistory": true, 2166 | "id": "ZWayVDev_zway_43-0-50-2", 2167 | "location": 1, 2168 | "metrics": { 2169 | "probeTitle": "Electric", 2170 | "scaleTitle": "W", 2171 | "level": 11.3, 2172 | "icon": "meter", 2173 | "title": "Fibaro Electric Meter (43.0.50.2)", 2174 | "modificationTime": 1483392338, 2175 | "lastLevel": 11.3 2176 | }, 2177 | "permanently_hidden": false, 2178 | "probeType": "meterElectric_watt", 2179 | "tags": [], 2180 | "visibility": true, 2181 | "updateTime": 1483392338 2182 | }, { 2183 | "creationTime": 1482439759, 2184 | "creatorId": 1, 2185 | "deviceType": "switchRGBW", 2186 | "h": -841995224, 2187 | "hasHistory": false, 2188 | "id": "ZWayVDev_zway_43-0-51-rgb", 2189 | "location": 1, 2190 | "metrics": { 2191 | "icon": "multilevel", 2192 | "title": "Fibaro Color (43.0.51)", 2193 | "color": { 2194 | "r": 0, 2195 | "g": 0, 2196 | "b": 0 2197 | }, 2198 | "level": "off", 2199 | "modificationTime": 1482439759, 2200 | "lastLevel": "off", 2201 | "rgbColors": "rgb(0,0,0)" 2202 | }, 2203 | "permanently_hidden": true, 2204 | "probeType": "switchColor_rgb", 2205 | "tags": [], 2206 | "visibility": true, 2207 | "updateTime": 1482859716 2208 | }, { 2209 | "creationTime": 1482439759, 2210 | "creatorId": 1, 2211 | "deviceType": "switchMultilevel", 2212 | "h": 1710853707, 2213 | "hasHistory": false, 2214 | "id": "ZWayVDev_zway_43-0-51-0", 2215 | "location": 1, 2216 | "metrics": { 2217 | "icon": "multilevel", 2218 | "title": "Fibaro Soft White (43.0.51.0)", 2219 | "level": 0, 2220 | "modificationTime": 1482439759, 2221 | "lastLevel": 0 2222 | }, 2223 | "permanently_hidden": true, 2224 | "probeType": "switchColor_soft_white", 2225 | "tags": [], 2226 | "visibility": true, 2227 | "updateTime": 1482859716 2228 | }, { 2229 | "creationTime": 1482439758, 2230 | "creatorId": 1, 2231 | "deviceType": "switchMultilevel", 2232 | "h": 1016334032, 2233 | "hasHistory": false, 2234 | "id": "ZWayVDev_zway_43-1-38", 2235 | "location": 1, 2236 | "metrics": { 2237 | "icon": "multilevel", 2238 | "title": "Fibaro Dimmer (43.1)", 2239 | "level": 62, 2240 | "modificationTime": 1483458471, 2241 | "lastLevel": 62 2242 | }, 2243 | "permanently_hidden": true, 2244 | "probeType": "multilevel", 2245 | "tags": [], 2246 | "visibility": true, 2247 | "updateTime": 1483458474 2248 | }, { 2249 | "creationTime": 1482439758, 2250 | "creatorId": 1, 2251 | "deviceType": "switchMultilevel", 2252 | "h": 1016363823, 2253 | "hasHistory": true, 2254 | "id": "ZWayVDev_zway_43-2-38", 2255 | "location": 1, 2256 | "metrics": { 2257 | "icon": "multilevel", 2258 | "title": "Rechtes Schranklicht Oben", 2259 | "level": 62, 2260 | "modificationTime": 1483458470, 2261 | "lastLevel": 62 2262 | }, 2263 | "permanently_hidden": false, 2264 | "probeType": "multilevel", 2265 | "tags": ["Homebridge.Skip"], 2266 | "visibility": true, 2267 | "updateTime": 1483458471 2268 | }, { 2269 | "creationTime": 1482439758, 2270 | "creatorId": 1, 2271 | "deviceType": "switchMultilevel", 2272 | "h": 1016393614, 2273 | "hasHistory": true, 2274 | "id": "ZWayVDev_zway_43-3-38", 2275 | "location": 1, 2276 | "metrics": { 2277 | "icon": "multilevel", 2278 | "title": "Rechtes Schranklicht Unten", 2279 | "level": 62, 2280 | "modificationTime": 1483458471, 2281 | "lastLevel": 62 2282 | }, 2283 | "permanently_hidden": false, 2284 | "probeType": "multilevel", 2285 | "tags": ["Homebridge.Skip"], 2286 | "visibility": true, 2287 | "updateTime": 1483458473 2288 | }, { 2289 | "creationTime": 1482439758, 2290 | "creatorId": 1, 2291 | "deviceType": "switchMultilevel", 2292 | "h": 1016423405, 2293 | "hasHistory": true, 2294 | "id": "ZWayVDev_zway_43-4-38", 2295 | "location": 1, 2296 | "metrics": { 2297 | "icon": "multilevel", 2298 | "title": "Rechtes Schranklicht Links", 2299 | "level": 62, 2300 | "modificationTime": 1483458471, 2301 | "lastLevel": 62 2302 | }, 2303 | "permanently_hidden": false, 2304 | "probeType": "multilevel", 2305 | "tags": ["Homebridge.Skip"], 2306 | "visibility": true, 2307 | "updateTime": 1483458473 2308 | }, { 2309 | "creationTime": 1482439759, 2310 | "creatorId": 1, 2311 | "deviceType": "switchMultilevel", 2312 | "h": 1016453196, 2313 | "hasHistory": true, 2314 | "id": "ZWayVDev_zway_43-5-38", 2315 | "location": 1, 2316 | "metrics": { 2317 | "icon": "multilevel", 2318 | "title": "Rechtes Schranklicht Rechts", 2319 | "level": 62, 2320 | "modificationTime": 1483458471, 2321 | "lastLevel": 62 2322 | }, 2323 | "permanently_hidden": false, 2324 | "probeType": "multilevel", 2325 | "tags": ["Homebridge.Skip"], 2326 | "visibility": true, 2327 | "updateTime": 1483458474 2328 | }, { 2329 | "creationTime": 1480501922, 2330 | "creatorId": 40, 2331 | "deviceType": "sensorMultiline", 2332 | "h": -2086988157, 2333 | "hasHistory": false, 2334 | "id": "ClimateControl_40", 2335 | "location": 0, 2336 | "metrics": { 2337 | "multilineType": "climateControl", 2338 | "title": "Heizung Control", 2339 | "icon": "thermostat", 2340 | "rooms": [{ 2341 | "room": 1, 2342 | "comfort": "24", 2343 | "mainSensor": "ZWayVDev_zway_11-0-49-1", 2344 | "state": "energySave", 2345 | "energySave": 19, 2346 | "targetTemp": 24, 2347 | "hasSchedule": false 2348 | }, { 2349 | "room": 3, 2350 | "comfort": "21", 2351 | "mainSensor": "ZWayVDev_zway_21-0-49-1", 2352 | "state": "energySave", 2353 | "energySave": 16, 2354 | "targetTemp": 21, 2355 | "hasSchedule": false 2356 | }, { 2357 | "room": 4, 2358 | "comfort": "24", 2359 | "mainSensor": "ZWayVDev_zway_24-0-49-1", 2360 | "state": "energySave", 2361 | "energySave": 19, 2362 | "targetTemp": 24, 2363 | "hasSchedule": true 2364 | }], 2365 | "state": "energySave" 2366 | }, 2367 | "permanently_hidden": false, 2368 | "probeType": "", 2369 | "tags": [], 2370 | "visibility": true, 2371 | "updateTime": 1482859732 2372 | }, { 2373 | "creationTime": 1475572732, 2374 | "creatorId": 1, 2375 | "deviceType": "battery", 2376 | "h": -237757934, 2377 | "hasHistory": false, 2378 | "id": "ZWayVDev_zway_17-0-128", 2379 | "location": 0, 2380 | "metrics": { 2381 | "probeTitle": "Battery", 2382 | "scaleTitle": "%", 2383 | "level": 79, 2384 | "icon": "battery", 2385 | "title": "Danfoss Battery (17.0)", 2386 | "modificationTime": 1483234169, 2387 | "lastLevel": 79 2388 | }, 2389 | "permanently_hidden": false, 2390 | "probeType": "", 2391 | "tags": [], 2392 | "visibility": true, 2393 | "updateTime": 1483468257 2394 | }, { 2395 | "creationTime": 1475572733, 2396 | "creatorId": 1, 2397 | "deviceType": "thermostat", 2398 | "h": 1219592106, 2399 | "hasHistory": true, 2400 | "id": "ZWayVDev_zway_17-0-67-1", 2401 | "location": 1, 2402 | "metrics": { 2403 | "scaleTitle": "°C", 2404 | "level": 24, 2405 | "min": 5, 2406 | "max": 40, 2407 | "icon": "thermostat", 2408 | "title": "Heizung Wohnzimmer", 2409 | "modificationTime": 1483459398, 2410 | "lastLevel": 24 2411 | }, 2412 | "permanently_hidden": false, 2413 | "probeType": "thermostat_set_point", 2414 | "tags": [], 2415 | "visibility": true, 2416 | "updateTime": 1483468257 2417 | }, { 2418 | "creationTime": 1475572734, 2419 | "creatorId": 1, 2420 | "deviceType": "sensorMultilevel", 2421 | "h": 1219534446, 2422 | "hasHistory": true, 2423 | "id": "ZWayVDev_zway_17-0-49-1", 2424 | "location": 1, 2425 | "metrics": { 2426 | "probeTitle": "Temperature", 2427 | "scaleTitle": "°C", 2428 | "level": 21.75, 2429 | "icon": "temperature", 2430 | "title": "Temperatur Wohnzimmer Heizung", 2431 | "modificationTime": 1483468257, 2432 | "lastLevel": 21.75 2433 | }, 2434 | "permanently_hidden": false, 2435 | "probeType": "temperature", 2436 | "tags": ["influx"], 2437 | "visibility": true, 2438 | "updateTime": 1483468257 2439 | }, { 2440 | "creationTime": 1481101372, 2441 | "creatorId": 1, 2442 | "deviceType": "sensorBinary", 2443 | "h": 1899591855, 2444 | "hasHistory": true, 2445 | "id": "ZWayVDev_zway_37-0-48-1", 2446 | "location": 6, 2447 | "metrics": { 2448 | "probeTitle": "General purpose", 2449 | "scaleTitle": "", 2450 | "icon": "motion", 2451 | "level": "off", 2452 | "title": "Bewegung Arbeitszimmer", 2453 | "modificationTime": 1483453229, 2454 | "lastLevel": "off" 2455 | }, 2456 | "permanently_hidden": false, 2457 | "probeType": "general_purpose", 2458 | "tags": ["influx"], 2459 | "visibility": true, 2460 | "updateTime": 1483468834 2461 | }, { 2462 | "creationTime": 1481101375, 2463 | "creatorId": 1, 2464 | "deviceType": "battery", 2465 | "h": -1047104560, 2466 | "hasHistory": false, 2467 | "id": "ZWayVDev_zway_37-0-128", 2468 | "location": 0, 2469 | "metrics": { 2470 | "probeTitle": "Battery", 2471 | "scaleTitle": "%", 2472 | "level": 100, 2473 | "icon": "battery", 2474 | "title": "Aeotec Battery (37.0)", 2475 | "modificationTime": 1481101375, 2476 | "lastLevel": 100 2477 | }, 2478 | "permanently_hidden": false, 2479 | "probeType": "", 2480 | "tags": [], 2481 | "visibility": true, 2482 | "updateTime": 1483469345 2483 | }, { 2484 | "creationTime": 1481101386, 2485 | "creatorId": 1, 2486 | "deviceType": "sensorMultilevel", 2487 | "h": 1899592816, 2488 | "hasHistory": true, 2489 | "id": "ZWayVDev_zway_37-0-49-1", 2490 | "location": 6, 2491 | "metrics": { 2492 | "probeTitle": "Temperature", 2493 | "scaleTitle": "°C", 2494 | "level": 20.1, 2495 | "icon": "temperature", 2496 | "title": "Temperatur Arbeitszimmer", 2497 | "modificationTime": 1483468951, 2498 | "lastLevel": 20.1 2499 | }, 2500 | "permanently_hidden": false, 2501 | "probeType": "temperature", 2502 | "tags": ["influx"], 2503 | "visibility": true, 2504 | "updateTime": 1483469345 2505 | }, { 2506 | "creationTime": 1481101386, 2507 | "creatorId": 1, 2508 | "deviceType": "sensorMultilevel", 2509 | "h": 1899592818, 2510 | "hasHistory": true, 2511 | "id": "ZWayVDev_zway_37-0-49-3", 2512 | "location": 6, 2513 | "metrics": { 2514 | "probeTitle": "Luminiscence", 2515 | "scaleTitle": "Lux", 2516 | "level": 0, 2517 | "icon": "luminosity", 2518 | "title": "Helligkeit Arbeitszimmer", 2519 | "modificationTime": 1483458539, 2520 | "lastLevel": 0 2521 | }, 2522 | "permanently_hidden": false, 2523 | "probeType": "luminosity", 2524 | "tags": ["influx"], 2525 | "visibility": true, 2526 | "updateTime": 1483469346 2527 | }, { 2528 | "creationTime": 1481101386, 2529 | "creatorId": 1, 2530 | "deviceType": "sensorMultilevel", 2531 | "h": 1899592820, 2532 | "hasHistory": true, 2533 | "id": "ZWayVDev_zway_37-0-49-5", 2534 | "location": 6, 2535 | "metrics": { 2536 | "probeTitle": "Humidity", 2537 | "scaleTitle": "%", 2538 | "level": 59, 2539 | "icon": "humidity", 2540 | "title": "Feuchtigkeit Arbeitszimmer", 2541 | "modificationTime": 1483468846, 2542 | "lastLevel": 59 2543 | }, 2544 | "permanently_hidden": false, 2545 | "probeType": "humidity", 2546 | "tags": ["influx"], 2547 | "visibility": true, 2548 | "updateTime": 1483469345 2549 | }, { 2550 | "creationTime": 1481101386, 2551 | "creatorId": 1, 2552 | "deviceType": "sensorMultilevel", 2553 | "h": -1242164762, 2554 | "hasHistory": true, 2555 | "id": "ZWayVDev_zway_37-0-49-27", 2556 | "location": 6, 2557 | "metrics": { 2558 | "probeTitle": "Ultraviolet", 2559 | "scaleTitle": "UV index", 2560 | "level": 0, 2561 | "icon": "ultraviolet", 2562 | "title": "Ultraviolet Arbeitszimmer", 2563 | "modificationTime": 1481101386, 2564 | "lastLevel": 0 2565 | }, 2566 | "permanently_hidden": false, 2567 | "probeType": "ultraviolet", 2568 | "tags": [], 2569 | "visibility": true, 2570 | "updateTime": 1483469346 2571 | }, { 2572 | "creationTime": 1481102513, 2573 | "creatorId": 1, 2574 | "deviceType": "sensorBinary", 2575 | "h": -652565106, 2576 | "hasHistory": true, 2577 | "id": "ZWayVDev_zway_38-0-48-1", 2578 | "location": 1, 2579 | "metrics": { 2580 | "probeTitle": "General purpose", 2581 | "scaleTitle": "", 2582 | "icon": "motion", 2583 | "level": "off", 2584 | "title": "Bewegung Wohnzimmer", 2585 | "modificationTime": 1483469109, 2586 | "lastLevel": "off" 2587 | }, 2588 | "permanently_hidden": false, 2589 | "probeType": "general_purpose", 2590 | "tags": ["Homebridge.Skip", "influx"], 2591 | "visibility": true, 2592 | "updateTime": 1483469605 2593 | }, { 2594 | "creationTime": 1481102515, 2595 | "creatorId": 1, 2596 | "deviceType": "sensorBinary", 2597 | "h": -288529487, 2598 | "hasHistory": false, 2599 | "id": "ZWayVDev_zway_38-0-113-7-3-A", 2600 | "location": 1, 2601 | "metrics": { 2602 | "icon": "smoke", 2603 | "level": "off", 2604 | "title": "Tamper Wohnzimmer", 2605 | "modificationTime": 1481397434, 2606 | "lastLevel": "off" 2607 | }, 2608 | "permanently_hidden": true, 2609 | "probeType": "alarm_burglar", 2610 | "tags": [], 2611 | "visibility": true, 2612 | "updateTime": 1483469782 2613 | }, { 2614 | "creationTime": 1481102516, 2615 | "creatorId": 1, 2616 | "deviceType": "battery", 2617 | "h": -159600879, 2618 | "hasHistory": false, 2619 | "id": "ZWayVDev_zway_38-0-128", 2620 | "location": 0, 2621 | "metrics": { 2622 | "probeTitle": "Battery", 2623 | "scaleTitle": "%", 2624 | "level": 100, 2625 | "icon": "battery", 2626 | "title": "Aeotec Battery (38.0)", 2627 | "modificationTime": 1481102516, 2628 | "lastLevel": 100 2629 | }, 2630 | "permanently_hidden": false, 2631 | "probeType": "", 2632 | "tags": [], 2633 | "visibility": true, 2634 | "updateTime": 1483469546 2635 | }, { 2636 | "creationTime": 1481102527, 2637 | "creatorId": 1, 2638 | "deviceType": "sensorMultilevel", 2639 | "h": -652564145, 2640 | "hasHistory": true, 2641 | "id": "ZWayVDev_zway_38-0-49-1", 2642 | "location": 1, 2643 | "metrics": { 2644 | "probeTitle": "Temperature", 2645 | "scaleTitle": "°C", 2646 | "level": 22.5, 2647 | "icon": "temperature", 2648 | "title": "Temperatur Wohnzimmer Sensor", 2649 | "modificationTime": 1483468783, 2650 | "lastLevel": 22.5 2651 | }, 2652 | "permanently_hidden": false, 2653 | "probeType": "temperature", 2654 | "tags": ["influx"], 2655 | "visibility": true, 2656 | "updateTime": 1483469548 2657 | }, { 2658 | "creationTime": 1481102527, 2659 | "creatorId": 1, 2660 | "deviceType": "sensorMultilevel", 2661 | "h": -652564143, 2662 | "hasHistory": true, 2663 | "id": "ZWayVDev_zway_38-0-49-3", 2664 | "location": 1, 2665 | "metrics": { 2666 | "probeTitle": "Luminiscence", 2667 | "scaleTitle": "Lux", 2668 | "level": 10, 2669 | "icon": "luminosity", 2670 | "title": "Helligkeit Wohnzimmer", 2671 | "modificationTime": 1483469546, 2672 | "lastLevel": 10 2673 | }, 2674 | "permanently_hidden": false, 2675 | "probeType": "luminosity", 2676 | "tags": ["influx"], 2677 | "visibility": true, 2678 | "updateTime": 1483469546 2679 | }, { 2680 | "creationTime": 1481102527, 2681 | "creatorId": 1, 2682 | "deviceType": "sensorMultilevel", 2683 | "h": -652564141, 2684 | "hasHistory": true, 2685 | "id": "ZWayVDev_zway_38-0-49-5", 2686 | "location": 1, 2687 | "metrics": { 2688 | "probeTitle": "Humidity", 2689 | "scaleTitle": "%", 2690 | "level": 52, 2691 | "icon": "humidity", 2692 | "title": "Feuchtigkeit Wohnzimmer", 2693 | "modificationTime": 1483465132, 2694 | "lastLevel": 52 2695 | }, 2696 | "permanently_hidden": false, 2697 | "probeType": "humidity", 2698 | "tags": ["influx"], 2699 | "visibility": true, 2700 | "updateTime": 1483469545 2701 | }, { 2702 | "creationTime": 1481102527, 2703 | "creatorId": 1, 2704 | "deviceType": "sensorMultilevel", 2705 | "h": 1245348071, 2706 | "hasHistory": true, 2707 | "id": "ZWayVDev_zway_38-0-49-27", 2708 | "location": 1, 2709 | "metrics": { 2710 | "probeTitle": "Ultraviolet", 2711 | "scaleTitle": "UV index", 2712 | "level": 0, 2713 | "icon": "ultraviolet", 2714 | "title": "Ultraviolet Wohnzimmer", 2715 | "modificationTime": 1481102527, 2716 | "lastLevel": 0 2717 | }, 2718 | "permanently_hidden": false, 2719 | "probeType": "ultraviolet", 2720 | "tags": [], 2721 | "visibility": true, 2722 | "updateTime": 1483469215 2723 | }, { 2724 | "creationTime": 1481101374, 2725 | "creatorId": 1, 2726 | "deviceType": "sensorBinary", 2727 | "h": 4873520, 2728 | "hasHistory": true, 2729 | "id": "ZWayVDev_zway_37-0-113-7-3-A", 2730 | "location": 6, 2731 | "metrics": { 2732 | "icon": "smoke", 2733 | "level": "off", 2734 | "title": "Tamper Arbeitszimmer", 2735 | "modificationTime": 1481102176, 2736 | "lastLevel": "off" 2737 | }, 2738 | "permanently_hidden": false, 2739 | "probeType": "alarm_burglar", 2740 | "tags": [], 2741 | "visibility": true, 2742 | "updateTime": 1483469441 2743 | }, { 2744 | "creationTime": 1480502353, 2745 | "creatorId": 1, 2746 | "deviceType": "sensorBinary", 2747 | "h": 156781520, 2748 | "hasHistory": true, 2749 | "id": "ZWayVDev_zway_36-0-48-1", 2750 | "location": 3, 2751 | "metrics": { 2752 | "probeTitle": "General purpose", 2753 | "scaleTitle": "", 2754 | "icon": "motion", 2755 | "level": "off", 2756 | "title": "Bewegung Schlafzimmer", 2757 | "modificationTime": 1483463593, 2758 | "lastLevel": "off" 2759 | }, 2760 | "permanently_hidden": false, 2761 | "probeType": "general_purpose", 2762 | "tags": ["influx"], 2763 | "visibility": true, 2764 | "updateTime": 1483469651 2765 | }] 2766 | }, 2767 | "code": 200, 2768 | "message": "200 OK", 2769 | "error": null 2770 | } 2771 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var inherits = require('util').inherits; 2 | var debug = require('debug')('ZWayServer'); 3 | var Service;// = require("../api").homebridge.hap.Service; 4 | var Characteristic;// = require("../api").homebridge.hap.Characteristic; 5 | //var types = require("../api").homebridge.hapLegacyTypes; 6 | var request = require("request"); 7 | var tough = require('tough-cookie'); 8 | var Q = require("q"); 9 | 10 | function ZWayServerPlatform(log, config){ 11 | this.log = log; 12 | this.url = config["url"]; 13 | this.login = config["login"]; 14 | this.password = config["password"]; 15 | this.opt_in = config["opt_in"]; 16 | this.name_overrides = config["name_overrides"]; 17 | this.batteryLow = config["battery_low_level"] || 15; 18 | this.OIUWatts = config["outlet_in_use_level"] || 2; 19 | this.blinkTime = (config["blink_time"]*1000) || (0.5*1000); 20 | this.pollInterval = config["poll_interval"] || 2; 21 | this.splitServices= config["split_services"] === undefined ? true : config["split_services"]; 22 | this.dimmerOffThreshold = config["dimmer_off_threshold"] === undefined ? 5 : config["dimmer_off_threshold"]; 23 | this.lastUpdate = 0; 24 | this.cxVDevMap = {}; 25 | this.vDevStore = {}; 26 | this.sessionId = ""; 27 | this.jar = request.jar(new tough.CookieJar()); 28 | } 29 | 30 | 31 | module.exports = function(homebridge) { 32 | Service = homebridge.hap.Service; 33 | Characteristic = homebridge.hap.Characteristic; 34 | 35 | ZWayServerPlatform.CurrentPowerConsumption = function() { 36 | Characteristic.call(this, 'Consumption', 'E863F10D-079E-48FF-8F27-9C2605A29F52'); 37 | this.setProps({ 38 | format: Characteristic.Formats.UINT16, 39 | unit: "watts", 40 | maxValue: 1000000000, 41 | minValue: 0, 42 | minStep: 1, 43 | perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] 44 | }); 45 | this.value = this.getDefaultValue(); 46 | }; 47 | ZWayServerPlatform.CurrentPowerConsumption.UUID = 'E863F10D-079E-48FF-8F27-9C2605A29F52'; 48 | inherits(ZWayServerPlatform.CurrentPowerConsumption, Characteristic); 49 | 50 | ZWayServerPlatform.TotalPowerConsumption = function() { 51 | Characteristic.call(this, 'Total Consumption', 'E863F10C-079E-48FF-8F27-9C2605A29F52'); 52 | this.setProps({ 53 | format: Characteristic.Formats.FLOAT, // Deviation from Eve Energy observed type 54 | unit: "kilowatthours", 55 | maxValue: 1000000000, 56 | minValue: 0, 57 | minStep: 0.001, 58 | perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] 59 | }); 60 | this.value = this.getDefaultValue(); 61 | }; 62 | ZWayServerPlatform.TotalPowerConsumption.UUID = 'E863F10C-079E-48FF-8F27-9C2605A29F52'; 63 | inherits(ZWayServerPlatform.TotalPowerConsumption, Characteristic); 64 | 65 | ZWayServerPlatform.ServiceUUIDReverseLookupMap = {}; 66 | for(var serviceKey in Service) if(Service[serviceKey].UUID != undefined) 67 | ZWayServerPlatform.ServiceUUIDReverseLookupMap[Service[serviceKey].UUID] = serviceKey; 68 | for(var serviceKey in ZWayServerPlatform) if(ZWayServerPlatform[serviceKey].UUID != undefined) 69 | ZWayServerPlatform.ServiceUUIDReverseLookupMap[ZWayServerPlatform[serviceKey].UUID] = serviceKey; 70 | 71 | ZWayServerAccessory.prototype.extraCharacteristicsMap = { 72 | "battery.Battery": [Characteristic.BatteryLevel, Characteristic.StatusLowBattery], 73 | "sensorMultilevel.Temperature": [Characteristic.CurrentTemperature, Characteristic.TemperatureDisplayUnits], 74 | "sensorMultilevel.Humidity": [Characteristic.CurrentRelativeHumidity], 75 | "sensorMultilevel.Luminiscence": [Characteristic.CurrentAmbientLightLevel], 76 | "sensorMultilevel.meterElectric_watt": [ZWayServerPlatform.CurrentPowerConsumption], 77 | "sensorMultilevel.meterElectric_kilowatt_per_hour": [ZWayServerPlatform.TotalPowerConsumption] 78 | } 79 | 80 | homebridge.registerAccessory("homebridge-zway", "ZWayServer", ZWayServerAccessory); 81 | homebridge.registerPlatform("homebridge-zway", "ZWayServer", ZWayServerPlatform); 82 | } 83 | 84 | ZWayServerPlatform.getVDevTypeKeyNormalizationMap = { 85 | "sensorBinary.general_purpose": "sensorBinary.General purpose", 86 | "sensorBinary.alarm_burglar": "sensorBinary", 87 | "sensorBinary.door": "sensorBinary.Door/Window", 88 | "sensorBinary.door-window": "sensorBinary.Door/Window", 89 | "sensorBinary.tamper": "sensorBinary.Tamper", 90 | "sensorMultilevel.temperature": "sensorMultilevel.Temperature", 91 | "sensorMultilevel.luminosity": "sensorMultilevel.Luminiscence", 92 | "sensorMultilevel.humidity": "sensorMultilevel.Humidity", 93 | "switchMultilevel.dimmer": "switchMultilevel", 94 | "switchRGBW.switchColor_undefined": "switchRGBW", 95 | "switchRGBW.switchColor_rgb": "switchRGBW", 96 | "switchMultilevel.multilevel": "switchMultilevel", 97 | "switchMultilevel.switchColor_soft_white": "switchMultilevel", 98 | "switchMultilevel.switchColor_cold_white": "switchMultilevel", 99 | "switchMultilevel.motor": "switchMultilevel.blind", 100 | "thermostat.thermostat_set_point": "thermostat", 101 | "battery": "battery.Battery" 102 | } 103 | ZWayServerPlatform.getVDevTypeKeyRoot = function(vdev){ 104 | var key = vdev.deviceType; 105 | var overrideDeviceType, overrideProbeType; 106 | if(overrideDeviceType = ZWayServerPlatform.prototype.getTagValue(vdev, "Override.deviceType")){ 107 | // NOTE: This feature should be considered UNDOCUMENTED and UNSUPPORTED and 108 | // may be removed at any time without notice. It should not be used in normal 109 | // circumstances. If you find this useful, you must submit an issue with a 110 | // use-case justification, at which point it may be considered to be supported 111 | // as a feature. Improper use may seriously interfere with proper functioning 112 | // of Homebridge or the ZWayServer platform! 113 | key = overrideDeviceType; 114 | } 115 | return key; 116 | } 117 | ZWayServerPlatform.getVDevTypeKey = function(vdev){ 118 | /* At present we normalize these values down from 2.2 nomenclature to 2.0 119 | nomenclature. At some point, this should be reversed. */ 120 | var nmap = ZWayServerPlatform.getVDevTypeKeyNormalizationMap; 121 | var key = ZWayServerPlatform.getVDevTypeKeyRoot(vdev); 122 | if(overrideProbeType = ZWayServerPlatform.prototype.getTagValue(vdev, "Override.probeType")){ 123 | // NOTE: While this is supported, it is intended to only be used by "Code 124 | // Devices" and "HTTP Devices" or other custom/unusual device types, and 125 | // should not be required or used in most other circumstances. Improper 126 | // use may seriously interfere with proper functioning of Homebridge or 127 | // the ZWayServer platform! 128 | key += "." + overrideProbeType; 129 | } else if(vdev.metrics && vdev.metrics.probeTitle == 'Electric'){ 130 | // We need greater specificity given by probeType, so override the 131 | // v2.0-favoring logic for this specific case... 132 | key += "." + vdev.probeType; 133 | } else if(vdev.metrics && vdev.metrics.probeTitle){ 134 | key += "." + vdev.metrics.probeTitle; 135 | } else if(vdev.probeType){ 136 | key += "." + vdev.probeType; 137 | } 138 | return nmap[key] || key; 139 | } 140 | 141 | ZWayServerPlatform.prototype = { 142 | 143 | zwayRequest: function(opts){ 144 | var that = this; 145 | var deferred = Q.defer(); 146 | 147 | opts.jar = true;//this.jar; 148 | opts.json = true; 149 | opts.headers = { 150 | "Cookie": "ZWAYSession=" + this.sessionId 151 | }; 152 | 153 | request(opts, function(error, response, body){ 154 | if(response && response.statusCode == 401){ 155 | debug("Authenticating..."); 156 | request({ 157 | method: "POST", 158 | url: that.url + 'ZAutomation/api/v1/login', 159 | body: { //JSON.stringify({ 160 | "form": true, 161 | "login": that.login, 162 | "password": that.password, 163 | "keepme": false, 164 | "default_ui": 1 165 | }, 166 | headers: { 167 | "Accept": "application/json", 168 | "Content-Type": "application/json" 169 | }, 170 | json: true, 171 | jar: true//that.jar 172 | }, function(error, response, body){ 173 | if(response && response.statusCode == 200){ 174 | that.sessionId = body.data.sid; 175 | opts.headers["Cookie"] = "ZWAYSession=" + that.sessionId; 176 | debug("Authenticated. Resubmitting original request..."); 177 | request(opts, function(error, response, body){ 178 | if(response.statusCode == 200){ 179 | deferred.resolve(body); 180 | } else { 181 | deferred.reject(response); 182 | } 183 | }); 184 | } else if(response && response.statusCode == 401){ 185 | that.log("ERROR: Fatal! Authentication failed (error code 401)! Check the username and password in config.json!"); 186 | deferred.reject(response); 187 | } else { 188 | that.log("ERROR: Fatal! Authentication failed with unexpected HTTP response code " + response.statusCode + "!"); 189 | deferred.reject(response); 190 | } 191 | }); 192 | } else if(response && response.statusCode == 200) { 193 | deferred.resolve(body); 194 | } else { 195 | that.log("ERROR: Request failed! " 196 | + (response ? "HTTP response code " + response.statusCode + ". " : "") 197 | + (error ? "Error code " + error.code + ". " : "") 198 | + "Check the URL in config.json and ensure that the URL can be reached from this system!"); 199 | if(response) debug(response); else debug(error); 200 | deferred.reject(response); 201 | } 202 | }); 203 | return deferred.promise; 204 | } 205 | , 206 | getTagValue: function(vdev, tagStem){ 207 | if(!(vdev.tags && vdev.tags.length > 0)) return false; 208 | var tagStem = "Homebridge." + tagStem; 209 | if(vdev.tags.indexOf(tagStem) >= 0) return true; 210 | var tags = vdev.tags, l = tags.length, tag; 211 | for(var i = 0; i < l; i++){ 212 | tag = tags[i]; 213 | if(tag.indexOf(tagStem + ":") === 0){ 214 | return tag.substr(tagStem.length + 1); 215 | } 216 | } 217 | return false; 218 | } 219 | , 220 | isVDevBridged: function(vdev){ 221 | var isBridged; 222 | var isTaggedSkip = this.getTagValue(vdev, "Skip"); 223 | var isTaggedInclude = this.getTagValue(vdev, "Include"); 224 | 225 | if(this.opt_in) isBridged = false; else isBridged = true; // Start with the initial bias 226 | if(vdev.permanently_hidden) isBridged = false; 227 | if(isTaggedInclude) isBridged = true; // Include overrides permanently_hidden 228 | if(isTaggedSkip) isBridged = false; // Skip overrides Include... 229 | if(this.opt_in && isTaggedInclude) isBridged = true; // ...unless we're in opt_in mode, where Include always wins. 230 | 231 | if(!isBridged && !this.opt_in){ 232 | debug("Device " + vdev.id + " skipped! "); 233 | debug({"permanently_hidden": vdev.permanently_hidden, "Skip": isTaggedSkip, "Include": isTaggedInclude, "opt_in": this.opt_in}); 234 | } 235 | return isBridged; 236 | } 237 | , 238 | accessories: function(callback) { 239 | debug("Fetching Z-Way devices..."); 240 | this.zwayRequest({ 241 | method: "GET", 242 | url: this.url + 'ZAutomation/api/v1/devices' 243 | }).then(function(result){ 244 | var foundAccessories = this.buildAccessoriesFromJson(result); 245 | callback(foundAccessories); 246 | // Start the polling process... 247 | this.pollingTimer = setTimeout(this.pollUpdate.bind(this), this.pollInterval*1000); 248 | }.bind(this)); 249 | 250 | } 251 | , 252 | buildAccessoriesFromJson: function(result){ 253 | 254 | //TODO: Unify this with getVDevServices, so there's only one place with mapping between service and vDev type. 255 | //Note: Order matters! 256 | var primaryDeviceClasses = [ 257 | "doorlock", 258 | "thermostat", 259 | "switchMultilevel.blind", 260 | "switchMultilevel", 261 | "switchBinary", 262 | "sensorBinary.alarm_smoke", 263 | "sensorBinary.Door/Window", 264 | "sensorBinary.alarm_door", 265 | "sensorBinary.alarmSensor_flood", 266 | 267 | // | Possible regression, this couldn't become a primary before, but it's needed for some LeakSensors... 268 | // v But now a "sensorBinary.General purpose" can become primary... Bug or Feature? 269 | "sensorBinary.General purpose", 270 | 271 | "toggleButton", 272 | "sensorMultilevel.Temperature", 273 | "sensorMultilevel.Humidity", 274 | ]; 275 | 276 | var that = this; 277 | var foundAccessories = []; 278 | 279 | this.lastUpdate = result.data.updateTime; 280 | 281 | var devices = result.data.devices; 282 | var groupedDevices = {}; 283 | for(var i = 0; i < devices.length; i++){ 284 | var vdev = devices[i]; 285 | 286 | if(!this.isVDevBridged(vdev)) continue; 287 | 288 | var gdid = this.getTagValue(vdev, "Accessory.Id"); 289 | if(!gdid){ 290 | gdid = vdev.id.replace(/^(.*?)_zway_(\d+-\d+)-\d.*/, '$1_$2'); 291 | } 292 | 293 | var gd = groupedDevices[gdid] || (groupedDevices[gdid] = { devices: [], types: {}, extras: {}, primary: undefined, cxmap: {} }); 294 | 295 | gd.devices.push(vdev); 296 | var vdevIndex = gd.devices.length - 1; 297 | 298 | var tk = ZWayServerPlatform.getVDevTypeKey(vdev); 299 | 300 | // If this is explicitly set as primary, set it now... 301 | if(this.getTagValue(vdev, "IsPrimary")){ 302 | // everybody out of the way! Can't be in "extras" if you're the primary... 303 | if(gd.types[tk] !== undefined){ 304 | gd.extras[tk] = gd.extras[tk] || []; 305 | gd.extras[tk].push(gd.types[tk]); 306 | delete gd.types[tk]; // clear the way for this one to be set here below... 307 | } 308 | gd.primary = vdevIndex; 309 | //gd.types[tk] = gd.primary; 310 | } 311 | 312 | if(gd.types[tk] === undefined){ 313 | gd.types[tk] = vdevIndex; 314 | } else { 315 | gd.extras[tk] = gd.extras[tk] || []; 316 | gd.extras[tk].push(vdevIndex); 317 | } 318 | var tkroot = ZWayServerPlatform.getVDevTypeKeyRoot(vdev); 319 | if(tk !== tkroot) gd.types[tkroot] = vdevIndex; // also include the deviceType only as a possibility 320 | 321 | // Create a map entry when Homebridge.Characteristic.Type is set... 322 | var ctype = this.getTagValue(vdev, "Characteristic.Type"); 323 | if(ctype && Characteristic[ctype]){ 324 | var cx = new Characteristic[ctype](); 325 | gd.cxmap[cx.UUID] = vdevIndex; 326 | } 327 | } 328 | 329 | for(var gdid in groupedDevices) { 330 | if(!groupedDevices.hasOwnProperty(gdid)) continue; 331 | 332 | // Debug/log... 333 | debug('Got grouped device ' + gdid + ' consisting of devices:'); 334 | var gd = groupedDevices[gdid]; 335 | for(var j = 0; j < gd.devices.length; j++){ 336 | debug(gd.devices[j].id + " - " + gd.devices[j].deviceType + (gd.devices[j].metrics && gd.devices[j].metrics.probeTitle ? "." + gd.devices[j].metrics.probeTitle : "")); 337 | } 338 | 339 | var accessory = null; 340 | if(gd.primary !== undefined){ 341 | var pd = gd.devices[gd.primary]; 342 | var name = pd.metrics && pd.metrics.title ? pd.metrics.title : pd.id; 343 | accessory = new ZWayServerAccessory(name, gd, that); 344 | } 345 | else for(var ti = 0; ti < primaryDeviceClasses.length; ti++){ 346 | if(gd.types[primaryDeviceClasses[ti]] !== undefined){ 347 | gd.primary = gd.types[primaryDeviceClasses[ti]]; 348 | var pd = gd.devices[gd.primary]; 349 | var name = pd.metrics && pd.metrics.title ? pd.metrics.title : pd.id; 350 | //debug("Using primary device with type " + primaryDeviceClasses[ti] + ", " + name + " (" + pd.id + ") as primary."); 351 | accessory = new ZWayServerAccessory(name, gd, that); 352 | break; 353 | } 354 | } 355 | 356 | if(!accessory) 357 | debug("WARN: Didn't find suitable device class!"); 358 | else 359 | foundAccessories.push(accessory); 360 | } 361 | return foundAccessories; 362 | 363 | } 364 | , 365 | 366 | pollUpdate: function(){ 367 | //debug("Polling for updates since " + this.lastUpdate + "..."); 368 | return this.zwayRequest({ 369 | method: "GET", 370 | url: this.url + 'ZAutomation/api/v1/devices', 371 | qs: {since: this.lastUpdate} 372 | }).then(function(result){ 373 | this.processPollUpdate(result); 374 | }.bind(this)) 375 | .fin(function(){ 376 | // setup next poll... 377 | this.pollingTimer = setTimeout(this.pollUpdate.bind(this), this.pollInterval*1000); 378 | }.bind(this)); 379 | } 380 | , 381 | processPollUpdate: function(result){ 382 | this.lastUpdate = result.data.updateTime; 383 | if(result.data && result.data.devices && result.data.devices.length){ 384 | var updates = result.data.devices; 385 | debug("Got " + updates.length + " updates."); 386 | for(var i = 0; i < updates.length; i++){ 387 | var upd = updates[i]; 388 | if(this.cxVDevMap[upd.id]){ 389 | var vdev = this.vDevStore[upd.id]; 390 | vdev.metrics.level = upd.metrics.level; 391 | if(upd.metrics.color){ 392 | vdev.metrics.r = upd.metrics.r; 393 | vdev.metrics.g = upd.metrics.g; 394 | vdev.metrics.b = upd.metrics.b; 395 | } 396 | var cxs = this.cxVDevMap[upd.id]; 397 | for(var j = 0; j < cxs.length; j++){ 398 | var cx = cxs[j]; 399 | if(typeof cx.zway_getValueFromVDev !== "function") continue; 400 | var oldValue = cx.value; 401 | var newValue = cx.zway_getValueFromVDev(vdev); 402 | if(oldValue !== newValue){ 403 | cx.value = newValue; 404 | cx.emit('change', { oldValue:oldValue, newValue:cx.value, context:null }); 405 | debug("Updated characteristic " + cx.displayName + " on " + vdev.metrics.title); 406 | } else { 407 | cx.emit('update', { oldTimestamp:vdev.updateTime, newTimestamp: upd.updateTime }); 408 | //debug("Characteristic " + cx.displayName + " on " + vdev.metrics.title + " showed timestamp update without value change."); 409 | } 410 | } 411 | vdev.updateTime = upd.updateTime; 412 | } 413 | } 414 | } 415 | } 416 | 417 | } 418 | 419 | function ZWayServerAccessory(name, devDesc, platform) { 420 | // device info 421 | this.name = name; 422 | this.devDesc = devDesc; 423 | this.platform = platform; 424 | this.log = platform.log; 425 | } 426 | 427 | 428 | ZWayServerAccessory.prototype = { 429 | 430 | getVDev: function(vdev){ 431 | return this.platform.zwayRequest({ 432 | method: "GET", 433 | url: this.platform.url + 'ZAutomation/api/v1/devices/' + vdev.id 434 | })//.then(function()); 435 | } 436 | , 437 | command: function(vdev, command, value) { 438 | return this.platform.zwayRequest({ 439 | method: "GET", 440 | url: this.platform.url + 'ZAutomation/api/v1/devices/' + vdev.id + '/command/' + command, 441 | qs: (value === undefined ? undefined : value) 442 | }); 443 | } 444 | , 445 | isInterlockOn: function(){ 446 | return !!this.interlock && !!this.interlock.value; 447 | } 448 | , 449 | rgb2hsv: function(obj) { 450 | // RGB: 0-255; H: 0-360, S,V: 0-100 451 | var r = obj.r/255, g = obj.g/255, b = obj.b/255; 452 | var max, min, d, h, s, v; 453 | 454 | min = Math.min(r, Math.min(g, b)); 455 | max = Math.max(r, Math.max(g, b)); 456 | 457 | if (min === max) { 458 | // shade of gray 459 | return {h: 0, s: 0, v: r * 100}; 460 | } 461 | 462 | var d = (r === min) ? g - b : ((b === min) ? r - g : b - r); 463 | h = (r === min) ? 3 : ((b === min) ? 1 : 5); 464 | h = 60 * (h - d/(max - min)); 465 | s = (max - min) / max; 466 | v = max; 467 | return {"h": h, "s": s * 100, "v": v * 100}; 468 | } 469 | , 470 | hsv2rgb: function(obj) { 471 | // H: 0-360; S,V: 0-100; RGB: 0-255 472 | var r, g, b; 473 | var sfrac = obj.s / 100; 474 | var vfrac = obj.v / 100; 475 | 476 | if(sfrac === 0){ 477 | var vbyte = Math.round(vfrac*255); 478 | return { r: vbyte, g: vbyte, b: vbyte }; 479 | } 480 | 481 | var hdb60 = (obj.h % 360) / 60; 482 | var sector = Math.floor(hdb60); 483 | var fpart = hdb60 - sector; 484 | var c = vfrac * (1 - sfrac); 485 | var x1 = vfrac * (1 - sfrac * fpart); 486 | var x2 = vfrac * (1 - sfrac * (1 - fpart)); 487 | switch(sector){ 488 | case 0: 489 | r = vfrac; g = x2; b = c; break; 490 | case 1: 491 | r = x1; g = vfrac; b = c; break; 492 | case 2: 493 | r = c; g = vfrac; b = x2; break; 494 | case 3: 495 | r = c; g = x1; b = vfrac; break; 496 | case 4: 497 | r = x2; g = c; b = vfrac; break; 498 | case 5: 499 | default: 500 | r = vfrac; g = c; b = x1; break; 501 | } 502 | 503 | return { "r": Math.round(255 * r), "g": Math.round(255 * g), "b": Math.round(255 * b) }; 504 | } 505 | , 506 | getVDevServices: function(vdev){ 507 | var typeKey = ZWayServerPlatform.getVDevTypeKey(vdev); 508 | //TODO: Make a second pass through the below logic with the root typeKey, but 509 | // only allow it to be used if Service.Type tag is set, at a minimum...dangerous! 510 | var services = [], service; 511 | switch (typeKey) { 512 | case "thermostat": 513 | services.push(new Service.Thermostat(vdev.metrics.title, vdev.id)); 514 | break; 515 | case "switchBinary": 516 | if(this.platform.getTagValue(vdev, "Service.Type") === "Lightbulb"){ 517 | services.push(new Service.Lightbulb(vdev.metrics.title, vdev.id)); 518 | } else if(this.platform.getTagValue(vdev, "Service.Type") === "Outlet"){ 519 | services.push(new Service.Outlet(vdev.metrics.title, vdev.id)); 520 | } else if(this.platform.getTagValue(vdev, "Service.Type") === "WindowCovering"){ 521 | services.push(new Service.WindowCovering(vdev.metrics.title, vdev.id)); 522 | } else { 523 | services.push(new Service.Switch(vdev.metrics.title, vdev.id)); 524 | } 525 | break; 526 | case "switchRGBW": 527 | case "switchMultilevel": 528 | var stype = this.platform.getTagValue(vdev, "Service.Type"); 529 | if(stype === "Switch"){ 530 | services.push(new Service.Switch(vdev.metrics.title, vdev.id)); 531 | } else if(stype === "WindowCovering"){ 532 | services.push(new Service.WindowCovering(vdev.metrics.title, vdev.id)); 533 | } else if (stype === "Fan") { 534 | services.push(new Service.Fan(vdev.metrics.title, vdev.id)); 535 | } else { 536 | services.push(new Service.Lightbulb(vdev.metrics.title, vdev.id)); 537 | } 538 | break; 539 | case "switchMultilevel.blind": 540 | services.push(new Service.WindowCovering(vdev.metrics.title, vdev.id)); 541 | break; 542 | case "sensorBinary.Door/Window": 543 | case "sensorBinary.alarm_door": 544 | var stype = this.platform.getTagValue(vdev, "Service.Type"); 545 | if(stype === "ContactSensor"){ 546 | services.push(new Service.ContactSensor(vdev.metrics.title, vdev.id)); 547 | } else if(stype === "GarageDoorOpener"){ 548 | services.push(new Service.GarageDoorOpener(vdev.metrics.title, vdev.id)); 549 | } else if(stype === "Window"){ 550 | services.push(new Service.Window(vdev.metrics.title, vdev.id)); 551 | } else { 552 | services.push(new Service.Door(vdev.metrics.title, vdev.id)); 553 | } 554 | break; 555 | case "sensorBinary.alarm_smoke": 556 | services.push(new Service.SmokeSensor(vdev.metrics.title, vdev.id)); 557 | break; 558 | case "sensorMultilevel.Temperature": 559 | services.push(new Service.TemperatureSensor(vdev.metrics.title, vdev.id)); 560 | break; 561 | case "sensorMultilevel.Humidity": 562 | services.push(new Service.HumiditySensor(vdev.metrics.title, vdev.id)); 563 | break; 564 | case "battery.Battery": 565 | services.push(new Service.BatteryService(vdev.metrics.title, vdev.id)); 566 | break; 567 | case "sensorMultilevel.Luminiscence": 568 | services.push(new Service.LightSensor(vdev.metrics.title, vdev.id)); 569 | break; 570 | case "sensorBinary": 571 | case "sensorBinary.General purpose": 572 | var stype = this.platform.getTagValue(vdev, "Service.Type"); 573 | if(stype === "MotionSensor"){ 574 | services.push(new Service.MotionSensor(vdev.metrics.title, vdev.id)); 575 | } else if(stype === "LeakSensor") { 576 | services.push(new Service.LeakSensor(vdev.metrics.title, vdev.id)); 577 | } else { 578 | services.push(new Service.ContactSensor(vdev.metrics.title, vdev.id)); 579 | } 580 | break; 581 | case "sensorBinary.alarmSensor_flood": 582 | services.push(new Service.LeakSensor(vdev.metrics.title, vdev.id)); 583 | break; 584 | case "doorlock": 585 | services.push(new Service.LockMechanism(vdev.metrics.title, vdev.id)); 586 | break; 587 | case "sensorMultilevel.meterElectric_watt": 588 | services.push(new Service.Outlet(vdev.metrics.title, vdev.id)); 589 | break; 590 | case "toggleButton": 591 | var stype = this.platform.getTagValue(vdev, "Service.Type"); 592 | if(stype === "StatefulProgrammableSwitch"){ 593 | services.push(new Service.StatefulProgrammableSwitch(vdev.metrics.title, vdev.id)); 594 | } else { 595 | services.push(new Service.StatelessProgrammableSwitch(vdev.metrics.title, vdev.id)); 596 | } 597 | break; 598 | } 599 | 600 | var validServices =[]; 601 | for(var i = 0; i < services.length; i++){ 602 | if(this.configureService(services[i], vdev)){ 603 | validServices.push(services[i]); 604 | debug('Found and configured Service "' + ZWayServerPlatform.ServiceUUIDReverseLookupMap[services[i].UUID] + '" for vdev "' + vdev.id + '" with typeKey "' + typeKey + '"') 605 | } else { 606 | debug('WARN: Failed to configure Service "' + ZWayServerPlatform.ServiceUUIDReverseLookupMap[services[i].UUID] + '" for vdev "' + vdev.id + '" with typeKey "' + typeKey + '"') 607 | } 608 | } 609 | return validServices; 610 | } 611 | , 612 | uuidToTypeKeyMap: null 613 | , 614 | extraCharacteristicsMap: { 615 | // moved to module.exports where Characteristic prototype is available... 616 | } 617 | , 618 | getVDevForCharacteristic: function(cx, vdevPreferred){ 619 | 620 | // If we know which vdev should be used for this Characteristic, we're done! 621 | if(this.devDesc.cxmap[cx.UUID] !== undefined){ 622 | return this.devDesc.devices[this.devDesc.cxmap[cx.UUID]]; 623 | } 624 | 625 | var map = this.uuidToTypeKeyMap; 626 | if(!map){ 627 | this.uuidToTypeKeyMap = map = {}; 628 | map[(new Characteristic.On).UUID] = ["switchBinary","switchMultilevel"]; 629 | map[(new Characteristic.OutletInUse).UUID] = ["sensorMultilevel.meterElectric_watt","switchBinary"]; 630 | map[(new Characteristic.Brightness).UUID] = ["switchMultilevel"]; 631 | map[(new Characteristic.RotationSpeed).UUID] = ["switchMultilevel"]; 632 | map[(new Characteristic.Hue).UUID] = ["switchRGBW"]; 633 | map[(new Characteristic.Saturation).UUID] = ["switchRGBW"]; 634 | map[(new Characteristic.CurrentTemperature).UUID] = ["sensorMultilevel.Temperature","thermostat"]; 635 | map[(new Characteristic.CurrentRelativeHumidity).UUID] = ["sensorMultilevel.Humidity"]; 636 | map[(new Characteristic.TargetTemperature).UUID] = ["thermostat"]; 637 | map[(new Characteristic.TemperatureDisplayUnits).UUID] = ["sensorMultilevel.Temperature","thermostat"]; //TODO: Always a fixed result 638 | map[(new Characteristic.CurrentHeatingCoolingState).UUID] = ["thermostat"]; //TODO: Always a fixed result 639 | map[(new Characteristic.TargetHeatingCoolingState).UUID] = ["thermostat"]; //TODO: Always a fixed result 640 | map[(new Characteristic.CurrentDoorState).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; 641 | map[(new Characteristic.TargetDoorState).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; //TODO: Always a fixed result 642 | map[(new Characteristic.ContactSensorState).UUID] = ["sensorBinary","sensorBinary.Door/Window"]; //NOTE: A root before a full...what we want? 643 | map[(new Characteristic.LeakDetected).UUID] = ["sensorBinary.alarmSensor_flood","sensorBinary.General purpose","sensorBinary"]; 644 | map[(new Characteristic.CurrentPosition).UUID] = ["sensorBinary.Door/Window","switchMultilevel.blind","switchBinary.motor","sensorBinary","switchMultilevel","switchBinary"]; // NOTE: switchBinary.motor may not exist...guessing? 645 | map[(new Characteristic.TargetPosition).UUID] = ["sensorBinary.Door/Window","switchMultilevel.blind","switchBinary.motor","sensorBinary","switchMultilevel","switchBinary"]; // NOTE: switchBinary.motor may not exist...guessing? 646 | map[(new Characteristic.PositionState).UUID] = ["sensorBinary.Door/Window","switchMultilevel.blind","switchBinary.motor","sensorBinary","switchMultilevel","switchBinary"]; // NOTE: switchBinary.motor may not exist...guessing? 647 | map[(new Characteristic.HoldPosition).UUID] = ["switchMultilevel.blind","switchBinary.motor","switchMultilevel"]; // NOTE: switchBinary.motor may not exist...guessing? 648 | map[(new Characteristic.ObstructionDetected).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; //TODO: Always a fixed result 649 | map[(new Characteristic.SmokeDetected).UUID] = ["sensorBinary.alarm_smoke","sensorBinary.alarm_heat"]; 650 | map[(new Characteristic.BatteryLevel).UUID] = ["battery.Battery"]; 651 | map[(new Characteristic.StatusLowBattery).UUID] = ["battery.Battery"]; 652 | map[(new Characteristic.ChargingState).UUID] = ["battery.Battery"]; //TODO: Always a fixed result 653 | map[(new Characteristic.CurrentAmbientLightLevel).UUID] = ["sensorMultilevel.Luminiscence"]; 654 | map[(new Characteristic.LockCurrentState).UUID] = ["doorlock"]; 655 | map[(new Characteristic.LockTargetState).UUID] = ["doorlock"]; 656 | map[(new Characteristic.StatusTampered).UUID] = ["sensorBinary.Tamper"]; 657 | map[(new Characteristic.ProgrammableSwitchEvent).UUID] = ["toggleButton"]; 658 | map[(new Characteristic.ProgrammableSwitchOutputState).UUID] = ["toggleButton"]; 659 | map[(new ZWayServerPlatform.CurrentPowerConsumption).UUID] = ["sensorMultilevel.meterElectric_watt"]; 660 | map[(new ZWayServerPlatform.TotalPowerConsumption).UUID] = ["sensorMultilevel.meterElectric_kilowatt_per_hour"]; 661 | } 662 | 663 | if(cx instanceof Characteristic.Name) return vdevPreferred; 664 | 665 | // Special cases! Ignore the preferred device when... 666 | // If cx is a CurrentTemperature, we want the sensor if available. 667 | if(cx instanceof Characteristic.CurrentTemperature) vdevPreferred = null; 668 | // If cx is OutletInUse, we want the power meter if available over the switch. 669 | if(cx instanceof Characteristic.OutletInUse) vdevPreferred = null; 670 | // 671 | 672 | var typekeys = map[cx.UUID]; 673 | if(typekeys === undefined) return null; 674 | 675 | //NOTE: We do NOT want to try the root key here, because there may be a better 676 | // match in another VDev...the preference doesn't extend to non-optimal matches. 677 | if(vdevPreferred && typekeys.indexOf(ZWayServerPlatform.getVDevTypeKey(vdevPreferred)) >= 0){ 678 | return vdevPreferred; 679 | } 680 | 681 | var candidates = this.devDesc.devices; 682 | for(var i = 0; i < typekeys.length; i++){ 683 | for(var j = 0; j < candidates.length; j++){ 684 | if(ZWayServerPlatform.getVDevTypeKey(candidates[j]) === typekeys[i]) return candidates[j]; 685 | // Also try the "root" key, e.g. sensorBinary vs. sensorBinary.general_purpose ... 686 | if(ZWayServerPlatform.getVDevTypeKeyRoot(candidates[j]) === typekeys[i]) return candidates[j]; 687 | } 688 | } 689 | return null; 690 | } 691 | , 692 | configureCharacteristic: function(cx, vdev, service){ 693 | var accessory = this; 694 | 695 | // Add this combination to the maps... 696 | if(!this.platform.cxVDevMap[vdev.id]) this.platform.cxVDevMap[vdev.id] = []; 697 | this.platform.cxVDevMap[vdev.id].push(cx); 698 | if(!this.platform.vDevStore[vdev.id]) this.platform.vDevStore[vdev.id] = vdev; 699 | 700 | var interlock = function(fnDownstream){ 701 | return function(newval, callback){ 702 | if(this.isInterlockOn()){ 703 | callback(new Error("Interlock is on! Changes locked out!")); 704 | } else { 705 | fnDownstream(newval, callback); 706 | } 707 | }.bind(accessory); 708 | }; 709 | 710 | if(cx instanceof Characteristic.Name){ 711 | cx.zway_getValueFromVDev = function(vdev){ 712 | return vdev.metrics.title; 713 | }; 714 | cx.value = cx.zway_getValueFromVDev(vdev); 715 | cx.on('get', function(callback, context){ 716 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 717 | callback(false, accessory.name); 718 | }); 719 | return cx; 720 | } 721 | 722 | // We don't want to override "Name"'s name...so we just move this below that block. 723 | var descOverride = this.platform.getTagValue(vdev, "Characteristic.Description"); 724 | if(descOverride){ 725 | cx.displayName = descOverride; 726 | } 727 | 728 | if(cx instanceof Characteristic.On){ 729 | cx.zway_getValueFromVDev = function(vdev){ 730 | var val = false; 731 | if(vdev.metrics.level === "on"){ 732 | val = true; 733 | } else if(vdev.metrics.level <= accessory.platform.dimmerOffThreshold) { 734 | val = false; 735 | } else if (vdev.metrics.level > accessory.platform.dimmerOffThreshold) { 736 | val = true; 737 | } 738 | return val; 739 | }; 740 | cx.value = cx.zway_getValueFromVDev(vdev); 741 | cx.on('get', function(callback, context){ 742 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 743 | this.getVDev(vdev).then(function(result){ 744 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 745 | callback(false, cx.zway_getValueFromVDev(result.data)); 746 | }); 747 | }.bind(this)); 748 | cx.on('set', interlock(function(powerOn, callback){ 749 | this.command(vdev, powerOn ? "on" : "off").then(function(result){ 750 | callback(); 751 | }); 752 | }.bind(this))); 753 | cx.on('change', function(ev){ 754 | debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); 755 | }); 756 | return cx; 757 | } 758 | 759 | if(cx instanceof Characteristic.OutletInUse){ 760 | cx.zway_getValueFromVDev = function(vdev){ 761 | var val = false; 762 | if(vdev.metrics.level === "on"){ 763 | val = true; 764 | } else if(vdev.metrics.level === "off") { 765 | val = false; 766 | } else if (vdev.deviceType === "sensorMultilevel") { 767 | var t = this.platform.getTagValue(vdev, "OutletInUse.Level") || this.platform.OIUWatts; 768 | if(vdev.metrics.level >= t){ 769 | val = true; 770 | } else { 771 | val = false; 772 | } 773 | } else { 774 | val = false; 775 | } 776 | return val; 777 | }.bind(this); 778 | cx.value = cx.zway_getValueFromVDev(vdev); 779 | cx.on('get', function(callback, context){ 780 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 781 | this.getVDev(vdev).then(function(result){ 782 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 783 | callback(false, cx.zway_getValueFromVDev(result.data)); 784 | }); 785 | }.bind(this)); 786 | cx.on('change', function(ev){ 787 | debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); 788 | }); 789 | return cx; 790 | } 791 | 792 | if(cx instanceof Characteristic.Brightness || cx instanceof Characteristic.RotationSpeed){ 793 | cx.zway_getValueFromVDev = function(vdev){ 794 | return vdev.metrics.level; 795 | }; 796 | cx.value = cx.zway_getValueFromVDev(vdev); 797 | cx.on('get', function(callback, context){ 798 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 799 | this.getVDev(vdev).then(function(result){ 800 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 801 | callback(false, cx.zway_getValueFromVDev(result.data)); 802 | }); 803 | }.bind(this)); 804 | cx.on('set', interlock(function(level, callback){ 805 | this.command(vdev, "exact", {level: parseInt(level, 10)}).then(function(result){ 806 | callback(); 807 | }); 808 | }.bind(this))); 809 | return cx; 810 | } 811 | 812 | if(cx instanceof Characteristic.Hue){ 813 | cx.zway_getValueFromVDev = function(vdev){ 814 | debug("Derived value " + accessory.rgb2hsv(vdev.metrics.color).h + " for hue."); 815 | return accessory.rgb2hsv(vdev.metrics.color).h; 816 | }; 817 | cx.value = cx.zway_getValueFromVDev(vdev); 818 | cx.on('get', function(callback, context){ 819 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 820 | this.getVDev(vdev).then(function(result){ 821 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 822 | callback(false, cx.zway_getValueFromVDev(result.data)); 823 | }); 824 | }.bind(this)); 825 | cx.on('set', interlock(function(hue, callback){ 826 | var scx = service.getCharacteristic(Characteristic.Saturation); 827 | var vcx = service.getCharacteristic(Characteristic.Brightness); 828 | if(!scx || !vcx){ 829 | debug("Hue without Saturation and Brightness is not supported! Cannot set value!") 830 | callback(true, cx.value); 831 | } 832 | var rgb = this.hsv2rgb({ h: hue, s: scx.value, v: vcx.value }); 833 | this.command(vdev, "exact", { red: rgb.r, green: rgb.g, blue: rgb.b }).then(function(result){ 834 | callback(); 835 | }); 836 | }.bind(this))); 837 | 838 | return cx; 839 | } 840 | 841 | if(cx instanceof Characteristic.Saturation){ 842 | cx.zway_getValueFromVDev = function(vdev){ 843 | debug("Derived value " + accessory.rgb2hsv(vdev.metrics.color).s + " for saturation."); 844 | return accessory.rgb2hsv(vdev.metrics.color).s; 845 | }; 846 | cx.value = cx.zway_getValueFromVDev(vdev); 847 | cx.on('get', function(callback, context){ 848 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 849 | this.getVDev(vdev).then(function(result){ 850 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 851 | callback(false, cx.zway_getValueFromVDev(result.data)); 852 | }); 853 | }.bind(this)); 854 | cx.on('set', interlock(function(saturation, callback){ 855 | var hcx = service.getCharacteristic(Characteristic.Hue); 856 | var vcx = service.getCharacteristic(Characteristic.Brightness); 857 | if(!hcx || !vcx){ 858 | debug("Saturation without Hue and Brightness is not supported! Cannot set value!") 859 | callback(true, cx.value); 860 | } 861 | var rgb = this.hsv2rgb({ h: hcx.value, s: saturation, v: vcx.value }); 862 | this.command(vdev, "exact", { red: rgb.r, green: rgb.g, blue: rgb.b }).then(function(result){ 863 | callback(); 864 | }); 865 | }.bind(this))); 866 | 867 | return cx; 868 | } 869 | 870 | if(cx instanceof Characteristic.SmokeDetected){ 871 | cx.zway_getValueFromVDev = function(vdev){ 872 | return vdev.metrics.level == "on" ? Characteristic.SmokeDetected.SMOKE_DETECTED : Characteristic.SmokeDetected.SMOKE_NOT_DETECTED; 873 | }; 874 | cx.value = cx.zway_getValueFromVDev(vdev); 875 | cx.on('get', function(callback, context){ 876 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 877 | this.getVDev(vdev).then(function(result){ 878 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 879 | callback(false, cx.zway_getValueFromVDev(result.data)); 880 | }); 881 | }.bind(this)); 882 | cx.on('update', function(ev){ 883 | var value = cx.value; 884 | var inverse = value == Characteristic.SmokeDetected.SMOKE_NOT_DETECTED ? Characteristic.SmokeDetected.SMOKE_DETECTED : Characteristic.SmokeDetected.SMOKE_NOT_DETECTED; 885 | cx.emit('change', { oldValue: value, newValue: inverse }); 886 | setTimeout(function(){ cx.emit('change', { oldValue: inverse, newValue: value }); }, accessory.platform.blinkTime); 887 | }); 888 | return cx; 889 | } 890 | 891 | if(cx instanceof Characteristic.CurrentRelativeHumidity){ 892 | cx.zway_getValueFromVDev = function(vdev){ 893 | return vdev.metrics.level; 894 | }; 895 | cx.value = cx.zway_getValueFromVDev(vdev); 896 | cx.on('get', function(callback, context){ 897 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 898 | this.getVDev(vdev).then(function(result){ 899 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 900 | callback(false, cx.zway_getValueFromVDev(result.data)); 901 | }); 902 | }.bind(this)); 903 | cx.setProps({ 904 | minValue: 0, 905 | maxValue: 100 906 | }); 907 | return cx; 908 | } 909 | 910 | if(cx instanceof Characteristic.CurrentTemperature){ 911 | cx.zway_getValueFromVDev = function(vdev){ 912 | return vdev.metrics.level; 913 | }; 914 | cx.value = cx.zway_getValueFromVDev(vdev); 915 | cx.on('get', function(callback, context){ 916 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 917 | this.getVDev(vdev).then(function(result){ 918 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 919 | callback(false, cx.zway_getValueFromVDev(result.data)); 920 | }); 921 | }.bind(this)); 922 | cx.setProps({ 923 | minValue: vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : -40, 924 | maxValue: vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 999 925 | }); 926 | return cx; 927 | } 928 | 929 | if(cx instanceof Characteristic.TargetTemperature){ 930 | cx.zway_getValueFromVDev = function(vdev){ 931 | return vdev.metrics.level; 932 | }; 933 | cx.value = cx.zway_getValueFromVDev(vdev); 934 | cx.on('get', function(callback, context){ 935 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 936 | this.getVDev(vdev).then(function(result){ 937 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 938 | callback(false, cx.zway_getValueFromVDev(result.data)); 939 | }); 940 | }.bind(this)); 941 | cx.on('set', interlock(function(level, callback){ 942 | this.command(vdev, "exact", {level: parseInt(level, 10)}).then(function(result){ 943 | //debug("Got value: " + result.data.metrics.level + ", for " + vdev.metrics.title + "."); 944 | callback(); 945 | }); 946 | }.bind(this))); 947 | cx.setProps({ 948 | minValue: vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : 5, 949 | maxValue: vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 40 950 | }); 951 | return cx; 952 | } 953 | 954 | if(cx instanceof Characteristic.TemperatureDisplayUnits){ 955 | //TODO: Always in °C for now. 956 | cx.zway_getValueFromVDev = function(vdev){ 957 | return Characteristic.TemperatureDisplayUnits.CELSIUS; 958 | }; 959 | cx.value = cx.zway_getValueFromVDev(vdev); 960 | cx.on('get', function(callback, context){ 961 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 962 | callback(false, Characteristic.TemperatureDisplayUnits.CELSIUS); 963 | }); 964 | cx.setProps({ 965 | perms: [Characteristic.Perms.READ] 966 | }); 967 | return cx; 968 | } 969 | 970 | if(cx instanceof Characteristic.CurrentHeatingCoolingState){ 971 | //TODO: Always HEAT for now, we don't have an example to work with that supports another function. 972 | cx.zway_getValueFromVDev = function(vdev){ 973 | return Characteristic.CurrentHeatingCoolingState.HEAT; 974 | }; 975 | cx.value = cx.zway_getValueFromVDev(vdev); 976 | cx.on('get', function(callback, context){ 977 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 978 | callback(false, Characteristic.CurrentHeatingCoolingState.HEAT); 979 | }); 980 | return cx; 981 | } 982 | 983 | if(cx instanceof Characteristic.TargetHeatingCoolingState){ 984 | //TODO: Always HEAT for now, we don't have an example to work with that supports another function. 985 | cx.zway_getValueFromVDev = function(vdev){ 986 | return Characteristic.TargetHeatingCoolingState.HEAT; 987 | }; 988 | cx.value = cx.zway_getValueFromVDev(vdev); 989 | cx.on('get', function(callback, context){ 990 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 991 | callback(false, Characteristic.TargetHeatingCoolingState.HEAT); 992 | }); 993 | // Hmm... apparently if this is not setable, we can't add a thermostat change to a scene. So, make it writable but a no-op. 994 | cx.on('set', interlock(function(newValue, callback){ 995 | debug("WARN: Set of TargetHeatingCoolingState not yet implemented, resetting to HEAT!") 996 | callback(undefined, Characteristic.TargetHeatingCoolingState.HEAT); 997 | }.bind(this))); 998 | return cx; 999 | } 1000 | 1001 | if(cx instanceof Characteristic.CurrentDoorState){ 1002 | cx.zway_getValueFromVDev = function(vdev){ 1003 | return vdev.metrics.level === "off" ? Characteristic.CurrentDoorState.CLOSED : Characteristic.CurrentDoorState.OPEN; 1004 | }; 1005 | cx.value = cx.zway_getValueFromVDev(vdev); 1006 | cx.on('get', function(callback, context){ 1007 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 1008 | this.getVDev(vdev).then(function(result){ 1009 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 1010 | callback(false, cx.zway_getValueFromVDev(result.data)); 1011 | }); 1012 | }.bind(this)); 1013 | cx.on('change', function(ev){ 1014 | debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); 1015 | }); 1016 | cx.on('update', function(ev){ 1017 | var value = cx.value; 1018 | var inverse = value == Characteristic.CurrentDoorState.CLOSED ? Characteristic.CurrentDoorState.OPEN : Characteristic.CurrentDoorState.CLOSED; 1019 | cx.emit('change', { oldValue: value, newValue: inverse }); 1020 | setTimeout(function(){ cx.emit('change', { oldValue: inverse, newValue: value }); }, accessory.platform.blinkTime); 1021 | }); 1022 | return cx; 1023 | } 1024 | 1025 | if(cx instanceof Characteristic.TargetDoorState){ 1026 | //TODO: We only support this for Door sensors now, so it's a fixed value. 1027 | cx.zway_getValueFromVDev = function(vdev){ 1028 | return Characteristic.TargetDoorState.CLOSED; 1029 | }; 1030 | cx.value = cx.zway_getValueFromVDev(vdev); 1031 | cx.on('get', function(callback, context){ 1032 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 1033 | callback(false, Characteristic.TargetDoorState.CLOSED); 1034 | }); 1035 | cx.setProps({ 1036 | perms: [Characteristic.Perms.READ] 1037 | }); 1038 | return cx; 1039 | } 1040 | 1041 | if(cx instanceof Characteristic.ObstructionDetected){ 1042 | //TODO: We only support this for Door sensors now, so it's a fixed value. 1043 | cx.zway_getValueFromVDev = function(vdev){ 1044 | return false; 1045 | }; 1046 | cx.value = cx.zway_getValueFromVDev(vdev); 1047 | cx.on('get', function(callback, context){ 1048 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 1049 | callback(false, false); 1050 | }); 1051 | return cx; 1052 | } 1053 | 1054 | if(cx instanceof Characteristic.BatteryLevel){ 1055 | cx.zway_getValueFromVDev = function(vdev){ 1056 | return vdev.metrics.level; 1057 | }; 1058 | cx.value = cx.zway_getValueFromVDev(vdev); 1059 | cx.on('get', function(callback, context){ 1060 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 1061 | this.getVDev(vdev).then(function(result){ 1062 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 1063 | callback(false, cx.zway_getValueFromVDev(result.data)); 1064 | }); 1065 | }.bind(this)); 1066 | return cx; 1067 | } 1068 | 1069 | if(cx instanceof Characteristic.StatusLowBattery){ 1070 | cx.zway_getValueFromVDev = function(vdev){ 1071 | return vdev.metrics.level <= accessory.platform.batteryLow ? Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; 1072 | }; 1073 | cx.value = cx.zway_getValueFromVDev(vdev); 1074 | cx.on('get', function(callback, context){ 1075 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 1076 | this.getVDev(vdev).then(function(result){ 1077 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 1078 | callback(false, cx.zway_getValueFromVDev(result.data)); 1079 | }); 1080 | }.bind(this)); 1081 | return cx; 1082 | } 1083 | 1084 | if(cx instanceof Characteristic.ChargingState){ 1085 | //TODO: No known chargeable devices(?), so always return false. 1086 | cx.zway_getValueFromVDev = function(vdev){ 1087 | return Characteristic.ChargingState.NOT_CHARGING; 1088 | }; 1089 | cx.value = cx.zway_getValueFromVDev(vdev); 1090 | cx.on('get', function(callback, context){ 1091 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 1092 | callback(false, Characteristic.ChargingState.NOT_CHARGING); 1093 | }); 1094 | return cx; 1095 | } 1096 | 1097 | if(cx instanceof Characteristic.CurrentAmbientLightLevel){ 1098 | cx.zway_getValueFromVDev = function(vdev){ 1099 | if(vdev.metrics.scaleTitle === "%"){ 1100 | // Completely unscientific guess, based on test-fit data and Wikipedia real-world lux values. 1101 | // This will probably change! 1102 | var lux = 0.0005 * (vdev.metrics.level^3.6); 1103 | // Bounds checking now done upstream! 1104 | //if(lux < cx.minimumValue) return cx.minimumValue; if(lux > cx.maximumValue) return cx.maximumValue; 1105 | return lux; 1106 | } else { 1107 | return vdev.metrics.level; 1108 | } 1109 | }; 1110 | cx.value = cx.zway_getValueFromVDev(vdev); 1111 | cx.on('get', function(callback, context){ 1112 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 1113 | this.getVDev(vdev).then(function(result){ 1114 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 1115 | callback(false, cx.zway_getValueFromVDev(result.data)); 1116 | }); 1117 | }.bind(this)); 1118 | cx.on('change', function(ev){ 1119 | debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); 1120 | }); 1121 | return cx; 1122 | } 1123 | 1124 | if(cx instanceof Characteristic.MotionDetected){ 1125 | cx.zway_getValueFromVDev = function(vdev){ 1126 | return vdev.metrics.level === "off" ? false : true; 1127 | }; 1128 | cx.value = cx.zway_getValueFromVDev(vdev); 1129 | cx.on('get', function(callback, context){ 1130 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 1131 | this.getVDev(vdev).then(function(result){ 1132 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 1133 | callback(false, cx.zway_getValueFromVDev(result.data)); 1134 | }); 1135 | }.bind(this)); 1136 | cx.on('change', function(ev){ 1137 | debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); 1138 | }); 1139 | cx.on('update', function(ev){ 1140 | var value = cx.value; 1141 | var inverse = !value; 1142 | cx.emit('change', { oldValue: value, newValue: inverse }); 1143 | setTimeout(function(){ cx.emit('change', { oldValue: inverse, newValue: value }); }, accessory.platform.blinkTime); 1144 | }); 1145 | return cx; 1146 | } 1147 | 1148 | if(cx instanceof Characteristic.StatusTampered){ 1149 | cx.zway_getValueFromVDev = function(vdev){ 1150 | return vdev.metrics.level === "off" ? Characteristic.StatusTampered.NOT_TAMPERED : Characteristic.StatusTampered.TAMPERED; 1151 | }; 1152 | cx.value = cx.zway_getValueFromVDev(vdev); 1153 | cx.on('get', function(callback, context){ 1154 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 1155 | this.getVDev(vdev).then(function(result){ 1156 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 1157 | callback(false, cx.zway_getValueFromVDev(result.data)); 1158 | }); 1159 | }.bind(this)); 1160 | cx.on('change', function(ev){ 1161 | debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); 1162 | }); 1163 | cx.on('update', function(ev){ 1164 | var value = cx.value; 1165 | var inverse = value == Characteristic.StatusTampered.NOT_TAMPERED ? Characteristic.StatusTampered.TAMPERED : Characteristic.StatusTampered.NOT_TAMPERED; 1166 | cx.emit('change', { oldValue: value, newValue: inverse }); 1167 | setTimeout(function(){ cx.emit('change', { oldValue: inverse, newValue: value }); }, accessory.platform.blinkTime); 1168 | }); 1169 | return cx; 1170 | } 1171 | 1172 | if(cx instanceof Characteristic.ContactSensorState){ 1173 | cx.zway_getValueFromVDev = function(vdev){ 1174 | var boolval = vdev.metrics.level === "off" ? false : true; 1175 | boolval = accessory.platform.getTagValue(vdev, "ContactSensorState.Invert") ? !boolval : boolval; 1176 | return boolval ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED; 1177 | }; 1178 | cx.value = cx.zway_getValueFromVDev(vdev); 1179 | cx.on('get', function(callback, context){ 1180 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 1181 | this.getVDev(vdev).then(function(result){ 1182 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 1183 | callback(false, cx.zway_getValueFromVDev(result.data)); 1184 | }); 1185 | }.bind(this)); 1186 | cx.on('change', function(ev){ 1187 | debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); 1188 | }); 1189 | cx.on('update', function(ev){ 1190 | var value = cx.value; 1191 | var inverse = value == Characteristic.ContactSensorState.CONTACT_NOT_DETECTED ? Characteristic.ContactSensorState.CONTACT_DETECTED : Characteristic.ContactSensorState.CONTACT_NOT_DETECTED; 1192 | cx.emit('change', { oldValue: value, newValue: inverse }); 1193 | setTimeout(function(){ cx.emit('change', { oldValue: inverse, newValue: value }); }, accessory.platform.blinkTime); 1194 | }); 1195 | return cx; 1196 | } 1197 | 1198 | if(cx instanceof Characteristic.LeakDetected){ 1199 | cx.zway_getValueFromVDev = function(vdev){ 1200 | var boolval = vdev.metrics.level === "off" ? false : true; 1201 | return boolval ? Characteristic.LeakDetected.LEAK_DETECTED : Characteristic.LeakDetected.LEAK_NOT_DETECTED; 1202 | }; 1203 | cx.value = cx.zway_getValueFromVDev(vdev); 1204 | cx.on('get', function(callback, context){ 1205 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 1206 | this.getVDev(vdev).then(function(result){ 1207 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 1208 | callback(false, cx.zway_getValueFromVDev(result.data)); 1209 | }); 1210 | }.bind(this)); 1211 | cx.on('change', function(ev){ 1212 | debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); 1213 | }); 1214 | cx.on('update', function(ev){ 1215 | var value = cx.value; 1216 | var inverse = value == Characteristic.LeakDetected.LEAK_NOT_DETECTED ? Characteristic.LeakDetected.LEAK_DETECTED : Characteristic.LeakDetected.LEAK_NOT_DETECTED; 1217 | cx.emit('change', { oldValue: value, newValue: inverse }); 1218 | setTimeout(function(){ cx.emit('change', { oldValue: inverse, newValue: value }); }, accessory.platform.blinkTime); 1219 | }); 1220 | return cx; 1221 | } 1222 | 1223 | if(cx instanceof Characteristic.CurrentPosition){ 1224 | cx.zway_getValueFromVDev = function(vdev){ 1225 | var level = vdev.metrics.level; 1226 | if(level === undefined) return 0; // Code devices can sometimes have no defined level?? 1227 | if(level == "off") return 0; 1228 | if(level == "on") return 100; 1229 | return level == 99 ? 100 : level; 1230 | }; 1231 | cx.value = cx.zway_getValueFromVDev(vdev); 1232 | cx.on('get', function(callback, context){ 1233 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 1234 | this.getVDev(vdev).then(function(result){ 1235 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 1236 | callback(false, cx.zway_getValueFromVDev(result.data)); 1237 | }); 1238 | }.bind(this)); 1239 | cx.on('change', function(ev){ 1240 | debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); 1241 | }); 1242 | return cx; 1243 | } 1244 | 1245 | if(cx instanceof Characteristic.TargetPosition){ 1246 | cx.zway_getValueFromVDev = function(vdev){ 1247 | if(service instanceof Service.WindowCovering){ 1248 | // Whatever we set it to last...right? 1249 | // NOTE: TargetTemperature doesn't do this...source of feedback issues??? 1250 | if(this.value !== cx.getDefaultValue()) return this.value == 99 ? 100 : this.value; 1251 | // If we haven't set it, figure out what the current state is and assume that was the target... 1252 | var level = vdev.metrics.level; 1253 | if(level === undefined) return 0; // Code devices can sometimes have no defined level?? 1254 | if(level == "off") return 0; 1255 | if(level == "on") return 100; 1256 | return level == 99 ? 100 : level; 1257 | 1258 | } 1259 | // Door or Window sensor, so fixed value... 1260 | return 0; 1261 | }; 1262 | cx.value = cx.zway_getValueFromVDev(vdev); 1263 | cx.on('get', function(callback, context){ 1264 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 1265 | callback(false, cx.zway_getValueFromVDev(vdev)); 1266 | }); 1267 | cx.on('set', interlock(function(level, callback){ 1268 | if(isNaN(vdev.metrics.level)){ 1269 | // ^ Slightly kludgy (but fast) way to figure out if we've got a binary or multilevel device... 1270 | this.command(vdev, level == 0 ? "off" : "on").then(function(result){ 1271 | //debug("Got value: " + result.data.metrics.level + ", for " + vdev.metrics.title + "."); 1272 | callback(false); 1273 | }).catch(function(error){callback(error)}); 1274 | } else { 1275 | // For min and max, send up/down instead of explicit level, see issue #43... 1276 | var promise; 1277 | switch (parseInt(level, 10)) { 1278 | case 0: 1279 | promise = this.command(vdev, "down"); 1280 | break; 1281 | case 99: 1282 | case 100: 1283 | promise = this.command(vdev, "up"); 1284 | break; 1285 | default: 1286 | promise = this.command(vdev, "exact", {level: parseInt(level, 10)}) 1287 | } 1288 | promise.then(function(result){ 1289 | callback(false); 1290 | }).catch(function(error){callback(error)}); 1291 | } 1292 | }.bind(this))); 1293 | cx.setProps({ 1294 | minValue: vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : 0, 1295 | maxValue: vdev.metrics && (vdev.metrics.max !== undefined && vdev.metrics.max != 99) ? vdev.metrics.max : 100 1296 | }); 1297 | return cx; 1298 | } 1299 | 1300 | if(cx instanceof Characteristic.HoldPosition){ 1301 | cx.on('get', function(callback, context){ 1302 | debug("WARN: Getting value for read-only HoldPosition Characteristic on " + vdev.metrics.title + "...should this happen?"); 1303 | callback(false, null); 1304 | }); 1305 | cx.on('set', interlock(function(level, callback){ 1306 | this.command(vdev, "stop").then(function(result){ 1307 | //debug("Got value: " + result.data.metrics.level + ", for " + vdev.metrics.title + "."); 1308 | callback(false); 1309 | }).catch(function(error){callback(error)}); 1310 | }.bind(this))); 1311 | return cx; 1312 | } 1313 | 1314 | if(cx instanceof Characteristic.PositionState){ 1315 | // Always return STOPPED, we don't really get status updates from Z-Way... 1316 | cx.zway_getValueFromVDev = function(vdev){ 1317 | return Characteristic.PositionState.STOPPED; 1318 | }; 1319 | cx.value = cx.zway_getValueFromVDev(vdev); 1320 | cx.on('get', function(callback, context){ 1321 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 1322 | callback(false, cx.zway_getValueFromVDev(vdev)); 1323 | }); 1324 | return cx; 1325 | } 1326 | 1327 | if(cx instanceof Characteristic.LockCurrentState){ 1328 | cx.zway_getValueFromVDev = function(vdev){ 1329 | var val = Characteristic.LockCurrentState.UNKNOWN; 1330 | if(vdev.metrics.level === "open"){ 1331 | val = Characteristic.LockCurrentState.UNSECURED; 1332 | } else if(vdev.metrics.level === "close") { 1333 | val = Characteristic.LockCurrentState.SECURED; 1334 | } 1335 | return val; 1336 | }; 1337 | cx.value = cx.zway_getValueFromVDev(vdev); 1338 | cx.on('get', function(callback, context){ 1339 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 1340 | this.getVDev(vdev).then(function(result){ 1341 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 1342 | callback(false, cx.zway_getValueFromVDev(result.data)); 1343 | }); 1344 | }.bind(this)); 1345 | cx.on('change', function(ev){ 1346 | debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); 1347 | }); 1348 | return cx; 1349 | } 1350 | 1351 | if(cx instanceof Characteristic.LockTargetState){ 1352 | cx.zway_getValueFromVDev = function(vdev){ 1353 | var val = Characteristic.LockTargetState.UNSECURED; 1354 | if(vdev.metrics.level === "open"){ 1355 | val = Characteristic.LockTargetState.UNSECURED; 1356 | } else if(vdev.metrics.level === "closed") { 1357 | val = Characteristic.LockTargetState.SECURED; 1358 | } else if(vdev.metrics.level === "close") { 1359 | val = Characteristic.LockTargetState.SECURED; 1360 | } 1361 | debug("Returning LockTargetState of \"" + val + "\" because vdev.metrics.level returned \"" + vdev.metrics.level + "\""); 1362 | return val; 1363 | }; 1364 | cx.value = cx.zway_getValueFromVDev(vdev); 1365 | cx.on('get', function(callback, context){ 1366 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 1367 | this.getVDev(vdev).then(function(result){ 1368 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 1369 | callback(false, cx.zway_getValueFromVDev(result.data)); 1370 | }); 1371 | }.bind(this)); 1372 | cx.on('set', function(newValue, callback){ 1373 | if(newValue === false){ 1374 | newValue = Characteristic.LockTargetState.UNSECURED; 1375 | } 1376 | this.command(vdev, newValue === Characteristic.LockTargetState.UNSECURED ? "open" : "close").then(function(result){ 1377 | callback(); 1378 | }); 1379 | }.bind(this)); 1380 | cx.on('change', function(ev){ 1381 | debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); 1382 | }); 1383 | return cx; 1384 | } 1385 | 1386 | if(cx instanceof ZWayServerPlatform.CurrentPowerConsumption){ 1387 | cx.zway_getValueFromVDev = function(vdev){ 1388 | // Supposedly units are 0.1W, but by experience it's simply Watts ...? 1389 | return Math.round(vdev.metrics.level); 1390 | }; 1391 | cx.value = cx.zway_getValueFromVDev(vdev); 1392 | cx.on('get', function(callback, context){ 1393 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 1394 | this.getVDev(vdev).then(function(result){ 1395 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 1396 | callback(false, cx.zway_getValueFromVDev(result.data)); 1397 | }); 1398 | }.bind(this)); 1399 | cx.on('change', function(ev){ 1400 | debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); 1401 | }); 1402 | return cx; 1403 | } 1404 | 1405 | if(cx instanceof ZWayServerPlatform.TotalPowerConsumption){ 1406 | cx.zway_getValueFromVDev = function(vdev){ 1407 | // Supposedly units are 0.001kWh, but by experience it's simply kWh ...? 1408 | return Math.round(vdev.metrics.level*1000.0)/1000.0; //Math.round(vdev.metrics.level); 1409 | }; 1410 | cx.value = cx.zway_getValueFromVDev(vdev); 1411 | cx.on('get', function(callback, context){ 1412 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 1413 | this.getVDev(vdev).then(function(result){ 1414 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 1415 | callback(false, cx.zway_getValueFromVDev(result.data)); 1416 | }); 1417 | }.bind(this)); 1418 | cx.on('change', function(ev){ 1419 | debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); 1420 | }); 1421 | return cx; 1422 | } 1423 | 1424 | if(cx instanceof Characteristic.ProgrammableSwitchEvent){ 1425 | cx.zway_getValueFromVDev = function(vdev){ 1426 | return vdev.metrics.homebridge_level || 0; 1427 | }; 1428 | cx.zway_setValueOnVDev = function(value){ 1429 | if(value > cx.props['maxValue']) value = cx.props['minValue']; 1430 | vdev.metrics.homebridge_level = value; 1431 | return value; 1432 | }; 1433 | cx.value = cx.zway_getValueFromVDev(vdev); 1434 | cx.on('get', function(callback, context){ 1435 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 1436 | this.getVDev(vdev).then(function(result){ 1437 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 1438 | callback(false, cx.zway_getValueFromVDev(result.data)); 1439 | }); 1440 | }.bind(this)); 1441 | cx.on('change', function(ev){ 1442 | debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); 1443 | }); 1444 | cx.on('update', function(ev){ 1445 | cx.emit('change', { oldValue: cx.value, newValue: cx.value = cx.zway_setValueOnVDev(cx.value + 1)}); 1446 | if(service.UUID === Service.StatelessProgrammableSwitch.UUID) 1447 | setTimeout(function(){ 1448 | cx.emit('change', { 1449 | oldValue: cx.value, 1450 | newValue: cx.value = cx.zway_setValueOnVDev(cx.props['minValue']) 1451 | }); 1452 | }, accessory.platform.blinkTime); 1453 | }); 1454 | return cx; 1455 | } 1456 | 1457 | if(cx instanceof Characteristic.ProgrammableSwitchOutputState){ 1458 | cx.zway_getValueFromVDev = function(vdev){ 1459 | return vdev.metrics.homebridge_level || 0; 1460 | }; 1461 | cx.zway_setValueOnVDev = function(value){ 1462 | if(value > cx.props['maxValue']) value = cx.props['minValue']; 1463 | vdev.metrics.homebridge_level = value; 1464 | return value; 1465 | }; 1466 | cx.value = cx.zway_getValueFromVDev(vdev); 1467 | cx.on('get', function(callback, context){ 1468 | debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); 1469 | this.getVDev(vdev).then(function(result){ 1470 | debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); 1471 | callback(false, cx.zway_getValueFromVDev(result.data)); 1472 | }); 1473 | }.bind(this)); 1474 | cx.on('set', function(newValue, callback){ 1475 | zway_setValueOnVDev(newValue); 1476 | callback(); 1477 | }.bind(this)); 1478 | cx.on('change', function(ev){ 1479 | debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); 1480 | }); 1481 | return cx; 1482 | } 1483 | 1484 | } 1485 | , 1486 | configureService: function(service, vdev){ 1487 | var success = true; 1488 | for(var i = 0; i < service.characteristics.length; i++){ 1489 | var cx = service.characteristics[i]; 1490 | var vdev = this.getVDevForCharacteristic(cx, vdev); 1491 | if(!vdev){ 1492 | success = false; 1493 | debug("ERROR! Failed to configure required characteristic \"" + service.characteristics[i].displayName + "\"!"); 1494 | return false; // Can't configure this service, don't add it! 1495 | } 1496 | cx = this.configureCharacteristic(cx, vdev, service); 1497 | debug('Configured Characteristic "' + cx.displayName + '" for vdev "' + vdev.id + '"') 1498 | } 1499 | 1500 | // Special case: for Outlet, we want to add Eve consumption cx's as optional... 1501 | if(service instanceof Service.Outlet){ 1502 | service.addOptionalCharacteristic(ZWayServerPlatform.CurrentPowerConsumption); 1503 | service.addOptionalCharacteristic(ZWayServerPlatform.TotalPowerConsumption); 1504 | } 1505 | 1506 | for(var i = 0; i < service.optionalCharacteristics.length; i++){ 1507 | var cx = service.optionalCharacteristics[i]; 1508 | var vdev = this.getVDevForCharacteristic(cx, vdev); 1509 | if(!vdev) continue; 1510 | 1511 | //NOTE: Questionable logic, but if the vdev has already been used for the same 1512 | // characteristic type elsewhere, lets not duplicate it just for the sake of an 1513 | // optional characteristic. This eliminates the problem with RGB+W+W bulbs 1514 | // having the HSV controls shown again, but might have unintended consequences... 1515 | var othercx = null, othercxs = this.platform.cxVDevMap[vdev.id]; 1516 | if(othercxs) for(var j = 0; j < othercxs.length; j++) if(othercxs[j].UUID === cx.UUID) othercx = othercxs[j]; 1517 | if(othercx) 1518 | continue; 1519 | 1520 | cx = this.configureCharacteristic(cx, vdev, service); 1521 | try { 1522 | if(cx) service.addCharacteristic(cx); 1523 | debug('Configured Characteristic "' + cx.displayName + '" for vdev "' + vdev.id + '"') 1524 | } 1525 | catch (ex) { 1526 | debug('Adding Characteristic "' + cx.displayName + '" failed with message "' + ex.message + '". This may be expected.'); 1527 | } 1528 | } 1529 | return success; 1530 | } 1531 | , 1532 | getServices: function() { 1533 | var that = this; 1534 | 1535 | var vdevPrimary = this.devDesc.devices[this.devDesc.primary]; 1536 | var accId = this.platform.getTagValue(vdevPrimary, "Accessory.Id"); 1537 | if(!accId){ 1538 | accId = "VDev-" + vdevPrimary.h; //FIXME: Is this valid? 1539 | } 1540 | 1541 | var informationService = new Service.AccessoryInformation(); 1542 | 1543 | informationService 1544 | .setCharacteristic(Characteristic.Name, this.name) 1545 | .setCharacteristic(Characteristic.Manufacturer, "Z-Wave.me") 1546 | .setCharacteristic(Characteristic.Model, "Virtual Device (VDev version 1)") 1547 | .setCharacteristic(Characteristic.SerialNumber, accId); 1548 | 1549 | var services = [informationService]; 1550 | 1551 | services = services.concat(this.getVDevServices(vdevPrimary)); 1552 | if(services.length === 1){ 1553 | debug("WARN: Only the InformationService was successfully configured for " + vdevPrimary.id + "! No device services available!"); 1554 | return services; 1555 | } 1556 | 1557 | // Interlock specified? Create an interlock control switch... 1558 | if(this.platform.getTagValue(vdevPrimary, "Interlock") && services.length > 1){ 1559 | var ilsvc = new Service.Switch("Interlock", vdevPrimary.id + "_interlock"); 1560 | ilsvc.setCharacteristic(Characteristic.Name, "Interlock"); 1561 | 1562 | var ilcx = ilsvc.getCharacteristic(Characteristic.On); 1563 | ilcx.value = false; // Going to set this true in a minute... 1564 | ilcx.on('change', function(ev){ 1565 | debug("Interlock for device " + vdevPrimary.metrics.title + " changed from " + ev.oldValue + " to " + ev.newValue + "!"); 1566 | }.bind(this)); 1567 | 1568 | this.interlock = ilcx; 1569 | services.push(ilsvc); 1570 | 1571 | ilcx.setValue(true); // Initializes the interlock as on 1572 | } 1573 | 1574 | // Any extra switchMultilevels? Could be a RGBW+W bulb, add them as additional services... 1575 | if(this.devDesc.extras["switchMultilevel"]) for(var i = 0; i < this.devDesc.extras["switchMultilevel"].length; i++){ 1576 | var xvdev = this.devDesc.devices[this.devDesc.extras["switchMultilevel"][i]]; 1577 | var xservice = this.getVDevServices(xvdev); 1578 | services = services.concat(xservice); 1579 | } 1580 | 1581 | if(this.platform.splitServices){ 1582 | if(this.devDesc.types["battery.Battery"]){ 1583 | services = services.concat(this.getVDevServices(this.devDesc.devices[this.devDesc.types["battery.Battery"]])); 1584 | } 1585 | 1586 | // Odds and ends...if there are sensors that haven't been used, add services for them... 1587 | 1588 | var tempSensor = this.devDesc.types["sensorMultilevel.Temperature"] !== undefined ? this.devDesc.devices[this.devDesc.types["sensorMultilevel.Temperature"]] : false; 1589 | if(tempSensor && !this.platform.cxVDevMap[tempSensor.id]){ 1590 | services = services.concat(this.getVDevServices(tempSensor)); 1591 | } 1592 | 1593 | var rhSensor = this.devDesc.types["sensorMultilevel.Humidity"] !== undefined ? this.devDesc.devices[this.devDesc.types["sensorMultilevel.Humidity"]] : false; 1594 | if(rhSensor && !this.platform.cxVDevMap[rhSensor.id]){ 1595 | services = services.concat(this.getVDevServices(rhSensor)); 1596 | } 1597 | 1598 | var lightSensor = this.devDesc.types["sensorMultilevel.Luminiscence"] !== undefined ? this.devDesc.devices[this.devDesc.types["sensorMultilevel.Luminiscence"]] : false; 1599 | if(lightSensor && !this.platform.cxVDevMap[lightSensor.id]){ 1600 | services = services.concat(this.getVDevServices(lightSensor)); 1601 | } 1602 | 1603 | var wattSensor = this.devDesc.types["sensorMultilevel.meterElectric_watt"] !== undefined ? this.devDesc.devices[this.devDesc.types["sensorMultilevel.meterElectric_watt"]] : false; 1604 | if(wattSensor && !this.platform.cxVDevMap[wattSensor.id]){ 1605 | services = services.concat(this.getVDevServices(wattSensor)); 1606 | } 1607 | 1608 | //var kWhSensor = this.devDesc.types["sensorMultilevel.meterElectric_kilowatt_per_hour"] !== undefined ? this.devDesc.devices[this.devDesc.types["sensorMultilevel.meterElectric_kilowatt_per_hour"]] : false; 1609 | //if(kWhSensor && !this.platform.cxVDevMap[kWhSensor.id]){ 1610 | // services = services.concat(this.getVDevServices(kWhSensor)); 1611 | //} 1612 | 1613 | } else { 1614 | // Everything outside the primary service gets added as optional characteristics... 1615 | var service = services[1]; 1616 | var existingCxUUIDs = {}; 1617 | for(var i = 0; i < service.characteristics.length; i++) existingCxUUIDs[service.characteristics[i].UUID] = true; 1618 | 1619 | for(var i = 0; i < this.devDesc.devices.length; i++){ 1620 | var vdev = this.devDesc.devices[i]; 1621 | if(this.platform.cxVDevMap[vdev.id]) continue; // Don't double-use anything 1622 | //NOTE: Currently no root keys in the map...so don't bother trying for now...maybe ever (bad idea)? 1623 | var extraCxClasses = this.extraCharacteristicsMap[ZWayServerPlatform.getVDevTypeKey(vdev)]; 1624 | var extraCxs = []; 1625 | if(!extraCxClasses || extraCxClasses.length === 0) continue; 1626 | for(var j = 0; j < extraCxClasses.length; j++){ 1627 | var cx = new extraCxClasses[j](); 1628 | if(existingCxUUIDs[cx.UUID]) continue; // Don't have two of the same Characteristic type in one service! 1629 | var vdev2 = this.getVDevForCharacteristic(cx, vdev); // Just in case...will probably return vdev. 1630 | if(!vdev2){ 1631 | // Uh oh... one of the extraCxClasses can't be configured! Abort all extras for this vdev! 1632 | extraCxs = []; // to wipe out any already setup cxs. 1633 | break; 1634 | } 1635 | this.configureCharacteristic(cx, vdev2, service); 1636 | extraCxs.push(cx); 1637 | } 1638 | for(var j = 0; j < extraCxs.length; j++) 1639 | service.addCharacteristic(extraCxs[j]); 1640 | } 1641 | } 1642 | 1643 | debug("Loaded services for " + this.name); 1644 | return services; 1645 | } 1646 | }; 1647 | 1648 | module.exports.accessory = ZWayServerAccessory; 1649 | module.exports.platform = ZWayServerPlatform; 1650 | module.exports._initializer = module.exports; 1651 | --------------------------------------------------------------------------------