From 983f1362715fc3064c3b3fe5882f2d085449c227 Mon Sep 17 00:00:00 2001 From: ilcato Date: Tue, 15 Sep 2015 13:57:21 +0200 Subject: [PATCH 001/121] 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 002/121] 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 003/121] 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 004/121] 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: Thu, 17 Sep 2015 20:37:11 +0200 Subject: [PATCH 005/121] fix for PossibleSets regex first version of logitech harmony support (device and activity level) --- platforms/FHEM.js | 117 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 93 insertions(+), 24 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index 03f3ee1..70b7710 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -159,6 +159,31 @@ function FHEM_startLongpoll(connection) { FHEM_update( device+'-hue', hue ); FHEM_update( device+'-sat', sat ); FHEM_update( device+'-bri', bri ); + continue; + } else if( reading == 'activity') { + + Object.keys(FHEM_subscriptions).forEach(function(key) { + var parts = key.split( '-', 2 ); + if( parts[1] != reading ) + return; + if( parts[0] == device ) + return; + if( parts[0].substr(0,1) != '#' ) + return; + + var subscription = FHEM_subscriptions[key]; + var accessory = subscription.accessory; + + device = parts[0].substr(1); + + var state = 0; + if( value == accessory.activity_name ) + state = 1; + + subscription.characteristic.setValue(state, undefined, 'fromFhem'); +//console.log(key + ': ' + state ); + } ); + continue; } @@ -360,12 +385,8 @@ FHEMPlatform.prototype = { || s.Attributes.genericDeviceType ) { accessory = new FHEMAccessory(that.log, that.connection, s); - } else if( s.PossibleSets.match(/[\^ ]on\b/) - && s.PossibleSets.match(/[\^ ]off\b/) ) { - accessory = new FHEMAccessory(that.log, that.connection, s); - - } else if( s.PossibleSets.match(/[\^ ]Volume\b/) ) { //FIXME: use sets [Pp]lay/[Pp]ause/[Ss]top - that.log( s.Internals.NAME + ' has volume'); + } else if( s.PossibleSets.match(/\bon\b/) + && s.PossibleSets.match(/\boff\b/) ) { accessory = new FHEMAccessory(that.log, that.connection, s); } else if( s.Attributes.subType == 'thermostat' @@ -391,6 +412,30 @@ FHEMPlatform.prototype = { } else if( s.Readings.voc ) { accessory = new FHEMAccessory(that.log, that.connection, s); + } else if( s.Internals.TYPE == 'harmony' ) { + if( s.Internals.id ) { + if( s.Attributes.genericDeviceType ) + accessory = new FHEMAccessory(that.log, that.connection, s); + else + that.log( 'ignoring harmony device ' + s.Internals.NAME + ' without genericDeviceType attribte' ); + + } else { + that.log( 'creating devices for activities in ' + s.Internals.NAME ); + var match; + if( match = s.PossibleSets.match(/\bactivity:([^\s]*)/) ) { + var activities = match[1].split(','); + for( var i = 0; i < activities.length; i++ ) { + var activity = activities[i]; + accessory = new FHEMAccessory(that.log, that.connection, s, activity); + + if( accessory && Object.getOwnPropertyNames(accessory).length ) + foundAccessories.push(accessory); + } + accessory = null; + } + + } + } else { that.log( 'ignoring ' + s.Internals.NAME ); @@ -417,7 +462,7 @@ FHEMPlatform.prototype = { } function -FHEMAccessory(log, connection, s) { +FHEMAccessory(log, connection, s, activity_name) { //log( 'sets: ' + s.PossibleSets ); //log("got json: " + util.inspect(s) ); //log("got json: " + util.inspect(s.Internals) ); @@ -439,20 +484,20 @@ FHEMAccessory(log, connection, s) { this.mappings = {}; var match; - if( match = s.PossibleSets.match(/[\^ ]pct\b/) ) { + if( match = s.PossibleSets.match(/\bpct\b/) ) { this.mappings.pct = { reading: 'pct', cmd: 'pct' }; - } else if( match = s.PossibleSets.match(/[\^ ]dim\d+%/) ) { + } else if( match = s.PossibleSets.match(/\bdim\d+%/) ) { s.hasDim = true; s.pctMax = 100; } - if( match = s.PossibleSets.match(/[\^ ]hue[^\b\s]*(,(\d+)?)+\b/) ) { + if( match = s.PossibleSets.match(/\bhue[^\b\s]*(,(\d+)?)+\b/) ) { s.isLight = true; var max = 360; if( match[2] != undefined ) max = match[2]; this.mappings.hue = { reading: 'hue', cmd: 'hue', min: 0, max: max }; } - if( match = s.PossibleSets.match(/[\^ ]sat[^\b\s]*(,(\d+)?)+\b/) ) { + if( match = s.PossibleSets.match(/\bsat[^\b\s]*(,(\d+)?)+\b/) ) { s.isLight = true; var max = 100; if( match[2] != undefined ) @@ -460,12 +505,12 @@ FHEMAccessory(log, connection, s) { this.mappings.sat = { reading: 'sat', cmd: 'sat', min: 0, max: max }; } - if( s.PossibleSets.match(/[\^ ]rgb\b/) ) { + if( s.PossibleSets.match(/\brgb\b/) ) { s.isLight = true; this.mappings.rgb = { reading: 'rgb', cmd: 'rgb' }; if( s.Internals.TYPE == 'SWAP_0000002200000003' ) this.mappings.rgb = { reading: '0B-RGBlevel', cmd: 'rgb' }; - } else if( s.PossibleSets.match(/[\^ ]RGB\b/) ) { + } else if( s.PossibleSets.match(/\bRGB\b/) ) { s.isLight = true; this.mappings.rgb = { reading: 'RGB', cmd: 'RGB' }; } @@ -538,9 +583,9 @@ FHEMAccessory(log, connection, s) { else if( s.Attributes.model == 'fs20di' ) s.isLight = true; - if( s.PossibleSets.match(/[\^ ]desired-temp\b/) ) + if( s.PossibleSets.match(/\bdesired-temp\b/) ) this.mappings.thermostat = { reading: 'desired-temp', cmd: 'desired-temp' }; - else if( s.PossibleSets.match(/[\^ ]desiredTemperature\b/) ) + else if( s.PossibleSets.match(/\bdesiredTemperature\b/) ) this.mappings.thermostat = { reading: 'desiredTemperature', cmd: 'desiredTemperature' }; else if( s.isThermostat ) { s.isThermostat = false; @@ -550,8 +595,16 @@ FHEMAccessory(log, connection, s) { if( s.Internals.TYPE == 'SONOSPLAYER' ) //FIXME: use sets [Pp]lay/[Pp]ause/[Ss]top this.mappings.onOff = { reading: 'transportState', cmdOn: 'play', cmdOff: 'pause' }; - else if( s.PossibleSets.match(/[\^ ]on\b/) - && s.PossibleSets.match(/[\^ ]off\b/) ) { + + else if( s.Internals.TYPE == 'harmony' + && s.Internals.id ) + this.mappings.onOff = { reading: 'power', cmdOn: 'on', cmdOff: 'off' }; + + else if( s.Internals.TYPE == 'harmony' ) + this.mappings.onOff = { reading: 'activity', cmdOn: 'activity '+activity_name, cmdOff: 'off' }; + + else if( s.PossibleSets.match(/\bon\b/) + && s.PossibleSets.match(/\boff\b/) ) { this.mappings.onOff = { reading: 'state', cmdOn: 'on', cmdOff: 'off' }; if( !s.Readings.state ) delete this.mappings.onOff.reading; @@ -617,6 +670,8 @@ FHEMAccessory(log, connection, s) { log( s.Internals.NAME + ' has motor' ); if( this.mappings.direction ) log( s.Internals.NAME + ' has direction' ); + if( this.mappings.volume ) + log( s.Internals.NAME + ' has volume ['+ this.mappings.volume.reading + ':' + (this.mappings.volume.nocache ? 'not cached' : 'cached' ) +']' ); //log( util.inspect(s) ); @@ -630,6 +685,10 @@ FHEMAccessory(log, connection, s) { : ( s.Internals.model ? s.Internals.model : '' ) ); this.PossibleSets = s.PossibleSets; + if( activity_name ) + this.name = activity_name + ' (' + s.Internals.NAME + ')'; + this.activity_name = activity_name; + if( this.type == 'CUL_HM' ) { this.serial = s.Internals.DEF; if( s.Attributes.serialNr ) @@ -687,10 +746,10 @@ FHEMAccessory.prototype = { return undefined; if( reading == 'hue' ) { - value = Math.round(value * 360 / this.mappings.hue ? this.mappings.hue.max : 360); + value = Math.round(value * 360 / (this.mappings.hue ? this.mappings.hue.max : 360) ); } else if( reading == 'sat' ) { - value = Math.round(value * 100 / this.mappings.sat ? this.mappings.sat.max : 100); + value = Math.round(value * 100 / (this.mappings.sat ? this.mappings.sat.max : 100) ); } else if( reading == 'pct' ) { value = parseInt( value ); @@ -1032,7 +1091,7 @@ FHEMAccessory.prototype = { }, createDeviceService: function() { - var name = this.alias + 'xxx'; + var name = this.alias + ' (' + this.name + ')'; if( this.isSwitch ) { this.log(" switch service for " + this.name) @@ -1075,7 +1134,7 @@ FHEMAccessory.prototype = { identify: function(callback) { this.log('['+this.name+'] identify requested!'); - if( match = this.PossibleSets.match(/[\^ ]toggle\b/) ) { + if( match = this.PossibleSets.match(/\btoggle\b/) ) { this.command( 'identify' ); } callback(); @@ -1089,7 +1148,7 @@ FHEMAccessory.prototype = { informationService .setCharacteristic(Characteristic.Manufacturer, "FHEM:"+this.type) - .setCharacteristic(Characteristic.Model, "FHEM:"+this.model ? this.model : '') + .setCharacteristic(Characteristic.Model, "FHEM:"+ (this.model ? this.model : '') ) .setCharacteristic(Characteristic.SerialNumber, this.serial ? this.serial : ''); var controlService = this.createDeviceService(); @@ -1101,8 +1160,18 @@ FHEMAccessory.prototype = { var characteristic = controlService.getCharacteristic(Characteristic.On); FHEM_subscribe(characteristic, that.mappings.onOff.informId, that); - if( FHEM_cached[that.mappings.onOff.informId] != undefined ) - characteristic.value = FHEM_cached[that.mappings.onOff.informId]; + + if( this.activity_name ) { + FHEM_subscribe(characteristic, '#' + this.activity_name +'-'+ that.mappings.onOff.reading , that); + + if( FHEM_cached[that.mappings.onOff.informId] != undefined ) + characteristic.value = FHEM_cached[that.mappings.onOff.informId]==this.activity_name?1:0; + + } else { + if( FHEM_cached[that.mappings.onOff.informId] != undefined ) + characteristic.value = FHEM_cached[that.mappings.onOff.informId]; + + } characteristic .on('set', function(value, callback, context) { From ddf9fa01cfe158744e601248e6762774b5650785 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Thu, 17 Sep 2015 21:58:51 +0200 Subject: [PATCH 006/121] fixed PossibleSets regex again... --- platforms/FHEM.js | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index 70b7710..1e5e380 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -385,8 +385,8 @@ FHEMPlatform.prototype = { || s.Attributes.genericDeviceType ) { accessory = new FHEMAccessory(that.log, that.connection, s); - } else if( s.PossibleSets.match(/\bon\b/) - && s.PossibleSets.match(/\boff\b/) ) { + } else if( s.PossibleSets.match(/(^| )on\b/) + && s.PossibleSets.match(/(^| )off\b/) ) { accessory = new FHEMAccessory(that.log, that.connection, s); } else if( s.Attributes.subType == 'thermostat' @@ -422,8 +422,8 @@ FHEMPlatform.prototype = { } else { that.log( 'creating devices for activities in ' + s.Internals.NAME ); var match; - if( match = s.PossibleSets.match(/\bactivity:([^\s]*)/) ) { - var activities = match[1].split(','); + if( match = s.PossibleSets.match(/(^| )activity:([^\s]*)/) ) { + var activities = match[2].split(','); for( var i = 0; i < activities.length; i++ ) { var activity = activities[i]; accessory = new FHEMAccessory(that.log, that.connection, s, activity); @@ -484,33 +484,33 @@ FHEMAccessory(log, connection, s, activity_name) { this.mappings = {}; var match; - if( match = s.PossibleSets.match(/\bpct\b/) ) { + if( match = s.PossibleSets.match(/(^| )pct\b/) ) { this.mappings.pct = { reading: 'pct', cmd: 'pct' }; - } else if( match = s.PossibleSets.match(/\bdim\d+%/) ) { + } else if( match = s.PossibleSets.match(/(^| )dim\d+%/) ) { s.hasDim = true; s.pctMax = 100; } - if( match = s.PossibleSets.match(/\bhue[^\b\s]*(,(\d+)?)+\b/) ) { + if( match = s.PossibleSets.match(/(^| )hue[^\b\s]*(,(\d+)?)+\b/) ) { s.isLight = true; var max = 360; - if( match[2] != undefined ) - max = match[2]; + if( match[3] != undefined ) + max = match[3]; this.mappings.hue = { reading: 'hue', cmd: 'hue', min: 0, max: max }; } - if( match = s.PossibleSets.match(/\bsat[^\b\s]*(,(\d+)?)+\b/) ) { + if( match = s.PossibleSets.match(/(^| )sat[^\b\s]*(,(\d+)?)+\b/) ) { s.isLight = true; var max = 100; - if( match[2] != undefined ) - max = match[2]; + if( match[3] != undefined ) + max = match[3]; this.mappings.sat = { reading: 'sat', cmd: 'sat', min: 0, max: max }; } - if( s.PossibleSets.match(/\brgb\b/) ) { + if( s.PossibleSets.match(/(^| )rgb\b/) ) { s.isLight = true; this.mappings.rgb = { reading: 'rgb', cmd: 'rgb' }; if( s.Internals.TYPE == 'SWAP_0000002200000003' ) this.mappings.rgb = { reading: '0B-RGBlevel', cmd: 'rgb' }; - } else if( s.PossibleSets.match(/\bRGB\b/) ) { + } else if( s.PossibleSets.match(/(^| )RGB\b/) ) { s.isLight = true; this.mappings.rgb = { reading: 'RGB', cmd: 'RGB' }; } @@ -583,9 +583,9 @@ FHEMAccessory(log, connection, s, activity_name) { else if( s.Attributes.model == 'fs20di' ) s.isLight = true; - if( s.PossibleSets.match(/\bdesired-temp\b/) ) + if( s.PossibleSets.match(/(^| )desired-temp\b/) ) this.mappings.thermostat = { reading: 'desired-temp', cmd: 'desired-temp' }; - else if( s.PossibleSets.match(/\bdesiredTemperature\b/) ) + else if( s.PossibleSets.match(/(^| )desiredTemperature\b/) ) this.mappings.thermostat = { reading: 'desiredTemperature', cmd: 'desiredTemperature' }; else if( s.isThermostat ) { s.isThermostat = false; @@ -603,8 +603,8 @@ FHEMAccessory(log, connection, s, activity_name) { else if( s.Internals.TYPE == 'harmony' ) this.mappings.onOff = { reading: 'activity', cmdOn: 'activity '+activity_name, cmdOff: 'off' }; - else if( s.PossibleSets.match(/\bon\b/) - && s.PossibleSets.match(/\boff\b/) ) { + else if( s.PossibleSets.match(/(^| )on\b/) + && s.PossibleSets.match(/(^| )off\b/) ) { this.mappings.onOff = { reading: 'state', cmdOn: 'on', cmdOff: 'off' }; if( !s.Readings.state ) delete this.mappings.onOff.reading; @@ -1134,7 +1134,7 @@ FHEMAccessory.prototype = { identify: function(callback) { this.log('['+this.name+'] identify requested!'); - if( match = this.PossibleSets.match(/\btoggle\b/) ) { + if( match = this.PossibleSets.match(/(^| )toggle\b/) ) { this.command( 'identify' ); } callback(); From c7ab475dd4d72d5f7278082b1e2f02539e89db86 Mon Sep 17 00:00:00 2001 From: Snowdd1 Date: Fri, 18 Sep 2015 09:37:10 +0200 Subject: [PATCH 007/121] 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 008/121] 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 009/121] 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 010/121] 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 011/121] 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 012/121] 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 013/121] 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 014/121] 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 656a8057acbd177995f5a3dd9b63262082387d2b Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Sat, 19 Sep 2015 13:00:01 +0200 Subject: [PATCH 015/121] Initial work on reading, seems to work okay. --- platforms/ZWayServer.js | 71 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index 5b79c8e..7c8b925 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -11,6 +11,7 @@ function ZWayServerPlatform(log, config){ this.url = config["url"]; this.login = config["login"]; this.password = config["password"]; + this.opt_in = config["opt_in"]; this.name_overrides = config["name_overrides"]; this.batteryLow = config["battery_low_level"] || 15; this.pollInterval = config["poll_interval"] || 2; @@ -110,6 +111,7 @@ ZWayServerPlatform.prototype = { for(var i = 0; i < devices.length; i++){ var vdev = devices[i]; if(vdev.tags.indexOf("Homebridge:Skip") >= 0) { debug("Tag says skip!"); continue; } + if(this.opt_in && vdev.tags.indexOf("Homebridge:Include") < 0) continue; var gdid = vdev.id.replace(/^(.*?)_zway_(\d+-\d+)-\d.*/, '$1_$2'); var gd = groupedDevices[gdid] || (groupedDevices[gdid] = {devices: [], types: {}, primary: undefined}); gd.devices.push(vdev); @@ -222,6 +224,27 @@ ZWayServerAccessory.prototype = { }); }, + rgb2hsv: function(obj) { + var r = obj.r/255, g = obj.g/255, b = obj.b/255; + var max, min, d, h, s, v; + + if (min === max) { + // shade of gray + return [0, 0, r]; + } + + min = Math.min(r, Math.min(g, b)); + max = Math.max(r, Math.max(g, b)); + + var d = (r === min) ? g - b : ((b === min) ? r - g : b - r); + h = (r === min) ? 3 : ((b === min) ? 1 : 5); + h = 60 * (h - d/(max - min)); + s = (max - min) / max; + v = max; + return {"h": h, "s": s * 100, "v": v}; + } + , + getVDevServices: function(vdev){ var typeKey = ZWayServerPlatform.getVDevTypeKey(vdev); var services = [], service; @@ -272,6 +295,8 @@ ZWayServerAccessory.prototype = { this.uuidToTypeKeyMap = map = {}; map[(new Characteristic.On).UUID] = ["switchBinary","switchMultilevel"]; map[(new Characteristic.Brightness).UUID] = ["switchMultilevel"]; + map[(new Characteristic.Hue).UUID] = ["switchRGBW"]; + map[(new Characteristic.Saturation).UUID] = ["switchRGBW"]; map[(new Characteristic.CurrentTemperature).UUID] = ["sensorMultilevel.Temperature","thermostat"]; map[(new Characteristic.TargetTemperature).UUID] = ["thermostat"]; map[(new Characteristic.TemperatureDisplayUnits).UUID] = ["sensorMultilevel.Temperature","thermostat"]; //TODO: Always a fixed result @@ -310,7 +335,7 @@ ZWayServerAccessory.prototype = { } , configureCharacteristic: function(cx, vdev){ - var that = this; + var that, accessory = this; // Add this combination to the maps... if(!this.platform.cxVDevMap[vdev.id]) this.platform.cxVDevMap[vdev.id] = []; @@ -381,6 +406,50 @@ ZWayServerAccessory.prototype = { return cx; } + if(cx instanceof Characteristic.Hue){ + cx.zway_getValueFromVDev = function(vdev){ + return accessory.rgb2hsv(vdev.metrics.color).h; + }; + cx.value = cx.zway_getValueFromVDev(vdev); + cx.on('get', function(callback, context){ + debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); + this.getVDev(vdev).then(function(result){ + debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); + callback(false, cx.zway_getValueFromVDev(result.data)); + }); + }.bind(this)); + + cx.writeable = false; + //cx.on('set', function(level, callback){ + // this.command(vdev, "exact", {level: "on", "color.r": 255, "color.g": 0, "color.b": 0}).then(function(result){ + // callback(); + // }); + //}.bind(this)); + return cx; + } + + if(cx instanceof Characteristic.Saturation){ + cx.zway_getValueFromVDev = function(vdev){ + return accessory.rgb2hsv(vdev.metrics.color).s; + }; + cx.value = cx.zway_getValueFromVDev(vdev); + cx.on('get', function(callback, context){ + debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); + this.getVDev(vdev).then(function(result){ + debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); + callback(false, cx.zway_getValueFromVDev(result.data)); + }); + }.bind(this)); + + cx.writeable = false; + //cx.on('set', function(level, callback){ + // this.command(vdev, "exact", {level: "on", "color.r": 255, "color.g": 0, "color.b": 0}).then(function(result){ + // callback(); + // }); + //}.bind(this)); + return cx; + } + if(cx instanceof Characteristic.CurrentTemperature){ cx.zway_getValueFromVDev = function(vdev){ return vdev.metrics.level; From 387e7ec9cebd750aa9dcbd4a06178d5edb64b85d Mon Sep 17 00:00:00 2001 From: Snowdd1 Date: Sat, 19 Sep 2015 15:19:11 +0200 Subject: [PATCH 016/121] 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 017/121] 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 018/121] 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 019/121] 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 020/121] 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 021/121] 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 022/121] 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 7695eced0dd4d83c0d37ab77ecaf275c9c9ba0a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Mon, 21 Sep 2015 18:29:01 +0200 Subject: [PATCH 023/121] publish accessories only once parse min/max/step values for thermostats use this/bind instead of that added genericType ignore added batter state charactersitic preliminary thermostat controll mode use new characteristic.setProps() method some minor cleanups --- platforms/FHEM.js | 558 +++++++++++++++++++++++++++++----------------- 1 file changed, 356 insertions(+), 202 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index 1e5e380..fc114aa 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -31,6 +31,22 @@ FHEM_subscribe(characteristic, inform_id, accessory) { FHEM_subscriptions[inform_id] = { 'characteristic': characteristic, 'accessory': accessory }; } +function +FHEM_isPublished(device) { + var keys = Object.keys(FHEM_subscriptions); + for( var i = 0; i < keys.length; i++ ) { + var key = keys[i]; + + var subscription = FHEM_subscriptions[key]; + var accessory = subscription.accessory; + + if( accessory.device === device ) + return true; + }; + + return false; +} + // cached readings from longpoll & query var FHEM_cached = {}; //var FHEM_internal = {}; @@ -47,7 +63,7 @@ FHEM_update(inform_id, value, no_update) { console.log(" caching: " + inform_id + ": " + value + " as " + typeof(value) ); if( !no_update ) - subscription.characteristic.setValue(value, undefined, 'fromFhem'); + subscription.characteristic.setValue(value, undefined, 'fromFHEM'); } } @@ -149,17 +165,6 @@ function FHEM_startLongpoll(connection) { FHEM_update( device+'-pct', pct ); } - } else if(accessory.mappings.rgb && reading == accessory.mappings.rgb.reading) { - var hsv = FHEM_rgb2hsv(value); - var hue = parseInt( hsv[0] * 360 ); - var sat = parseInt( hsv[1] * 100 ); - var bri = parseInt( hsv[2] * 100 ); - - //FHEM_update( device+'-'+reading, value, false ); - FHEM_update( device+'-hue', hue ); - FHEM_update( device+'-sat', sat ); - FHEM_update( device+'-bri', bri ); - continue; } else if( reading == 'activity') { Object.keys(FHEM_subscriptions).forEach(function(key) { @@ -180,11 +185,23 @@ function FHEM_startLongpoll(connection) { if( value == accessory.activity_name ) state = 1; - subscription.characteristic.setValue(state, undefined, 'fromFhem'); -//console.log(key + ': ' + state ); + subscription.characteristic.setValue(state, undefined, 'fromFHEM'); } ); continue; + + } else if(accessory.mappings.rgb && reading == accessory.mappings.rgb.reading) { + var hsv = FHEM_rgb2hsv(value); + var hue = parseInt( hsv[0] * 360 ); + var sat = parseInt( hsv[1] * 100 ); + var bri = parseInt( hsv[2] * 100 ); + + //FHEM_update( device+'-'+reading, value, false ); + FHEM_update( device+'-hue', hue ); + FHEM_update( device+'-sat', sat ); + FHEM_update( device+'-bri', bri ); + continue; + } value = accessory.reading2homekit(reading, value); @@ -364,69 +381,74 @@ FHEMPlatform.prototype = { asyncCalls++; - var that = this; this.connection.request.get( { url: url, json: true, gzip: true }, function(err, response, json) { if( !err && response.statusCode == 200 ) { - that.log( 'got: ' + json['totalResultsReturned'] + ' results' ); -//that.log("got json: " + util.inspect(json) ); + this.log( 'got: ' + json['totalResultsReturned'] + ' results' ); +//this.log("got json: " + util.inspect(json) ); if( json['totalResultsReturned'] ) { var sArray=FHEM_sortByKey(json['Results'],"Name"); sArray.map(function(s) { var accessory; - if( s.Attributes.disable == 1 ) { - that.log( s.Internals.NAME + ' is disabled'); + if( FHEM_isPublished(s.Internals.NAME) ) + this.log( s.Internals.NAME + ' is already published'); + + else if( s.Attributes.disable == 1 ) { + this.log( s.Internals.NAME + ' is disabled'); } else if( s.Internals.TYPE == 'structure' ) { - that.log( s.Internals.NAME + ' is a structure'); + this.log( 'ignoring structure ' + s.Internals.NAME ); } else if( s.Attributes.genericDisplayType || s.Attributes.genericDeviceType ) { - accessory = new FHEMAccessory(that.log, that.connection, s); + accessory = new FHEMAccessory(this.log, this.connection, s); } else if( s.PossibleSets.match(/(^| )on\b/) && s.PossibleSets.match(/(^| )off\b/) ) { - accessory = new FHEMAccessory(that.log, that.connection, s); + accessory = new FHEMAccessory(this.log, this.connection, s); } else if( s.Attributes.subType == 'thermostat' || s.Attributes.subType == 'blindActuator' || s.Attributes.subType == 'threeStateSensor' ) { - accessory = new FHEMAccessory(that.log, that.connection, s); + accessory = new FHEMAccessory(this.log, this.connection, s); } else if( s.Attributes.model == 'HM-SEC-WIN' ) { - accessory = new FHEMAccessory(that.log, that.connection, s); + accessory = new FHEMAccessory(this.log, this.connection, s); } else if( s.Attributes.model == 'HM-SEC-KEY' ) { - accessory = new FHEMAccessory(that.log, that.connection, s); + accessory = new FHEMAccessory(this.log, this.connection, s); } else if( s.Internals.TYPE == 'PRESENCE' ) { - accessory = new FHEMAccessory(that.log, that.connection, s); + accessory = new FHEMAccessory(this.log, this.connection, s); + + } else if( s.Internals.TYPE == 'SONOSPLAYER' ) { + accessory = new FHEMAccessory(this.log, this.connection, s); } else if( s.Readings.temperature ) { - accessory = new FHEMAccessory(that.log, that.connection, s); + accessory = new FHEMAccessory(this.log, this.connection, s); } else if( s.Readings.humidity ) { - accessory = new FHEMAccessory(that.log, that.connection, s); + accessory = new FHEMAccessory(this.log, this.connection, s); } else if( s.Readings.voc ) { - accessory = new FHEMAccessory(that.log, that.connection, s); + accessory = new FHEMAccessory(this.log, this.connection, s); } else if( s.Internals.TYPE == 'harmony' ) { - if( s.Internals.id ) { + if( s.Internals.id != undefined ) { if( s.Attributes.genericDeviceType ) - accessory = new FHEMAccessory(that.log, that.connection, s); + accessory = new FHEMAccessory(this.log, this.connection, s); else - that.log( 'ignoring harmony device ' + s.Internals.NAME + ' without genericDeviceType attribte' ); + this.log( 'ignoring harmony device ' + s.Internals.NAME + ' without genericDeviceType attribte' ); } else { - that.log( 'creating devices for activities in ' + s.Internals.NAME ); + this.log( 'creating devices for activities in ' + s.Internals.NAME ); var match; if( match = s.PossibleSets.match(/(^| )activity:([^\s]*)/) ) { var activities = match[2].split(','); for( var i = 0; i < activities.length; i++ ) { var activity = activities[i]; - accessory = new FHEMAccessory(that.log, that.connection, s, activity); + accessory = new FHEMAccessory(this.log, this.connection, s, activity); if( accessory && Object.getOwnPropertyNames(accessory).length ) foundAccessories.push(accessory); @@ -437,27 +459,27 @@ FHEMPlatform.prototype = { } } else { - that.log( 'ignoring ' + s.Internals.NAME ); + this.log( 'ignoring ' + s.Internals.NAME + ' (' + s.Internals.TYPE + ')' ); } if( accessory && Object.getOwnPropertyNames(accessory).length ) foundAccessories.push(accessory); - }); + }.bind(this) ); } - //callback(foundAccessories); - callbackLater(); + callback(foundAccessories); + //callbackLater(); } else { - that.log("There was a problem connecting to FHEM (1)."); + this.log("There was a problem connecting to FHEM (1)."); if( response ) - that.log( " " + response.statusCode + ": " + response.statusMessage ); + this.log( " " + response.statusCode + ": " + response.statusMessage ); } - }); + }.bind(this) ); } } @@ -471,11 +493,11 @@ FHEMAccessory(log, connection, s, activity_name) { return new FHEMAccessory(log, connection, s); if( s.Attributes.disable == 1 ) { - that.log( s.Internals.NAME + ' is disabled'); + this.log( s.Internals.NAME + ' is disabled'); return null; } else if( s.Internals.TYPE == 'structure' ) { - that.log( s.Internals.NAME + ' is a structure'); + this.log( 'ignoring structure ' + s.Internals.NAME ); return null; } @@ -537,6 +559,9 @@ FHEMAccessory(log, connection, s, activity_name) { if( s.Readings.motor ) this.mappings.motor = { reading: 'motor' }; + if( s.Readings.battery ) + this.mappings.battery = { reading: 'battery' }; + if( s.Readings.direction ) this.mappings.direction = { reading: 'direction' }; @@ -545,7 +570,10 @@ FHEMAccessory(log, connection, s, activity_name) { if( !genericType ) genericType = s.Attributes.genericDisplayType; - if( genericType == 'switch' ) + if( genericType == 'ignore' ) + return null; + + else if( genericType == 'switch' ) s.isSwitch = true; else if( genericType == 'garage' ) @@ -583,21 +611,45 @@ FHEMAccessory(log, connection, s, activity_name) { else if( s.Attributes.model == 'fs20di' ) s.isLight = true; - if( s.PossibleSets.match(/(^| )desired-temp\b/) ) + //if( s.PossibleSets.match(/(^| )desired-temp\b/) ) { + if( match = s.PossibleSets.match(/(^| )desired-temp(:[^\d]*([^\$ ]*))?/) ) { this.mappings.thermostat = { reading: 'desired-temp', cmd: 'desired-temp' }; - else if( s.PossibleSets.match(/(^| )desiredTemperature\b/) ) + + if( s.Readings.controlMode ) + this.mappings.thermostat_mode = { reading: 'controlMode', cmd: 'controlMode' }; + + if( match[3] ) { + var values = match[3].split(','); + this.mappings.thermostat.min = parseFloat(values[0]); + this.mappings.thermostat.max = parseFloat(values[values.length-1]); + this.mappings.thermostat.step = values[1] - values[0]; + } + + //} else if( s.PossibleSets.match(/(^| )desiredTemperature\b/) ) { + } else if( match = s.PossibleSets.match(/(^| )desiredTemperature(:[^\d]*([^\$ ]*))?/) ) { this.mappings.thermostat = { reading: 'desiredTemperature', cmd: 'desiredTemperature' }; - else if( s.isThermostat ) { + if( s.Readings.mode ) + this.mappings.thermostat_mode = { reading: 'mode', cmd: 'desiredTemperature' }; + + if( match[3] ) { + var values = match[3].split(','); + this.mappings.thermostat.min = values[0]; + this.mappings.thermostat.max = values[values.length-2]; + this.mappings.thermostat.step = values[1] - values[0]; + } + + } else if( s.isThermostat ) { s.isThermostat = false; delete this.mappings.thermostat; log( s.Internals.NAME + ' is NOT a thermostat. set for target temperature missing' ); + } if( s.Internals.TYPE == 'SONOSPLAYER' ) //FIXME: use sets [Pp]lay/[Pp]ause/[Ss]top this.mappings.onOff = { reading: 'transportState', cmdOn: 'play', cmdOff: 'pause' }; else if( s.Internals.TYPE == 'harmony' - && s.Internals.id ) + && s.Internals.id != undefined ) this.mappings.onOff = { reading: 'power', cmdOn: 'on', cmdOff: 'off' }; else if( s.Internals.TYPE == 'harmony' ) @@ -635,7 +687,7 @@ FHEMAccessory(log, connection, s, activity_name) { else if( this.mappings.blind ) log( s.Internals.NAME + ' is blind ['+ this.mappings.blind.reading +']' ); else if( this.mappings.thermostat ) - log( s.Internals.NAME + ' is thermostat ['+ this.mappings.thermostat.reading +']' ); + log( s.Internals.NAME + ' is thermostat ['+ this.mappings.thermostat.reading + ';' + this.mappings.thermostat.min + '-' + this.mappings.thermostat.max + ':' + this.mappings.thermostat.step +']' ); else if( this.mappings.contact ) log( s.Internals.NAME + ' is contact sensor [' + this.mappings.contact.reading +']' ); else if( this.mappings.occupancy ) @@ -660,6 +712,8 @@ FHEMAccessory(log, connection, s, activity_name) { log( s.Internals.NAME + ' has hue [0-' + this.mappings.hue.max +']' ); if( this.mappings.sat ) log( s.Internals.NAME + ' has sat [0-' + this.mappings.sat.max +']' ); + if( this.mappings.thermostat_mode ) + log( s.Internals.NAME + ' has thermostat mode ['+ this.mappings.thermostat_mode.reading + ';' + this.mappings.thermostat_mode.cmd +']' ); if( this.mappings.temperature ) log( s.Internals.NAME + ' has temperature ['+ this.mappings.temperature.reading +']' ); if( this.mappings.humidity ) @@ -667,9 +721,11 @@ FHEMAccessory(log, connection, s, activity_name) { if( this.mappings.airquality ) log( s.Internals.NAME + ' has voc ['+ this.mappings.airquality.reading +']' ); if( this.mappings.motor ) - log( s.Internals.NAME + ' has motor' ); + log( s.Internals.NAME + ' has motor ['+ this.mappings.motor.reading +']' ); + if( this.mappings.battery ) + log( s.Internals.NAME + ' has battery ['+ this.mappings.battery.reading +']' ); if( this.mappings.direction ) - log( s.Internals.NAME + ' has direction' ); + log( s.Internals.NAME + ' has direction ['+ this.mappings.direction.reading +']' ); if( this.mappings.volume ) log( s.Internals.NAME + ' has volume ['+ this.mappings.volume.reading + ':' + (this.mappings.volume.nocache ? 'not cached' : 'cached' ) +']' ); @@ -717,22 +773,21 @@ FHEMAccessory(log, connection, s, activity_name) { if( this.mappings.blind || this.mappings.door || this.mappings.garage || this.mappings.window || this.mappings.thermostat ) delete this.mappings.onOff; - var that = this; Object.keys(this.mappings).forEach(function(key) { - var reading = that.mappings[key].reading; + var reading = this.mappings[key].reading; if( s.Readings[reading] && s.Readings[reading].Value ) { var value = s.Readings[reading].Value; - value = that.reading2homekit(reading, value); + value = this.reading2homekit(reading, value); if( value != undefined ) { - var inform_id = that.device +'-'+ reading; - that.mappings[key].informId = inform_id; + var inform_id = this.device +'-'+ reading; + this.mappings[key].informId = inform_id; - if( !that.mappings[key].nocache ) + if( !this.mappings[key].nocache ) FHEM_cached[inform_id] = value; } } - } ); + }.bind(this) ); this.log = log; this.connection = connection; @@ -762,6 +817,20 @@ FHEMAccessory.prototype = { else value = Characteristic.PositionState.STOPPED; + } else if(reading == 'controlMode') { + if( value.match(/^auto/)) + value = Characteristic.TargetHeatingCoolingState.AUTO; + else if( value.match(/^manu/)) + value = Characteristic.TargetHeatingCoolingState.HEAT; + else + value = Characteristic.TargetHeatingCoolingState.OFF; + + } else if(reading == 'mode') { + if( value.match(/^auto/)) + value = Characteristic.TargetHeatingCoolingState.AUTO; + else + value = Characteristic.TargetHeatingCoolingState.HEAT; + } else if(reading == 'direction') { if( value.match(/^opening/)) value = PositionState.INCREASING; @@ -806,6 +875,16 @@ FHEMAccessory.prototype = { || reading == 'desiredTemperature' ) { value = parseFloat( value ); + if( this.mappings.thermostat + && reading == this.mappings.thermostat.reading ) { + if( value < this.mappings.thermostat.min ) + value = this.mappings.thermostat.min; + else if( value > this.mappings.thermostat.max ) + value = this.mappings.thermostat.min; + + value = Math.round(value / this.mappings.thermostat.step) * this.mappings.thermostat.step; + } + } else if( reading == 'humidity' ) { value = parseInt( value ); @@ -824,6 +903,12 @@ FHEMAccessory.prototype = { else Characteristic.AirQuality.UNKNOWN; + } else if( reading == 'battery' ) { + if( value == 'ok' ) + value = Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; + else + value = Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW; + } else if( reading == 'state' ) { if( value.match(/^set-/ ) ) return undefined; @@ -860,8 +945,8 @@ FHEMAccessory.prototype = { } this.log(this.name + " delaying command " + c + " with value " + value); - var that = this; - this.delayed[c] = setTimeout( function(){clearTimeout(that.delayed[c]);that.command(c,value)}, delay?delay:1000 ); + this.delayed[c] = setTimeout( function(){clearTimeout(this.delayed[c]); this.command(c,value);}.bind(this), + delay?delay:1000 ); }, command: function(c,value) { @@ -927,6 +1012,27 @@ FHEMAccessory.prototype = { } else if( c == 'targetTemperature' ) { cmd = "set " + this.device + " " + this.mappings.thermostat.cmd + " " + value; + } else if( c == 'targetMode' ) { + var set = this.mappings.thermostat_mode.cmd; + if( value == Characteristic.TargetHeatingCoolingState.OFF ) { + value = 'off' + if( this.mappings.thermostat_mode.cmd == 'controlMode' ) + set = 'desired-temp'; + + } else if( value == Characteristic.TargetHeatingCoolingState.AUTO ) { + value = 'auto' + + }else { + if( this.mappings.thermostat_mode == 'controlMode' ) + value = 'manu'; + else { + value = FHEM_cached[this.mappings.thermostat.informId]; + set = 'desired-temp'; + } + + } + cmd = "set " + this.device + " " + set + " " + value; + } else if( c == 'targetPosition' ) { if( this.mappings.window ) { if( value == 0 ) @@ -953,24 +1059,22 @@ FHEMAccessory.prototype = { var url = encodeURI( this.connection.base_url + "/fhem?cmd=" + cmd + "&XHR=1"); this.log( ' executing: ' + url ); - var that = this; - this.connection.request.get( { url: url, gzip: true }, - function(err, response, result) { - if( !err && response.statusCode == 200 ) { - if( callback ) - callback( result ); + this.connection.request + .get( { url: url, gzip: true }, + function(err, response, result) { + if( !err && response.statusCode == 200 ) { + if( callback ) + callback( result ); - } else { - that.log("There was a problem connecting to FHEM ("+ url +")."); - if( response ) - that.log( " " + response.statusCode + ": " + response.statusMessage ); + } else { + this.log("There was a problem connecting to FHEM ("+ url +")."); + if( response ) + this.log( " " + response.statusCode + ": " + response.statusMessage ); - } + } - } ).on( 'error', function(err) { - that.log("There was a problem connecting to FHEM ("+ url +"):"+ err); - - } ); + }.bind(this) ) + .on( 'error', function(err) { this.log("There was a problem connecting to FHEM ("+ url +"):"+ err); }.bind(this) ); }, query: function(reading, callback) { @@ -1015,11 +1119,10 @@ FHEMAccessory.prototype = { var cmd = '{ReadingsVal("'+this.device+'","'+query_reading+'","")}'; - var that = this; this.execute( cmd, function(result) { value = result.replace(/[\r\n]/g, ""); - that.log(" value: " + value); + this.log(" value: " + value); if( value == undefined ) return value; @@ -1055,28 +1158,28 @@ FHEMAccessory.prototype = { else value = Characteristic.LockCurrentState.UNSECURED; - } else if(reading == 'hue' && query_reading == that.mappings.rgb) { - //FHEM_update( that.device+'-'+query_reading, value ); + } else if(reading == 'hue' && query_reading == this.mappings.rgb) { + //FHEM_update( this.device+'-'+query_reading, value ); value = parseInt( FHEM_rgb2hsv(value)[0] * 360 ); - } else if(reading == 'sat' && query_reading == that.mappings.rgb) { - //FHEM_update( that.device+'-'+query_reading, value ); + } else if(reading == 'sat' && query_reading == this.mappings.rgb) { + //FHEM_update( this.device+'-'+query_reading, value ); value = parseInt( FHEM_rgb2hsv(value)[1] * 100 ); - } else if(reading == 'bri' && query_reading == that.mappings.rgb) { - //FHEM_update( that.device+'-'+query_reading, value ); + } else if(reading == 'bri' && query_reading == this.mappings.rgb) { + //FHEM_update( this.device+'-'+query_reading, value ); value = parseInt( FHEM_rgb2hsv(value)[2] * 100 ); } } else { - value = that.reading2homekit(reading, value); + value = this.reading2homekit(reading, value); } - that.log(" mapped: " + value); - FHEM_update( that.device + '-' + reading, value, true ); + this.log(" mapped: " + value); + FHEM_update( this.device + '-' + reading, value, true ); if( callback != undefined ) { if( value == undefined ) @@ -1087,7 +1190,7 @@ FHEMAccessory.prototype = { return value ; - } ); + }.bind(this) ); }, createDeviceService: function() { @@ -1153,34 +1256,33 @@ FHEMAccessory.prototype = { var controlService = this.createDeviceService(); - var that = this; if( this.mappings.onOff ) { this.log(" power characteristic for " + this.name) var characteristic = controlService.getCharacteristic(Characteristic.On); - FHEM_subscribe(characteristic, that.mappings.onOff.informId, that); + FHEM_subscribe(characteristic, this.mappings.onOff.informId, this); if( this.activity_name ) { - FHEM_subscribe(characteristic, '#' + this.activity_name +'-'+ that.mappings.onOff.reading , that); + FHEM_subscribe(characteristic, '#' + this.activity_name +'-'+ this.mappings.onOff.reading , this); - if( FHEM_cached[that.mappings.onOff.informId] != undefined ) - characteristic.value = FHEM_cached[that.mappings.onOff.informId]==this.activity_name?1:0; + if( FHEM_cached[this.mappings.onOff.informId] != undefined ) + characteristic.value = FHEM_cached[this.mappings.onOff.informId]==this.activity_name?1:0; } else { - if( FHEM_cached[that.mappings.onOff.informId] != undefined ) - characteristic.value = FHEM_cached[that.mappings.onOff.informId]; + if( FHEM_cached[this.mappings.onOff.informId] != undefined ) + characteristic.value = FHEM_cached[this.mappings.onOff.informId]; } characteristic .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.command( 'set', value == 0 ? that.mappings.onOff.cmdOff : that.mappings.onOff.cmdOn ); + if( context !== 'fromFHEM' ) + this.command( 'set', value == 0 ? this.mappings.onOff.cmdOff : this.mappings.onOff.cmdOn ); callback(); }.bind(this) ) .on('get', function(callback) { - that.query(that.mappings.onOff.reading, callback); + this.query(this.mappings.onOff.reading, callback); }.bind(this) ); } @@ -1189,18 +1291,18 @@ FHEMAccessory.prototype = { var characteristic = controlService.addCharacteristic(Characteristic.Brightness); - FHEM_subscribe(characteristic, that.mappings.pct.informId, that); - if( FHEM_cached[that.mappings.pct.informId] != undefined ) - characteristic.value = FHEM_cached[that.mappings.pct.informId]; + FHEM_subscribe(characteristic, this.mappings.pct.informId, this); + if( FHEM_cached[this.mappings.pct.informId] != undefined ) + characteristic.value = FHEM_cached[this.mappings.pct.informId]; characteristic .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.command('pct', value); + if( context !== 'fromFHEM' ) + this.command('pct', value); callback(); }.bind(this) ) .on('get', function(callback) { - that.query(that.mappings.pct.reading, callback); + this.query(this.mappings.pct.reading, callback); }.bind(this) ); } else if( this.hasDim ) { @@ -1208,39 +1310,38 @@ FHEMAccessory.prototype = { var characteristic = controlService.addCharacteristic(Characteristic.Brightness); - FHEM_subscribe(characteristic, that.name+'-pct', that); + FHEM_subscribe(characteristic, this.name+'-pct', this); characteristic.value = 0; characteristic.maximumValue = this.pctMax; characteristic .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.delayed('dim', value); + if( context !== 'fromFHEM' ) + this.delayed('dim', value); callback(); }.bind(this) ) .on('get', function(callback) { - that.query('pct', callback); + this.query('pct', callback); }.bind(this) ); - } - if( that.mappings.hue ) { + if( this.mappings.hue ) { this.log(" hue characteristic for " + this.name) var characteristic = controlService.addCharacteristic(Characteristic.Hue); - FHEM_subscribe(characteristic, that.mappings.hue.informId, that); - if( FHEM_cached[that.mappings.hue.informId] != undefined ) - characteristic.value = FHEM_cached[that.mappings.hue.informId]; + FHEM_subscribe(characteristic, this.mappings.hue.informId, this); + if( FHEM_cached[this.mappings.hue.informId] != undefined ) + characteristic.value = FHEM_cached[this.mappings.hue.informId]; characteristic .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.command('hue', value); + if( context !== 'fromFHEM' ) + this.command('hue', value); callback(); }.bind(this) ) .on('get', function(callback) { - that.query(that.mappings.hue.reading, callback); + this.query(this.mappings.hue.reading, callback); }.bind(this) ); } else if( this.mappings.rgb ) { @@ -1248,18 +1349,18 @@ FHEMAccessory.prototype = { var characteristic = controlService.addCharacteristic(Characteristic.Hue); - FHEM_subscribe(characteristic, that.name+'-hue', that); - FHEM_subscribe(characteristic, that.mappings.rgb.informId, that); + FHEM_subscribe(characteristic, this.name+'-hue', this); + FHEM_subscribe(characteristic, this.mappings.rgb.informId, this); characteristic.value = 0; characteristic .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.command('H-rgb', value); + if( context !== 'fromFHEM' ) + this.command('H-rgb', value); callback(); }.bind(this) ) .on('get', function(callback) { - that.query('hue', callback); + this.query('hue', callback); }.bind(this) ); if( !this.mappings.sat ) { @@ -1267,17 +1368,17 @@ FHEMAccessory.prototype = { var characteristic = controlService.addCharacteristic(Characteristic.Saturation); - FHEM_subscribe(characteristic, that.name+'-sat', that); + FHEM_subscribe(characteristic, this.name+'-sat', this); characteristic.value = 100; characteristic .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.command('S-rgb', value); + if( context !== 'fromFHEM' ) + this.command('S-rgb', value); callback(); }.bind(this) ) .on('get', function(callback) { - that.query('sat', callback); + this.query('sat', callback); }.bind(this) ); } @@ -1286,20 +1387,19 @@ FHEMAccessory.prototype = { var characteristic = controlService.addCharacteristic(Characteristic.Brightness); - FHEM_subscribe(characteristic, that.name+'-bri', that); + FHEM_subscribe(characteristic, this.name+'-bri', this); characteristic.value = 0; characteristic .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.command('B-rgb', value); + if( context !== 'fromFHEM' ) + this.command('B-rgb', value); callback(); }.bind(this) ) .on('get', function(callback) { - that.query('bri', callback); + this.query('bri', callback); }.bind(this) ); } - } if( this.mappings.sat ) { @@ -1307,18 +1407,18 @@ FHEMAccessory.prototype = { var characteristic = controlService.addCharacteristic(Characteristic.Saturation); - FHEM_subscribe(characteristic, that.mappings.sat.informId, that); - if( FHEM_cached[that.mappings.sat.informId] != undefined ) - characteristic.value = FHEM_cached[that.mappings.sat.informId]; + FHEM_subscribe(characteristic, this.mappings.sat.informId, this); + if( FHEM_cached[this.mappings.sat.informId] != undefined ) + characteristic.value = FHEM_cached[this.mappings.sat.informId]; characteristic .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.command('sat', value); + if( context !== 'fromFHEM' ) + this.command('sat', value); callback(); }.bind(this) ) .on('get', function(callback) { - that.query(that.mappings.sat.reading, callback); + this.query(this.mappings.sat.reading, callback); }.bind(this) ); } @@ -1328,32 +1428,35 @@ FHEMAccessory.prototype = { var characteristic = new Characteristic('Volume', '00000027-0000-1000-8000-0026BB765291'); // FIXME!!! controlService.addCharacteristic(characteristic); - if( !that.mappings.volume.nocache ) { - FHEM_subscribe(characteristic, that.mappings.volume.informId, that); - characteristic.value = FHEM_cached[that.mappings.volume.informId]; + if( !this.mappings.volume.nocache ) { + FHEM_subscribe(characteristic, this.mappings.volume.informId, this); + characteristic.value = FHEM_cached[this.mappings.volume.informId]; } else { characteristic.value = 10; } - characteristic.format = 'uint8'; - characteristic.unit = 'percentage'; - characteristic.maximumValue = 100; - characteristic.minimumValue = 0; - characteristic.stepValue = 1; + characteristic.setProps({ + format: Characteristic.Formats.UINT8, + unit: Characteristic.Units.PERCENTAGE, + maxValue: 100, + minValue: 0, + minStep: 1, + perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] + }); + characteristic.readable = true; characteristic.writable = true; characteristic.supportsEventNotification = true; characteristic .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.delayed('volume', value); + if( context !== 'fromFHEM' ) + this.delayed('volume', value); callback(); }.bind(this) ) .on('get', function(callback) { - that.query(that.mappings.volume.reading, callback); + this.query(this.mappings.volume.reading, callback); }.bind(this) ); - } if( this.mappings.blind ) { @@ -1361,29 +1464,33 @@ FHEMAccessory.prototype = { var characteristic = controlService.getCharacteristic(Characteristic.CurrentPosition); - FHEM_subscribe(characteristic, that.mappings.blind.informId, that); - characteristic.value = FHEM_cached[that.mappings.blind.informId]; + var step = 1; + FHEM_subscribe(characteristic, this.mappings.blind.informId, this); + characteristic.value = Math.round(FHEM_cached[this.mappings.blind.informId] / step) * step; characteristic .on('get', function(callback) { - that.query(that.mappings.blind.reading, callback); + this.query(this.mappings.blind.reading, callback); }.bind(this) ); this.log(" target position characteristic for " + this.name) var characteristic = controlService.getCharacteristic(Characteristic.TargetPosition); + characteristic.setProps( { + minStep: step, + } ); - characteristic.value = FHEM_cached[that.mappings.blind.informId]; + characteristic.value = FHEM_cached[this.mappings.blind.informId]; characteristic .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.delayed('targetPosition', value, 1500); + if( context !== 'fromFHEM' ) + this.delayed('targetPosition', value, 1500); callback(); }.bind(this) ) .on('get', function(callback) { - that.query(that.mappings.blind.reading, callback); + this.query(this.mappings.blind.reading, callback); }.bind(this) ); @@ -1391,16 +1498,15 @@ FHEMAccessory.prototype = { var characteristic = controlService.getCharacteristic(Characteristic.PositionState); - if( that.mappings.motor ) - FHEM_subscribe(characteristic, that.mappings.motor.informId, that); - characteristic.value = that.mappings.motor?FHEM_cached[that.mappings.motor.informId]:Characteristic.PositionState.STOPPED; + if( this.mappings.motor ) + FHEM_subscribe(characteristic, this.mappings.motor.informId, this); + characteristic.value = this.mappings.motor?FHEM_cached[this.mappings.motor.informId]:Characteristic.PositionState.STOPPED; characteristic .on('get', function(callback) { - if( that.mappings.motor ) - that.query(that.mappings.motor.reading, callback); + if( this.mappings.motor ) + this.query(this.mappings.motor.reading, callback); }.bind(this) ); - } if( this.mappings.window ) { @@ -1408,13 +1514,13 @@ FHEMAccessory.prototype = { var characteristic = controlService.getCharacteristic(Characteristic.CurrentPosition); - FHEM_subscribe(characteristic, that.name+'-state', that); - FHEM_subscribe(characteristic, that.mappings.window.informId, that); - characteristic.value = FHEM_cached[that.mappings.window.informId]; + FHEM_subscribe(characteristic, this.name+'-state', this); + FHEM_subscribe(characteristic, this.mappings.window.informId, this); + characteristic.value = FHEM_cached[this.mappings.window.informId]; characteristic .on('get', function(callback) { - that.query(that.mappings.window.reading, callback); + this.query(this.mappings.window.reading, callback); }.bind(this) ); @@ -1422,16 +1528,16 @@ FHEMAccessory.prototype = { var characteristic = controlService.getCharacteristic(Characteristic.TargetPosition); - characteristic.value = FHEM_cached[that.mappings.window.informId]; + characteristic.value = FHEM_cached[this.mappings.window.informId]; characteristic .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.delayed('targetPosition', value, 1500); + if( context !== 'fromFHEM' ) + this.delayed('targetPosition', value, 1500); callback(); }.bind(this) ) .on('get', function(callback) { - that.query(that.mappings.window.reading, callback); + this.query(this.mappings.window.reading, callback); }.bind(this) ); @@ -1439,16 +1545,15 @@ FHEMAccessory.prototype = { var characteristic = controlService.getCharacteristic(Characteristic.PositionState); - if( that.mappings.direction ) - FHEM_subscribe(characteristic, that.mappings.direction.informId, that); - characteristic.value = that.mappings.direction?FHEM_cached[that.mappings.direction.informId]:Characteristic.PositionState.STOPPED; + if( this.mappings.direction ) + FHEM_subscribe(characteristic, this.mappings.direction.informId, this); + characteristic.value = this.mappings.direction?FHEM_cached[this.mappings.direction.informId]:Characteristic.PositionState.STOPPED; characteristic .on('get', function(callback) { - if( that.mappings.direction ) - that.query(that.mappings.direction.reading, callback); + if( this.mappings.direction ) + this.query(this.mappings.direction.reading, callback); }.bind(this) ); - } if( this.mappings.garage ) { @@ -1472,8 +1577,8 @@ FHEMAccessory.prototype = { characteristic .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.command( 'set', value == 0 ? that.mappings.garage.cmdOpen : that.mappings.garage.cmdClose ); + if( context !== 'fromFHEM' ) + this.command( 'set', value == 0 ? this.mappings.garage.cmdOpen : this.mappings.garage.cmdClose ); callback(); }.bind(this) ) .on('get', function(callback) { @@ -1485,14 +1590,13 @@ FHEMAccessory.prototype = { var characteristic = controlService.getCharacteristic(Characteristic.ObstructionDetected); - //FHEM_subscribe(characteristic, that.mappings.direction.informId, that); + //FHEM_subscribe(characteristic, this.mappings.direction.informId, this); characteristic.value = 0; characteristic .on('get', function(callback) { callback(undefined,1); }.bind(this) ); - } if( this.mappings.temperature ) { @@ -1501,14 +1605,13 @@ FHEMAccessory.prototype = { var characteristic = controlService.getCharacteristic(Characteristic.CurrentTemperature) || controlService.addCharacteristic(Characteristic.CurrentTemperature); - FHEM_subscribe(characteristic, that.mappings.temperature.informId, that); - characteristic.value = FHEM_cached[that.mappings.temperature.informId]; + FHEM_subscribe(characteristic, this.mappings.temperature.informId, this); + characteristic.value = FHEM_cached[this.mappings.temperature.informId]; characteristic .on('get', function(callback) { - that.query(that.mappings.temperature.reading, callback); + this.query(this.mappings.temperature.reading, callback); }.bind(this) ); - } if( this.mappings.humidity ) { @@ -1517,14 +1620,13 @@ FHEMAccessory.prototype = { var characteristic = controlService.getCharacteristic(Characteristic.CurrentRelativeHumidity) || controlService.addCharacteristic(Characteristic.CurrentRelativeHumidity); - FHEM_subscribe(characteristic, that.mappings.humidity.informId, that); - characteristic.value = FHEM_cached[that.mappings.humidity.informId]; + FHEM_subscribe(characteristic, this.mappings.humidity.informId, this); + characteristic.value = FHEM_cached[this.mappings.humidity.informId]; characteristic .on('get', function(callback) { - that.query(that.mappings.humidity.reading, callback); + this.query(this.mappings.humidity.reading, callback); }.bind(this) ); - } if( this.mappings.airquality ) { @@ -1533,12 +1635,27 @@ FHEMAccessory.prototype = { var characteristic = controlService.getCharacteristic(Characteristic.AirQuality) || controlService.addCharacteristic(Characteristic.AirQuality); - FHEM_subscribe(characteristic, that.mappings.airquality.informId, that); - characteristic.value = FHEM_cached[that.mappings.airquality.informId]; + FHEM_subscribe(characteristic, this.mappings.airquality.informId, this); + characteristic.value = FHEM_cached[this.mappings.airquality.informId]; characteristic .on('get', function(callback) { - that.query(that.mappings.airquality.reading, callback); + this.query(this.mappings.airquality.reading, callback); + }.bind(this) ); + } + + if( this.mappings.battery ) { + this.log(" battery status characteristic for " + this.name) + + var characteristic = controlService.getCharacteristic(Characteristic.StatusLowBattery) + || controlService.addCharacteristic(Characteristic.StatusLowBattery); + + FHEM_subscribe(characteristic, this.mappings.battery.informId, this); + characteristic.value = FHEM_cached[this.mappings.battery.informId]; + + characteristic + .on('get', function(callback) { + this.query(this.mappings.battery.reading, callback); }.bind(this) ); } @@ -1549,19 +1666,58 @@ FHEMAccessory.prototype = { var characteristic = controlService.getCharacteristic(Characteristic.TargetTemperature); - FHEM_subscribe(characteristic, that.mappings.thermostat.informId, that); - characteristic.value = FHEM_cached[that.mappings.thermostat.informId]; + FHEM_subscribe(characteristic, this.mappings.thermostat.informId, this); + characteristic.value = FHEM_cached[this.mappings.thermostat.informId]; + +console.log( this.mappings.thermostat ); + characteristic.setProps( { + maxValue: this.mappings.thermostat.max, + minValue: this.mappings.thermostat.min, + minStep: this.mappings.thermostat.step, + } ); characteristic .on('set', function(value, callback, context) { - if( context !== 'fromFhem' ) - that.delayed('targetTemperature', value, 1500); + if( context !== 'fromFHEM' ) + this.delayed('targetTemperature', value, 1500); callback(); }.bind(this) ) .on('get', function(callback) { - that.query(that.mappings.thermostat.reading, callback); + this.query(this.mappings.thermostat.reading, callback); }.bind(this) ); + if( this.mappings.thermostat_mode ) { + this.log(" current mode characteristic for " + this.name) + + var characteristic = controlService.getCharacteristic(Characteristic.CurrentHeatingCoolingState); + + FHEM_subscribe(characteristic, this.mappings.thermostat_mode.informId, this); + characteristic.value = FHEM_cached[this.mappings.thermostat_mode.informId]; + + characteristic + .on('get', function(callback) { + this.query(this.mappings.thermostat_mode.reading, callback); + }.bind(this) ); + } + + if( this.mappings.thermostat_mode ) { + this.log(" target mode characteristic for " + this.name) + + var characteristic = controlService.getCharacteristic(Characteristic.TargetHeatingCoolingState); + + FHEM_subscribe(characteristic, this.mappings.thermostat_mode.informId, this); + characteristic.value = FHEM_cached[this.mappings.thermostat_mode.informId]; + + characteristic + .on('set', function(value, callback, context) { + if( context !== 'fromFHEM' ) + this.command('targetMode', value); + callback(); + }.bind(this) ) + .on('get', function(callback) { + this.query(this.mappings.thermostat_mode.reading, callback); + }.bind(this) ); + } } if( this.mappings.contact ) { @@ -1569,14 +1725,13 @@ FHEMAccessory.prototype = { var characteristic = controlService.getCharacteristic(Characteristic.ContactSensorState); - FHEM_subscribe(characteristic, that.mappings.contact.informId, that); - characteristic.value = FHEM_cached[that.mappings.contact.informId]; + FHEM_subscribe(characteristic, this.mappings.contact.informId, this); + characteristic.value = FHEM_cached[this.mappings.contact.informId]; characteristic .on('get', function(callback) { - that.query(that.mappings.contact.reading, callback); + this.query(this.mappings.contact.reading, callback); }.bind(this) ); - } if( this.mappings.occupancy ) { @@ -1584,14 +1739,13 @@ FHEMAccessory.prototype = { var characteristic = controlService.getCharacteristic(Characteristic.OccupancyDetected); - FHEM_subscribe(characteristic, that.mappings.occupancy.informId, that); - characteristic.value = FHEM_cached[that.mappings.occupancy.informId]; + FHEM_subscribe(characteristic, this.mappings.occupancy.informId, this); + characteristic.value = FHEM_cached[this.mappings.occupancy.informId]; characteristic .on('get', function(callback) { - that.query(that.mappings.occupancy.reading, callback); + this.query(this.mappings.occupancy.reading, callback); }.bind(this) ); - } return [informationService, controlService]; From 1b69cffeb22f6e103eb550a1e8725638570aac31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Mon, 21 Sep 2015 18:50:40 +0200 Subject: [PATCH 024/121] removed debug output --- platforms/FHEM.js | 1 - 1 file changed, 1 deletion(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index fc114aa..e8b24da 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -1669,7 +1669,6 @@ FHEMAccessory.prototype = { FHEM_subscribe(characteristic, this.mappings.thermostat.informId, this); characteristic.value = FHEM_cached[this.mappings.thermostat.informId]; -console.log( this.mappings.thermostat ); characteristic.setProps( { maxValue: this.mappings.thermostat.max, minValue: this.mappings.thermostat.min, From 2a63a9ff2dd743af802fb8e620d977bcac5cac5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Mon, 21 Sep 2015 19:48:51 +0200 Subject: [PATCH 025/121] adde MAX ShutterContact --- platforms/FHEM.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index e8b24da..f122989 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -602,6 +602,10 @@ FHEMAccessory(log, connection, s, activity_name) { } else if( s.Internals.TYPE == 'CUL_FHTTK' ) { this.mappings.contact = { reading: 'Window' }; + } else if( s.Internals.TYPE == 'MAX' + && s.Internals.type == 'ShutterContact' ) { + this.mappings.contact = { reading: 'state' }; + } else if( s.Attributes.subType == 'threeStateSensor' ) { this.mappings.contact = { reading: 'contact' }; @@ -921,6 +925,10 @@ FHEMAccessory.prototype = { if( value == 'off' ) value = 0; + else if( value == 'opened' ) + value = Characteristic.ContactSensorState.CONTACT_NOT_DETECTED; + else if( value == 'closed' ) + value = Characteristic.ContactSensorState.CONTACT_DETECTED; else if( value == 'present' ) value = Characteristic.OccupancyDetected.OCCUPANCY_DETECTED; else if( value == 'absent' ) From afcb86ef0edc5b3176a14c6f920a34acccb989d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Tue, 22 Sep 2015 18:53:19 +0200 Subject: [PATCH 026/121] changed harmony device to a single accessiory with mutliple switches added hue colormode & xy to hsv conversion some small cleanups --- platforms/FHEM.js | 276 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 209 insertions(+), 67 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index f122989..d1b9399 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -1,4 +1,5 @@ // FHEM Platform Shim for HomeBridge +// current version on https://github.com/justme-1968/homebridge // // Remember to add platform to config.json. Example: // "platforms": [ @@ -12,14 +13,10 @@ // 'filter': "room=xyz" // } // ], -// -// 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 Service = require("HAP-NodeJS").Service; var Characteristic = require("HAP-NodeJS").Characteristic; -var types = require('HAP-NodeJS/accessories/types.js'); var util = require('util'); @@ -167,25 +164,21 @@ function FHEM_startLongpoll(connection) { } else if( reading == 'activity') { + FHEM_update( device+'-'+reading, value, true ); + Object.keys(FHEM_subscriptions).forEach(function(key) { - var parts = key.split( '-', 2 ); + var parts = key.split( '-', 3 ); + if( parts[0] != '#' + device ) + return; if( parts[1] != reading ) return; - if( parts[0] == device ) - return; - if( parts[0].substr(0,1) != '#' ) - return; var subscription = FHEM_subscriptions[key]; var accessory = subscription.accessory; - device = parts[0].substr(1); + var activity = parts[2]; - var state = 0; - if( value == accessory.activity_name ) - state = 1; - - subscription.characteristic.setValue(state, undefined, 'fromFHEM'); + subscription.characteristic.setValue(value==activity?1:0, undefined, 'fromFHEM'); } ); continue; @@ -202,6 +195,24 @@ function FHEM_startLongpoll(connection) { FHEM_update( device+'-bri', bri ); continue; + } else if(accessory.mappings.colormode) { + //FIXME: add colormode ct + if( reading == 'xy') { + var xy = value.split(','); + var rgb = FHEM_xyY2rgb(xy[0], xy[1] , 1); + var hsv = FHEM_rgb2hsv(rgb); + var hue = parseInt( hsv[0] * 360 ); + var sat = parseInt( hsv[1] * 100 ); + var bri = parseInt( hsv[2] * 100 ); + + FHEM_update( device+'-hue', hue ); + FHEM_update( device+'-sat', sat ); + FHEM_update( device+'-bri', bri ); + } + + FHEM_update( device+'-'+reading, value, false ); + continue; + } value = accessory.reading2homekit(reading, value); @@ -322,6 +333,90 @@ FHEM_hsv2rgb(h,s,v) { return FHEM_rgb2hex( Math.round(r*255),Math.round(g*255),Math.round(b*255) ); } +function +FHEM_ct2rgb(ct) +{ + // calculation from http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code + // adjusted by 1000K + var temp = (1000000/ct)/100 + 10; + + var r = 0; + var g = 0; + var b = 0; + + r = 255; + if( temp > 66 ) + r = 329.698727446 * Math.pow(temp - 60, -0.1332047592); + if( r < 0 ) + r = 0; + if( r > 255 ) + r = 255; + + if( temp <= 66 ) + g = 99.4708025861 * Math.log(temp) - 161.1195681661; + else + g = 288.1221695283 * Math.pow(temp - 60, -0.0755148492); + if( g < 0 ) + g = 0; + if( g > 255 ); + g = 255; + + b = 255; + if( temp <= 19 ) + b = 0; + if( temp < 66 ) + b = 138.5177312231 * log(temp-10) - 305.0447927307; + if( b < 0 ) + b = 0; + if( b > 255 ) + b = 255; + + return FHEM_rgb2hex( Math.round(r*255),Math.round(g*255),Math.round(b*255) ); +} + +function +FHEM_xyY2rgb(x,y,Y) +{ + // calculation from http://www.brucelindbloom.com/index.html + + var r = 0; + var g = 0; + var b = 0; + + if( y > 0 ) { + var X = x * Y / y; + var Z = (1 - x - y)*Y / y; + + if( X > 1 + || Y > 1 + || Z > 1 ) { + var f = Math.max(X,Y,Z); + X /= f; + Y /= f; + Z /= f; + } + + r = 0.7982 * X + 0.3389 * Y - 0.1371 * Z; + g = -0.5918 * X + 1.5512 * Y + 0.0406 * Z; + b = 0.0008 * X + 0.0239 * Y + 0.9753 * Z; + + if( r > 1 + || g > 1 + || b > 1 ) { + var f = Math.max(r,g,b); + r /= f; + g /= f; + b /= f; + } + + r *= 255; + g *= 255; + b *= 255; + } + + return FHEM_rgb2hex( Math.round(r*255),Math.round(g*255),Math.round(b*255) ); +} + function FHEM_rgb2hsv(r,g,b){ @@ -435,28 +530,7 @@ FHEMPlatform.prototype = { accessory = new FHEMAccessory(this.log, this.connection, s); } else if( s.Internals.TYPE == 'harmony' ) { - if( s.Internals.id != undefined ) { - if( s.Attributes.genericDeviceType ) - accessory = new FHEMAccessory(this.log, this.connection, s); - else - this.log( 'ignoring harmony device ' + s.Internals.NAME + ' without genericDeviceType attribte' ); - - } else { - this.log( 'creating devices for activities in ' + s.Internals.NAME ); - var match; - if( match = s.PossibleSets.match(/(^| )activity:([^\s]*)/) ) { - var activities = match[2].split(','); - for( var i = 0; i < activities.length; i++ ) { - var activity = activities[i]; - accessory = new FHEMAccessory(this.log, this.connection, s, activity); - - if( accessory && Object.getOwnPropertyNames(accessory).length ) - foundAccessories.push(accessory); - } - accessory = null; - } - - } + accessory = new FHEMAccessory(this.log, this.connection, s); } else { this.log( 'ignoring ' + s.Internals.NAME + ' (' + s.Internals.TYPE + ')' ); @@ -484,7 +558,7 @@ FHEMPlatform.prototype = { } function -FHEMAccessory(log, connection, s, activity_name) { +FHEMAccessory(log, connection, s) { //log( 'sets: ' + s.PossibleSets ); //log("got json: " + util.inspect(s) ); //log("got json: " + util.inspect(s.Internals) ); @@ -527,6 +601,12 @@ FHEMAccessory(log, connection, s, activity_name) { this.mappings.sat = { reading: 'sat', cmd: 'sat', min: 0, max: max }; } + if( s.Readings.colormode ) + this.mappings.colormode = { reading: 'colormode' }; + if( s.Readings.xy ) + this.mappings.xy = { reading: 'xy' }; + //FIXME: add ct/colortemperature + if( s.PossibleSets.match(/(^| )rgb\b/) ) { s.isLight = true; this.mappings.rgb = { reading: 'rgb', cmd: 'rgb' }; @@ -652,14 +732,17 @@ FHEMAccessory(log, connection, s, activity_name) { if( s.Internals.TYPE == 'SONOSPLAYER' ) //FIXME: use sets [Pp]lay/[Pp]ause/[Ss]top this.mappings.onOff = { reading: 'transportState', cmdOn: 'play', cmdOff: 'pause' }; - else if( s.Internals.TYPE == 'harmony' - && s.Internals.id != undefined ) - this.mappings.onOff = { reading: 'power', cmdOn: 'on', cmdOff: 'off' }; + else if( s.Internals.TYPE == 'harmony' ) { + if( s.Internals.id != undefined ) { + if( s.Attributes.genericDeviceType ) + this.mappings.onOff = { reading: 'power', cmdOn: 'on', cmdOff: 'off' }; + else + return null; - else if( s.Internals.TYPE == 'harmony' ) - this.mappings.onOff = { reading: 'activity', cmdOn: 'activity '+activity_name, cmdOff: 'off' }; + } else + this.mappings.onOff = { reading: 'activity', cmdOn: 'activity', cmdOff: 'off' }; - else if( s.PossibleSets.match(/(^| )on\b/) + } else if( s.PossibleSets.match(/(^| )on\b/) && s.PossibleSets.match(/(^| )off\b/) ) { this.mappings.onOff = { reading: 'state', cmdOn: 'on', cmdOff: 'off' }; if( !s.Readings.state ) @@ -713,9 +796,13 @@ FHEMAccessory(log, connection, s, activity_name) { if( this.mappings.onOff ) log( s.Internals.NAME + ' has onOff [' + this.mappings.onOff.reading + ';' + this.mappings.onOff.cmdOn +',' + this.mappings.onOff.cmdOff + ']' ); if( this.mappings.hue ) - log( s.Internals.NAME + ' has hue [0-' + this.mappings.hue.max +']' ); + log( s.Internals.NAME + ' has hue [' + this.mappings.hue.reading + ';0-' + this.mappings.hue.max +']' ); if( this.mappings.sat ) - log( s.Internals.NAME + ' has sat [0-' + this.mappings.sat.max +']' ); + log( s.Internals.NAME + ' has sat [' + this.mappings.sat.reading + ';0-' + this.mappings.sat.max +']' ); + if( this.mappings.colormode ) + log( s.Internals.NAME + ' has colormode [' + this.mappings.colormode.reading +']' ); + if( this.mappings.xy ) + log( s.Internals.NAME + ' has xy [' + this.mappings.xy.reading +']' ); if( this.mappings.thermostat_mode ) log( s.Internals.NAME + ' has thermostat mode ['+ this.mappings.thermostat_mode.reading + ';' + this.mappings.thermostat_mode.cmd +']' ); if( this.mappings.temperature ) @@ -745,10 +832,6 @@ FHEMAccessory(log, connection, s, activity_name) { : ( s.Internals.model ? s.Internals.model : '' ) ); this.PossibleSets = s.PossibleSets; - if( activity_name ) - this.name = activity_name + ' (' + s.Internals.NAME + ')'; - this.activity_name = activity_name; - if( this.type == 'CUL_HM' ) { this.serial = s.Internals.DEF; if( s.Attributes.serialNr ) @@ -935,7 +1018,7 @@ FHEMAccessory.prototype = { value = Characteristic.OccupancyDetected.OCCUPANCY_NOT_DETECTED; else if( value == '000000' ) value = 0; - else if( value.match( /^[A-D]0$/ ) ) //FIXME: is handled by event_map now + else if( value.match( /^[A-D]0$/ ) ) //FIXME: not necessary any more. handled by event_map now. value = 0; else value = 1; @@ -1201,8 +1284,10 @@ FHEMAccessory.prototype = { }.bind(this) ); }, - createDeviceService: function() { + createDeviceService: function(subtype) { var name = this.alias + ' (' + this.name + ')'; + if( subtype ) + name = subtype + ' (' + this.name + ')'; if( this.isSwitch ) { this.log(" switch service for " + this.name) @@ -1239,8 +1324,8 @@ FHEMAccessory.prototype = { return new Service.AirQualitySensor(name); } - this.log(" switch service for " + this.name) - return new Service.Switch(name); + this.log(" switch service for " + this.name + ' (' + subtype + ')' ) + return new Service.Switch(name, subtype); }, identify: function(callback) { @@ -1252,17 +1337,84 @@ FHEMAccessory.prototype = { }, getServices: function() { + var services = []; + this.log("creating services for " + this.name) this.log(" information service for " + this.name) var informationService = new Service.AccessoryInformation(); + services.push( informationService ); informationService .setCharacteristic(Characteristic.Manufacturer, "FHEM:"+this.type) .setCharacteristic(Characteristic.Model, "FHEM:"+ (this.model ? this.model : '') ) .setCharacteristic(Characteristic.SerialNumber, this.serial ? this.serial : ''); + + // FIXME: allow multiple switch characteristics also for other types. check if this.mappings.onOff an array. + if( this.type == 'harmony' + && this.mappings.onOff.reading == 'activity' ) { + + FHEM_subscribe(undefined, this.mappings.onOff.informId, this); + + var match; + if( match = this.PossibleSets.match(/(^| )activity:([^\s]*)/) ) { + var activities = match[2].split(','); + for( var i = 0; i < activities.length; i++ ) { + var activity = activities[i]; + + var controlService = this.createDeviceService(activity); + services.push( controlService ); + + this.log(" power characteristic for " + this.name + ' ' + activity); + + var characteristic = controlService.getCharacteristic(Characteristic.On); + + FHEM_subscribe(characteristic, '#' + this.device + '-' + this.mappings.onOff.reading + '-' + activity, this); + + characteristic.value = (FHEM_cached[this.mappings.onOff.informId]==activity?1:0); + + characteristic + .on('set', function(activity, value, callback, context) { + if( context !== 'fromFHEM' ) + this.command( 'set', value == 0 ? this.mappings.onOff.cmdOff : this.mappings.onOff.cmdOn + ' ' + activity ); + callback(); + }.bind(this, activity) ) + .on('get', function(activity, callback) { + var result = this.query(this.mappings.onOff.reading); + callback( undefined, result==activity?1:0 ); + }.bind(this, activity) ); + } + } + + return services; + } + + if( this.mappings.xy + && this.mappings.colormode ) { + FHEM_subscribe(undefined, this.mappings.xy.informId, this); + FHEM_subscribe(undefined, this.mappings.colormode.informId, this); + + + //FIXME: add colormode ct + if( FHEM_cached[this.mappings.colormode.informId] == 'xy' ) { + var value = FHEM_cached[this.mappings.xy.informId]; + var xy = value.split(','); + var rgb = FHEM_xyY2rgb(xy[0], xy[1] , 1); + var hsv = FHEM_rgb2hsv(rgb); + var hue = parseInt( hsv[0] * 360 ); + var sat = parseInt( hsv[1] * 100 ); + var bri = parseInt( hsv[2] * 100 ); + + //FHEM_update( device+'-'+reading, value, false ); + FHEM_update( this.device+'-hue', hue ); + FHEM_update( this.device+'-sat', sat ); + FHEM_update( this.device+'-bri', bri ); + } + } + var controlService = this.createDeviceService(); + services.push( controlService ); if( this.mappings.onOff ) { this.log(" power characteristic for " + this.name) @@ -1271,17 +1423,8 @@ FHEMAccessory.prototype = { FHEM_subscribe(characteristic, this.mappings.onOff.informId, this); - if( this.activity_name ) { - FHEM_subscribe(characteristic, '#' + this.activity_name +'-'+ this.mappings.onOff.reading , this); - - if( FHEM_cached[this.mappings.onOff.informId] != undefined ) - characteristic.value = FHEM_cached[this.mappings.onOff.informId]==this.activity_name?1:0; - - } else { - if( FHEM_cached[this.mappings.onOff.informId] != undefined ) - characteristic.value = FHEM_cached[this.mappings.onOff.informId]; - - } + if( FHEM_cached[this.mappings.onOff.informId] != undefined ) + characteristic.value = FHEM_cached[this.mappings.onOff.informId]; characteristic .on('set', function(value, callback, context) { @@ -1668,7 +1811,6 @@ FHEMAccessory.prototype = { } - //FIXME: parse range and set designedMinValue & designedMaxValue & designedMinStep if( this.mappings.thermostat ) { this.log(" target temperature characteristic for " + this.name) @@ -1755,7 +1897,7 @@ FHEMAccessory.prototype = { }.bind(this) ); } - return [informationService, controlService]; + return services; } }; From ca232f3111305803b2d418764312c72762c534cf Mon Sep 17 00:00:00 2001 From: Derek Foreman Date: Fri, 25 Sep 2015 00:26:07 -0400 Subject: [PATCH 027/121] 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 028/121] 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 bfbaa7437550f086c0cd6a42ba6817ab9ff62bfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Fri, 25 Sep 2015 19:20:54 +0200 Subject: [PATCH 029/121] added firmware revision characteristic fix for genericDeviceType ignore some more fixes set uuid_base to real device serial number if available --- platforms/FHEM.js | 57 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index d1b9399..222e77d 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -242,7 +242,8 @@ function FHEM_startLongpoll(connection) { } -function FHEMPlatform(log, config) { +function +FHEMPlatform(log, config) { this.log = log; this.server = config['server']; this.port = config['port']; @@ -567,15 +568,24 @@ FHEMAccessory(log, connection, s) { return new FHEMAccessory(log, connection, s); if( s.Attributes.disable == 1 ) { - this.log( s.Internals.NAME + ' is disabled'); + log( s.Internals.NAME + ' is disabled'); return null; } else if( s.Internals.TYPE == 'structure' ) { - this.log( 'ignoring structure ' + s.Internals.NAME ); + log( 'ignoring ' + s.Internals.NAME + ' (' + s.Internals.TYPE + ')' ); return null; } + var genericType = s.Attributes.genericDeviceType; + if( !genericType ) + genericType = s.Attributes.genericDisplayType; + + if( genericType == 'ignore' ) { + log( 'ignoring ' + s.Internals.NAME ); + return null; + } + this.mappings = {}; @@ -645,15 +655,11 @@ FHEMAccessory(log, connection, s) { if( s.Readings.direction ) this.mappings.direction = { reading: 'direction' }; + if( s.Readings['D-firmware'] ) + this.mappings.firmware = { reading: 'D-firmware' }; - var genericType = s.Attributes.genericDeviceType; - if( !genericType ) - genericType = s.Attributes.genericDisplayType; - if( genericType == 'ignore' ) - return null; - - else if( genericType == 'switch' ) + if( genericType == 'switch' ) s.isSwitch = true; else if( genericType == 'garage' ) @@ -817,6 +823,8 @@ FHEMAccessory(log, connection, s) { log( s.Internals.NAME + ' has battery ['+ this.mappings.battery.reading +']' ); if( this.mappings.direction ) log( s.Internals.NAME + ' has direction ['+ this.mappings.direction.reading +']' ); + if( this.mappings.firmware ) + log( s.Internals.NAME + ' has firmware ['+ this.mappings.firmware.reading +']' ); if( this.mappings.volume ) log( s.Internals.NAME + ' has volume ['+ this.mappings.volume.reading + ':' + (this.mappings.volume.nocache ? 'not cached' : 'cached' ) +']' ); @@ -833,22 +841,24 @@ FHEMAccessory(log, connection, s) { this.PossibleSets = s.PossibleSets; if( this.type == 'CUL_HM' ) { - this.serial = s.Internals.DEF; + this.serial = this.type + '.' + s.Internals.DEF; if( s.Attributes.serialNr ) this.serial = s.Attributes.serialNr; else if( s.Readings['D-serialNr'] && s.Readings['D-serialNr'].Value ) this.serial = s.Readings['D-serialNr'].Value; } else if( this.type == 'CUL_WS' ) - this.serial = s.Internals.DEF; + this.serial = this.type + '.' + s.Internals.DEF; else if( this.type == 'FS20' ) - this.serial = s.Internals.DEF; + this.serial = this.type + '.' + s.Internals.DEF; else if( this.type == 'IT' ) - this.serial = s.Internals.DEF; + this.serial = this.type + '.' + s.Internals.DEF; else if( this.type == 'HUEDevice' ) this.serial = s.Internals.uniqueid; else if( this.type == 'SONOSPLAYER' ) this.serial = s.Internals.UDN; + this.uuid_base = this.serial; + this.hasDim = s.hasDim; this.pctMax = s.pctMax; @@ -1350,6 +1360,24 @@ FHEMAccessory.prototype = { .setCharacteristic(Characteristic.Model, "FHEM:"+ (this.model ? this.model : '') ) .setCharacteristic(Characteristic.SerialNumber, this.serial ? this.serial : ''); + + if( this.mappings.firmware ) { + this.log(" firmware revision characteristic for " + this.name) + + var characteristic = informationService.getCharacteristic(Characteristic.FirmwareRevision) + || informationService.addCharacteristic(Characteristic.FirmwareRevision); + + FHEM_subscribe(characteristic, this.mappings.firmware.informId, this); + + characteristic.value = FHEM_cached[this.mappings.firmware.informId]; + + characteristic + .on('get', function(callback) { + if( this.mappings.firmware ) + this.query(this.mappings.firmware.reading, callback); + }.bind(this) ); + } + // FIXME: allow multiple switch characteristics also for other types. check if this.mappings.onOff an array. if( this.type == 'harmony' @@ -1372,6 +1400,7 @@ FHEMAccessory.prototype = { FHEM_subscribe(characteristic, '#' + this.device + '-' + this.mappings.onOff.reading + '-' + activity, this); + characteristic.displayName = activity; characteristic.value = (FHEM_cached[this.mappings.onOff.informId]==activity?1:0); characteristic From 78394bc95d696114bfd0218bedc12bfd53531d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Fri, 25 Sep 2015 22:43:57 +0200 Subject: [PATCH 030/121] added CurrentAmbientLightLevel characteristic --- platforms/FHEM.js | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index 222e77d..e2261d8 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -643,6 +643,9 @@ FHEMAccessory(log, connection, s) { if( s.Readings.humidity ) this.mappings.humidity = { reading: 'humidity' }; + if( s.Readings.luminosity ) + this.mappings.light = { reading: 'luminosity' }; + if( s.Readings.voc ) this.mappings.airquality = { reading: 'voc' }; @@ -815,6 +818,8 @@ FHEMAccessory(log, connection, s) { log( s.Internals.NAME + ' has temperature ['+ this.mappings.temperature.reading +']' ); if( this.mappings.humidity ) log( s.Internals.NAME + ' has humidity ['+ this.mappings.humidity.reading +']' ); + if( this.mappings.light ) + log( s.Internals.NAME + ' has light ['+ this.mappings.light.reading +']' ); if( this.mappings.airquality ) log( s.Internals.NAME + ' has voc ['+ this.mappings.airquality.reading +']' ); if( this.mappings.motor ) @@ -985,6 +990,9 @@ FHEMAccessory.prototype = { } else if( reading == 'humidity' ) { value = parseInt( value ); + } else if( reading == 'luminosity' ) { + value = parseFloat( value ) / 0.265; + } else if( reading == 'voc' ) { value = parseInt( value ); if( value > 1500 ) @@ -1329,8 +1337,11 @@ FHEMAccessory.prototype = { } else if( this.mappings.humidity ) { this.log(" humidity sensor service for " + this.name) return new Service.HumiditySensor(name); + } else if( this.mappings.light ) { + this.log(" light sensor service for " + this.name) + return new Service.LightSensor(name); } else if( this.mappings.airquality ) { - this.log(" humidity sensor service for " + this.name) + this.log(" air quality sensor service for " + this.name) return new Service.AirQualitySensor(name); } @@ -1360,7 +1371,7 @@ FHEMAccessory.prototype = { .setCharacteristic(Characteristic.Model, "FHEM:"+ (this.model ? this.model : '') ) .setCharacteristic(Characteristic.SerialNumber, this.serial ? this.serial : ''); - + if( this.mappings.firmware ) { this.log(" firmware revision characteristic for " + this.name) @@ -1809,6 +1820,21 @@ FHEMAccessory.prototype = { }.bind(this) ); } + if( this.mappings.light ) { + this.log(" light characteristic for " + this.name) + + var characteristic = controlService.getCharacteristic(Characteristic.CurrentAmbientLightLevel) + || controlService.addCharacteristic(Characteristic.CurrentAmbientLightLevel); + + FHEM_subscribe(characteristic, this.mappings.light.informId, this); + characteristic.value = FHEM_cached[this.mappings.light.informId]; + + characteristic + .on('get', function(callback) { + this.query(this.mappings.light.reading, callback); + }.bind(this) ); + } + if( this.mappings.airquality ) { this.log(" air quality characteristic for " + this.name) From c6c45d9e3db184b94b1fd9a32e2b38264dc2e9df Mon Sep 17 00:00:00 2001 From: ilcato Date: Sun, 27 Sep 2015 13:00:57 +0200 Subject: [PATCH 031/121] 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; From 2a66eac0584c9d38d97bcf987874d1e67bc1c2a0 Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Mon, 28 Sep 2015 06:12:25 +0200 Subject: [PATCH 032/121] Fix for multiple devices, refactor, and custom Service+Characteristic --- platforms/YamahaAVR.js | 219 +++++++++++++++++++++++++---------------- 1 file changed, 133 insertions(+), 86 deletions(-) diff --git a/platforms/YamahaAVR.js b/platforms/YamahaAVR.js index f554fa0..1659c0f 100644 --- a/platforms/YamahaAVR.js +++ b/platforms/YamahaAVR.js @@ -1,5 +1,10 @@ var types = require("HAP-NodeJS/accessories/types.js"); +var inherits = require('util').inherits; +var debug = require('debug')('YamahaAVR'); +var Service = require("HAP-NodeJS").Service; +var Characteristic = require("HAP-NodeJS").Characteristic; var Yamaha = require('yamaha-nodejs'); +var Q = require('q'); var mdns = require('mdns'); //workaround for raspberry pi var sequence = [ @@ -12,10 +17,53 @@ function YamahaAVRPlatform(log, config){ this.log = log; this.config = config; this.playVolume = config["play_volume"]; + this.minVolume = config["min_volume"] || -50.0; + this.maxVolume = config["max_volume"] || -20.0; + this.gapVolume = this.maxVolume - this.minVolume; this.setMainInputTo = config["setMainInputTo"]; + this.expectedDevices = config["expected_devices"] || 100; + this.discoveryTimeout = config["discovery_timeout"] || 30; this.browser = mdns.createBrowser(mdns.tcp('http'), {resolverSequence: sequence}); } +// Custom Characteristics and service... + +YamahaAVRPlatform.AudioVolume = function() { + Characteristic.call(this, 'Audio Volume', '00001001-0000-1000-8000-135D67EC4377'); + this.format = 'uint8'; + this.unit = 'percentage'; + this.maximumValue = 100; + this.minimumValue = 0; + this.stepValue = 1; + this.readable = true; + this.writable = true; + this.supportsEventNotification = true; + this.value = this.getDefaultValue(); +}; +inherits(YamahaAVRPlatform.AudioVolume, Characteristic); + +YamahaAVRPlatform.Muting = function() { + Characteristic.call(this, 'Muting', '00001002-0000-1000-8000-135D67EC4377'); + this.format = 'bool'; + this.readable = true; + this.writable = true; + this.supportsEventNotification = true; + this.value = this.getDefaultValue(); +}; +inherits(YamahaAVRPlatform.Muting, Characteristic); + +YamahaAVRPlatform.AudioDeviceService = function(displayName, subtype) { + Service.call(this, displayName, '00000001-0000-1000-8000-135D67EC4377', subtype); + + // Required Characteristics + this.addCharacteristic(YamahaAVRPlatform.AudioVolume); + + // Optional Characteristics + this.addOptionalCharacteristic(YamahaAVRPlatform.Muting); +}; +inherits(YamahaAVRPlatform.AudioDeviceService, Service); + + YamahaAVRPlatform.prototype = { accessories: function(callback) { this.log("Getting Yamaha AVR devices."); @@ -24,7 +72,9 @@ YamahaAVRPlatform.prototype = { var browser = this.browser; browser.stop(); browser.removeAllListeners('serviceUp'); // cleanup listeners - + var accessories = []; + var timer, timeElapsed = 0, checkCyclePeriod = 5000; + browser.on('serviceUp', function(service){ var name = service.name; //console.log('Found HTTP service "' + name + '"'); @@ -36,12 +86,36 @@ YamahaAVRPlatform.prototype = { var sysId = sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0]; that.log("Found Yamaha " + sysModel + " - " + sysId + ", \"" + name + "\""); var accessory = new YamahaAVRAccessory(that.log, that.config, service, yamaha, sysConfig); - callback([accessory]); + accessories.push(accessory); + if(accessories.length >= this.expectedDevices) + timeoutFunction(); // We're done, call the timeout function now. + //callback([accessory]); }, function(err){ return; - }) + }); }); browser.start(); + + // The callback can only be called once...so we'll have to find as many as we can + // in a fixed time and then call them in. + var timeoutFunction = function(){ + if(accessories.length >= that.expectedDevices){ + clearTimeout(timer); + } else { + timeElapsed += checkCyclePeriod; + if(timeElapsed > that.discoveryTimeout * 1000){ + that.log("Waited " + that.discoveryTimeout + " seconds, stopping discovery."); + } else { + timer = setTimeout(timeoutFunction, checkCyclePeriod); + return; + } + } + browser.stop(); + browser.removeAllListeners('serviceUp'); + that.log("Discovery finished, found " + accessories.length + " Yamaha AVR devices."); + callback(accessories); + }; + timer = setTimeout(timeoutFunction, checkCyclePeriod); } }; @@ -56,6 +130,9 @@ function YamahaAVRAccessory(log, config, mdnsService, yamaha, sysConfig) { this.serviceName = mdnsService.name + " Speakers"; this.setMainInputTo = config["setMainInputTo"]; this.playVolume = this.config["play_volume"]; + this.minVolume = config["min_volume"] || -50.0; + this.maxVolume = config["max_volume"] || -20.0; + this.gapVolume = this.maxVolume - this.minVolume; } YamahaAVRAccessory.prototype = { @@ -66,104 +143,74 @@ YamahaAVRAccessory.prototype = { if (playing) { - yamaha.powerOn().then(function(){ + return yamaha.powerOn().then(function(){ if (that.playVolume) return yamaha.setVolumeTo(that.playVolume*10); - else return { then: function(f, r){ f(); } }; + else return Q(); }).then(function(){ if (that.setMainInputTo) return yamaha.setMainInputTo(that.setMainInputTo); - else return { then: function(f, r){ f(); } }; + else return Q(); }).then(function(){ if (that.setMainInputTo == "AirPlay") return yamaha.SendXMLToReceiver( 'Play' ); - else return { then: function(f, r){ f(); } }; - //else return Promise.fulfilled(undefined); + else return Q(); }); } else { - yamaha.powerOff(); + return yamaha.powerOff(); } }, getServices: function() { var that = this; - return [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - },{ - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Yamaha", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0], - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0], - 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 - }] - },{ - sType: types.SWITCH_STYPE, - characteristics: [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.serviceName, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { that.setPlaying(value); }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Change the playback state of the Yamaha AV Receiver", - designedMaxLength: 1 - }] - }]; + var informationService = new Service.AccessoryInformation(); + var yamaha = this.yamaha; + + informationService + .setCharacteristic(Characteristic.Name, this.name) + .setCharacteristic(Characteristic.Manufacturer, "Yamaha") + .setCharacteristic(Characteristic.Model, this.sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0]) + .setCharacteristic(Characteristic.SerialNumber, this.sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0]); + + var switchService = new Service.Switch("Power State"); + switchService.getCharacteristic(Characteristic.On) + .on('get', function(callback, context){ + yamaha.isOn().then(function(result){ + callback(false, result); + }.bind(this)); + }.bind(this)) + .on('set', function(powerOn, callback){ + this.setPlaying(powerOn).then(function(){ + callback(false, powerOn); + }, function(error){ + callback(error, !powerOn); //TODO: Actually determine and send real new status. + }); + }.bind(this)); + + var audioDeviceService = new YamahaAVRPlatform.AudioDeviceService("Audio Functions"); + audioDeviceService.getCharacteristic(YamahaAVRPlatform.AudioVolume) + .on('get', function(callback, context){ + yamaha.getBasicInfo().done(function(basicInfo){ + var v = basicInfo.getVolume()/10.0; + var p = 100 * ((v - that.minVolume) / that.gapVolume); + p = p < 0 ? 0 : p > 100 ? 100 : Math.round(p); + debug("Got volume percent of " + p + "%"); + callback(false, p); + }); + }) + .on('set', function(p, callback){ + var v = ((p / 100) * that.gapVolume) + that.minVolume; + v = Math.round(v*10.0); + debug("Setting volume to " + v); + yamaha.setVolumeTo(v).then(function(){ + callback(false, p); + }); + }) + .getValue(null, null); // force an asynchronous get + + + return [informationService, switchService, audioDeviceService]; + } }; From 7aa758cb042bac0de651107a4e64eb28a82313cb Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Tue, 29 Sep 2015 06:53:22 +0200 Subject: [PATCH 033/121] Working towards getting the extra dimmers in one accessory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Having trouble with service subtypes…hmm… --- platforms/ZWayServer.js | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index 7c8b925..4915f89 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -113,10 +113,16 @@ ZWayServerPlatform.prototype = { if(vdev.tags.indexOf("Homebridge:Skip") >= 0) { debug("Tag says skip!"); continue; } if(this.opt_in && vdev.tags.indexOf("Homebridge:Include") < 0) continue; var gdid = vdev.id.replace(/^(.*?)_zway_(\d+-\d+)-\d.*/, '$1_$2'); - var gd = groupedDevices[gdid] || (groupedDevices[gdid] = {devices: [], types: {}, primary: undefined}); + var gd = groupedDevices[gdid] || (groupedDevices[gdid] = {devices: [], types: {}, extras: {}, primary: undefined}); gd.devices.push(vdev); - gd.types[ZWayServerPlatform.getVDevTypeKey(vdev)] = gd.devices.length - 1; - gd.types[vdev.deviceType] = gd.devices.length - 1; // also include the deviceType only as a possibility + var tk = ZWayServerPlatform.getVDevTypeKey(vdev); + if(gd.types[tk] === undefined){ + gd.types[tk] = gd.devices.length - 1; + } else { + gd.extras[tk] = gd.extras[tk] || []; + gd.extras[tk].push(gd.devices.length - 1); + } + if(tk !== vdev.deviceType) gd.types[vdev.deviceType] = gd.devices.length - 1; // also include the deviceType only as a possibility } //TODO: Make a second pass, re-splitting any devices that don't make sense together for(var gdid in groupedDevices) { @@ -250,25 +256,25 @@ ZWayServerAccessory.prototype = { var services = [], service; switch (typeKey) { case "switchBinary": - services.push(new Service.Switch(vdev.metrics.title)); + services.push(new Service.Switch(vdev.metrics.title, vdev.id)); break; case "switchMultilevel": - services.push(new Service.Lightbulb(vdev.metrics.title)); + services.push(new Service.Lightbulb(vdev.metrics.title, vdev.id)); break; case "thermostat": - services.push(new Service.Thermostat(vdev.metrics.title)); + services.push(new Service.Thermostat(vdev.metrics.title, vdev.id)); break; case "sensorMultilevel.Temperature": - services.push(new Service.TemperatureSensor(vdev.metrics.title)); + services.push(new Service.TemperatureSensor(vdev.metrics.title, vdev.id)); break; case "sensorBinary.Door/Window": - services.push(new Service.GarageDoorOpener(vdev.metrics.title)); + services.push(new Service.GarageDoorOpener(vdev.metrics.title, vdev.id)); break; case "battery.Battery": - services.push(new Service.BatteryService(vdev.metrics.title)); + services.push(new Service.BatteryService(vdev.metrics.title, vdev.id)); break; case "sensorMultilevel.Luminiscence": - services.push(new Service.LightSensor(vdev.metrics.title)); + services.push(new Service.LightSensor(vdev.metrics.title, vdev.id)); break; } @@ -335,7 +341,7 @@ ZWayServerAccessory.prototype = { } , configureCharacteristic: function(cx, vdev){ - var that, accessory = this; + var accessory = this; // Add this combination to the maps... if(!this.platform.cxVDevMap[vdev.id]) this.platform.cxVDevMap[vdev.id] = []; @@ -349,7 +355,7 @@ ZWayServerAccessory.prototype = { cx.value = cx.zway_getValueFromVDev(vdev); cx.on('get', function(callback, context){ debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - callback(false, that.name); + callback(false, accessory.name); }); cx.writable = false; return cx; @@ -597,7 +603,7 @@ ZWayServerAccessory.prototype = { if(cx instanceof Characteristic.StatusLowBattery){ cx.zway_getValueFromVDev = function(vdev){ - return vdev.metrics.level <= that.platform.batteryLow ? Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; + return vdev.metrics.level <= accessory.platform.batteryLow ? Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; }; cx.value = cx.zway_getValueFromVDev(vdev); cx.on('get', function(callback, context){ @@ -686,6 +692,12 @@ ZWayServerAccessory.prototype = { var services = [informationService]; services = services.concat(this.getVDevServices(this.devDesc.devices[this.devDesc.primary])); + + // Any extra switchMultilevels? Could be a RGBW+W bulb, add them as additional services... + if(this.devDesc.extras["switchMultilevel"]) for(var i = 0; i < this.devDesc.extras["switchMultilevel"].length; i++){ + var xvdev = this.devDesc.devices[this.devDesc.extras["switchMultilevel"][i]]; + services = services.concat(this.getVDevServices(xvdev)); + } if(this.platform.splitServices){ if(this.devDesc.types["battery.Battery"]){ From 0b3930e458054fbbd93144dc7c8420b4120a39bd Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Tue, 29 Sep 2015 06:59:40 +0200 Subject: [PATCH 034/121] Erm...oops. --- platforms/ZWayServer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index 33c5ac7..fcab891 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -93,8 +93,8 @@ ZWayServerPlatform.prototype = { "thermostat", "switchMultilevel", "switchBinary", - "sensorBinary.Door/Window" - "sensorMultilevel.Temperature", + "sensorBinary.Door/Window", + "sensorMultilevel.Temperature" ]; var that = this; From 99da61d30a1295b17e24bd8f1f8566d01eb29226 Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Tue, 29 Sep 2015 07:06:05 +0200 Subject: [PATCH 035/121] Lost some subtype parameters in deconfliction --- platforms/ZWayServer.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index fcab891..c86d4ac 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -256,16 +256,16 @@ ZWayServerAccessory.prototype = { var services = [], service; switch (typeKey) { case "thermostat": - services.push(new Service.Thermostat(vdev.metrics.title)); + services.push(new Service.Thermostat(vdev.metrics.title, vdev.id)); break; case "switchBinary": - services.push(new Service.Switch(vdev.metrics.title)); + services.push(new Service.Switch(vdev.metrics.title, vdev.id)); break; case "switchMultilevel": - services.push(new Service.Lightbulb(vdev.metrics.title)); + services.push(new Service.Lightbulb(vdev.metrics.title, vdev.id)); break; case "sensorBinary.Door/Window": - services.push(new Service.GarageDoorOpener(vdev.metrics.title)); + services.push(new Service.GarageDoorOpener(vdev.metrics.title, vdev.id)); break; case "sensorMultilevel.Temperature": services.push(new Service.TemperatureSensor(vdev.metrics.title, vdev.id)); From 2d88dafa11b1ebda2c2523e334c273ed89b5a802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Tue, 29 Sep 2015 22:45:34 +0200 Subject: [PATCH 036/121] disabled thermostat mode some cleanups added timestamp to longpoll console output --- platforms/FHEM.js | 61 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index e2261d8..f76593f 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -57,7 +57,8 @@ FHEM_update(inform_id, value, no_update) { FHEM_cached[inform_id] = value; //FHEM_cached[inform_id] = { 'value': value, 'timestamp': Date.now() }; - console.log(" caching: " + inform_id + ": " + value + " as " + typeof(value) ); + var date = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, ''); + console.log(" " + date + " caching: " + inform_id + ": " + value + " as " + typeof(value) ); if( !no_update ) subscription.characteristic.setValue(value, undefined, 'fromFHEM'); @@ -457,9 +458,60 @@ FHEM_rgb2hsv(r,g,b){ return [h,s,v]; } +function +FHEM_execute(log,connection,cmd,callback) { + var url = encodeURI( connection.base_url + "/fhem?cmd=" + cmd + "&XHR=1"); + log( ' executing: ' + url ); + + connection.request + .get( { url: url, gzip: true }, + function(err, response, result) { + if( !err && response.statusCode == 200 ) { + result = result.replace(/[\r\n]/g, ""); + if( callback ) + callback( result ); + + } else { + log("There was a problem connecting to FHEM ("+ url +")."); + if( response ) + log( " " + response.statusCode + ": " + response.statusMessage ); + + } + + } ) + .on( 'error', function(err) { log("There was a problem connecting to FHEM ("+ url +"):"+ err); } ); +} FHEMPlatform.prototype = { + execute: function(cmd,callback) {FHEM_execute(this.log, this.connection, cmd, callback)}, + + checkAndSetGenericDeviceType: function() { + this.log("Checking genericDeviceType..."); + + var cmd = '{AttrVal("global","userattr","")}'; + + this.execute( cmd, + function(result) { + //if( result == undefined ) + //result = ""; + + if( !result.match(/(^| )genericDeviceType\b/) ) { + //FIXME: use addToAttrList + var cmd = 'attr global userattr ' + result + ' genericDeviceType:ignore,switch,outlet,light,blind,thermostat,garage,window,lock'; + this.execute( cmd, + function(result) { +console.log( result ); + console.log( 'genericDeviceType attribute was not known. please restart homebridge.' ); + process.exit(0); + } ); + } + }.bind(this) ); + + }, + accessories: function(callback) { + //this.checkAndSetGenericDeviceType(); + this.log("Fetching FHEM switchable devices..."); var foundAccessories = []; @@ -1164,7 +1216,8 @@ FHEMAccessory.prototype = { this.execute(cmd); }, - execute: function(cmd,callback) { + execute: function(cmd,callback) {FHEM_execute(this.log, this.connection, cmd, callback)}, + executexxx: function(cmd,callback) { var url = encodeURI( this.connection.base_url + "/fhem?cmd=" + cmd + "&XHR=1"); this.log( ' executing: ' + url ); @@ -1890,7 +1943,7 @@ FHEMAccessory.prototype = { this.query(this.mappings.thermostat.reading, callback); }.bind(this) ); - if( this.mappings.thermostat_mode ) { + if( this.mappings.thermostat_modex ) { this.log(" current mode characteristic for " + this.name) var characteristic = controlService.getCharacteristic(Characteristic.CurrentHeatingCoolingState); @@ -1904,7 +1957,7 @@ FHEMAccessory.prototype = { }.bind(this) ); } - if( this.mappings.thermostat_mode ) { + if( this.mappings.thermostat_modex ) { this.log(" target mode characteristic for " + this.name) var characteristic = controlService.getCharacteristic(Characteristic.TargetHeatingCoolingState); From ba5722c5174ec2fba0fa0b6670aaf6ebdaa2e936 Mon Sep 17 00:00:00 2001 From: Raoul Date: Wed, 30 Sep 2015 09:21:59 +0200 Subject: [PATCH 037/121] props update minValue, maxValue used in Temperature devices (Apple's default values too narrow for beer coolers I've heard) adapted call syntax for "Simplify Characteristic properties" https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR108 --- accessories/knxdevice.js | 51 +++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/accessories/knxdevice.js b/accessories/knxdevice.js index 5801249..6efbbe3 100644 --- a/accessories/knxdevice.js +++ b/accessories/knxdevice.js @@ -195,26 +195,44 @@ KNXDevice.prototype = { }, // float knxregister_float: function(addresses, characteristic) { + // update for props refactor https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR50 + + var validValue = true; + var hk_value = 0.0; 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); - var hk_value = Math.round(val*10)/10; - if (hk_value>=characteristic.minimumValue && hk_value<=characteristic.maximumValue) { + // make hk_value compliant to properties + if (characteristic.props.minStep) { + // quantize + hk_value = Math.round(val/characteristic.props.minStep)*characteristic.props.minStep; + } else { + hk_value = val; + } + // range check + validValue = true; // assume validity at beginning + if (characteristic.props.minValue) { + validValue = validValue && (hk_value>=characteristic.props.minValue); + } + if (characteristic.props.maxValue) { + validValue = validValue && (hk_value<=characteristic.props.maxValue); + } + if (validValue) { 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); + this.log("Accessory "+ this.name +":" + characteristic.displayName+ ": Value %s out of bounds %s...%s ",hk_value, characteristic.props.minValue, characteristic.props.maxValue); } }.bind(this)); }, //integer knxregister_int: function(addresses, characteristic) { - this.log("knx registering FLOAT " + addresses); + this.log("knx registering INT " + 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.props.minValue || 0) && val<=(characteristic.props.maxValue || 255)) { characteristic.setValue(val, undefined, 'fromKNXBus'); } else { - this.log("Value %s out of bounds %s...%s ",hk_value, (characteristic.minimumValue || 0), (characteristic.maximumValue || 255)); + this.log("Accessory "+ this.name +":" + characteristic.displayName+ ": Value %s out of bounds %s...%s ",hk_value, (characteristic.props.minValue || 0), (characteristic.props.maxValue || 255)); } }.bind(this)); }, @@ -775,16 +793,25 @@ KNXDevice.prototype = { var myService = new Service.Thermostat(config.name,config.name); // CurrentTemperature) + // props update for https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR108 if (config.CurrentTemperature) { this.log("Thermostat CurrentTemperature characteristic enabled"); + myService.getCharacteristic(Characteristic.CurrentTemperature).setProps({ + minValue: config.CurrentTemperature.minValue || -40, + maxValue: config.CurrentTemperature.maxValue || 60 + }); // °C by default this.bindCharacteristic(myService, Characteristic.CurrentTemperature, "Float", config.CurrentTemperature); } // TargetTemperature if available if (config.TargetTemperature) { this.log("Thermostat TargetTemperature characteristic enabled"); // default boundary too narrow for thermostats - myService.getCharacteristic(Characteristic.TargetTemperature).minimumValue=0; // °C - myService.getCharacteristic(Characteristic.TargetTemperature).maximumValue=40; // °C + // props update for https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR108 + myService.getCharacteristic(Characteristic.TargetTemperature).setProps({ + minValue: config.TargetTemperature.minValue || 0, + maxValue: config.TargetTemperature.maxValue || 40 + }); + this.bindCharacteristic(myService, Characteristic.TargetTemperature, "Float", config.TargetTemperature); } // HVAC @@ -812,10 +839,16 @@ KNXDevice.prototype = { } var myService = new Service.TemperatureSensor(config.name,config.name); // CurrentTemperature) + // props update for https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR108 if (config.CurrentTemperature) { - this.log("TemperatureSensor CurrentTemperature characteristic enabled"); + this.log("Thermostat CurrentTemperature characteristic enabled"); + myService.getCharacteristic(Characteristic.CurrentTemperature).setProps({ + minValue: config.CurrentTemperature.minValue || -40, + maxValue: config.CurrentTemperature.maxValue || 60 + }); // °C by default this.bindCharacteristic(myService, Characteristic.CurrentTemperature, "Float", config.CurrentTemperature); } + return myService; }, getWindowService: function(config) { From 54f0c2f0cb894b77e78032311e3bf54252646aa0 Mon Sep 17 00:00:00 2001 From: Snowdd1 Date: Wed, 30 Sep 2015 20:03:20 +0200 Subject: [PATCH 038/121] Doc updates --- platforms/KNX-sample-config.json | 306 ++++++++++++++++--------------- platforms/KNX.md | 2 +- 2 files changed, 155 insertions(+), 153 deletions(-) diff --git a/platforms/KNX-sample-config.json b/platforms/KNX-sample-config.json index 2c29736..86e070b 100644 --- a/platforms/KNX-sample-config.json +++ b/platforms/KNX-sample-config.json @@ -1,154 +1,156 @@ { - "bridge": { - "name": "Homebridge", - "username": "CC:22:3D:E3:CE:30", - "port": 51826, - "pin": "031-45-154" - }, - "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!", - "platforms": [ - { - "platform": "KNX", - "name": "KNX", - "knxd_ip": "192.168.178.205", - "knxd_port": 6720, - "accessories": [ - { - "accessory_type": "knxdevice", - "description": "Only generic type knxdevice is supported, all previous knx types have been merged into that.", - "name": "Living Room North Lamp", - "services": [ - { - "type": "Lightbulb", - "description": "iOS8 Lightbulb type, supports On (Switch) and Brightness", - "name": "Living Room North Lamp", - "On": { - "Set": "1/1/6", - "Listen": [ - "1/1/63" - ] - }, - "Brightness": { - "Set": "1/1/62", - "Listen": [ - "1/1/64" - ] - } - } - ], - "services-description": "Services is an array, you CAN have multiple service types in one accessory, though it is not fully supported in many iOS HK apps, such as EVE and myTouchHome" - }, - { - "accessory_type": "knxdevice", - "name": "Office Temperature", - "description": "iOS8.4.1 TemperatureSensor type, supports CurrentTemperature", - "services": [ - { - "type": "TemperatureSensor", - "name": "Raumtemperatur", - "CurrentTemperature": { - "Listen": "3/3/44" - } - } - ] - }, - { - "accessory_type": "knxdevice", - "name": "Office Window Lock", - "services": [ - { - "type": "LockMechanism", - "description": "iOS8 Lock mechanism, Supports LockCurrentStateSecured0 OR LockCurrentState, LockTargetStateSecured0 OR LockTargetState, use depending if LOCKED is 0 or 1", - "name": "Office Window Lock", - "LockCurrentStateSecured0": { - "Listen": "5/3/15" - }, - "LockTargetStateSecured0": { - "Listen": "5/3/15" - } - } - ] - }, - { - "accessory_type": "knxdevice", - "description": "sample device with multiple services. Multiple services of different types are widely supported", - "name": "Office", - "services": [ - { - "type": "Lightbulb", - "name": "Office Lamp", - "On": { - "Set": "1/3/5" - } - }, - { - "type": "Thermostat", - "description": "iOS8 Thermostat type, supports CurrentTemperature, TargetTemperature, CurrentHeatingCoolingState ", - "name": "Raumtemperatur", - "CurrentTemperature": { - "Listen": "3/3/44" - }, - "TargetTemperature": { - "Set": "3/3/94" - }, - "CurrentHeatingCoolingState": { - "Listen": "3/3/64" - } - }, - { - "type": "WindowCovering", - "description": "iOS9 Window covering (blinds etc) type, still WIP", - "name": "Blinds", - "TargetPosition": { - "Set": "1/2/3", - "Listen": "1/2/4" - }, - "CurrentPosition": { - "Set": "1/3/1", - "Listen": "1/3/2" - }, - "PositionState": { - "Listen": "2/7/1" - } - } - ] - }, - { - "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": [] + "bridge": { + "name": "Homebridge", + "username": "CC:22:3D:E3:CE:30", + "port": 51826, + "pin": "031-45-154" + }, + "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!", + "platforms": [ + { + "platform": "KNX", + "name": "KNX", + "knxd_ip": "192.168.178.205", + "knxd_port": 6720, + "accessories": [ + { + "accessory_type": "knxdevice", + "description": "Only generic type knxdevice is supported, all previous knx types have been merged into that.", + "name": "Living Room North Lamp", + "services": [ + { + "type": "Lightbulb", + "description": "iOS8 Lightbulb type, supports On (Switch) and Brightness", + "name": "Living Room North Lamp", + "On": { + "Set": "1/1/6", + "Listen": [ + "1/1/63" + ] + }, + "Brightness": { + "Set": "1/1/62", + "Listen": [ + "1/1/64" + ] + } + } + ], + "services-description": "Services is an array, you CAN have multiple service types in one accessory, though it is not fully supported in many iOS HK apps, such as EVE and myTouchHome" + }, + { + "accessory_type": "knxdevice", + "name": "Office Temperature", + "description": "iOS8.4.1 TemperatureSensor type, supports CurrentTemperature", + "services": [ + { + "type": "TemperatureSensor", + "name": "Raumtemperatur", + "CurrentTemperature": { + "Listen": "3/3/44" + } + } + ] + }, + { + "accessory_type": "knxdevice", + "name": "Office Window Lock", + "services": [ + { + "type": "LockMechanism", + "description": "iOS8 Lock mechanism, Supports LockCurrentStateSecured0 OR LockCurrentState, LockTargetStateSecured0 OR LockTargetState, use depending if LOCKED is 0 or 1", + "name": "Office Window Lock", + "LockCurrentStateSecured0": { + "Listen": "5/3/15" + }, + "LockTargetStateSecured0": { + "Listen": "5/3/15" + } + } + ] + }, + { + "accessory_type": "knxdevice", + "description": "sample device with multiple services. Multiple services of different types are widely supported", + "name": "Office", + "services": [ + { + "type": "Lightbulb", + "name": "Office Lamp", + "On": { + "Set": "1/3/5" + } + }, + { + "type": "Thermostat", + "description": "iOS8 Thermostat type, supports CurrentTemperature, TargetTemperature, CurrentHeatingCoolingState ", + "name": "Raumtemperatur", + "CurrentTemperature": { + "Listen": "3/3/44" + }, + "TargetTemperature": { + "Set": "3/3/94" + }, + "CurrentHeatingCoolingState": { + "Listen": "3/3/64" + } + }, + { + "type": "WindowCovering", + "description": "iOS9 Window covering (blinds etc) type, still WIP", + "name": "Blinds", + "TargetPosition": { + "Set": "1/2/3", + "Listen": "1/2/4" + }, + "CurrentPosition": { + "Set": "1/3/1", + "Listen": "1/3/2" + }, + "PositionState": { + "Listen": "2/7/1" + } + } + ] + }, + { + "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 diff --git a/platforms/KNX.md b/platforms/KNX.md index c3b4fe3..bab706d 100644 --- a/platforms/KNX.md +++ b/platforms/KNX.md @@ -152,7 +152,7 @@ Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group addres ## WindowCovering - CurrentPosition: DPT5 percentage - TargetPosition: DPT5 percentage -- PositionState: DPT5 value [listen only] +- PositionState: DPT5 value [listen only: 0 Closing, 1 Opening, 2 STopped] ### not yet supported - HoldPosition From 98ee9f8f95d7f2debada459b96b4cdba4a8b39ff Mon Sep 17 00:00:00 2001 From: Snowdd1 Date: Wed, 30 Sep 2015 22:07:33 +0200 Subject: [PATCH 039/121] Return value guarding; Cleaner logs --- accessories/knxdevice.js | 154 +++++++++++++++++++-------------------- platforms/KNX.md | 33 ++++++++- 2 files changed, 108 insertions(+), 79 deletions(-) diff --git a/accessories/knxdevice.js b/accessories/knxdevice.js index 6efbbe3..b2967c2 100644 --- a/accessories/knxdevice.js +++ b/accessories/knxdevice.js @@ -159,7 +159,7 @@ KNXDevice.prototype = { knxregister_bool: function(addresses, characteristic) { this.log("knx registering BOOLEAN " + 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); + this.log("[" +this.name + "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type + " for " + characteristic.displayName); // iterate(characteristic); characteristic.setValue(val ? 1 : 0, undefined, 'fromKNXBus'); }.bind(this)); @@ -167,7 +167,7 @@ KNXDevice.prototype = { knxregister_boolReverse: function(addresses, characteristic) { this.log("knx registering BOOLEAN " + 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); + this.log("[" +this.name + "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type + " for " + characteristic.displayName); // iterate(characteristic); characteristic.setValue(val ? 0 : 1, undefined, 'fromKNXBus'); }.bind(this)); @@ -176,19 +176,19 @@ KNXDevice.prototype = { knxregister_percent: function(addresses, characteristic) { this.log("knx registering PERCENT " + 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); + this.log("[" +this.name + "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); if (type !== "DPT5") { this.log("[ERROR] Received value cannot be a percentage value"); } else { - if (!characteristic.timeout) { - if (characteristic.timeout < Date.now()) { +// if (!characteristic.timeout) { +// if (characteristic.timeout < Date.now()) { characteristic.setValue(Math.round(val/255*100), undefined, 'fromKNXBus'); - } else { - this.log("Blackout time"); - } - } else { - characteristic.setValue(Math.round(val/255*100), undefined, 'fromKNXBus'); - } // todo get the boolean logic right into one OR expresssion +// } else { +// this.log("Blackout time"); +// } +// } else { +// characteristic.setValue(Math.round(val/255*100), undefined, 'fromKNXBus'); +// } // todo get the boolean logic right into one OR expresssion } }.bind(this)); @@ -199,13 +199,13 @@ KNXDevice.prototype = { var validValue = true; var hk_value = 0.0; - this.log("knx registering FLOAT " + addresses); + this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: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); + this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); // make hk_value compliant to properties if (characteristic.props.minStep) { // quantize - hk_value = Math.round(val/characteristic.props.minStep)*characteristic.props.minStep; + hk_value = Math.round(val/characteristic.props.minStep)/(1/characteristic.props.minStep); } else { hk_value = val; } @@ -220,26 +220,26 @@ KNXDevice.prototype = { if (validValue) { characteristic.setValue(hk_value, undefined, 'fromKNXBus'); // 1 decimal for HomeKit } else { - this.log("Accessory "+ this.name +":" + characteristic.displayName+ ": Value %s out of bounds %s...%s ",hk_value, characteristic.props.minValue, characteristic.props.maxValue); + this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: Value %s out of bounds %s...%s ",hk_value, characteristic.props.minValue, characteristic.props.maxValue); } }.bind(this)); }, //integer knxregister_int: function(addresses, characteristic) { - this.log("knx registering INT " + addresses); + this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:knx registering INT " + 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); + this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); if (val>=(characteristic.props.minValue || 0) && val<=(characteristic.props.maxValue || 255)) { characteristic.setValue(val, undefined, 'fromKNXBus'); } else { - this.log("Accessory "+ this.name +":" + characteristic.displayName+ ": Value %s out of bounds %s...%s ",hk_value, (characteristic.props.minValue || 0), (characteristic.props.maxValue || 255)); + this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: Value %s out of bounds %s...%s ",hk_value, (characteristic.props.minValue || 0), (characteristic.props.maxValue || 255)); } }.bind(this)); }, knxregister_HVAC: function(addresses, characteristic) { - this.log("knx registering HVAC " + addresses); + this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:knx registering HVAC " + 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); + this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); var HAPvalue = 0; switch (val){ case 0: @@ -279,9 +279,9 @@ KNXDevice.prototype = { */ // undefined, has to match! knxregister: function(addresses, characteristic) { - this.log("knx registering " + addresses); + this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:knx registering " + 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); + this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); characteristic.setValue(val, undefined, 'fromKNXBus'); }.bind(this)); }, @@ -296,7 +296,7 @@ KNXDevice.prototype = { */ setBooleanState: function(value, callback, context, gaddress) { if (context === 'fromKNXBus') { - this.log(gaddress + " event ping pong, exit!"); +// this.log(gaddress + " event ping pong, exit!"); if (callback) { callback(); } @@ -305,14 +305,14 @@ KNXDevice.prototype = { if (value) { numericValue = 1; // need 0 or 1, not true or something } - this.log("Setting "+gaddress+" Boolean to %s", numericValue); + this.log("["+ this.name +"]:Setting "+gaddress+" Boolean to %s", numericValue); this.knxwrite(callback, gaddress,'DPT1',numericValue); } }, setBooleanReverseState: function(value, callback, context, gaddress) { if (context === 'fromKNXBus') { - this.log(gaddress + " event ping pong, exit!"); +// this.log(gaddress + " event ping pong, exit!"); if (callback) { callback(); } @@ -321,14 +321,14 @@ KNXDevice.prototype = { if (!value) { numericValue = 1; // need 0 or 1, not true or something } - this.log("Setting "+gaddress+" Boolean to %s", numericValue); + this.log("["+ this.name +"]:Setting "+gaddress+" Boolean to %s", numericValue); this.knxwrite(callback, gaddress,'DPT1',numericValue); } }, setPercentage: function(value, callback, context, gaddress) { if (context === 'fromKNXBus') { - this.log("event ping pong, exit!"); +// this.log(gaddress + "event ping pong, exit!"); if (callback) { callback(); } @@ -337,13 +337,13 @@ KNXDevice.prototype = { if (value) { numericValue = Math.round(255*value/100); // convert 1..100 to 1..255 for KNX bus } - this.log("Setting "+gaddress+" percentage to %s (%s)", value, numericValue); + this.log("["+ this.name +"]:Setting "+gaddress+" percentage to %s (%s)", value, numericValue); this.knxwrite(callback, gaddress,'DPT5',numericValue); } }, setInt: function(value, callback, context, gaddress) { if (context === 'fromKNXBus') { - this.log("event ping pong, exit!"); +// this.log(gaddress + "event ping pong, exit!"); if (callback) { callback(); } @@ -352,13 +352,13 @@ KNXDevice.prototype = { 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.log("["+ this.name +"]: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!"); +// this.log(gaddress + " event ping pong, exit!"); if (callback) { callback(); } @@ -367,13 +367,13 @@ KNXDevice.prototype = { if (value) { numericValue = value; // homekit expects precision of 1 decimal } - this.log("Setting "+gaddress+" Float to %s", numericValue); + this.log("["+ this.name +"]:Setting "+gaddress+" Float to %s", numericValue); this.knxwrite(callback, gaddress,'DPT9',numericValue); } }, setHVACState: function(value, callback, context, gaddress) { if (context === 'fromKNXBus') { - this.log(gaddress + " event ping pong, exit!"); +// this.log(gaddress + " event ping pong, exit!"); if (callback) { callback(); } @@ -396,7 +396,7 @@ KNXDevice.prototype = { KNXvalue = 1; } - this.log("Setting "+gaddress+" HVAC to %s", KNXvalue); + this.log("["+ this.name +"]:Setting "+gaddress+" HVAC to %s", KNXvalue); this.knxwrite(callback, gaddress,'DPT5',KNXvalue); } @@ -405,7 +405,7 @@ KNXDevice.prototype = { * */ identify: function(callback) { - this.log("Identify requested!"); + this.log("["+ this.name +"]:Identify requested!"); callback(); // success }, /** bindCharacteristic @@ -479,10 +479,10 @@ KNXDevice.prototype = { this.knxregister_HVAC([config.Set].concat(config.Listen || []), myCharacteristic); break; default: - this.log("[ERROR] unknown type passed"); + this.log("[ERROR] unknown type passed: ["+valueType+"]"); throw new Error("[ERROR] unknown type passed"); } - this.log("Issuing read requests on the KNX bus..."); + this.log("["+ this.name +"]:["+myCharacteristic.displayName+"]: Issuing read requests on the KNX bus..."); this.knxreadarray([config.Set].concat(config.Listen || [])); } return myCharacteristic; // for chaining or whatsoever @@ -512,30 +512,30 @@ KNXDevice.prototype = { var myService = new Service.ContactSensor(config.name,config.name); if (config.ContactSensorState) { - this.log("ContactSensor ContactSensorState characteristic enabled"); + this.log("["+ this.name +"]:ContactSensor ContactSensorState characteristic enabled"); this.bindCharacteristic(myService, Characteristic.ContactSensorState, "Bool", config.ContactSensorState); } else if (config.ContactSensorStateContact1) { - this.log("ContactSensor ContactSensorStateContact1 characteristic enabled"); + this.log("["+ this.name +"]:ContactSensor ContactSensorStateContact1 characteristic enabled"); this.bindCharacteristic(myService, Characteristic.ContactSensorState, "BoolReverse", config.ContactSensorStateContact1); } //optionals if (config.StatusActive) { - this.log("ContactSensor StatusActive characteristic enabled"); + this.log("["+ this.name +"]: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"); + this.log("["+ this.name +"]: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"); + this.log("["+ this.name +"]: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"); + this.log("["+ this.name +"]:ContactSensor StatusLowBattery characteristic enabled"); myService.addCharacteristic(Characteristic.StatusLowBattery); this.bindCharacteristic(myService, Characteristic.StatusLowBattery, "Bool", config.StatusLowBattery); } @@ -573,27 +573,27 @@ KNXDevice.prototype = { var myService = new Service.GarageDoorOpener(config.name,config.name); if (config.CurrentDoorState) { - this.log("GarageDoorOpener CurrentDoorState characteristic enabled"); + this.log("["+ this.name +"]:GarageDoorOpener CurrentDoorState characteristic enabled"); this.bindCharacteristic(myService, Characteristic.CurrentDoorState, "Int", config.CurrentDoorState); } if (config.TargetDoorState) { - this.log("GarageDoorOpener TargetDoorState characteristic enabled"); + this.log("["+ this.name +"]: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) { - this.log("GarageDoorOpener ObstructionDetected characteristic enabled"); + this.log("["+ this.name +"]:GarageDoorOpener ObstructionDetected characteristic enabled"); this.bindCharacteristic(myService, Characteristic.ObstructionDetected, "Bool", config.ObstructionDetected); } //optionals if (config.LockCurrentState) { - this.log("GarageDoorOpener LockCurrentState characteristic enabled"); + this.log("["+ this.name +"]: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"); + this.log("["+ this.name +"]:GarageDoorOpener LockTargetState characteristic enabled"); myService.addCharacteristic(Characteristic.LockTargetState); this.bindCharacteristic(myService, Characteristic.LockTargetState, "Bool", config.LockTargetState); } @@ -614,12 +614,12 @@ KNXDevice.prototype = { var myService = new Service.Lightbulb(config.name,config.name); // On (and Off) if (config.On) { - this.log("Lightbulb on/off characteristic enabled"); + this.log("["+ this.name +"]:Lightbulb on/off characteristic enabled"); this.bindCharacteristic(myService, Characteristic.On, "Bool", config.On); } // On characteristic // Brightness if available if (config.Brightness) { - this.log("Lightbulb Brightness characteristic enabled"); + this.log("["+ this.name +"]:Lightbulb Brightness characteristic enabled"); myService.addCharacteristic(Characteristic.Brightness); // it's an optional this.bindCharacteristic(myService, Characteristic.Brightness, "Percent", config.Brightness); } @@ -641,7 +641,7 @@ KNXDevice.prototype = { var myService = new Service.LightSensor(config.name,config.name); // CurrentTemperature) if (config.CurrentAmbientLightLevel) { - this.log("LightSensor CurrentAmbientLightLevel characteristic enabled"); + this.log("["+ this.name +"]:LightSensor CurrentAmbientLightLevel characteristic enabled"); this.bindCharacteristic(myService, Characteristic.CurrentAmbientLightLevel, "Float", config.CurrentAmbientLightLevel); } return myService; @@ -666,19 +666,19 @@ KNXDevice.prototype = { // LockCurrentState if (config.LockCurrentState) { // for normal contacts: Secured = 1 - this.log("LockMechanism LockCurrentState characteristic enabled"); + this.log("["+ this.name +"]:LockMechanism LockCurrentState characteristic enabled"); this.bindCharacteristic(myService, Characteristic.LockCurrentState, "Bool", config.LockCurrentState); } else if (config.LockCurrentStateSecured0) { // for reverse contacts Secured = 0 - this.log("LockMechanism LockCurrentState characteristic enabled"); + this.log("["+ this.name +"]:LockMechanism LockCurrentState characteristic enabled"); this.bindCharacteristic(myService, Characteristic.LockCurrentState, "BoolReverse", config.LockCurrentStateSecured0); } // LockTargetState if (config.LockTargetState) { - this.log("LockMechanism LockTargetState characteristic enabled"); + this.log("["+ this.name +"]:LockMechanism LockTargetState characteristic enabled"); this.bindCharacteristic(myService, Characteristic.LockTargetState, "Bool", config.LockTargetState); } else if (config.LockTargetStateSecured0) { - this.log("LockMechanism LockTargetState characteristic enabled"); + this.log("["+ this.name +"]:LockMechanism LockTargetState characteristic enabled"); this.bindCharacteristic(myService, Characteristic.LockTargetState, "BoolReverse", config.LockTargetStateSecured0); } @@ -701,27 +701,27 @@ KNXDevice.prototype = { var myService = new Service.MotionSensor(config.name,config.name); if (config.MotionDetected) { - this.log("MotionSensor MotionDetected characteristic enabled"); + this.log("["+ this.name +"]:MotionSensor MotionDetected characteristic enabled"); this.bindCharacteristic(myService, Characteristic.MotionDetected, "Bool", config.MotionDetected); } //optionals if (config.StatusActive) { - this.log("MotionSensor StatusActive characteristic enabled"); + this.log("["+ this.name +"]: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"); + this.log("["+ this.name +"]: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"); + this.log("["+ this.name +"]: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"); + this.log("["+ this.name +"]:MotionSensor StatusLowBattery characteristic enabled"); myService.addCharacteristic(Characteristic.StatusLowBattery); this.bindCharacteristic(myService, Characteristic.StatusLowBattery, "Bool", config.StatusLowBattery); } @@ -744,11 +744,11 @@ KNXDevice.prototype = { var myService = new Service.Outlet(config.name,config.name); // On (and Off) if (config.On) { - this.log("Outlet on/off characteristic enabled"); + this.log("["+ this.name +"]: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.log("["+ this.name +"]:Outlet on/off characteristic enabled"); this.bindCharacteristic(myService, Characteristic.OutletInUse, "Bool", config.OutletInUse); } return myService; @@ -766,7 +766,7 @@ KNXDevice.prototype = { var myService = new Service.Switch(config.name,config.name); // On (and Off) if (config.On) { - this.log("Switch on/off characteristic enabled"); + this.log("["+ this.name +"]:Switch on/off characteristic enabled"); this.bindCharacteristic(myService, Characteristic.On, "Bool", config.On); } // On characteristic @@ -795,7 +795,7 @@ KNXDevice.prototype = { // CurrentTemperature) // props update for https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR108 if (config.CurrentTemperature) { - this.log("Thermostat CurrentTemperature characteristic enabled"); + this.log("["+ this.name +"]:Thermostat CurrentTemperature characteristic enabled"); myService.getCharacteristic(Characteristic.CurrentTemperature).setProps({ minValue: config.CurrentTemperature.minValue || -40, maxValue: config.CurrentTemperature.maxValue || 60 @@ -804,7 +804,7 @@ KNXDevice.prototype = { } // TargetTemperature if available if (config.TargetTemperature) { - this.log("Thermostat TargetTemperature characteristic enabled"); + this.log("["+ this.name +"]:Thermostat TargetTemperature characteristic enabled"); // default boundary too narrow for thermostats // props update for https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR108 myService.getCharacteristic(Characteristic.TargetTemperature).setProps({ @@ -816,12 +816,12 @@ KNXDevice.prototype = { } // HVAC if (config.CurrentHeatingCoolingState) { - this.log("Thermostat CurrentHeatingCoolingState characteristic enabled"); + this.log("["+ this.name +"]:Thermostat CurrentHeatingCoolingState characteristic enabled"); this.bindCharacteristic(myService, Characteristic.CurrentHeatingCoolingState, "HVAC", config.CurrentHeatingCoolingState); } // HVAC if (config.TargetHeatingCoolingState) { - this.log("Thermostat TargetHeatingCoolingState characteristic enabled"); + this.log("["+ this.name +"]:Thermostat TargetHeatingCoolingState characteristic enabled"); this.bindCharacteristic(myService, Characteristic.TargetHeatingCoolingState, "HVAC", config.TargetHeatingCoolingState); } return myService; @@ -841,7 +841,7 @@ KNXDevice.prototype = { // CurrentTemperature) // props update for https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR108 if (config.CurrentTemperature) { - this.log("Thermostat CurrentTemperature characteristic enabled"); + this.log("["+ this.name +"]:TemperatureSensor CurrentTemperature characteristic enabled"); myService.getCharacteristic(Characteristic.CurrentTemperature).setProps({ minValue: config.CurrentTemperature.minValue || -40, maxValue: config.CurrentTemperature.maxValue || 60 @@ -878,15 +878,15 @@ KNXDevice.prototype = { var myService = new Service.Window(config.name,config.name); if (config.CurrentPosition) { - this.log("Window CurrentPosition characteristic enabled"); + this.log("["+ this.name +"]:Window CurrentPosition characteristic enabled"); this.bindCharacteristic(myService, Characteristic.CurrentPosition, "Percent", config.CurrentPosition); } if (config.TargetPosition) { - this.log("Window TargetPosition characteristic enabled"); + this.log("["+ this.name +"]:Window TargetPosition characteristic enabled"); this.bindCharacteristic(myService, Characteristic.TargetPosition, "Percent", config.TargetPosition); } if (config.PositionState) { - this.log("Window PositionState characteristic enabled"); + this.log("["+ this.name +"]:Window PositionState characteristic enabled"); this.bindCharacteristic(myService, Characteristic.PositionState, "Float", config.PositionState); } return myService; @@ -913,15 +913,15 @@ KNXDevice.prototype = { var myService = new Service.WindowCovering(config.name,config.name); if (config.CurrentPosition) { - this.log("WindowCovering CurrentPosition characteristic enabled"); + this.log("["+ this.name +"]:WindowCovering CurrentPosition characteristic enabled"); this.bindCharacteristic(myService, Characteristic.CurrentPosition, "Percent", config.CurrentPosition); } if (config.TargetPosition) { - this.log("WindowCovering TargetPosition characteristic enabled"); + this.log("["+ this.name +"]:WindowCovering TargetPosition characteristic enabled"); this.bindCharacteristic(myService, Characteristic.TargetPosition, "Percent", config.TargetPosition); } if (config.PositionState) { - this.log("WindowCovering PositionState characteristic enabled"); + this.log("["+ this.name +"]:WindowCovering PositionState characteristic enabled"); this.bindCharacteristic(myService, Characteristic.PositionState, "Float", config.PositionState); } return myService; @@ -943,7 +943,7 @@ KNXDevice.prototype = { informationService .setCharacteristic(Characteristic.Manufacturer, "Opensource Community") .setCharacteristic(Characteristic.Model, "KNX Universal Device") - .setCharacteristic(Characteristic.SerialNumber, "Version 1.1.2"); + .setCharacteristic(Characteristic.SerialNumber, "Version 1.1.4"); accessoryServices.push(informationService); @@ -998,8 +998,8 @@ KNXDevice.prototype = { accessoryServices.push(this.getWindowCoveringService(configService)); break; default: - this.log("[ERROR] unknown 'type' property of '"+configService.type+"' for service "+ configService.name + " in config.json. KNX platform section fault "); - //throw new Error("[ERROR] unknown 'type' property for service "+ configService.name + " in config.json. KNX platform section fault "); + this.log("[ERROR] unknown 'type' property of ["+configService.type+"] for service ["+ configService.name + "] in config.json. KNX platform section fault "); + throw new Error("[ERROR] unknown 'type' property of ["+configService.type+"] for service '"+ configService.name + "' in config.json. KNX platform section fault "); } } // start listening for events on the bus (if not started yet - will prevent itself) diff --git a/platforms/KNX.md b/platforms/KNX.md index bab706d..0b4d4e9 100644 --- a/platforms/KNX.md +++ b/platforms/KNX.md @@ -47,7 +47,7 @@ You have to add services in the following syntax: { "type": "SERVICENAME", "description": "This is just for you to remember things", - "name": "We need a name for each service, though it usually shows only if multiple services are present in one accessory", + "name": "beer tap thermostat", "CHARACTERISTIC1": { "Set": "1/1/6", "Listen": [ @@ -68,6 +68,35 @@ Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group addres `"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:` +For two characteristics there are additional minValue and maxValue attributes. These are CurrentTemperature and TargetTemperature, and are used in TemperatureSensor and Thermostat. + +So the charcteristic section may look like: + + ````json + { + "type": "Thermostat", + "description": "Sample thermostat", + "name": "We need a name for each service, though it usually shows only if multiple services are present in one accessory", + "CurrentTemperature": { + "Set": "1/1/6", + "Listen": [ + "1/1/63" + ], + minValue: -18, + maxValue: 30 + }, + "TargetTemperature": { + "Set": "1/1/62", + "Listen": [ + "1/1/64" + ], + minValue: -4, + maxValue: 12 + } + } +```` + + # Supported Services and their characteristics ## ContactSensor @@ -139,7 +168,7 @@ Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group addres - CurrentTemperature: DPT9.001 in °C [listen only] ## Thermostat -- CurrentTemperature: DPT9.001 in °C [listen only] +- CurrentTemperature: DPT9.001 in °C [listen only], -40 to 80°C if not overriden as shown above - 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 From 1fce5c875439a9a61a9f1ff4f7c483aedba440e0 Mon Sep 17 00:00:00 2001 From: Jon Maddox Date: Thu, 1 Oct 2015 01:29:42 -0400 Subject: [PATCH 040/121] ignore hiddent Home Assistant devices --- platforms/HomeAssistant.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/platforms/HomeAssistant.js b/platforms/HomeAssistant.js index 5a23ab5..61d3bc2 100644 --- a/platforms/HomeAssistant.js +++ b/platforms/HomeAssistant.js @@ -152,15 +152,24 @@ HomeAssistantPlatform.prototype = { entity = data[i] entity_type = entity.entity_id.split('.')[0] + // ignore devices that are not in the list of supported types if (that.supportedTypes.indexOf(entity_type) == -1) { continue; } + // ignore hidden devices + if (entity.attributes && entity.attributes.hidden) { + continue; + } + var accessory = null if (entity_type == 'light') { accessory = new HomeAssistantLight(that.log, entity, that) }else if (entity_type == 'switch'){ + console.log(JSON.stringify(entity)) + console.log(""); + console.log(""); accessory = new HomeAssistantSwitch(that.log, entity, that) }else if (entity_type == 'scene'){ accessory = new HomeAssistantSwitch(that.log, entity, that, 'scene') From 45f40874efd315c0aa7ecb145c521f3a6ac5ef92 Mon Sep 17 00:00:00 2001 From: Mikulas Date: Thu, 1 Oct 2015 23:22:17 +0200 Subject: [PATCH 041/121] add lifx to sample config --- config-sample.json | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/config-sample.json b/config-sample.json index 6f24554..7af74db 100644 --- a/config-sample.json +++ b/config-sample.json @@ -89,14 +89,19 @@ "delay": 30, "repeat": 3, "zones":["Kitchen Lamp","Bedroom Lamp","Living Room Lamp","Hallway Lamp"] - }, - { + }, + { "platform": "HomeAssistant", "name": "HomeAssistant", "host": "http://192.168.1.10:8123", "password": "XXXXX", "supported_types": ["light", "switch", "media_player", "scene"] - } + }, + { + "platform": "LIFx", + "name": "LIFx", + "access_token": "XXXXXXXX generate at https://cloud.lifx.com/settings" + } ], "accessories": [ From 3bca9f70f55ba8fd37b7f8b561e1c9196bd26ab4 Mon Sep 17 00:00:00 2001 From: Mike Enriquez Date: Thu, 1 Oct 2015 20:39:00 -0400 Subject: [PATCH 042/121] freeze node-xmpp-client version to working version --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 3a3ca74..fdb62a6 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "xmldoc": "0.1.x", "komponist" : "0.1.0", "yamaha-nodejs": "0.4.x", - "debug": "^2.2.0" + "debug": "^2.2.0", + "node-xmpp-client": "1.0.0-alpha23" } } From 98519c84dd642093247e2f40930dcaf92250cefb Mon Sep 17 00:00:00 2001 From: Mike Enriquez Date: Thu, 1 Oct 2015 23:08:36 -0400 Subject: [PATCH 043/121] Fix crash when trying to get/set power state - Call getPowerState on the object instead of passing the function to fix undefined isActivity error - Remove callback param from setPowerState since it is not called with a callback param. - startActivity expects the activity id as a parameter. We can't actually toggle activities off and on. There is a "PowerOff" activity that will turn everything off. --- platforms/LogitechHarmony.js | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/platforms/LogitechHarmony.js b/platforms/LogitechHarmony.js index a5c5e22..845f7f8 100644 --- a/platforms/LogitechHarmony.js +++ b/platforms/LogitechHarmony.js @@ -163,9 +163,9 @@ LogitechHarmonyAccessory.prototype = { var self = this; if (this.isActivity) { - hub.getCurrentActivity().then(function (currentActivity) { - callback(currentActivity.id === self.id); - }).except(function (err) { + this.hub.getCurrentActivity().then(function (currentActivity) { + callback(currentActivity === self.id); + }).catch(function (err) { self.log('Unable to get current activity with error', err); callback(false); }); @@ -175,28 +175,23 @@ LogitechHarmonyAccessory.prototype = { } }, - setPowerState: function (state, callback) { + setPowerState: function (state) { var self = this; if (this.isActivity) { this.log('Set activity ' + this.name + ' power state to ' + state); - // Activity id -1 is turn off all devices - var id = state ? this.id : -1; - - this.hub.startActivity(id) + this.hub.startActivity(self.id) .then(function () { self.log('Finished setting activity ' + self.name + ' power state to ' + state); - callback(); }) .catch(function (err) { self.log('Failed setting activity ' + self.name + ' power state to ' + state + ' with error ' + err); - callback(err); }); } else { // TODO: Support setting device power this.log('TODO: Support setting device power'); - callback(); + // callback(); } }, @@ -284,7 +279,9 @@ LogitechHarmonyAccessory.prototype = { onUpdate: function (value) { self.setPowerState(value) }, - onRead: self.getPowerState, + onRead: function(callback) { + self.getPowerState(callback) + }, perms: ["pw","pr","ev"], format: "bool", initialValue: 0, @@ -300,6 +297,5 @@ LogitechHarmonyAccessory.prototype = { }; -module.exports.accessory = LogitechHarmonyAccessory; module.exports.platform = LogitechHarmonyPlatform; From d04d41734477256875717ab4d7a0c2a4457e5ca7 Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Fri, 2 Oct 2015 06:19:59 +0200 Subject: [PATCH 044/121] Initial read/write support for RGB bulbs complete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Needs testing, but seems to work with my Aeon bulb…taking into account the wonkiness of that bulb with Z-Way, at least. --- platforms/ZWayServer.js | 108 +++++++++++++++++++++++++++++++++++----- 1 file changed, 95 insertions(+), 13 deletions(-) diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index c86d4ac..730eb9b 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -180,6 +180,11 @@ ZWayServerPlatform.prototype = { if(this.cxVDevMap[upd.id]){ var vdev = this.vDevStore[upd.id]; vdev.metrics.level = upd.metrics.level; + if(upd.metrics.color){ + vdev.metrics.r = upd.metrics.r; + vdev.metrics.g = upd.metrics.g; + vdev.metrics.b = upd.metrics.b; + } vdev.updateTime = upd.updateTime; var cxs = this.cxVDevMap[upd.id]; for(var j = 0; j < cxs.length; j++){ @@ -231,26 +236,62 @@ ZWayServerAccessory.prototype = { }, rgb2hsv: function(obj) { + // RGB: 0-255; H: 0-360, S,V: 0-100 var r = obj.r/255, g = obj.g/255, b = obj.b/255; var max, min, d, h, s, v; - if (min === max) { - // shade of gray - return [0, 0, r]; - } - min = Math.min(r, Math.min(g, b)); max = Math.max(r, Math.max(g, b)); + if (min === max) { + // shade of gray + return {h: 0, s: 0, v: r * 100}; + } + var d = (r === min) ? g - b : ((b === min) ? r - g : b - r); h = (r === min) ? 3 : ((b === min) ? 1 : 5); h = 60 * (h - d/(max - min)); s = (max - min) / max; v = max; - return {"h": h, "s": s * 100, "v": v}; + return {"h": h, "s": s * 100, "v": v * 100}; + } + , + hsv2rgb: function(obj) { + // H: 0-360; S,V: 0-100; RGB: 0-255 + var r, g, b; + var sfrac = obj.s / 100; + var vfrac = obj.v / 100; + + if(sfrac === 0){ + var vbyte = Math.round(vfrac*255); + return { r: vbyte, g: vbyte, b: vbyte }; + } + + var hdb60 = (obj.h % 360) / 60; + var sector = Math.floor(hdb60); + var fpart = hdb60 - sector; + var c = vfrac * (1 - sfrac); + var x1 = vfrac * (1 - sfrac * fpart); + var x2 = vfrac * (1 - sfrac * (1 - fpart)); + switch(sector){ + case 0: + r = vfrac; g = x2; b = c; break; + case 1: + r = x1; g = vfrac; b = c; break; + case 2: + r = c; g = vfrac; b = x2; break; + case 3: + r = c; g = x1; b = vfrac; break; + case 4: + r = x2; g = c; b = vfrac; break; + case 5: + default: + r = vfrac; g = c; b = x1; break; + } + + return { "r": Math.round(255 * r), "g": Math.round(255 * g), "b": Math.round(255 * b) }; } , - getVDevServices: function(vdev){ var typeKey = ZWayServerPlatform.getVDevTypeKey(vdev); var services = [], service; @@ -340,7 +381,7 @@ ZWayServerAccessory.prototype = { return null; } , - configureCharacteristic: function(cx, vdev){ + configureCharacteristic: function(cx, vdev, service){ var accessory = this; // Add this combination to the maps... @@ -414,6 +455,7 @@ ZWayServerAccessory.prototype = { if(cx instanceof Characteristic.Hue){ cx.zway_getValueFromVDev = function(vdev){ + debug("Derived value " + accessory.rgb2hsv(vdev.metrics.color).h + " for hue."); return accessory.rgb2hsv(vdev.metrics.color).h; }; cx.value = cx.zway_getValueFromVDev(vdev); @@ -424,6 +466,18 @@ ZWayServerAccessory.prototype = { callback(false, cx.zway_getValueFromVDev(result.data)); }); }.bind(this)); + cx.on('set', function(hue, callback){ + var scx = service.getCharacteristic(Characteristic.Saturation); + var vcx = service.getCharacteristic(Characteristic.Brightness); + if(!scx || !vcx){ + debug("Hue without Saturation and Brightness is not supported! Cannot set value!") + callback(true, cx.value); + } + var rgb = this.hsv2rgb({ h: hue, s: scx.value, v: vcx.value }); + this.command(vdev, "exact", { red: rgb.r, green: rgb.g, blue: rgb.b }).then(function(result){ + callback(); + }); + }.bind(this)); cx.writeable = false; //cx.on('set', function(level, callback){ @@ -436,6 +490,7 @@ ZWayServerAccessory.prototype = { if(cx instanceof Characteristic.Saturation){ cx.zway_getValueFromVDev = function(vdev){ + debug("Derived value " + accessory.rgb2hsv(vdev.metrics.color).s + " for saturation."); return accessory.rgb2hsv(vdev.metrics.color).s; }; cx.value = cx.zway_getValueFromVDev(vdev); @@ -446,6 +501,18 @@ ZWayServerAccessory.prototype = { callback(false, cx.zway_getValueFromVDev(result.data)); }); }.bind(this)); + cx.on('set', function(saturation, callback){ + var hcx = service.getCharacteristic(Characteristic.Hue); + var vcx = service.getCharacteristic(Characteristic.Brightness); + if(!hcx || !vcx){ + debug("Saturation without Hue and Brightness is not supported! Cannot set value!") + callback(true, cx.value); + } + var rgb = this.hsv2rgb({ h: hcx.value, s: saturation, v: vcx.value }); + this.command(vdev, "exact", { red: rgb.r, green: rgb.g, blue: rgb.b }).then(function(result){ + callback(); + }); + }.bind(this)); cx.writeable = false; //cx.on('set', function(level, callback){ @@ -666,14 +733,29 @@ ZWayServerAccessory.prototype = { success = false; debug("ERROR! Failed to configure required characteristic \"" + service.characteristics[i].displayName + "\"!"); } - cx = this.configureCharacteristic(cx, vdev); + cx = this.configureCharacteristic(cx, vdev, service); } for(var i = 0; i < service.optionalCharacteristics.length; i++){ var cx = service.optionalCharacteristics[i]; - var vdev = this.getVDevForCharacteristic(cx); + var vdev = this.getVDevForCharacteristic(cx, vdev); if(!vdev) continue; - cx = this.configureCharacteristic(cx, vdev); - if(cx) service.addCharacteristic(cx); + + //NOTE: Questionable logic, but if the vdev has already been used for the same + // characteristic type elsewhere, lets not duplicate it just for the sake of an + // optional characteristic. This eliminates the problem with RGB+W+W bulbs + // having the HSV controls shown again, but might have unintended consequences... + var othercx, othercxs = this.platform.cxVDevMap[vdev.id]; + if(othercxs) for(var j = 0; j < othercxs.length; j++) if(othercxs[j].UUID === cx.UUID) othercx = othercxs[j]; + if(othercx) + continue; + + cx = this.configureCharacteristic(cx, vdev, service); + try { + if(cx) service.addCharacteristic(cx); + } + catch (ex) { + debug('Adding Characteristic "' + cx.displayName + '" failed with message "' + ex.message + '". This may be expected.'); + } } return success; } @@ -736,7 +818,7 @@ ZWayServerAccessory.prototype = { extraCxs = []; // to wipe out any already setup cxs. break; } - this.configureCharacteristic(cx, vdev2); + this.configureCharacteristic(cx, vdev2, service); extraCxs.push(cx); } for(var j = 0; j < extraCxs.length; j++) From fd72307384a432ac84123b4267cc12b1b64abef5 Mon Sep 17 00:00:00 2001 From: Snowdd1 Date: Fri, 2 Oct 2015 08:17:59 +0200 Subject: [PATCH 045/121] Fix hint in KNX-sample-config.json --- platforms/KNX-sample-config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platforms/KNX-sample-config.json b/platforms/KNX-sample-config.json index 86e070b..05b7e15 100644 --- a/platforms/KNX-sample-config.json +++ b/platforms/KNX-sample-config.json @@ -8,7 +8,7 @@ "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!", + "hint3": "For valid services and their characteristics have a look at the KNX.md file in folder platforms!", "platforms": [ { "platform": "KNX", From 3031ba7e0fdcf78251aa0691219b2b71d01f8434 Mon Sep 17 00:00:00 2001 From: Snowdd1 Date: Fri, 2 Oct 2015 18:05:40 +0200 Subject: [PATCH 046/121] Refactoring of reversely used group addresses The use of 0/1 for binary objects depends on installation, a contact sensor can have contact=1 or open=1 The same applies to percentages: a window can be 100% closed or 100% open. For a more general usage of reversed values, a suffix ("R") can now be attached to all DPT1 and DPT5 group addresses. Please see updated KNX.md for examples and details. Note: outdated characteristic-workaround throw an exception at boot-up: - ContactSensorStateContact1 - LockCurrentStateSecured0 - LockTargetStateSecured0 Please see error message and KNX.md for reference. --- accessories/knxdevice.js | 162 +++++++++++++++++-------------- platforms/KNX-sample-config.json | 10 +- platforms/KNX.js | 17 ++-- platforms/KNX.md | 30 ++++-- 4 files changed, 127 insertions(+), 92 deletions(-) diff --git a/accessories/knxdevice.js b/accessories/knxdevice.js index b2967c2..4656522 100644 --- a/accessories/knxdevice.js +++ b/accessories/knxdevice.js @@ -14,6 +14,9 @@ New 2015-09-18: New 2015-09-19: - GarageDoorOpener Service - MotionSensor Service +New 2015-10-02: +- Check for valid group addresses +- new "R" flag allowed for Boolean addresses: 1/2/3R is the boolean not(1/2/3), i.e. 0 and 1 switched on read and write * */ var Service = require("HAP-NodeJS").Service; @@ -24,6 +27,8 @@ var knxd_startMonitor = require('../platforms/KNX.js').startMonitor; var milliTimeout = 300; // used to block responses while swiping +var colorOn = "\x1b[30;47m"; +var colorOff = "\x1b[0m"; function KNXDevice(log, config) { this.log = log; @@ -115,6 +120,7 @@ KNXDevice.prototype = { if (!groupAddress) { return null; } + this.log("[knxdevice:knxread] preparing knx request for "+groupAddress); var knxdConnection = new knxd.Connection(); // this.log("DEBUG in knxread: created empty connection, trying to connect socket to "+this.knxd_ip+":"+this.knxd_port); knxdConnection.socketRemote({ host: this.knxd_ip, port: this.knxd_port }, function() { @@ -130,7 +136,7 @@ KNXDevice.prototype = { if (err) { this.log("[ERROR] knxread:sendAPDU: " + err); } else { - this.log("knx request sent for "+groupAddress); + this.log("[knxdevice:knxread] knx request sent for "+groupAddress); } }.bind(this)); } @@ -143,12 +149,12 @@ KNXDevice.prototype = { // handle multiple addresses for (var i = 0; i < groupAddresses.length; i++) { if (groupAddresses[i]) { // do not bind empty addresses - this.knxread (groupAddresses[i]); + this.knxread (groupAddresses[i].match(/(\d*\/\d*\/\d*)/)[0]); // clean address } } } else { // it's only one - this.knxread (groupAddresses); + this.knxread (groupAddresses.match(/(\d*\/\d*\/\d*)/)[0]); // regex for cleaning address } }, @@ -158,38 +164,30 @@ KNXDevice.prototype = { // boolean: get 0 or 1 from the bus, write boolean knxregister_bool: function(addresses, characteristic) { this.log("knx registering BOOLEAN " + addresses); - knxd_registerGA(addresses, function(val, src, dest, type){ + knxd_registerGA(addresses, function(val, src, dest, type, reverse){ this.log("[" +this.name + "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type + " for " + characteristic.displayName); // iterate(characteristic); - characteristic.setValue(val ? 1 : 0, undefined, 'fromKNXBus'); - }.bind(this)); - }, - knxregister_boolReverse: function(addresses, characteristic) { - this.log("knx registering BOOLEAN " + addresses); - knxd_registerGA(addresses, function(val, src, dest, type){ - this.log("[" +this.name + "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type + " for " + characteristic.displayName); -// iterate(characteristic); - characteristic.setValue(val ? 0 : 1, undefined, 'fromKNXBus'); + + characteristic.setValue(val ? (reverse ? 0:1) : (reverse ? 1:0), undefined, 'fromKNXBus'); }.bind(this)); }, +// knxregister_boolReverse: function(addresses, characteristic) { +// this.log("knx registering BOOLEAN REVERSE " + addresses); +// knxd_registerGA(addresses, function(val, src, dest, type, reverse){ +// this.log("[" +this.name + "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type + " for " + characteristic.displayName); +//// iterate(characteristic); +// characteristic.setValue(val ? 0 : 1, undefined, 'fromKNXBus'); +// }.bind(this)); +// }, // percentage: get 0..255 from the bus, write 0..100 to characteristic knxregister_percent: function(addresses, characteristic) { this.log("knx registering PERCENT " + addresses); - knxd_registerGA(addresses, function(val, src, dest, type){ + knxd_registerGA(addresses, function(val, src, dest, type, reverse){ this.log("[" +this.name + "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); if (type !== "DPT5") { this.log("[ERROR] Received value cannot be a percentage value"); } else { -// if (!characteristic.timeout) { -// if (characteristic.timeout < Date.now()) { - characteristic.setValue(Math.round(val/255*100), undefined, 'fromKNXBus'); -// } else { -// this.log("Blackout time"); -// } -// } else { -// characteristic.setValue(Math.round(val/255*100), undefined, 'fromKNXBus'); -// } // todo get the boolean logic right into one OR expresssion - + characteristic.setValue(Math.round(( reverse ? (255-val):val)/255*100), undefined, 'fromKNXBus'); } }.bind(this)); }, @@ -200,7 +198,7 @@ KNXDevice.prototype = { var validValue = true; var hk_value = 0.0; this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:knx registering FLOAT " + addresses); - knxd_registerGA(addresses, function(val, src, dest, type){ + knxd_registerGA(addresses, function(val, src, dest, type, reverse){ this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); // make hk_value compliant to properties if (characteristic.props.minStep) { @@ -227,10 +225,10 @@ KNXDevice.prototype = { //integer knxregister_int: function(addresses, characteristic) { this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:knx registering INT " + addresses); - knxd_registerGA(addresses, function(val, src, dest, type){ + knxd_registerGA(addresses, function(val, src, dest, type, reverse){ this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); if (val>=(characteristic.props.minValue || 0) && val<=(characteristic.props.maxValue || 255)) { - characteristic.setValue(val, undefined, 'fromKNXBus'); + characteristic.setValue(reverse ? (255-val):val, undefined, 'fromKNXBus'); } else { this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: Value %s out of bounds %s...%s ",hk_value, (characteristic.props.minValue || 0), (characteristic.props.maxValue || 255)); } @@ -294,39 +292,39 @@ KNXDevice.prototype = { * }.bind(this)); * */ - setBooleanState: function(value, callback, context, gaddress) { + setBooleanState: function(value, callback, context, gaddress, reverseflag) { if (context === 'fromKNXBus') { // this.log(gaddress + " event ping pong, exit!"); if (callback) { callback(); } } else { - var numericValue = 0; + var numericValue = reverseflag ? 1:0; if (value) { - numericValue = 1; // need 0 or 1, not true or something + numericValue = reverseflag ? 0:1; // need 0 or 1, not true or something } - this.log("["+ this.name +"]:Setting "+gaddress+" Boolean to %s", numericValue); + this.log("["+ this.name +"]:Setting "+gaddress+" " + reverseflag ? " (reverse)":""+ " Boolean to %s", numericValue); this.knxwrite(callback, gaddress,'DPT1',numericValue); } }, - setBooleanReverseState: function(value, callback, context, gaddress) { - if (context === 'fromKNXBus') { -// this.log(gaddress + " event ping pong, exit!"); - if (callback) { - callback(); - } - } else { - var numericValue = 0; - if (!value) { - numericValue = 1; // need 0 or 1, not true or something - } - this.log("["+ this.name +"]:Setting "+gaddress+" Boolean to %s", numericValue); - this.knxwrite(callback, gaddress,'DPT1',numericValue); - } - - }, - setPercentage: function(value, callback, context, gaddress) { +// setBooleanReverseState: function(value, callback, context, gaddress) { +// if (context === 'fromKNXBus') { +//// this.log(gaddress + " event ping pong, exit!"); +// if (callback) { +// callback(); +// } +// } else { +// var numericValue = 0; +// if (!value) { +// numericValue = 1; // need 0 or 1, not true or something +// } +// this.log("["+ this.name +"]:Setting "+gaddress+" Boolean to %s", numericValue); +// this.knxwrite(callback, gaddress,'DPT1',numericValue); +// } +// +// }, + setPercentage: function(value, callback, context, gaddress, reverseflag) { if (context === 'fromKNXBus') { // this.log(gaddress + "event ping pong, exit!"); if (callback) { @@ -334,8 +332,11 @@ KNXDevice.prototype = { } } else { var numericValue = 0; - if (value) { - numericValue = Math.round(255*value/100); // convert 1..100 to 1..255 for KNX bus + value = ( value>=0 ? (value<=100 ? value:100):0 ); //ensure range 0..100 + if (reverseflag) { + numericValue = 255 - Math.round(255*value/100); // convert 0..100 to 255..0 for KNX bus + } else { + numericValue = Math.round(255*value/100); // convert 0..100 to 0..255 for KNX bus } this.log("["+ this.name +"]:Setting "+gaddress+" percentage to %s (%s)", value, numericValue); this.knxwrite(callback, gaddress,'DPT5',numericValue); @@ -350,7 +351,7 @@ KNXDevice.prototype = { } else { var numericValue = 0; if (value && value>=0 && value<=255) { - numericValue = value; // assure 1..255 for KNX bus + numericValue = value; // assure 0..255 for KNX bus } this.log("["+ this.name +"]:Setting "+gaddress+" int to %s (%s)", value, numericValue); this.knxwrite(callback, gaddress,'DPT5',numericValue); @@ -411,27 +412,44 @@ KNXDevice.prototype = { /** bindCharacteristic * initializes callbacks for 'set' events (from HK) and for KNX bus reads (to HK) */ - bindCharacteristic: function(myService, characteristicType, valueType, config) { + bindCharacteristic: function(myService, characteristicType, valueType, config, defaultValue) { var myCharacteristic = myService.getCharacteristic(characteristicType); + var setGA = ""; + var setReverse = false; if (myCharacteristic === undefined) { throw new Error("unknown characteristics cannot be bound"); } + if (defaultValue) { + myCharacteristic.setValue(defaultValue); + } if (config.Set) { // can write + // extract address and Reverse flag + setGA = config.Set.match(/\d*\/\d*\/\d*/); + if (setGA===null) { + this.log(colorOn + "["+ this.name +"]:["+myCharacteristic.displayName+"] Error in group adress: ["+ config.Set +"] "+colorOff); + throw new Error("EINVGROUPADRESS - Invalid group address given"); + } else { + setGA=setGA[0]; // first element of returned array is the group address + } + + setReverse = config.Set.match(/\d*\/\d*\/\d*(R)/) ? true:false; + switch (valueType) { case "Bool": myCharacteristic.on('set', function(value, callback, context) { - this.setBooleanState(value, callback, context, config.Set); - }.bind(this)); - break; - case "BoolReverse": - myCharacteristic.on('set', function(value, callback, context) { - this.setBooleanReverseState(value, callback, context, config.Set); + this.setBooleanState(value, callback, context, setGA, setReverse); //NEW }.bind(this)); break; +// case "BoolReverse": +// this.log("["+ this.name +"]:["+myCharacteristic.displayName+"] \x1b[30;47m%s\x1b[0mWARNING in group adress: "+ config.Set +": Legacy BoolReverse used. Use " + config.Set +"R instead"); +// myCharacteristic.on('set', function(value, callback, context) { +// this.setBooleanReverseState(value, callback, context, config.Set); +// }.bind(this)); +// break; case "Percent": myCharacteristic.on('set', function(value, callback, context) { - this.setPercentage(value, callback, context, config.Set); + this.setPercentage(value, callback, context, setGA, setReverse); myCharacteristic.timeout = Date.now()+milliTimeout; }.bind(this)); break; @@ -451,7 +469,7 @@ KNXDevice.prototype = { }.bind(this)); break; default: { - this.log("[ERROR] unknown type passed"); + this.log(colorOn + "[ERROR] unknown type passed: [" + valueType+"]"+ colorOff); throw new Error("[ERROR] unknown type passed"); } } @@ -463,9 +481,9 @@ KNXDevice.prototype = { case "Bool": this.knxregister_bool([config.Set].concat(config.Listen || []), myCharacteristic); break; - case "BoolReverse": - this.knxregister_boolReverse([config.Set].concat(config.Listen || []), myCharacteristic); - break; +// case "BoolReverse": +// this.knxregister_boolReverse([config.Set].concat(config.Listen || []), myCharacteristic); +// break; case "Percent": this.knxregister_percent([config.Set].concat(config.Listen || []), myCharacteristic); break; @@ -479,8 +497,8 @@ KNXDevice.prototype = { this.knxregister_HVAC([config.Set].concat(config.Listen || []), myCharacteristic); break; default: - this.log("[ERROR] unknown type passed: ["+valueType+"]"); - throw new Error("[ERROR] unknown type passed"); + this.log(colorOn+ "[ERROR] unknown type passed: ["+valueType+"]"+colorOff); + throw new Error("[ERROR] unknown type passed"); } this.log("["+ this.name +"]:["+myCharacteristic.displayName+"]: Issuing read requests on the KNX bus..."); this.knxreadarray([config.Set].concat(config.Listen || [])); @@ -515,8 +533,8 @@ KNXDevice.prototype = { this.log("["+ this.name +"]:ContactSensor ContactSensorState characteristic enabled"); this.bindCharacteristic(myService, Characteristic.ContactSensorState, "Bool", config.ContactSensorState); } else if (config.ContactSensorStateContact1) { - this.log("["+ this.name +"]:ContactSensor ContactSensorStateContact1 characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.ContactSensorState, "BoolReverse", config.ContactSensorStateContact1); + this.log(colorOn+ "[ERROR] outdated type passed: [ContactSensorStateContact1]"+colorOff); + throw new Error("[ERROR] outdated type passed"); } //optionals if (config.StatusActive) { @@ -670,16 +688,16 @@ KNXDevice.prototype = { this.bindCharacteristic(myService, Characteristic.LockCurrentState, "Bool", config.LockCurrentState); } else if (config.LockCurrentStateSecured0) { // for reverse contacts Secured = 0 - this.log("["+ this.name +"]:LockMechanism LockCurrentState characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.LockCurrentState, "BoolReverse", config.LockCurrentStateSecured0); + this.log(colorOn+ "[ERROR] outdated type passed: [LockCurrentStateSecured0]"+colorOff); + throw new Error("[ERROR] outdated type passed"); } // LockTargetState if (config.LockTargetState) { this.log("["+ this.name +"]:LockMechanism LockTargetState characteristic enabled"); this.bindCharacteristic(myService, Characteristic.LockTargetState, "Bool", config.LockTargetState); } else if (config.LockTargetStateSecured0) { - this.log("["+ this.name +"]:LockMechanism LockTargetState characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.LockTargetState, "BoolReverse", config.LockTargetStateSecured0); + this.log(colorOn+ "[ERROR] outdated type passed: [LockTargetStateSecured0]"+colorOff); + throw new Error("[ERROR] outdated type passed"); } //iterate(myService); @@ -887,7 +905,7 @@ KNXDevice.prototype = { } if (config.PositionState) { this.log("["+ this.name +"]:Window PositionState characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.PositionState, "Float", config.PositionState); + this.bindCharacteristic(myService, Characteristic.PositionState, "Int", config.PositionState); } return myService; }, diff --git a/platforms/KNX-sample-config.json b/platforms/KNX-sample-config.json index 05b7e15..eda2fe3 100644 --- a/platforms/KNX-sample-config.json +++ b/platforms/KNX-sample-config.json @@ -61,13 +61,13 @@ "services": [ { "type": "LockMechanism", - "description": "iOS8 Lock mechanism, Supports LockCurrentStateSecured0 OR LockCurrentState, LockTargetStateSecured0 OR LockTargetState, use depending if LOCKED is 0 or 1", + "description": "iOS8 Lock mechanism, Supports LockCurrentState, LockTargetState, append R to the addresses if LOCKED is 1", "name": "Office Window Lock", - "LockCurrentStateSecured0": { - "Listen": "5/3/15" + "LockCurrentState": { + "Listen": "5/3/15R" }, - "LockTargetStateSecured0": { - "Listen": "5/3/15" + "LockTargetState": { + "Listen": "5/3/16R" } } ] diff --git a/platforms/KNX.js b/platforms/KNX.js index 573b3b9..65f7a13 100644 --- a/platforms/KNX.js +++ b/platforms/KNX.js @@ -116,8 +116,8 @@ function groupsocketlisten(opts, callback) { } -var registerSingleGA = function registerSingleGA (groupAddress, callback) { - subscriptions.push({address: groupAddress, callback: callback }); +var registerSingleGA = function registerSingleGA (groupAddress, callback, reverse) { + subscriptions.push({address: groupAddress, callback: callback, reverse:reverse }); } /* @@ -143,7 +143,7 @@ var startMonitor = function startMonitor(opts) { // using { host: name-ip, port if (subscriptions[i].address === dest) { // found one, notify console.log('HIT: Write from '+src+' to '+dest+': '+val+' ['+type+']'); - subscriptions[i].callback(val, src, dest, type); + subscriptions[i].callback(val, src, dest, type, subscriptions[i].reverse); } } }); @@ -156,7 +156,7 @@ var startMonitor = function startMonitor(opts) { // using { host: name-ip, port if (subscriptions[i].address === dest) { // found one, notify // console.log('HIT: Response from '+src+' to '+dest+': '+val+' ['+type+']'); - subscriptions[i].callback(val, src, dest, type); + subscriptions[i].callback(val, src, dest, type, subscriptions[i].reverse); } } @@ -185,13 +185,16 @@ var registerGA = function (groupAddresses, callback) { if (groupAddresses.constructor.toString().indexOf("Array") > -1) { // handle multiple addresses for (var i = 0; i < groupAddresses.length; i++) { - if (groupAddresses[i]) { // do not bind empty addresses - registerSingleGA (groupAddresses[i], callback); + if (groupAddresses[i] && groupAddresses[i].match(/(\d*\/\d*\/\d*)/)) { // do not bind empty addresses or invalid addresses + // clean the addresses + registerSingleGA (groupAddresses[i].match(/(\d*\/\d*\/\d*)/)[0], callback,groupAddresses[i].match(/\d*\/\d*\/\d*(R)/) ? true:false ); } } } else { // it's only one - registerSingleGA (groupAddresses, callback); + if (groupAddresses.match(/(\d*\/\d*\/\d*)/)) { + registerSingleGA (groupAddresses.match(/(\d*\/\d*\/\d*)/)[0], callback, groupAddresses[i].match(/\d*\/\d*\/\d*(R)/) ? true:false); + } } // console.log("listeners now: " + subscriptions.length); }; diff --git a/platforms/KNX.md b/platforms/KNX.md index 0b4d4e9..3e649b9 100644 --- a/platforms/KNX.md +++ b/platforms/KNX.md @@ -97,11 +97,25 @@ So the charcteristic section may look like: ```` +## reversal of values for characteristics +In general, all DPT1 types can be reversed. If you need a 1 for "contact" of a contact senser, you can append an "R" to the group address. +Likewise, all percentages of DPT5 can be reversed, if you need a 100% (=255) for window closed, append an "R" to the group address. Do not forget the listening addresses! + ````json + { + "type": "ContactSensor", + "description": "Sample ContactSensor with 1 as contact (0 is Apple's default)", + "name": "WindowContact1", + "ContactSensorState": { + "Listen": [ + "1/1/100R" + ] + } + } +```` # Supported Services and their characteristics - ## ContactSensor -- ContactSensorState: DPT 1.002, 0 as contact **OR** -- ContactSensorStateContact1: DPT 1.002, 1 as contact +- ContactSensorState: DPT 1.002, 0 as contact +- ~~ContactSensorStateContact1: DPT 1.002, 1 as contact~~ - StatusActive: DPT 1.011, 1 as true - StatusFault: DPT 1.011, 1 as true @@ -142,10 +156,10 @@ So the charcteristic section may look like: - CurrentAmbientLightLevel: DPT 9.004, 0 to 100000 Lux ## 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 +- LockCurrentState: DPT 1, 1 as secured +- ~~LockCurrentStateSecured0: DPT 1, 0 as secured~~ +- LockTargetState: DPT 1, 1 as secured +- ~~LockTargetStateSecured0: DPT 1, 0 as secured~~ *ToDo here: correction of mappings, HomeKit reqires lock states UNSECURED=0, SECURED=1, JAMMED = 2, UNKNOWN=3* @@ -181,7 +195,7 @@ So the charcteristic section may look like: ## WindowCovering - CurrentPosition: DPT5 percentage - TargetPosition: DPT5 percentage -- PositionState: DPT5 value [listen only: 0 Closing, 1 Opening, 2 STopped] +- PositionState: DPT5 value [listen only: 0 Closing, 1 Opening, 2 Stopped] ### not yet supported - HoldPosition From ac62a44ce27ae30468dc65f59e3d0bdb9c3429c8 Mon Sep 17 00:00:00 2001 From: Raoul Date: Sat, 3 Oct 2015 14:48:06 +0200 Subject: [PATCH 047/121] Updates to wrong json Examples in KNX.md doc file Missing quotes around new key names. --- platforms/KNX.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/platforms/KNX.md b/platforms/KNX.md index 3e649b9..ffb7301 100644 --- a/platforms/KNX.md +++ b/platforms/KNX.md @@ -82,16 +82,16 @@ So the charcteristic section may look like: "Listen": [ "1/1/63" ], - minValue: -18, - maxValue: 30 + "minValue": -18, + "maxValue": 30 }, "TargetTemperature": { "Set": "1/1/62", "Listen": [ "1/1/64" ], - minValue: -4, - maxValue: 12 + "minValue": -4, + "maxValue": 12 } } ```` @@ -179,11 +179,11 @@ Likewise, all percentages of DPT5 can be reversed, if you need a 100% (=255) for - On: DPT 1.001, 1 as on, 0 as off ## TemperatureSensor -- CurrentTemperature: DPT9.001 in °C [listen only] +- CurrentTemperature: DPT9.001 in °C [listen only] ## Thermostat -- CurrentTemperature: DPT9.001 in °C [listen only], -40 to 80°C if not overriden as shown above -- TargetTemperature: DPT9.001, values 0..40°C only, all others are ignored +- CurrentTemperature: DPT9.001 in °C [listen only], -40 to 80°C if not overriden as shown above +- 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 From 2652f33a0a2de8566df0d346ec19c2bc1ceaff9d Mon Sep 17 00:00:00 2001 From: Mike Enriquez Date: Sat, 3 Oct 2015 22:04:51 -0400 Subject: [PATCH 048/121] send command every 20s to prevent timing out --- platforms/LogitechHarmony.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/platforms/LogitechHarmony.js b/platforms/LogitechHarmony.js index 845f7f8..c4ee5a0 100644 --- a/platforms/LogitechHarmony.js +++ b/platforms/LogitechHarmony.js @@ -53,6 +53,14 @@ LogitechHarmonyPlatform.prototype = { .then(function (client) { self.log("Connected to Logitech Harmony remote hub"); + // prevent connection from closing + setTimeout(function() { + setInterval(function() { + self.log("Sending command to prevent timeout"); + client.getCurrentActivity(); + }, 20000); + }, 5000); + callback(null, client); }); }; From c24a94c072bedbf035464ba5d1a54c123b4ee456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Mon, 5 Oct 2015 12:00:43 +0200 Subject: [PATCH 049/121] added ROOMMATE devices --- platforms/FHEM.js | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index f76593f..5033dfa 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -567,7 +567,8 @@ console.log( result ); } else if( s.Attributes.model == 'HM-SEC-KEY' ) { accessory = new FHEMAccessory(this.log, this.connection, s); - } else if( s.Internals.TYPE == 'PRESENCE' ) { + } else if( s.Internals.TYPE == 'PRESENCE' + || s.Internals.TYPE == 'ROOMMATE' ) { accessory = new FHEMAccessory(this.log, this.connection, s); } else if( s.Internals.TYPE == 'SONOSPLAYER' ) { @@ -729,30 +730,33 @@ FHEMAccessory(log, connection, s) { this.mappings.blind = { reading: 'pct', cmd: 'pct' }; } else if( genericType == 'window' - || s.Attributes.model == 'HM-SEC-WIN' ) { + || s.Attributes.model == 'HM-SEC-WIN' ) this.mappings.window = { reading: 'level', cmd: 'level' }; - } else if( genericType == 'lock' - || s.Attributes.model == 'HM-SEC-KEY' ) { + else if( genericType == 'lock' + || s.Attributes.model == 'HM-SEC-KEY' ) this.mappings.lock = { reading: 'lock' }; - } else if( genericType == 'thermostat' - || s.Attributes.subType == 'thermostat' ) { + else if( genericType == 'thermostat' + || s.Attributes.subType == 'thermostat' ) s.isThermostat = true; - } else if( s.Internals.TYPE == 'CUL_FHTTK' ) { + else if( s.Internals.TYPE == 'CUL_FHTTK' ) this.mappings.contact = { reading: 'Window' }; - } else if( s.Internals.TYPE == 'MAX' - && s.Internals.type == 'ShutterContact' ) { + else if( s.Internals.TYPE == 'MAX' + && s.Internals.type == 'ShutterContact' ) this.mappings.contact = { reading: 'state' }; - } else if( s.Attributes.subType == 'threeStateSensor' ) { + else if( s.Attributes.subType == 'threeStateSensor' ) this.mappings.contact = { reading: 'contact' }; - } else if( s.Internals.TYPE == 'PRESENCE' ) + else if( s.Internals.TYPE == 'PRESENCE' ) this.mappings.occupancy = { reading: 'state' }; + else if( s.Internals.TYPE == 'ROOMMATE' ) + this.mappings.occupancy = { reading: 'presence' }; + else if( s.Attributes.model == 'fs20di' ) s.isLight = true; @@ -1066,6 +1070,12 @@ FHEMAccessory.prototype = { else value = Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW; + } else if( reading == 'presence' ) { + if( value == 'present' ) + value = Characteristic.OccupancyDetected.OCCUPANCY_DETECTED; + else + value = Characteristic.OccupancyDetected.OCCUPANCY_NOT_DETECTED; + } else if( reading == 'state' ) { if( value.match(/^set-/ ) ) return undefined; From b66610ce935e5d0660ba76e925a8eca282f338eb Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Mon, 5 Oct 2015 17:12:06 -0700 Subject: [PATCH 050/121] Update Lockitron state on successful change --- accessories/Lockitron.js | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/accessories/Lockitron.js b/accessories/Lockitron.js index 04414af..8088d14 100644 --- a/accessories/Lockitron.js +++ b/accessories/Lockitron.js @@ -11,6 +11,17 @@ function LockitronAccessory(log, config) { this.name = config["name"]; this.accessToken = config["api_token"]; this.lockID = config["lock_id"]; + + this.service = new Service.LockMechanism(this.name); + + this.service + .getCharacteristic(Characteristic.LockCurrentState) + .on('get', this.getState.bind(this)); + + this.service + .getCharacteristic(Characteristic.LockTargetState) + .on('get', this.getState.bind(this)) + .on('set', this.setState.bind(this)); } LockitronAccessory.prototype.getState = function(callback) { @@ -36,7 +47,7 @@ LockitronAccessory.prototype.getState = function(callback) { } LockitronAccessory.prototype.setState = function(state, callback) { - var lockitronState = (state == 1) ? "lock" : "unlock"; + var lockitronState = (state == Characteristic.LockTargetState.SECURED) ? "lock" : "unlock"; this.log("Set state to %s", lockitronState); @@ -47,6 +58,14 @@ LockitronAccessory.prototype.setState = function(state, callback) { if (!err && response.statusCode == 200) { this.log("State change complete."); + + // we succeeded, so update the "current" state as well + var currentState = (state == Characteristic.LockTargetState.SECURED) ? + Characteristic.LockCurrentState.SECURED : Characteristic.LockCurrentState.UNSECURED; + + this.service + .setCharacteristic(Characteristic.LockCurrentState, currentState); + callback(null); // success } else { @@ -57,17 +76,5 @@ LockitronAccessory.prototype.setState = function(state, callback) { }, LockitronAccessory.prototype.getServices = function() { - - var service = new Service.LockMechanism(this.name); - - service - .getCharacteristic(Characteristic.LockCurrentState) - .on('get', this.getState.bind(this)); - - service - .getCharacteristic(Characteristic.LockTargetState) - .on('get', this.getState.bind(this)) - .on('set', this.setState.bind(this)); - - return [service]; + return [this.service]; } From feca9fbf0d9418ea742bdf58aa9a71ef1b79776e Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Mon, 5 Oct 2015 17:29:53 -0700 Subject: [PATCH 051/121] Support WeMo "Light" services --- accessories/WeMo.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/accessories/WeMo.js b/accessories/WeMo.js index c19e19f..b97b041 100644 --- a/accessories/WeMo.js +++ b/accessories/WeMo.js @@ -144,6 +144,16 @@ WeMoAccessory.prototype.getServices = function() { return [garageDoorService]; } + else if (this.service == "Light") { + var lightbulbService = new Service.Lightbulb(this.name); + + lightbulbService + .getCharacteristic(Characteristic.On) + .on('get', this.getPowerOn.bind(this)) + .on('set', this.setPowerOn.bind(this)); + + return [lightbulbService]; + } else if (this.service == "MotionSensor") { var motionSensorService = new Service.MotionSensor(this.name); From e6afda55d645caeb960f65e4c094ba0cc4c37a61 Mon Sep 17 00:00:00 2001 From: Snowdd1 Date: Tue, 6 Oct 2015 20:47:07 +0200 Subject: [PATCH 052/121] New UUID generation addresses #203 # This is a breaking change for all configured homekit databases, as it will remove room/area assignments and other things configured in iOS apps ... unless your accessory type was called `Object` before Changes: - Without any changes to the configuration file all accessories UUIDs will be generated by `uuid.generate(accessoryType + ":"` instead of the constructor name which turned out to be unreliable in that context. So about all UUIDs will change. - new parameter uuid_base can be used for accessories in config.json to use a different base for UUID generation than the displayName, which might not be unique - platforms can add the same property to their accessories before returned, just as they have a name property right now. --- app.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app.js b/app.js index a8c2006..126b7e3 100644 --- a/app.js +++ b/app.js @@ -86,7 +86,7 @@ function loadAccessories() { log("Initializing %s accessory...", accessoryType); var accessoryInstance = new accessoryConstructor(log, accessoryConfig); - var accessory = createAccessory(accessoryInstance, accessoryName); + var accessory = createAccessory(accessoryInstance, accessoryName, accessoryType, accessoryConfig.uuid_base); //pass accessoryType for UUID generation, and optional parameter uuid_base which can be used instead of displayName for UUID generation // add it to the bridge bridge.addBridgedAccessory(accessory); @@ -113,11 +113,11 @@ function loadPlatforms() { log("Initializing %s platform...", platformType); var platformInstance = new platformConstructor(log, platformConfig); - loadPlatformAccessories(platformInstance, log); + loadPlatformAccessories(platformInstance, log, platformType); } } -function loadPlatformAccessories(platformInstance, log) { +function loadPlatformAccessories(platformInstance, log, platformType) { asyncCalls++; platformInstance.accessories(once(function(foundAccessories){ asyncCalls--; @@ -129,7 +129,7 @@ function loadPlatformAccessories(platformInstance, log) { log("Initializing platform accessory '%s'...", accessoryName); - var accessory = createAccessory(accessoryInstance, accessoryName); + var accessory = createAccessory(accessoryInstance, accessoryName, platformType, accessoryInstance.uuid_base); // add it to the bridge bridge.addBridgedAccessory(accessory); @@ -141,7 +141,7 @@ function loadPlatformAccessories(platformInstance, log) { })); } -function createAccessory(accessoryInstance, displayName) { +function createAccessory(accessoryInstance, displayName, accessoryType, uuid_base) { var services = accessoryInstance.getServices(); @@ -159,7 +159,7 @@ function createAccessory(accessoryInstance, displayName) { // The returned "services" for this accessory are simply an array of new-API-style // Service instances which we can add to a created HAP-NodeJS Accessory directly. - var accessoryUUID = uuid.generate(accessoryInstance.constructor.name + ":" + displayName); + var accessoryUUID = uuid.generate(accessoryType + ":" + (uuid_base || displayName)); var accessory = new Accessory(displayName, accessoryUUID); From af79ea4fbf5b172cdf0f076307da91e0b37fdd55 Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Wed, 7 Oct 2015 05:38:24 +0200 Subject: [PATCH 053/121] Beginning tag support enhancements Generalized tag recognition, tags are now `Homebridge.*` instead of `Homebridge:*`, initial attempt at `IsPrimary` but probably not working right yet. --- platforms/ZWayServer.js | 49 +++++++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index 730eb9b..f21f39b 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -83,7 +83,20 @@ ZWayServerPlatform.prototype = { return deferred.promise; } , - + getTagValue: function(vdev, tagStem){ + if(!(vdev.tags && vdev.tags.length > 0)) return false; + var tagStem = "Homebridge." + tagStem; + if(vdev.tags.indexOf(tagStem) >= 0) return true; + var tags = vdev.tags, l = tags.length, tag; + for(var i = 0; i < l; i++){ + tag = tags[i]; + if(tag.indexOf(tagStem + ":") === 0){ + return tag.substr(tagStem.length + 1); + } + } + return false; + } + , accessories: function(callback) { debug("Fetching Z-Way devices..."); @@ -110,12 +123,24 @@ ZWayServerPlatform.prototype = { var groupedDevices = {}; for(var i = 0; i < devices.length; i++){ var vdev = devices[i]; - if(vdev.tags.indexOf("Homebridge:Skip") >= 0) { debug("Tag says skip!"); continue; } - if(this.opt_in && vdev.tags.indexOf("Homebridge:Include") < 0) continue; + if(this.getTagValue("Skip")) { debug("Tag says skip!"); continue; } + if(this.opt_in && !this.getTagValue(vdev, "Include")) continue; var gdid = vdev.id.replace(/^(.*?)_zway_(\d+-\d+)-\d.*/, '$1_$2'); var gd = groupedDevices[gdid] || (groupedDevices[gdid] = {devices: [], types: {}, extras: {}, primary: undefined}); gd.devices.push(vdev); var tk = ZWayServerPlatform.getVDevTypeKey(vdev); + + // If this is explicitly set as primary, set it now... + if(this.getTagValue("IsPrimary")){ + gd.primary = gd.devices.length - 1; + if(gd.types[tk] !== undefined){ + // everybody out of the way! + gd.extras[tk] = gd.extras[tk] || []; + gd.extras[tk].push(gd.types[tk]); + } + gd.types[tk] = gd.primary; + } + if(gd.types[tk] === undefined){ gd.types[tk] = gd.devices.length - 1; } else { @@ -136,12 +161,17 @@ ZWayServerPlatform.prototype = { } var accessory = null; - for(var ti = 0; ti < primaryDeviceClasses.length; ti++){ + if(gd.primary !== undefined){ + var pd = gd.devices[gd.primary]; + var name = pd.metrics && pd.metrics.title ? pd.metrics.title : pd.id; + accessory = new ZWayServerAccessory(name, gd, that); + } + else for(var ti = 0; ti < primaryDeviceClasses.length; ti++){ if(gd.types[primaryDeviceClasses[ti]] !== undefined){ gd.primary = gd.types[primaryDeviceClasses[ti]]; var pd = gd.devices[gd.primary]; var name = pd.metrics && pd.metrics.title ? pd.metrics.title : pd.id; - debug("Using primary device with type " + primaryDeviceClasses[ti] + ", " + name + " (" + pd.id + ") as primary."); + //debug("Using primary device with type " + primaryDeviceClasses[ti] + ", " + name + " (" + pd.id + ") as primary."); accessory = new ZWayServerAccessory(name, gd, that); break; } @@ -303,7 +333,11 @@ ZWayServerAccessory.prototype = { services.push(new Service.Switch(vdev.metrics.title, vdev.id)); break; case "switchMultilevel": - services.push(new Service.Lightbulb(vdev.metrics.title, vdev.id)); + if(this.platform.getTagValue(vdev, "ServiceType") === "Switch"){ + services.push(new Service.Switch(vdev.metrics.title, vdev.id)); + } else { + services.push(new Service.Lightbulb(vdev.metrics.title, vdev.id)); + } break; case "sensorBinary.Door/Window": services.push(new Service.GarageDoorOpener(vdev.metrics.title, vdev.id)); @@ -778,7 +812,8 @@ ZWayServerAccessory.prototype = { // Any extra switchMultilevels? Could be a RGBW+W bulb, add them as additional services... if(this.devDesc.extras["switchMultilevel"]) for(var i = 0; i < this.devDesc.extras["switchMultilevel"].length; i++){ var xvdev = this.devDesc.devices[this.devDesc.extras["switchMultilevel"][i]]; - services = services.concat(this.getVDevServices(xvdev)); + var xservice = this.getVDevServices(xvdev); + services = services.concat(xservice); } if(this.platform.splitServices){ From a15c026f2f5503ca80ed47881c5bf102d5bf28b0 Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Wed, 7 Oct 2015 06:59:04 +0200 Subject: [PATCH 054/121] Manual address specification working now --- platforms/YamahaAVR.js | 54 +++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/platforms/YamahaAVR.js b/platforms/YamahaAVR.js index 1659c0f..f08fa96 100644 --- a/platforms/YamahaAVR.js +++ b/platforms/YamahaAVR.js @@ -23,6 +23,7 @@ function YamahaAVRPlatform(log, config){ this.setMainInputTo = config["setMainInputTo"]; this.expectedDevices = config["expected_devices"] || 100; this.discoveryTimeout = config["discovery_timeout"] || 30; + this.manualAddresses = config["manual_addresses"] || {}; this.browser = mdns.createBrowser(mdns.tcp('http'), {resolverSequence: sequence}); } @@ -75,25 +76,44 @@ YamahaAVRPlatform.prototype = { var accessories = []; var timer, timeElapsed = 0, checkCyclePeriod = 5000; - browser.on('serviceUp', function(service){ + // Hmm... seems we need to prevent double-listing via manual and Bonjour... + var sysIds = {}; + + var setupFromService = function(service){ var name = service.name; //console.log('Found HTTP service "' + name + '"'); // We can't tell just from mdns if this is an AVR... if (service.port != 80) return; // yamaha-nodejs assumes this, so finding one on another port wouldn't do any good anyway. var yamaha = new Yamaha(service.host); - yamaha.getSystemConfig().then(function(sysConfig){ - var sysModel = sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0]; - var sysId = sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0]; - that.log("Found Yamaha " + sysModel + " - " + sysId + ", \"" + name + "\""); - var accessory = new YamahaAVRAccessory(that.log, that.config, service, yamaha, sysConfig); - accessories.push(accessory); - if(accessories.length >= this.expectedDevices) - timeoutFunction(); // We're done, call the timeout function now. - //callback([accessory]); - }, function(err){ - return; + yamaha.getSystemConfig().then( + function(sysConfig){ + var sysModel = sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0]; + var sysId = sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0]; + if(sysIds[sysId]){ + this.log("WARN: Got multiple systems with ID " + sysId + "! Omitting duplicate!"); + return; + } + sysIds[sysId] = true; + this.log("Found Yamaha " + sysModel + " - " + sysId + ", \"" + name + "\""); + var accessory = new YamahaAVRAccessory(this.log, this.config, name, yamaha, sysConfig); + accessories.push(accessory); + if(accessories.length >= this.expectedDevices) + timeoutFunction(); // We're done, call the timeout function now. + }.bind(this) + ); + }.bind(this); + + // process manually specified devices... + for(var key in this.manualAddresses){ + if(!this.manualAddresses.hasOwnProperty(key)) continue; + setupFromService({ + name: key, + host: this.manualAddresses[key], + port: 80 }); - }); + } + + browser.on('serviceUp', setupFromService); browser.start(); // The callback can only be called once...so we'll have to find as many as we can @@ -119,15 +139,15 @@ YamahaAVRPlatform.prototype = { } }; -function YamahaAVRAccessory(log, config, mdnsService, yamaha, sysConfig) { +function YamahaAVRAccessory(log, config, name, yamaha, sysConfig) { this.log = log; this.config = config; - this.mdnsService = mdnsService; this.yamaha = yamaha; this.sysConfig = sysConfig; - this.name = mdnsService.name; - this.serviceName = mdnsService.name + " Speakers"; + this.nameSuffix = config["name_suffix"] || " Speakers"; + this.name = name; + this.serviceName = name + this.nameSuffix; this.setMainInputTo = config["setMainInputTo"]; this.playVolume = this.config["play_volume"]; this.minVolume = config["min_volume"] || -50.0; From ed8a3c806231dc51fecbea64812c93a6cc4e42d0 Mon Sep 17 00:00:00 2001 From: Snowdd1 Date: Wed, 7 Oct 2015 10:39:00 +0200 Subject: [PATCH 055/121] uuid_base in constructor --- accessories/knxdevice.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/accessories/knxdevice.js b/accessories/knxdevice.js index 4656522..6aa4ffd 100644 --- a/accessories/knxdevice.js +++ b/accessories/knxdevice.js @@ -17,6 +17,8 @@ New 2015-09-19: New 2015-10-02: - Check for valid group addresses - new "R" flag allowed for Boolean addresses: 1/2/3R is the boolean not(1/2/3), i.e. 0 and 1 switched on read and write +New 2015-10-07: +- Accept uuid_base parameter from config.json to use as unique identifier in UUIDs instead of name (optional) * */ var Service = require("HAP-NodeJS").Service; @@ -38,6 +40,9 @@ function KNXDevice(log, config) { if (config.name) { this.name = config.name; } + if (config.uuid_base) { + this.uuid_base = config.uuid_base; + } if (config.knxd_ip){ this.knxd_ip = config.knxd_ip; } else { From 772f35efac270edde65cb6a4fe8af2cc02573bb2 Mon Sep 17 00:00:00 2001 From: stipus Date: Thu, 8 Oct 2015 09:07:33 +0200 Subject: [PATCH 056/121] Create HomeSeer.js --- platforms/HomeSeer.js | 370 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 370 insertions(+) create mode 100644 platforms/HomeSeer.js diff --git a/platforms/HomeSeer.js b/platforms/HomeSeer.js new file mode 100644 index 0000000..4618efe --- /dev/null +++ b/platforms/HomeSeer.js @@ -0,0 +1,370 @@ +'use strict'; + +// +// HomeSeer Platform Shim for HomeBridge +// V0.1 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/07 - Initial version +// +// +// Remember to add platform to config.json. +// +// You can get HomeSeer Device References by clicking a HomeSeer device name, then +// choosing the Advanced Tab. +// +// Example: +// "platforms": [ +// { +// "platform": "HomeSeer", // required +// "name": "HomeSeer", // required +// "url": "http://192.168.3.4:81", // required +// "accessories":[ +// { +// "ref":8, // required - HomeSeer Device Reference (To get it, select the HS Device - then Advanced Tab) +// "type":"Lightbulb", // Optional - Lightbulb is the default +// "name":"My Light", // Optional - HomeSeer device name is the default +// "offValue":"0", // Optional - 0 is the default +// "onValue":"100", // Optional - 100 is the default +// "can_dim":true // Optional - true is the default - false for a non dimmable lightbulb +// }, +// { +// "ref":9 // This is a dimmable Lightbulb by default +// }, +// { +// "ref":58, // This is an controllable outlet +// "type":"Outlet" +// } +// ] +// } +// ], +// +// +// SUPORTED TYPES: +// - Lightbulb (can_dim, onValue, offValue options) +// - Fan (onValue, offValue options) +// - Switch (onValue, offValue options) +// - Outlet (onValue, offValue options) +// - TemperatureSensor +// - ContactSensor +// - MotionSensor +// - LeakSensor +// - LightSensor +// - OccupancySensor +// - SmokeSensor +// - Door + + +var Service = require("HAP-NodeJS").Service; +var Characteristic = require("HAP-NodeJS").Characteristic; +var request = require("request"); + + +function httpRequest(url, method, callback) { + request({ + url: url, + method: method + }, + function (error, response, body) { + callback(error, response, body) + }) +} + + + +function HomeSeerPlatform(log, config){ + this.log = log; + this.config = config; +} + +HomeSeerPlatform.prototype = { + accessories: function(callback) { + this.log("Fetching HomeSeer devices."); + + var refList = ""; + for( var i=0; i Date: Thu, 8 Oct 2015 18:55:30 +0200 Subject: [PATCH 057/121] added EnOcean blinds --- platforms/FHEM.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index 5033dfa..d591fc2 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -727,7 +727,10 @@ FHEMAccessory(log, connection, s) { else if( genericType == 'blind' || s.Attributes.subType == 'blindActuator' ) { delete this.mappings.pct; - this.mappings.blind = { reading: 'pct', cmd: 'pct' }; + if( s.PossibleSets.match(/[\^ ]position\b/) ) + this.mappings.blind = { reading: 'position', cmd: 'position' }; + else + this.mappings.blind = { reading: 'pct', cmd: 'pct' }; } else if( genericType == 'window' || s.Attributes.model == 'HM-SEC-WIN' ) @@ -917,6 +920,8 @@ FHEMAccessory(log, connection, s) { this.serial = s.Internals.uniqueid; else if( this.type == 'SONOSPLAYER' ) this.serial = s.Internals.UDN; + else if( this.type == 'EnOcean' ) + this.serial = this.type + '.' + s.Internals.DEF; this.uuid_base = this.serial; @@ -967,6 +972,9 @@ FHEMAccessory.prototype = { } else if( reading == 'pct' ) { value = parseInt( value ); + } else if( reading == 'position' ) { + value = parseInt( value ); + } else if(reading == 'motor') { if( value.match(/^up/)) value = Characteristic.PositionState.INCREASING; From d8a21133e9c74bd423c521df9aa7a8b4340206a4 Mon Sep 17 00:00:00 2001 From: Ethan Gill Date: Thu, 8 Oct 2015 13:50:34 -0400 Subject: [PATCH 058/121] Simple fix for undefined Nest device names --- platforms/Nest.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/platforms/Nest.js b/platforms/Nest.js index 414fcef..c1ef3bd 100644 --- a/platforms/Nest.js +++ b/platforms/Nest.js @@ -44,7 +44,11 @@ NestPlatform.prototype = { function NestThermostatAccessory(log, name, device, deviceId) { // device info - this.name = name; + if (name) { + this.name = name; + } else { + this.name = "Nest"; + } this.model = device.model_version; this.serial = device.serial_number; this.deviceId = deviceId; @@ -390,4 +394,4 @@ NestThermostatAccessory.prototype = { } module.exports.accessory = NestThermostatAccessory; -module.exports.platform = NestPlatform; \ No newline at end of file +module.exports.platform = NestPlatform; From c0dfc9a8cdc95badc21c1956feadf1204786c2ad Mon Sep 17 00:00:00 2001 From: Theodor Tonum Date: Sat, 10 Oct 2015 01:14:10 +0200 Subject: [PATCH 059/121] Add support for local Telldus control --- config-sample.json | 4 + package.json | 1 + platforms/Telldus.js | 265 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 270 insertions(+) create mode 100644 platforms/Telldus.js diff --git a/config-sample.json b/config-sample.json index 7af74db..b837f61 100644 --- a/config-sample.json +++ b/config-sample.json @@ -23,6 +23,10 @@ "token" : "telldus token", "token_secret" : "telldus token secret" }, + { + "platform" : "Telldus", + "name" : "Telldus" + }, { "platform": "Wink", "name": "Wink", diff --git a/package.json b/package.json index 3a3ca74..8f88a35 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "tough-cookie": "^2.0.0", "request": "2.49.x", "sonos": "0.8.x", + "telldus": "0.0.9", "telldus-live": "0.2.x", "teslams": "1.0.1", "unofficial-nest-api": "git+https://github.com/hachidorii/unofficial_nodejs_nest.git#d8d48edc952b049ff6320ef99afa7b2f04cdee98", diff --git a/platforms/Telldus.js b/platforms/Telldus.js new file mode 100644 index 0000000..87d37f1 --- /dev/null +++ b/platforms/Telldus.js @@ -0,0 +1,265 @@ +var types = require("HAP-NodeJS/accessories/types.js"); +var telldus = require('telldus'); + +function TelldusPlatform(log, config) { + var that = this; + that.log = log; +} + +TelldusPlatform.prototype = { + + accessories: function(callback) { + var that = this; + + that.log("Fetching devices..."); + + var devices = telldus.getDevicesSync(); + + that.log("Found " + devices.length + " devices..."); + + var foundAccessories = []; + + // Clean non device + for (var i = 0; i < devices.length; i++) { + if (devices[i].type != 'DEVICE') { + devices.splice(i, 1); + } + } + + for (var i = 0; i < devices.length; i++) { + if (devices[i].type === 'DEVICE') { + TelldusAccessory.create(that.log, devices[i], function(err, accessory) { + if (!!err) that.log("Couldn't load device info"); + foundAccessories.push(accessory); + if (foundAccessories.length >= devices.length) { + callback(foundAccessories); + } + }); + } + } + } +}; + +var TelldusAccessory = function TelldusAccessory(log, device) { + + this.log = log; + + var m = device.model.split(':'); + + this.dimTimeout = false; + + // Set accessory info + this.device = device; + this.id = device.id; + this.name = device.name; + this.manufacturer = "Telldus"; // NOTE: Change this later + this.model = device.model; + this.status = device.status; + switch (device.status.name) { + case 'OFF': + this.state = 0; + this.stateValue = 0; + break; + case 'ON': + this.state = 2; + this.stateValue = 1; + break; + case 'DIM': + this.state = 16; + this.stateValue = device.status.level; + break; + } +}; + +TelldusAccessory.create = function (log, device, callback) { + + callback(null, new TelldusAccessory(log, device)); + +}; + +TelldusAccessory.prototype = { + + dimmerValue: function() { + + if (this.state === 1) { + return 100; + } + + if (this.state === 16 && this.stateValue != "unde") { + return parseInt(this.stateValue * 100 / 255); + } + + return 0; + }, + + informationCharacteristics: function() { + var that = this; + + informationCharacteristics = [ + { + 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: that.manufacturer, + supportEvents: false, + supportBonjour: false, + manfDescription: "Manufacturer", + designedMaxLength: 255 + },{ + cType: types.MODEL_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: that.model, + 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: function () { + telldus.turnOff(that.id, function(err){ + if (!!err) that.log("Error: " + err.message); + telldus.turnOn(that.id, function(err){ + if (!!err) that.log("Error: " + err.message); + telldus.turnOff(that.id, function(err){ + if (!!err) that.log("Error: " + err.message); + telldus.turnOn(that.id, function(err){ + if (!!err) that.log("Error: " + err.message); + }); + }); + }); + }); + }, + perms: ["pw"], + format: "bool", + initialValue: false, + supportEvents: false, + supportBonjour: false, + manfDescription: "Identify Accessory", + designedMaxLength: 1 + } + ]; + return informationCharacteristics; + }, + + controlCharacteristics: function() { + var that = this; + + cTypes = [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: that.name, + supportEvents: true, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + }] + + cTypes.push({ + cType: types.POWER_STATE_CTYPE, + onUpdate: function(value) { + if (value) { + telldus.turnOn(that.id, function(err){ + if (!!err) { + that.log("Error: " + err.message) + } else { + that.log(that.name + " - Updated power state: ON"); + } + }); + } else { + telldus.turnOff(that.id, function(err){ + if (!!err) { + that.log("Error: " + err.message) + } else { + that.log(that.name + " - Updated power state: OFF"); + } + }); + } + }, + perms: ["pw","pr","ev"], + format: "bool", + initialValue: (that.state != 2 && (that.state === 16 && that.stateValue != 0)) ? 1 : 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Change the power state", + designedMaxLength: 1 + }) + + if (that.model === "selflearning-dimmer") { + cTypes.push({ + cType: types.BRIGHTNESS_CTYPE, + onUpdate: function (value) { + if (that.dimTimeout) { + clearTimeout(that.dimTimeout); + } + + that.dimTimeout = setTimeout(function(){ + telldus.dim(that.id, (255 * (value / 100)), function(err, result){ + if (!!err) { + that.log("Error: " + err.message); + } else { + that.log(that.name + " - Updated brightness: " + value); + } + }); + that.dimTimeout = false; + }, 250); + }, + perms: ["pw", "pr", "ev"], + format: "int", + initialValue: that.dimmerValue(), + supportEvents: true, + supportBonjour: false, + manfDescription: "Adjust Brightness of Light", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }) + } + + return cTypes + }, + + getServices: function() { + + var services = [ + { + sType: types.ACCESSORY_INFORMATION_STYPE, + characteristics: this.informationCharacteristics() + }, + { + sType: types.LIGHTBULB_STYPE, + characteristics: this.controlCharacteristics() + } + ]; + + return services; + } +}; + +module.exports.platform = TelldusPlatform; +module.exports.accessory = TelldusAccessory; From 4fbd7eb775a1137b8b88dfd674d4c6769c8ad54e Mon Sep 17 00:00:00 2001 From: stipus Date: Sat, 10 Oct 2015 12:38:46 +0200 Subject: [PATCH 060/121] Fix for HomeSeer occupancy sensor --- platforms/HomeSeer.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platforms/HomeSeer.js b/platforms/HomeSeer.js index 4618efe..1ecbd43 100644 --- a/platforms/HomeSeer.js +++ b/platforms/HomeSeer.js @@ -3,6 +3,7 @@ // // HomeSeer Platform Shim for HomeBridge // V0.1 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/07 - Initial version +// V0.2 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/10 - occupancy sensor fix // // // Remember to add platform to config.json. @@ -320,7 +321,7 @@ HomeSeerAccessory.prototype = { } case "OccupancySensor": { var occupancySensorService = new Service.OccupancySensor(); - motionSensorService + occupancySensorService .getCharacteristic(Characteristic.OccupancyDetected) .on('get', this.getPowerState.bind(this)); services.push( occupancySensorService ); From b4f4f58519b0f428218d094fc9591caf727a78cc Mon Sep 17 00:00:00 2001 From: iRaven Date: Sat, 10 Oct 2015 14:05:16 +0200 Subject: [PATCH 061/121] Added Get-State Function --- accessories/HomeMatic.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/accessories/HomeMatic.js b/accessories/HomeMatic.js index ce575b1..89e43b0 100644 --- a/accessories/HomeMatic.js +++ b/accessories/HomeMatic.js @@ -30,7 +30,31 @@ HomeMatic.prototype = { } }); }, + getPowerState: function(callback) { + var that = this; + + this.log("Getting Power State of CCU"); + request.get({ + url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuID, + }, function(err, response, body) { + if (!err && response.statusCode == 200) { + + //that.log("Response:"+response.body); + var responseString = response.body.substring(83,87); + //that.log(responseString); + switch(responseString){ + case "true": {modvalue = "1";break;} + case "fals": {modvalue = "0";break;} + } + callback(parseInt(modvalue)); + that.log("Getting Power State complete."); + } + else { + that.log("Error '"+err+"' getting Power State: " + body); + } + }); + }, getServices: function() { var that = this; return [{ @@ -101,6 +125,7 @@ HomeMatic.prototype = { },{ cType: types.POWER_STATE_CTYPE, onUpdate: function(value) { that.setPowerState(value); }, + onRead: function(callback) { that.getPowerState(callback); }, perms: ["pw","pr","ev"], format: "bool", initialValue: false, From 4a831422eb3c565bb33c965992196f982f88a1fa Mon Sep 17 00:00:00 2001 From: "stevetrease@gmail.com" Date: Sat, 10 Oct 2015 14:41:00 +0100 Subject: [PATCH 062/121] Added two new accessories for a readonly thermometer and hygrometer (humidity meter) based on HttpAccessory. --- accessories/HttpHygrometer.js | 71 ++++++++++++++++++++++++++++++ accessories/HttpThermometer.js | 79 ++++++++++++++++++++++++++++++++++ config-sample.json | 15 ++++++- 3 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 accessories/HttpHygrometer.js create mode 100644 accessories/HttpThermometer.js diff --git a/accessories/HttpHygrometer.js b/accessories/HttpHygrometer.js new file mode 100644 index 0000000..61ad3b9 --- /dev/null +++ b/accessories/HttpHygrometer.js @@ -0,0 +1,71 @@ +var Service = require("HAP-NodeJS").Service; +var Characteristic = require("HAP-NodeJS").Characteristic; +var request = require("request"); + +module.exports = { + accessory: HygrometerAccessory +} + +function HygrometerAccessory(log, config) { + this.log = log; + + // url info + this.url = config["url"]; + this.http_method = config["http_method"]; +} + + +HygrometerAccessory.prototype = { + + httpRequest: function(url, method, callback) { + request({ + url: url, + method: method + }, + function (error, response, body) { + callback(error, response, body) + }) + }, + + + identify: function(callback) { + this.log("Identify requested!"); + callback(); // success + }, + + getCurrentRelativeHumidity: function (callback) { + var that = this; + that.log ("getting CurrentCurrentRelativeHumidity"); + + this.httpRequest(this.url, this.http_method, function(error, response, body) { + if (error) { + this.log('HTTP function failed: %s', error); + callback(error); + } + else { + this.log('HTTP function succeeded - %s', body); + callback(null, Number(body)); + } + }.bind(this)); + }, + + getServices: function() { + + // you can OPTIONALLY create an information service if you wish to override + // the default values for things like serial number, model, etc. + var informationService = new Service.AccessoryInformation(); + + informationService + .setCharacteristic(Characteristic.Manufacturer, "HTTP Manufacturer") + .setCharacteristic(Characteristic.Model, "HTTP Hygrometer") + .setCharacteristic(Characteristic.SerialNumber, "HTTP Serial Number"); + + var humidityService = new Service.HumiditySensor(); + + humidityService + .getCharacteristic(Characteristic.CurrentRelativeHumidity) + .on('get', this.getCurrentRelativeHumidity.bind(this)); + + return [informationService, humidityService]; + } +}; diff --git a/accessories/HttpThermometer.js b/accessories/HttpThermometer.js new file mode 100644 index 0000000..ac9bdc2 --- /dev/null +++ b/accessories/HttpThermometer.js @@ -0,0 +1,79 @@ +var Service = require("HAP-NodeJS").Service; +var Characteristic = require("HAP-NodeJS").Characteristic; +var request = require("request"); + +module.exports = { + accessory: ThermometerAccessory +} + +function ThermometerAccessory(log, config) { + this.log = log; + + // url info + this.url = config["url"]; + this.http_method = config["http_method"]; +} + + +ThermometerAccessory.prototype = { + + httpRequest: function(url, method, callback) { + request({ + url: url, + method: method + }, + function (error, response, body) { + callback(error, response, body) + }) + }, + + + identify: function(callback) { + this.log("Identify requested!"); + callback(); // success + }, + + getCurrentTemperature: function (callback) { + var that = this; + that.log ("getting CurrentTemperature"); + + + this.httpRequest(this.url, this.http_method, function(error, response, body) { + if (error) { + this.log('HTTP function failed: %s', error); + callback(error); + } + else { + this.log('HTTP function succeeded - %s', body); + callback(null, Number(body)); + } + }.bind(this)); + }, + + getTemperatureUnits: function (callback) { + var that = this; + that.log ("getTemperature Units"); + // 1 = F and 0 = C + callback (null, 0); + }, + + getServices: function() { + + // you can OPTIONALLY create an information service if you wish to override + // the default values for things like serial number, model, etc. + var informationService = new Service.AccessoryInformation(); + + informationService + .setCharacteristic(Characteristic.Manufacturer, "HTTP Manufacturer") + .setCharacteristic(Characteristic.Model, "HTTP Thermometer") + .setCharacteristic(Characteristic.SerialNumber, "HTTP Serial Number"); + + var temperatureService = new Service.TemperatureSensor(); + + temperatureService + .getCharacteristic(Characteristic.CurrentTemperature) + .on('get', this.getCurrentTemperature.bind(this)); + + return [informationService, temperatureService]; + } +}; diff --git a/config-sample.json b/config-sample.json index 7af74db..85fd6e3 100644 --- a/config-sample.json +++ b/config-sample.json @@ -164,7 +164,20 @@ "off_url": "https://192.168.1.22:3030/devices/23222/off", "brightness_url": "https://192.168.1.22:3030/devices/23222/brightness/%b", "http_method": "POST" - },{ + }, + { + "accessory": "HttpHygrometer", + "name": "Kitchen", + "url": "http://host/URL", + "http_method": "GET" + }, + { + "accessory": "HttpThermometer", + "name": "Garage", + "url": "http://home/URL", + "http_method": "GET" + }, + { "accessory": "ELKM1", "name": "Security System", "description": "Allows basic control of Elk M1 security system. You can use 1 of 3 arm modes: Away, Stay, Night. If you need to access all 3, create 3 accessories with different names.", From 25299a7863455ed83b653e3a95419e609f48314a Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Sat, 10 Oct 2015 15:45:27 +0200 Subject: [PATCH 063/121] =?UTF-8?q?Last=20bits=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- platforms/ZWayServer.js | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index f21f39b..0b81e88 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -125,20 +125,26 @@ ZWayServerPlatform.prototype = { var vdev = devices[i]; if(this.getTagValue("Skip")) { debug("Tag says skip!"); continue; } if(this.opt_in && !this.getTagValue(vdev, "Include")) continue; - var gdid = vdev.id.replace(/^(.*?)_zway_(\d+-\d+)-\d.*/, '$1_$2'); + + var gdid = this.getTagValue(vdev, "Accessory.Id"); + if(!gdid){ + gdid = vdev.id.replace(/^(.*?)_zway_(\d+-\d+)-\d.*/, '$1_$2'); + } + var gd = groupedDevices[gdid] || (groupedDevices[gdid] = {devices: [], types: {}, extras: {}, primary: undefined}); gd.devices.push(vdev); var tk = ZWayServerPlatform.getVDevTypeKey(vdev); // If this is explicitly set as primary, set it now... - if(this.getTagValue("IsPrimary")){ - gd.primary = gd.devices.length - 1; + if(this.getTagValue(vdev, "IsPrimary")){ + // everybody out of the way! Can't be in "extras" if you're the primary... if(gd.types[tk] !== undefined){ - // everybody out of the way! gd.extras[tk] = gd.extras[tk] || []; gd.extras[tk].push(gd.types[tk]); + delete gd.types[tk]; // clear the way for this one to be set here below... } - gd.types[tk] = gd.primary; + gd.primary = gd.devices.length - 1; + //gd.types[tk] = gd.primary; } if(gd.types[tk] === undefined){ @@ -149,7 +155,7 @@ ZWayServerPlatform.prototype = { } if(tk !== vdev.deviceType) gd.types[vdev.deviceType] = gd.devices.length - 1; // also include the deviceType only as a possibility } - //TODO: Make a second pass, re-splitting any devices that don't make sense together + for(var gdid in groupedDevices) { if(!groupedDevices.hasOwnProperty(gdid)) continue; @@ -183,7 +189,6 @@ ZWayServerPlatform.prototype = { foundAccessories.push(accessory); } -//foundAccessories = foundAccessories.slice(0, 10); // Limit to a few devices for testing... callback(foundAccessories); // Start the polling process... @@ -332,8 +337,9 @@ ZWayServerAccessory.prototype = { case "switchBinary": services.push(new Service.Switch(vdev.metrics.title, vdev.id)); break; + case "switchRGBW": case "switchMultilevel": - if(this.platform.getTagValue(vdev, "ServiceType") === "Switch"){ + if(this.platform.getTagValue(vdev, "Service.Type") === "Switch"){ services.push(new Service.Switch(vdev.metrics.title, vdev.id)); } else { services.push(new Service.Lightbulb(vdev.metrics.title, vdev.id)); @@ -436,6 +442,12 @@ ZWayServerAccessory.prototype = { return cx; } + // We don't want to override "Name"'s name...so we just move this below that block. + var descOverride = this.platform.getTagValue(vdev, "Characteristic.Description"); + if(descOverride){ + cx.displayName = descOverride; + } + if(cx instanceof Characteristic.On){ cx.zway_getValueFromVDev = function(vdev){ var val = false; @@ -797,17 +809,23 @@ ZWayServerAccessory.prototype = { getServices: function() { var that = this; + var vdevPrimary = this.devDesc.devices[this.devDesc.primary]; + var accId = this.platform.getTagValue(vdevPrimary, "Accessory.Id"); + if(!accId){ + accId = "VDev-" + vdevPrimary.h; //FIXME: Is this valid? + } + var informationService = new Service.AccessoryInformation(); informationService .setCharacteristic(Characteristic.Name, this.name) .setCharacteristic(Characteristic.Manufacturer, "Z-Wave.me") .setCharacteristic(Characteristic.Model, "Virtual Device (VDev version 1)") - .setCharacteristic(Characteristic.SerialNumber, "VDev-" + this.devDesc.devices[this.devDesc.primary].h) //FIXME: Is this valid?); + .setCharacteristic(Characteristic.SerialNumber, accId); var services = [informationService]; - services = services.concat(this.getVDevServices(this.devDesc.devices[this.devDesc.primary])); + services = services.concat(this.getVDevServices(vdevPrimary)); // Any extra switchMultilevels? Could be a RGBW+W bulb, add them as additional services... if(this.devDesc.extras["switchMultilevel"]) for(var i = 0; i < this.devDesc.extras["switchMultilevel"].length; i++){ From 05e811cdd66c25c12d60dad03236613750834cce Mon Sep 17 00:00:00 2001 From: iRaven Date: Sat, 10 Oct 2015 15:56:51 +0200 Subject: [PATCH 064/121] Added HM-Sec-RHS Support as contact --- accessories/HomeMaticWindow.js | 123 +++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 accessories/HomeMaticWindow.js diff --git a/accessories/HomeMaticWindow.js b/accessories/HomeMaticWindow.js new file mode 100644 index 0000000..b7e585d --- /dev/null +++ b/accessories/HomeMaticWindow.js @@ -0,0 +1,123 @@ +var types = require("HAP-NodeJS/accessories/types.js"); +var Characteristic = require("HAP-NodeJS").Characteristic; +var request = require("request"); + +function HomeMaticWindow(log, config) { + this.log = log; + this.name = config["name"]; + this.ccuID = config["ccu_id"]; + this.ccuIP = config["ccu_ip"]; +} + +HomeMaticWindow.prototype = { + + + getPowerState: function(callback) { + var that = this; + + this.log("Getting Window State of CCU"); + request.get({ + url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuID, + }, function(err, response, body) { + + if (!err && response.statusCode == 200) { + + //that.log("Response:"+response.body); + var responseString = response.body.substring(83,84); + //that.log(responseString); + switch(responseString){ + case "0": {callback(Characteristic.ContactSensorState.CONTACT_DETECTED);break;} + case "1": {callback(Characteristic.ContactSensorState.CONTACT_NOT_DETECTED);break;} + case "2": {callback(Characteristic.ContactSensorState.CONTACT_NOT_DETECTED);break;} + } + that.log("Getting Window State complete."); + } + else { + that.log("Error '"+err+"' getting Window State: " + body); + } + }); + }, + + getServices: function() { + var that = this; + return [{ + sType: types.ACCESSORY_INFORMATION_STYPE, + characteristics: [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of the accessory", + designedMaxLength: 255 + },{ + cType: types.MANUFACTURER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: "Homematic", + supportEvents: false, + supportBonjour: false, + manfDescription: "Manufacturer", + designedMaxLength: 255 + },{ + cType: types.MODEL_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: "HM-Sec-RHS", + 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 + }] + },{ + sType: types.CONTACT_SENSOR_STYPE, + characteristics: [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + },{ + cType: types.CONTACT_SENSOR_STATE_CTYPE, + onRead: function(callback) { that.getPowerState(callback); }, + perms: ["pr","ev"], + format: "bool", + initialValue: false, + supportEvents: false, + supportBonjour: false, + manfDescription: "Get Window state of a Variable", + designedMaxLength: 1 + }] + }]; + } +}; + +module.exports.accessory = HomeMaticWindow; From a274ae4edab182a22e08b1bab875a1a15fe37646 Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Sun, 11 Oct 2015 05:44:44 +0200 Subject: [PATCH 065/121] Switches to the *new* new Characteristics API format for the two custom Characteristics. Fixes #247 --- platforms/YamahaAVR.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/platforms/YamahaAVR.js b/platforms/YamahaAVR.js index f08fa96..0dde24f 100644 --- a/platforms/YamahaAVR.js +++ b/platforms/YamahaAVR.js @@ -31,24 +31,24 @@ function YamahaAVRPlatform(log, config){ YamahaAVRPlatform.AudioVolume = function() { Characteristic.call(this, 'Audio Volume', '00001001-0000-1000-8000-135D67EC4377'); - this.format = 'uint8'; - this.unit = 'percentage'; - this.maximumValue = 100; - this.minimumValue = 0; - this.stepValue = 1; - this.readable = true; - this.writable = true; - this.supportsEventNotification = true; + this.setProps({ + format: Characteristic.Formats.UINT8, + unit: Characteristic.Units.PERCENTAGE, + maxValue: 100, + minValue: 0, + minStep: 1, + perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] + }); this.value = this.getDefaultValue(); }; inherits(YamahaAVRPlatform.AudioVolume, Characteristic); YamahaAVRPlatform.Muting = function() { Characteristic.call(this, 'Muting', '00001002-0000-1000-8000-135D67EC4377'); - this.format = 'bool'; - this.readable = true; - this.writable = true; - this.supportsEventNotification = true; + this.setProps({ + format: Characteristic.Formats.UINT8, + perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] + }); this.value = this.getDefaultValue(); }; inherits(YamahaAVRPlatform.Muting, Characteristic); From e4fa276de2b29eec9a8ad640653f43781f15051e Mon Sep 17 00:00:00 2001 From: rodtoll Date: Sun, 11 Oct 2015 08:14:48 -0700 Subject: [PATCH 066/121] Adding isy-js based ISY platform support --- package.json | 1 + platforms/isy-js.js | 674 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 675 insertions(+) create mode 100644 platforms/isy-js.js diff --git a/package.json b/package.json index 3a3ca74..a55f737 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "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", + "isy-js": "", "lifx-api": "^1.0.1", "lifx": "git+https://github.com/magicmonkey/lifxjs.git", "mdns": "^2.2.4", diff --git a/platforms/isy-js.js b/platforms/isy-js.js new file mode 100644 index 0000000..37d515b --- /dev/null +++ b/platforms/isy-js.js @@ -0,0 +1,674 @@ +var types = require("HAP-NodeJS/accessories/types.js"); +var isy = require('isy-js'); +var Service = require("HAP-NodeJS").Service; +var Characteristic = require("HAP-NodeJS").Characteristic; +var inherits = require('util').inherits; + +var deviceMap = {}; + +function ISYChangeHandler(isy,device) { + var deviceToUpdate = deviceMap[device.address]; + if(deviceToUpdate != null) { + deviceToUpdate.handleExternalChange(); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////// +// PLATFORM + +function ISYPlatform(log,config) { + this.log = log; + this.config = config; + this.host = config.host; + this.username = config.username; + this.password = config.password; + this.elkEnabled = config.elkEnabled; + this.isy = new isy.ISY(this.host, this.username,this.password, config.elkEnabled, ISYChangeHandler); +} + +ISYPlatform.prototype.shouldIgnore = function(device) { + var deviceAddress = device.address; + var deviceName = device.name; + for(var index = 0; index < this.config.ignoreDevices.length; index++) { + var rule = this.config.ignoreDevices[index]; + if(rule.nameContains != "") { + if(deviceName.indexOf(rule.nameContains) == -1) { + continue; + } + } + if(rule.lastAddressDigit != "") { + if(deviceAddress.indexOf(rule.lastAddressDigit,deviceAddress.length-2) == -1) { + continue; + } + } + if(rule.address != "") { + if(deviceAddress != rule.address) { + continue; + } + } + console.log("@@@@@@ Ignoring device: "+deviceName+" ["+deviceAddress+"] because of rule ["+rule.nameContains+"] ["+rule.lastAddressDigit+"] ["+rule.address+"]"); + return true; + + } + return false; +} + +ISYPlatform.prototype.accessories = function(callback) { + var that = this; + this.isy.initialize(function() { + var results = []; + var deviceList = that.isy.getDeviceList(); + for(var index = 0; index < deviceList.length; index++) { + var device = deviceList[index]; + var homeKitDevice = null; + if(!that.shouldIgnore(device)) { + + if(device.deviceType == that.isy.DEVICE_TYPE_LIGHT || device.deviceType == that.isy.DEVICE_TYPE_DIMMABLE_LIGHT) { + homeKitDevice = new ISYLightAccessory(that.log,device); + } else if(device.deviceType == that.isy.DEVICE_TYPE_LOCK || device.deviceType == that.isy.DEVICE_TYPE_SECURE_LOCK) { + homeKitDevice = new ISYLockAccessory(that.log,device); + } else if(device.deviceType == that.isy.DEVICE_TYPE_OUTLET) { + homeKitDevice = new ISYOutletAccessory(that.log,device); + } else if(device.deviceType == that.isy.DEVICE_TYPE_FAN) { + homeKitDevice = new ISYFanAccessory(that.log,device); + } else if(device.deviceType == that.isy.DEVICE_TYPE_DOOR_WINDOW_SENSOR) { + homeKitDevice = new ISYDoorWindowSensorAccessory(that.log,device); + } else if(device.deviceType == that.isy.DEVICE_TYPE_ALARM_DOOR_WINDOW_SENSOR) { + homeKitDevice = new ISYDoorWindowSensorAccessory(that.log,device); + } else if(device.deviceType == that.isy.DEVICE_TYPE_ALARM_PANEL) { + homeKitDevice = new ISYElkAlarmPanelAccessory(that.log,device); + } + if(homeKitDevice != null) { + deviceMap[device.address] = homeKitDevice; + results.push(homeKitDevice); + } + } + } + if(that.isy.elkEnabled) { + var panelDevice = that.isy.getElkAlarmPanel(); + var panelDeviceHK = new ISYElkAlarmPanelAccessory(that.log,panelDevice); + deviceMap[panelDevice.address] = panelDeviceHK; + results.push(panelDeviceHK); + } + console.log("Filtered device has: "+results.length+" devices"); + callback(results); + }); +} + +///////////////////////////////////////////////////////////////////////////////////////////////// +// FANS + +function ISYFanAccessory(log,device) { + this.log = log; + this.device = device; + this.address = device.address; + this.name = device.name; +} + +ISYFanAccessory.prototype.identify = function(callback) { + // Do the identify action + callback(); +} + +ISYFanAccessory.prototype.translateFanSpeedToHK = function(fanSpeed) { + if(fanSpeed == "Off") { + return 0; + } else if(fanSpeed == "Low") { + return 32; + } else if(fanSpeed == "Medium") { + return 67; + } else if(fanSpeed == "High") { + return 100; + } else { + this.log("!!!! ERROR: Unknown fan speed: "+fanSpeed); + return 0; + } +} + +ISYFanAccessory.prototype.translateHKToFanSpeed = function(fanStateHK) { + if(fanStateHK == 0) { + return "Off"; + } else if(fanStateHK > 0 && fanStateHK <=32) { + return "Low"; + } else if(fanStateHK > 33 && fanStateHK <= 67) { + return "Medium"; + } else if(fanStateHK > 67) { + return "High"; + } else { + this.log("!!!!! ERROR: Unknown fan state!"); + return "Off"; + } +} + +ISYFanAccessory.prototype.getFanRotationSpeed = function(callback) { + callback(null,this.translateFanSpeedToHK(this.device.getCurrentFanState())); +} + +ISYFanAccessory.prototype.setFanRotationSpeed = function(fanStateHK,callback) { + var newFanState = this.translateHKToFanSpeed(fanStateHK); + this.log("Sending command to set fan state to: "+newFanState); + if(newFanState != this.device.getCurrentFanState()) { + this.device.sendFanCommand(newFanState, function(result) { + callback(); + }); + } else { + this.log("Fan command does not change actual speed"); + callback(); + } +} + + +ISYFanAccessory.prototype.getIsFanOn = function() { + return (this.device.getCurrentFanState() != "Off"); +} + +ISYFanAccessory.prototype.getFanOnState = function(callback) { + callback(null,this.getIsFanOn()); +} + +ISYFanAccessory.prototype.setFanOnState = function(onState,callback) { + if(onState != this.getIsFanOn()) { + if(onState) { + this.setFanRotationSpeed(this.translateFanSpeedToHK("Medium"), callback); + } else { + this.setFanRotationSpeed(this.translateFanSpeedToHK("Off"), callback); + } + } else { + this.log("Fan command does not change actual state"); + callback(); + } +} + + +ISYFanAccessory.prototype.handleExternalChange = function() { + this.fanService + .setCharacteristic(Characteristic.On, this.getIsFanOn()); + + this.fanService + .setCharacteristic(Characteristic.RotationSpeed, this.translateFanSpeedToHK(this.device.getCurrentFanState())); +} + +ISYFanAccessory.prototype.getServices = function() { + var informationService = new Service.AccessoryInformation(); + + informationService + .setCharacteristic(Characteristic.Manufacturer, "SmartHome") + .setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName) + .setCharacteristic(Characteristic.SerialNumber, this.device.address); + + var fanService = new Service.Fan(); + + this.fanService = fanService; + this.informationService = informationService; + + fanService + .getCharacteristic(Characteristic.On) + .on('set', this.setFanOnState.bind(this)); + + fanService + .getCharacteristic(Characteristic.On) + .on('get', this.getFanOnState.bind(this)); + + fanService + .addCharacteristic(new Characteristic.RotationSpeed()) + .on('get', this.getFanRotationSpeed.bind(this)); + + fanService + .getCharacteristic(Characteristic.RotationSpeed) + .on('set', this.setFanRotationSpeed.bind(this)); + + return [informationService, fanService]; +} + +///////////////////////////////////////////////////////////////////////////////////////////////// +// OUTLETS + +function ISYOutletAccessory(log,device) { + this.log = log; + this.device = device; + this.address = device.address; + this.name = device.name; +} + +ISYOutletAccessory.prototype.identify = function(callback) { + // Do the identify action + callback(); +} + +ISYOutletAccessory.prototype.setOutletState = function(outletState,callback) { + this.log("Sending command to set outlet state to: "+outletState); + if(outletState != this.device.getCurrentOutletState()) { + this.device.sendOutletCommand(outletState, function(result) { + callback(); + }); + } else { + callback(); + } +} + +ISYOutletAccessory.prototype.getOutletState = function(callback) { + callback(null,this.device.getCurrentOutletState()); +} + +ISYOutletAccessory.prototype.getOutletInUseState = function(callback) { + callback(null, true); +} + +ISYOutletAccessory.prototype.handleExternalChange = function() { + this.outletService + .setCharacteristic(Characteristic.On, this.device.getCurrentOutletState()); +} + +ISYOutletAccessory.prototype.getServices = function() { + var informationService = new Service.AccessoryInformation(); + + informationService + .setCharacteristic(Characteristic.Manufacturer, "SmartHome") + .setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName) + .setCharacteristic(Characteristic.SerialNumber, this.device.address); + + var outletService = new Service.Outlet(); + + this.outletService = outletService; + this.informationService = informationService; + + outletService + .getCharacteristic(Characteristic.On) + .on('set', this.setOutletState.bind(this)); + + outletService + .getCharacteristic(Characteristic.On) + .on('get', this.getOutletState.bind(this)); + + outletService + .getCharacteristic(Characteristic.OutletInUse) + .on('get', this.getOutletInUseState.bind(this)); + + return [informationService, outletService]; +} + +///////////////////////////////////////////////////////////////////////////////////////////////// +// LOCKS + +function ISYLockAccessory(log,device) { + this.log = log; + this.device = device; + this.address = device.address; + this.name = device.name; +} + +ISYLockAccessory.prototype.identify = function(callback) { + callback(); +} + +ISYLockAccessory.prototype.setTargetLockState = function(lockState,callback) { + this.log("Sending command to set lock state to: "+lockState); + if(lockState != this.getDeviceCurrentStateAsHK()) { + var targetLockValue = (lockState == 0) ? false : true; + this.device.sendLockCommand(targetLockValue, function(result) { + callback(); + }); + } else { + callback(); + } +} + +ISYLockAccessory.prototype.getDeviceCurrentStateAsHK = function() { + return (this.device.getCurrentLockState() ? 1 : 0); +} + +ISYLockAccessory.prototype.getLockCurrentState = function(callback) { + callback(null, this.getDeviceCurrentStateAsHK()); +} + +ISYLockAccessory.prototype.getTargetLockState = function(callback) { + this.getLockCurrentState(callback); +} + +ISYLockAccessory.prototype.handleExternalChange = function() { + this.lockService + .setCharacteristic(Characteristic.LockTargetState, this.getDeviceCurrentStateAsHK()); + this.lockService + .setCharacteristic(Characteristic.LockCurrentState, this.getDeviceCurrentStateAsHK()); +} + +ISYLockAccessory.prototype.getServices = function() { + var informationService = new Service.AccessoryInformation(); + + informationService + .setCharacteristic(Characteristic.Manufacturer, "SmartHome") + .setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName) + .setCharacteristic(Characteristic.SerialNumber, this.device.address); + + var lockMechanismService = new Service.LockMechanism(); + + this.lockService = lockMechanismService; + this.informationService = informationService; + + lockMechanismService + .getCharacteristic(Characteristic.LockTargetState) + .on('set', this.setTargetLockState.bind(this)); + + lockMechanismService + .getCharacteristic(Characteristic.LockTargetState) + .on('get', this.getTargetLockState.bind(this)); + + lockMechanismService + .getCharacteristic(Characteristic.LockCurrentState) + .on('get', this.getLockCurrentState.bind(this)); + + return [informationService, lockMechanismService]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////// +// LIGHTS + +function ISYLightAccessory(log,device) { + this.log = log; + this.device = device; + this.address = device.address; + this.name = device.name; + this.dimmable = (this.device.deviceType == "DimmableLight"); +} + +ISYLightAccessory.prototype.identify = function(callback) { + this.device.sendLightCommand(true, function(result) { + this.device.sendLightCommand(false, function(result) { + callback(); + }); + }); +} + +ISYLightAccessory.prototype.setPowerState = function(powerOn,callback) { + this.log("=== Setting powerstate to %s", powerOn); + if(powerOn != this.device.getCurrentLightState()) { + this.log("+++ Changing powerstate to "+powerOn); + this.device.sendLightCommand(powerOn, function(result) { + callback(); + }); + } else { + this.log("--- Ignoring redundant setPowerState"); + callback(); + } +} + +ISYLightAccessory.prototype.handleExternalChange = function() { + this.log("=== Handling external change for light"); + this.lightService + .setCharacteristic(Characteristic.On, this.device.getCurrentLightState()); + if(this.device.deviceType == this.device.isy.DEVICE_TYPE_DIMMABLE_LIGHT) { + this.lightService + .setCharacteristic(Characteristic.Brightness, this.device.getCurrentLightDimState() ); + } +} + +ISYLightAccessory.prototype.getPowerState = function(callback) { + callback(null,this.device.getCurrentLightState()); +} + +ISYLightAccessory.prototype.setBrightness = function(level,callback) { + this.log("Setting brightness to %s", level); + if(level != this.device.getCurrentLightDimState()) { + this.log("+++ Changing Brightness to "+level); + this.device.sendLightDimCommand(level, function(result) { + callback(); + }); + } else { + this.log("--- Ignoring redundant setBrightness"); + callback(); + } +} + +ISYLightAccessory.prototype.getBrightness = function(callback) { + callback(null,this.device.getCurrentLightDimState()); +} + +ISYLightAccessory.prototype.getServices = function() { + var informationService = new Service.AccessoryInformation(); + + informationService + .setCharacteristic(Characteristic.Manufacturer, "SmartHome") + .setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName) + .setCharacteristic(Characteristic.SerialNumber, this.device.address); + + var lightBulbService = new Service.Lightbulb(); + + this.informationService = informationService; + this.lightService = lightBulbService; + + lightBulbService + .getCharacteristic(Characteristic.On) + .on('set', this.setPowerState.bind(this)); + + lightBulbService + .getCharacteristic(Characteristic.On) + .on('get', this.getPowerState.bind(this)); + + if(this.dimmable) { + lightBulbService + .addCharacteristic(new Characteristic.Brightness()) + .on('get', this.getBrightness.bind(this)); + + lightBulbService + .getCharacteristic(Characteristic.Brightness) + .on('set', this.setBrightness.bind(this)); + } + + return [informationService, lightBulbService]; +} + +///////////////////////////////////////////////////////////////////////////////////////////////// +// CONTACT SENSOR + +function ISYDoorWindowSensorAccessory(log,device) { + this.log = log; + this.device = device; + this.address = device.address; + this.name = device.name; + this.doorWindowState = false; +} + +ISYDoorWindowSensorAccessory.prototype.identify = function(callback) { + // Do the identify action + callback(); +} + +ISYDoorWindowSensorAccessory.prototype.translateCurrentDoorWindowState = function() { + return (this.device.getCurrentDoorWindowState()) ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED; +} + +ISYDoorWindowSensorAccessory.prototype.getCurrentDoorWindowState = function(callback) { + callback(null,this.translateCurrentDoorWindowState()); +} + +ISYDoorWindowSensorAccessory.prototype.handleExternalChange = function() { + this.sensorService + .setCharacteristic(Characteristic.ContactSensorState, this.translateCurrentDoorWindowState()); +} + +ISYDoorWindowSensorAccessory.prototype.getServices = function() { + var informationService = new Service.AccessoryInformation(); + + informationService + .setCharacteristic(Characteristic.Manufacturer, "SmartHome") + .setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName) + .setCharacteristic(Characteristic.SerialNumber, this.device.address); + + var sensorService = new Service.ContactSensor(); + + this.sensorService = sensorService; + this.informationService = informationService; + + sensorService + .getCharacteristic(Characteristic.ContactSensorState) + .on('get', this.getCurrentDoorWindowState.bind(this)); + + return [informationService, sensorService]; +} + +///////////////////////////////////////////////////////////////////////////////////////////////// +// ELK SENSOR PANEL + +function ISYElkAlarmPanelAccessory(log,device) { + this.log = log; + this.device = device; + this.address = device.address; + this.name = device.name; +} + +ISYElkAlarmPanelAccessory.prototype.identify = function(callback) { + callback(); +} + +ISYElkAlarmPanelAccessory.prototype.setAlarmTargetState = function(targetStateHK,callback) { + this.log("Sending command to set alarm panel state to: "+targetStateHK); + var targetState = this.translateHKToAlarmTargetState(targetState); + if(this.alarmTargetState != targetState) { + this.device.sendSetAlarmModeCommand(targetState, function(result) { + callback(); + }); + } else { + callback(); + } +} + +////// Current State + +/* +ELKAlarmPanelDevice.prototype.ALARM_STATE_NOT_READY_TO_ARM = 0; +ELKAlarmPanelDevice.prototype.ALARM_STATE_READY_TO_ARM = 1; +ELKAlarmPanelDevice.prototype.ALARM_STATE_READY_TO_ARM_VIOLATION = 2; +ELKAlarmPanelDevice.prototype.ALARM_STATE_ARMED_WITH_TIMER = 3; +ELKAlarmPanelDevice.prototype.ALARM_STATE_ARMED_FULLY = 4; +ELKAlarmPanelDevice.prototype.ALARM_STATE_FORCE_ARMED_VIOLATION = 5; +ELKAlarmPanelDevice.prototype.ALARM_STATE_ARMED_WITH_BYPASS = 6; +*/ + +/* +ELKAlarmPanelDevice.prototype.ALARM_TRIP_STATE_DISARMED = 0; +ELKAlarmPanelDevice.prototype.ALARM_TRIP_STATE_EXIT_DELAY = 1; +ELKAlarmPanelDevice.prototype.ALARM_TRIP_STATE_TRIPPED = 2; +*/ + +/* +Characteristic.SecuritySystemCurrentState.STAY_ARM = 0; +Characteristic.SecuritySystemCurrentState.AWAY_ARM = 1; +Characteristic.SecuritySystemCurrentState.NIGHT_ARM = 2; +Characteristic.SecuritySystemCurrentState.DISARMED = 3; +Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED = 4; +*/ + +ISYElkAlarmPanelAccessory.prototype.translateAlarmCurrentStateToHK = function() { + var tripState = this.device.getAlarmTripState(); + if(tripState == this.device.ALARM_TRIP_STATE_DISARMED || tripState == this.device.ALARM_TRIP_STATE_EXIT_DELAY) { + return Characteristic.SecuritySystemCurrentState.DISARMED; + } else if(tripState ==this.device.ALARM_TRIP_STATE_TRIPPED) { + return Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED; + } else { + var sourceAlarmState = this.device.getAlarmMode(); + if(sourceAlarmState == this.device.ALARM_MODE_STAY || sourceAlarmState == this.device.ALARM_MODE_STAY_INSTANT ) { + return Characteristic.SecuritySystemCurrentState.STAY_ARM; + } else if(sourceAlarmState == this.device.ALARM_MODE_AWAY || sourceAlarmState == this.device.ALARM_MODE_VACATION) { + return Characteristic.SecuritySystemCurrentState.AWAY_ARM; + } else if(sourceAlarmState == this.device.ALARM_MODE_NIGHT || sourceAlarmState == this.device.ALARM_MODE_NIGHT_INSTANT) { + return Characteristic.SecuritySystemCurrentState.NIGHT_ARM; + } else { + return Characteristic.SecuritySystemCurrentState.DISARM; + } + } +} + +////// Target Mode + +/* +ELKAlarmPanelDevice.prototype.ALARM_MODE_DISARMED = 0; +ELKAlarmPanelDevice.prototype.ALARM_MODE_AWAY = 1; +ELKAlarmPanelDevice.prototype.ALARM_MODE_STAY = 2; +ELKAlarmPanelDevice.prototype.ALARM_MODE_STAY_INSTANT = 3; +ELKAlarmPanelDevice.prototype.ALARM_MODE_NIGHT = 4; +ELKAlarmPanelDevice.prototype.ALARM_MODE_NIGHT_INSTANT = 5; +ELKAlarmPanelDevice.prototype.ALARM_MODE_VACATION = 6; +*/ + +/* +Characteristic.SecuritySystemTargetState.STAY_ARM = 0; +Characteristic.SecuritySystemTargetState.AWAY_ARM = 1; +Characteristic.SecuritySystemTargetState.NIGHT_ARM = 2; +Characteristic.SecuritySystemTargetState.DISARM = 3; +*/ + + +ISYElkAlarmPanelAccessory.prototype.translateAlarmTargetStateToHK = function() { + var sourceAlarmState = this.device.getAlarmMode(); + if(sourceAlarmState == this.device.ALARM_MODE_STAY || sourceAlarmState == this.device.ALARM_MODE_STAY_INSTANT ) { + return Characteristic.SecuritySystemTargetState.STAY_ARM; + } else if(sourceAlarmState == this.device.ALARM_MODE_AWAY || sourceAlarmState == this.device.ALARM_MODE_VACATION) { + return Characteristic.SecuritySystemTargetState.AWAY_ARM; + } else if(sourceAlarmState == this.device.ALARM_MODE_NIGHT || sourceAlarmState == this.device.ALARM_MODE_NIGHT_INSTANT) { + return Characteristic.SecuritySystemTargetState.NIGHT_ARM; + } else { + return Characteristic.SecuritySystemTargetState.DISARM; + } +} + +ISYElkAlarmPanelAccessory.prototype.translateHKToAlarmTargetState = function(state) { + if(state == Characteristic.SecuritySystemTargetState.STAY_ARM) { + return this.device.ALARM_MODE_STAY; + } else if(state == Characteristic.SecuritySystemTargetState.AWAY_ARM) { + return this.device.ALARM_MODE_AWAY; + } else if(state == Characteristic.SecuritySystemTargetState.NIGHT_ARM) { + return this.device.NIGHT_ARM; + } else { + return this.device.DISARM; + } +} + +ISYElkAlarmPanelAccessory.prototype.getAlarmTargetState = function(callback) { + callback(null,this.translateAlarmTargetStateToHK()); +} + +ISYElkAlarmPanelAccessory.prototype.getAlarmCurrentState = function(callback) { + callback(null,this.translateAlarmCurrentStateToHK()); +} + +ISYElkAlarmPanelAccessory.prototype.handleExternalChange = function() { + this.alarmPanelService + .setCharacteristic(Characteristic.SecuritySystemTargetState, this.translateAlarmTargetStateToHK()); + this.alarmPanelService + .setCharacteristic(Characteristic.SecuritySystemCurrentState, this.translateAlarmCurrentStateToHK()); +} + +ISYElkAlarmPanelAccessory.prototype.getServices = function() { + var informationService = new Service.AccessoryInformation(); + + informationService + .setCharacteristic(Characteristic.Manufacturer, "SmartHome") + .setCharacteristic(Characteristic.Model, this.device.deviceFriendlyName) + .setCharacteristic(Characteristic.SerialNumber, this.device.address); + + var alarmPanelService = new Service.SecuritySystem(); + + this.alarmPanelService = alarmPanelService; + this.informationService = informationService; + + alarmPanelService + .getCharacteristic(Characteristic.SecuritySystemTargetState) + .on('set', this.setAlarmTargetState.bind(this)); + + alarmPanelService + .getCharacteristic(Characteristic.SecuritySystemTargetState) + .on('get', this.getAlarmTargetState.bind(this)); + + alarmPanelService + .getCharacteristic(Characteristic.SecuritySystemCurrentState) + .on('get', this.getAlarmCurrentState.bind(this)); + + return [informationService, alarmPanelService]; +} + +module.exports.platform = ISYPlatform; +module.exports.accessory = ISYFanAccessory; +module.exports.accessory = ISYLightAccessory; +module.exports.accessory = ISYLockAccessory; +module.exports.accessory = ISYOutletAccessory; +module.exports.accessory = ISYDoorWindowSensorAccessory; From 40a96808b2e69137e6f2d690f213fbb7751b8fd5 Mon Sep 17 00:00:00 2001 From: rodtoll Date: Sun, 11 Oct 2015 08:45:18 -0700 Subject: [PATCH 067/121] Added docs to the isy-js file. --- platforms/isy-js.js | 52 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/platforms/isy-js.js b/platforms/isy-js.js index 37d515b..f28c0c0 100644 --- a/platforms/isy-js.js +++ b/platforms/isy-js.js @@ -1,9 +1,61 @@ +/* + ISY-JS + + ISY-99 REST / WebSockets based HomeBridge shim. + + Supports the following Insteon devices: Lights (dimmable and non-dimmable), Fans, Outlets, Door/Window Sensors, MorningLinc locks, Inline Lincs and I/O Lincs. + Also supports ZWave based locks. If elkEnabled is set to true then this will also expose your Elk Alarm Panel and all of your Elk Sensors. + + Turns out that HomeBridge platforms can only return a maximum of 100 devices. So if you end up exposing more then 100 devices through HomeBridge the HomeKit + software will fail adding the HomeBridge to your HomeKit network. To address this issue this platform provides an option to screen out devices based on + criteria specified in the config. + + Configuration sample: + + "platforms": [ + { + "platform": "isy-js", + "name": "isy-js", + "host": "10.0.1.12", + "username": "admin", + "password": "password", + "elkEnabled": true, + "ignoreDevices": [ + { "nameContains": "ApplianceLinc", "lastAddressDigit": "", "address": ""}, + { "nameContains": "Bedroom.Side Gate", "lastAddressDigit": "", "address": ""}, + { "nameContains": "Remote", "lastAddressDigit": "", "address": "" }, + { "nameContains": "Keypad", "lastAddressDigit": "2", "address": "" }, + ] + } + ] + + Fields: + "platform" - Must be set to isy-js + "name" - Can be set to whatever you want + "host" - IP address of the ISY + "username" - Your ISY username + "password" - Your ISY password + "elkEnabled" - true if there is an elk alarm panel connected to your ISY + "ignoreDevices" - Array of objects specifying criteria for screening out devices from the network. nameContains is the only required criteria. If the other criteria + are blank all devices will match those criteria (providing they match the name criteria). + "nameContains" - Specifies a substring to check against the names of the ISY devices. Required field for the criteria. + "lastAddressDigit" - Specifies a single digit in the ISY address of a device which should be used to match the device. Example use of this is for composite + devices like keypads so you can screen out the non-main buttons. + "address" - ISY address to match. + + Examples: + + { "nameContains": "Keypad", "lastAddressDigit": "2", "address": "" } - Ignore all devices which have the word Keypad in their name and whose last address digit is 2. + { "nameContains": "Remote", "lastAddressDigit": "", "address": "" } - Ignore all devices which have the word Remote in their name + { "nameContains": "", "lastAddressDigit": "", "address": "15 5 3 2"} - Ignore the device with an ISY address of 15 5 3 2. +*/ var types = require("HAP-NodeJS/accessories/types.js"); var isy = require('isy-js'); var Service = require("HAP-NodeJS").Service; var Characteristic = require("HAP-NodeJS").Characteristic; var inherits = require('util').inherits; +// Global device map. Needed to map incoming notifications to the corresponding HomeKit device for update. var deviceMap = {}; function ISYChangeHandler(isy,device) { From 6030928c1271f4f704502e9f7b7981014fa13e1e Mon Sep 17 00:00:00 2001 From: rodtoll Date: Sun, 11 Oct 2015 13:34:58 -0700 Subject: [PATCH 068/121] Fixed elk panel state transitions. --- platforms/isy-js.js | 77 ++++++++++++--------------------------------- 1 file changed, 20 insertions(+), 57 deletions(-) diff --git a/platforms/isy-js.js b/platforms/isy-js.js index f28c0c0..5f01a11 100644 --- a/platforms/isy-js.js +++ b/platforms/isy-js.js @@ -573,51 +573,30 @@ ISYElkAlarmPanelAccessory.prototype.identify = function(callback) { } ISYElkAlarmPanelAccessory.prototype.setAlarmTargetState = function(targetStateHK,callback) { - this.log("Sending command to set alarm panel state to: "+targetStateHK); - var targetState = this.translateHKToAlarmTargetState(targetState); - if(this.alarmTargetState != targetState) { + this.log("***** Sending command to set alarm panel state to: "+targetStateHK); + var targetState = this.translateHKToAlarmTargetState(targetStateHK); + this.log("***** Would send the target state of: "+targetState); + if(this.device.getAlarmMode() != targetState) { this.device.sendSetAlarmModeCommand(targetState, function(result) { callback(); }); } else { + this.log("***** Redundant command, already in that state."); callback(); } } -////// Current State - -/* -ELKAlarmPanelDevice.prototype.ALARM_STATE_NOT_READY_TO_ARM = 0; -ELKAlarmPanelDevice.prototype.ALARM_STATE_READY_TO_ARM = 1; -ELKAlarmPanelDevice.prototype.ALARM_STATE_READY_TO_ARM_VIOLATION = 2; -ELKAlarmPanelDevice.prototype.ALARM_STATE_ARMED_WITH_TIMER = 3; -ELKAlarmPanelDevice.prototype.ALARM_STATE_ARMED_FULLY = 4; -ELKAlarmPanelDevice.prototype.ALARM_STATE_FORCE_ARMED_VIOLATION = 5; -ELKAlarmPanelDevice.prototype.ALARM_STATE_ARMED_WITH_BYPASS = 6; -*/ - -/* -ELKAlarmPanelDevice.prototype.ALARM_TRIP_STATE_DISARMED = 0; -ELKAlarmPanelDevice.prototype.ALARM_TRIP_STATE_EXIT_DELAY = 1; -ELKAlarmPanelDevice.prototype.ALARM_TRIP_STATE_TRIPPED = 2; -*/ - -/* -Characteristic.SecuritySystemCurrentState.STAY_ARM = 0; -Characteristic.SecuritySystemCurrentState.AWAY_ARM = 1; -Characteristic.SecuritySystemCurrentState.NIGHT_ARM = 2; -Characteristic.SecuritySystemCurrentState.DISARMED = 3; -Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED = 4; -*/ - ISYElkAlarmPanelAccessory.prototype.translateAlarmCurrentStateToHK = function() { var tripState = this.device.getAlarmTripState(); - if(tripState == this.device.ALARM_TRIP_STATE_DISARMED || tripState == this.device.ALARM_TRIP_STATE_EXIT_DELAY) { - return Characteristic.SecuritySystemCurrentState.DISARMED; - } else if(tripState ==this.device.ALARM_TRIP_STATE_TRIPPED) { - return Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED; + var sourceAlarmState = this.device.getAlarmState(); + + if(tripState >= this.device.ALARM_TRIP_STATE_TRIPPED) { + return Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED; + } else if(sourceAlarmState == this.device.ALARM_STATE_NOT_READY_TO_ARM || + sourceAlarmState == this.device.ALARM_STATE_READY_TO_ARM || + sourceAlarmState == this.device.ALARM_STATE_READY_TO_ARM_VIOLATION) { + return Characteristic.SecuritySystemCurrentState.DISARMED; } else { - var sourceAlarmState = this.device.getAlarmMode(); if(sourceAlarmState == this.device.ALARM_MODE_STAY || sourceAlarmState == this.device.ALARM_MODE_STAY_INSTANT ) { return Characteristic.SecuritySystemCurrentState.STAY_ARM; } else if(sourceAlarmState == this.device.ALARM_MODE_AWAY || sourceAlarmState == this.device.ALARM_MODE_VACATION) { @@ -625,31 +604,12 @@ ISYElkAlarmPanelAccessory.prototype.translateAlarmCurrentStateToHK = function() } else if(sourceAlarmState == this.device.ALARM_MODE_NIGHT || sourceAlarmState == this.device.ALARM_MODE_NIGHT_INSTANT) { return Characteristic.SecuritySystemCurrentState.NIGHT_ARM; } else { - return Characteristic.SecuritySystemCurrentState.DISARM; + this.log("***** Setting to disarmed because sourceAlarmState is "+sourceAlarmState); + return Characteristic.SecuritySystemCurrentState.DISARMED; } } } -////// Target Mode - -/* -ELKAlarmPanelDevice.prototype.ALARM_MODE_DISARMED = 0; -ELKAlarmPanelDevice.prototype.ALARM_MODE_AWAY = 1; -ELKAlarmPanelDevice.prototype.ALARM_MODE_STAY = 2; -ELKAlarmPanelDevice.prototype.ALARM_MODE_STAY_INSTANT = 3; -ELKAlarmPanelDevice.prototype.ALARM_MODE_NIGHT = 4; -ELKAlarmPanelDevice.prototype.ALARM_MODE_NIGHT_INSTANT = 5; -ELKAlarmPanelDevice.prototype.ALARM_MODE_VACATION = 6; -*/ - -/* -Characteristic.SecuritySystemTargetState.STAY_ARM = 0; -Characteristic.SecuritySystemTargetState.AWAY_ARM = 1; -Characteristic.SecuritySystemTargetState.NIGHT_ARM = 2; -Characteristic.SecuritySystemTargetState.DISARM = 3; -*/ - - ISYElkAlarmPanelAccessory.prototype.translateAlarmTargetStateToHK = function() { var sourceAlarmState = this.device.getAlarmMode(); if(sourceAlarmState == this.device.ALARM_MODE_STAY || sourceAlarmState == this.device.ALARM_MODE_STAY_INSTANT ) { @@ -669,9 +629,9 @@ ISYElkAlarmPanelAccessory.prototype.translateHKToAlarmTargetState = function(sta } else if(state == Characteristic.SecuritySystemTargetState.AWAY_ARM) { return this.device.ALARM_MODE_AWAY; } else if(state == Characteristic.SecuritySystemTargetState.NIGHT_ARM) { - return this.device.NIGHT_ARM; + return this.device.ALARM_MODE_NIGHT; } else { - return this.device.DISARM; + return this.device.ALARM_MODE_DISARMED; } } @@ -684,6 +644,8 @@ ISYElkAlarmPanelAccessory.prototype.getAlarmCurrentState = function(callback) { } ISYElkAlarmPanelAccessory.prototype.handleExternalChange = function() { + this.log("***** Source device. Currenty state locally -"+this.device.getAlarmStatusAsText()); + this.log("***** Got alarm change notification. Setting HK target state to: "+this.translateAlarmTargetStateToHK()+" Setting HK Current state to: "+this.translateAlarmCurrentStateToHK()); this.alarmPanelService .setCharacteristic(Characteristic.SecuritySystemTargetState, this.translateAlarmTargetStateToHK()); this.alarmPanelService @@ -724,3 +686,4 @@ module.exports.accessory = ISYLightAccessory; module.exports.accessory = ISYLockAccessory; module.exports.accessory = ISYOutletAccessory; module.exports.accessory = ISYDoorWindowSensorAccessory; +module.exports.accessory = ISYElkAlarmPanelAccessory; From c506c44d6091b05b550b6fc901f4a1c69a7b2605 Mon Sep 17 00:00:00 2001 From: stipus Date: Mon, 12 Oct 2015 02:02:37 +0200 Subject: [PATCH 069/121] Update HomeSeer.js --- platforms/HomeSeer.js | 248 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 230 insertions(+), 18 deletions(-) diff --git a/platforms/HomeSeer.js b/platforms/HomeSeer.js index 1ecbd43..8cb371a 100644 --- a/platforms/HomeSeer.js +++ b/platforms/HomeSeer.js @@ -2,8 +2,15 @@ // // HomeSeer Platform Shim for HomeBridge -// V0.1 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/07 - Initial version -// V0.2 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/10 - occupancy sensor fix +// V0.1 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/07 +// - Initial version +// V0.2 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/10 +// - Occupancy sensor fix +// V0.3 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/11 +// - Added TemperatureUnit=F|C option to temperature sensors +// - Added negative temperature support to temperature sensors +// V0.4 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/11 +// - Added thermostat support // // // Remember to add platform to config.json. @@ -14,24 +21,50 @@ // Example: // "platforms": [ // { -// "platform": "HomeSeer", // required -// "name": "HomeSeer", // required -// "url": "http://192.168.3.4:81", // required +// "platform": "HomeSeer", // Required +// "name": "HomeSeer", // Required +// "host": "http://192.168.3.4:81", // Required - If you did setup HomeSeer authentication, use "http://user:password@ip_address:port" // "accessories":[ // { -// "ref":8, // required - HomeSeer Device Reference (To get it, select the HS Device - then Advanced Tab) -// "type":"Lightbulb", // Optional - Lightbulb is the default -// "name":"My Light", // Optional - HomeSeer device name is the default -// "offValue":"0", // Optional - 0 is the default -// "onValue":"100", // Optional - 100 is the default -// "can_dim":true // Optional - true is the default - false for a non dimmable lightbulb +// "ref":8, // Required - HomeSeer Device Reference (To get it, select the HS Device - then Advanced Tab) +// "type":"Lightbulb", // Optional - Lightbulb is the default +// "name":"My Light", // Optional - HomeSeer device name is the default +// "offValue":"0", // Optional - 0 is the default +// "onValue":"100", // Optional - 100 is the default +// "can_dim":true // Optional - true is the default - false for a non dimmable lightbulb // }, // { -// "ref":9 // This is a dimmable Lightbulb by default +// "ref":9 // This is a dimmable Lightbulb by default // }, // { -// "ref":58, // This is an controllable outlet +// "ref":58, // This is an controllable outlet // "type":"Outlet" +// }, +// { +// "ref":111, +// "type":"TemperatureSensor", // Required for a temperature sensor +// "temperatureUnit":"F", // Optional - C is the default +// "name":"Bedroom temp" // Optional - HomeSeer device name is the default +// }, +// { +// "ref":113, // Required - HomeSeer Device Reference of the Current Temperature Device +// "type":"Thermostat", // Required for a Thermostat +// "name":"Température Salon", // Optional - HomeSeer device name is the default +// "temperatureUnit":"C", // Optional - F for Fahrenheit, C for Celsius, C is the default +// "setPointRef":167, // Required - HomeSeer device reference for your thermostat Set Point. +// "setPointReadOnly":true, // Optional - Set to false if your SetPoint is read/write. true is the default +// "stateRef":166, // Required - HomeSeer device reference for your thermostat current state +// "stateOffValues":[0,4,5], // Required - List of the HomeSeer device values for a HomeKit state=OFF +// "stateHeatValues":[1], // Required - List of the HomeSeer device values for a HomeKit state=HEAT +// "stateCoolValues":[2], // Required - List of the HomeSeer device values for a HomeKit state=COOL +// "stateAutoValues":[3], // Required - List of the HomeSeer device values for a HomeKit state=AUTO +// "controlRef":168, // Required - HomeSeer device reference for your thermostat mode control (It can be the same as stateRef for some thermostats) +// "controlOffValue":0, // Required - Value for OFF +// "controlHeatValue":1, // Required - Value for HEAT +// "controlCoolValue":2, // Required - Value for COOL +// "controlAutoValue":3, // Required - Value for AUTO +// "coolingThresholdRef":169, // Optional - Not-implemented-yet - HomeSeer device reference for your thermostat cooling threshold +// "heatingThresholdRef":170 // Optional - Not-implemented-yet - HomeSeer device reference for your thermostat heating threshold // } // ] // } @@ -43,7 +76,8 @@ // - Fan (onValue, offValue options) // - Switch (onValue, offValue options) // - Outlet (onValue, offValue options) -// - TemperatureSensor +// - TemperatureSensor (temperatureUnit=C|F) +// - Thermostat (temperatureUnit, setPoint, state, control options) // - ContactSensor // - MotionSensor // - LeakSensor @@ -89,6 +123,7 @@ HomeSeerPlatform.prototype = { var that = this; var foundAccessories = []; var url = this.config["host"] + "/JSON?request=getstatus&ref=" + refList; + httpRequest( url, "GET", function(error, response, body) { if (error) { this.log('HomeSeer status function failed: %s', error.message); @@ -115,8 +150,9 @@ function HomeSeerAccessory(log, platformConfig, status ) { this.onValue = "100"; this.offValue = "0"; - this.control_url = platformConfig["host"] + "/JSON?request=controldevicebyvalue&ref=" + this.ref + "&value="; - this.status_url = platformConfig["host"] + "/JSON?request=getstatus&ref=" + this.ref; + this.access_url = platformConfig["host"] + "/JSON?"; + this.control_url = this.access_url + "request=controldevicebyvalue&ref=" + this.ref + "&value="; + this.status_url = this.access_url + "request=getstatus&ref=" + this.ref; for( var i=0; i Date: Mon, 12 Oct 2015 02:11:24 +0200 Subject: [PATCH 070/121] Update HomeSeer.js --- platforms/HomeSeer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platforms/HomeSeer.js b/platforms/HomeSeer.js index 8cb371a..e691b68 100644 --- a/platforms/HomeSeer.js +++ b/platforms/HomeSeer.js @@ -505,7 +505,7 @@ HomeSeerAccessory.prototype = { } case "OccupancySensor": { var occupancySensorService = new Service.OccupancySensor(); - motionSensorService + occupancySensorService .getCharacteristic(Characteristic.OccupancyDetected) .on('get', this.getPowerState.bind(this)); services.push( occupancySensorService ); From c59b87d17d3fb032ce087ca7cb7119e46c4b5ce0 Mon Sep 17 00:00:00 2001 From: stipus Date: Mon, 12 Oct 2015 02:15:41 +0200 Subject: [PATCH 071/121] Update HomeSeer.js --- platforms/HomeSeer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platforms/HomeSeer.js b/platforms/HomeSeer.js index e691b68..e3c289b 100644 --- a/platforms/HomeSeer.js +++ b/platforms/HomeSeer.js @@ -9,7 +9,7 @@ // V0.3 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/11 // - Added TemperatureUnit=F|C option to temperature sensors // - Added negative temperature support to temperature sensors -// V0.4 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/11 +// V0.4 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/12 // - Added thermostat support // // From d4279c5ef5be6880c093dee570ab51e999ae35f7 Mon Sep 17 00:00:00 2001 From: iRaven Date: Mon, 12 Oct 2015 18:12:14 +0200 Subject: [PATCH 072/121] Added Support for Thermostat HM-CC-RT-DN --- accessories/HomeMaticThermo | 264 ++++++++++++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 accessories/HomeMaticThermo diff --git a/accessories/HomeMaticThermo b/accessories/HomeMaticThermo new file mode 100644 index 0000000..f3d300f --- /dev/null +++ b/accessories/HomeMaticThermo @@ -0,0 +1,264 @@ +var types = require("HAP-NodeJS/accessories/types.js"); +var request = require("request"); + +function HomeMaticThermo(log, config) { + this.log = log; + this.name = config["name"]; + this.ccuIDTargetTemp = config["ccu_id_TargetTemp"]; + this.ccuIDCurrentTemp = config["ccu_id_CurrentTemp"]; + this.ccuIDControlMode = config["ccu_id_ControlMode"]; + this.ccuIDManuMode = config["ccu_id_ManuMode"]; + this.ccuIDAutoMode = config["ccu_id_AutoMode"]; + this.ccuIP = config["ccu_ip"]; +} + +HomeMaticThermo.prototype = { + + setTargetTemperature: function(value) { + + var that = this; + + this.log("Setting target Temperature of CCU to " + value); + this.log(this.ccuIDTargetTemp + " " + value); + + request.put({ + url: "http://"+this.ccuIP+"/config/xmlapi/statechange.cgi?ise_id="+this.ccuIDTargetTemp+"&new_value="+ value, + }, function(err, response, body) { + + if (!err && response.statusCode == 200) { + that.log("State change complete."); + } + else { + that.log("Error '"+err+"' setting Temperature: " + body); + } + }); + }, + getCurrentTemperature: function(callback) { + + var that = this; + + this.log("Getting current Temperature of CCU"); + request.get({ + url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuIDCurrentTemp, + }, function(err, response, body) { + + if (!err && response.statusCode == 200) { + + //that.log("Response:"+response.body); + var responseString = response.body.substring(83,87); + //that.log(responseString); + callback(parseFloat(responseString)); + //that.log("Getting current temperature complete."); + } + else { + that.log("Error '"+err+"' getting Temperature: " + body); + } + }); + }, + getTargetTemperature: function(callback) { + + var that = this; + +this.log("Getting target Temperature of CCU"); + request.get({ + url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuIDTargetTemp, + }, function(err, response, body) { + + if (!err && response.statusCode == 200) { + + //that.log("Response:"+response.body); + var responseString = response.body.substring(83,87); + //that.log(responseString); + callback(parseFloat(responseString)); + //that.log("Getting target temperature complete."); + } + else { + that.log("Error '"+err+"' getting Temperature: " + body); + } + }); + }, + getMode: function(callback) { + + var that = this; + + //this.log("Getting target Mode of CCU"); + //this.log(this.ccuID+ value); + + request.get({ + url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuIDControlMode, + }, function(err, response, body) { + + if (!err && response.statusCode == 200) { + + //that.log("Response:"+response.body); + var responseInt = response.body.substring(83,84); + //that.log(responseString); + if (responseInt == 1) + { callback(parseInt("0")); } + if (responseInt == 0) + { callback(parseInt("1")); } + //that.log("Getting mode complete."); + } + else { + that.log("Error '"+err+"' getting Mode: " + body); + } + }); + }, + setMode: function(value) { + + var that = this; + + //this.log("Seting target Mode of CCU:" + value); + var modvalue; + var dpID; + switch(value) { + case 3: {modvalue = "true";dpID=this.ccuIDAutoMode;break;} //auto + case 1: {modvalue = "true";dpID=this.ccuIDAutoMode;break;} //heating => auto + default: {modvalue = "1";dpID=this.ccuIDManuMode;} //default => off (manual) + } + + request.put({ + url: "http://"+this.ccuIP+"/config/xmlapi/statechange.cgi?ise_id="+dpID+"&new_value="+ modvalue, + }, function(err, response, body) { + + if (!err && response.statusCode == 200) { + //that.log("Setting Mode complete."); + } + else { + that.log("Error '"+err+"' setting Mode: " + body); + } + }); + }, + getServices: function() { + var that = this; + return [{ + sType: types.ACCESSORY_INFORMATION_STYPE, + characteristics: [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of the accessory", + designedMaxLength: 255 + },{ + cType: types.MANUFACTURER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: "test", + supportEvents: false, + supportBonjour: false, + manfDescription: "Manufacturer", + designedMaxLength: 255 + },{ + cType: types.MODEL_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: "test", + supportEvents: false, + supportBonjour: false, + manfDescription: "Model", + designedMaxLength: 255 + },{ + cType: types.SERIAL_NUMBER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: "A1S2NREF88EW", + 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 + }] + },{ + sType: types.THERMOSTAT_STYPE, + characteristics: [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + },{ + cType: types.CURRENTHEATINGCOOLING_CTYPE, + onRead: function(callback) { that.getMode(callback); }, + perms: ["pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Mode", + designedMaxLength: 1, + designedMinValue: 0, + designedMaxValue: 2, + designedMinStep: 1, + },{ + cType: types.TARGETHEATINGCOOLING_CTYPE, + onRead: function(callback) { that.getMode(callback); }, + onUpdate: function(value) { that.setMode(value);}, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Target Mode", + designedMinValue: 0, + designedMaxValue: 3, + designedMinStep: 1, + },{ + cType: types.CURRENT_TEMPERATURE_CTYPE, + onRead: function(callback) { that.getCurrentTemperature(callback); }, + onUpdate: null, + perms: ["pr","ev"], + format: "float", + initialValue: 13.0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Temperature", + unit: "celsius" + },{ + cType: types.TARGET_TEMPERATURE_CTYPE, + onUpdate: function(value) { that.setTargetTemperature(value); }, + onRead: function(callback) { that.getTargetTemperature(callback); }, + perms: ["pw","pr","ev"], + format: "float", + initialValue: 19.0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Target Temperature", + designedMinValue: 4, + designedMaxValue: 25, + designedMinStep: 0.1, + unit: "celsius" + },{ + cType: types.TEMPERATURE_UNITS_CTYPE, + onUpdate: null, + perms: ["pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Unit" + }] + }]; + } +}; + +module.exports.accessory = HomeMaticThermo; From 1f8db79324ad2935d143a43b9d4f23a786c89a9a Mon Sep 17 00:00:00 2001 From: stipus Date: Tue, 13 Oct 2015 00:53:19 +0200 Subject: [PATCH 073/121] Added support for humidity sensors, battery level sensors, low battery flag for all sensors, and ability to run HomeSeer events - Added Humidity sensor support - Added Battery support - Added low battery support for all sensors - Added HomeSeer event support (using HomeKit switches...) --- platforms/HomeSeer.js | 214 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 193 insertions(+), 21 deletions(-) diff --git a/platforms/HomeSeer.js b/platforms/HomeSeer.js index e3c289b..e0c5c79 100644 --- a/platforms/HomeSeer.js +++ b/platforms/HomeSeer.js @@ -11,6 +11,12 @@ // - Added negative temperature support to temperature sensors // V0.4 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/12 // - Added thermostat support +// V0.5 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/12 +// - Added Humidity sensor support +// V0.6 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/12 +// - Added Battery support +// - Added low battery support for all sensors +// - Added HomeSeer event support (using HomeKit switches...) // // // Remember to add platform to config.json. @@ -24,7 +30,16 @@ // "platform": "HomeSeer", // Required // "name": "HomeSeer", // Required // "host": "http://192.168.3.4:81", // Required - If you did setup HomeSeer authentication, use "http://user:password@ip_address:port" -// "accessories":[ +// +// "events":[ // Optional - List of Events - Currently they are imported into HomeKit as switches +// { +// "eventGroup":"My Group", // Required - The HomeSeer event group +// "eventName":"My Event", // Required - The HomeSeer event name +// "name":"Test" // Optional - HomeSeer event name is the default +// } +// ], +// +// "accessories":[ // Required - List of Accessories // { // "ref":8, // Required - HomeSeer Device Reference (To get it, select the HS Device - then Advanced Tab) // "type":"Lightbulb", // Optional - Lightbulb is the default @@ -37,14 +52,16 @@ // "ref":9 // This is a dimmable Lightbulb by default // }, // { -// "ref":58, // This is an controllable outlet +// "ref":58, // This is a controllable outlet // "type":"Outlet" // }, // { -// "ref":111, +// "ref":111, // Required - HomeSeer Device Reference for your sensor // "type":"TemperatureSensor", // Required for a temperature sensor // "temperatureUnit":"F", // Optional - C is the default -// "name":"Bedroom temp" // Optional - HomeSeer device name is the default +// "name":"Bedroom temp", // Optional - HomeSeer device name is the default +// "batteryRef":112, // Optional - HomeSeer device reference for the sensor battery level +// "batteryThreshold":15 // Optional - If sensor battery level is below this value, the HomeKit LowBattery characteristic is set to 1. Default is 10 // }, // { // "ref":113, // Required - HomeSeer Device Reference of the Current Temperature Device @@ -65,6 +82,12 @@ // "controlAutoValue":3, // Required - Value for AUTO // "coolingThresholdRef":169, // Optional - Not-implemented-yet - HomeSeer device reference for your thermostat cooling threshold // "heatingThresholdRef":170 // Optional - Not-implemented-yet - HomeSeer device reference for your thermostat heating threshold +// }, +// { +// "ref":115, // Required - HomeSeer Device Reference for a device holding battery level (0-100) +// "type":"Battery", // Required for a Battery +// "name":"Roomba battery", // Optional - HomeSeer device name is the default +// "batteryThreshold":15 // Optional - If the level is below this value, the HomeKit LowBattery characteristic is set to 1. Default is 10 // } // ] // } @@ -72,18 +95,20 @@ // // // SUPORTED TYPES: -// - Lightbulb (can_dim, onValue, offValue options) -// - Fan (onValue, offValue options) -// - Switch (onValue, offValue options) -// - Outlet (onValue, offValue options) +// - Lightbulb (can_dim, onValue, offValue options) +// - Fan (onValue, offValue options) +// - Switch (onValue, offValue options) +// - Outlet (onValue, offValue options) +// - Thermostat (temperatureUnit, setPoint, state, control options) // - TemperatureSensor (temperatureUnit=C|F) -// - Thermostat (temperatureUnit, setPoint, state, control options) -// - ContactSensor -// - MotionSensor -// - LeakSensor -// - LightSensor -// - OccupancySensor -// - SmokeSensor +// - ContactSensor (0=no contact, 1=contact - batteryRef, batteryThreshold option) +// - MotionSensor (0=no motion, 1=motion - batteryRef, batteryThreshold option) +// - LeakSensor (0=no leak, 1=leak - batteryRef, batteryThreshold option) +// - LightSensor (HomeSeer device value in Lux - batteryRef, batteryThreshold option) +// - HumiditySensor (HomeSeer device value in % - batteryRef, batteryThreshold option) +// - OccupancySensor (0=no occupancy, 1=occupancy - batteryRef, batteryThreshold option) +// - SmokeSensor (0=no smoke, 1=smoke - batteryRef, batteryThreshold option) +// - Battery (batteryThreshold option) // - Door @@ -111,19 +136,25 @@ function HomeSeerPlatform(log, config){ HomeSeerPlatform.prototype = { accessories: function(callback) { - this.log("Fetching HomeSeer devices."); + var that = this; + var foundAccessories = []; + if( this.config.events ) { + this.log("Creating HomeSeer events."); + for( var i=0; i Date: Tue, 13 Oct 2015 05:37:44 +0200 Subject: [PATCH 074/121] MotionDetector added, with new needed configuration tags. --- platforms/ZWayServer.js | 70 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index 0b81e88..92938a3 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -131,8 +131,11 @@ ZWayServerPlatform.prototype = { gdid = vdev.id.replace(/^(.*?)_zway_(\d+-\d+)-\d.*/, '$1_$2'); } - var gd = groupedDevices[gdid] || (groupedDevices[gdid] = {devices: [], types: {}, extras: {}, primary: undefined}); + var gd = groupedDevices[gdid] || (groupedDevices[gdid] = { devices: [], types: {}, extras: {}, primary: undefined, cxmap: {} }); + gd.devices.push(vdev); + var vdevIndex = gd.devices.length - 1; + var tk = ZWayServerPlatform.getVDevTypeKey(vdev); // If this is explicitly set as primary, set it now... @@ -143,17 +146,24 @@ ZWayServerPlatform.prototype = { gd.extras[tk].push(gd.types[tk]); delete gd.types[tk]; // clear the way for this one to be set here below... } - gd.primary = gd.devices.length - 1; + gd.primary = vdevIndex; //gd.types[tk] = gd.primary; } if(gd.types[tk] === undefined){ - gd.types[tk] = gd.devices.length - 1; + gd.types[tk] = vdevIndex; } else { gd.extras[tk] = gd.extras[tk] || []; - gd.extras[tk].push(gd.devices.length - 1); + gd.extras[tk].push(vdevIndex); + } + if(tk !== vdev.deviceType) gd.types[vdev.deviceType] = vdevIndex; // also include the deviceType only as a possibility + + // Create a map entry when Homebridge.Characteristic.Type is set... + var ctype = this.getTagValue(vdev, "Characteristic.Type"); + if(ctype && Characteristic[ctype]){ + var cx = new Characteristic[ctype](); + gd.cxmap[cx.UUID] = vdevIndex; } - if(tk !== vdev.deviceType) gd.types[vdev.deviceType] = gd.devices.length - 1; // also include the deviceType only as a possibility } for(var gdid in groupedDevices) { @@ -357,6 +367,11 @@ ZWayServerAccessory.prototype = { case "sensorMultilevel.Luminiscence": services.push(new Service.LightSensor(vdev.metrics.title, vdev.id)); break; + case "sensorBinary": + var stype = this.platform.getTagValue(vdev, "Service.Type"); + if(stype === "MotionSensor"){ + services.push(new Service.MotionSensor(vdev.metrics.title, vdev.id)); + } } var validServices =[]; @@ -377,6 +392,12 @@ ZWayServerAccessory.prototype = { } , getVDevForCharacteristic: function(cx, vdevPreferred){ + + // If we know which vdev should be used for this Characteristic, we're done! + if(this.devDesc.cxmap[cx.UUID] !== undefined){ + return this.devDesc.devices[this.devDesc.cxmap[cx.UUID]]; + } + var map = this.uuidToTypeKeyMap; if(!map){ this.uuidToTypeKeyMap = map = {}; @@ -399,7 +420,7 @@ ZWayServerAccessory.prototype = { } if(cx instanceof Characteristic.Name) return vdevPreferred; - + // Special case!: If cx is a CurrentTemperature, ignore the preferred device...we want the sensor if available! if(cx instanceof Characteristic.CurrentTemperature) vdevPreferred = null; // @@ -768,6 +789,43 @@ ZWayServerAccessory.prototype = { }); return cx; } + + if(cx instanceof Characteristic.MotionDetected){ + cx.zway_getValueFromVDev = function(vdev){ + return vdev.metrics.level === "off" ? false : true; + }; + cx.value = cx.zway_getValueFromVDev(vdev); + cx.on('get', function(callback, context){ + debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); + this.getVDev(vdev).then(function(result){ + debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); + callback(false, cx.zway_getValueFromVDev(result.data)); + }); + }.bind(this)); + cx.on('change', function(ev){ + debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); + }); + return cx; + } + + if(cx instanceof Characteristic.StatusTampered){ + cx.zway_getValueFromVDev = function(vdev){ + return vdev.metrics.level === "off" ? Characteristic.StatusTampered.NOT_TAMPERED : Characteristic.StatusTampered.TAMPERED; + }; + cx.value = cx.zway_getValueFromVDev(vdev); + cx.on('get', function(callback, context){ + debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); + this.getVDev(vdev).then(function(result){ + debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); + callback(false, cx.zway_getValueFromVDev(result.data)); + }); + }.bind(this)); + cx.on('change', function(ev){ + debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); + }); + return cx; + } + } , configureService: function(service, vdev){ From fdbd33f29dc97476745770a36af2d6481c084e1e Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Tue, 13 Oct 2015 06:50:23 +0200 Subject: [PATCH 075/121] Updated for new tweaked "setProps" API, removed cruft. --- platforms/ZWayServer.js | 43 +++++++++++++++-------------------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index 92938a3..e50336e 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -459,7 +459,6 @@ ZWayServerAccessory.prototype = { debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); callback(false, accessory.name); }); - cx.writable = false; return cx; } @@ -546,12 +545,6 @@ ZWayServerAccessory.prototype = { }); }.bind(this)); - cx.writeable = false; - //cx.on('set', function(level, callback){ - // this.command(vdev, "exact", {level: "on", "color.r": 255, "color.g": 0, "color.b": 0}).then(function(result){ - // callback(); - // }); - //}.bind(this)); return cx; } @@ -581,12 +574,6 @@ ZWayServerAccessory.prototype = { }); }.bind(this)); - cx.writeable = false; - //cx.on('set', function(level, callback){ - // this.command(vdev, "exact", {level: "on", "color.r": 255, "color.g": 0, "color.b": 0}).then(function(result){ - // callback(); - // }); - //}.bind(this)); return cx; } @@ -602,8 +589,10 @@ ZWayServerAccessory.prototype = { callback(false, cx.zway_getValueFromVDev(result.data)); }); }.bind(this)); - cx.minimumValue = vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : -40; - cx.maximumValue = vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 999; + cx.setProps({ + minValue: vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : -40, + maxValue: vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 999 + }); return cx; } @@ -625,8 +614,10 @@ ZWayServerAccessory.prototype = { callback(); }); }.bind(this)); - cx.minimumValue = vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : 5; - cx.maximumValue = vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 40; + cx.setProps({ + minValue: vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : 5, + maxValue: vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 40 + }); return cx; } @@ -640,7 +631,9 @@ ZWayServerAccessory.prototype = { debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); callback(false, Characteristic.TemperatureDisplayUnits.CELSIUS); }); - cx.writable = false; + cx.setProps({ + perms: [Characteristic.Perms.READ] + }); return cx; } @@ -668,7 +661,6 @@ ZWayServerAccessory.prototype = { callback(false, Characteristic.TargetHeatingCoolingState.HEAT); }); // Hmm... apparently if this is not setable, we can't add a thermostat change to a scene. So, make it writable but a no-op. - cx.writable = true; cx.on('set', function(newValue, callback){ debug("WARN: Set of TargetHeatingCoolingState not yet implemented, resetting to HEAT!") callback(undefined, Characteristic.TargetHeatingCoolingState.HEAT); @@ -703,8 +695,9 @@ ZWayServerAccessory.prototype = { debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); callback(false, Characteristic.TargetDoorState.CLOSED); }); - //cx.readable = false; - cx.writable = false; + cx.setProps({ + perms: [Characteristic.Perms.READ] + }); } if(cx instanceof Characteristic.ObstructionDetected){ @@ -717,8 +710,6 @@ ZWayServerAccessory.prototype = { debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); callback(false, false); }); - //cx.readable = false; - cx.writable = false; } if(cx instanceof Characteristic.BatteryLevel){ @@ -759,8 +750,6 @@ ZWayServerAccessory.prototype = { debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); callback(false, Characteristic.ChargingState.NOT_CHARGING); }); - //cx.readable = false; - cx.writable = false; } if(cx instanceof Characteristic.CurrentAmbientLightLevel){ @@ -769,8 +758,8 @@ ZWayServerAccessory.prototype = { // Completely unscientific guess, based on test-fit data and Wikipedia real-world lux values. // This will probably change! var lux = 0.0005 * (vdev.metrics.level^3.6); - if(lux < cx.minimumValue) return cx.minimumValue; - if(lux > cx.maximumValue) return cx.maximumValue; + // Bounds checking now done upstream! + //if(lux < cx.minimumValue) return cx.minimumValue; if(lux > cx.maximumValue) return cx.maximumValue; return lux; } else { return vdev.metrics.level; From 7c7ceb64534eeaf875ddaf18b685aa2053e19e21 Mon Sep 17 00:00:00 2001 From: iRaven Date: Tue, 13 Oct 2015 13:06:11 +0200 Subject: [PATCH 076/121] Rename HomeMaticThermo to HomeMaticThermo.js --- accessories/{HomeMaticThermo => HomeMaticThermo.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename accessories/{HomeMaticThermo => HomeMaticThermo.js} (100%) diff --git a/accessories/HomeMaticThermo b/accessories/HomeMaticThermo.js similarity index 100% rename from accessories/HomeMaticThermo rename to accessories/HomeMaticThermo.js From d8a35963266e6a2db4ff271cd57c8caa22da098b Mon Sep 17 00:00:00 2001 From: iRaven Date: Tue, 13 Oct 2015 17:39:28 +0200 Subject: [PATCH 077/121] Added HomematicThermo, HomematicWindow --- config-sample.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/config-sample.json b/config-sample.json index 7af74db..1e9d872 100644 --- a/config-sample.json +++ b/config-sample.json @@ -149,6 +149,24 @@ "ccu_id": "The XMP-API id of your HomeMatic device", "ccu_ip": "The IP-Adress of your HomeMatic CCU device" }, + { + "accessory": "HomeMaticWindow", + "name": "Contact", + "description": "Control HomeMatic devices (The XMP-API addon for the CCU is required)", + "ccu_id": "The XMP-API id of your HomeMatic device (type HM-Sec-RHS)", + "ccu_ip": "The IP-Adress of your HomeMatic CCU device" + }, + { + "accessory": "HomeMaticThermo", + "name": "Contact", + "description": "Control HomeMatic devices (The XMP-API addon for the CCU is required)", + "ccu_id_TargetTemp": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )", + "ccu_id_CurrentTemp": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )", + "ccu_id_ControlMode": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )", + "ccu_id_ManuMode": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )", + "ccu_id_AutoMode": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )", + "ccu_ip": "The IP-Adress of your HomeMatic CCU device" + }, { "accessory": "X10", "name": "Lamp", From a056b16c35e0e6c6dfc91bbd73fca2ae01f7009a Mon Sep 17 00:00:00 2001 From: stipus Date: Wed, 14 Oct 2015 22:06:11 +0200 Subject: [PATCH 078/121] Added CarbonMonoxide and CarbonDioxide support - Added CarbonMonoxide and CarbonDioxide sensor support - Added onValues option to all binary sensors - Added uuid_base parameter to all HomeSeer accessories --- platforms/HomeSeer.js | 212 +++++++++++++++++++++++++++++------------- 1 file changed, 149 insertions(+), 63 deletions(-) diff --git a/platforms/HomeSeer.js b/platforms/HomeSeer.js index e0c5c79..c4ccde7 100644 --- a/platforms/HomeSeer.js +++ b/platforms/HomeSeer.js @@ -17,6 +17,13 @@ // - Added Battery support // - Added low battery support for all sensors // - Added HomeSeer event support (using HomeKit switches...) +// V0.7 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/13 +// - You can add multiple HomeKit devices for the same HomeSeer device reference +// - Added CarbonMonoxide sensor +// - Added CarbonDioxide sensor +// - Added onValues option to all binary sensors +// V0.8 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/14 +// - Added uuid_base parameter to all accessories // // // Remember to add platform to config.json. @@ -35,7 +42,8 @@ // { // "eventGroup":"My Group", // Required - The HomeSeer event group // "eventName":"My Event", // Required - The HomeSeer event name -// "name":"Test" // Optional - HomeSeer event name is the default +// "name":"Test", // Optional - HomeSeer event name is the default +// "uuid_base":"SomeUniqueId" // Optional - HomeKit identifier will be derived from this parameter instead of the name // } // ], // @@ -46,7 +54,8 @@ // "name":"My Light", // Optional - HomeSeer device name is the default // "offValue":"0", // Optional - 0 is the default // "onValue":"100", // Optional - 100 is the default -// "can_dim":true // Optional - true is the default - false for a non dimmable lightbulb +// "can_dim":true, // Optional - true is the default - false for a non dimmable lightbulb +// "uuid_base":"SomeUniqueId2" // Optional - HomeKit identifier will be derived from this parameter instead of the name // }, // { // "ref":9 // This is a dimmable Lightbulb by default @@ -64,6 +73,22 @@ // "batteryThreshold":15 // Optional - If sensor battery level is below this value, the HomeKit LowBattery characteristic is set to 1. Default is 10 // }, // { +// "ref":34, // Required - HomeSeer Device Reference for your sensor +// "type":"SmokeSensor", // Required for a smoke sensor +// "name":"Kichen smoke detector", // Optional - HomeSeer device name is the default +// "batteryRef":35, // Optional - HomeSeer device reference for the sensor battery level +// "batteryThreshold":15, // Optional - If sensor battery level is below this value, the HomeKit LowBattery characteristic is set to 1. Default is 10 +// "onValues":[1,1.255] // Optional - List of all HomeSeer values triggering a "ON" sensor state - Default is any value different than 0 +// }, +// { +// "ref":34, // Required - HomeSeer Device Reference for your sensor (Here it's the same device as the SmokeSensor above) +// "type":"CarbonMonoxideSensor", // Required for a carbon monoxide sensor +// "name":"Kichen CO detector", // Optional - HomeSeer device name is the default +// "batteryRef":35, // Optional - HomeSeer device reference for the sensor battery level +// "batteryThreshold":15, // Optional - If sensor battery level is below this value, the HomeKit LowBattery characteristic is set to 1. Default is 10 +// "onValues":[2,2.255] // Optional - List of all HomeSeer values triggering a "ON" sensor state - Default is any value different than 0 +// }, +// { // "ref":113, // Required - HomeSeer Device Reference of the Current Temperature Device // "type":"Thermostat", // Required for a Thermostat // "name":"Température Salon", // Optional - HomeSeer device name is the default @@ -76,10 +101,10 @@ // "stateCoolValues":[2], // Required - List of the HomeSeer device values for a HomeKit state=COOL // "stateAutoValues":[3], // Required - List of the HomeSeer device values for a HomeKit state=AUTO // "controlRef":168, // Required - HomeSeer device reference for your thermostat mode control (It can be the same as stateRef for some thermostats) -// "controlOffValue":0, // Required - Value for OFF -// "controlHeatValue":1, // Required - Value for HEAT -// "controlCoolValue":2, // Required - Value for COOL -// "controlAutoValue":3, // Required - Value for AUTO +// "controlOffValue":0, // Required - HomeSeer device control value for OFF +// "controlHeatValue":1, // Required - HomeSeer device control value for HEAT +// "controlCoolValue":2, // Required - HomeSeer device control value for COOL +// "controlAutoValue":3, // Required - HomeSeer device control value for AUTO // "coolingThresholdRef":169, // Optional - Not-implemented-yet - HomeSeer device reference for your thermostat cooling threshold // "heatingThresholdRef":170 // Optional - Not-implemented-yet - HomeSeer device reference for your thermostat heating threshold // }, @@ -95,20 +120,22 @@ // // // SUPORTED TYPES: -// - Lightbulb (can_dim, onValue, offValue options) -// - Fan (onValue, offValue options) -// - Switch (onValue, offValue options) -// - Outlet (onValue, offValue options) -// - Thermostat (temperatureUnit, setPoint, state, control options) -// - TemperatureSensor (temperatureUnit=C|F) -// - ContactSensor (0=no contact, 1=contact - batteryRef, batteryThreshold option) -// - MotionSensor (0=no motion, 1=motion - batteryRef, batteryThreshold option) -// - LeakSensor (0=no leak, 1=leak - batteryRef, batteryThreshold option) -// - LightSensor (HomeSeer device value in Lux - batteryRef, batteryThreshold option) -// - HumiditySensor (HomeSeer device value in % - batteryRef, batteryThreshold option) -// - OccupancySensor (0=no occupancy, 1=occupancy - batteryRef, batteryThreshold option) -// - SmokeSensor (0=no smoke, 1=smoke - batteryRef, batteryThreshold option) -// - Battery (batteryThreshold option) +// - Lightbulb (can_dim, onValue, offValue options) +// - Fan (onValue, offValue options) +// - Switch (onValue, offValue options) +// - Outlet (onValue, offValue options) +// - Thermostat (temperatureUnit, setPoint, state, control options) +// - TemperatureSensor (temperatureUnit=C|F) +// - HumiditySensor (HomeSeer device value in % - batteryRef, batteryThreshold options) +// - LightSensor (HomeSeer device value in Lux - batteryRef, batteryThreshold options) +// - ContactSensor (onValues, batteryRef, batteryThreshold options) +// - MotionSensor (onValues, batteryRef, batteryThreshold options) +// - LeakSensor (onValues, batteryRef, batteryThreshold options) +// - OccupancySensor (onValues, batteryRef, batteryThreshold options) +// - SmokeSensor (onValues, batteryRef, batteryThreshold options) +// - CarbonMonoxideSensor (onValues, batteryRef, batteryThreshold options) +// - CarbonDioxideSensor (onValues, batteryRef, batteryThreshold options) +// - Battery (batteryThreshold option) // - Door @@ -163,9 +190,14 @@ HomeSeerPlatform.prototype = { else { this.log('HomeSeer status function succeeded!'); var response = JSON.parse( body ); - for( var i=0; i Date: Thu, 15 Oct 2015 14:25:20 -0700 Subject: [PATCH 079/121] Initial support for Indigo server (http://www.indigodomo.com) --- platforms/Indigo.js | 502 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 502 insertions(+) create mode 100644 platforms/Indigo.js diff --git a/platforms/Indigo.js b/platforms/Indigo.js new file mode 100644 index 0000000..e42849a --- /dev/null +++ b/platforms/Indigo.js @@ -0,0 +1,502 @@ +var types = require("HAP-NodeJS/accessories/types.js"); +var Characteristic = require("HAP-NodeJS").Characteristic; +var request = require('request'); +var async = require('async'); + + +function IndigoPlatform(log, config) { + this.log = log; + + this.baseURL = "http://" + config["host"] + ":" + config["port"]; + + if (config["username"] && config["password"]) { + this.auth = { + 'user': config["username"], + 'pass': config["password"], + 'sendImmediately': false + }; + } +} + +IndigoPlatform.prototype = { + accessories: function(callback) { + var that = this; + this.log("Discovering Indigo Devices."); + + var options = { + url: this.baseURL + "/devices.json/", + method: 'GET' + }; + if (this.auth) { + options['auth'] = this.auth; + } + this.foundAccessories = []; + this.callback = callback; + + request(options, function(error, response, body) { + if (error) { + console.trace("Requesting Indigo devices."); + that.log(error); + return error; + } + + // Cheesy hack because response may have an extra comma at the start of the array, which is invalid + var firstComma = body.indexOf(","); + if (firstComma < 10) { + body = "[" + body.substr(firstComma + 1); + } + + var json = JSON.parse(body); + async.each(json, function(item, asyncCallback) { + var deviceURL = that.baseURL + item.restURL; + var deviceOptions = { + url: deviceURL, + method: 'GET' + }; + if (that.auth) { + deviceOptions['auth'] = that.auth; + } + + request(deviceOptions, function(deviceError, deviceResponse, deviceBody) { + if (deviceError) { + asyncCallback(deviceError); + } + + var deviceJson = JSON.parse(deviceBody); + that.log("Discovered " + deviceJson.type + ": " + deviceJson.name); + that.foundAccessories.push( + new IndigoAccessory(that.log, that.auth, deviceURL, deviceJson)); + asyncCallback(); + }); + }, function(asyncError) { + // This will be called after all the requests complete + if (asyncError) { + console.trace("Requesting Indigo device info."); + that.log(asyncError); + } else { + that.callback(that.foundAccessories.sort(function (a,b) { + return (a.name > b.name) - (a.name < b.name); + })); + } + }); + }); + } +} + + +function IndigoAccessory(log, auth, deviceURL, json) { + this.log = log; + this.auth = auth; + this.deviceURL = deviceURL; + + for (var prop in json) { + if (json.hasOwnProperty(prop)) { + this[prop] = json[prop]; + } + } +} + +IndigoAccessory.prototype = { + getStatus: function(callback) { + var that = this; + + var options = { + url: this.deviceURL, + method: 'GET' + }; + if (this.auth) { + options['auth'] = this.auth; + } + + request(options, function(error, response, body) { + if (error) { + console.trace("Requesting Device Status."); + that.log(error); + return error; + } + + that.log("getStatus of " + that.name + ": " + body); + callback(JSON.parse(body)); + }); + }, + + updateStatus: function(params) { + var that = this; + var options = { + url: this.deviceURL + "?" + params, + method: 'PUT' + }; + if (this.auth) { + options['auth'] = this.auth; + } + + this.log("updateStatus of " + that.name + ": " + params); + request(options, function(error, response, body) { + if (error) { + console.trace("Updating Device Status."); + that.log(error); + return error; + } + }); + }, + + query: function(prop, callback) { + this.getStatus(function(json) { + callback(json[prop]); + }); + }, + + turnOn: function() { + if (this.typeSupportsOnOff) { + this.updateStatus("isOn=1"); + } + }, + + turnOff: function() { + if (this.typeSupportsOnOff) { + this.updateStatus("isOn=0"); + } + }, + + setBrightness: function(brightness) { + if (this.typeSupportsDim && brightness >= 0 && brightness <= 100) { + this.updateStatus("brightness=" + brightness); + } + }, + + setSpeedIndex: function(speedIndex) { + if (this.typeSupportsSpeedControl && speedIndex >= 0 && speedIndex <= 3) { + this.updateStatus("speedIndex=" + speedIndex); + } + }, + + getCurrentHeatingCooling: function(callback) { + this.getStatus(function(json) { + var mode = 0; + if (json["hvacOperatonModeIsHeat"]) { + mode = 1; + } + else if (json["hvacOperationModeIsCool"]) { + mode = 2; + } + else if (json["hvacOperationModeIsAuto"]) { + mode = 3; + } + callback(mode); + }); + }, + + setTargetHeatingCooling: function(mode) { + if (mode == 0) { + param = "Off"; + } + else if (mode == 1) { + param = "Heat"; + } + else if (mode == 2) { + param = "Cool"; + } + else if (mode == 3) { + param = "Auto"; + } + + if (param) { + this.updateStatus("hvacOperationModeIs" + param + "=true"); + } + }, + + getTargetTemperature: function(callback) { + this.getStatus(function(json) { + var result; + if (json["hvacOperatonModeIsHeat"]) { + result = json["setpointHeat"]; + } + else if (json["hvacOperationModeIsCool"]) { + result = json["setpointCool"]; + } + else { + result = (json["setpointHeat"] + json["setpointCool"]) / 2; + } + callback(result); + }); + }, + + setTargetTemperature: function(temperature) { + var that = this; + this.getStatus(function(json) { + if (json["hvacOperatonModeIsHeat"]) { + that.updateStatus("setpointHeat=" + temperature); + } + else if (json["hvacOperationModeIsCool"]) { + that.updateStatus("setpointCool=" + temperature); + } + else { + var cool = temperature + 5; + var heat = temperature - 5; + that.updateStatus("setpointCool=" + cool + "&setpointHeat=" + heat); + } + }); + }, + + informationCharacteristics: function() { + return [ + { + cType: types.NAME_CTYPE, + onUpdate: null, + perms: [Characteristic.Perms.READ], + format: Characteristic.Formats.STRING, + initialValue: this.name, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of the accessory", + designedMaxLength: 255 + },{ + cType: types.MANUFACTURER_CTYPE, + onUpdate: null, + perms: [Characteristic.Perms.READ], + format: Characteristic.Formats.STRING, + initialValue: "Indigo", + supportEvents: false, + supportBonjour: false, + manfDescription: "Manufacturer", + designedMaxLength: 255 + },{ + cType: types.MODEL_CTYPE, + onUpdate: null, + perms: [Characteristic.Perms.READ], + format: Characteristic.Formats.STRING, + initialValue: this.type, + supportEvents: false, + supportBonjour: false, + manfDescription: "Model", + designedMaxLength: 255 + },{ + cType: types.SERIAL_NUMBER_CTYPE, + onUpdate: null, + perms: [Characteristic.Perms.READ], + format: Characteristic.Formats.STRING, + initialValue: this.addressStr, + supportEvents: false, + supportBonjour: false, + manfDescription: "SN", + designedMaxLength: 255 + },{ + cType: types.IDENTIFY_CTYPE, + onUpdate: null, + perms: [Characteristic.Perms.WRITE], + format: Characteristic.Formats.BOOL, + initialValue: false, + supportEvents: false, + supportBonjour: false, + manfDescription: "Identify Accessory", + designedMaxLength: 1 + } + ] + }, + + controlCharacteristics: function(that) { + var cTypes = [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: [Characteristic.Perms.READ], + format: Characteristic.Formats.STRING, + initialValue: that.name, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of the accessory", + designedMaxLength: 255 + }]; + +/* if (that.typeSupportsOnOff) + { */ + cTypes.push({ + cType: types.POWER_STATE_CTYPE, + perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.BOOL, + initialValue: (that.isOn) ? 1 : 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Change the power state", + designedMaxLength: 1, + onUpdate: function(value) { + if (value == 0) { + that.turnOff(); + } else { + that.turnOn(); + } + }, + onRead: function(callback) { + that.query("isOn", function(isOn) { + callback((isOn) ? 1 : 0); + }); + } + }); +// } + if (that.typeSupportsDim) + { + cTypes.push({ + cType: types.BRIGHTNESS_CTYPE, + perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.INT, + initialValue: that.brightness, + supportEvents: true, + supportBonjour: false, + manfDescription: "Adjust Brightness of Light", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: Characteristic.Units.PERCENTAGE, + onUpdate: function(value) { + that.setBrightness(value); + }, + onRead: function(callback) { + that.query("brightness", callback); + } + }); + } + if (that.typeSupportsSpeedControl) + { + cTypes.push({ + cType: types.ROTATION_SPEED_CTYPE, + perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.INT, + initialValue: 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Change the speed of the fan", + designedMaxLength: 1, + designedMinValue: 0, + designedMaxValue: 3, + designedMinStep: 1, + onUpdate: function(value) { + that.setSpeedIndex(value); + }, + onRead: function(callback) { + that.query("speedIndex", callback); + } + }); + } + if (that.typeSupportsHVAC) + { + cTypes.push({ + cType: types.CURRENTHEATINGCOOLING_CTYPE, + perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.INT, + initialValue: 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Current Mode", + designedMaxLength: 1, + designedMinValue: 0, + designedMaxValue: 3, + designedMinStep: 1, + onUpdate: null, + onRead: function(callback) { + that.getCurrentHeatingCooling(callback); + } + }); + cTypes.push({ + cType: types.TARGETHEATINGCOOLING_CTYPE, + perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.INT, + initialValue: 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Target Mode", + designedMaxLength: 1, + designedMinValue: 0, + designedMaxValue: 3, + designedMinStep: 1, + onUpdate: function(value) { + that.setTargetHeatingCooling(value); + }, + onRead: function(callback) { + that.getCurrentHeatingCooling(callback); + } + }); + cTypes.push({ + cType: types.CURRENT_TEMPERATURE_CTYPE, + perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.INT, + designedMinValue: 0, + designedMaxValue: 110, + designedMinStep: 1, + initialValue: 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Current Temperature", + unit: Characteristic.Units.FAHRENHEIT, + onUpdate: null, + onRead: function(callback) { + that.query("displayRawState", callback); + } + }); + cTypes.push({ + cType: types.TARGET_TEMPERATURE_CTYPE, + perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.INT, + designedMinValue: 0, + designedMaxValue: 110, + designedMinStep: 1, + initialValue: 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Target Temperature", + unit: Characteristic.Units.FAHRENHEIT, + onUpdate: function(value) { + that.setTargetTemperature(value); + }, + onRead: function(callback) { + that.getTargetTemperature(callback); + } + }); + cTypes.push({ + cType: types.TEMPERATURE_UNITS_CTYPE, + perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.INT, + initialValue: 1, + supportEvents: false, + supportBonjour: false, + manfDescription: "Unit", + onUpdate: null, + onRead: function(callback) { + callback(Characteristic.Units.FAHRENHEIT); + } + }); + } + + return cTypes; + }, + + sType: function() { + if (this.typeSupportsHVAC) { + return types.THERMOSTAT_STYPE; + } else if (this.typeSupportsDim) { + return types.LIGHTBULB_STYPE; + } else if (this.typeSupportsSpeedControl) { + return types.FAN_STYPE; + } else if (this.typeSupportsOnOff) { + return types.SWITCH_STYPE; + } + + return types.SWITCH_STYPE; + }, + + getServices: function() { + var that = this; + var services = [{ + sType: types.ACCESSORY_INFORMATION_STYPE, + characteristics: that.informationCharacteristics(), + }, + { + sType: that.sType(), + characteristics: that.controlCharacteristics(that) + }]; + + that.log("Loaded services for " + that.name); + return services; + } +}; + +module.exports.accessory = IndigoAccessory; +module.exports.platform = IndigoPlatform; From 9ad8a111c68b342069cfbcd1e0ff1bf1090022c4 Mon Sep 17 00:00:00 2001 From: Mike Riccio Date: Thu, 15 Oct 2015 15:07:14 -0700 Subject: [PATCH 080/121] Added intro comment. --- platforms/Indigo.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/platforms/Indigo.js b/platforms/Indigo.js index e42849a..f77813a 100644 --- a/platforms/Indigo.js +++ b/platforms/Indigo.js @@ -1,3 +1,24 @@ +// Indigo Platform Shim for HomeBridge +// Written by Mike Riccio (https://github.com/webdeck) +// Based on many of the other HomeBridge plartform modules +// See http://www.indigodomo.com/ for more info on Indigo +// +// Remember to add platform to config.json. Example: +// "platforms": [ +// { +// "platform": "Indigo", // required +// "name": "Indigo", // required +// "host": "127.0.0.1", // required +// "port": "8176", // required +// "username": "username", // optional +// "password": "password" // optional +// } +// ], +// +// 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 Characteristic = require("HAP-NodeJS").Characteristic; var request = require('request'); From 6fa69c5c4bb17cfbae3607cc8ed7eaa92d4f43e0 Mon Sep 17 00:00:00 2001 From: Mike Riccio Date: Thu, 15 Oct 2015 16:47:59 -0700 Subject: [PATCH 081/121] Removed tabs --- platforms/Indigo.js | 436 ++++++++++++++++++++++---------------------- 1 file changed, 218 insertions(+), 218 deletions(-) diff --git a/platforms/Indigo.js b/platforms/Indigo.js index f77813a..1332004 100644 --- a/platforms/Indigo.js +++ b/platforms/Indigo.js @@ -8,10 +8,10 @@ // { // "platform": "Indigo", // required // "name": "Indigo", // required -// "host": "127.0.0.1", // required -// "port": "8176", // required -// "username": "username", // optional -// "password": "password" // optional +// "host": "127.0.0.1", // required +// "port": "8176", // required +// "username": "username", // optional +// "password": "password" // optional // } // ], // @@ -28,19 +28,19 @@ var async = require('async'); function IndigoPlatform(log, config) { this.log = log; - this.baseURL = "http://" + config["host"] + ":" + config["port"]; + this.baseURL = "http://" + config["host"] + ":" + config["port"]; - if (config["username"] && config["password"]) { - this.auth = { - 'user': config["username"], - 'pass': config["password"], - 'sendImmediately': false - }; - } + if (config["username"] && config["password"]) { + this.auth = { + 'user': config["username"], + 'pass': config["password"], + 'sendImmediately': false + }; + } } IndigoPlatform.prototype = { - accessories: function(callback) { + accessories: function(callback) { var that = this; this.log("Discovering Indigo Devices."); @@ -48,11 +48,11 @@ IndigoPlatform.prototype = { url: this.baseURL + "/devices.json/", method: 'GET' }; - if (this.auth) { - options['auth'] = this.auth; - } + if (this.auth) { + options['auth'] = this.auth; + } this.foundAccessories = []; - this.callback = callback; + this.callback = callback; request(options, function(error, response, body) { if (error) { @@ -61,73 +61,73 @@ IndigoPlatform.prototype = { return error; } - // Cheesy hack because response may have an extra comma at the start of the array, which is invalid - var firstComma = body.indexOf(","); - if (firstComma < 10) { - body = "[" + body.substr(firstComma + 1); - } + // Cheesy hack because response may have an extra comma at the start of the array, which is invalid + var firstComma = body.indexOf(","); + if (firstComma < 10) { + body = "[" + body.substr(firstComma + 1); + } - var json = JSON.parse(body); - async.each(json, function(item, asyncCallback) { - var deviceURL = that.baseURL + item.restURL; - var deviceOptions = { - url: deviceURL, - method: 'GET' - }; - if (that.auth) { - deviceOptions['auth'] = that.auth; - } + var json = JSON.parse(body); + async.each(json, function(item, asyncCallback) { + var deviceURL = that.baseURL + item.restURL; + var deviceOptions = { + url: deviceURL, + method: 'GET' + }; + if (that.auth) { + deviceOptions['auth'] = that.auth; + } - request(deviceOptions, function(deviceError, deviceResponse, deviceBody) { - if (deviceError) { - asyncCallback(deviceError); - } + request(deviceOptions, function(deviceError, deviceResponse, deviceBody) { + if (deviceError) { + asyncCallback(deviceError); + } - var deviceJson = JSON.parse(deviceBody); - that.log("Discovered " + deviceJson.type + ": " + deviceJson.name); - that.foundAccessories.push( - new IndigoAccessory(that.log, that.auth, deviceURL, deviceJson)); - asyncCallback(); - }); - }, function(asyncError) { - // This will be called after all the requests complete - if (asyncError) { - console.trace("Requesting Indigo device info."); - that.log(asyncError); - } else { - that.callback(that.foundAccessories.sort(function (a,b) { - return (a.name > b.name) - (a.name < b.name); - })); - } - }); - }); - } + var deviceJson = JSON.parse(deviceBody); + that.log("Discovered " + deviceJson.type + ": " + deviceJson.name); + that.foundAccessories.push( + new IndigoAccessory(that.log, that.auth, deviceURL, deviceJson)); + asyncCallback(); + }); + }, function(asyncError) { + // This will be called after all the requests complete + if (asyncError) { + console.trace("Requesting Indigo device info."); + that.log(asyncError); + } else { + that.callback(that.foundAccessories.sort(function (a,b) { + return (a.name > b.name) - (a.name < b.name); + })); + } + }); + }); + } } function IndigoAccessory(log, auth, deviceURL, json) { this.log = log; - this.auth = auth; + this.auth = auth; this.deviceURL = deviceURL; - for (var prop in json) { - if (json.hasOwnProperty(prop)) { - this[prop] = json[prop]; - } - } + for (var prop in json) { + if (json.hasOwnProperty(prop)) { + this[prop] = json[prop]; + } + } } IndigoAccessory.prototype = { getStatus: function(callback) { - var that = this; + var that = this; var options = { - url: this.deviceURL, + url: this.deviceURL, method: 'GET' }; - if (this.auth) { - options['auth'] = this.auth; - } + if (this.auth) { + options['auth'] = this.auth; + } request(options, function(error, response, body) { if (error) { @@ -136,22 +136,22 @@ IndigoAccessory.prototype = { return error; } - that.log("getStatus of " + that.name + ": " + body); - callback(JSON.parse(body)); + that.log("getStatus of " + that.name + ": " + body); + callback(JSON.parse(body)); }); }, updateStatus: function(params) { - var that = this; + var that = this; var options = { - url: this.deviceURL + "?" + params, + url: this.deviceURL + "?" + params, method: 'PUT' }; - if (this.auth) { - options['auth'] = this.auth; - } + if (this.auth) { + options['auth'] = this.auth; + } - this.log("updateStatus of " + that.name + ": " + params); + this.log("updateStatus of " + that.name + ": " + params); request(options, function(error, response, body) { if (error) { console.trace("Updating Device Status."); @@ -162,101 +162,101 @@ IndigoAccessory.prototype = { }, query: function(prop, callback) { - this.getStatus(function(json) { - callback(json[prop]); - }); + this.getStatus(function(json) { + callback(json[prop]); + }); }, - turnOn: function() { - if (this.typeSupportsOnOff) { - this.updateStatus("isOn=1"); - } - }, - - turnOff: function() { - if (this.typeSupportsOnOff) { - this.updateStatus("isOn=0"); - } - }, + turnOn: function() { + if (this.typeSupportsOnOff) { + this.updateStatus("isOn=1"); + } + }, + + turnOff: function() { + if (this.typeSupportsOnOff) { + this.updateStatus("isOn=0"); + } + }, - setBrightness: function(brightness) { - if (this.typeSupportsDim && brightness >= 0 && brightness <= 100) { - this.updateStatus("brightness=" + brightness); - } - }, - - setSpeedIndex: function(speedIndex) { - if (this.typeSupportsSpeedControl && speedIndex >= 0 && speedIndex <= 3) { - this.updateStatus("speedIndex=" + speedIndex); - } - }, - + setBrightness: function(brightness) { + if (this.typeSupportsDim && brightness >= 0 && brightness <= 100) { + this.updateStatus("brightness=" + brightness); + } + }, + + setSpeedIndex: function(speedIndex) { + if (this.typeSupportsSpeedControl && speedIndex >= 0 && speedIndex <= 3) { + this.updateStatus("speedIndex=" + speedIndex); + } + }, + getCurrentHeatingCooling: function(callback) { - this.getStatus(function(json) { - var mode = 0; - if (json["hvacOperatonModeIsHeat"]) { - mode = 1; - } - else if (json["hvacOperationModeIsCool"]) { - mode = 2; - } - else if (json["hvacOperationModeIsAuto"]) { - mode = 3; - } - callback(mode); - }); + this.getStatus(function(json) { + var mode = 0; + if (json["hvacOperatonModeIsHeat"]) { + mode = 1; + } + else if (json["hvacOperationModeIsCool"]) { + mode = 2; + } + else if (json["hvacOperationModeIsAuto"]) { + mode = 3; + } + callback(mode); + }); }, setTargetHeatingCooling: function(mode) { - if (mode == 0) { - param = "Off"; - } - else if (mode == 1) { - param = "Heat"; - } - else if (mode == 2) { - param = "Cool"; - } - else if (mode == 3) { - param = "Auto"; - } + if (mode == 0) { + param = "Off"; + } + else if (mode == 1) { + param = "Heat"; + } + else if (mode == 2) { + param = "Cool"; + } + else if (mode == 3) { + param = "Auto"; + } - if (param) { - this.updateStatus("hvacOperationModeIs" + param + "=true"); - } + if (param) { + this.updateStatus("hvacOperationModeIs" + param + "=true"); + } }, getTargetTemperature: function(callback) { - this.getStatus(function(json) { - var result; - if (json["hvacOperatonModeIsHeat"]) { - result = json["setpointHeat"]; - } - else if (json["hvacOperationModeIsCool"]) { - result = json["setpointCool"]; - } - else { - result = (json["setpointHeat"] + json["setpointCool"]) / 2; - } - callback(result); - }); + this.getStatus(function(json) { + var result; + if (json["hvacOperatonModeIsHeat"]) { + result = json["setpointHeat"]; + } + else if (json["hvacOperationModeIsCool"]) { + result = json["setpointCool"]; + } + else { + result = (json["setpointHeat"] + json["setpointCool"]) / 2; + } + callback(result); + }); }, setTargetTemperature: function(temperature) { - var that = this; - this.getStatus(function(json) { - if (json["hvacOperatonModeIsHeat"]) { - that.updateStatus("setpointHeat=" + temperature); - } - else if (json["hvacOperationModeIsCool"]) { - that.updateStatus("setpointCool=" + temperature); - } - else { - var cool = temperature + 5; - var heat = temperature - 5; - that.updateStatus("setpointCool=" + cool + "&setpointHeat=" + heat); - } - }); + var that = this; + this.getStatus(function(json) { + if (json["hvacOperatonModeIsHeat"]) { + that.updateStatus("setpointHeat=" + temperature); + } + else if (json["hvacOperationModeIsCool"]) { + that.updateStatus("setpointCool=" + temperature); + } + else { + var cool = temperature + 5; + var heat = temperature - 5; + that.updateStatus("setpointCool=" + cool + "&setpointHeat=" + heat); + } + }); }, informationCharacteristics: function() { @@ -316,7 +316,7 @@ IndigoAccessory.prototype = { }, controlCharacteristics: function(that) { - var cTypes = [{ + var cTypes = [{ cType: types.NAME_CTYPE, onUpdate: null, perms: [Characteristic.Perms.READ], @@ -326,15 +326,15 @@ IndigoAccessory.prototype = { supportBonjour: false, manfDescription: "Name of the accessory", designedMaxLength: 255 - }]; + }]; /* if (that.typeSupportsOnOff) - { */ + { */ cTypes.push({ cType: types.POWER_STATE_CTYPE, perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], format: Characteristic.Formats.BOOL, - initialValue: (that.isOn) ? 1 : 0, + initialValue: (that.isOn) ? 1 : 0, supportEvents: true, supportBonjour: false, manfDescription: "Change the power state", @@ -347,14 +347,14 @@ IndigoAccessory.prototype = { } }, onRead: function(callback) { - that.query("isOn", function(isOn) { - callback((isOn) ? 1 : 0); - }); + that.query("isOn", function(isOn) { + callback((isOn) ? 1 : 0); + }); } }); -// } - if (that.typeSupportsDim) - { +// } + if (that.typeSupportsDim) + { cTypes.push({ cType: types.BRIGHTNESS_CTYPE, perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], @@ -368,10 +368,10 @@ IndigoAccessory.prototype = { designedMinStep: 1, unit: Characteristic.Units.PERCENTAGE, onUpdate: function(value) { - that.setBrightness(value); + that.setBrightness(value); }, onRead: function(callback) { - that.query("brightness", callback); + that.query("brightness", callback); } }); } @@ -386,11 +386,11 @@ IndigoAccessory.prototype = { supportBonjour: false, manfDescription: "Change the speed of the fan", designedMaxLength: 1, - designedMinValue: 0, - designedMaxValue: 3, - designedMinStep: 1, + designedMinValue: 0, + designedMaxValue: 3, + designedMinStep: 1, onUpdate: function(value) { - that.setSpeedIndex(value); + that.setSpeedIndex(value); }, onRead: function(callback) { that.query("speedIndex", callback); @@ -400,44 +400,44 @@ IndigoAccessory.prototype = { if (that.typeSupportsHVAC) { cTypes.push({ - cType: types.CURRENTHEATINGCOOLING_CTYPE, - perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], - format: Characteristic.Formats.INT, - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Current Mode", - designedMaxLength: 1, - designedMinValue: 0, - designedMaxValue: 3, - designedMinStep: 1, - onUpdate: null, - onRead: function(callback) { - that.getCurrentHeatingCooling(callback); - } + cType: types.CURRENTHEATINGCOOLING_CTYPE, + perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.INT, + initialValue: 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Current Mode", + designedMaxLength: 1, + designedMinValue: 0, + designedMaxValue: 3, + designedMinStep: 1, + onUpdate: null, + onRead: function(callback) { + that.getCurrentHeatingCooling(callback); + } }); cTypes.push({ - cType: types.TARGETHEATINGCOOLING_CTYPE, - perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], - format: Characteristic.Formats.INT, - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Target Mode", - designedMaxLength: 1, - designedMinValue: 0, - designedMaxValue: 3, - designedMinStep: 1, - onUpdate: function(value) { - that.setTargetHeatingCooling(value); - }, - onRead: function(callback) { - that.getCurrentHeatingCooling(callback); - } + cType: types.TARGETHEATINGCOOLING_CTYPE, + perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.INT, + initialValue: 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Target Mode", + designedMaxLength: 1, + designedMinValue: 0, + designedMaxValue: 3, + designedMinStep: 1, + onUpdate: function(value) { + that.setTargetHeatingCooling(value); + }, + onRead: function(callback) { + that.getCurrentHeatingCooling(callback); + } }); cTypes.push({ - cType: types.CURRENT_TEMPERATURE_CTYPE, - perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + cType: types.CURRENT_TEMPERATURE_CTYPE, + perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], format: Characteristic.Formats.INT, designedMinValue: 0, designedMaxValue: 110, @@ -446,11 +446,11 @@ IndigoAccessory.prototype = { supportEvents: true, supportBonjour: false, manfDescription: "Current Temperature", - unit: Characteristic.Units.FAHRENHEIT, - onUpdate: null, + unit: Characteristic.Units.FAHRENHEIT, + onUpdate: null, onRead: function(callback) { that.query("displayRawState", callback); - } + } }); cTypes.push({ cType: types.TARGET_TEMPERATURE_CTYPE, @@ -463,12 +463,12 @@ IndigoAccessory.prototype = { supportEvents: true, supportBonjour: false, manfDescription: "Target Temperature", - unit: Characteristic.Units.FAHRENHEIT, - onUpdate: function(value) { + unit: Characteristic.Units.FAHRENHEIT, + onUpdate: function(value) { that.setTargetTemperature(value); }, onRead: function(callback) { - that.getTargetTemperature(callback); + that.getTargetTemperature(callback); } }); cTypes.push({ @@ -479,9 +479,9 @@ IndigoAccessory.prototype = { supportEvents: false, supportBonjour: false, manfDescription: "Unit", - onUpdate: null, + onUpdate: null, onRead: function(callback) { - callback(Characteristic.Units.FAHRENHEIT); + callback(Characteristic.Units.FAHRENHEIT); } }); } @@ -490,13 +490,13 @@ IndigoAccessory.prototype = { }, sType: function() { - if (this.typeSupportsHVAC) { - return types.THERMOSTAT_STYPE; - } else if (this.typeSupportsDim) { + if (this.typeSupportsHVAC) { + return types.THERMOSTAT_STYPE; + } else if (this.typeSupportsDim) { return types.LIGHTBULB_STYPE; } else if (this.typeSupportsSpeedControl) { return types.FAN_STYPE; - } else if (this.typeSupportsOnOff) { + } else if (this.typeSupportsOnOff) { return types.SWITCH_STYPE; } @@ -504,7 +504,7 @@ IndigoAccessory.prototype = { }, getServices: function() { - var that = this; + var that = this; var services = [{ sType: types.ACCESSORY_INFORMATION_STYPE, characteristics: that.informationCharacteristics(), From bab1eb730e332b1e946ccc70709554d74a106447 Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Thu, 15 Oct 2015 20:08:25 -0700 Subject: [PATCH 082/121] Version bump to node-icontrol --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8f88a35..feb0370 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "lifx": "git+https://github.com/magicmonkey/lifxjs.git", "mdns": "^2.2.4", "node-hue-api": "^1.0.5", - "node-icontrol": "^0.1.4", + "node-icontrol": "^0.1.5", "node-milight-promise": "0.0.x", "node-persist": "0.0.x", "q": "1.4.x", From aad811fe6e794b37e253c4bc39e94bef77a559a6 Mon Sep 17 00:00:00 2001 From: Mike Riccio Date: Thu, 15 Oct 2015 21:31:08 -0700 Subject: [PATCH 083/121] Fixed thermostats to report celsius. --- platforms/Indigo.js | 101 ++++++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 46 deletions(-) diff --git a/platforms/Indigo.js b/platforms/Indigo.js index 1332004..98f259c 100644 --- a/platforms/Indigo.js +++ b/platforms/Indigo.js @@ -226,34 +226,42 @@ IndigoAccessory.prototype = { } }, + // Note: HomeKit wants all temperature values to be in celsius + getCurrentTemperature: function(callback) { + this.query("displayRawState", function(temperature) { + callback((temperature - 32.0) * 5.0 / 9.0); + }); + }, + getTargetTemperature: function(callback) { this.getStatus(function(json) { - var result; + var temperature; if (json["hvacOperatonModeIsHeat"]) { - result = json["setpointHeat"]; + temperature = json["setpointHeat"]; } else if (json["hvacOperationModeIsCool"]) { - result = json["setpointCool"]; + temperature = json["setpointCool"]; } else { - result = (json["setpointHeat"] + json["setpointCool"]) / 2; + temperature = (json["setpointHeat"] + json["setpointCool"]) / 2.0; } - callback(result); + callback((temperature - 32.0) * 5.0 / 9.0); }); }, setTargetTemperature: function(temperature) { var that = this; + var t = (temperature * 9.0 / 5.0) + 32.0; this.getStatus(function(json) { if (json["hvacOperatonModeIsHeat"]) { - that.updateStatus("setpointHeat=" + temperature); + that.updateStatus("setpointHeat=" + t); } else if (json["hvacOperationModeIsCool"]) { - that.updateStatus("setpointCool=" + temperature); + that.updateStatus("setpointCool=" + t); } else { - var cool = temperature + 5; - var heat = temperature - 5; + var cool = t + 5; + var heat = t - 5; that.updateStatus("setpointCool=" + cool + "&setpointHeat=" + heat); } }); @@ -328,39 +336,36 @@ IndigoAccessory.prototype = { designedMaxLength: 255 }]; -/* if (that.typeSupportsOnOff) - { */ - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], - format: Characteristic.Formats.BOOL, - initialValue: (that.isOn) ? 1 : 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1, - onUpdate: function(value) { - if (value == 0) { - that.turnOff(); - } else { - that.turnOn(); - } - }, - onRead: function(callback) { - that.query("isOn", function(isOn) { - callback((isOn) ? 1 : 0); - }); + cTypes.push({ + cType: types.POWER_STATE_CTYPE, + perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.BOOL, + initialValue: (that.isOn) ? 1 : 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Change the power state", + designedMaxLength: 1, + onUpdate: function(value) { + if (value == 0) { + that.turnOff(); + } else { + that.turnOn(); } - }); -// } - if (that.typeSupportsDim) - { + }, + onRead: function(callback) { + that.query("isOn", function(isOn) { + callback((isOn) ? 1 : 0); + }); + } + }); + + if (that.typeSupportsDim) { cTypes.push({ cType: types.BRIGHTNESS_CTYPE, perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], format: Characteristic.Formats.INT, initialValue: that.brightness, - supportEvents: true, + supportEvents: false, supportBonjour: false, manfDescription: "Adjust Brightness of Light", designedMinValue: 0, @@ -375,14 +380,14 @@ IndigoAccessory.prototype = { } }); } - if (that.typeSupportsSpeedControl) - { + + if (that.typeSupportsSpeedControl) { cTypes.push({ cType: types.ROTATION_SPEED_CTYPE, perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], format: Characteristic.Formats.INT, initialValue: 0, - supportEvents: true, + supportEvents: false, supportBonjour: false, manfDescription: "Change the speed of the fan", designedMaxLength: 1, @@ -397,14 +402,14 @@ IndigoAccessory.prototype = { } }); } - if (that.typeSupportsHVAC) - { + + if (that.typeSupportsHVAC) { cTypes.push({ cType: types.CURRENTHEATINGCOOLING_CTYPE, perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], format: Characteristic.Formats.INT, initialValue: 0, - supportEvents: true, + supportEvents: false, supportBonjour: false, manfDescription: "Current Mode", designedMaxLength: 1, @@ -416,12 +421,13 @@ IndigoAccessory.prototype = { that.getCurrentHeatingCooling(callback); } }); + cTypes.push({ cType: types.TARGETHEATINGCOOLING_CTYPE, perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], format: Characteristic.Formats.INT, initialValue: 0, - supportEvents: true, + supportEvents: false, supportBonjour: false, manfDescription: "Target Mode", designedMaxLength: 1, @@ -435,6 +441,7 @@ IndigoAccessory.prototype = { that.getCurrentHeatingCooling(callback); } }); + cTypes.push({ cType: types.CURRENT_TEMPERATURE_CTYPE, perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], @@ -443,15 +450,16 @@ IndigoAccessory.prototype = { designedMaxValue: 110, designedMinStep: 1, initialValue: 0, - supportEvents: true, + supportEvents: false, supportBonjour: false, manfDescription: "Current Temperature", unit: Characteristic.Units.FAHRENHEIT, onUpdate: null, onRead: function(callback) { - that.query("displayRawState", callback); + that.getCurrentTemperature(callback); } }); + cTypes.push({ cType: types.TARGET_TEMPERATURE_CTYPE, perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], @@ -460,7 +468,7 @@ IndigoAccessory.prototype = { designedMaxValue: 110, designedMinStep: 1, initialValue: 0, - supportEvents: true, + supportEvents: false, supportBonjour: false, manfDescription: "Target Temperature", unit: Characteristic.Units.FAHRENHEIT, @@ -471,6 +479,7 @@ IndigoAccessory.prototype = { that.getTargetTemperature(callback); } }); + cTypes.push({ cType: types.TEMPERATURE_UNITS_CTYPE, perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], From 7233c5bf7454008a45fd4c29219e09fec2f4b5cb Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Fri, 16 Oct 2015 10:50:55 +0200 Subject: [PATCH 084/121] Support for case-sensitive file systems and current HAP-NodeJS with nodejs 4 support --- accessories/FileSensor.js | 4 ++-- accessories/GenericRS232Device.js | 4 ++-- accessories/HomeMaticWindow.js | 2 +- accessories/Http.js | 4 ++-- accessories/HttpHygrometer.js | 4 ++-- accessories/HttpThermometer.js | 4 ++-- accessories/Lockitron.js | 4 ++-- accessories/WeMo.js | 4 ++-- accessories/iControl.js | 4 ++-- accessories/knxdevice.js | 4 ++-- accessories/mpdclient.js | 4 ++-- app.js | 14 +++++++------- package.json | 2 +- platforms/FHEM.js | 4 ++-- platforms/FibaroHC2.js | 4 ++-- platforms/HomeAssistant.js | 4 ++-- platforms/HomeSeer.js | 4 ++-- platforms/LIFx.js | 4 ++-- platforms/MiLight.js | 4 ++-- platforms/YamahaAVR.js | 4 ++-- platforms/ZWayServer.js | 4 ++-- 21 files changed, 45 insertions(+), 45 deletions(-) diff --git a/accessories/FileSensor.js b/accessories/FileSensor.js index e377dc6..112089e 100644 --- a/accessories/FileSensor.js +++ b/accessories/FileSensor.js @@ -1,5 +1,5 @@ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var chokidar = require("chokidar"); var debug = require("debug")("FileSensorAccessory"); var crypto = require("crypto"); diff --git a/accessories/GenericRS232Device.js b/accessories/GenericRS232Device.js index b84e4cc..01fe80c 100644 --- a/accessories/GenericRS232Device.js +++ b/accessories/GenericRS232Device.js @@ -1,5 +1,5 @@ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var SerialPort = require("serialport").SerialPort; module.exports = { diff --git a/accessories/HomeMaticWindow.js b/accessories/HomeMaticWindow.js index b7e585d..865b24a 100644 --- a/accessories/HomeMaticWindow.js +++ b/accessories/HomeMaticWindow.js @@ -1,5 +1,5 @@ var types = require("HAP-NodeJS/accessories/types.js"); -var Characteristic = require("HAP-NodeJS").Characteristic; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); function HomeMaticWindow(log, config) { diff --git a/accessories/Http.js b/accessories/Http.js index e1859cf..fb708e3 100644 --- a/accessories/Http.js +++ b/accessories/Http.js @@ -1,5 +1,5 @@ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); module.exports = { diff --git a/accessories/HttpHygrometer.js b/accessories/HttpHygrometer.js index 61ad3b9..f722fe8 100644 --- a/accessories/HttpHygrometer.js +++ b/accessories/HttpHygrometer.js @@ -1,5 +1,5 @@ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); module.exports = { diff --git a/accessories/HttpThermometer.js b/accessories/HttpThermometer.js index ac9bdc2..9235a57 100644 --- a/accessories/HttpThermometer.js +++ b/accessories/HttpThermometer.js @@ -1,5 +1,5 @@ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); module.exports = { diff --git a/accessories/Lockitron.js b/accessories/Lockitron.js index 8088d14..ed95b7e 100644 --- a/accessories/Lockitron.js +++ b/accessories/Lockitron.js @@ -1,5 +1,5 @@ -var Service = require('HAP-NodeJS').Service; -var Characteristic = require('HAP-NodeJS').Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); module.exports = { diff --git a/accessories/WeMo.js b/accessories/WeMo.js index b97b041..5cbc3f4 100644 --- a/accessories/WeMo.js +++ b/accessories/WeMo.js @@ -1,5 +1,5 @@ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var wemo = require('wemo'); module.exports = { diff --git a/accessories/iControl.js b/accessories/iControl.js index d948867..a4299b5 100644 --- a/accessories/iControl.js +++ b/accessories/iControl.js @@ -1,6 +1,6 @@ var iControl = require('node-icontrol').iControl; -var Service = require('HAP-NodeJS').Service; -var Characteristic = require('HAP-NodeJS').Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; module.exports = { accessory: iControlAccessory diff --git a/accessories/knxdevice.js b/accessories/knxdevice.js index 6aa4ffd..92bb88c 100644 --- a/accessories/knxdevice.js +++ b/accessories/knxdevice.js @@ -21,8 +21,8 @@ New 2015-10-07: - Accept uuid_base parameter from config.json to use as unique identifier in UUIDs instead of name (optional) * */ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var knxd = require("eibd"); var knxd_registerGA = require('../platforms/KNX.js').registerGA; var knxd_startMonitor = require('../platforms/KNX.js').startMonitor; diff --git a/accessories/mpdclient.js b/accessories/mpdclient.js index 8fee5eb..ac6bf5d 100644 --- a/accessories/mpdclient.js +++ b/accessories/mpdclient.js @@ -1,5 +1,5 @@ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); var komponist = require('komponist') diff --git a/app.js b/app.js index 126b7e3..ffc203e 100644 --- a/app.js +++ b/app.js @@ -1,13 +1,13 @@ var fs = require('fs'); var path = require('path'); var storage = require('node-persist'); -var hap = require('HAP-NodeJS'); -var uuid = require('HAP-NodeJS').uuid; -var Bridge = require('HAP-NodeJS').Bridge; -var Accessory = require('HAP-NodeJS').Accessory; -var Service = require('HAP-NodeJS').Service; -var Characteristic = require('HAP-NodeJS').Characteristic; -var accessoryLoader = require('HAP-NodeJS').AccessoryLoader; +var hap = require("hap-nodejs"); +var uuid = require("hap-nodejs").uuid; +var Bridge = require("hap-nodejs").Bridge; +var Accessory = require("hap-nodejs").Accessory; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; +var accessoryLoader = require("hap-nodejs").AccessoryLoader; var once = require('HAP-NodeJS/lib/util/once').once; console.log("Starting HomeBridge server..."); diff --git a/package.json b/package.json index feb0370..95db873 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#4650e771f356a220868d873d16564a6be6603ff7", + "hap-nodejs": "git+https://github.com/KhaosT/HAP-NodeJS#215a3bb1d603097d63ba73d4f7d731813c6b87e5", "harmonyhubjs-client": "^1.1.4", "harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git", "lifx-api": "^1.0.1", diff --git a/platforms/FHEM.js b/platforms/FHEM.js index 03f3ee1..7ef5e37 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -16,8 +16,8 @@ // 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 Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var types = require('HAP-NodeJS/accessories/types.js'); diff --git a/platforms/FibaroHC2.js b/platforms/FibaroHC2.js index 4567b40..8293645 100644 --- a/platforms/FibaroHC2.js +++ b/platforms/FibaroHC2.js @@ -15,8 +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 Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); function FibaroHC2Platform(log, config){ diff --git a/platforms/HomeAssistant.js b/platforms/HomeAssistant.js index 61d3bc2..1bfdc6c 100644 --- a/platforms/HomeAssistant.js +++ b/platforms/HomeAssistant.js @@ -67,8 +67,8 @@ // 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 Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var url = require('url') var request = require("request"); diff --git a/platforms/HomeSeer.js b/platforms/HomeSeer.js index c4ccde7..ac0bfa0 100644 --- a/platforms/HomeSeer.js +++ b/platforms/HomeSeer.js @@ -139,8 +139,8 @@ // - Door -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); diff --git a/platforms/LIFx.js b/platforms/LIFx.js index 79988eb..89de156 100644 --- a/platforms/LIFx.js +++ b/platforms/LIFx.js @@ -16,8 +16,8 @@ // The default code for all HomeBridge accessories is 031-45-154. // -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var lifxRemoteObj = require('lifx-api'); var lifx_remote; diff --git a/platforms/MiLight.js b/platforms/MiLight.js index 3869e74..0325352 100644 --- a/platforms/MiLight.js +++ b/platforms/MiLight.js @@ -47,8 +47,8 @@ TODO: */ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var Milight = require('node-milight-promise').MilightController; var commands = require('node-milight-promise').commands; diff --git a/platforms/YamahaAVR.js b/platforms/YamahaAVR.js index 0dde24f..be73b77 100644 --- a/platforms/YamahaAVR.js +++ b/platforms/YamahaAVR.js @@ -1,8 +1,8 @@ var types = require("HAP-NodeJS/accessories/types.js"); var inherits = require('util').inherits; var debug = require('debug')('YamahaAVR'); -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var Yamaha = require('yamaha-nodejs'); var Q = require('q'); var mdns = require('mdns'); diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index e50336e..9800ab6 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -1,6 +1,6 @@ var debug = require('debug')('ZWayServer'); -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var types = require("HAP-NodeJS/accessories/types.js"); var request = require("request"); var tough = require('tough-cookie'); From 23f190e3d8d82d0663a996d30cca1ec655800ea7 Mon Sep 17 00:00:00 2001 From: stipus Date: Fri, 16 Oct 2015 13:00:18 +0200 Subject: [PATCH 085/121] GarageDoorOpener and Lock support - Smoke sensor battery fix - Added offEventGroup && offEventName to events (turn on launches one HS event. turn off can launch another HS event) - Added GarageDoorOpener support - Added Lock support --- platforms/HomeSeer.js | 253 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 239 insertions(+), 14 deletions(-) diff --git a/platforms/HomeSeer.js b/platforms/HomeSeer.js index c4ccde7..1ebf307 100644 --- a/platforms/HomeSeer.js +++ b/platforms/HomeSeer.js @@ -1,29 +1,34 @@ 'use strict'; // -// HomeSeer Platform Shim for HomeBridge -// V0.1 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/07 +// HomeSeer Platform Shim for HomeBridge by Jean-Michel Joudrier - (stipus at stipus dot com) +// V0.1 - 2015/10/07 // - Initial version -// V0.2 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/10 +// V0.2 - 2015/10/10 // - Occupancy sensor fix -// V0.3 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/11 +// V0.3 - 2015/10/11 // - Added TemperatureUnit=F|C option to temperature sensors // - Added negative temperature support to temperature sensors -// V0.4 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/12 +// V0.4 - 2015/10/12 // - Added thermostat support -// V0.5 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/12 +// V0.5 - 2015/10/12 // - Added Humidity sensor support -// V0.6 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/12 +// V0.6 - 2015/10/12 // - Added Battery support // - Added low battery support for all sensors // - Added HomeSeer event support (using HomeKit switches...) -// V0.7 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/13 +// V0.7 - 2015/10/13 // - You can add multiple HomeKit devices for the same HomeSeer device reference // - Added CarbonMonoxide sensor // - Added CarbonDioxide sensor // - Added onValues option to all binary sensors -// V0.8 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/14 +// V0.8 - 2015/10/14 // - Added uuid_base parameter to all accessories +// V0.9 - 2015/10/16 +// - Smoke sensor battery fix +// - Added offEventGroup && offEventName to events (turn on launches one HS event. turn off can launch another HS event) +// - Added GarageDoorOpener support +// - Added Lock support // // // Remember to add platform to config.json. @@ -41,7 +46,9 @@ // "events":[ // Optional - List of Events - Currently they are imported into HomeKit as switches // { // "eventGroup":"My Group", // Required - The HomeSeer event group -// "eventName":"My Event", // Required - The HomeSeer event name +// "eventName":"My On Event", // Required - The HomeSeer event name +// "offEventGroup":"My Group", // Optional - The HomeSeer event group for turn-off +// "offEventName":"My Off Event", // Optional - The HomeSeer event name for turn-off // "name":"Test", // Optional - HomeSeer event name is the default // "uuid_base":"SomeUniqueId" // Optional - HomeKit identifier will be derived from this parameter instead of the name // } @@ -55,7 +62,7 @@ // "offValue":"0", // Optional - 0 is the default // "onValue":"100", // Optional - 100 is the default // "can_dim":true, // Optional - true is the default - false for a non dimmable lightbulb -// "uuid_base":"SomeUniqueId2" // Optional - HomeKit identifier will be derived from this parameter instead of the name +// "uuid_base":"SomeUniqueId2" // Optional - HomeKit identifier will be derived from this parameter instead of the name. You SHOULD add this parameter to all accessories ! // }, // { // "ref":9 // This is a dimmable Lightbulb by default @@ -109,6 +116,38 @@ // "heatingThresholdRef":170 // Optional - Not-implemented-yet - HomeSeer device reference for your thermostat heating threshold // }, // { +// "ref":200, // Required - HomeSeer Device Reference of a garage door opener +// "type":"GarageDoorOpener", // Required for a Garage Door Opener +// "name":"Garage Door", // Optional - HomeSeer device name is the default +// "stateRef":201, // Required - HomeSeer device reference for your garage door opener current state (can be the same as ref) +// "stateOpenValues":[0], // Required - List of the HomeSeer device values for a HomeKit state=OPEN +// "stateClosedValues":[1], // Required - List of the HomeSeer device values for a HomeKit state=CLOSED +// "stateOpeningValues":[2], // Optional - List of the HomeSeer device values for a HomeKit state=OPENING +// "stateClosingValues":[3], // Optional - List of the HomeSeer device values for a HomeKit state=CLOSING +// "stateStoppedValues":[4], // Optional - List of the HomeSeer device values for a HomeKit state=STOPPED +// "controlRef":201, // Required - HomeSeer device reference for your garage door opener control (can be the same as ref and stateRef) +// "controlOpenValue":0, // Required - HomeSeer device control value for OPEN +// "controlCloseValue":1, // Required - HomeSeer device control value for CLOSE +// "obstructionRef":201, // Optional - HomeSeer device reference for your garage door opener obstruction state (can be the same as ref) +// "obstructionValues":[5], // Optional - List of the HomeSeer device values for a HomeKit obstruction state=OBSTRUCTION +// "lockRef":202, // Optional - HomeSeer device reference for your garage door lock (can be the same as ref) +// "lockUnsecuredValues":[0], // Optional - List of the HomeSeer device values for a HomeKit lock state=UNSECURED +// "lockSecuredValues":[1], // Optional - List of the HomeSeer device values for a HomeKit lock state=SECURED +// "lockJammedValues":[2], // Optional - List of the HomeSeer device values for a HomeKit lock state=JAMMED +// "unlockValue":0, // Optional - HomeSeer device control value to unlock the garage door opener +// "lockValue":1 // Optional - HomeSeer device control value to lock the garage door opener +// }, +// { +// "ref":210, // Required - HomeSeer Device Reference of a Lock +// "type":"Lock", // Required for a Lock +// "name":"Main Door Lock", // Optional - HomeSeer device name is the default +// "lockUnsecuredValues":[0], // Required - List of the HomeSeer device values for a HomeKit lock state=UNSECURED +// "lockSecuredValues":[1], // Required - List of the HomeSeer device values for a HomeKit lock state=SECURED +// "lockJammedValues":[2], // Optional - List of the HomeSeer device values for a HomeKit lock state=JAMMED +// "unlockValue":0, // Required - HomeSeer device control value to unlock +// "lockValue":1 // Required - HomeSeer device control value to lock +// }, +// { // "ref":115, // Required - HomeSeer Device Reference for a device holding battery level (0-100) // "type":"Battery", // Required for a Battery // "name":"Roomba battery", // Optional - HomeSeer device name is the default @@ -136,6 +175,8 @@ // - CarbonMonoxideSensor (onValues, batteryRef, batteryThreshold options) // - CarbonDioxideSensor (onValues, batteryRef, batteryThreshold options) // - Battery (batteryThreshold option) +// - GarageDoorOpener (state, control, obstruction, lock options) +// - Lock (unsecured, secured, jammed options) // - Door @@ -520,6 +561,143 @@ HomeSeerAccessory.prototype = { }.bind(this)); }, + getCurrentDoorState: function(callback) { + var ref = this.config.stateRef; + var url = this.access_url + "request=getstatus&ref=" + ref; + + httpRequest(url, 'GET', function(error, response, body) { + if (error) { + this.log('HomeSeer get current door state function failed: %s', error.message); + callback( error, 0 ); + } + else { + var status = JSON.parse( body ); + var value = status.Devices[0].value; + + this.log('HomeSeer get target door state function succeeded: value=' + value ); + if( this.config.stateOpenValues.indexOf(value) != -1 ) + callback( null, 0 ); + else if( this.config.stateClosedValues.indexOf(value) != -1 ) + callback( null, 1 ); + else if( this.config.stateOpeningValues && this.config.stateOpeningValues.indexOf(value) != -1 ) + callback( null, 2 ); + else if( this.config.stateClosingValues && this.config.stateClosingValues.indexOf(value) != -1 ) + callback( null, 3 ); + else if( this.config.stateStoppedValues && this.config.stateStoppedValues.indexOf(value) != -1 ) + callback( null, 4 ); + else { + this.log( "Error: value for current door state not in stateO0penValues, stateClosedValues, stateOpeningValues, stateClosingValues, stateStoppedValues" ); + callback( null, 0 ); + } + } + }.bind(this)); + }, + + setTargetDoorState: function(state, callback) { + this.log("Setting target door state state to %s", state); + + var ref = this.config.controlRef; + var value = 0; + if( state == 0 ) + value = this.config.controlOpenValue; + else if( state == 1 ) + value = this.config.controlCloseValue; + + var url = this.access_url + "request=controldevicebyvalue&ref=" + ref + "&value=" + value; + httpRequest(url, 'GET', function(error, response, body) { + if (error) { + this.log('HomeSeer set target door state function failed: %s', error.message); + callback(error); + } + else { + this.log('HomeSeer set target door state function succeeded!'); + callback(); + } + }.bind(this)); + }, + + getObstructionDetected: function(callback) { + if( this.config.obstructionRef ) { + var ref = this.config.obstructionRef; + var url = this.access_url + "request=getstatus&ref=" + ref; + + httpRequest(url, 'GET', function(error, response, body) { + if (error) { + this.log('HomeSeer get obstruction detected function failed: %s', error.message); + callback( error, 0 ); + } + else { + var status = JSON.parse( body ); + var value = status.Devices[0].value; + + this.log('HomeSeer get obstruction detected function succeeded: value=' + value ); + if( this.config.obstructionValues && this.config.obstructionValues.indexOf(value) != -1 ) + callback( null, 1 ); + else { + callback( null, 0 ); + } + } + }.bind(this)); + } + else { + callback( null, 0 ); + } + }, + + getLockCurrentState: function(callback) { + var ref = this.config.lockRef; + var url = this.access_url + "request=getstatus&ref=" + ref; + + httpRequest(url, 'GET', function(error, response, body) { + if (error) { + this.log('HomeSeer get lock current state function failed: %s', error.message); + callback( error, 3 ); + } + else { + var status = JSON.parse( body ); + var value = status.Devices[0].value; + + this.log('HomeSeer get lock current state function succeeded: value=' + value ); + if( this.config.lockUnsecuredValues && this.config.lockUnsecuredValues.indexOf(value) != -1 ) + callback( null, 0 ); + else if( this.config.lockSecuredValues && this.config.lockSecuredValues.indexOf(value) != -1 ) + callback( null, 1 ); + else if( this.config.lockJammedValues && this.config.lockJammedValues.indexOf(value) != -1 ) + callback( null, 2 ); + else { + callback( null, 3 ); + } + } + }.bind(this)); + }, + + setLockTargetState: function(state, callback) { + this.log("Setting target lock state state to %s", state); + + var ref = this.config.lockRef; + var value = 0; + if( state == 0 && this.config.unlockValue ) + value = this.config.unlockValue; + else if( state == 1 && this.config.lockValue ) + value = this.config.lockValue; + + var url = this.access_url + "request=controldevicebyvalue&ref=" + ref + "&value=" + value; + httpRequest(url, 'GET', function(error, response, body) { + if (error) { + this.log('HomeSeer set target lock state function failed: %s', error.message); + callback(error); + } + else { + this.log('HomeSeer set target lock state function succeeded!'); + callback(); + } + }.bind(this)); + }, + + getPositionState: function(callback) { + callback( null, 2 ); // Temporarily return STOPPED. TODO: full door support + }, + getServices: function() { var services = [] @@ -675,7 +853,7 @@ HomeSeerAccessory.prototype = { .getCharacteristic(Characteristic.SmokeDetected) .on('get', this.getBinarySensorState.bind(this)); if( this.config.batteryRef ) { - temperatureSensorService + smokeSensorService .addCharacteristic(new Characteristic.StatusLowBattery()) .on('get', this.getLowBatteryStatus.bind(this)); } @@ -716,6 +894,9 @@ HomeSeerAccessory.prototype = { doorService .getCharacteristic(Characteristic.TargetPosition) .on('set', this.setValue.bind(this)); + doorService + .getCharacteristic(Characteristic.PositionState) + .on('get', this.getPositionState.bind(this)); services.push( doorService ); break; } @@ -759,6 +940,41 @@ HomeSeerAccessory.prototype = { services.push( thermostatService ); break; } + case "GarageDoorOpener": { + var garageDoorOpenerService = new Service.GarageDoorOpener(); + garageDoorOpenerService + .getCharacteristic(Characteristic.CurrentDoorState) + .on('get', this.getCurrentDoorState.bind(this)); + garageDoorOpenerService + .getCharacteristic(Characteristic.TargetDoorState) + .on('set', this.setTargetDoorState.bind(this)); + garageDoorOpenerService + .getCharacteristic(Characteristic.ObstructionDetected) + .on('get', this.getObstructionDetected.bind(this)); + if( this.config.lockRef ) { + garageDoorOpenerService + .addCharacteristic(new Characteristic.LockCurrentState()) + .on('get', this.getLockCurrentState.bind(this)); + garageDoorOpenerService + .addCharacteristic(new Characteristic.LockTargetState()) + .on('set', this.setLockTargetState.bind(this)); + } + services.push( garageDoorOpenerService ); + break; + } + case "Lock": { + this.config.lockRef = this.ref; + var lockService = new Service.LockMechanism(); + lockService + .getCharacteristic(Characteristic.LockCurrentState) + .on('get', this.getLockCurrentState.bind(this)); + lockService + .getCharacteristic(Characteristic.LockTargetState) + .on('set', this.setLockTargetState.bind(this)); + services.push( lockService ); + break; + } + default:{ var lightbulbService = new Service.Lightbulb(); lightbulbService @@ -787,7 +1003,11 @@ function HomeSeerEvent(log, platformConfig, eventConfig ) { this.model = "HomeSeer Event"; this.access_url = platformConfig["host"] + "/JSON?"; - this.launch_url = this.access_url + "request=runevent&group=" + encodeURIComponent(this.config.eventGroup) + "&name=" + encodeURIComponent(this.config.eventName); + this.on_url = this.access_url + "request=runevent&group=" + encodeURIComponent(this.config.eventGroup) + "&name=" + encodeURIComponent(this.config.eventName); + + if( this.config.offEventGroup && this.config.offEventName ) { + this.off_url = this.access_url + "request=runevent&group=" + encodeURIComponent(this.config.offEventGroup) + "&name=" + encodeURIComponent(this.config.offEventName); + } if( this.config.name ) this.name = this.config.name; @@ -805,7 +1025,12 @@ HomeSeerEvent.prototype = { launchEvent: function(value, callback) { this.log("Setting event value to %s", value); - httpRequest(this.launch_url, 'GET', function(error, response, body) { + var url = this.on_url; + if( value == 0 && this.off_url ) { + url = this.off_url; + } + + httpRequest(url, 'GET', function(error, response, body) { if (error) { this.log('HomeSeer run event function failed: %s', error.message); callback(error); From c22c14584dab3b8d592184aa4852a540885f6121 Mon Sep 17 00:00:00 2001 From: Mike Riccio Date: Fri, 16 Oct 2015 09:36:18 -0700 Subject: [PATCH 086/121] Added more logging when there's an error getting device details. --- platforms/Indigo.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/platforms/Indigo.js b/platforms/Indigo.js index 98f259c..cd20c84 100644 --- a/platforms/Indigo.js +++ b/platforms/Indigo.js @@ -80,14 +80,16 @@ IndigoPlatform.prototype = { request(deviceOptions, function(deviceError, deviceResponse, deviceBody) { if (deviceError) { - asyncCallback(deviceError); + console.trace("Requesting Indigo device info: " + deviceURL + "\nError: " + deviceError + "\nResponse: " + deviceBody); + asyncCallback(deviceError) + } + else { + var deviceJson = JSON.parse(deviceBody); + that.log("Discovered " + deviceJson.type + ": " + deviceJson.name); + that.foundAccessories.push( + new IndigoAccessory(that.log, that.auth, deviceURL, deviceJson)); + asyncCallback(); } - - var deviceJson = JSON.parse(deviceBody); - that.log("Discovered " + deviceJson.type + ": " + deviceJson.name); - that.foundAccessories.push( - new IndigoAccessory(that.log, that.auth, deviceURL, deviceJson)); - asyncCallback(); }); }, function(asyncError) { // This will be called after all the requests complete From bb1c193bc030921ee620ba5d9c75e9e41ac3f90c Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Fri, 16 Oct 2015 19:10:14 +0200 Subject: [PATCH 087/121] Use npm release of hap-nodejs --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 95db873..bbc5669 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#215a3bb1d603097d63ba73d4f7d731813c6b87e5", + "hap-nodejs": "^0.0.2", "harmonyhubjs-client": "^1.1.4", "harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git", "lifx-api": "^1.0.1", From 831480d035660ba0a2532af4ebfc7a1cefa915c9 Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Fri, 16 Oct 2015 10:40:01 -0700 Subject: [PATCH 088/121] Remove telldus for now --- package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/package.json b/package.json index bbc5669..ff5563b 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,6 @@ "tough-cookie": "^2.0.0", "request": "2.49.x", "sonos": "0.8.x", - "telldus": "0.0.9", - "telldus-live": "0.2.x", "teslams": "1.0.1", "unofficial-nest-api": "git+https://github.com/hachidorii/unofficial_nodejs_nest.git#d8d48edc952b049ff6320ef99afa7b2f04cdee98", "wemo": "0.2.x", From c5499c122e11bfb3ee130f0670d80f0c38bc7aa1 Mon Sep 17 00:00:00 2001 From: Mike Riccio Date: Fri, 16 Oct 2015 20:21:30 -0700 Subject: [PATCH 089/121] Add devices even if discovery errors. Don't assign everything POWER_STATE_CTYPE. --- platforms/Indigo.js | 67 +++++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/platforms/Indigo.js b/platforms/Indigo.js index cd20c84..1b7f722 100644 --- a/platforms/Indigo.js +++ b/platforms/Indigo.js @@ -19,8 +19,8 @@ // The default code for all HomeBridge accessories is 031-45-154. // -var types = require("HAP-NodeJS/accessories/types.js"); -var Characteristic = require("HAP-NodeJS").Characteristic; +var types = require("hap-nodejs/accessories/types.js"); +var Characteristic = require("hap-nodejs").Characteristic; var request = require('request'); var async = require('async'); @@ -96,11 +96,11 @@ IndigoPlatform.prototype = { if (asyncError) { console.trace("Requesting Indigo device info."); that.log(asyncError); - } else { - that.callback(that.foundAccessories.sort(function (a,b) { - return (a.name > b.name) - (a.name < b.name); - })); } + + that.callback(that.foundAccessories.sort(function (a,b) { + return (a.name > b.name) - (a.name < b.name); + })); }); }); } @@ -326,6 +326,8 @@ IndigoAccessory.prototype = { }, controlCharacteristics: function(that) { + var hasAType = false; + var cTypes = [{ cType: types.NAME_CTYPE, onUpdate: null, @@ -338,30 +340,8 @@ IndigoAccessory.prototype = { designedMaxLength: 255 }]; - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], - format: Characteristic.Formats.BOOL, - initialValue: (that.isOn) ? 1 : 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1, - onUpdate: function(value) { - if (value == 0) { - that.turnOff(); - } else { - that.turnOn(); - } - }, - onRead: function(callback) { - that.query("isOn", function(isOn) { - callback((isOn) ? 1 : 0); - }); - } - }); - if (that.typeSupportsDim) { + hasAType = true; cTypes.push({ cType: types.BRIGHTNESS_CTYPE, perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], @@ -384,6 +364,7 @@ IndigoAccessory.prototype = { } if (that.typeSupportsSpeedControl) { + hasAType = true; cTypes.push({ cType: types.ROTATION_SPEED_CTYPE, perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], @@ -406,6 +387,7 @@ IndigoAccessory.prototype = { } if (that.typeSupportsHVAC) { + hasAType = true; cTypes.push({ cType: types.CURRENTHEATINGCOOLING_CTYPE, perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], @@ -486,7 +468,7 @@ IndigoAccessory.prototype = { cType: types.TEMPERATURE_UNITS_CTYPE, perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], format: Characteristic.Formats.INT, - initialValue: 1, + initialValue: Characteristic.Units.FAHRENHEIT, supportEvents: false, supportBonjour: false, manfDescription: "Unit", @@ -497,6 +479,31 @@ IndigoAccessory.prototype = { }); } + if (that.typeSupportsOnOff || !hasAType) { + cTypes.push({ + cType: types.POWER_STATE_CTYPE, + perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.BOOL, + initialValue: (that.isOn) ? 1 : 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Change the power state", + designedMaxLength: 1, + onUpdate: function(value) { + if (value == 0) { + that.turnOff(); + } else { + that.turnOn(); + } + }, + onRead: function(callback) { + that.query("isOn", function(isOn) { + callback((isOn) ? 1 : 0); + }); + } + }); + } + return cTypes; }, From 896d6b40d958a3e93e12ef807b9c1958d08410cc Mon Sep 17 00:00:00 2001 From: Mike Riccio Date: Fri, 16 Oct 2015 20:36:32 -0700 Subject: [PATCH 090/121] Fixed fahrenheit units, was giving error. --- platforms/Indigo.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/platforms/Indigo.js b/platforms/Indigo.js index 1b7f722..cc77e21 100644 --- a/platforms/Indigo.js +++ b/platforms/Indigo.js @@ -430,10 +430,10 @@ IndigoAccessory.prototype = { cType: types.CURRENT_TEMPERATURE_CTYPE, perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], format: Characteristic.Formats.INT, - designedMinValue: 0, - designedMaxValue: 110, + designedMinValue: 16, + designedMaxValue: 38, designedMinStep: 1, - initialValue: 0, + initialValue: 20, supportEvents: false, supportBonjour: false, manfDescription: "Current Temperature", @@ -448,10 +448,10 @@ IndigoAccessory.prototype = { cType: types.TARGET_TEMPERATURE_CTYPE, perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], format: Characteristic.Formats.INT, - designedMinValue: 0, - designedMaxValue: 110, + designedMinValue: 16, + designedMaxValue: 38, designedMinStep: 1, - initialValue: 0, + initialValue: 20, supportEvents: false, supportBonjour: false, manfDescription: "Target Temperature", @@ -468,13 +468,13 @@ IndigoAccessory.prototype = { cType: types.TEMPERATURE_UNITS_CTYPE, perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], format: Characteristic.Formats.INT, - initialValue: Characteristic.Units.FAHRENHEIT, + initialValue: 1, supportEvents: false, supportBonjour: false, manfDescription: "Unit", onUpdate: null, onRead: function(callback) { - callback(Characteristic.Units.FAHRENHEIT); + callback(1); } }); } From f2d22584fcc79a6c7b59222687ee26d9b844ec3c Mon Sep 17 00:00:00 2001 From: tommasomarchionni Date: Sat, 17 Oct 2015 12:34:40 +0200 Subject: [PATCH 091/121] Update app.js --- app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.js b/app.js index ffc203e..a17f8ed 100644 --- a/app.js +++ b/app.js @@ -8,7 +8,7 @@ var Accessory = require("hap-nodejs").Accessory; var Service = require("hap-nodejs").Service; var Characteristic = require("hap-nodejs").Characteristic; var accessoryLoader = require("hap-nodejs").AccessoryLoader; -var once = require('HAP-NodeJS/lib/util/once').once; +var once = require("hap-nodejs").once; console.log("Starting HomeBridge server..."); From 385908fa8910f8f5a062e5a4042cef2116725286 Mon Sep 17 00:00:00 2001 From: tommasomarchionni Date: Sat, 17 Oct 2015 13:23:30 +0200 Subject: [PATCH 092/121] Update app.js --- app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.js b/app.js index a17f8ed..20279fa 100644 --- a/app.js +++ b/app.js @@ -8,7 +8,7 @@ var Accessory = require("hap-nodejs").Accessory; var Service = require("hap-nodejs").Service; var Characteristic = require("hap-nodejs").Characteristic; var accessoryLoader = require("hap-nodejs").AccessoryLoader; -var once = require("hap-nodejs").once; +var once = require("hap-nodejs/lib/util/once").once; console.log("Starting HomeBridge server..."); From 196cda80634d59582f22ae7d08c28c7050edfdc4 Mon Sep 17 00:00:00 2001 From: William Bout Date: Sat, 17 Oct 2015 18:12:11 +0200 Subject: [PATCH 093/121] Update Hyperion.js Fix require error --- accessories/Hyperion.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accessories/Hyperion.js b/accessories/Hyperion.js index b01db55..8767b19 100644 --- a/accessories/Hyperion.js +++ b/accessories/Hyperion.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var net = require('net'); var Color = require('color'); From 38cb94c012eaf249ca8187e87124aa2633f98e00 Mon Sep 17 00:00:00 2001 From: Mason James Date: Sat, 17 Oct 2015 18:41:57 -0400 Subject: [PATCH 094/121] Update hap-nodejs requires Update remaining platforms and accessories --- accessories/AD2USB.js | 2 +- accessories/Carwings.js | 2 +- accessories/ELKM1.js | 2 +- accessories/HomeMatic.js | 2 +- accessories/HomeMaticThermo.js | 2 +- accessories/HomeMaticWindow.js | 2 +- accessories/LiftMaster.js | 2 +- accessories/Tesla.js | 2 +- accessories/X10.js | 2 +- platforms/Domoticz.js | 2 +- platforms/FHEM.js | 2 +- platforms/FibaroHC2.js | 2 +- platforms/ISY.js | 2 +- platforms/KNX.js | 2 +- platforms/LogitechHarmony.js | 2 +- platforms/Nest.js | 2 +- platforms/PhilipsHue.js | 2 +- platforms/SmartThings.js | 2 +- platforms/Sonos.js | 2 +- platforms/Telldus.js | 2 +- platforms/TelldusLive.js | 2 +- platforms/Wink.js | 2 +- platforms/YamahaAVR.js | 2 +- platforms/ZWayServer.js | 2 +- 24 files changed, 24 insertions(+), 24 deletions(-) diff --git a/accessories/AD2USB.js b/accessories/AD2USB.js index ea05377..bcef82d 100644 --- a/accessories/AD2USB.js +++ b/accessories/AD2USB.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var AD2USB = require('ad2usb'); var CUSTOM_PANEL_LCD_TEXT_CTYPE = "A3E7B8F9-216E-42C1-A21C-97D4E3BE52C8"; diff --git a/accessories/Carwings.js b/accessories/Carwings.js index 150c936..7d0cd82 100644 --- a/accessories/Carwings.js +++ b/accessories/Carwings.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var carwings = require("carwingsjs"); function CarwingsAccessory(log, config) { diff --git a/accessories/ELKM1.js b/accessories/ELKM1.js index d32cc1b..ead0f17 100644 --- a/accessories/ELKM1.js +++ b/accessories/ELKM1.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var elkington = require("elkington"); function ElkM1Accessory(log, config) { diff --git a/accessories/HomeMatic.js b/accessories/HomeMatic.js index 89e43b0..23a7ecb 100644 --- a/accessories/HomeMatic.js +++ b/accessories/HomeMatic.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); function HomeMatic(log, config) { diff --git a/accessories/HomeMaticThermo.js b/accessories/HomeMaticThermo.js index f3d300f..86db229 100644 --- a/accessories/HomeMaticThermo.js +++ b/accessories/HomeMaticThermo.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); function HomeMaticThermo(log, config) { diff --git a/accessories/HomeMaticWindow.js b/accessories/HomeMaticWindow.js index 865b24a..6dcaf78 100644 --- a/accessories/HomeMaticWindow.js +++ b/accessories/HomeMaticWindow.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); diff --git a/accessories/LiftMaster.js b/accessories/LiftMaster.js index baf3be8..c239f24 100644 --- a/accessories/LiftMaster.js +++ b/accessories/LiftMaster.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); // This seems to be the "id" of the official LiftMaster iOS app diff --git a/accessories/Tesla.js b/accessories/Tesla.js index 016a465..8d96e57 100644 --- a/accessories/Tesla.js +++ b/accessories/Tesla.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var tesla = require("teslams"); function TeslaAccessory(log, config) { diff --git a/accessories/X10.js b/accessories/X10.js index 6668a44..0cf781c 100644 --- a/accessories/X10.js +++ b/accessories/X10.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); function X10(log, config) { diff --git a/platforms/Domoticz.js b/platforms/Domoticz.js index 8930011..948167d 100644 --- a/platforms/Domoticz.js +++ b/platforms/Domoticz.js @@ -50,7 +50,7 @@ // 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 types = require("hap-nodejs/accessories/types.js"); var request = require("request"); function DomoticzPlatform(log, config){ diff --git a/platforms/FHEM.js b/platforms/FHEM.js index 7ef5e37..b892a0a 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -19,7 +19,7 @@ var Service = require("hap-nodejs").Service; var Characteristic = require("hap-nodejs").Characteristic; -var types = require('HAP-NodeJS/accessories/types.js'); +var types = require('hap-nodejs/accessories/types.js'); var util = require('util'); diff --git a/platforms/FibaroHC2.js b/platforms/FibaroHC2.js index 8293645..57c880c 100644 --- a/platforms/FibaroHC2.js +++ b/platforms/FibaroHC2.js @@ -14,7 +14,7 @@ // 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 types = require("hap-nodejs/accessories/types.js"); var Service = require("hap-nodejs").Service; var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); diff --git a/platforms/ISY.js b/platforms/ISY.js index b4b20a8..53c4980 100644 --- a/platforms/ISY.js +++ b/platforms/ISY.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var xml2js = require('xml2js'); var request = require('request'); var util = require('util'); diff --git a/platforms/KNX.js b/platforms/KNX.js index 65f7a13..0ca4309 100644 --- a/platforms/KNX.js +++ b/platforms/KNX.js @@ -2,7 +2,7 @@ * based on Sonos platform */ 'use strict'; -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); //var hardware = require('myHardwareSupport'); //require any additional hardware packages var knxd = require('eibd'); diff --git a/platforms/LogitechHarmony.js b/platforms/LogitechHarmony.js index a5c5e22..0521a65 100644 --- a/platforms/LogitechHarmony.js +++ b/platforms/LogitechHarmony.js @@ -17,7 +17,7 @@ // -var types = require('HAP-NodeJS/accessories/types.js'); +var types = require('hap-nodejs/accessories/types.js'); var harmonyDiscover = require('harmonyhubjs-discover'); var harmony = require('harmonyhubjs-client'); diff --git a/platforms/Nest.js b/platforms/Nest.js index c1ef3bd..9d225c0 100644 --- a/platforms/Nest.js +++ b/platforms/Nest.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var nest = require('unofficial-nest-api'); function NestPlatform(log, config){ diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 9bceaf0..40dcc33 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -33,7 +33,7 @@ var hue = require("node-hue-api"), HueApi = hue.HueApi, lightState = hue.lightState; -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); function PhilipsHuePlatform(log, config) { this.log = log; diff --git a/platforms/SmartThings.js b/platforms/SmartThings.js index 580e70b..470ee55 100644 --- a/platforms/SmartThings.js +++ b/platforms/SmartThings.js @@ -1,7 +1,7 @@ // SmartThings JSON API SmartApp required // https://github.com/jnewland/SmartThings/blob/master/JSON.groovy // -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); function SmartThingsPlatform(log, config){ diff --git a/platforms/Sonos.js b/platforms/Sonos.js index 1d19c2f..812a803 100644 --- a/platforms/Sonos.js +++ b/platforms/Sonos.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var sonos = require('sonos'); function SonosPlatform(log, config){ diff --git a/platforms/Telldus.js b/platforms/Telldus.js index 87d37f1..c192a31 100644 --- a/platforms/Telldus.js +++ b/platforms/Telldus.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var telldus = require('telldus'); function TelldusPlatform(log, config) { diff --git a/platforms/TelldusLive.js b/platforms/TelldusLive.js index b6a88f1..0e861b5 100644 --- a/platforms/TelldusLive.js +++ b/platforms/TelldusLive.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var TellduAPI = require("telldus-live"); function TelldusLivePlatform(log, config) { diff --git a/platforms/Wink.js b/platforms/Wink.js index e2ec455..d9de07f 100644 --- a/platforms/Wink.js +++ b/platforms/Wink.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var wink = require('wink-js'); var model = { diff --git a/platforms/YamahaAVR.js b/platforms/YamahaAVR.js index be73b77..8d79058 100644 --- a/platforms/YamahaAVR.js +++ b/platforms/YamahaAVR.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var inherits = require('util').inherits; var debug = require('debug')('YamahaAVR'); var Service = require("hap-nodejs").Service; diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index 9800ab6..f456cff 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -1,7 +1,7 @@ var debug = require('debug')('ZWayServer'); var Service = require("hap-nodejs").Service; var Characteristic = require("hap-nodejs").Characteristic; -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); var tough = require('tough-cookie'); var Q = require("q"); From 15e9219c80aea5c106af3f775fdec1d42084127b Mon Sep 17 00:00:00 2001 From: David Garozzo Date: Sat, 17 Oct 2015 21:43:43 -0400 Subject: [PATCH 095/121] add async dependency for Indigo --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index ff5563b..f8bf8e4 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "license": "ISC", "dependencies": { "ad2usb": "git+https://github.com/alistairg/node-ad2usb.git#local", + "async": "^1.4.2", "carwingsjs": "0.0.x", "chokidar": "^1.0.5", "color": "0.10.x", From ad62bd4b15a954defbd3a211de588f5058db6847 Mon Sep 17 00:00:00 2001 From: Mike Riccio Date: Sat, 17 Oct 2015 20:27:04 -0700 Subject: [PATCH 096/121] Graceful handling of JSON parse errors from Indigo responses. Make initial discover queries serialized to not overwhelm slow Indigo servers. --- platforms/Indigo.js | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/platforms/Indigo.js b/platforms/Indigo.js index cc77e21..4886f29 100644 --- a/platforms/Indigo.js +++ b/platforms/Indigo.js @@ -68,7 +68,7 @@ IndigoPlatform.prototype = { } var json = JSON.parse(body); - async.each(json, function(item, asyncCallback) { + async.eachSeries(json, function(item, asyncCallback) { var deviceURL = that.baseURL + item.restURL; var deviceOptions = { url: deviceURL, @@ -81,15 +81,19 @@ IndigoPlatform.prototype = { request(deviceOptions, function(deviceError, deviceResponse, deviceBody) { if (deviceError) { console.trace("Requesting Indigo device info: " + deviceURL + "\nError: " + deviceError + "\nResponse: " + deviceBody); - asyncCallback(deviceError) } else { - var deviceJson = JSON.parse(deviceBody); - that.log("Discovered " + deviceJson.type + ": " + deviceJson.name); - that.foundAccessories.push( - new IndigoAccessory(that.log, that.auth, deviceURL, deviceJson)); - asyncCallback(); + try { + var deviceJson = JSON.parse(deviceBody); + that.log("Discovered " + deviceJson.type + ": " + deviceJson.name); + that.foundAccessories.push( + new IndigoAccessory(that.log, that.auth, deviceURL, deviceJson)); + } + catch (e) { + that.log("Error parsing Indigo device info: " + deviceURL + "\nException: " + e + "\nResponse: " + deviceBody); + } } + asyncCallback(); }); }, function(asyncError) { // This will be called after all the requests complete @@ -135,11 +139,18 @@ IndigoAccessory.prototype = { if (error) { console.trace("Requesting Device Status."); that.log(error); - return error; } - - that.log("getStatus of " + that.name + ": " + body); - callback(JSON.parse(body)); + else { + that.log("getStatus of " + that.name + ": " + body); + try { + var json = JSON.parse(body); + callback(json); + } + catch (e) { + console.trace("Requesting Device Status."); + that.log("Exception: " + e + "\nResponse: " + body); + } + } }); }, From 915e67458354bc130bf47261827cab228cb6db94 Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Sun, 18 Oct 2015 13:29:50 +0200 Subject: [PATCH 097/121] Change all HAP-NodeJS require calls to lowercase --- accessories/AD2USB.js | 2 +- accessories/Carwings.js | 2 +- accessories/ELKM1.js | 2 +- accessories/HomeMatic.js | 2 +- accessories/HomeMaticThermo.js | 2 +- accessories/HomeMaticWindow.js | 2 +- accessories/LiftMaster.js | 2 +- accessories/Tesla.js | 2 +- accessories/X10.js | 2 +- platforms/Domoticz.js | 2 +- platforms/FHEM.js | 2 +- platforms/FibaroHC2.js | 2 +- platforms/ISY.js | 2 +- platforms/KNX.js | 2 +- platforms/LogitechHarmony.js | 2 +- platforms/Nest.js | 2 +- platforms/PhilipsHue.js | 2 +- platforms/SmartThings.js | 2 +- platforms/Sonos.js | 2 +- platforms/Telldus.js | 2 +- platforms/TelldusLive.js | 2 +- platforms/Wink.js | 2 +- platforms/YamahaAVR.js | 2 +- platforms/ZWayServer.js | 2 +- 24 files changed, 24 insertions(+), 24 deletions(-) diff --git a/accessories/AD2USB.js b/accessories/AD2USB.js index ea05377..bcef82d 100644 --- a/accessories/AD2USB.js +++ b/accessories/AD2USB.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var AD2USB = require('ad2usb'); var CUSTOM_PANEL_LCD_TEXT_CTYPE = "A3E7B8F9-216E-42C1-A21C-97D4E3BE52C8"; diff --git a/accessories/Carwings.js b/accessories/Carwings.js index 150c936..7d0cd82 100644 --- a/accessories/Carwings.js +++ b/accessories/Carwings.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var carwings = require("carwingsjs"); function CarwingsAccessory(log, config) { diff --git a/accessories/ELKM1.js b/accessories/ELKM1.js index d32cc1b..ead0f17 100644 --- a/accessories/ELKM1.js +++ b/accessories/ELKM1.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var elkington = require("elkington"); function ElkM1Accessory(log, config) { diff --git a/accessories/HomeMatic.js b/accessories/HomeMatic.js index 89e43b0..23a7ecb 100644 --- a/accessories/HomeMatic.js +++ b/accessories/HomeMatic.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); function HomeMatic(log, config) { diff --git a/accessories/HomeMaticThermo.js b/accessories/HomeMaticThermo.js index f3d300f..86db229 100644 --- a/accessories/HomeMaticThermo.js +++ b/accessories/HomeMaticThermo.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); function HomeMaticThermo(log, config) { diff --git a/accessories/HomeMaticWindow.js b/accessories/HomeMaticWindow.js index 865b24a..6dcaf78 100644 --- a/accessories/HomeMaticWindow.js +++ b/accessories/HomeMaticWindow.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); diff --git a/accessories/LiftMaster.js b/accessories/LiftMaster.js index baf3be8..c239f24 100644 --- a/accessories/LiftMaster.js +++ b/accessories/LiftMaster.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); // This seems to be the "id" of the official LiftMaster iOS app diff --git a/accessories/Tesla.js b/accessories/Tesla.js index 016a465..8d96e57 100644 --- a/accessories/Tesla.js +++ b/accessories/Tesla.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var tesla = require("teslams"); function TeslaAccessory(log, config) { diff --git a/accessories/X10.js b/accessories/X10.js index 6668a44..0cf781c 100644 --- a/accessories/X10.js +++ b/accessories/X10.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); function X10(log, config) { diff --git a/platforms/Domoticz.js b/platforms/Domoticz.js index 8930011..948167d 100644 --- a/platforms/Domoticz.js +++ b/platforms/Domoticz.js @@ -50,7 +50,7 @@ // 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 types = require("hap-nodejs/accessories/types.js"); var request = require("request"); function DomoticzPlatform(log, config){ diff --git a/platforms/FHEM.js b/platforms/FHEM.js index 7ef5e37..b892a0a 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -19,7 +19,7 @@ var Service = require("hap-nodejs").Service; var Characteristic = require("hap-nodejs").Characteristic; -var types = require('HAP-NodeJS/accessories/types.js'); +var types = require('hap-nodejs/accessories/types.js'); var util = require('util'); diff --git a/platforms/FibaroHC2.js b/platforms/FibaroHC2.js index 8293645..57c880c 100644 --- a/platforms/FibaroHC2.js +++ b/platforms/FibaroHC2.js @@ -14,7 +14,7 @@ // 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 types = require("hap-nodejs/accessories/types.js"); var Service = require("hap-nodejs").Service; var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); diff --git a/platforms/ISY.js b/platforms/ISY.js index b4b20a8..53c4980 100644 --- a/platforms/ISY.js +++ b/platforms/ISY.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var xml2js = require('xml2js'); var request = require('request'); var util = require('util'); diff --git a/platforms/KNX.js b/platforms/KNX.js index 65f7a13..0ca4309 100644 --- a/platforms/KNX.js +++ b/platforms/KNX.js @@ -2,7 +2,7 @@ * based on Sonos platform */ 'use strict'; -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); //var hardware = require('myHardwareSupport'); //require any additional hardware packages var knxd = require('eibd'); diff --git a/platforms/LogitechHarmony.js b/platforms/LogitechHarmony.js index a5c5e22..0521a65 100644 --- a/platforms/LogitechHarmony.js +++ b/platforms/LogitechHarmony.js @@ -17,7 +17,7 @@ // -var types = require('HAP-NodeJS/accessories/types.js'); +var types = require('hap-nodejs/accessories/types.js'); var harmonyDiscover = require('harmonyhubjs-discover'); var harmony = require('harmonyhubjs-client'); diff --git a/platforms/Nest.js b/platforms/Nest.js index c1ef3bd..9d225c0 100644 --- a/platforms/Nest.js +++ b/platforms/Nest.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var nest = require('unofficial-nest-api'); function NestPlatform(log, config){ diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 9bceaf0..40dcc33 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -33,7 +33,7 @@ var hue = require("node-hue-api"), HueApi = hue.HueApi, lightState = hue.lightState; -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); function PhilipsHuePlatform(log, config) { this.log = log; diff --git a/platforms/SmartThings.js b/platforms/SmartThings.js index 580e70b..470ee55 100644 --- a/platforms/SmartThings.js +++ b/platforms/SmartThings.js @@ -1,7 +1,7 @@ // SmartThings JSON API SmartApp required // https://github.com/jnewland/SmartThings/blob/master/JSON.groovy // -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); function SmartThingsPlatform(log, config){ diff --git a/platforms/Sonos.js b/platforms/Sonos.js index 1d19c2f..812a803 100644 --- a/platforms/Sonos.js +++ b/platforms/Sonos.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var sonos = require('sonos'); function SonosPlatform(log, config){ diff --git a/platforms/Telldus.js b/platforms/Telldus.js index 87d37f1..c192a31 100644 --- a/platforms/Telldus.js +++ b/platforms/Telldus.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var telldus = require('telldus'); function TelldusPlatform(log, config) { diff --git a/platforms/TelldusLive.js b/platforms/TelldusLive.js index b6a88f1..0e861b5 100644 --- a/platforms/TelldusLive.js +++ b/platforms/TelldusLive.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var TellduAPI = require("telldus-live"); function TelldusLivePlatform(log, config) { diff --git a/platforms/Wink.js b/platforms/Wink.js index e2ec455..d9de07f 100644 --- a/platforms/Wink.js +++ b/platforms/Wink.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var wink = require('wink-js'); var model = { diff --git a/platforms/YamahaAVR.js b/platforms/YamahaAVR.js index be73b77..8d79058 100644 --- a/platforms/YamahaAVR.js +++ b/platforms/YamahaAVR.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var inherits = require('util').inherits; var debug = require('debug')('YamahaAVR'); var Service = require("hap-nodejs").Service; diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index 9800ab6..f456cff 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -1,7 +1,7 @@ var debug = require('debug')('ZWayServer'); var Service = require("hap-nodejs").Service; var Characteristic = require("hap-nodejs").Characteristic; -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); var tough = require('tough-cookie'); var Q = require("q"); From bf125078a1264cded3c24914988814370146a206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Sun, 18 Oct 2015 18:25:31 +0200 Subject: [PATCH 098/121] added first version of HM-SEC-KEY-S some bug fixes --- platforms/FHEM.js | 67 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index d591fc2..a40120b 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -60,7 +60,7 @@ FHEM_update(inform_id, value, no_update) { var date = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, ''); console.log(" " + date + " caching: " + inform_id + ": " + value + " as " + typeof(value) ); - if( !no_update ) + if( !no_update && subscription.characteristic ) subscription.characteristic.setValue(value, undefined, 'fromFHEM'); } } @@ -564,7 +564,7 @@ console.log( result ); } else if( s.Attributes.model == 'HM-SEC-WIN' ) { accessory = new FHEMAccessory(this.log, this.connection, s); - } else if( s.Attributes.model == 'HM-SEC-KEY' ) { + } else if( s.Attributes.model && s.Attributes.model.match(/^HM-SEC-KEY/) ) { accessory = new FHEMAccessory(this.log, this.connection, s); } else if( s.Internals.TYPE == 'PRESENCE' @@ -718,6 +718,9 @@ FHEMAccessory(log, connection, s) { if( genericType == 'switch' ) s.isSwitch = true; + else if( genericType == 'outlet' ) + s.isOutlet = true; + else if( genericType == 'garage' ) this.mappings.garage = { cmdOpen: 'on', cmdClose: 'off' }; @@ -737,8 +740,8 @@ FHEMAccessory(log, connection, s) { this.mappings.window = { reading: 'level', cmd: 'level' }; else if( genericType == 'lock' - || s.Attributes.model == 'HM-SEC-KEY' ) - this.mappings.lock = { reading: 'lock' }; + || ( s.Attributes.model && s.Attributes.model.match(/^HM-SEC-KEY/ ) ) ) + this.mappings.lock = { reading: 'lock', cmdLock: 'lock', cmdUnlock: 'unlock' }; else if( genericType == 'thermostat' || s.Attributes.subType == 'thermostat' ) @@ -855,6 +858,8 @@ FHEMAccessory(log, connection, s) { log( s.Internals.NAME + ' is dimable [0-'+ s.pctMax +']' ); else if( s.isLight ) log( s.Internals.NAME + ' is light' ); + else if( s.isOutlet ) + log( s.Internals.NAME + ' is outlet' ); else if( this.mappings.onOff || s.isSwitch ) log( s.Internals.NAME + ' is switchable' ); else if( !this.mappings ) @@ -930,6 +935,7 @@ FHEMAccessory(log, connection, s) { this.isLight = s.isLight; this.isSwitch = s.isSwitch; + this.isOutlet = s.isOutlet; //log( util.inspect(s.Readings) ); @@ -1104,6 +1110,10 @@ FHEMAccessory.prototype = { value = Characteristic.OccupancyDetected.OCCUPANCY_DETECTED; else if( value == 'absent' ) value = Characteristic.OccupancyDetected.OCCUPANCY_NOT_DETECTED; + else if( value == 'locked' ) + value = Characteristic.LockCurrentState.SECURED; + else if( value == 'unlocked' ) + value = Characteristic.LockCurrentState.UNSECURED; else if( value == '000000' ) value = 0; else if( value.match( /^[A-D]0$/ ) ) //FIXME: not necessary any more. handled by event_map now. @@ -1293,7 +1303,7 @@ FHEMAccessory.prototype = { query_reading = 'state'; } else if( reading == 'lock' && this.mappings.lock ) { - query_reading = 'state'; + //query_reading = 'state'; } @@ -1374,16 +1384,24 @@ FHEMAccessory.prototype = { }, createDeviceService: function(subtype) { - var name = this.alias + ' (' + this.name + ')'; + //var name = this.alias + ' (' + this.name + ')'; + var name = this.alias; if( subtype ) - name = subtype + ' (' + this.name + ')'; + //name = subtype + ' (' + this.name + ')'; + name = subtype + ' (' + this.alias + ')'; if( this.isSwitch ) { this.log(" switch service for " + this.name) return new Service.Switch(name); + } else if( this.isOutlet ) { + this.log(" outlet service for " + this.name) + return new Service.Outlet(name); } else if( this.mappings.garage ) { this.log(" garage door opener service for " + this.name) return new Service.GarageDoorOpener(name); + } else if( this.mappings.lock ) { + this.log(" lock mechanism service for " + this.name) + return new Service.LockMechanism(name); } else if( this.mappings.window ) { this.log(" window service for " + this.name) return new Service.Window(name); @@ -1476,7 +1494,7 @@ FHEMAccessory.prototype = { var controlService = this.createDeviceService(activity); services.push( controlService ); - this.log(" power characteristic for " + this.name + ' ' + activity); + this.log(" on characteristic for " + this.name + ' ' + activity); var characteristic = controlService.getCharacteristic(Characteristic.On); @@ -1528,7 +1546,7 @@ FHEMAccessory.prototype = { services.push( controlService ); if( this.mappings.onOff ) { - this.log(" power characteristic for " + this.name) + this.log(" on characteristic for " + this.name) var characteristic = controlService.getCharacteristic(Characteristic.On); @@ -1818,6 +1836,37 @@ FHEMAccessory.prototype = { }.bind(this) ); } + if( this.mappings.lock ) { + this.log(" lock current state characteristic for " + this.name) + + var characteristic = controlService.getCharacteristic(Characteristic.LockCurrentState); + + //FHEM_subscribe(characteristic, this.name+'-state', this); + FHEM_subscribe(characteristic, this.mappings.lock.informId, this); + characteristic.value = FHEM_cached[this.mappings.lock.informId]; + + characteristic + .on('get', function(callback) { + this.query(this.mappings.lock.reading, callback); + }.bind(this) ); + + this.log(" lock target state characteristic for " + this.name) + + var characteristic = controlService.getCharacteristic(Characteristic.LockTargetState); + + characteristic.value = FHEM_cached[this.mappings.lock.informId]; + + characteristic + .on('set', function(value, callback, context) { + if( context !== 'fromFHEM' ) + this.command( 'set', value == Characteristic.LockTargetState.UNSECURED ? this.mappings.lock.cmdUnlock : this.mappings.lock.cmdLock ); + callback(); + }.bind(this) ) + .on('get', function(callback) { + this.query(this.mappings.lock.reading, callback); + }.bind(this) ); + } + if( this.mappings.garage ) { this.log(" current door state characteristic for " + this.name) From 2d80e5f838c94d2f481be79c6832c286032fd06f Mon Sep 17 00:00:00 2001 From: rodtoll Date: Sun, 18 Oct 2015 11:19:08 -0700 Subject: [PATCH 099/121] Updated to latest version of hap base library. Root project migrated to lower case version. Also added new uuid_base required field which is now needed. Refactored device constructors to use common code. --- platforms/isy-js.js | 49 ++++++++++++++++++++------------------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/platforms/isy-js.js b/platforms/isy-js.js index 5f01a11..03ca340 100644 --- a/platforms/isy-js.js +++ b/platforms/isy-js.js @@ -49,10 +49,12 @@ { "nameContains": "Remote", "lastAddressDigit": "", "address": "" } - Ignore all devices which have the word Remote in their name { "nameContains": "", "lastAddressDigit": "", "address": "15 5 3 2"} - Ignore the device with an ISY address of 15 5 3 2. */ -var types = require("HAP-NodeJS/accessories/types.js"); + + +var types = require("hap-nodejs/accessories/types.js"); var isy = require('isy-js'); -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var inherits = require('util').inherits; // Global device map. Needed to map incoming notifications to the corresponding HomeKit device for update. @@ -147,14 +149,22 @@ ISYPlatform.prototype.accessories = function(callback) { }); } +///////////////////////////////////////////////////////////////////////////////////////////////// +// BASE FOR ALL DEVICES + +function ISYAccessoryBaseSetup(accessory,log,device) { + accessory.log = log; + accessory.device = device; + accessory.address = device.address; + accessory.name = device.name; + accessory.uuid_base = device.isy.address+":"+device.address; +} + ///////////////////////////////////////////////////////////////////////////////////////////////// // FANS function ISYFanAccessory(log,device) { - this.log = log; - this.device = device; - this.address = device.address; - this.name = device.name; + ISYAccessoryBaseSetup(this,log,device); } ISYFanAccessory.prototype.identify = function(callback) { @@ -276,10 +286,7 @@ ISYFanAccessory.prototype.getServices = function() { // OUTLETS function ISYOutletAccessory(log,device) { - this.log = log; - this.device = device; - this.address = device.address; - this.name = device.name; + ISYAccessoryBaseSetup(this,log,device); } ISYOutletAccessory.prototype.identify = function(callback) { @@ -343,10 +350,7 @@ ISYOutletAccessory.prototype.getServices = function() { // LOCKS function ISYLockAccessory(log,device) { - this.log = log; - this.device = device; - this.address = device.address; - this.name = device.name; + ISYAccessoryBaseSetup(this,log,device); } ISYLockAccessory.prototype.identify = function(callback) { @@ -416,10 +420,7 @@ ISYLockAccessory.prototype.getServices = function() { // LIGHTS function ISYLightAccessory(log,device) { - this.log = log; - this.device = device; - this.address = device.address; - this.name = device.name; + ISYAccessoryBaseSetup(this,log,device); this.dimmable = (this.device.deviceType == "DimmableLight"); } @@ -513,10 +514,7 @@ ISYLightAccessory.prototype.getServices = function() { // CONTACT SENSOR function ISYDoorWindowSensorAccessory(log,device) { - this.log = log; - this.device = device; - this.address = device.address; - this.name = device.name; + ISYAccessoryBaseSetup(this,log,device); this.doorWindowState = false; } @@ -562,10 +560,7 @@ ISYDoorWindowSensorAccessory.prototype.getServices = function() { // ELK SENSOR PANEL function ISYElkAlarmPanelAccessory(log,device) { - this.log = log; - this.device = device; - this.address = device.address; - this.name = device.name; + ISYAccessoryBaseSetup(this,log,device); } ISYElkAlarmPanelAccessory.prototype.identify = function(callback) { From 3c53b319cb256131ff1817cfa2633fd8abb2132c Mon Sep 17 00:00:00 2001 From: Kraig McConaghy Date: Sun, 18 Oct 2015 13:58:10 -0500 Subject: [PATCH 100/121] Updated to fixed harmonyhubjs-client --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8d1521c..bc26177 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "eibd": "^0.3.1", "elkington": "kevinohara80/elkington", "hap-nodejs": "^0.0.2", - "harmonyhubjs-client": "^1.1.4", + "harmonyhubjs-client": "enriquez/harmonyhubjs-client#patch-1", "harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git", "lifx-api": "^1.0.1", "lifx": "git+https://github.com/magicmonkey/lifxjs.git", From 6008374b9ee22e0c37af9637ff3f496b374e1e03 Mon Sep 17 00:00:00 2001 From: Kraig McConaghy Date: Sun, 18 Oct 2015 14:00:06 -0500 Subject: [PATCH 101/121] Converted LogitechHarmony accessory to be based on new HAP api --- platforms/LogitechHarmony.js | 155 +++++++---------------------------- 1 file changed, 30 insertions(+), 125 deletions(-) diff --git a/platforms/LogitechHarmony.js b/platforms/LogitechHarmony.js index c4ee5a0..a0172a4 100644 --- a/platforms/LogitechHarmony.js +++ b/platforms/LogitechHarmony.js @@ -24,6 +24,12 @@ var harmony = require('harmonyhubjs-client'); var _harmonyHubPort = 61991; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; +var Accessory = require("hap-nodejs").Accessory; +var uuid = require("hap-nodejs").uuid; +var inherits = require('util').inherits; + function sortByKey (array, key) { return array.sort(function(a, b) { @@ -119,7 +125,7 @@ LogitechHarmonyPlatform.prototype = { var sArray = sortByKey(json['result'],"Name"); sArray.map(function(s) { - accessory = new LogitechHarmonyAccessory(self.log, self.server, self.port, false, s.idx, s.Name, s.HaveDimmer, s.MaxDimLevel, (s.SubType=="RGB")||(s.SubType=="RGBW")); + accessory = new LogitechHarmonyActivityAccessory(self.log, self.server, self.port, false, s.idx, s.Name, s.HaveDimmer, s.MaxDimLevel, (s.SubType=="RGB")||(s.SubType=="RGBW")); foundAccessories.push(accessory); }); @@ -139,7 +145,7 @@ LogitechHarmonyPlatform.prototype = { var sArray = sortByKey(activities, "label"); sArray.map(function(s) { - var accessory = new LogitechHarmonyAccessory(self.log, hub, s, true); + var accessory = new LogitechHarmonyActivityAccessory(self.log, hub, s); // TODO: Update the initial power state foundAccessories.push(accessory); }); @@ -153,40 +159,46 @@ LogitechHarmonyPlatform.prototype = { }; -function LogitechHarmonyAccessory (log, hub, details, isActivity) { +function LogitechHarmonyActivityAccessory (log, hub, details) { this.log = log; this.hub = hub; this.details = details; this.id = details.id; this.name = details.label; - this.isActivity = isActivity; - this.isActivityActive = false; + Accessory.call(this, this.name, uuid.generate(this.id)); + + this.getService(Service.AccessoryInformation) + .setCharacteristic(Characteristic.Manufacturer, "Logitech") + .setCharacteristic(Characteristic.Model, "Harmony") + // TODO: Add hub unique id to this for people with multiple hubs so that it is really a guid. + .setCharacteristic(Characteristic.SerialNumber, this.id); + + this.addService(Service.Switch) + .getCharacteristic(Characteristic.On) + .on('get', this.getPowerState) + .on('set', this.setPowerState); +}; +inherits(LogitechHarmonyActivityAccessory, Accessory); +LogitechHarmonyActivityAccessory.prototype.parent = Accessory.prototype; +LogitechHarmonyActivityAccessory.prototype.getServices = function() { + return this.services; }; - -LogitechHarmonyAccessory.prototype = { - // TODO: Somehow make this event driven so that it tells the user what activity is on - getPowerState: function (callback) { + LogitechHarmonyActivityAccessory.prototype.getPowerState = function (callback) { var self = this; - if (this.isActivity) { this.hub.getCurrentActivity().then(function (currentActivity) { callback(currentActivity === self.id); }).catch(function (err) { self.log('Unable to get current activity with error', err); callback(false); }); - } else { - // TODO: Support onRead for devices - this.log('TODO: Support onRead for devices'); - } - }, + }; - setPowerState: function (state) { + LogitechHarmonyActivityAccessory.prototype.setPowerState = function (state) { var self = this; - if (this.isActivity) { this.log('Set activity ' + this.name + ' power state to ' + state); this.hub.startActivity(self.id) @@ -196,114 +208,7 @@ LogitechHarmonyAccessory.prototype = { .catch(function (err) { self.log('Failed setting activity ' + self.name + ' power state to ' + state + ' with error ' + err); }); - } else { - // TODO: Support setting device power - this.log('TODO: Support setting device power'); - // callback(); - } - }, - - getServices: function () { - var self = this; - - return [ - { - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: [ - { - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: self.name, - supportEvents: false, - supportBonjour: false, - manfDescription: "Name of the accessory", - designedMaxLength: 255 - }, - { - cType: types.MANUFACTURER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Logitech", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - }, - { - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: "Harmony", - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - }, - { - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - // TODO: Add hub unique id to this for people with multiple hubs so that it is really a guid. - initialValue: self.id, - 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 - } - ] - }, - { - sType: types.SWITCH_STYPE, - characteristics: [ - { - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: self.name, - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - }, - { - cType: types.POWER_STATE_CTYPE, - onUpdate: function (value) { - self.setPowerState(value) - }, - onRead: function(callback) { - self.getPowerState(callback) - }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1 - } - ] - } - ]; - } - -}; + }; module.exports.platform = LogitechHarmonyPlatform; From e30b504583cab85dd1ea2ee3b38e3e547db89ba1 Mon Sep 17 00:00:00 2001 From: Kraig McConaghy Date: Sun, 18 Oct 2015 16:00:02 -0500 Subject: [PATCH 102/121] Added handling to keep track of activity state --- platforms/LogitechHarmony.js | 90 +++++++++++++++++++++--------------- 1 file changed, 54 insertions(+), 36 deletions(-) diff --git a/platforms/LogitechHarmony.js b/platforms/LogitechHarmony.js index a0172a4..2167b1c 100644 --- a/platforms/LogitechHarmony.js +++ b/platforms/LogitechHarmony.js @@ -63,7 +63,7 @@ LogitechHarmonyPlatform.prototype = { setTimeout(function() { setInterval(function() { self.log("Sending command to prevent timeout"); - client.getCurrentActivity(); + client.getCurrentActivity().then(self.updateCurrentActivity.bind(self)); }, 20000); }, 5000); @@ -99,6 +99,13 @@ LogitechHarmonyPlatform.prototype = { discover.start(); }, + updateCurrentActivity: function(currentActivity) { + var actAccessories = this.activityAccessories; + if (actAccessories instanceof Array) { + actAccessories.map(function(a) { a.updateActivityState(currentActivity); }); + } + }, + accessories: function (callback) { var self = this; var foundAccessories = []; @@ -142,30 +149,36 @@ LogitechHarmonyPlatform.prototype = { .then(function (activities) { self.log("Found activities: \n" + activities.map(function (a) { return "\t" + a.label; }).join("\n")); - var sArray = sortByKey(activities, "label"); - - sArray.map(function(s) { - var accessory = new LogitechHarmonyActivityAccessory(self.log, hub, s); - // TODO: Update the initial power state - foundAccessories.push(accessory); + hub.getCurrentActivity().then(function (currentActivity) { + var actAccessories = []; + var sArray = sortByKey(activities, "label"); + sArray.map(function(s) { + var accessory = new LogitechHarmonyActivityAccessory(self.log, hub, s, self.updateCurrentActivity.bind(self)); + accessory.updateActivityState(currentActivity); + actAccessories.push(accessory); + foundAccessories.push(accessory); + }); + self.activityAccessories = actAccessories; + callback(foundAccessories); + }).catch(function (err) { + self.log('Unable to get current activity with error', err); + callback(false); }); - - callback(foundAccessories); }); }; - } - }; - -function LogitechHarmonyActivityAccessory (log, hub, details) { +function LogitechHarmonyActivityAccessory (log, hub, details, updateCurrentActivity) { this.log = log; this.hub = hub; this.details = details; this.id = details.id; this.name = details.label; + this.isOn = false; + this.updateCurrentActivity = updateCurrentActivity; Accessory.call(this, this.name, uuid.generate(this.id)); + var self = this; this.getService(Service.AccessoryInformation) .setCharacteristic(Characteristic.Manufacturer, "Logitech") @@ -175,8 +188,12 @@ function LogitechHarmonyActivityAccessory (log, hub, details) { this.addService(Service.Switch) .getCharacteristic(Characteristic.On) - .on('get', this.getPowerState) - .on('set', this.setPowerState); + .on('get', function(callback) { + // Refreshed automatically by platform + callback(null, self.isOn); + }) + .on('set', this.setPowerState.bind(this)); + }; inherits(LogitechHarmonyActivityAccessory, Accessory); LogitechHarmonyActivityAccessory.prototype.parent = Accessory.prototype; @@ -184,31 +201,32 @@ LogitechHarmonyActivityAccessory.prototype.getServices = function() { return this.services; }; - // TODO: Somehow make this event driven so that it tells the user what activity is on - LogitechHarmonyActivityAccessory.prototype.getPowerState = function (callback) { - var self = this; +LogitechHarmonyActivityAccessory.prototype.updateActivityState = function (currentActivity) { + this.isOn = (currentActivity === this.id); + // Force get to trigger 'change' if needed + this.getService(Service.Switch) + .getCharacteristic(Characteristic.On) + .getValue(); +}; - this.hub.getCurrentActivity().then(function (currentActivity) { - callback(currentActivity === self.id); - }).catch(function (err) { - self.log('Unable to get current activity with error', err); - callback(false); - }); - }; +LogitechHarmonyActivityAccessory.prototype.setPowerState = function (state, callback) { - LogitechHarmonyActivityAccessory.prototype.setPowerState = function (state) { - var self = this; + var self = this; - this.log('Set activity ' + this.name + ' power state to ' + state); + this.log('Set activity ' + this.name + ' power state to ' + state); - this.hub.startActivity(self.id) - .then(function () { - self.log('Finished setting activity ' + self.name + ' power state to ' + state); - }) - .catch(function (err) { - self.log('Failed setting activity ' + self.name + ' power state to ' + state + ' with error ' + err); - }); - }; + var nextActivity = self.id; + this.hub.startActivity(nextActivity) + .then(function () { + self.log('Finished setting activity ' + self.name + ' power state to ' + state); + self.updateCurrentActivity(nextActivity); + if (callback) callback(null, state); + }) + .catch(function (err) { + self.log('Failed setting activity ' + self.name + ' power state to ' + state + ' with error ' + err); + if (callback) callback(err); + }); +}; module.exports.platform = LogitechHarmonyPlatform; From 6e5c35ec8820e47cf1f890189c8506543e7b06fc Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Sun, 18 Oct 2015 14:23:00 -0700 Subject: [PATCH 103/121] Restore telldus-live See #277 for discussion. --- package.json | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f8bf8e4..6bea480 100644 --- a/package.json +++ b/package.json @@ -16,30 +16,31 @@ "carwingsjs": "0.0.x", "chokidar": "^1.0.5", "color": "0.10.x", + "debug": "^2.2.0", "eibd": "^0.3.1", "elkington": "kevinohara80/elkington", "hap-nodejs": "^0.0.2", "harmonyhubjs-client": "^1.1.4", "harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git", - "lifx-api": "^1.0.1", + "komponist": "0.1.0", "lifx": "git+https://github.com/magicmonkey/lifxjs.git", + "lifx-api": "^1.0.1", "mdns": "^2.2.4", "node-hue-api": "^1.0.5", "node-icontrol": "^0.1.5", "node-milight-promise": "0.0.x", "node-persist": "0.0.x", "q": "1.4.x", - "tough-cookie": "^2.0.0", "request": "2.49.x", "sonos": "0.8.x", + "telldus-live": "^0.2.1", "teslams": "1.0.1", + "tough-cookie": "^2.0.0", "unofficial-nest-api": "git+https://github.com/hachidorii/unofficial_nodejs_nest.git#d8d48edc952b049ff6320ef99afa7b2f04cdee98", "wemo": "0.2.x", "wink-js": "0.0.5", "xml2js": "0.4.x", "xmldoc": "0.1.x", - "komponist" : "0.1.0", - "yamaha-nodejs": "0.4.x", - "debug": "^2.2.0" + "yamaha-nodejs": "0.4.x" } } From 6604c2b69aff3b4156102e0aaa20487413aa4a0f Mon Sep 17 00:00:00 2001 From: Kraig McConaghy Date: Sun, 18 Oct 2015 19:08:46 -0500 Subject: [PATCH 104/121] Changed structure to handle numerous issues plus add enhancements. No activity off properly calls "Power Off" (and thus "Power Off" has been removed). HomeKit responses are properly set back when activity finishes switching. Some amount of handling has been added for timeouts while switching activities as well as request sync (you don't seem to be able to send multiple requests to the hub simultaneously, and the activity switching sometimes times out the connection, so a reconnect system was added). --- package.json | 3 +- platforms/LogitechHarmony.js | 257 ++++++++++++++++++++--------------- 2 files changed, 148 insertions(+), 112 deletions(-) diff --git a/package.json b/package.json index bc26177..65f1b4a 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "komponist" : "0.1.0", "yamaha-nodejs": "0.4.x", "debug": "^2.2.0", - "node-xmpp-client": "1.0.0-alpha23" + "node-xmpp-client": "1.0.0-alpha23", + "queue": "^3.1.0" } } diff --git a/platforms/LogitechHarmony.js b/platforms/LogitechHarmony.js index 2167b1c..c8249f4 100644 --- a/platforms/LogitechHarmony.js +++ b/platforms/LogitechHarmony.js @@ -29,6 +29,7 @@ var Characteristic = require("hap-nodejs").Characteristic; var Accessory = require("hap-nodejs").Accessory; var uuid = require("hap-nodejs").uuid; var inherits = require('util').inherits; +var queue = require('queue'); function sortByKey (array, key) { @@ -47,136 +48,185 @@ function LogitechHarmonyPlatform (log, config) { LogitechHarmonyPlatform.prototype = { - // Find one Harmony remote hub (only support one for now) - locateHub: function (callback) { - var self = this; - - // Connect to a Harmony hub - var createClient = function (ipAddress) { - self.log("Connecting to Logitech Harmony remote hub..."); - - harmony(ipAddress) - .then(function (client) { - self.log("Connected to Logitech Harmony remote hub"); - - // prevent connection from closing - setTimeout(function() { - setInterval(function() { - self.log("Sending command to prevent timeout"); - client.getCurrentActivity().then(self.updateCurrentActivity.bind(self)); - }, 20000); - }, 5000); - - callback(null, client); - }); - }; - - // Use the ip address in configuration if available - if (this.ip_address) { - console.log("Using Logitech Harmony hub ip address from configuration"); - - return createClient(this.ip_address) - } - - this.log("Searching for Logitech Harmony remote hubs..."); - - // Discover the harmony hub with bonjour - var discover = new harmonyDiscover(_harmonyHubPort); - - // TODO: Support update event with some way to add accessories - // TODO: Have some kind of timeout with an error message. Right now this searches forever until it finds one hub. - discover.on('online', function (hubInfo) { - self.log("Found Logitech Harmony remote hub: " + hubInfo.ip); - - // Stop looking for hubs once we find the first one - // TODO: Support multiple hubs - discover.stop(); - - createClient(hubInfo.ip); - }); - - // Start looking for hubs - discover.start(); - }, - - updateCurrentActivity: function(currentActivity) { - var actAccessories = this.activityAccessories; - if (actAccessories instanceof Array) { - actAccessories.map(function(a) { a.updateActivityState(currentActivity); }); - } - }, - accessories: function (callback) { - var self = this; + var plat = this; var foundAccessories = []; + var activityAccessories = []; + var hub = null; + var hubIP = null; + var hubQueue = queue(); + hubQueue.concurrency = 1; // Get the first hub - this.locateHub(function (err, hub) { + locateHub(function (err, client, clientIP) { if (err) throw err; - self.log("Fetching Logitech Harmony devices and activites..."); + plat.log("Fetching Logitech Harmony devices and activites..."); + hub = client; + hubIP = clientIP; //getDevices(hub); - getActivities(hub); + getActivities(); }); - // Get Harmony Devices - /* - var getDevices = function(hub) { - self.log("Fetching Logitech Harmony devices..."); + // Find one Harmony remote hub (only support one for now) + function locateHub(callback) { + // Use the ip address in configuration if available + if (plat.ip_address) { + console.log("Using Logitech Harmony hub ip address from configuration"); - hub.getDevices() - .then(function (devices) { - self.log("Found devices: ", devices); + return createClient(plat.ip_address, callback) + } - var sArray = sortByKey(json['result'],"Name"); + plat.log("Searching for Logitech Harmony remote hubs..."); - sArray.map(function(s) { - accessory = new LogitechHarmonyActivityAccessory(self.log, self.server, self.port, false, s.idx, s.Name, s.HaveDimmer, s.MaxDimLevel, (s.SubType=="RGB")||(s.SubType=="RGBW")); - foundAccessories.push(accessory); + // Discover the harmony hub with bonjour + var discover = new harmonyDiscover(_harmonyHubPort); + + // TODO: Support update event with some way to add accessories + // TODO: Have some kind of timeout with an error message. Right now this searches forever until it finds one hub. + discover.on('online', function (hubInfo) { + plat.log("Found Logitech Harmony remote hub: " + hubInfo.ip); + + // Stop looking for hubs once we find the first one + // TODO: Support multiple hubs + discover.stop(); + + createClient(hubInfo.ip, callback); + }); + + // Start looking for hubs + discover.start(); + } + + // Connect to a Harmony hub + function createClient(ipAddress, callback) { + plat.log("Connecting to Logitech Harmony remote hub..."); + harmony(ipAddress) + .then(function (client) { + plat.log("Connected to Logitech Harmony remote hub"); + callback(null, client, ipAddress); }); - - callback(foundAccessories); - }); - }; - */ + } // Get Harmony Activities - var getActivities = function(hub) { - self.log("Fetching Logitech Harmony activities..."); + function getActivities() { + plat.log("Fetching Logitech Harmony activities..."); hub.getActivities() .then(function (activities) { - self.log("Found activities: \n" + activities.map(function (a) { return "\t" + a.label; }).join("\n")); + plat.log("Found activities: \n" + activities.map(function (a) { return "\t" + a.label; }).join("\n")); hub.getCurrentActivity().then(function (currentActivity) { var actAccessories = []; var sArray = sortByKey(activities, "label"); sArray.map(function(s) { - var accessory = new LogitechHarmonyActivityAccessory(self.log, hub, s, self.updateCurrentActivity.bind(self)); - accessory.updateActivityState(currentActivity); - actAccessories.push(accessory); - foundAccessories.push(accessory); + var accessory = createActivityAccessory(s); + if (accessory.id > 0) { + accessory.updateActivityState(currentActivity); + actAccessories.push(accessory); + foundAccessories.push(accessory); + } }); - self.activityAccessories = actAccessories; + activityAccessories = actAccessories; + keepAliveRefreshLoop(); callback(foundAccessories); }).catch(function (err) { - self.log('Unable to get current activity with error', err); - callback(false); + plat.log('Unable to get current activity with error', err); + throw err; }); }); - }; + } + + function createActivityAccessory(activity) { + var accessory = new LogitechHarmonyActivityAccessory(plat.log, activity, changeCurrentActivity.bind(plat), -1); + return accessory; + } + + var isChangingActivity = false; + function changeCurrentActivity(nextActivity, callback) { + if (!nextActivity) { + nextActivity = -1; + } + plat.log('Queue activity to ' + nextActivity); + executeOnHub(function(h, cb) { + plat.log('Set activity to ' + nextActivity); + h.startActivity(nextActivity) + .then(function () { + cb(); + isChangingActivity = false; + plat.log('Finished setting activity to ' + nextActivity); + updateCurrentActivity(nextActivity); + if (callback) callback(null, nextActivity); + }) + .catch(function (err) { + cb(); + isChangingActivity = false; + plat.log('Failed setting activity to ' + nextActivity + ' with error ' + err); + if (callback) callback(err); + }); + }, function(){ + callback(Error("Set activity failed too many times")); + }); + } + + function updateCurrentActivity(currentActivity) { + var actAccessories = activityAccessories; + if (actAccessories instanceof Array) { + actAccessories.map(function(a) { a.updateActivityState(currentActivity); }); + } + } + + // prevent connection from closing + function keepAliveRefreshLoop() { + setTimeout(function() { + setInterval(function() { + executeOnHub(function(h, cb) { + plat.log("Refresh Status"); + h.getCurrentActivity() + .then(function(currentActivity){ + cb(); + updateCurrentActivity(currentActivity); + }) + .catch(cb); + }); + }, 20000); + }, 5000); + } + + function executeOnHub(func, funcMaxTimeout) + { + if (!func) return; + hubQueue.push(function(cb) { + var tout = setTimeout(function(){ + plat.log("Reconnecting to Hub " + hubIP); + createClient(hubIP, function(err, newHub){ + if (err) throw err; + hub = newHub; + if (funcMaxTimeout) { + funcMaxTimeout(); + } + cb(); + }); + }, 30000); + func(hub, function(){ + clearTimeout(tout); + cb(); + }); + }); + if (!hubQueue.running){ + hubQueue.start(); + } + } } }; -function LogitechHarmonyActivityAccessory (log, hub, details, updateCurrentActivity) { +function LogitechHarmonyActivityAccessory (log, details, changeCurrentActivity) { this.log = log; - this.hub = hub; - this.details = details; this.id = details.id; this.name = details.label; this.isOn = false; - this.updateCurrentActivity = updateCurrentActivity; + this.changeCurrentActivity = changeCurrentActivity; Accessory.call(this, this.name, uuid.generate(this.id)); var self = this; @@ -194,7 +244,7 @@ function LogitechHarmonyActivityAccessory (log, hub, details, updateCurrentActiv }) .on('set', this.setPowerState.bind(this)); -}; +} inherits(LogitechHarmonyActivityAccessory, Accessory); LogitechHarmonyActivityAccessory.prototype.parent = Accessory.prototype; LogitechHarmonyActivityAccessory.prototype.getServices = function() { @@ -210,22 +260,7 @@ LogitechHarmonyActivityAccessory.prototype.updateActivityState = function (curre }; LogitechHarmonyActivityAccessory.prototype.setPowerState = function (state, callback) { - - var self = this; - - this.log('Set activity ' + this.name + ' power state to ' + state); - - var nextActivity = self.id; - this.hub.startActivity(nextActivity) - .then(function () { - self.log('Finished setting activity ' + self.name + ' power state to ' + state); - self.updateCurrentActivity(nextActivity); - if (callback) callback(null, state); - }) - .catch(function (err) { - self.log('Failed setting activity ' + self.name + ' power state to ' + state + ' with error ' + err); - if (callback) callback(err); - }); + this.changeCurrentActivity(state ? this.id : null, callback); }; module.exports.platform = LogitechHarmonyPlatform; From 8fcd5d39a6b3756d5f9860bd9619cd21258cdeba Mon Sep 17 00:00:00 2001 From: rodtoll Date: Sun, 18 Oct 2015 19:47:30 -0700 Subject: [PATCH 105/121] Moved all debug logging to be controlled by the ISYJSDEBUG environment variable AND moved all log messages to go through the global logger offered by homebridge. --- platforms/isy-js.js | 50 +++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/platforms/isy-js.js b/platforms/isy-js.js index 03ca340..3e24ab8 100644 --- a/platforms/isy-js.js +++ b/platforms/isy-js.js @@ -67,6 +67,12 @@ function ISYChangeHandler(isy,device) { } } +function ISYJSDebugMessage(isy,message) { + if(process.env.ISYJSDEBUG != undefined) { + isy.log(message); + } +} + //////////////////////////////////////////////////////////////////////////////////////////////// // PLATFORM @@ -100,7 +106,7 @@ ISYPlatform.prototype.shouldIgnore = function(device) { continue; } } - console.log("@@@@@@ Ignoring device: "+deviceName+" ["+deviceAddress+"] because of rule ["+rule.nameContains+"] ["+rule.lastAddressDigit+"] ["+rule.address+"]"); + ISYJSDebugMessage(this,"Ignoring device: "+deviceName+" ["+deviceAddress+"] because of rule ["+rule.nameContains+"] ["+rule.lastAddressDigit+"] ["+rule.address+"]"); return true; } @@ -144,7 +150,7 @@ ISYPlatform.prototype.accessories = function(callback) { deviceMap[panelDevice.address] = panelDeviceHK; results.push(panelDeviceHK); } - console.log("Filtered device has: "+results.length+" devices"); + ISYJSDebugMessage(that,"Filtered device has: "+results.length+" devices"); callback(results); }); } @@ -182,7 +188,7 @@ ISYFanAccessory.prototype.translateFanSpeedToHK = function(fanSpeed) { } else if(fanSpeed == "High") { return 100; } else { - this.log("!!!! ERROR: Unknown fan speed: "+fanSpeed); + ISYJSDebugMessage(this,"!!!! ERROR: Unknown fan speed: "+fanSpeed); return 0; } } @@ -197,7 +203,7 @@ ISYFanAccessory.prototype.translateHKToFanSpeed = function(fanStateHK) { } else if(fanStateHK > 67) { return "High"; } else { - this.log("!!!!! ERROR: Unknown fan state!"); + ISYJSDebugMessage(this,"ERROR: Unknown fan state!"); return "Off"; } } @@ -208,13 +214,13 @@ ISYFanAccessory.prototype.getFanRotationSpeed = function(callback) { ISYFanAccessory.prototype.setFanRotationSpeed = function(fanStateHK,callback) { var newFanState = this.translateHKToFanSpeed(fanStateHK); - this.log("Sending command to set fan state to: "+newFanState); + ISYJSDebugMessage(this,"Sending command to set fan state to: "+newFanState); if(newFanState != this.device.getCurrentFanState()) { this.device.sendFanCommand(newFanState, function(result) { callback(); }); } else { - this.log("Fan command does not change actual speed"); + ISYJSDebugMessage(this,"Fan command does not change actual speed"); callback(); } } @@ -236,7 +242,7 @@ ISYFanAccessory.prototype.setFanOnState = function(onState,callback) { this.setFanRotationSpeed(this.translateFanSpeedToHK("Off"), callback); } } else { - this.log("Fan command does not change actual state"); + ISYJSDebugMessage(this,"Fan command does not change actual state"); callback(); } } @@ -295,7 +301,7 @@ ISYOutletAccessory.prototype.identify = function(callback) { } ISYOutletAccessory.prototype.setOutletState = function(outletState,callback) { - this.log("Sending command to set outlet state to: "+outletState); + ISYJSDebugMessage(this,"Sending command to set outlet state to: "+outletState); if(outletState != this.device.getCurrentOutletState()) { this.device.sendOutletCommand(outletState, function(result) { callback(); @@ -358,7 +364,7 @@ ISYLockAccessory.prototype.identify = function(callback) { } ISYLockAccessory.prototype.setTargetLockState = function(lockState,callback) { - this.log("Sending command to set lock state to: "+lockState); + ISYJSDebugMessage(this,"Sending command to set lock state to: "+lockState); if(lockState != this.getDeviceCurrentStateAsHK()) { var targetLockValue = (lockState == 0) ? false : true; this.device.sendLockCommand(targetLockValue, function(result) { @@ -433,20 +439,20 @@ ISYLightAccessory.prototype.identify = function(callback) { } ISYLightAccessory.prototype.setPowerState = function(powerOn,callback) { - this.log("=== Setting powerstate to %s", powerOn); + ISYJSDebugMessage(this,"Setting powerstate to %s", powerOn); if(powerOn != this.device.getCurrentLightState()) { - this.log("+++ Changing powerstate to "+powerOn); + ISYJSDebugMessage(this,"Changing powerstate to "+powerOn); this.device.sendLightCommand(powerOn, function(result) { callback(); }); } else { - this.log("--- Ignoring redundant setPowerState"); + ISYJSDebugMessage(this,"Ignoring redundant setPowerState"); callback(); } } ISYLightAccessory.prototype.handleExternalChange = function() { - this.log("=== Handling external change for light"); + ISYJSDebugMessage(this,"Handling external change for light"); this.lightService .setCharacteristic(Characteristic.On, this.device.getCurrentLightState()); if(this.device.deviceType == this.device.isy.DEVICE_TYPE_DIMMABLE_LIGHT) { @@ -460,14 +466,14 @@ ISYLightAccessory.prototype.getPowerState = function(callback) { } ISYLightAccessory.prototype.setBrightness = function(level,callback) { - this.log("Setting brightness to %s", level); + ISYJSDebugMessage(this,"Setting brightness to %s", level); if(level != this.device.getCurrentLightDimState()) { - this.log("+++ Changing Brightness to "+level); + ISYJSDebugMessage(this,"Changing Brightness to "+level); this.device.sendLightDimCommand(level, function(result) { callback(); }); } else { - this.log("--- Ignoring redundant setBrightness"); + ISYJSDebugMessage(this,"Ignoring redundant setBrightness"); callback(); } } @@ -568,15 +574,15 @@ ISYElkAlarmPanelAccessory.prototype.identify = function(callback) { } ISYElkAlarmPanelAccessory.prototype.setAlarmTargetState = function(targetStateHK,callback) { - this.log("***** Sending command to set alarm panel state to: "+targetStateHK); + ISYJSDebugMessage(this,"Sending command to set alarm panel state to: "+targetStateHK); var targetState = this.translateHKToAlarmTargetState(targetStateHK); - this.log("***** Would send the target state of: "+targetState); + ISYJSDebugMessage(this,"Would send the target state of: "+targetState); if(this.device.getAlarmMode() != targetState) { this.device.sendSetAlarmModeCommand(targetState, function(result) { callback(); }); } else { - this.log("***** Redundant command, already in that state."); + ISYJSDebugMessage(this,"Redundant command, already in that state."); callback(); } } @@ -599,7 +605,7 @@ ISYElkAlarmPanelAccessory.prototype.translateAlarmCurrentStateToHK = function() } else if(sourceAlarmState == this.device.ALARM_MODE_NIGHT || sourceAlarmState == this.device.ALARM_MODE_NIGHT_INSTANT) { return Characteristic.SecuritySystemCurrentState.NIGHT_ARM; } else { - this.log("***** Setting to disarmed because sourceAlarmState is "+sourceAlarmState); + ISYJSDebugMessage(this,"Setting to disarmed because sourceAlarmState is "+sourceAlarmState); return Characteristic.SecuritySystemCurrentState.DISARMED; } } @@ -639,8 +645,8 @@ ISYElkAlarmPanelAccessory.prototype.getAlarmCurrentState = function(callback) { } ISYElkAlarmPanelAccessory.prototype.handleExternalChange = function() { - this.log("***** Source device. Currenty state locally -"+this.device.getAlarmStatusAsText()); - this.log("***** Got alarm change notification. Setting HK target state to: "+this.translateAlarmTargetStateToHK()+" Setting HK Current state to: "+this.translateAlarmCurrentStateToHK()); + ISYJSDebugMessage(this,"Source device. Currenty state locally -"+this.device.getAlarmStatusAsText()); + ISYJSDebugMessage(this,"Got alarm change notification. Setting HK target state to: "+this.translateAlarmTargetStateToHK()+" Setting HK Current state to: "+this.translateAlarmCurrentStateToHK()); this.alarmPanelService .setCharacteristic(Characteristic.SecuritySystemTargetState, this.translateAlarmTargetStateToHK()); this.alarmPanelService From 74e37cc488288f11b201fb218f81b088844a5e6c Mon Sep 17 00:00:00 2001 From: rodtoll Date: Sun, 18 Oct 2015 21:09:59 -0700 Subject: [PATCH 106/121] Added more documentation. --- platforms/isy-js.js | 108 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 90 insertions(+), 18 deletions(-) diff --git a/platforms/isy-js.js b/platforms/isy-js.js index 3e24ab8..b2c3728 100644 --- a/platforms/isy-js.js +++ b/platforms/isy-js.js @@ -48,6 +48,8 @@ { "nameContains": "Keypad", "lastAddressDigit": "2", "address": "" } - Ignore all devices which have the word Keypad in their name and whose last address digit is 2. { "nameContains": "Remote", "lastAddressDigit": "", "address": "" } - Ignore all devices which have the word Remote in their name { "nameContains": "", "lastAddressDigit": "", "address": "15 5 3 2"} - Ignore the device with an ISY address of 15 5 3 2. + + TODOS: Implement identify functions (beep perhaps?) and more device types. */ @@ -60,6 +62,9 @@ var inherits = require('util').inherits; // Global device map. Needed to map incoming notifications to the corresponding HomeKit device for update. var deviceMap = {}; +// This function responds to changes in devices from the isy-js library. Uses the global device map to update +// the state. +// TODO: Move this to a member function of the ISYPlatform object so we don't need a global map. function ISYChangeHandler(isy,device) { var deviceToUpdate = deviceMap[device.address]; if(deviceToUpdate != null) { @@ -67,6 +72,7 @@ function ISYChangeHandler(isy,device) { } } +// Helper function to have ISYJSDEBUG control if debug output appears function ISYJSDebugMessage(isy,message) { if(process.env.ISYJSDEBUG != undefined) { isy.log(message); @@ -76,6 +82,7 @@ function ISYJSDebugMessage(isy,message) { //////////////////////////////////////////////////////////////////////////////////////////////// // PLATFORM +// Construct the ISY platform. log = Logger, config = homebridge cofnig function ISYPlatform(log,config) { this.log = log; this.config = config; @@ -86,6 +93,7 @@ function ISYPlatform(log,config) { this.isy = new isy.ISY(this.host, this.username,this.password, config.elkEnabled, ISYChangeHandler); } +// Checks the device against the configuration to see if it should be ignored. ISYPlatform.prototype.shouldIgnore = function(device) { var deviceAddress = device.address; var deviceName = device.name; @@ -113,6 +121,7 @@ ISYPlatform.prototype.shouldIgnore = function(device) { return false; } +// Calls the isy-js library, retrieves the list of devices, and maps them to appropriate ISYXXXXAccessory devices. ISYPlatform.prototype.accessories = function(callback) { var that = this; this.isy.initialize(function() { @@ -139,6 +148,7 @@ ISYPlatform.prototype.accessories = function(callback) { homeKitDevice = new ISYElkAlarmPanelAccessory(that.log,device); } if(homeKitDevice != null) { + // Make sure the device is address to the global map deviceMap[device.address] = homeKitDevice; results.push(homeKitDevice); } @@ -158,6 +168,7 @@ ISYPlatform.prototype.accessories = function(callback) { ///////////////////////////////////////////////////////////////////////////////////////////////// // BASE FOR ALL DEVICES +// Provides common constructor tasks function ISYAccessoryBaseSetup(accessory,log,device) { accessory.log = log; accessory.device = device; @@ -167,8 +178,10 @@ function ISYAccessoryBaseSetup(accessory,log,device) { } ///////////////////////////////////////////////////////////////////////////////////////////////// -// FANS +// FANS - ISYFanAccessory +// Implemetnts the fan service for an isy fan device. +// Constructs a fan accessory object. device is the isy-js device object and log is the logger. function ISYFanAccessory(log,device) { ISYAccessoryBaseSetup(this,log,device); } @@ -178,14 +191,17 @@ ISYFanAccessory.prototype.identify = function(callback) { callback(); } +// Translates the fan speed as an isy-js string into the corresponding homekit constant level. +// Homekit doesn't have steps for the fan speed and needs to have a value from 0 to 100. We +// split the range into 4 steps and map them to the 4 isy-js levels. ISYFanAccessory.prototype.translateFanSpeedToHK = function(fanSpeed) { - if(fanSpeed == "Off") { + if(fanSpeed == this.device.isy.ISYFanDevice.FAN_OFF) { return 0; - } else if(fanSpeed == "Low") { + } else if(fanSpeed == this.device.isy.ISYFanDevice.FAN_LOW) { return 32; - } else if(fanSpeed == "Medium") { + } else if(fanSpeed == this.device.isy.ISYFanDevice.FAN_MEDIUM) { return 67; - } else if(fanSpeed == "High") { + } else if(fanSpeed == this.device.isy.ISYFanDevice.FAN_HIGH) { return 100; } else { ISYJSDebugMessage(this,"!!!! ERROR: Unknown fan speed: "+fanSpeed); @@ -193,25 +209,29 @@ ISYFanAccessory.prototype.translateFanSpeedToHK = function(fanSpeed) { } } +// Translates the fan level from homebridge into the isy-js level. Maps from the 0-100 +// to the four isy-js fan speed levels. ISYFanAccessory.prototype.translateHKToFanSpeed = function(fanStateHK) { if(fanStateHK == 0) { - return "Off"; + return this.device.isy.ISYFanDevice.FAN_OFF; } else if(fanStateHK > 0 && fanStateHK <=32) { - return "Low"; + return this.device.isy.ISYFanDevice.FAN_LOW; } else if(fanStateHK > 33 && fanStateHK <= 67) { - return "Medium"; + return this.device.isy.ISYFanDevice.FAN_MEDIUM; } else if(fanStateHK > 67) { - return "High"; + return this.device.isy.ISYFanDevice.FAN_HIGH; } else { ISYJSDebugMessage(this,"ERROR: Unknown fan state!"); - return "Off"; + return this.device.isy.ISYFanDevice.FAN_OFF; } } +// Returns the current state of the fan from the isy-js level to the 0-100 level of HK. ISYFanAccessory.prototype.getFanRotationSpeed = function(callback) { callback(null,this.translateFanSpeedToHK(this.device.getCurrentFanState())); } +// Sets the current state of the fan from the 0-100 level of HK to the isy-js level. ISYFanAccessory.prototype.setFanRotationSpeed = function(fanStateHK,callback) { var newFanState = this.translateHKToFanSpeed(fanStateHK); ISYJSDebugMessage(this,"Sending command to set fan state to: "+newFanState); @@ -225,21 +245,23 @@ ISYFanAccessory.prototype.setFanRotationSpeed = function(fanStateHK,callback) { } } - +// Returns true if the fan is on ISYFanAccessory.prototype.getIsFanOn = function() { return (this.device.getCurrentFanState() != "Off"); } +// Returns the state of the fan to the homebridge system for the On characteristic ISYFanAccessory.prototype.getFanOnState = function(callback) { callback(null,this.getIsFanOn()); } +// Sets the fan state based on the value of the On characteristic. Default to Medium for on. ISYFanAccessory.prototype.setFanOnState = function(onState,callback) { if(onState != this.getIsFanOn()) { if(onState) { - this.setFanRotationSpeed(this.translateFanSpeedToHK("Medium"), callback); + this.setFanRotationSpeed(this.translateFanSpeedToHK(this.device.isy.ISYFanDevice.FAN_MEDIUM), callback); } else { - this.setFanRotationSpeed(this.translateFanSpeedToHK("Off"), callback); + this.setFanRotationSpeed(this.translateFanSpeedToHK(this.device.isy.ISYFanDevice.FAN_OFF), callback); } } else { ISYJSDebugMessage(this,"Fan command does not change actual state"); @@ -247,7 +269,7 @@ ISYFanAccessory.prototype.setFanOnState = function(onState,callback) { } } - +// Mirrors change in the state of the underlying isj-js device object. ISYFanAccessory.prototype.handleExternalChange = function() { this.fanService .setCharacteristic(Characteristic.On, this.getIsFanOn()); @@ -256,6 +278,7 @@ ISYFanAccessory.prototype.handleExternalChange = function() { .setCharacteristic(Characteristic.RotationSpeed, this.translateFanSpeedToHK(this.device.getCurrentFanState())); } +// Returns the services supported by the fan device. ISYFanAccessory.prototype.getServices = function() { var informationService = new Service.AccessoryInformation(); @@ -289,17 +312,21 @@ ISYFanAccessory.prototype.getServices = function() { } ///////////////////////////////////////////////////////////////////////////////////////////////// -// OUTLETS +// OUTLETS - ISYOutletAccessory +// Implements the Outlet service for ISY devices. +// Constructs an outlet. log = HomeBridge logger, device = isy-js device to wrap function ISYOutletAccessory(log,device) { ISYAccessoryBaseSetup(this,log,device); } +// Handles the identify command ISYOutletAccessory.prototype.identify = function(callback) { // Do the identify action callback(); } +// Handles a request to set the outlet state. Ignores redundant sets based on current states. ISYOutletAccessory.prototype.setOutletState = function(outletState,callback) { ISYJSDebugMessage(this,"Sending command to set outlet state to: "+outletState); if(outletState != this.device.getCurrentOutletState()) { @@ -311,19 +338,24 @@ ISYOutletAccessory.prototype.setOutletState = function(outletState,callback) { } } +// Handles a request to get the current outlet state based on underlying isy-js device object. ISYOutletAccessory.prototype.getOutletState = function(callback) { callback(null,this.device.getCurrentOutletState()); } +// Handles a request to get the current in use state of the outlet. We set this to true always as +// there is no way to deterine this through the isy. ISYOutletAccessory.prototype.getOutletInUseState = function(callback) { callback(null, true); } +// Mirrors change in the state of the underlying isj-js device object. ISYOutletAccessory.prototype.handleExternalChange = function() { this.outletService .setCharacteristic(Characteristic.On, this.device.getCurrentOutletState()); } +// Returns the set of services supported by this object. ISYOutletAccessory.prototype.getServices = function() { var informationService = new Service.AccessoryInformation(); @@ -353,16 +385,20 @@ ISYOutletAccessory.prototype.getServices = function() { } ///////////////////////////////////////////////////////////////////////////////////////////////// -// LOCKS +// LOCKS - ISYLockAccessory +// Implements the lock service for isy-js devices. +// Constructs a lock accessory. log = homebridge logger, device = isy-js device object being wrapped function ISYLockAccessory(log,device) { ISYAccessoryBaseSetup(this,log,device); } +// Handles an identify request ISYLockAccessory.prototype.identify = function(callback) { callback(); } +// Handles a set to the target lock state. Will ignore redundant commands. ISYLockAccessory.prototype.setTargetLockState = function(lockState,callback) { ISYJSDebugMessage(this,"Sending command to set lock state to: "+lockState); if(lockState != this.getDeviceCurrentStateAsHK()) { @@ -375,18 +411,22 @@ ISYLockAccessory.prototype.setTargetLockState = function(lockState,callback) { } } +// Translates underlying lock state into the corresponding homekit state ISYLockAccessory.prototype.getDeviceCurrentStateAsHK = function() { return (this.device.getCurrentLockState() ? 1 : 0); } +// Handles request to get the current lock state for homekit ISYLockAccessory.prototype.getLockCurrentState = function(callback) { callback(null, this.getDeviceCurrentStateAsHK()); } +// Handles request to get the target lock state for homekit ISYLockAccessory.prototype.getTargetLockState = function(callback) { this.getLockCurrentState(callback); } +// Mirrors change in the state of the underlying isj-js device object. ISYLockAccessory.prototype.handleExternalChange = function() { this.lockService .setCharacteristic(Characteristic.LockTargetState, this.getDeviceCurrentStateAsHK()); @@ -394,6 +434,7 @@ ISYLockAccessory.prototype.handleExternalChange = function() { .setCharacteristic(Characteristic.LockCurrentState, this.getDeviceCurrentStateAsHK()); } +// Returns the set of services supported by this object. ISYLockAccessory.prototype.getServices = function() { var informationService = new Service.AccessoryInformation(); @@ -424,12 +465,16 @@ ISYLockAccessory.prototype.getServices = function() { //////////////////////////////////////////////////////////////////////////////////////////////////////// // LIGHTS +// Implements the Light service for homekit based on an underlying isy-js device. Is dimmable or not depending +// on if the underlying device is dimmable. +// Constructs the light accessory. log = homebridge logger, device = isy-js device object being wrapped function ISYLightAccessory(log,device) { ISYAccessoryBaseSetup(this,log,device); this.dimmable = (this.device.deviceType == "DimmableLight"); } +// Handles the identify command ISYLightAccessory.prototype.identify = function(callback) { this.device.sendLightCommand(true, function(result) { this.device.sendLightCommand(false, function(result) { @@ -438,6 +483,7 @@ ISYLightAccessory.prototype.identify = function(callback) { }); } +// Handles request to set the current powerstate from homekit. Will ignore redundant commands. ISYLightAccessory.prototype.setPowerState = function(powerOn,callback) { ISYJSDebugMessage(this,"Setting powerstate to %s", powerOn); if(powerOn != this.device.getCurrentLightState()) { @@ -451,6 +497,7 @@ ISYLightAccessory.prototype.setPowerState = function(powerOn,callback) { } } +// Mirrors change in the state of the underlying isj-js device object. ISYLightAccessory.prototype.handleExternalChange = function() { ISYJSDebugMessage(this,"Handling external change for light"); this.lightService @@ -461,10 +508,12 @@ ISYLightAccessory.prototype.handleExternalChange = function() { } } +// Handles request to get the current on state ISYLightAccessory.prototype.getPowerState = function(callback) { callback(null,this.device.getCurrentLightState()); } +// Handles request to set the brightness level of dimmable lights. Ignore redundant commands. ISYLightAccessory.prototype.setBrightness = function(level,callback) { ISYJSDebugMessage(this,"Setting brightness to %s", level); if(level != this.device.getCurrentLightDimState()) { @@ -478,10 +527,12 @@ ISYLightAccessory.prototype.setBrightness = function(level,callback) { } } +// Handles a request to get the current brightness level for dimmable lights. ISYLightAccessory.prototype.getBrightness = function(callback) { callback(null,this.device.getCurrentLightDimState()); } +// Returns the set of services supported by this object. ISYLightAccessory.prototype.getServices = function() { var informationService = new Service.AccessoryInformation(); @@ -517,31 +568,38 @@ ISYLightAccessory.prototype.getServices = function() { } ///////////////////////////////////////////////////////////////////////////////////////////////// -// CONTACT SENSOR +// CONTACT SENSOR - ISYDoorWindowSensorAccessory +// Implements the ContactSensor service. +// Constructs a Door Window Sensor (contact sensor) accessory. log = HomeBridge logger, device = wrapped isy-js device. function ISYDoorWindowSensorAccessory(log,device) { ISYAccessoryBaseSetup(this,log,device); this.doorWindowState = false; } +// Handles the identify command. ISYDoorWindowSensorAccessory.prototype.identify = function(callback) { // Do the identify action callback(); } +// Translates the state of the underlying device object into the corresponding homekit compatible state ISYDoorWindowSensorAccessory.prototype.translateCurrentDoorWindowState = function() { return (this.device.getCurrentDoorWindowState()) ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED; } +// Handles the request to get he current door window state. ISYDoorWindowSensorAccessory.prototype.getCurrentDoorWindowState = function(callback) { callback(null,this.translateCurrentDoorWindowState()); } +// Mirrors change in the state of the underlying isj-js device object. ISYDoorWindowSensorAccessory.prototype.handleExternalChange = function() { this.sensorService .setCharacteristic(Characteristic.ContactSensorState, this.translateCurrentDoorWindowState()); } +// Returns the set of services supported by this object. ISYDoorWindowSensorAccessory.prototype.getServices = function() { var informationService = new Service.AccessoryInformation(); @@ -563,16 +621,20 @@ ISYDoorWindowSensorAccessory.prototype.getServices = function() { } ///////////////////////////////////////////////////////////////////////////////////////////////// -// ELK SENSOR PANEL +// ELK SENSOR PANEL - ISYElkAlarmPanelAccessory +// Implements the SecuritySystem service for an elk security panel connected to the isy system +// Constructs the alarm panel accessory. log = HomeBridge logger, device = underlying isy-js device being wrapped function ISYElkAlarmPanelAccessory(log,device) { ISYAccessoryBaseSetup(this,log,device); } +// Handles the identify command ISYElkAlarmPanelAccessory.prototype.identify = function(callback) { callback(); } +// Handles the request to set the alarm target state ISYElkAlarmPanelAccessory.prototype.setAlarmTargetState = function(targetStateHK,callback) { ISYJSDebugMessage(this,"Sending command to set alarm panel state to: "+targetStateHK); var targetState = this.translateHKToAlarmTargetState(targetStateHK); @@ -587,6 +649,10 @@ ISYElkAlarmPanelAccessory.prototype.setAlarmTargetState = function(targetStateHK } } +// Translates from the current state of the elk alarm system into a homekit compatible state. The elk panel has a lot more +// possible states then can be directly represented by homekit so we map them. If the alarm is going off then it is tripped. +// If it is arming or armed it is considered armed. Stay maps to the state state, away to the away state, night to the night +// state. ISYElkAlarmPanelAccessory.prototype.translateAlarmCurrentStateToHK = function() { var tripState = this.device.getAlarmTripState(); var sourceAlarmState = this.device.getAlarmState(); @@ -611,6 +677,7 @@ ISYElkAlarmPanelAccessory.prototype.translateAlarmCurrentStateToHK = function() } } +// Translates the current target state of hthe underlying alarm into the appropriate homekit value ISYElkAlarmPanelAccessory.prototype.translateAlarmTargetStateToHK = function() { var sourceAlarmState = this.device.getAlarmMode(); if(sourceAlarmState == this.device.ALARM_MODE_STAY || sourceAlarmState == this.device.ALARM_MODE_STAY_INSTANT ) { @@ -624,6 +691,7 @@ ISYElkAlarmPanelAccessory.prototype.translateAlarmTargetStateToHK = function() { } } +// Translates the homekit version of the alarm target state into the appropriate elk alarm panel state ISYElkAlarmPanelAccessory.prototype.translateHKToAlarmTargetState = function(state) { if(state == Characteristic.SecuritySystemTargetState.STAY_ARM) { return this.device.ALARM_MODE_STAY; @@ -636,14 +704,17 @@ ISYElkAlarmPanelAccessory.prototype.translateHKToAlarmTargetState = function(sta } } +// Handles request to get the target alarm state ISYElkAlarmPanelAccessory.prototype.getAlarmTargetState = function(callback) { callback(null,this.translateAlarmTargetStateToHK()); } +// Handles request to get the current alarm state ISYElkAlarmPanelAccessory.prototype.getAlarmCurrentState = function(callback) { callback(null,this.translateAlarmCurrentStateToHK()); } +// Mirrors change in the state of the underlying isj-js device object. ISYElkAlarmPanelAccessory.prototype.handleExternalChange = function() { ISYJSDebugMessage(this,"Source device. Currenty state locally -"+this.device.getAlarmStatusAsText()); ISYJSDebugMessage(this,"Got alarm change notification. Setting HK target state to: "+this.translateAlarmTargetStateToHK()+" Setting HK Current state to: "+this.translateAlarmCurrentStateToHK()); @@ -653,6 +724,7 @@ ISYElkAlarmPanelAccessory.prototype.handleExternalChange = function() { .setCharacteristic(Characteristic.SecuritySystemCurrentState, this.translateAlarmCurrentStateToHK()); } +// Returns the set of services supported by this object. ISYElkAlarmPanelAccessory.prototype.getServices = function() { var informationService = new Service.AccessoryInformation(); From 7b24d142b4156fe69619f0560ca73ad7e66d4e7c Mon Sep 17 00:00:00 2001 From: tommasomarchionni Date: Mon, 19 Oct 2015 14:52:08 +0200 Subject: [PATCH 107/121] Create Openhab.js --- platforms/Openhab.js | 347 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 platforms/Openhab.js diff --git a/platforms/Openhab.js b/platforms/Openhab.js new file mode 100644 index 0000000..a148240 --- /dev/null +++ b/platforms/Openhab.js @@ -0,0 +1,347 @@ +// OpenHAB Platform Shim for HomeBridge +// Written by Tommaso Marchionni +// Based on many of the other HomeBridge platform modules +// +// Revisions: +// +// 17 October 2015 [tommasomarchionni] +// - Initial release +// +// Remember to add platform to config.json. Example: +// "platforms": [ +// { +// "platform": "Openhab", +// "name": "Openhab", +// "server": "127.0.0.1", +// "port": "8080", +// "sitemap": "demo" +// } +// ], +// +// Example of sitemap in OpenHAB: +// sitemap homekit label="HomeKit" { +// Switch item=Light_1 label="Light 1" +// } +// +// 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"); +var Service = require("hap-nodejs/lib/Service.js").Service; +var Characteristic = require("hap-nodejs").Characteristic; + +function OpenhabPlatform(log, config){ + this.log = log; + this.user = config["user"]; + this.password = config["password"]; + this.server = config["server"]; + this.port = config["port"]; + + this.protocol = "http"; + + this.sitemap = "demo"; + if (typeof config["sitemap"] != 'undefined') { + this.sitemap = config["sitemap"]; + } + +} + +OpenhabPlatform.prototype = { + + sitemapUrl: function() { + var serverString = this.server; + //TODO da verificare + if (this.user && this.password) { + serverString = this.user + ":" + this.password + "@" + serverString; + } + + return this.protocol + "://" + serverString + ":" + this.port + "/rest/sitemaps/" + this.sitemap + "?type=json"; + }, + + parseSitemap: function(sitemap) { + var widgets = [].concat(sitemap.homepage.widget); + var result = []; + for (var i = 0; i < widgets.length; i++) { + var widget = widgets[i]; + if (!widget.item) { + //TODO to handle frame + this.log("WARN: The widget '" + widget.label + "' does not reference an item."); + continue; + } + + if (widget.item.type=="SwitchItem" || widget.item.type=="DimmerItem" || widget.item.type == "RollershutterItem"){ + accessory = new OpenhabAccessory(this.log,this,widget.widgetId,widget.label,widget.item) + this.log("Accessory Found: " + widget.label); + result.push(accessory); + } + + + + } + return result; + }, + + accessories: function(callback) { + this.log("Fetching OpenHAB devices."); + var that = this; + + url = that.sitemapUrl(); + this.log("Connecting to " + url); + request.get({ + url: url, + json: true + }, function(err, response, json) { + if (!err && response.statusCode == 200) { + callback(that.parseSitemap(json)); + } else { + that.log("There was a problem connecting to OpenHAB."); + } + }); + } +}; + +function OpenhabAccessory(log, platform, widgetId, label, detail) { + this.log = log; + this.platform = platform; + this.idx = widgetId; + this.name = label; + this.label = label; + this.type = detail.type; + this.deviceURL = detail.link; + this.addressStr = "n/a"; + this.state = detail.state; + + if (this.type == "DimmerItem") { + this.typeSupportsOnOff = true; + this.typeSupportsDim = true; + } + + if (this.type == "SwitchItem") { + this.typeSupportsOnOff = true; + } + + if (this.type == "RollershutterItem") { + this.typeSupportsWindowCovering = true; + } + +} + +OpenhabAccessory.prototype = { + + updateStatus: function(command) { + var that = this; + + var options = { + url: this.deviceURL, + method: 'POST', + body: "" + command + }; + if (this.auth) { + options['auth'] = this.auth; + } + + that.log("eseguo post"); + + request(options, function(error, response, body) { + if (error) { + console.trace("Updating Device Status."); + that.log(error); + return error; + } + + that.log("updateStatus of " + that.name + ": " + command); + + }); + }, + + getServiceType: function() { + if (this.typeSupportsWindowCovering){ + return new Service.WindowCovering; + } else if (this.typeSupportsDim) { + return new Service.Lightbulb; + } else if (this.typeSupportsOnOff) { + return new Service.Switch; + } + }, + + updateStatus: function(command, callback) { + var that = this; + + var options = { + url: this.deviceURL, + method: 'POST', + body: "" + command + }; + if (this.auth) { + options['auth'] = this.auth; + } + + request(options, function(error, response, body) { + if (error) { + //console.trace("Updating Device Status."); + //that.log(error); + //return error; + callback(new Error(error)); + } else { + that.log("updateStatus of " + that.name + ": " + command); + callback(true); + } + }.bind(this)); + }, + + setPowerState: function(powerOn, callback) { + var that = this; + + if (this.typeSupportsOnOff) { + if (powerOn) { + var command = "ON"; + } else { + var command = "OFF"; + } + + this.log("Setting power state on the '"+this.name+"' to " + command); + this.updateStatus(command, function(noError){ + if (noError) { + that.log("Successfully set '"+that.name+"' to " + command); + callback(); + } else { + callback(new Error('Can not communicate with OpenHAB.')); + } + }.bind(this)); + + }else{ + callback(new Error(this.name + " not supports ONOFF")); + } + }, + + getStatus: function(callback){ + var that = this; + this.log("Fetching status brightness for: " + this.name); + + var options = { + url: this.deviceURL + '/state?type=json', + method: 'GET' + }; + + if (this.auth) { + options['auth'] = this.auth; + } + + request(options, function(error, response, body) { + if (error) { + //console.trace("Requesting Device Status."); + //that.log(error); + //return error; + callback(new Error('Can not communicate with Home Assistant.')); + } else { + that.log("getStatus of " + that.name + ": " + body); + callback(null,body); + } + + + + }.bind(this)); + + }, + + getCurrentPosition: function(callback){ + callback(100); + }, + + getPositionState: function(callback){ + this.log("Fetching position state for: " + this.name); + callback(Characteristic.PositionState.STOPPED); + }, + + setTargetPosition: function(level, callback) { + var that = this; + + this.log("Setting target position on the '"+this.name+"' to " + level); + + this.updateStatus(level, function(noError){ + if (noError) { + that.log("Successfully set position on the '"+that.name+"' to " + level); + callback(); + } else { + callback(new Error('Can not communicate with OpenHAB.')); + } + }.bind(this)); + + }, + + setBrightness: function(level, callback) { + var that = this; + + if (this.typeSupportsDim && level >= 0 && level <= 100) { + + this.log("Setting brightness on the '"+this.name+"' to " + level); + + this.updateStatus(level, function(noError){ + if (noError) { + that.log("Successfully set brightness on the '"+that.name+"' to " + level); + callback(); + } else { + callback(new Error('Can not communicate with OpenHAB.')); + } + }.bind(this)); + } + }, + + getServices: function() { + + var informationService = new Service.AccessoryInformation(); + + informationService + .setCharacteristic(Characteristic.Manufacturer, "OpenHAB") + .setCharacteristic(Characteristic.Model, this.type) + .setCharacteristic(Characteristic.SerialNumber, "1234567890") + .setCharacteristic(Characteristic.Name, this.label); + + var otherService = this.getServiceType(); + + if (this.typeSupportsOnOff) { + otherService + .getCharacteristic(Characteristic.On) + .on('get', this.getStatus.bind(this)) + .on('set', this.setPowerState.bind(this)); + + } + + if (this.typeSupportsDim) { + otherService + .addCharacteristic(Characteristic.Brightness) + .on('get', this.getStatus.bind(this)) + .on('set', this.setBrightness.bind(this)); + } + + if (this.typeSupportsWindowCovering) { + var currentPosition = 100; + + otherService + .getCharacteristic(Characteristic.CurrentPosition) + .on('get', this.getCurrentPosition.bind(this)) + .setValue(currentPosition); + + otherService + .getCharacteristic(Characteristic.PositionState) + .on('get', this.getPositionState.bind(this)) + .setValue(Characteristic.PositionState.STOPPED); + + otherService + .getCharacteristic(Characteristic.TargetPosition) + .on('get', this.getCurrentPosition.bind(this)) + .on('set', this.setTargetPosition.bind(this)); + + } + + console.log(informationService); + + return [informationService, otherService]; + } + +} + +module.exports.accessory = OpenhabAccessory; +module.exports.platform = OpenhabPlatform; From 0cb57d2a44e8b8708a66cc20d335902228d49b2c Mon Sep 17 00:00:00 2001 From: rodtoll Date: Mon, 19 Oct 2015 08:03:42 -0700 Subject: [PATCH 108/121] Improper constants for the fan speed. Fixing. --- platforms/isy-js.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/platforms/isy-js.js b/platforms/isy-js.js index b2c3728..1db30b4 100644 --- a/platforms/isy-js.js +++ b/platforms/isy-js.js @@ -195,13 +195,13 @@ ISYFanAccessory.prototype.identify = function(callback) { // Homekit doesn't have steps for the fan speed and needs to have a value from 0 to 100. We // split the range into 4 steps and map them to the 4 isy-js levels. ISYFanAccessory.prototype.translateFanSpeedToHK = function(fanSpeed) { - if(fanSpeed == this.device.isy.ISYFanDevice.FAN_OFF) { + if(fanSpeed == this.device.FAN_LEVEL_OFF) { return 0; - } else if(fanSpeed == this.device.isy.ISYFanDevice.FAN_LOW) { + } else if(fanSpeed == this.device.FAN_LEVEL_LOW) { return 32; - } else if(fanSpeed == this.device.isy.ISYFanDevice.FAN_MEDIUM) { + } else if(fanSpeed == this.device.FAN_LEVEL_MEDIUM) { return 67; - } else if(fanSpeed == this.device.isy.ISYFanDevice.FAN_HIGH) { + } else if(fanSpeed == this.device.FAN_LEVEL_HIGH) { return 100; } else { ISYJSDebugMessage(this,"!!!! ERROR: Unknown fan speed: "+fanSpeed); @@ -213,16 +213,16 @@ ISYFanAccessory.prototype.translateFanSpeedToHK = function(fanSpeed) { // to the four isy-js fan speed levels. ISYFanAccessory.prototype.translateHKToFanSpeed = function(fanStateHK) { if(fanStateHK == 0) { - return this.device.isy.ISYFanDevice.FAN_OFF; + return this.device.FAN_LEVEL_OFF; } else if(fanStateHK > 0 && fanStateHK <=32) { - return this.device.isy.ISYFanDevice.FAN_LOW; - } else if(fanStateHK > 33 && fanStateHK <= 67) { - return this.device.isy.ISYFanDevice.FAN_MEDIUM; + return this.device.FAN_LEVEL_LOW; + } else if(fanStateHK >= 33 && fanStateHK <= 67) { + return this.device.FAN_LEVEL_MEDIUM; } else if(fanStateHK > 67) { - return this.device.isy.ISYFanDevice.FAN_HIGH; + return this.device.FAN_LEVEL_HIGH; } else { ISYJSDebugMessage(this,"ERROR: Unknown fan state!"); - return this.device.isy.ISYFanDevice.FAN_OFF; + return this.device.FAN_LEVEL_OFF; } } @@ -259,9 +259,9 @@ ISYFanAccessory.prototype.getFanOnState = function(callback) { ISYFanAccessory.prototype.setFanOnState = function(onState,callback) { if(onState != this.getIsFanOn()) { if(onState) { - this.setFanRotationSpeed(this.translateFanSpeedToHK(this.device.isy.ISYFanDevice.FAN_MEDIUM), callback); + this.setFanRotationSpeed(this.translateFanSpeedToHK(this.device.FAN_LEVEL_MEDIUM), callback); } else { - this.setFanRotationSpeed(this.translateFanSpeedToHK(this.device.isy.ISYFanDevice.FAN_OFF), callback); + this.setFanRotationSpeed(this.translateFanSpeedToHK(this.device.FAN_LEVEL_OFF), callback); } } else { ISYJSDebugMessage(this,"Fan command does not change actual state"); From 42f88864f9d8edb70e604acbbcecb8b096277857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Mon, 19 Oct 2015 20:00:02 +0200 Subject: [PATCH 109/121] check for hap-nodejs and HAP-NodeJS allow longpoll to multiple fhem servers increase reconnect timeout after subsequent longpoll disconnect and error added target door state for HM-SEC-KEY -> allows open to be send added current door state to contact sensor -> allows to ask for open windows with siri some cleanups --- platforms/FHEM.js | 122 ++++++++++++++++++++++++++++++---------------- 1 file changed, 81 insertions(+), 41 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index a40120b..cfd1d80 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -14,8 +14,17 @@ // } // ], -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +try { + var Service = require("hap-nodejs").Service; +} catch(err) { + Service = require("HAP-NodeJS").Service; +} + +try { + var Characteristic = require("HAP-NodeJS").Characteristic; +} catch(err) { + Characteristic = require("hap-nodejs").Characteristic; +} var util = require('util'); @@ -66,18 +75,21 @@ FHEM_update(inform_id, value, no_update) { } -var FHEM_lastEventTime; -var FHEM_longpoll_running = false; +var FHEM_lastEventTime = {}; +var FHEM_longpoll_running = {}; //FIXME: add filter function FHEM_startLongpoll(connection) { - if( FHEM_longpoll_running ) + if( FHEM_longpoll_running[connection.base_url] ) return; - FHEM_longpoll_running = true; + FHEM_longpoll_running[connection.base_url] = true; + + if( connection.disconnects == undefined ) + connection.disconnects = 0; var filter = ".*"; var since = "null"; - if( FHEM_lastEventTime ) - since = FHEM_lastEventTime/1000; + if( FHEM_lastEventTime[connection.base_url] ) + since = FHEM_lastEventTime[connection.base_url]/1000; var query = "/fhem.pl?XHR=1"+ "&inform=type=status;filter="+filter+";since="+since+";fmt=JSON"+ "×tamp="+Date.now() @@ -122,7 +134,7 @@ function FHEM_startLongpoll(connection) { var subscription = FHEM_subscriptions[d[0]]; if( subscription != undefined ) { //console.log( "Rcvd: "+(l.length>132 ? l.substring(0,132)+"...("+l.length+")":l) ); - FHEM_lastEventTime = lastEventTime; + FHEM_lastEventTime[connection.base_url] = lastEventTime; var accessory = subscription.accessory; var value = d[1]; @@ -227,17 +239,27 @@ function FHEM_startLongpoll(connection) { input = input.substr(FHEM_longpollOffset); FHEM_longpollOffset = 0; - } ).on( 'end', function() { - console.log( "longpoll ended" ); + connection.disconnects = 0; - FHEM_longpoll_running = false; - setTimeout( function(){FHEM_startLongpoll(connection)}, 2000 ); + } ).on( 'end', function() { + FHEM_longpoll_running[connection.base_url] = false; + + connection.disconnects++; + var timeout = 500 * connection.disconnects - 300; + if( timeout > 30000 ) timeout = 30000; + + console.log( "longpoll ended, reconnect in: " + timeout + "msec" ); + setTimeout( function(){FHEM_startLongpoll(connection)}, timeout ); } ).on( 'error', function(err) { - console.log( "longpoll error: " + err ); + FHEM_longpoll_running[connection.base_url] = false; - FHEM_longpoll_running = false; - setTimeout( function(){FHEM_startLongpoll(connection)}, 5000 ); + connection.disconnects++; + var timeout = 5000 * connection.disconnects; + if( timeout > 30000 ) timeout = 30000; + + console.log( "longpoll error: " + err + ", retry in: " + timeout + "msec" ); + setTimeout( function(){FHEM_startLongpoll(connection)}, timeout ); } ); } @@ -740,10 +762,12 @@ FHEMAccessory(log, connection, s) { this.mappings.window = { reading: 'level', cmd: 'level' }; else if( genericType == 'lock' - || ( s.Attributes.model && s.Attributes.model.match(/^HM-SEC-KEY/ ) ) ) - this.mappings.lock = { reading: 'lock', cmdLock: 'lock', cmdUnlock: 'unlock' }; + || ( s.Attributes.model && s.Attributes.model.match(/^HM-SEC-KEY/ ) ) ) { + this.mappings.lock = { reading: 'lock', cmdLock: 'lock', cmdUnlock: 'unlock', cmdOpen: 'open' }; + if( s.Internals.TYPE == 'dummy' ) + this.mappings.lock = { reading: 'lock', cmdLock: 'lock locked', cmdUnlock: 'lock unlocked', cmdOpen: 'open' }; - else if( genericType == 'thermostat' + } else if( genericType == 'thermostat' || s.Attributes.subType == 'thermostat' ) s.isThermostat = true; @@ -1245,27 +1269,6 @@ FHEMAccessory.prototype = { }, execute: function(cmd,callback) {FHEM_execute(this.log, this.connection, cmd, callback)}, - executexxx: function(cmd,callback) { - var url = encodeURI( this.connection.base_url + "/fhem?cmd=" + cmd + "&XHR=1"); - this.log( ' executing: ' + url ); - - this.connection.request - .get( { url: url, gzip: true }, - function(err, response, result) { - if( !err && response.statusCode == 200 ) { - if( callback ) - callback( result ); - - } else { - this.log("There was a problem connecting to FHEM ("+ url +")."); - if( response ) - this.log( " " + response.statusCode + ": " + response.statusMessage ); - - } - - }.bind(this) ) - .on( 'error', function(err) { this.log("There was a problem connecting to FHEM ("+ url +"):"+ err); }.bind(this) ); - }, query: function(reading, callback) { if( reading == undefined ) { @@ -1865,6 +1868,26 @@ FHEMAccessory.prototype = { .on('get', function(callback) { this.query(this.mappings.lock.reading, callback); }.bind(this) ); + + if( this.mappings.lock.cmdOpen ) { + this.log(" target door state characteristic for " + this.name) + + var characteristic = controlService.addCharacteristic(Characteristic.TargetDoorState); + + characteristic.value = Characteristic.TargetDoorState.CLOSED; + + characteristic + .on('set', function(characteristic,value, callback, context) { + if( context !== 'fromFHEM' ) { + this.command( 'set', this.mappings.lock.cmdOpen ); + setTimeout( function(){characteristic.setValue(Characteristic.TargetDoorState.CLOSED, undefined, 'fromFHEM');}, 500 ); + } + if( callback ) callback(); + }.bind(this,characteristic) ) + .on('get', function(callback) { + callback(undefined,Characteristic.TargetDoorState.CLOSED); + }.bind(this) ); + } } if( this.mappings.garage ) { @@ -1897,6 +1920,7 @@ FHEMAccessory.prototype = { }.bind(this) ); + if( 0 ) { this.log(" obstruction detected characteristic for " + this.name) var characteristic = controlService.getCharacteristic(Characteristic.ObstructionDetected); @@ -1908,6 +1932,7 @@ FHEMAccessory.prototype = { .on('get', function(callback) { callback(undefined,1); }.bind(this) ); + } } if( this.mappings.temperature ) { @@ -2056,6 +2081,19 @@ FHEMAccessory.prototype = { .on('get', function(callback) { this.query(this.mappings.contact.reading, callback); }.bind(this) ); + + if( 1 ) { + this.log(" current door state characteristic for " + this.name) + + var characteristic = controlService.addCharacteristic(Characteristic.CurrentDoorState); + + characteristic.value = FHEM_cached[this.mappings.contact.informId]==Characteristic.ContactSensorState.CONTACT_DETECTED?Characteristic.CurrentDoorState.CLOSED:Characteristic.CurrentDoorState.OPEN; + + characteristic + .on('get', function(callback) { + callback(undefined, FHEM_cached[this.mappings.contact.informId]==Characteristic.ContactSensorState.CONTACT_DETECTED?Characteristic.CurrentDoorState.CLOSED:Characteristic.CurrentDoorState.OPEN); + }.bind(this) ); + } } if( this.mappings.occupancy ) { @@ -2093,7 +2131,9 @@ function FHEMdebug_handleRequest(request, response){ if( request.url == "/cached" ) { response.write( "home

" ); if( FHEM_lastEventTime ) - response.write( "FHEM_lastEventTime: "+ new Date(FHEM_lastEventTime) +"

" ); + var keys = Object.keys(FHEM_lastEventTime); + for( var i = 0; i < keys.length; i++ ) + response.write( "FHEM_lastEventTime " + keys[i] + ": "+ new Date(FHEM_lastEventTime[keys[i]]) +"

" ); response.end( "cached: " + util.inspect(FHEM_cached).replace(/\n/g, '
') ); } else if( request.url == "/subscriptions" ) { From 626871680c35ee820248e5ef342a19286dea1c7f Mon Sep 17 00:00:00 2001 From: Kraig McConaghy Date: Mon, 19 Oct 2015 13:46:06 -0500 Subject: [PATCH 110/121] Reorganized dependencies to help with merge --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 65f1b4a..0704076 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,9 @@ "node-icontrol": "^0.1.5", "node-milight-promise": "0.0.x", "node-persist": "0.0.x", + "node-xmpp-client": "1.0.0-alpha23", "q": "1.4.x", + "queue": "^3.1.0", "tough-cookie": "^2.0.0", "request": "2.49.x", "sonos": "0.8.x", @@ -39,8 +41,6 @@ "xmldoc": "0.1.x", "komponist" : "0.1.0", "yamaha-nodejs": "0.4.x", - "debug": "^2.2.0", - "node-xmpp-client": "1.0.0-alpha23", - "queue": "^3.1.0" + "debug": "^2.2.0" } } From d7bca45e70e16aceb0768470bdc74fb956747b4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Mon, 19 Oct 2015 21:13:44 +0200 Subject: [PATCH 111/121] debug browser formating change --- platforms/FHEM.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index cfd1d80..b126043 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -2133,7 +2133,8 @@ function FHEMdebug_handleRequest(request, response){ if( FHEM_lastEventTime ) var keys = Object.keys(FHEM_lastEventTime); for( var i = 0; i < keys.length; i++ ) - response.write( "FHEM_lastEventTime " + keys[i] + ": "+ new Date(FHEM_lastEventTime[keys[i]]) +"

" ); + response.write( "FHEM_lastEventTime " + keys[i] + ": "+ new Date(FHEM_lastEventTime[keys[i]]) +"
" ); + response.write( "
" ); response.end( "cached: " + util.inspect(FHEM_cached).replace(/\n/g, '
') ); } else if( request.url == "/subscriptions" ) { From 8ebd6ecdd262b6b1608b8f7beb1028f1fc224c6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Mon, 19 Oct 2015 22:08:07 +0200 Subject: [PATCH 112/121] hap-nodejs vs. HAP-NodeJS fix --- platforms/FHEM.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index ece6dab..08f337d 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -14,16 +14,18 @@ // } // ], +var Service; try { - var Service = require("hap-nodejs").Service; + Service = require("hap-nodejs").Service; } catch(err) { Service = require("HAP-NodeJS").Service; } +var Characteristic; try { - var Characteristic = require("HAP-NodeJS").Characteristic; -} catch(err) { Characteristic = require("hap-nodejs").Characteristic; +} catch(err) { + Characteristic = require("HAP-NodeJS").Characteristic; } var util = require('util'); From 70ec9a889129d069eb34c632c8ee8093e582b482 Mon Sep 17 00:00:00 2001 From: rodtoll Date: Mon, 19 Oct 2015 19:38:14 -0700 Subject: [PATCH 113/121] Fixed bug in alarm state. Was using alarm state instead of mode to translate current state. --- platforms/isy-js.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/platforms/isy-js.js b/platforms/isy-js.js index 1db30b4..172bd32 100644 --- a/platforms/isy-js.js +++ b/platforms/isy-js.js @@ -656,6 +656,7 @@ ISYElkAlarmPanelAccessory.prototype.setAlarmTargetState = function(targetStateHK ISYElkAlarmPanelAccessory.prototype.translateAlarmCurrentStateToHK = function() { var tripState = this.device.getAlarmTripState(); var sourceAlarmState = this.device.getAlarmState(); + var sourceAlarmMode = this.device.getAlarmMode(); if(tripState >= this.device.ALARM_TRIP_STATE_TRIPPED) { return Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED; @@ -664,14 +665,14 @@ ISYElkAlarmPanelAccessory.prototype.translateAlarmCurrentStateToHK = function() sourceAlarmState == this.device.ALARM_STATE_READY_TO_ARM_VIOLATION) { return Characteristic.SecuritySystemCurrentState.DISARMED; } else { - if(sourceAlarmState == this.device.ALARM_MODE_STAY || sourceAlarmState == this.device.ALARM_MODE_STAY_INSTANT ) { + if(sourceAlarmMode == this.device.ALARM_MODE_STAY || sourceAlarmMode == this.device.ALARM_MODE_STAY_INSTANT ) { return Characteristic.SecuritySystemCurrentState.STAY_ARM; - } else if(sourceAlarmState == this.device.ALARM_MODE_AWAY || sourceAlarmState == this.device.ALARM_MODE_VACATION) { + } else if(sourceAlarmMode == this.device.ALARM_MODE_AWAY || sourceAlarmMode == this.device.ALARM_MODE_VACATION) { return Characteristic.SecuritySystemCurrentState.AWAY_ARM; - } else if(sourceAlarmState == this.device.ALARM_MODE_NIGHT || sourceAlarmState == this.device.ALARM_MODE_NIGHT_INSTANT) { + } else if(sourceAlarmMode == this.device.ALARM_MODE_NIGHT || sourceAlarmMode == this.device.ALARM_MODE_NIGHT_INSTANT) { return Characteristic.SecuritySystemCurrentState.NIGHT_ARM; } else { - ISYJSDebugMessage(this,"Setting to disarmed because sourceAlarmState is "+sourceAlarmState); + ISYJSDebugMessage(this,"Setting to disarmed because sourceAlarmMode is "+sourceAlarmMode); return Characteristic.SecuritySystemCurrentState.DISARMED; } } From 13e884613898164d4abf11e06522110aad2a37fc Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Tue, 20 Oct 2015 07:04:18 +0200 Subject: [PATCH 114/121] Support Door, Window, and ContactSensor for sensorBinary.Door/Window --- platforms/ZWayServer.js | 79 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index e50336e..c367fad 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -356,7 +356,16 @@ ZWayServerAccessory.prototype = { } break; case "sensorBinary.Door/Window": - services.push(new Service.GarageDoorOpener(vdev.metrics.title, vdev.id)); + var stype = this.platform.getTagValue(vdev, "Service.Type"); + if(stype === "ContactSensor"){ + services.push(new Service.ContactSensor(vdev.metrics.title, vdev.id)); + } else if(stype === "GarageDoorOpener"){ + services.push(new Service.GarageDoorOpener(vdev.metrics.title, vdev.id)); + } else if(stype === "Window"){ + services.push(new Service.GarageDoorOpener(vdev.metrics.title, vdev.id)); + } else { + services.push(new Service.Door(vdev.metrics.title, vdev.id)); + } break; case "sensorMultilevel.Temperature": services.push(new Service.TemperatureSensor(vdev.metrics.title, vdev.id)); @@ -371,6 +380,8 @@ ZWayServerAccessory.prototype = { var stype = this.platform.getTagValue(vdev, "Service.Type"); if(stype === "MotionSensor"){ services.push(new Service.MotionSensor(vdev.metrics.title, vdev.id)); + } else { + services.push(new Service.ContactSensor(vdev.metrics.title, vdev.id)); } } @@ -412,6 +423,10 @@ ZWayServerAccessory.prototype = { map[(new Characteristic.TargetHeatingCoolingState).UUID] = ["thermostat"]; //TODO: Always a fixed result map[(new Characteristic.CurrentDoorState).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; map[(new Characteristic.TargetDoorState).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; //TODO: Always a fixed result + map[(new Characteristic.ContactSensorState).UUID] = ["sensorBinary"]; + map[(new Characteristic.CurrentPosition).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; + map[(new Characteristic.TargetPosition).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; + map[(new Characteristic.PositionState).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; map[(new Characteristic.ObstructionDetected).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; //TODO: Always a fixed result map[(new Characteristic.BatteryLevel).UUID] = ["battery.Battery"]; map[(new Characteristic.StatusLowBattery).UUID] = ["battery.Battery"]; @@ -815,6 +830,68 @@ ZWayServerAccessory.prototype = { return cx; } + if(cx instanceof Characteristic.ContactSensorState){ + cx.zway_getValueFromVDev = function(vdev){ + var boolval = vdev.metrics.level === "off" ? false : true; + boolval = accessory.platform.getTagValue(vdev, "ContactSensorState.Invert") ? !boolval : boolval; + return boolval ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED; + }; + cx.value = cx.zway_getValueFromVDev(vdev); + cx.on('get', function(callback, context){ + debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); + this.getVDev(vdev).then(function(result){ + debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); + callback(false, cx.zway_getValueFromVDev(result.data)); + }); + }.bind(this)); + cx.on('change', function(ev){ + debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); + }); + return cx; + } + + if(cx instanceof Characteristic.CurrentPosition){ + cx.zway_getValueFromVDev = function(vdev){ + return vdev.metrics.level === "off" ? 0 : 100 ; + }; + cx.value = cx.zway_getValueFromVDev(vdev); + cx.on('get', function(callback, context){ + debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); + this.getVDev(vdev).then(function(result){ + debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); + callback(false, cx.zway_getValueFromVDev(result.data)); + }); + }.bind(this)); + cx.on('change', function(ev){ + debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); + }); + return cx; + } + + if(cx instanceof Characteristic.TargetPosition){ + //TODO: Currently only Door sensors, so always return 0. + cx.zway_getValueFromVDev = function(vdev){ + return 0; + }; + cx.value = cx.zway_getValueFromVDev(vdev); + cx.on('get', function(callback, context){ + debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); + callback(false, cx.zway_getValueFromVDev(vdev)); + }); + } + + if(cx instanceof Characteristic.PositionState){ + //TODO: Currently only Door sensors, so always return STOPPED. + cx.zway_getValueFromVDev = function(vdev){ + return Characteristic.PositionState.STOPPED; + }; + cx.value = cx.zway_getValueFromVDev(vdev); + cx.on('get', function(callback, context){ + debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); + callback(false, cx.zway_getValueFromVDev(vdev)); + }); + } + } , configureService: function(service, vdev){ From 997906bab8aa65e0dc092ed9ec4cc1cdbf18850c Mon Sep 17 00:00:00 2001 From: Mario Drengner Date: Wed, 21 Oct 2015 14:35:35 +0200 Subject: [PATCH 115/121] Ignore Bridge No need to control the bridge --- platforms/Sonos.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platforms/Sonos.js b/platforms/Sonos.js index 812a803..8e7581d 100644 --- a/platforms/Sonos.js +++ b/platforms/Sonos.js @@ -34,7 +34,7 @@ SonosPlatform.prototype = { that.log("Found device at " + device.host); device.deviceDescription(function (err, description) { - if (description["zoneType"] != '11' && description["zoneType"] != '8') { // 8 is the Sonos SUB + if (description["zoneType"] != '11' && description["zoneType"] != '8' && description["zoneType"] != '4') { // 8 is the Sonos SUB, 4 is the Sonos Bridge var roomName = description["roomName"]; if (!roomNamesFound[roomName]) { From bfc151e70a0fb605a4a932f990758fddb45d6f2d Mon Sep 17 00:00:00 2001 From: Kraig McConaghy Date: Wed, 21 Oct 2015 18:01:43 -0500 Subject: [PATCH 116/121] Updated to official version of harmonyhubjs-client package now that the fix has been merged/released --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 82a8753..3d62cd4 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "eibd": "^0.3.1", "elkington": "kevinohara80/elkington", "hap-nodejs": "^0.0.2", - "harmonyhubjs-client": "enriquez/harmonyhubjs-client#patch-1", + "harmonyhubjs-client": "^1.1.6", "harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git", "komponist": "0.1.0", "lifx": "git+https://github.com/magicmonkey/lifxjs.git", From b09835600df5ade860512228dd4af2d20be184a2 Mon Sep 17 00:00:00 2001 From: Snowdd1 Date: Fri, 23 Oct 2015 12:04:27 +0200 Subject: [PATCH 117/121] Variable out of scope --- platforms/KNX.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/platforms/KNX.js b/platforms/KNX.js index 0ca4309..1051fb9 100644 --- a/platforms/KNX.js +++ b/platforms/KNX.js @@ -3,19 +3,18 @@ */ 'use strict'; var types = require("hap-nodejs/accessories/types.js"); -//var hardware = require('myHardwareSupport'); //require any additional hardware packages + var knxd = require('eibd'); function KNXPlatform(log, config){ this.log = log; this.config = config; -// this.property1 = config.property1; -// this.property2 = config.property2; + // initiate connection to bus for listening ==> done with first shim -}; +} KNXPlatform.prototype = { accessories: function(callback) { @@ -49,10 +48,10 @@ KNXPlatform.prototype = { break; default: // do something else - this.log("unkown accessory type found") + this.log("unkown accessory type found"); } - }; + } // if done, return the array to callback function this.log("returning "+myAccessories.length+" accessories"); callback(myAccessories); @@ -118,7 +117,7 @@ function groupsocketlisten(opts, callback) { var registerSingleGA = function registerSingleGA (groupAddress, callback, reverse) { subscriptions.push({address: groupAddress, callback: callback, reverse:reverse }); -} +}; /* * public busMonitor.startMonitor() @@ -193,7 +192,7 @@ var registerGA = function (groupAddresses, callback) { } else { // it's only one if (groupAddresses.match(/(\d*\/\d*\/\d*)/)) { - registerSingleGA (groupAddresses.match(/(\d*\/\d*\/\d*)/)[0], callback, groupAddresses[i].match(/\d*\/\d*\/\d*(R)/) ? true:false); + registerSingleGA (groupAddresses.match(/(\d*\/\d*\/\d*)/)[0], callback, groupAddresses.match(/\d*\/\d*\/\d*(R)/) ? true:false); } } // console.log("listeners now: " + subscriptions.length); From e2157aed9cca89a4d19559da71722f6bdfc1a791 Mon Sep 17 00:00:00 2001 From: Francesco Carucci Date: Fri, 23 Oct 2015 13:08:35 -0700 Subject: [PATCH 118/121] Append id to nest accessory name --- .gitignore | 4 ++++ platforms/Nest.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 81a1589..1ed5cf8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,8 @@ npm-debug.log # HomeBridge config.json +config.test.json persist/ + + +.AppleDouble diff --git a/platforms/Nest.js b/platforms/Nest.js index 9d225c0..7853a67 100644 --- a/platforms/Nest.js +++ b/platforms/Nest.js @@ -47,7 +47,7 @@ function NestThermostatAccessory(log, name, device, deviceId) { if (name) { this.name = name; } else { - this.name = "Nest"; + this.name = "Nest" + device.serial_number; } this.model = device.model_version; this.serial = device.serial_number; From 1f7db5e661cd8fc3f667c3e4fb681af242e2c2e3 Mon Sep 17 00:00:00 2001 From: Francesco Carucci Date: Fri, 23 Oct 2015 23:56:54 +0000 Subject: [PATCH 119/121] MyQ: check MyQDeviceTypeName against 'VGDO' as well --- accessories/LiftMaster.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/accessories/LiftMaster.js b/accessories/LiftMaster.js index c239f24..1f35847 100644 --- a/accessories/LiftMaster.js +++ b/accessories/LiftMaster.js @@ -89,7 +89,7 @@ LiftMasterAccessory.prototype = { for (var i=0; i Date: Fri, 23 Oct 2015 19:53:02 -0500 Subject: [PATCH 120/121] Fixed initial color calc to range from 0-360 (as documented) instead of 0-100 (likely typo) --- platforms/PhilipsHue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 40dcc33..b6fe8fe 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -167,7 +167,7 @@ PhilipsHueAccessory.prototype = { // Convert 0-65535 to 0-360 hueToArcDegrees: function(value) { value = value/65535; - value = value*100; + value = value*360; value = Math.round(value); return value; }, From e72309fab0005bacc2c729becd79b1efccb60ff4 Mon Sep 17 00:00:00 2001 From: Dennis Soderstrom Date: Sat, 24 Oct 2015 12:39:43 +0200 Subject: [PATCH 121/121] Handle where no model is returned from Telldus Live --- platforms/TelldusLive.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platforms/TelldusLive.js b/platforms/TelldusLive.js index 0e861b5..fd479d9 100644 --- a/platforms/TelldusLive.js +++ b/platforms/TelldusLive.js @@ -58,7 +58,7 @@ var TelldusLiveAccessory = function TelldusLiveAccessory(log, cloud, device) { this.log = log; this.cloud = cloud; - var m = device.model.split(':'); + var m = device.model ? device.model.split(':') : ['unknown', 'unknown'] ; // Set accessory info this.device = device;