From 983f1362715fc3064c3b3fe5882f2d085449c227 Mon Sep 17 00:00:00 2001 From: ilcato Date: Tue, 15 Sep 2015 13:57:21 +0200 Subject: [PATCH 01/22] Create FibaroHC2.js --- platforms/FibaroHC2.js | 445 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 445 insertions(+) create mode 100644 platforms/FibaroHC2.js diff --git a/platforms/FibaroHC2.js b/platforms/FibaroHC2.js new file mode 100644 index 0000000..25e093f --- /dev/null +++ b/platforms/FibaroHC2.js @@ -0,0 +1,445 @@ +// Fibaro Home Center 2 Platform Shim for HomeBridge +// +// Remember to add platform to config.json. Example: +// "platforms": [ +// { + "platform": "FibaroHC2", + "name": "FibaroHC2", + "host": "PUT IP ADDRESS OF YOUR HC2 HERE", + "username": "PUT USERNAME OF YOUR HC2 HERE", + "password": "PUT PASSWORD OF YOUR HC2 HERE" +// } +// ], +// +// When you attempt to add a device, it will ask for a "PIN code". +// The default code for all HomeBridge accessories is 031-45-154. + +var types = require("HAP-NodeJS/accessories/types.js"); +var request = require("request"); + +function FibaroHC2Platform(log, config){ + this.log = log; + this.host = config["host"]; + this.username = config["username"]; + this.password = config["password"]; + this.auth = "Basic " + new Buffer(this.username + ":" + this.password).toString("base64"); + this.url = "http://"+this.host+"/api/devices"; +} + +FibaroHC2Platform.prototype = { + accessories: function(callback) { + this.log("Fetching Fibaro Home Center devices..."); + + var that = this; + var foundAccessories = []; + + request.get({ + url: this.url, + headers : { + "Authorization" : this.auth + }, + json: true + }, function(err, response, json) { + if (!err && response.statusCode == 200) { + if (json != undefined) { + json.map(function(s) { + that.log("Found: " + s.type); + if (s.visible == true) { + if (s.type == "com.fibaro.multilevelSwitch") { + accessory = new FibaroDimmerAccessory(that, s.name, s.id); + foundAccessories.push(accessory); + } else if (s.type == "com.fibaro.FGRM222") + { + accessory = new FibaroRollerShutterAccessory(that, s.name, s.id); + foundAccessories.push(accessory); + } else if (s.type == "com.fibaro.binarySwitch" || s.type == "com.fibaro.developer.bxs.virtualBinarySwitch") + { + accessory = new FibaroBinarySwitchAccessory(that, s.name, s.id); + foundAccessories.push(accessory); + } else if (s.type == "com.fibaro.FGMS001") + { + accessory = new FibaroMotionSensorAccessory(that, s.name, s.id); + foundAccessories.push(accessory); + } else if (s.type == "com.fibaro.temperatureSensor") + { + accessory = new FibaroTemperatureSensorAccessory(that, s.name, s.id); + foundAccessories.push(accessory); + } else if (s.type == "com.fibaro.doorSensor") + { + accessory = new FibaroDoorSensorAccessory(that, s.name, s.id); + foundAccessories.push(accessory); + } + + } + }) + } + callback(foundAccessories); + } else { + that.log("There was a problem authenticating with FibaroHC2."); + } + }); + + }, + getAccessoryValue: function(callback, returnBoolean, that) { + var url = "http://"+that.platform.host+"/api/devices/"+that.id+"/properties/value"; + request.get({ + headers : { + "Authorization" : that.platform.auth + }, + json: true, + url: url + }, function(err, response, json) { + that.platform.log(url); + if (!err && response.statusCode == 200) { + if (returnBoolean) + callback(json.value == 0 ? 0 : 1); + else + callback(json.value); + } else { + that.platform.log("There was a problem getting value from" + that.id); + } + }) + }, + getAccessoryServices: function(that) { + var services = [{ + sType: types.ACCESSORY_INFORMATION_STYPE, + characteristics: this.informationCharacteristics(that), + }, + { + sType: that.SERVICE_TYPE, + characteristics: this.controlCharacteristics(that) + }]; + this.log("Loaded services for " + that.name) + return services; + }, + command: function(c,value, that) { + var url = "http://"+this.host+"/api/devices/"+that.id+"/action/"+c; + var body = value != undefined ? JSON.stringify({ + "args": [ + value + ] + }) : null; + var method = "post"; + request({ + url: url, + body: body, + method: method, + headers: { + "Authorization" : this.auth + }, + }, function(err, response) { + if (err) { + that.platform.log("There was a problem sending command " + c + " to" + that.name); + that.platform.log(url); + } else { + that.platform.log(that.name + " sent command " + c); + that.platform.log(url); + } + }); + }, + informationCharacteristics: function(that) + { + return [ + { + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: that.name, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of the accessory", + designedMaxLength: 255 + },{ + cType: types.MANUFACTURER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: "Fibaro", + supportEvents: false, + supportBonjour: false, + manfDescription: "Manufacturer", + designedMaxLength: 255 + },{ + cType: types.MODEL_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: that.MODEL_TYPE, + supportEvents: false, + supportBonjour: false, + manfDescription: "Model", + designedMaxLength: 255 + },{ + cType: types.SERIAL_NUMBER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: "A1S2NASF88EW", + supportEvents: false, + supportBonjour: false, + manfDescription: "SN", + designedMaxLength: 255 + },{ + cType: types.IDENTIFY_CTYPE, + onUpdate: null, + perms: ["pw"], + format: "bool", + initialValue: false, + supportEvents: false, + supportBonjour: false, + manfDescription: "Identify Accessory", + designedMaxLength: 1 + } + ] + }, + controlCharacteristics: function(that) { + var cTypes = []; + var l = that.CONTROL_CHARACTERISTICS.length; + for (var i = 0; i < l; i++) { + if (that.CONTROL_CHARACTERISTICS[i] == types.NAME_CTYPE) { + cTypes.push({ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: that.name, + supportEvents: true, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + }); + } else if (that.CONTROL_CHARACTERISTICS[i] == types.POWER_STATE_CTYPE) { + cTypes.push({ + cType: types.POWER_STATE_CTYPE, + onUpdate: function(value) { + if (value == 0) { + that.platform.command("turnOff", null, that) + } else { + that.platform.command("turnOn", null, that) + } + }, + onRead: function(callback) { + that.platform.getAccessoryValue(callback, true, that); + }, + perms: ["pw","pr","ev"], + format: "bool", + initialValue: 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Change the power state", + designedMaxLength: 1 + }); + } else if (that.CONTROL_CHARACTERISTICS[i] == types.BRIGHTNESS_CTYPE) { + cTypes.push({ + cType: types.BRIGHTNESS_CTYPE, + onUpdate: function(value) { that.platform.command("setValue", value, that); }, + onRead: function(callback) { + that.platform.getAccessoryValue(callback, false, that); + }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Adjust Brightness of Light", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 10, + unit: "%" + }); + } else if (that.CONTROL_CHARACTERISTICS[i] == types.WINDOW_COVERING_CURRENT_POSITION_CTYPE) { + cTypes.push({ + cType: types.WINDOW_COVERING_CURRENT_POSITION_CTYPE, + onRead: function(callback) { + that.platform.getAccessoryValue(callback, false, that); + }, + perms: ["pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Blind Position", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }); + } else if (that.CONTROL_CHARACTERISTICS[i] == types.WINDOW_COVERING_TARGET_POSITION_CTYPE) { + cTypes.push({ + cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, + onUpdate: function(value) { that.platform.command("setValue", value, that); }, + onRead: function(callback) { + that.platform.getAccessoryValue(callback, false, that); + }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Target Blind Position", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }); + } else if (that.CONTROL_CHARACTERISTICS[i] == types.WINDOW_COVERING_OPERATION_STATE_CTYPE) { + cTypes.push({ + cType: types.WINDOW_COVERING_OPERATION_STATE_CTYPE, + perms: ["pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Position State", + designedMinValue: 0, + designedMaxValue: 2, + designedMinStep: 1, + }); + } else if (that.CONTROL_CHARACTERISTICS[i] == types.CURRENT_TEMPERATURE_CTYPE) { + cTypes.push({ + cType: types.CURRENT_TEMPERATURE_CTYPE, + onRead: function(callback) { + that.platform.getAccessoryValue(callback, false, that); + }, + perms: ["pr","ev"], + format: "float", + unit: "celsius", + stepValue: 0.1, + initialValue: 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Get current temperature" + }); + } else if (that.CONTROL_CHARACTERISTICS[i] == types.MOTION_DETECTED_CTYPE) { + cTypes.push({ + cType: types.MOTION_DETECTED_CTYPE, + onRead: function(callback) { + that.platform.getAccessoryValue(callback, true, that); + }, + perms: ["pr","ev"], + format: "bool", + initialValue: 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Detect motion", + designedMaxLength: 1 + }); + } else if (that.CONTROL_CHARACTERISTICS[i] == types.CONTACT_SENSOR_STATE_CTYPE) { + cTypes.push({ + cType: types.CONTACT_SENSOR_STATE_CTYPE, + onRead: function(callback) { + that.platform.getAccessoryValue(callback, true, that); + }, + perms: ["pr","ev"], + format: "bool", + initialValue: 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Detect door contact", + designedMaxLength: 1 + }); + } + } + return cTypes + } +} + +function FibaroDimmerAccessory(platform, name, id) { + // device info + this.platform = platform; + this.name = name; + this.id = id; + this.MODEL_TYPE = "Dimmer"; + this.SERVICE_TYPE = types.LIGHTBULB_STYPE; + this.CONTROL_CHARACTERISTICS = [types.NAME_CTYPE, types.POWER_STATE_CTYPE, types.BRIGHTNESS_CTYPE]; +} + +FibaroDimmerAccessory.prototype = { + getServices: function() { + return this.platform.getAccessoryServices(this); + } +}; + +function FibaroRollerShutterAccessory(platform, name, id) { + // device info + this.platform = platform; + this.name = name; + this.id = id; + this.MODEL_TYPE = "Roller Shutter 2"; + this.SERVICE_TYPE = types.WINDOW_COVERING_STYPE; + this.CONTROL_CHARACTERISTICS = [types.NAME_CTYPE, types.WINDOW_COVERING_CURRENT_POSITION_CTYPE, types.WINDOW_COVERING_TARGET_POSITION_CTYPE, types.WINDOW_COVERING_OPERATION_STATE_CTYPE]; + +} + +FibaroRollerShutterAccessory.prototype = { + getServices: function() { + return this.platform.getAccessoryServices(this); + } +}; + +function FibaroBinarySwitchAccessory(platform, name, id) { + // device info + this.platform = platform; + this.name = name; + this.id = id; + this.MODEL_TYPE = "Binary Switch"; + this.SERVICE_TYPE = types.SWITCH_STYPE; + this.CONTROL_CHARACTERISTICS = [types.NAME_CTYPE, types.POWER_STATE_CTYPE]; +} + +FibaroBinarySwitchAccessory.prototype = { + getServices: function() { + return this.platform.getAccessoryServices(this); + } +}; + +function FibaroTemperatureSensorAccessory(platform, name, id) { + // device info + this.platform = platform; + this.name = name; + this.id = id; + this.MODEL_TYPE = "Temperature Sensor"; + this.SERVICE_TYPE = types.TEMPERATURE_SENSOR_STYPE; + this.CONTROL_CHARACTERISTICS = [types.NAME_CTYPE, types.CURRENT_TEMPERATURE_CTYPE]; +} + +FibaroTemperatureSensorAccessory.prototype = { + getServices: function() { + return this.platform.getAccessoryServices(this); + } +}; + +function FibaroMotionSensorAccessory(platform, name, id) { + // device info + this.platform = platform; + this.name = name; + this.id = id; + this.MODEL_TYPE = "Motion Sensor"; + this.SERVICE_TYPE = types.MOTION_SENSOR_STYPE; + this.CONTROL_CHARACTERISTICS = [types.NAME_CTYPE, types.MOTION_DETECTED_CTYPE]; +} + +FibaroMotionSensorAccessory.prototype = { + getServices: function() { + return this.platform.getAccessoryServices(this); + } +}; + +function FibaroDoorSensorAccessory(platform, name, id) { + // device info + this.platform = platform; + this.name = name; + this.id = id; + this.MODEL_TYPE = "Door Sensor"; + this.SERVICE_TYPE = types.CONTACT_SENSOR_STYPE; + this.CONTROL_CHARACTERISTICS = [types.NAME_CTYPE, types.CONTACT_SENSOR_STATE_CTYPE]; +} + +FibaroDoorSensorAccessory.prototype = { + getServices: function() { + return this.platform.getAccessoryServices(this); + } +}; + +module.exports.platform = FibaroHC2Platform; From d36827f92de51ab86b6b5656730a9a0f4c310732 Mon Sep 17 00:00:00 2001 From: ilcato Date: Tue, 15 Sep 2015 14:26:42 +0200 Subject: [PATCH 02/22] Fixed comments --- platforms/FibaroHC2.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/platforms/FibaroHC2.js b/platforms/FibaroHC2.js index 25e093f..14dbdd2 100644 --- a/platforms/FibaroHC2.js +++ b/platforms/FibaroHC2.js @@ -3,11 +3,11 @@ // Remember to add platform to config.json. Example: // "platforms": [ // { - "platform": "FibaroHC2", - "name": "FibaroHC2", - "host": "PUT IP ADDRESS OF YOUR HC2 HERE", - "username": "PUT USERNAME OF YOUR HC2 HERE", - "password": "PUT PASSWORD OF YOUR HC2 HERE" +// "platform": "FibaroHC2", +// "name": "FibaroHC2", +// "host": "PUT IP ADDRESS OF YOUR HC2 HERE", +// "username": "PUT USERNAME OF YOUR HC2 HERE", +// "password": "PUT PASSWORD OF YOUR HC2 HERE" // } // ], // From a041407104f3670a9081d8649014fd7132bd04a2 Mon Sep 17 00:00:00 2001 From: ilcato Date: Tue, 15 Sep 2015 19:03:08 +0200 Subject: [PATCH 03/22] Added automatic status update through event mechanism --- platforms/FibaroHC2.js | 99 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 7 deletions(-) diff --git a/platforms/FibaroHC2.js b/platforms/FibaroHC2.js index 14dbdd2..6eef466 100644 --- a/platforms/FibaroHC2.js +++ b/platforms/FibaroHC2.js @@ -18,12 +18,14 @@ var types = require("HAP-NodeJS/accessories/types.js"); var request = require("request"); function FibaroHC2Platform(log, config){ - this.log = log; - this.host = config["host"]; - this.username = config["username"]; - this.password = config["password"]; - this.auth = "Basic " + new Buffer(this.username + ":" + this.password).toString("base64"); - this.url = "http://"+this.host+"/api/devices"; + this.log = log; + this.host = config["host"]; + this.username = config["username"]; + this.password = config["password"]; + this.auth = "Basic " + new Buffer(this.username + ":" + this.password).toString("base64"); + this.url = "http://"+this.host+"/api/devices"; + + startPollingUpdate( this ); } FibaroHC2Platform.prototype = { @@ -75,7 +77,7 @@ FibaroHC2Platform.prototype = { } callback(foundAccessories); } else { - that.log("There was a problem authenticating with FibaroHC2."); + that.log("There was a problem connecting with FibaroHC2."); } }); @@ -212,6 +214,10 @@ FibaroHC2Platform.prototype = { } else if (that.CONTROL_CHARACTERISTICS[i] == types.POWER_STATE_CTYPE) { cTypes.push({ cType: types.POWER_STATE_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + subscribeUpdate(characteristic, that, true); + }, onUpdate: function(value) { if (value == 0) { that.platform.command("turnOff", null, that) @@ -233,6 +239,10 @@ FibaroHC2Platform.prototype = { } else if (that.CONTROL_CHARACTERISTICS[i] == types.BRIGHTNESS_CTYPE) { cTypes.push({ cType: types.BRIGHTNESS_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + subscribeUpdate(characteristic, that, false); + }, onUpdate: function(value) { that.platform.command("setValue", value, that); }, onRead: function(callback) { that.platform.getAccessoryValue(callback, false, that); @@ -251,6 +261,10 @@ FibaroHC2Platform.prototype = { } else if (that.CONTROL_CHARACTERISTICS[i] == types.WINDOW_COVERING_CURRENT_POSITION_CTYPE) { cTypes.push({ cType: types.WINDOW_COVERING_CURRENT_POSITION_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + subscribeUpdate(characteristic, that, false); + }, onRead: function(callback) { that.platform.getAccessoryValue(callback, false, that); }, @@ -268,6 +282,10 @@ FibaroHC2Platform.prototype = { } else if (that.CONTROL_CHARACTERISTICS[i] == types.WINDOW_COVERING_TARGET_POSITION_CTYPE) { cTypes.push({ cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + subscribeUpdate(characteristic, that, false); + }, onUpdate: function(value) { that.platform.command("setValue", value, that); }, onRead: function(callback) { that.platform.getAccessoryValue(callback, false, that); @@ -299,6 +317,10 @@ FibaroHC2Platform.prototype = { } else if (that.CONTROL_CHARACTERISTICS[i] == types.CURRENT_TEMPERATURE_CTYPE) { cTypes.push({ cType: types.CURRENT_TEMPERATURE_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + subscribeUpdate(characteristic, that, false); + }, onRead: function(callback) { that.platform.getAccessoryValue(callback, false, that); }, @@ -314,6 +336,10 @@ FibaroHC2Platform.prototype = { } else if (that.CONTROL_CHARACTERISTICS[i] == types.MOTION_DETECTED_CTYPE) { cTypes.push({ cType: types.MOTION_DETECTED_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + subscribeUpdate(characteristic, that, true); + }, onRead: function(callback) { that.platform.getAccessoryValue(callback, true, that); }, @@ -328,6 +354,10 @@ FibaroHC2Platform.prototype = { } else if (that.CONTROL_CHARACTERISTICS[i] == types.CONTACT_SENSOR_STATE_CTYPE) { cTypes.push({ cType: types.CONTACT_SENSOR_STATE_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + subscribeUpdate(characteristic, that, true); + }, onRead: function(callback) { that.platform.getAccessoryValue(callback, true, that); }, @@ -441,5 +471,60 @@ FibaroDoorSensorAccessory.prototype = { return this.platform.getAccessoryServices(this); } }; +var lastPoll=0; +var pollingUpdateRunning = false; + +function startPollingUpdate( platform ) +{ + if( pollingUpdateRunning ) + return; + pollingUpdateRunning = true; + + var updateUrl = "http://"+platform.host+"/api/refreshStates?last="+lastPoll; + + request.get({ + url: updateUrl, + headers : { + "Authorization" : platform.auth + }, + json: true + }, function(err, response, json) { + if (!err && response.statusCode == 200) { + if (json != undefined) { + lastPoll = json.last; + if (json.changes != undefined) { + json.changes.map(function(s) { + if (s.value != undefined) { + + var value=parseInt(s.value); + if (isNaN(value)) + value=(s.value === "true"); + for (i=0;i Date: Wed, 16 Sep 2015 21:56:09 +0200 Subject: [PATCH 04/22] Fixed status update --- platforms/FibaroHC2.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/platforms/FibaroHC2.js b/platforms/FibaroHC2.js index 6eef466..a1336d5 100644 --- a/platforms/FibaroHC2.js +++ b/platforms/FibaroHC2.js @@ -115,6 +115,11 @@ FibaroHC2Platform.prototype = { return services; }, command: function(c,value, that) { + if (that.doNotSet != undefined && that.doNotSet == true) { + that.doNotSet = false; + return; + } + var url = "http://"+this.host+"/api/devices/"+that.id+"/action/"+c; var body = value != undefined ? JSON.stringify({ "args": [ @@ -502,6 +507,7 @@ function startPollingUpdate( platform ) for (i=0;i Date: Fri, 18 Sep 2015 09:37:10 +0200 Subject: [PATCH 05/22] Cleanup KNX file names and markdown I found that the description was wrongly named and in the wrong folder. It is now named after the platform shim to appear next to it in listings, also the config-sample that comes along with it. As long as we do not go for own repositories and npm packages, should we consider folders for additional files if required? --- ...ig-sample-knx.json => KNX-sample-config.json} | 0 accessories/knxdevice.md => platforms/KNX.md | 16 ++++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) rename platforms/{config-sample-knx.json => KNX-sample-config.json} (100%) rename accessories/knxdevice.md => platforms/KNX.md (85%) diff --git a/platforms/config-sample-knx.json b/platforms/KNX-sample-config.json similarity index 100% rename from platforms/config-sample-knx.json rename to platforms/KNX-sample-config.json diff --git a/accessories/knxdevice.md b/platforms/KNX.md similarity index 85% rename from accessories/knxdevice.md rename to platforms/KNX.md index c9131cc..77dfe4d 100644 --- a/accessories/knxdevice.md +++ b/platforms/KNX.md @@ -1,7 +1,7 @@ # Syntax of the config.json In the platforms section, you can insert a KNX type platform. You need to configure all devices directly in the config.json. - +````json "platforms": [ { "platform": "KNX", @@ -33,8 +33,9 @@ You need to configure all devices directly in the config.json. ] } } - +```` In the accessories section (the array within the brackets [ ]) you can insert as many objects as you like in the following form +````json { "accessory_type": "knxdevice", "name": "Here goes your display name, this will be shown in HomeKit apps", @@ -43,8 +44,9 @@ In the accessories section (the array within the brackets [ ]) you can insert as } ] } - +```` You have to add services in the following syntax: +````json { "type": "SERVICENAME", "description": "This is just for you to remember things", @@ -62,9 +64,11 @@ You have to add services in the following syntax: ] } } -CHARACTERISTIC are properties that are dependent on the service type, so they are listed below. -Two kinds of addresses are supported: "Set":"1/2/3" is a writable group address, to which changes are sent if the service supports changing values. Changes on the bus are listened to, too. -"Listen":["1/2/3","1/2/4","1/2/5"] is an array of addresses that are listened to additionally. To these addresses never values get written, but the on startup the service will issue read requests to ALL addresses listed in Set: and in Listen: +```` +`CHARACTERISTICx` are properties that are dependent on the service type, so they are listed below. + +Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group address, to which changes are sent if the service supports changing values. Changes on the bus are listened to, too. +`"Listen":["1/2/3","1/2/4","1/2/5"]` is an array of addresses that are listened to additionally. To these addresses never values get written, but the on startup the service will issue *KNX read requests* to ALL addresses listed in `Set:` and in `Listen:` # Supported Services and their characteristics From 76a5fd3b7f4f0fef556d84c24de833e89710767f Mon Sep 17 00:00:00 2001 From: Snowdd1 Date: Fri, 18 Sep 2015 09:40:38 +0200 Subject: [PATCH 06/22] markdown \ test --- platforms/KNX.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platforms/KNX.md b/platforms/KNX.md index 77dfe4d..f46cf98 100644 --- a/platforms/KNX.md +++ b/platforms/KNX.md @@ -74,7 +74,7 @@ Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group addres # Supported Services and their characteristics ## Lightbulb - On: DPT 1, 1 as on, 0 as off + On: DPT 1, 1 as on, 0 as off
Brightness: DPT5 percentage, 100% (=255) the brightest From 24a8dcb8cc08be0412903057d8272b4e4fc8d03a Mon Sep 17 00:00:00 2001 From: Snowdd1 Date: Fri, 18 Sep 2015 09:45:08 +0200 Subject: [PATCH 07/22] more markdown formatting --- platforms/KNX.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/platforms/KNX.md b/platforms/KNX.md index f46cf98..dbfd23c 100644 --- a/platforms/KNX.md +++ b/platforms/KNX.md @@ -74,17 +74,18 @@ Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group addres # Supported Services and their characteristics ## Lightbulb - On: DPT 1, 1 as on, 0 as off
- Brightness: DPT5 percentage, 100% (=255) the brightest + - On: DPT 1, 1 as on, 0 as off + - Brightness: DPT5 percentage, 100% (=255) the brightest ## LockMechanism -LockCurrentState: DPT 1, 1 as secured +- LockCurrentState: DPT 1, 1 as secured OR (but not both:) -LockCurrentStateSecured0: DPT 1, 0 as secured +- LockCurrentStateSecured0: DPT 1, 0 as secured -LockTargetState: DPT 1, 1 as secured -LockTargetStateSecured0: DPT 1, 0 as secured +- LockTargetState: DPT 1, 1 as secured +OR +- LockTargetStateSecured0: DPT 1, 0 as secured ## Thermostat CurrentTemperature: DPT9 in °C [listen only] From f802d845092007246934e23c1df0d19885fa1081 Mon Sep 17 00:00:00 2001 From: Snowdd1 Date: Fri, 18 Sep 2015 09:47:52 +0200 Subject: [PATCH 08/22] Even more markdown formatting --- platforms/KNX.md | 53 +++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/platforms/KNX.md b/platforms/KNX.md index dbfd23c..22b3aee 100644 --- a/platforms/KNX.md +++ b/platforms/KNX.md @@ -79,51 +79,48 @@ Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group addres ## LockMechanism -- LockCurrentState: DPT 1, 1 as secured -OR (but not both:) +- LockCurrentState: DPT 1, 1 as secured **OR (but not both:)** - LockCurrentStateSecured0: DPT 1, 0 as secured -- LockTargetState: DPT 1, 1 as secured -OR +- LockTargetState: DPT 1, 1 as secured **OR** - LockTargetStateSecured0: DPT 1, 0 as secured ## Thermostat -CurrentTemperature: DPT9 in °C [listen only] -TargetTemperature: DPT9, values 0..40°C only, all others are ignored -CurrentHeatingCoolingState: DPT5 HVAC, because of the incompatible mapping only off and heating (=auto) are shown, [listen only] -TargetHeatingCoolingState: as above +- CurrentTemperature: DPT9 in °C [listen only] +- TargetTemperature: DPT9, values 0..40°C only, all others are ignored +- CurrentHeatingCoolingState: DPT5 HVAC, because of the incompatible mapping only off and heating (=auto) are shown, [listen only] +- TargetHeatingCoolingState: as above ## TemperatureSensor -CurrentTemperature: DPT9 in °C [listen only] +- CurrentTemperature: DPT9 in °C [listen only] ## Window -CurrentPosition: DPT5 percentage -TargetPosition: DPT5 percentage -PositionState: DPT5 value [listen only] +- CurrentPosition: DPT5 percentage +- TargetPosition: DPT5 percentage +- PositionState: DPT5 value [listen only] ## WindowCovering -CurrentPosition: DPT5 percentage -TargetPosition: DPT5 percentage -PositionState: DPT5 value [listen only] +- CurrentPosition: DPT5 percentage +- TargetPosition: DPT5 percentage +- PositionState: DPT5 value [listen only] ### not yet supported -HoldPosition -TargetHorizontalTiltAngle -TargetVerticalTiltAngle -CurrentHorizontalTiltAngle -CurrentVerticalTiltAngle -ObstructionDetected +- HoldPosition +- TargetHorizontalTiltAngle +- TargetVerticalTiltAngle +- CurrentHorizontalTiltAngle +- CurrentVerticalTiltAngle +- ObstructionDetected ## ContactSensor -ContactSensorState: DPT 1, 0 as contact -OR -ContactSensorStateContact1: DPT 1, 1 as contact +- ContactSensorState: DPT 1, 0 as contact **OR** +- ContactSensorStateContact1: DPT 1, 1 as contact -StatusActive: DPT 1, 1 as true -StatusFault: DPT 1, 1 as true -StatusTampered: DPT 1, 1 as true -StatusLowBattery: DPT 1, 1 as true +- StatusActive: DPT 1, 1 as true +- StatusFault: DPT 1, 1 as true +- StatusTampered: DPT 1, 1 as true +- StatusLowBattery: DPT 1, 1 as true # DISCLAIMER From 93ea0dedede1e5576e304b8ce8c34477c4d724a1 Mon Sep 17 00:00:00 2001 From: Snowdd1 Date: Fri, 18 Sep 2015 10:28:31 +0200 Subject: [PATCH 09/22] Cleanup and new devices Added services: - Switch - Outlet --- accessories/knxdevice.js | 347 +++++++++++++++++++-------------------- platforms/KNX.md | 44 ++--- 2 files changed, 193 insertions(+), 198 deletions(-) diff --git a/accessories/knxdevice.js b/accessories/knxdevice.js index 824b9dd..b554b9a 100644 --- a/accessories/knxdevice.js +++ b/accessories/knxdevice.js @@ -1,13 +1,16 @@ -/* +/** * This is a KNX universal accessory shim. * This is NOT the version for dynamic installation * New 2015-09-16: Welcome iOS9.0 -new features includ: -services: -Window -WindowCovering -ContactSensor +new features include: +- services: +- Window +- WindowCovering +- ContactSensor +New 2015-0918: +- Services Switch and Outlet +- Code cleanup * */ var Service = require("HAP-NodeJS").Service; @@ -102,7 +105,6 @@ KNXDevice.prototype = { }.bind(this)); }.bind(this)); }, - // issues an all purpose read request on the knx bus // DOES NOT WAIT for an answer. Please register the address with a callback using registerGA() function knxread: function(groupAddress){ @@ -132,7 +134,6 @@ KNXDevice.prototype = { }.bind(this)); }.bind(this)); }, - // issuing multiple read requests at once knxreadarray: function (groupAddresses) { if (groupAddresses.constructor.toString().indexOf("Array") > -1) { @@ -147,7 +148,9 @@ KNXDevice.prototype = { this.knxread (groupAddresses); } }, - +/** Write special type routines + * + */ // special types knxwrite_percent: function(callback, groupAddress, value) { var numericValue = 0; @@ -159,10 +162,9 @@ KNXDevice.prototype = { } this.knxwrite(callback, groupAddress,'DPT5',numericValue); }, - - - // need to spit registers into types - +/** Registering routines + * + */ // boolean: get 0 or 1 from the bus, write boolean knxregister_bool: function(addresses, characteristic) { this.log("knx registering BOOLEAN " + addresses); @@ -201,7 +203,6 @@ KNXDevice.prototype = { } }.bind(this)); }, - // float knxregister_float: function(addresses, characteristic) { this.log("knx registering FLOAT " + addresses); @@ -216,8 +217,6 @@ KNXDevice.prototype = { }.bind(this)); }, - - // what about HVAC heating cooling types? knxregister_HVAC: function(addresses, characteristic) { this.log("knx registering HVAC " + addresses); knxd_registerGA(addresses, function(val, src, dest, type){ @@ -245,7 +244,7 @@ KNXDevice.prototype = { characteristic.setValue(HAPvalue, undefined, 'fromKNXBus'); }.bind(this)); }, - // to do! KNX: DPT 20.102 = One Byte like DPT5 + /** KNX HVAC (heating, ventilation, and air conditioning) types do not really match to homekit types: // 0 = Auto // 1 = Comfort // 2 = Standby @@ -257,8 +256,8 @@ KNXDevice.prototype = { // Characteristic.TargetHeatingCoolingState.HEAT = 1; // Characteristic.TargetHeatingCoolingState.COOL = 2; // Characteristic.TargetHeatingCoolingState.AUTO = 3; - - + AUTO (3) is not allowed as return type from devices! +*/ // undefined, has to match! knxregister: function(addresses, characteristic) { this.log("knx registering " + addresses); @@ -268,14 +267,14 @@ KNXDevice.prototype = { }.bind(this)); }, - /* - * set methods used for creating callbacks, such as - * var Characteristic = myService.addCharacteristic(new Characteristic.Brightness()) - * .on('set', function(value, callback, context) { - * this.setPercentage(value, callback, context, this.config[index].Set) - * }.bind(this)); - * - */ +/** set methods used for creating callbacks + * such as + * var Characteristic = myService.addCharacteristic(new Characteristic.Brightness()) + * .on('set', function(value, callback, context) { + * this.setPercentage(value, callback, context, this.config[index].Set) + * }.bind(this)); + * + */ setBooleanState: function(value, callback, context, gaddress) { if (context === 'fromKNXBus') { this.log(gaddress + " event ping pong, exit!"); @@ -308,7 +307,6 @@ KNXDevice.prototype = { } }, - setPercentage: function(value, callback, context, gaddress) { if (context === 'fromKNXBus') { this.log("event ping pong, exit!"); @@ -324,7 +322,6 @@ KNXDevice.prototype = { this.knxwrite(callback, gaddress,'DPT5',numericValue); } }, - setFloat: function(value, callback, context, gaddress) { if (context === 'fromKNXBus') { this.log(gaddress + " event ping pong, exit!"); @@ -340,7 +337,6 @@ KNXDevice.prototype = { this.knxwrite(callback, gaddress,'DPT9',numericValue); } }, - setHVACState: function(value, callback, context, gaddress) { if (context === 'fromKNXBus') { this.log(gaddress + " event ping pong, exit!"); @@ -371,21 +367,16 @@ KNXDevice.prototype = { } }, - - +/** identify dummy + * + */ identify: function(callback) { this.log("Identify requested!"); callback(); // success }, - - - /* - * function getXXXXXXXService(config) - * - * returns a configured service object to the caller (accessory/device) - * - */ - +/** bindCharacteristic + * initializes callbacks for 'set' events (from HK) and for KNX bus reads (to HK) + */ bindCharacteristic: function(myService, characteristicType, valueType, config) { var myCharacteristic = myService.getCharacteristic(characteristicType); if (myCharacteristic === undefined) { @@ -453,7 +444,60 @@ KNXDevice.prototype = { } return myCharacteristic; // for chaining or whatsoever }, - +/** + * function getXXXXXXXService(config) + * returns a configured service object to the caller (accessory/device) + * + * @param config + * pass a configuration array parsed from config.json + * specifically for this service + * + */ + getContactSenserService: function(config) { +// Characteristic.ContactSensorState.CONTACT_DETECTED = 0; +// Characteristic.ContactSensorState.CONTACT_NOT_DETECTED = 1; + + // some sanity checks + if (config.type !== "ContactSensor") { + this.log("[ERROR] ContactSensor Service for non 'ContactSensor' service called"); + return undefined; + } + if (!config.name) { + this.log("[ERROR] ContactSensor Service without 'name' property called"); + return undefined; + } + + var myService = new Service.ContactSensor(config.name,config.name); + if (config.ContactSensorState) { + this.log("ContactSensor ContactSensorState characteristic enabled"); + this.bindCharacteristic(myService, Characteristic.ContactSensorState, "Bool", config.ContactSensorState); + } else if (config.ContactSensorStateContact1) { + this.log("ContactSensor ContactSensorStateContact1 characteristic enabled"); + this.bindCharacteristic(myService, Characteristic.ContactSensorState, "BoolReverse", config.ContactSensorStateContact1); + } + //optionals + if (config.StatusActive) { + this.log("ContactSensor StatusActive characteristic enabled"); + myService.addCharacteristic(Characteristic.StatusActive); + this.bindCharacteristic(myService, Characteristic.StatusActive, "Bool", config.StatusActive); + } + if (config.StatusFault) { + this.log("ContactSensor StatusFault characteristic enabled"); + myService.addCharacteristic(Characteristic.StatusFault); + this.bindCharacteristic(myService, Characteristic.StatusFault, "Bool", config.StatusFault); + } + if (config.StatusTampered) { + this.log("ContactSensor StatusTampered characteristic enabled"); + myService.addCharacteristic(Characteristic.StatusTampered); + this.bindCharacteristic(myService, Characteristic.StatusTampered, "Bool", config.StatusTampered); + } + if (config.StatusLowBattery) { + this.log("ContactSensor StatusLowBattery characteristic enabled"); + myService.addCharacteristic(Characteristic.StatusLowBattery); + this.bindCharacteristic(myService, Characteristic.StatusLowBattery, "Bool", config.StatusLowBattery); + } + return myService; + }, getLightbulbService: function(config) { // some sanity checks //this.config = config; @@ -482,13 +526,13 @@ KNXDevice.prototype = { //iterate(myService); return myService; }, - getLockMechanismService: function(config) { - // some sanity checks - //this.config = config; + +/** //this.config = config; // Characteristic.LockCurrentState.UNSECURED = 0; // Characteristic.LockCurrentState.SECURED = 1; - +*/ + // some sanity checks if (config.type !== "LockMechanism") { this.log("[ERROR] LockMechanism Service for non 'LockMechanism' service called"); return undefined; @@ -497,6 +541,7 @@ KNXDevice.prototype = { this.log("[ERROR] LockMechanism Service without 'name' property called"); return undefined; } + var myService = new Service.LockMechanism(config.name,config.name); // LockCurrentState if (config.LockCurrentState) { @@ -520,28 +565,61 @@ KNXDevice.prototype = { //iterate(myService); return myService; }, - + getOutletService: function(config) { + /** + * this.addCharacteristic(Characteristic.On); + * this.addCharacteristic(Characteristic.OutletInUse); + */ + // some sanity checks + if (config.type !== "Outlet") { + this.log("[ERROR] Outlet Service for non 'Outlet' service called"); + return undefined; + } + if (!config.name) { + this.log("[ERROR] Outlet Service without 'name' property called"); + return undefined; + } + var myService = new Service.Outlet(config.name,config.name); + // On (and Off) + if (config.On) { + this.log("Outlet on/off characteristic enabled"); + this.bindCharacteristic(myService, Characteristic.On, "Bool", config.On); + } // OutletInUse characteristic + if (config.OutletInUse) { + this.log("Outlet on/off characteristic enabled"); + this.bindCharacteristic(myService, Characteristic.OutletInUse, "Bool", config.OutletInUse); + } + return myService; + }, + getSwitchService: function(config) { + // some sanity checks + if (config.type !== "Switch") { + this.log("[ERROR] Switch Service for non 'Switch' service called"); + return undefined; + } + if (!config.name) { + this.log("[ERROR] Switch Service without 'name' property called"); + return undefined; + } + var myService = new Service.Switch(config.name,config.name); + // On (and Off) + if (config.On) { + this.log("Switch on/off characteristic enabled"); + this.bindCharacteristic(myService, Characteristic.On, "Bool", config.On); + } // On characteristic + return myService; + }, getThermostatService: function(config) { - - -// // Required Characteristics -// this.addCharacteristic(Characteristic.CurrentHeatingCoolingState); -// this.addCharacteristic(Characteristic.TargetHeatingCoolingState); -// this.addCharacteristic(Characteristic.CurrentTemperature); //check -// this.addCharacteristic(Characteristic.TargetTemperature); // -// this.addCharacteristic(Characteristic.TemperatureDisplayUnits); - // -// // Optional Characteristics -// this.addOptionalCharacteristic(Characteristic.CurrentRelativeHumidity); -// this.addOptionalCharacteristic(Characteristic.TargetRelativeHumidity); -// this.addOptionalCharacteristic(Characteristic.CoolingThresholdTemperature); -// this.addOptionalCharacteristic(Characteristic.HeatingThresholdTemperature); - +/** + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.CurrentRelativeHumidity); + this.addOptionalCharacteristic(Characteristic.TargetRelativeHumidity); + this.addOptionalCharacteristic(Characteristic.CoolingThresholdTemperature); + this.addOptionalCharacteristic(Characteristic.HeatingThresholdTemperature); +*/ // some sanity checks - - if (config.type !== "Thermostat") { this.log("[ERROR] Thermostat Service for non 'Thermostat' service called"); return undefined; @@ -550,6 +628,7 @@ KNXDevice.prototype = { this.log("[ERROR] Thermostat Service without 'name' property called"); return undefined; } + var myService = new Service.Thermostat(config.name,config.name); // CurrentTemperature) if (config.CurrentTemperature) { @@ -581,15 +660,9 @@ KNXDevice.prototype = { } return myService; }, - - // temperature sensor type (iOS9 assumed) getTemperatureSensorService: function(config) { - - // some sanity checks - - if (config.type !== "TemperatureSensor") { this.log("[ERROR] TemperatureSensor Service for non 'TemperatureSensor' service called"); return undefined; @@ -606,28 +679,18 @@ KNXDevice.prototype = { } return myService; }, - - - - // window type (iOS9 assumed) getWindowService: function(config) { -// Service.Window = function(displayName, subtype) { -// Service.call(this, displayName, '0000008B-0000-1000-8000-0026BB765291', subtype); -// -// // Required Characteristics -// this.addCharacteristic(Characteristic.CurrentPosition); -// this.addCharacteristic(Characteristic.TargetPosition); -// this.addCharacteristic(Characteristic.PositionState); -// -// // Optional Characteristics -// this.addOptionalCharacteristic(Characteristic.HoldPosition); -// this.addOptionalCharacteristic(Characteristic.ObstructionDetected); -// this.addOptionalCharacteristic(Characteristic.Name); - - // Characteristic.PositionState.DECREASING = 0; -// Characteristic.PositionState.INCREASING = 1; -// Characteristic.PositionState.STOPPED = 2; - +/** + Optional Characteristics + this.addOptionalCharacteristic(Characteristic.HoldPosition); + this.addOptionalCharacteristic(Characteristic.ObstructionDetected); + this.addOptionalCharacteristic(Characteristic.Name); + + PositionState values: The KNX blind actuators I have return only MOVING=1 and STOPPED=0 + Characteristic.PositionState.DECREASING = 0; + Characteristic.PositionState.INCREASING = 1; + Characteristic.PositionState.STOPPED = 2; +*/ // some sanity checks @@ -656,34 +719,17 @@ KNXDevice.prototype = { } return myService; }, - - -// /** -// * Service "Window Covering" -// */ -// -// Service.WindowCovering = function(displayName, subtype) { -// Service.call(this, displayName, '0000008C-0000-1000-8000-0026BB765291', subtype); -// -// // Required Characteristics -// this.addCharacteristic(Characteristic.CurrentPosition); -// this.addCharacteristic(Characteristic.TargetPosition); -// this.addCharacteristic(Characteristic.PositionState); -// -// // Optional Characteristics -// this.addOptionalCharacteristic(Characteristic.HoldPosition); -// this.addOptionalCharacteristic(Characteristic.TargetHorizontalTiltAngle); -// this.addOptionalCharacteristic(Characteristic.TargetVerticalTiltAngle); -// this.addOptionalCharacteristic(Characteristic.CurrentHorizontalTiltAngle); -// this.addOptionalCharacteristic(Characteristic.CurrentVerticalTiltAngle); -// this.addOptionalCharacteristic(Characteristic.ObstructionDetected); -// this.addOptionalCharacteristic(Characteristic.Name); -// }; getWindowCoveringService: function(config) { - + /** + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.HoldPosition); + this.addOptionalCharacteristic(Characteristic.TargetHorizontalTiltAngle); + this.addOptionalCharacteristic(Characteristic.TargetVerticalTiltAngle); + this.addOptionalCharacteristic(Characteristic.CurrentHorizontalTiltAngle); + this.addOptionalCharacteristic(Characteristic.CurrentVerticalTiltAngle); + this.addOptionalCharacteristic(Characteristic.ObstructionDetected); + */ // some sanity checks - - if (config.type !== "WindowCovering") { this.log("[ERROR] WindowCovering Service for non 'WindowCovering' service called"); return undefined; @@ -692,8 +738,8 @@ KNXDevice.prototype = { this.log("[ERROR] WindowCovering Service without 'name' property called"); return undefined; } - var myService = new Service.WindowCovering(config.name,config.name); + var myService = new Service.WindowCovering(config.name,config.name); if (config.CurrentPosition) { this.log("WindowCovering CurrentPosition characteristic enabled"); this.bindCharacteristic(myService, Characteristic.CurrentPosition, "Percent", config.CurrentPosition); @@ -709,64 +755,6 @@ KNXDevice.prototype = { return myService; }, -// Service.ContactSensor = function(displayName, subtype) { -// Service.call(this, displayName, '00000080-0000-1000-8000-0026BB765291', subtype); -// -// // Required Characteristics -// this.addCharacteristic(Characteristic.ContactSensorState); -// -// // Optional Characteristics -// this.addOptionalCharacteristic(Characteristic.StatusActive); -// this.addOptionalCharacteristic(Characteristic.StatusFault); -// this.addOptionalCharacteristic(Characteristic.StatusTampered); -// this.addOptionalCharacteristic(Characteristic.StatusLowBattery); -// this.addOptionalCharacteristic(Characteristic.Name); -// }; -// Characteristic.ContactSensorState.CONTACT_DETECTED = 0; -// Characteristic.ContactSensorState.CONTACT_NOT_DETECTED = 1; - getContactSenserService: function(config) { - // some sanity checks - if (config.type !== "ContactSensor") { - this.log("[ERROR] ContactSensor Service for non 'ContactSensor' service called"); - return undefined; - } - if (!config.name) { - this.log("[ERROR] ContactSensor Service without 'name' property called"); - return undefined; - } - var myService = new Service.ContactSensor(config.name,config.name); - - if (config.ContactSensorState) { - this.log("ContactSensor ContactSensorState characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.ContactSensorState, "Bool", config.ContactSensorState); - } else if (config.ContactSensorStateContact1) { - this.log("ContactSensor ContactSensorStateContact1 characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.ContactSensorState, "BoolReverse", config.ContactSensorStateContact1); - } - //optionals - if (config.StatusActive) { - this.log("ContactSensor StatusActive characteristic enabled"); - myService.addCharacteristic(Characteristic.StatusActive); - this.bindCharacteristic(myService, Characteristic.StatusActive, "Bool", config.StatusActive); - } - if (config.StatusFault) { - this.log("ContactSensor StatusFault characteristic enabled"); - myService.addCharacteristic(Characteristic.StatusFault); - this.bindCharacteristic(myService, Characteristic.StatusFault, "Bool", config.StatusFault); - } - if (config.StatusTampered) { - this.log("ContactSensor StatusTampered characteristic enabled"); - myService.addCharacteristic(Characteristic.StatusTampered); - this.bindCharacteristic(myService, Characteristic.StatusTampered, "Bool", config.StatusTampered); - } - if (config.StatusLowBattery) { - this.log("ContactSensor StatusLowBattery characteristic enabled"); - myService.addCharacteristic(Characteristic.StatusLowBattery); - this.bindCharacteristic(myService, Characteristic.StatusLowBattery, "Bool", config.StatusLowBattery); - } - return myService; - }, - /* assemble the device ***************************************************************************************************/ @@ -788,8 +776,8 @@ KNXDevice.prototype = { accessoryServices.push(informationService); - iterate(this.config); -// throw new Error("STOP"); + //iterate(this.config); + if (!this.config.services){ this.log("No services found in accessory?!") } @@ -814,6 +802,9 @@ KNXDevice.prototype = { case "LockMechanism": accessoryServices.push(this.getLockMechanismService(configService)); break; + case "Switch": + accessoryServices.push(this.getSwitchService(configService)); + break; case "TemperatureSensor": accessoryServices.push(this.getTemperatureSensorService(configService)); break; diff --git a/platforms/KNX.md b/platforms/KNX.md index 22b3aee..6512835 100644 --- a/platforms/KNX.md +++ b/platforms/KNX.md @@ -19,19 +19,16 @@ You need to configure all devices directly in the config.json. "name": "Living Room North Lamp", "On": { "Set": "1/1/6", - "Listen": [ - "1/1/63" - ] + "Listen": ["1/1/63"] }, "Brightness": { "Set": "1/1/62", - "Listen": [ - "1/1/64" - ] + "Listen": ["1/1/64"] } } ] } + ] } ```` In the accessories section (the array within the brackets [ ]) you can insert as many objects as you like in the following form @@ -73,28 +70,42 @@ Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group addres # Supported Services and their characteristics +## ContactSensor +- ContactSensorState: DPT 1, 0 as contact **OR** +- ContactSensorStateContact1: DPT 1, 1 as contact + +- StatusActive: DPT 1, 1 as true +- StatusFault: DPT 1, 1 as true +- StatusTampered: DPT 1, 1 as true +- StatusLowBattery: DPT 1, 1 as true ## Lightbulb - On: DPT 1, 1 as on, 0 as off - Brightness: DPT5 percentage, 100% (=255) the brightest ## LockMechanism -- LockCurrentState: DPT 1, 1 as secured **OR (but not both:)** +- LockCurrentState: DPT 1, 1 as secured **OR (but not both:)** - LockCurrentStateSecured0: DPT 1, 0 as secured -- LockTargetState: DPT 1, 1 as secured **OR** +- LockTargetState: DPT 1, 1 as secured **OR** - LockTargetStateSecured0: DPT 1, 0 as secured +## Outlet + - On: DPT 1, 1 as on, 0 as off + - OutletInUse: DPT 1, 1 as on, 0 as off + +## Switch + - On: DPT 1, 1 as on, 0 as off + +## TemperatureSensor +- CurrentTemperature: DPT9 in °C [listen only] + ## Thermostat - CurrentTemperature: DPT9 in °C [listen only] - TargetTemperature: DPT9, values 0..40°C only, all others are ignored - CurrentHeatingCoolingState: DPT5 HVAC, because of the incompatible mapping only off and heating (=auto) are shown, [listen only] - TargetHeatingCoolingState: as above - -## TemperatureSensor -- CurrentTemperature: DPT9 in °C [listen only] - ## Window - CurrentPosition: DPT5 percentage - TargetPosition: DPT5 percentage @@ -113,16 +124,9 @@ Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group addres - CurrentVerticalTiltAngle - ObstructionDetected -## ContactSensor -- ContactSensorState: DPT 1, 0 as contact **OR** -- ContactSensorStateContact1: DPT 1, 1 as contact -- StatusActive: DPT 1, 1 as true -- StatusFault: DPT 1, 1 as true -- StatusTampered: DPT 1, 1 as true -- StatusLowBattery: DPT 1, 1 as true # DISCLAIMER -This is work in progress! +**This is work in progress!** From 91f6ccb2d4ea2d21902e0a144c8046ba77880f45 Mon Sep 17 00:00:00 2001 From: Snowdd1 Date: Fri, 18 Sep 2015 13:18:27 +0200 Subject: [PATCH 10/22] Added LightSensor Service --- accessories/knxdevice.js | 27 ++++++++++++++++++++++++--- platforms/KNX.md | 6 ++++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/accessories/knxdevice.js b/accessories/knxdevice.js index b554b9a..db93fd4 100644 --- a/accessories/knxdevice.js +++ b/accessories/knxdevice.js @@ -526,6 +526,25 @@ KNXDevice.prototype = { //iterate(myService); return myService; }, + getLightSensorService: function(config) { + + // some sanity checks + if (config.type !== "LightSensor") { + this.log("[ERROR] LightSensor Service for non 'LightSensor' service called"); + return undefined; + } + if (!config.name) { + this.log("[ERROR] LightSensor Service without 'name' property called"); + return undefined; + } + var myService = new Service.LightSensor(config.name,config.name); + // CurrentTemperature) + if (config.CurrentAmbientLightLevel) { + this.log("LightSensor CurrentAmbientLightLevel characteristic enabled"); + this.bindCharacteristic(myService, Characteristic.CurrentAmbientLightLevel, "Float", config.CurrentAmbientLightLevel); + } + return myService; + }, getLockMechanismService: function(config) { /** //this.config = config; @@ -756,10 +775,9 @@ KNXDevice.prototype = { }, + - /* assemble the device ***************************************************************************************************/ - - +/* assemble the device ***************************************************************************************************/ getServices: function() { // you can OPTIONALLY create an information service if you wish to override @@ -799,6 +817,9 @@ KNXDevice.prototype = { case "Lightbulb": accessoryServices.push(this.getLightbulbService(configService)); break; + case "LightSensor": + accessoryServices.push(this.getLightSensorService(configService)); + break; case "LockMechanism": accessoryServices.push(this.getLockMechanismService(configService)); break; diff --git a/platforms/KNX.md b/platforms/KNX.md index 6512835..937af45 100644 --- a/platforms/KNX.md +++ b/platforms/KNX.md @@ -78,15 +78,17 @@ Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group addres - StatusFault: DPT 1, 1 as true - StatusTampered: DPT 1, 1 as true - StatusLowBattery: DPT 1, 1 as true + ## Lightbulb - On: DPT 1, 1 as on, 0 as off - Brightness: DPT5 percentage, 100% (=255) the brightest - + +## LightSensor +- CurrentAmbientLightLevel: DPT 9, 0 to 100000 Lux ## LockMechanism - LockCurrentState: DPT 1, 1 as secured **OR (but not both:)** - LockCurrentStateSecured0: DPT 1, 0 as secured - - LockTargetState: DPT 1, 1 as secured **OR** - LockTargetStateSecured0: DPT 1, 0 as secured From 32877abc98fbd124cffb88083883faaa31b9b028 Mon Sep 17 00:00:00 2001 From: ilcato Date: Fri, 18 Sep 2015 17:33:36 +0200 Subject: [PATCH 11/22] Complete refactoring and adoption of new API Added support for LightSensor --- platforms/FibaroHC2.js | 540 ++++++++++++----------------------------- 1 file changed, 157 insertions(+), 383 deletions(-) diff --git a/platforms/FibaroHC2.js b/platforms/FibaroHC2.js index a1336d5..c3e92e8 100644 --- a/platforms/FibaroHC2.js +++ b/platforms/FibaroHC2.js @@ -15,6 +15,8 @@ // The default code for all HomeBridge accessories is 031-45-154. var types = require("HAP-NodeJS/accessories/types.js"); +var Service = require("HAP-NodeJS").Service; +var Characteristic = require("HAP-NodeJS").Characteristic; var request = require("request"); function FibaroHC2Platform(log, config){ @@ -47,31 +49,27 @@ FibaroHC2Platform.prototype = { json.map(function(s) { that.log("Found: " + s.type); if (s.visible == true) { - if (s.type == "com.fibaro.multilevelSwitch") { - accessory = new FibaroDimmerAccessory(that, s.name, s.id); + var accessory = null; + if (s.type == "com.fibaro.multilevelSwitch") + accessory = new FibaroDimmerAccessory(that, s); + else if (s.type == "com.fibaro.FGRM222" || s.type == "com.fibaro.FGR221") + accessory = new FibaroRollerShutterAccessory(that, s); + else if (s.type == "com.fibaro.binarySwitch" || s.type == "com.fibaro.developer.bxs.virtualBinarySwitch") + accessory = new FibaroBinarySwitchAccessory(that, s); + else if (s.type == "com.fibaro.FGMS001" || s.type == "com.fibaro.motionSensor") + accessory = new FibaroMotionSensorAccessory(that, s); + else if (s.type == "com.fibaro.temperatureSensor") + accessory = new FibaroTemperatureSensorAccessory(that, s); + else if (s.type == "com.fibaro.doorSensor") + accessory = new FibaroDoorSensorAccessory(that, s); + else if (s.type == "com.fibaro.lightSensor") + accessory = new FibaroLightSensorAccessory(that, s); + if (accessory != null) { + accessory.getServices = function() { + return that.getServices(accessory); + }; foundAccessories.push(accessory); - } else if (s.type == "com.fibaro.FGRM222") - { - accessory = new FibaroRollerShutterAccessory(that, s.name, s.id); - foundAccessories.push(accessory); - } else if (s.type == "com.fibaro.binarySwitch" || s.type == "com.fibaro.developer.bxs.virtualBinarySwitch") - { - accessory = new FibaroBinarySwitchAccessory(that, s.name, s.id); - foundAccessories.push(accessory); - } else if (s.type == "com.fibaro.FGMS001") - { - accessory = new FibaroMotionSensorAccessory(that, s.name, s.id); - foundAccessories.push(accessory); - } else if (s.type == "com.fibaro.temperatureSensor") - { - accessory = new FibaroTemperatureSensorAccessory(that, s.name, s.id); - foundAccessories.push(accessory); - } else if (s.type == "com.fibaro.doorSensor") - { - accessory = new FibaroDoorSensorAccessory(that, s.name, s.id); - foundAccessories.push(accessory); - } - + } } }) } @@ -82,49 +80,10 @@ FibaroHC2Platform.prototype = { }); }, - getAccessoryValue: function(callback, returnBoolean, that) { - var url = "http://"+that.platform.host+"/api/devices/"+that.id+"/properties/value"; - request.get({ - headers : { - "Authorization" : that.platform.auth - }, - json: true, - url: url - }, function(err, response, json) { - that.platform.log(url); - if (!err && response.statusCode == 200) { - if (returnBoolean) - callback(json.value == 0 ? 0 : 1); - else - callback(json.value); - } else { - that.platform.log("There was a problem getting value from" + that.id); - } - }) - }, - getAccessoryServices: function(that) { - var services = [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: this.informationCharacteristics(that), - }, - { - sType: that.SERVICE_TYPE, - characteristics: this.controlCharacteristics(that) - }]; - this.log("Loaded services for " + that.name) - return services; - }, command: function(c,value, that) { - if (that.doNotSet != undefined && that.doNotSet == true) { - that.doNotSet = false; - return; - } - var url = "http://"+this.host+"/api/devices/"+that.id+"/action/"+c; var body = value != undefined ? JSON.stringify({ - "args": [ - value - ] + "args": [ value ] }) : null; var method = "post"; request({ @@ -144,338 +103,153 @@ FibaroHC2Platform.prototype = { } }); }, - informationCharacteristics: function(that) - { - return [ - { - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: that.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Fibaro", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: that.MODEL_TYPE, - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "A1S2NASF88EW", - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: null, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 + getAccessoryValue: function(callback, returnBoolean, that) { + var url = "http://"+that.platform.host+"/api/devices/"+that.id+"/properties/value"; + request.get({ + headers : { + "Authorization" : that.platform.auth + }, + json: true, + url: url + }, function(err, response, json) { + that.platform.log(url); + if (!err && response.statusCode == 200) { + if (returnBoolean) + callback(undefined, json.value == 0 ? 0 : 1); + else + callback(undefined, json.value); + } else { + that.platform.log("There was a problem getting value from" + that.id); } - ] + }) }, - controlCharacteristics: function(that) { - var cTypes = []; - var l = that.CONTROL_CHARACTERISTICS.length; - for (var i = 0; i < l; i++) { - if (that.CONTROL_CHARACTERISTICS[i] == types.NAME_CTYPE) { - cTypes.push({ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: that.name, - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - }); - } else if (that.CONTROL_CHARACTERISTICS[i] == types.POWER_STATE_CTYPE) { - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - subscribeUpdate(characteristic, that, true); - }, - onUpdate: function(value) { - if (value == 0) { - that.platform.command("turnOff", null, that) - } else { - that.platform.command("turnOn", null, that) - } - }, - onRead: function(callback) { - that.platform.getAccessoryValue(callback, true, that); - }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1 - }); - } else if (that.CONTROL_CHARACTERISTICS[i] == types.BRIGHTNESS_CTYPE) { - cTypes.push({ - cType: types.BRIGHTNESS_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - subscribeUpdate(characteristic, that, false); - }, - onUpdate: function(value) { that.platform.command("setValue", value, that); }, - onRead: function(callback) { - that.platform.getAccessoryValue(callback, false, that); - }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust Brightness of Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 10, - unit: "%" - }); - } else if (that.CONTROL_CHARACTERISTICS[i] == types.WINDOW_COVERING_CURRENT_POSITION_CTYPE) { - cTypes.push({ - cType: types.WINDOW_COVERING_CURRENT_POSITION_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - subscribeUpdate(characteristic, that, false); - }, - onRead: function(callback) { - that.platform.getAccessoryValue(callback, false, that); - }, - perms: ["pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Blind Position", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }); - } else if (that.CONTROL_CHARACTERISTICS[i] == types.WINDOW_COVERING_TARGET_POSITION_CTYPE) { - cTypes.push({ - cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - subscribeUpdate(characteristic, that, false); - }, - onUpdate: function(value) { that.platform.command("setValue", value, that); }, - onRead: function(callback) { - that.platform.getAccessoryValue(callback, false, that); - }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Target Blind Position", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }); - } else if (that.CONTROL_CHARACTERISTICS[i] == types.WINDOW_COVERING_OPERATION_STATE_CTYPE) { - cTypes.push({ - cType: types.WINDOW_COVERING_OPERATION_STATE_CTYPE, - perms: ["pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Position State", - designedMinValue: 0, - designedMaxValue: 2, - designedMinStep: 1, - }); - } else if (that.CONTROL_CHARACTERISTICS[i] == types.CURRENT_TEMPERATURE_CTYPE) { - cTypes.push({ - cType: types.CURRENT_TEMPERATURE_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - subscribeUpdate(characteristic, that, false); - }, - onRead: function(callback) { - that.platform.getAccessoryValue(callback, false, that); - }, - perms: ["pr","ev"], - format: "float", - unit: "celsius", - stepValue: 0.1, - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Get current temperature" - }); - } else if (that.CONTROL_CHARACTERISTICS[i] == types.MOTION_DETECTED_CTYPE) { - cTypes.push({ - cType: types.MOTION_DETECTED_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - subscribeUpdate(characteristic, that, true); - }, - onRead: function(callback) { - that.platform.getAccessoryValue(callback, true, that); - }, - perms: ["pr","ev"], - format: "bool", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Detect motion", - designedMaxLength: 1 - }); - } else if (that.CONTROL_CHARACTERISTICS[i] == types.CONTACT_SENSOR_STATE_CTYPE) { - cTypes.push({ - cType: types.CONTACT_SENSOR_STATE_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - subscribeUpdate(characteristic, that, true); - }, - onRead: function(callback) { - that.platform.getAccessoryValue(callback, true, that); - }, - perms: ["pr","ev"], - format: "bool", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Detect door contact", - designedMaxLength: 1 - }); - } - } - return cTypes - } + getInformationService: function(homebridgeAccessory) { + var informationService = new Service.AccessoryInformation(); + informationService + .setCharacteristic(Characteristic.Name, homebridgeAccessory.name) + .setCharacteristic(Characteristic.Manufacturer, homebridgeAccessory.manufacturer) + .setCharacteristic(Characteristic.Model, homebridgeAccessory.model) + .setCharacteristic(Characteristic.SerialNumber, homebridgeAccessory.serialNumber); + return informationService; + }, + bindCharacteristicEvents: function(characteristic, homebridgeAccessory) { + var onOff = characteristic.format == "bool" ? true : false; + var readOnly = characteristic.writable == undefined || characteristic.writable == false ? true : false; + subscribeUpdate(characteristic, homebridgeAccessory, onOff); + if (!readOnly) { + characteristic + .on('set', function(value, callback, context) { + if( context !== 'fromFibaro' ) { + if (onOff) + homebridgeAccessory.platform.command(value == 0 ? "turnOff": "turnOn", null, homebridgeAccessory); + else + homebridgeAccessory.platform.command("setValue", value, homebridgeAccessory); + } + callback(); + }.bind(this) ); + } + characteristic + .on('get', function(callback) { + homebridgeAccessory.platform.getAccessoryValue(callback, onOff, homebridgeAccessory); + }.bind(this) ); + }, + getServices: function(homebridgeAccessory) { + var informationService = homebridgeAccessory.platform.getInformationService(homebridgeAccessory); + for (var i=0; i < homebridgeAccessory.characteristics.length; i++) { + var characteristic = homebridgeAccessory.controlService.getCharacteristic(homebridgeAccessory.characteristics[i]); + if (characteristic == undefined) + characteristic = homebridgeAccessory.controlService.addCharacteristic(homebridgeAccessory.characteristics[i]); + homebridgeAccessory.platform.bindCharacteristicEvents(characteristic, homebridgeAccessory); + } + + return [informationService, homebridgeAccessory.controlService]; + } } -function FibaroDimmerAccessory(platform, name, id) { - // device info - this.platform = platform; - this.name = name; - this.id = id; - this.MODEL_TYPE = "Dimmer"; - this.SERVICE_TYPE = types.LIGHTBULB_STYPE; - this.CONTROL_CHARACTERISTICS = [types.NAME_CTYPE, types.POWER_STATE_CTYPE, types.BRIGHTNESS_CTYPE]; +function FibaroDimmerAccessory(platform, s) { + this.platform = platform; + this.remoteAccessory = s; + this.id = s.id; + this.name = s.name; + this.model = s.type; + this.manufacturer = "Fibaro"; + this.serialNumber = ""; + this.controlService = new Service.Lightbulb(this.name); + this.characteristics = [Characteristic.On, Characteristic.Brightness]; } -FibaroDimmerAccessory.prototype = { - getServices: function() { - return this.platform.getAccessoryServices(this); - } -}; - -function FibaroRollerShutterAccessory(platform, name, id) { - // device info - this.platform = platform; - this.name = name; - this.id = id; - this.MODEL_TYPE = "Roller Shutter 2"; - this.SERVICE_TYPE = types.WINDOW_COVERING_STYPE; - this.CONTROL_CHARACTERISTICS = [types.NAME_CTYPE, types.WINDOW_COVERING_CURRENT_POSITION_CTYPE, types.WINDOW_COVERING_TARGET_POSITION_CTYPE, types.WINDOW_COVERING_OPERATION_STATE_CTYPE]; - +function FibaroRollerShutterAccessory(platform, s) { + this.platform = platform; + this.remoteAccessory = s; + this.id = s.id; + this.name = s.name; + this.model = s.type; + this.manufacturer = "Fibaro"; + this.serialNumber = ""; + this.controlService = new Service.WindowCovering(this.name); + this.characteristics = [Characteristic.CurrentPosition, Characteristic.TargetPosition, Characteristic.PositionState]; } -FibaroRollerShutterAccessory.prototype = { - getServices: function() { - return this.platform.getAccessoryServices(this); - } -}; - -function FibaroBinarySwitchAccessory(platform, name, id) { - // device info - this.platform = platform; - this.name = name; - this.id = id; - this.MODEL_TYPE = "Binary Switch"; - this.SERVICE_TYPE = types.SWITCH_STYPE; - this.CONTROL_CHARACTERISTICS = [types.NAME_CTYPE, types.POWER_STATE_CTYPE]; +function FibaroBinarySwitchAccessory(platform, s) { + this.platform = platform; + this.remoteAccessory = s; + this.id = s.id; + this.name = s.name; + this.model = s.type; + this.manufacturer = "Fibaro"; + this.serialNumber = ""; + this.controlService = new Service.Switch(this.name); + this.characteristics = [Characteristic.On]; } -FibaroBinarySwitchAccessory.prototype = { - getServices: function() { - return this.platform.getAccessoryServices(this); - } -}; - -function FibaroTemperatureSensorAccessory(platform, name, id) { - // device info - this.platform = platform; - this.name = name; - this.id = id; - this.MODEL_TYPE = "Temperature Sensor"; - this.SERVICE_TYPE = types.TEMPERATURE_SENSOR_STYPE; - this.CONTROL_CHARACTERISTICS = [types.NAME_CTYPE, types.CURRENT_TEMPERATURE_CTYPE]; +function FibaroMotionSensorAccessory(platform, s) { + this.platform = platform; + this.remoteAccessory = s; + this.id = s.id; + this.name = s.name; + this.model = "Motion Sensor"; + this.manufacturer = "Fibaro"; + this.serialNumber = ""; + this.controlService = new Service.MotionSensor(this.name); + this.characteristics = [Characteristic.MotionDetected]; } -FibaroTemperatureSensorAccessory.prototype = { - getServices: function() { - return this.platform.getAccessoryServices(this); - } -}; - -function FibaroMotionSensorAccessory(platform, name, id) { - // device info - this.platform = platform; - this.name = name; - this.id = id; - this.MODEL_TYPE = "Motion Sensor"; - this.SERVICE_TYPE = types.MOTION_SENSOR_STYPE; - this.CONTROL_CHARACTERISTICS = [types.NAME_CTYPE, types.MOTION_DETECTED_CTYPE]; +function FibaroTemperatureSensorAccessory(platform, s) { + this.platform = platform; + this.remoteAccessory = s; + this.id = s.id; + this.name = s.name; + this.model = s.type; + this.manufacturer = "Fibaro"; + this.serialNumber = ""; + this.controlService = new Service.TemperatureSensor(this.name); + this.characteristics = [Characteristic.CurrentTemperature]; } -FibaroMotionSensorAccessory.prototype = { - getServices: function() { - return this.platform.getAccessoryServices(this); - } -}; - -function FibaroDoorSensorAccessory(platform, name, id) { - // device info - this.platform = platform; - this.name = name; - this.id = id; - this.MODEL_TYPE = "Door Sensor"; - this.SERVICE_TYPE = types.CONTACT_SENSOR_STYPE; - this.CONTROL_CHARACTERISTICS = [types.NAME_CTYPE, types.CONTACT_SENSOR_STATE_CTYPE]; +function FibaroDoorSensorAccessory(platform, s) { + this.platform = platform; + this.remoteAccessory = s; + this.id = s.id; + this.name = s.name; + this.model = s.type; + this.manufacturer = "Fibaro"; + this.serialNumber = ""; + this.controlService = new Service.ContactSensor(this.name); + this.characteristics = [Characteristic.ContactSensorState]; +} + +function FibaroLightSensorAccessory(platform, s) { + this.platform = platform; + this.remoteAccessory = s; + this.id = s.id; + this.name = s.name; + this.model = s.type; + this.manufacturer = "Fibaro"; + this.serialNumber = ""; + this.controlService = new Service.LightSensor(this.name); + this.characteristics = [Characteristic.CurrentAmbientLightLevel]; } -FibaroDoorSensorAccessory.prototype = { - getServices: function() { - return this.platform.getAccessoryServices(this); - } -}; var lastPoll=0; var pollingUpdateRunning = false; @@ -507,11 +281,10 @@ function startPollingUpdate( platform ) for (i=0;i Date: Sat, 19 Sep 2015 10:12:35 +0200 Subject: [PATCH 12/22] Added support for Fibaro Outlet --- platforms/FibaroHC2.js | 94 ++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 59 deletions(-) diff --git a/platforms/FibaroHC2.js b/platforms/FibaroHC2.js index c3e92e8..c18d0dd 100644 --- a/platforms/FibaroHC2.js +++ b/platforms/FibaroHC2.js @@ -64,10 +64,20 @@ FibaroHC2Platform.prototype = { accessory = new FibaroDoorSensorAccessory(that, s); else if (s.type == "com.fibaro.lightSensor") accessory = new FibaroLightSensorAccessory(that, s); + else if (s.type == "com.fibaro.FGWP101") + accessory = new FibaroOutletAccessory(that, s); + if (accessory != null) { accessory.getServices = function() { return that.getServices(accessory); }; + accessory.platform = that; + accessory.remoteAccessory = s; + accessory.id = s.id; + accessory.name = s.name; + accessory.model = s.type; + accessory.manufacturer = "Fibaro"; + accessory.serialNumber = ""; foundAccessories.push(accessory); } } @@ -103,23 +113,30 @@ FibaroHC2Platform.prototype = { } }); }, - getAccessoryValue: function(callback, returnBoolean, that) { - var url = "http://"+that.platform.host+"/api/devices/"+that.id+"/properties/value"; + getAccessoryValue: function(callback, returnBoolean, homebridgeAccessory, powerValue) { + var url = "http://"+homebridgeAccessory.platform.host+"/api/devices/"+homebridgeAccessory.id+"/properties/"; + if (powerValue) + url = url + "power"; + else + url = url + "value"; + request.get({ headers : { - "Authorization" : that.platform.auth + "Authorization" : homebridgeAccessory.platform.auth }, json: true, url: url }, function(err, response, json) { - that.platform.log(url); + homebridgeAccessory.platform.log(url); if (!err && response.statusCode == 200) { - if (returnBoolean) + if (powerValue) { + callback(undefined, parseFloat(json.value) > 1.0 ? true : false); + } else if (returnBoolean) callback(undefined, json.value == 0 ? 0 : 1); else callback(undefined, json.value); } else { - that.platform.log("There was a problem getting value from" + that.id); + homebridgeAccessory.platform.log("There was a problem getting value from" + homebridgeAccessory.id); } }) }, @@ -135,6 +152,7 @@ FibaroHC2Platform.prototype = { bindCharacteristicEvents: function(characteristic, homebridgeAccessory) { var onOff = characteristic.format == "bool" ? true : false; var readOnly = characteristic.writable == undefined || characteristic.writable == false ? true : false; + var powerValue = (characteristic.UUID == "00000026-0000-1000-8000-0026BB765291") ? true : false; subscribeUpdate(characteristic, homebridgeAccessory, onOff); if (!readOnly) { characteristic @@ -150,7 +168,7 @@ FibaroHC2Platform.prototype = { } characteristic .on('get', function(callback) { - homebridgeAccessory.platform.getAccessoryValue(callback, onOff, homebridgeAccessory); + homebridgeAccessory.platform.getAccessoryValue(callback, onOff, homebridgeAccessory, powerValue); }.bind(this) ); }, getServices: function(homebridgeAccessory) { @@ -167,89 +185,45 @@ FibaroHC2Platform.prototype = { } function FibaroDimmerAccessory(platform, s) { - this.platform = platform; - this.remoteAccessory = s; - this.id = s.id; - this.name = s.name; - this.model = s.type; - this.manufacturer = "Fibaro"; - this.serialNumber = ""; this.controlService = new Service.Lightbulb(this.name); this.characteristics = [Characteristic.On, Characteristic.Brightness]; } function FibaroRollerShutterAccessory(platform, s) { - this.platform = platform; - this.remoteAccessory = s; - this.id = s.id; - this.name = s.name; - this.model = s.type; - this.manufacturer = "Fibaro"; - this.serialNumber = ""; this.controlService = new Service.WindowCovering(this.name); this.characteristics = [Characteristic.CurrentPosition, Characteristic.TargetPosition, Characteristic.PositionState]; } function FibaroBinarySwitchAccessory(platform, s) { - this.platform = platform; - this.remoteAccessory = s; - this.id = s.id; - this.name = s.name; - this.model = s.type; - this.manufacturer = "Fibaro"; - this.serialNumber = ""; this.controlService = new Service.Switch(this.name); this.characteristics = [Characteristic.On]; } function FibaroMotionSensorAccessory(platform, s) { - this.platform = platform; - this.remoteAccessory = s; - this.id = s.id; - this.name = s.name; - this.model = "Motion Sensor"; - this.manufacturer = "Fibaro"; - this.serialNumber = ""; this.controlService = new Service.MotionSensor(this.name); this.characteristics = [Characteristic.MotionDetected]; } function FibaroTemperatureSensorAccessory(platform, s) { - this.platform = platform; - this.remoteAccessory = s; - this.id = s.id; - this.name = s.name; - this.model = s.type; - this.manufacturer = "Fibaro"; - this.serialNumber = ""; this.controlService = new Service.TemperatureSensor(this.name); this.characteristics = [Characteristic.CurrentTemperature]; } function FibaroDoorSensorAccessory(platform, s) { - this.platform = platform; - this.remoteAccessory = s; - this.id = s.id; - this.name = s.name; - this.model = s.type; - this.manufacturer = "Fibaro"; - this.serialNumber = ""; this.controlService = new Service.ContactSensor(this.name); this.characteristics = [Characteristic.ContactSensorState]; } function FibaroLightSensorAccessory(platform, s) { - this.platform = platform; - this.remoteAccessory = s; - this.id = s.id; - this.name = s.name; - this.model = s.type; - this.manufacturer = "Fibaro"; - this.serialNumber = ""; this.controlService = new Service.LightSensor(this.name); this.characteristics = [Characteristic.CurrentAmbientLightLevel]; } +function FibaroOutletAccessory(platform, s) { + this.controlService = new Service.Outlet(this.name); + this.characteristics = [Characteristic.On, Characteristic.OutletInUse]; +} + var lastPoll=0; var pollingUpdateRunning = false; @@ -281,10 +255,12 @@ function startPollingUpdate( platform ) for (i=0;i 1.0 ? true : false, undefined, 'fromFibaro'); + } else if ((subscription.onOff && typeof(value) == "boolean") || !subscription.onOff) + subscription.characteristic.setValue(value, undefined, 'fromFibaro'); else - subscription.characteristic.setValue(value == 0 ? false : true, undefined, 'fromFibaro'); + subscription.characteristic.setValue(value == 0 ? false : true, undefined, 'fromFibaro'); } } } From 387e7ec9cebd750aa9dcbd4a06178d5edb64b85d Mon Sep 17 00:00:00 2001 From: Snowdd1 Date: Sat, 19 Sep 2015 15:19:11 +0200 Subject: [PATCH 13/22] KNX support for Garage Door Opener The garage door opener device MUST adhere to HomeKit numeric conventions, see KNX.md documentation --- accessories/knxdevice.js | 90 +++++++++++++++++++++++++++++++++++++--- platforms/KNX.md | 71 +++++++++++++++++++++---------- 2 files changed, 134 insertions(+), 27 deletions(-) diff --git a/accessories/knxdevice.js b/accessories/knxdevice.js index db93fd4..f94f5b2 100644 --- a/accessories/knxdevice.js +++ b/accessories/knxdevice.js @@ -322,6 +322,21 @@ KNXDevice.prototype = { this.knxwrite(callback, gaddress,'DPT5',numericValue); } }, + setInt: function(value, callback, context, gaddress) { + if (context === 'fromKNXBus') { + this.log("event ping pong, exit!"); + if (callback) { + callback(); + } + } else { + var numericValue = 0; + if (value && value>=0 && value<=255) { + numericValue = value; // assure 1..255 for KNX bus + } + this.log("Setting "+gaddress+" int to %s (%s)", value, numericValue); + this.knxwrite(callback, gaddress,'DPT5',numericValue); + } + }, setFloat: function(value, callback, context, gaddress) { if (context === 'fromKNXBus') { this.log(gaddress + " event ping pong, exit!"); @@ -406,6 +421,11 @@ KNXDevice.prototype = { this.setFloat(value, callback, context, config.Set); }.bind(this)); break; + case "Int": + myCharacteristic.on('set', function(value, callback, context) { + this.setInt(value, callback, context, config.Set); + }.bind(this)); + break; case "HVAC": myCharacteristic.on('set', function(value, callback, context) { this.setHVACState(value, callback, context, config.Set); @@ -432,6 +452,10 @@ KNXDevice.prototype = { case "Float": this.knxregister_float([config.Set].concat(config.Listen || []), myCharacteristic); break; + case "Int": + // use float as return type for ints, for we don't care + this.knxregister_float([config.Set].concat(config.Listen || []), myCharacteristic); + break; case "HVAC": this.knxregister_HVAC([config.Set].concat(config.Listen || []), myCharacteristic); break; @@ -498,6 +522,62 @@ KNXDevice.prototype = { } return myService; }, + getGarageDoorOpenerService: function(config) { +// // Required Characteristics +// this.addCharacteristic(Characteristic.CurrentDoorState); +// this.addCharacteristic(Characteristic.TargetDoorState); +// this.addCharacteristic(Characteristic.ObstructionDetected); +// Characteristic.CurrentDoorState.OPEN = 0; +// Characteristic.CurrentDoorState.CLOSED = 1; +// Characteristic.CurrentDoorState.OPENING = 2; +// Characteristic.CurrentDoorState.CLOSING = 3; +// Characteristic.CurrentDoorState.STOPPED = 4; +// // +// // Optional Characteristics +// this.addOptionalCharacteristic(Characteristic.LockCurrentState); +// this.addOptionalCharacteristic(Characteristic.LockTargetState); + // The value property of LockCurrentState must be one of the following: +// Characteristic.LockCurrentState.UNSECURED = 0; +// Characteristic.LockCurrentState.SECURED = 1; +// Characteristic.LockCurrentState.JAMMED = 2; +// Characteristic.LockCurrentState.UNKNOWN = 3; + + // some sanity checks + if (config.type !== "GarageDoorOpener") { + this.log("[ERROR] GarageDoorOpener Service for non 'GarageDoorOpener' service called"); + return undefined; + } + if (!config.name) { + this.log("[ERROR] GarageDoorOpener Service without 'name' property called"); + return undefined; + } + + var myService = new Service.GarageDoorOpener(config.name,config.name); + if (config.CurrentDoorState) { + this.log("GarageDoorOpener CurrentDoorState characteristic enabled"); + this.bindCharacteristic(myService, Characteristic.CurrentDoorState, "Int", config.CurrentDoorState); + } + if (config.TargetDoorState) { + this.log("GarageDoorOpener TargetDoorState characteristic enabled"); + this.bindCharacteristic(myService, Characteristic.TargetDoorState, "Int", config.TargetDoorState); + } + if (config.ObstructionDetected) { + this.log("GarageDoorOpener ObstructionDetected characteristic enabled"); + this.bindCharacteristic(myService, Characteristic.ObstructionDetected, "Bool", config.ObstructionDetected); + } + //optionals + if (config.LockCurrentState) { + this.log("GarageDoorOpener LockCurrentState characteristic enabled"); + myService.addCharacteristic(Characteristic.LockCurrentState); + this.bindCharacteristic(myService, Characteristic.LockCurrentState, "Int", config.LockCurrentState); + } + if (config.LockTargetState) { + this.log("GarageDoorOpener LockTargetState characteristic enabled"); + myService.addCharacteristic(Characteristic.LockTargetState); + this.bindCharacteristic(myService, Characteristic.LockTargetState, "Bool", config.LockTargetState); + } + return myService; + }, getLightbulbService: function(config) { // some sanity checks //this.config = config; @@ -657,11 +737,6 @@ KNXDevice.prototype = { // TargetTemperature if available if (config.TargetTemperature) { this.log("Thermostat TargetTemperature characteristic enabled"); - - // DEBUG - console.log("default value: " + myService.getCharacteristic(Characteristic.TargetTemperature).value); - // DEBUG - // default boundary too narrow for thermostats myService.getCharacteristic(Characteristic.TargetTemperature).minimumValue=0; // °C myService.getCharacteristic(Characteristic.TargetTemperature).maximumValue=40; // °C @@ -790,7 +865,7 @@ KNXDevice.prototype = { informationService .setCharacteristic(Characteristic.Manufacturer, "Opensource Community") .setCharacteristic(Characteristic.Model, "KNX Universal Device") - .setCharacteristic(Characteristic.SerialNumber, "Version 1.1"); + .setCharacteristic(Characteristic.SerialNumber, "Version 1.1.2"); accessoryServices.push(informationService); @@ -814,6 +889,9 @@ KNXDevice.prototype = { case "ContactSensor": accessoryServices.push(this.getContactSenserService(configService)); break; + case "GarageDoorOpener": + accessoryServices.push(this.getGarageDoorOpenerService(configService)); + break; case "Lightbulb": accessoryServices.push(this.getLightbulbService(configService)); break; diff --git a/platforms/KNX.md b/platforms/KNX.md index 937af45..2eba871 100644 --- a/platforms/KNX.md +++ b/platforms/KNX.md @@ -71,47 +71,76 @@ Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group addres # Supported Services and their characteristics ## ContactSensor -- ContactSensorState: DPT 1, 0 as contact **OR** -- ContactSensorStateContact1: DPT 1, 1 as contact +- ContactSensorState: DPT 1.002, 0 as contact **OR** +- ContactSensorStateContact1: DPT 1.002, 1 as contact + +- StatusActive: DPT 1.011, 1 as true +- StatusFault: DPT 1.011, 1 as true +- StatusTampered: DPT 1.011, 1 as true +- StatusLowBattery: DPT 1.011, 1 as true + +## GarageDoorOpener +- CurrentDoorState: DPT5 integer value in range 0..4 + // Characteristic.CurrentDoorState.OPEN = 0; + // Characteristic.CurrentDoorState.CLOSED = 1; + // Characteristic.CurrentDoorState.OPENING = 2; + // Characteristic.CurrentDoorState.CLOSING = 3; + // Characteristic.CurrentDoorState.STOPPED = 4; + +- TargetDoorState: DPT5 integer value in range 0..1 + // Characteristic.TargetDoorState.OPEN = 0; + // Characteristic.TargetDoorState.CLOSED = 1; + +- ObstructionDetected: DPT1, 1 as true + +- LockCurrentState: DPT5 integer value in range 0..3 + // Characteristic.LockCurrentState.UNSECURED = 0; + // Characteristic.LockCurrentState.SECURED = 1; + // Characteristic.LockCurrentState.JAMMED = 2; + // Characteristic.LockCurrentState.UNKNOWN = 3; + +- LockTargetState: DPT5 integer value in range 0..1 + // Characteristic.LockTargetState.UNSECURED = 0; + // Characteristic.LockTargetState.SECURED = 1; + -- StatusActive: DPT 1, 1 as true -- StatusFault: DPT 1, 1 as true -- StatusTampered: DPT 1, 1 as true -- StatusLowBattery: DPT 1, 1 as true ## Lightbulb - - On: DPT 1, 1 as on, 0 as off - - Brightness: DPT5 percentage, 100% (=255) the brightest + - On: DPT 1.001, 1 as on, 0 as off + - Brightness: DPT5.001 percentage, 100% (=255) the brightest ## LightSensor -- CurrentAmbientLightLevel: DPT 9, 0 to 100000 Lux +- CurrentAmbientLightLevel: DPT 9.004, 0 to 100000 Lux -## LockMechanism +## LockMechanism (This is poorly mapped!) - LockCurrentState: DPT 1, 1 as secured **OR (but not both:)** - LockCurrentStateSecured0: DPT 1, 0 as secured - LockTargetState: DPT 1, 1 as secured **OR** - LockTargetStateSecured0: DPT 1, 0 as secured +*ToDo here: correction of mappings, HomeKit reqires lock states UNSECURED=0, SECURED=1, JAMMED = 2, UNKNOWN=3* + + ## Outlet - - On: DPT 1, 1 as on, 0 as off - - OutletInUse: DPT 1, 1 as on, 0 as off + - On: DPT 1.001, 1 as on, 0 as off + - OutletInUse: DPT 1.011, 1 as on, 0 as off ## Switch - - On: DPT 1, 1 as on, 0 as off + - On: DPT 1.001, 1 as on, 0 as off ## TemperatureSensor -- CurrentTemperature: DPT9 in °C [listen only] +- CurrentTemperature: DPT9.001 in °C [listen only] ## Thermostat -- CurrentTemperature: DPT9 in °C [listen only] -- TargetTemperature: DPT9, values 0..40°C only, all others are ignored -- CurrentHeatingCoolingState: DPT5 HVAC, because of the incompatible mapping only off and heating (=auto) are shown, [listen only] -- TargetHeatingCoolingState: as above +- CurrentTemperature: DPT9.001 in °C [listen only] +- TargetTemperature: DPT9.001, values 0..40°C only, all others are ignored +- CurrentHeatingCoolingState: DPT20.102 HVAC, because of the incompatible mapping only off and heating (=auto) are shown, [listen only] +- TargetHeatingCoolingState: DPT20.102 HVAC, as above ## Window -- CurrentPosition: DPT5 percentage -- TargetPosition: DPT5 percentage -- PositionState: DPT5 value [listen only] +- CurrentPosition: DPT5.001 percentage +- TargetPosition: DPT5.001 percentage +- PositionState: DPT5.005 value [listen only: 0 Increasing, 1 Decreasing, 2 Stopped] ## WindowCovering - CurrentPosition: DPT5 percentage From eec663a5c8e1a6c69d7388686edc664a88460163 Mon Sep 17 00:00:00 2001 From: Snowdd1 Date: Sat, 19 Sep 2015 18:37:19 +0200 Subject: [PATCH 14/22] Routines for unsigned int values, such as enumeration like types --- accessories/knxdevice.js | 44 +++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/accessories/knxdevice.js b/accessories/knxdevice.js index f94f5b2..6c95a1a 100644 --- a/accessories/knxdevice.js +++ b/accessories/knxdevice.js @@ -8,9 +8,11 @@ new features include: - Window - WindowCovering - ContactSensor -New 2015-0918: +New 2015-09-18: - Services Switch and Outlet - Code cleanup +New 2015-09-19: +- GarageDoorOpener Service * */ var Service = require("HAP-NodeJS").Service; @@ -148,20 +150,7 @@ KNXDevice.prototype = { this.knxread (groupAddresses); } }, -/** Write special type routines - * - */ - // special types - knxwrite_percent: function(callback, groupAddress, value) { - var numericValue = 0; - if (value && value>=0 && value <= 100) { - numericValue = 255*value/100; // convert 1..100 to 1..255 for KNX bus - } else { - this.log("[ERROR] Percentage value ot of bounds "); - numericValue = 0; - } - this.knxwrite(callback, groupAddress,'DPT5',numericValue); - }, + /** Registering routines * */ @@ -210,11 +199,22 @@ KNXDevice.prototype = { this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); var hk_value = Math.round(val*10)/10; if (hk_value>=characteristic.minimumValue && hk_value<=characteristic.maximumValue) { - characteristic.setValue(hk_value, undefined, 'fromKNXBus'); // 1 decoimal for HomeKit + characteristic.setValue(hk_value, undefined, 'fromKNXBus'); // 1 decimal for HomeKit } else { this.log("Value %s out of bounds %s...%s ",hk_value, characteristic.minimumValue, characteristic.maximumValue); } - + }.bind(this)); + }, + //integer + knxregister_int: function(addresses, characteristic) { + this.log("knx registering FLOAT " + addresses); + knxd_registerGA(addresses, function(val, src, dest, type){ + this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); + if (val>=(characteristic.minimumValue || 0) && val<=(characteristic.maximumValue || 255) { + characteristic.setValue(val, undefined, 'fromKNXBus'); + } else { + this.log("Value %s out of bounds %s...%s ",hk_value, (characteristic.minimumValue || 0), (characteristic.maximumValue || 255)); + } }.bind(this)); }, knxregister_HVAC: function(addresses, characteristic) { @@ -431,9 +431,10 @@ KNXDevice.prototype = { this.setHVACState(value, callback, context, config.Set); }.bind(this)); break; - default: + default: { this.log("[ERROR] unknown type passed"); - throw new Error("[ERROR] unknown type passed"); + throw new Error("[ERROR] unknown type passed"); + } } } if ([config.Set].concat(config.Listen || []).length>0) { @@ -453,8 +454,7 @@ KNXDevice.prototype = { this.knxregister_float([config.Set].concat(config.Listen || []), myCharacteristic); break; case "Int": - // use float as return type for ints, for we don't care - this.knxregister_float([config.Set].concat(config.Listen || []), myCharacteristic); + this.knxregister_int([config.Set].concat(config.Listen || []), myCharacteristic); break; case "HVAC": this.knxregister_HVAC([config.Set].concat(config.Listen || []), myCharacteristic); @@ -559,6 +559,8 @@ KNXDevice.prototype = { } if (config.TargetDoorState) { this.log("GarageDoorOpener TargetDoorState characteristic enabled"); + //myService.getCharacteristic(Characteristic.TargetDoorState).minimumValue=0; // + //myService.getCharacteristic(Characteristic.TargetDoorState).maximumValue=4; // this.bindCharacteristic(myService, Characteristic.TargetDoorState, "Int", config.TargetDoorState); } if (config.ObstructionDetected) { From 84c51aa27a7162423f7568233e623e06219f1a8e Mon Sep 17 00:00:00 2001 From: Snowdd1 Date: Sat, 19 Sep 2015 21:24:55 +0200 Subject: [PATCH 15/22] GarageDoorOpener fixed --- accessories/knxdevice.js | 2 +- platforms/KNX-sample-config.json | 56 +++++++++++++++++++++----------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/accessories/knxdevice.js b/accessories/knxdevice.js index 6c95a1a..642d925 100644 --- a/accessories/knxdevice.js +++ b/accessories/knxdevice.js @@ -210,7 +210,7 @@ KNXDevice.prototype = { this.log("knx registering FLOAT " + addresses); knxd_registerGA(addresses, function(val, src, dest, type){ this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); - if (val>=(characteristic.minimumValue || 0) && val<=(characteristic.maximumValue || 255) { + if (val>=(characteristic.minimumValue || 0) && val<=(characteristic.maximumValue || 255)) { characteristic.setValue(val, undefined, 'fromKNXBus'); } else { this.log("Value %s out of bounds %s...%s ",hk_value, (characteristic.minimumValue || 0), (characteristic.maximumValue || 255)); diff --git a/platforms/KNX-sample-config.json b/platforms/KNX-sample-config.json index d74a14f..2c29736 100644 --- a/platforms/KNX-sample-config.json +++ b/platforms/KNX-sample-config.json @@ -7,8 +7,8 @@ }, "description": "This is an example configuration file for KNX platform shim", "hint": "Always paste into jsonlint.com validation page before starting your homebridge, saves a lot of frustration", - "hint2":"Replace all group addresses by current addresses of your installation, these are arbitrary examples!", - "hint3":"For valid services and their characteristics have a look at the knxdevice.md file in folder accessories!" + "hint2": "Replace all group addresses by current addresses of your installation, these are arbitrary examples!", + "hint3": "For valid services and their characteristics have a look at the knxdevice.md file in folder accessories!", "platforms": [ { "platform": "KNX", @@ -74,7 +74,7 @@ }, { "accessory_type": "knxdevice", - "description":"sample device with multiple services. Multiple services of different types are widely supported", + "description": "sample device with multiple services. Multiple services of different types are widely supported", "name": "Office", "services": [ { @@ -102,11 +102,11 @@ "type": "WindowCovering", "description": "iOS9 Window covering (blinds etc) type, still WIP", "name": "Blinds", - "Target": { + "TargetPosition": { "Set": "1/2/3", "Listen": "1/2/4" }, - "Current": { + "CurrentPosition": { "Set": "1/3/1", "Listen": "1/3/2" }, @@ -115,22 +115,40 @@ } } ] - },{ - "accessory_type": "knxdevice", - - "description":"sample contact sensor device", - "name": "Office", - "services": [ - { - "type": "ContactSensor", - "name": "Office Door", - "ContactSensorState": { - "Listen": "5/3/5" + }, + { + "accessory_type": "knxdevice", + "description": "sample contact sensor device", + "name": "Office Contact", + "services": [ + { + "type": "ContactSensor", + "name": "Office Door", + "ContactSensorState": { + "Listen": "5/3/5" + } } - }, - + ] + }, + { + "accessory_type": "knxdevice", + "description": "sample garage door opener", + "name": "Office Garage", + "services": [ + { + "type": "GarageDoorOpener", + "name": "Office Garage Opener", + "CurrentDoorState": { + "Listen": "5/4/5" + }, + "TargetDoorState": { + "Listen": "5/4/6" + } + } + ] + } ] } ], "accessories": [] -} +} \ No newline at end of file From 48f613f8ae1be2b80fcad54d9a2de965cc6db8f9 Mon Sep 17 00:00:00 2001 From: Snowdd1 Date: Sat, 19 Sep 2015 21:47:07 +0200 Subject: [PATCH 16/22] New Motion Sensor service --- accessories/knxdevice.js | 48 +++++++++++++++++++++++++++++++++++++++- platforms/KNX.md | 7 ++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/accessories/knxdevice.js b/accessories/knxdevice.js index 642d925..9ce2b5e 100644 --- a/accessories/knxdevice.js +++ b/accessories/knxdevice.js @@ -12,7 +12,8 @@ New 2015-09-18: - Services Switch and Outlet - Code cleanup New 2015-09-19: -- GarageDoorOpener Service +- GarageDoorOpener Service +- MotionSensor Service * */ var Service = require("HAP-NodeJS").Service; @@ -666,6 +667,48 @@ KNXDevice.prototype = { //iterate(myService); return myService; }, + getMotionSenserService: function(config) { +// Characteristic.ContactSensorState.CONTACT_DETECTED = 0; +// Characteristic.ContactSensorState.CONTACT_NOT_DETECTED = 1; + + // some sanity checks + if (config.type !== "MotionSensor") { + this.log("[ERROR] MotionSensor Service for non 'MotionSensor' service called"); + return undefined; + } + if (!config.name) { + this.log("[ERROR] MotionSensor Service without 'name' property called"); + return undefined; + } + + var myService = new Service.MotionSensor(config.name,config.name); + if (config.ContactSensorState) { + this.log("MotionSensor MotionDetected characteristic enabled"); + this.bindCharacteristic(myService, Characteristic.MotionDetected, "Bool", config.MotionDetected); + } + //optionals + if (config.StatusActive) { + this.log("MotionSensor StatusActive characteristic enabled"); + myService.addCharacteristic(Characteristic.StatusActive); + this.bindCharacteristic(myService, Characteristic.StatusActive, "Bool", config.StatusActive); + } + if (config.StatusFault) { + this.log("MotionSensor StatusFault characteristic enabled"); + myService.addCharacteristic(Characteristic.StatusFault); + this.bindCharacteristic(myService, Characteristic.StatusFault, "Bool", config.StatusFault); + } + if (config.StatusTampered) { + this.log("MotionSensor StatusTampered characteristic enabled"); + myService.addCharacteristic(Characteristic.StatusTampered); + this.bindCharacteristic(myService, Characteristic.StatusTampered, "Bool", config.StatusTampered); + } + if (config.StatusLowBattery) { + this.log("MotionSensor StatusLowBattery characteristic enabled"); + myService.addCharacteristic(Characteristic.StatusLowBattery); + this.bindCharacteristic(myService, Characteristic.StatusLowBattery, "Bool", config.StatusLowBattery); + } + return myService; + }, getOutletService: function(config) { /** * this.addCharacteristic(Characteristic.On); @@ -903,6 +946,9 @@ KNXDevice.prototype = { case "LockMechanism": accessoryServices.push(this.getLockMechanismService(configService)); break; + case "MotionSensor": + accessoryServices.push(this.getMotionSensorService(configService)); + break; case "Switch": accessoryServices.push(this.getSwitchService(configService)); break; diff --git a/platforms/KNX.md b/platforms/KNX.md index 2eba871..c3b4fe3 100644 --- a/platforms/KNX.md +++ b/platforms/KNX.md @@ -120,6 +120,13 @@ Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group addres *ToDo here: correction of mappings, HomeKit reqires lock states UNSECURED=0, SECURED=1, JAMMED = 2, UNKNOWN=3* +## MotionSensor +- MotionDetected: DPT 1.002, 1 as motion detected + +- StatusActive: DPT 1.011, 1 as true +- StatusFault: DPT 1.011, 1 as true +- StatusTampered: DPT 1.011, 1 as true +- StatusLowBattery: DPT 1.011, 1 as true ## Outlet - On: DPT 1.001, 1 as on, 0 as off From 80b2a047d42d3da815c38ed784d421f10ec9ca05 Mon Sep 17 00:00:00 2001 From: Snowdd1 Date: Sat, 19 Sep 2015 21:56:20 +0200 Subject: [PATCH 17/22] Typo fixes --- accessories/knxdevice.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/accessories/knxdevice.js b/accessories/knxdevice.js index 9ce2b5e..5801249 100644 --- a/accessories/knxdevice.js +++ b/accessories/knxdevice.js @@ -667,7 +667,7 @@ KNXDevice.prototype = { //iterate(myService); return myService; }, - getMotionSenserService: function(config) { + getMotionSensorService: function(config) { // Characteristic.ContactSensorState.CONTACT_DETECTED = 0; // Characteristic.ContactSensorState.CONTACT_NOT_DETECTED = 1; @@ -682,7 +682,7 @@ KNXDevice.prototype = { } var myService = new Service.MotionSensor(config.name,config.name); - if (config.ContactSensorState) { + if (config.MotionDetected) { this.log("MotionSensor MotionDetected characteristic enabled"); this.bindCharacteristic(myService, Characteristic.MotionDetected, "Bool", config.MotionDetected); } From 67fb6e8b4decde8b42a80fe54e1df3661696a664 Mon Sep 17 00:00:00 2001 From: Kai Date: Mon, 21 Sep 2015 00:59:26 +0200 Subject: [PATCH 18/22] fixing thermostat. switch the order of the characteristics now my stellaZ thermostat is shown correct in apps like eve and insteon+ --- platforms/ZWayServer.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index 5b79c8e..70c2d6e 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -89,11 +89,11 @@ ZWayServerPlatform.prototype = { //TODO: Unify this with getVDevServices, so there's only one place with mapping between service and vDev type. //Note: Order matters! var primaryDeviceClasses = [ - "switchBinary", "thermostat", - "sensorBinary.Door/Window", "sensorMultilevel.Temperature", - "switchMultilevel" + "switchMultilevel", + "switchBinary", + "sensorBinary.Door/Window" ]; var that = this; @@ -226,24 +226,24 @@ ZWayServerAccessory.prototype = { var typeKey = ZWayServerPlatform.getVDevTypeKey(vdev); var services = [], service; switch (typeKey) { - case "switchBinary": - services.push(new Service.Switch(vdev.metrics.title)); - break; - case "switchMultilevel": - services.push(new Service.Lightbulb(vdev.metrics.title)); - break; - case "thermostat": + case "thermostat": services.push(new Service.Thermostat(vdev.metrics.title)); break; case "sensorMultilevel.Temperature": services.push(new Service.TemperatureSensor(vdev.metrics.title)); break; - case "sensorBinary.Door/Window": - services.push(new Service.GarageDoorOpener(vdev.metrics.title)); + case "switchMultilevel": + services.push(new Service.Lightbulb(vdev.metrics.title)); break; case "battery.Battery": services.push(new Service.BatteryService(vdev.metrics.title)); break; + case "switchBinary": + services.push(new Service.Switch(vdev.metrics.title)); + break; + case "sensorBinary.Door/Window": + services.push(new Service.GarageDoorOpener(vdev.metrics.title)); + break; case "sensorMultilevel.Luminiscence": services.push(new Service.LightSensor(vdev.metrics.title)); break; From 625b9f47df2061f11099753108bc2d38364ebd6e Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Mon, 21 Sep 2015 08:17:46 -0700 Subject: [PATCH 19/22] Bump HAP-NodeJS Closes #181 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d0eb41c..3a3ca74 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "color": "0.10.x", "eibd": "^0.3.1", "elkington": "kevinohara80/elkington", - "hap-nodejs": "git+https://github.com/KhaosT/HAP-NodeJS#0030b35856e04ee2b42f0d05839feaa5c44cbd1f", + "hap-nodejs": "git+https://github.com/KhaosT/HAP-NodeJS#4650e771f356a220868d873d16564a6be6603ff7", "harmonyhubjs-client": "^1.1.4", "harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git", "lifx-api": "^1.0.1", From ca232f3111305803b2d418764312c72762c534cf Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 25 Sep 2015 00:26:07 -0400 Subject: [PATCH 20/22] Noted updated for requirement to successfully get HAP-NodeJS installed. --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 641e32f..2748494 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,13 @@ You'll also need some patience, as Siri can be very strict about sentence struct # Getting Started -OK, if you're still excited enough about ordering Siri to make your coffee (which, who wouldn't be!) then here's how to set things up. First, clone this repo: +OK, if you're still excited enough about ordering Siri to make your coffee (which, who wouldn't be!) then here's how to set things up. + +**Note:** You'll need to make sure you have the following package installed for your platform. + + * libavahi-compat-libdnssd-dev + +First, clone this repo: $ git clone https://github.com/nfarina/homebridge.git $ cd homebridge @@ -60,6 +66,7 @@ OK, if you're still excited enough about ordering Siri to make your coffee (whic **Node**: You'll need to have NodeJS version 0.12.x or better installed for required submodule `HAP-NodeJS` to load. + Now you should be able to run the homebridge server: $ cd homebridge From bb3381e10af1c472f81422608b8b91e240a8cc8c Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Thu, 24 Sep 2015 22:15:58 -0700 Subject: [PATCH 21/22] Update README.md --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2748494..ab3b98a 100644 --- a/README.md +++ b/README.md @@ -54,9 +54,7 @@ You'll also need some patience, as Siri can be very strict about sentence struct OK, if you're still excited enough about ordering Siri to make your coffee (which, who wouldn't be!) then here's how to set things up. -**Note:** You'll need to make sure you have the following package installed for your platform. - - * libavahi-compat-libdnssd-dev +**Note:** If you're running on Linux, you'll need to make sure you have the `libavahi-compat-libdnssd-dev` package installed. First, clone this repo: @@ -64,7 +62,7 @@ First, clone this repo: $ cd homebridge $ npm install -**Node**: You'll need to have NodeJS version 0.12.x or better installed for required submodule `HAP-NodeJS` to load. +**Note**: You'll need to have NodeJS version 0.12.x or better installed for required submodule `HAP-NodeJS` to load. Now you should be able to run the homebridge server: From c6c45d9e3db184b94b1fd9a32e2b38264dc2e9df Mon Sep 17 00:00:00 2001 From: ilcato Date: Sun, 27 Sep 2015 13:00:57 +0200 Subject: [PATCH 22/22] Fixed incompatibility with new version of HAP-Node.js --- platforms/FibaroHC2.js | 65 +++++++++++------------------------------- 1 file changed, 16 insertions(+), 49 deletions(-) diff --git a/platforms/FibaroHC2.js b/platforms/FibaroHC2.js index c18d0dd..4567b40 100644 --- a/platforms/FibaroHC2.js +++ b/platforms/FibaroHC2.js @@ -51,22 +51,21 @@ FibaroHC2Platform.prototype = { if (s.visible == true) { var accessory = null; if (s.type == "com.fibaro.multilevelSwitch") - accessory = new FibaroDimmerAccessory(that, s); + accessory = new FibaroAccessory(new Service.Lightbulb(s.name), [Characteristic.On, Characteristic.Brightness]); else if (s.type == "com.fibaro.FGRM222" || s.type == "com.fibaro.FGR221") - accessory = new FibaroRollerShutterAccessory(that, s); + accessory = new FibaroAccessory(new Service.WindowCovering(s.name), [Characteristic.CurrentPosition, Characteristic.TargetPosition, Characteristic.PositionState]); else if (s.type == "com.fibaro.binarySwitch" || s.type == "com.fibaro.developer.bxs.virtualBinarySwitch") - accessory = new FibaroBinarySwitchAccessory(that, s); + accessory = new FibaroAccessory(new Service.Switch(s.name), [Characteristic.On]); else if (s.type == "com.fibaro.FGMS001" || s.type == "com.fibaro.motionSensor") - accessory = new FibaroMotionSensorAccessory(that, s); + accessory = new FibaroAccessory(new Service.MotionSensor(s.name), [Characteristic.MotionDetected]); else if (s.type == "com.fibaro.temperatureSensor") - accessory = new FibaroTemperatureSensorAccessory(that, s); + accessory = new FibaroAccessory(new Service.TemperatureSensor(s.name), [Characteristic.CurrentTemperature]); else if (s.type == "com.fibaro.doorSensor") - accessory = new FibaroDoorSensorAccessory(that, s); + accessory = new FibaroAccessory(new Service.ContactSensor(s.name), [Characteristic.ContactSensorState]); else if (s.type == "com.fibaro.lightSensor") - accessory = new FibaroLightSensorAccessory(that, s); + accessory = new FibaroAccessory(new Service.LightSensor(s.name), [Characteristic.CurrentAmbientLightLevel]); else if (s.type == "com.fibaro.FGWP101") - accessory = new FibaroOutletAccessory(that, s); - + accessory = new FibaroAccessory(new Service.Outlet(s.name), [Characteristic.On, Characteristic.OutletInUse]); if (accessory != null) { accessory.getServices = function() { return that.getServices(accessory); @@ -150,8 +149,11 @@ FibaroHC2Platform.prototype = { return informationService; }, bindCharacteristicEvents: function(characteristic, homebridgeAccessory) { - var onOff = characteristic.format == "bool" ? true : false; - var readOnly = characteristic.writable == undefined || characteristic.writable == false ? true : false; + var onOff = characteristic.props.format == "bool" ? true : false; + var readOnly = true; + for (var i = 0; i < characteristic.props.perms.length; i++) + if (characteristic.props.perms[i] == "pw") + readOnly = false; var powerValue = (characteristic.UUID == "00000026-0000-1000-8000-0026BB765291") ? true : false; subscribeUpdate(characteristic, homebridgeAccessory, onOff); if (!readOnly) { @@ -184,44 +186,9 @@ FibaroHC2Platform.prototype = { } } -function FibaroDimmerAccessory(platform, s) { - this.controlService = new Service.Lightbulb(this.name); - this.characteristics = [Characteristic.On, Characteristic.Brightness]; -} - -function FibaroRollerShutterAccessory(platform, s) { - this.controlService = new Service.WindowCovering(this.name); - this.characteristics = [Characteristic.CurrentPosition, Characteristic.TargetPosition, Characteristic.PositionState]; -} - -function FibaroBinarySwitchAccessory(platform, s) { - this.controlService = new Service.Switch(this.name); - this.characteristics = [Characteristic.On]; -} - -function FibaroMotionSensorAccessory(platform, s) { - this.controlService = new Service.MotionSensor(this.name); - this.characteristics = [Characteristic.MotionDetected]; -} - -function FibaroTemperatureSensorAccessory(platform, s) { - this.controlService = new Service.TemperatureSensor(this.name); - this.characteristics = [Characteristic.CurrentTemperature]; -} - -function FibaroDoorSensorAccessory(platform, s) { - this.controlService = new Service.ContactSensor(this.name); - this.characteristics = [Characteristic.ContactSensorState]; -} - -function FibaroLightSensorAccessory(platform, s) { - this.controlService = new Service.LightSensor(this.name); - this.characteristics = [Characteristic.CurrentAmbientLightLevel]; -} - -function FibaroOutletAccessory(platform, s) { - this.controlService = new Service.Outlet(this.name); - this.characteristics = [Characteristic.On, Characteristic.OutletInUse]; +function FibaroAccessory(controlService, characteristics) { + this.controlService = controlService; + this.characteristics = characteristics; } var lastPoll=0;