From 6f141089052d3503496c7978a465ca3cb5e6efe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Thu, 17 Sep 2015 20:37:11 +0200 Subject: [PATCH 01/89] 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 02/89] 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 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 03/89] 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 04/89] 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 05/89] 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 06/89] 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 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 07/89] 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 08/89] 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 84cac442e2a53cf51b2bc524620e4d21c114c8e1 Mon Sep 17 00:00:00 2001 From: Vincent Drevelle Date: Tue, 29 Sep 2015 11:59:58 +0200 Subject: [PATCH 09/89] Read Philips Hue lights status from bridge. Update the PhilipsHue.js platform to use HAP-NodeJS AccessoryInformation and LightBulb Service classes. --- platforms/PhilipsHue.js | 254 +++++++++++++++++----------------------- 1 file changed, 106 insertions(+), 148 deletions(-) diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 9bceaf0..ffed1ee 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -33,7 +33,12 @@ var hue = require("node-hue-api"), HueApi = hue.HueApi, lightState = hue.lightState; +/* // Oldschool var types = require("HAP-NodeJS/accessories/types.js"); +*/ +var Service = require("HAP-NodeJS").Service; +var Characteristic = require("HAP-NodeJS").Characteristic; + function PhilipsHuePlatform(log, config) { this.log = log; @@ -167,7 +172,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; }, @@ -186,8 +191,7 @@ PhilipsHueAccessory.prototype = { return value; }, // Create and set a light state - executeChange: function(api, device, characteristic, value) { - var that = this; + executeChange: function(characteristic, value, callback) { var state = lightState.create(); switch(characteristic.toLowerCase()) { case 'identify': @@ -211,164 +215,118 @@ PhilipsHueAccessory.prototype = { state.saturation(value); break; } - api.setLightState(device.id, state, function(err, lights) { + this.api.setLightState(this.id, state, function(err, lights) { + if (callback == null) { + return; + } if (!err) { - that.log(device.name + ", characteristic: " + characteristic + ", value: " + value + "."); + if (callback) callback(); // Success + callback = null; + this.log("Set " + this.device.name + ", characteristic: " + characteristic + ", value: " + value + "."); } else { if (err.code == "ECONNRESET") { setTimeout(function() { - that.executeChange(api, device, characteristic, value); + this.executeChange(characteristic, value, callback); }, 300); } else { - that.log(err); + this.log(err); + callback(new Error(err)); } } - }); + }.bind(this)); }, + // Read light state + // TODO: implement clever polling/update and caching + // maybe a better NodeJS hue API exists for this + getState: function(characteristic, callback) { + this.api.lightStatus(this.id, function(err, status) { + if (callback == null) { + return; + } + + if (err) { + if (err.code == "ECONNRESET") { + setTimeout(function() { + this.getState(characteristic, callback); + }.bind(this), 300); + } else { + this.log(err); + callback(new Error(err)); + } + } + + else { + switch(characteristic.toLowerCase()) { + case 'power': + callback(null, status.state.on ? 1 : 0); + break; + case 'hue': + callback(null, this.hueToArcDegrees(status.state.hue)); + break; + case 'brightness': + callback(null, this.bitsToPercentage(status.state.bri)); + break; + case 'saturation': + callback(null, this.bitsToPercentage(status.state.sat)); + break; + //default: + // this.log("Device " + that.device.name + " does not support reading characteristic " + characteristic); + // callback(Error("Device " + that.device.name + " does not support reading characteristic " + characteristic) ); + } + + callback = null; + + //this.log("Get " + that.device.name + ", characteristic: " + characteristic + ", value: " + value + "."); + } + }.bind(this)); + }, + + // Respond to identify request + identify: function(callback) { + this.executeChange("identify", true, callback); + }, + // Get Services getServices: function() { var that = this; - var bulb_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.POWER_STATE_CTYPE, - onUpdate: function(value) { - that.executeChange(that.api, that.device, "power", value); - }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: that.device.state.on, - supportEvents: false, - supportBonjour: false, - manfDescription: "Turn On the Light", - designedMaxLength: 1 - },{ - cType: types.BRIGHTNESS_CTYPE, - onUpdate: function(value) { - that.executeChange(that.api, that.device, "brightness", value); - }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: that.bitsToPercentage(that.device.state.bri), - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust Brightness of Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - } - ]; - // Handle the Hue/Hue Lux divergence - if (that.device.state.hasOwnProperty('hue') && that.device.state.hasOwnProperty('sat')) { - bulb_characteristics.push({ - cType: types.HUE_CTYPE, - onUpdate: function(value) { - that.executeChange(that.api, that.device, "hue", value); - }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: that.hueToArcDegrees(that.device.state.hue), - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust Hue of Light", - designedMinValue: 0, - designedMaxValue: 360, - designedMinStep: 1, - unit: "arcdegrees" - }); - bulb_characteristics.push({ - cType: types.SATURATION_CTYPE, - onUpdate: function(value) { - that.executeChange(that.api, that.device, "saturation", value); - }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: that.bitsToPercentage(that.device.state.sat), - supportEvents: false, - supportBonjour: false, - manfDescription: "Adjust Saturation of Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }); - } - var accessory_data = [ - { - 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: "Philips", - 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: that.device.uniqueid, - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: function(value) { - that.executeChange(that.api, that.device, "identify", value); - }, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - } - ] - },{ - sType: types.LIGHTBULB_STYPE, - // `bulb_characteristics` defined based on bulb type - characteristics: bulb_characteristics - } - ]; - return accessory_data; + + // Use HomeKit types defined in HAP node JS + var lightbulbService = new Service.Lightbulb(this.name); + + // Basic light controls, common to Hue and Hue lux + lightbulbService + .getCharacteristic(Characteristic.On) + .on('get', function(callback) { that.getState("power", callback);}) + .on('set', function(value, callback) { that.executeChange("power", value, callback);}); + + lightbulbService + .addCharacteristic(Characteristic.Brightness) + .on('get', function(callback) { that.getState("brightness", callback);}) + .on('set', function(value, callback) { that.executeChange("brightness", value, callback);}); + + // Handle the Hue/Hue Lux divergence + if (this.device.state.hasOwnProperty('hue') && this.device.state.hasOwnProperty('sat')) { + lightbulbService + .addCharacteristic(Characteristic.Hue) + .on('get', function(callback) { that.getState("hue", callback);}) + .on('set', function(value, callback) { that.executeChange("hue", value, callback);}); + + lightbulbService + .addCharacteristic(Characteristic.Saturation) + .on('get', function(callback) { that.getState("saturation", callback);}) + .on('set', function(value, callback) { that.executeChange("saturation", value, callback);}); + } + + var informationService = new Service.AccessoryInformation(); + + informationService + .setCharacteristic(Characteristic.Manufacturer, "Philips") + .setCharacteristic(Characteristic.Model, this.model) + .setCharacteristic(Characteristic.SerialNumber, this.device.uniqueid) + .addCharacteristic(Characteristic.FirmwareRevision, this.device.swversion); + + return [informationService, lightbulbService]; } }; 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 10/89] 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 3bca9f70f55ba8fd37b7f8b561e1c9196bd26ab4 Mon Sep 17 00:00:00 2001 From: Mike Enriquez Date: Thu, 1 Oct 2015 20:39:00 -0400 Subject: [PATCH 11/89] 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 12/89] 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 2652f33a0a2de8566df0d346ec19c2bc1ceaff9d Mon Sep 17 00:00:00 2001 From: Mike Enriquez Date: Sat, 3 Oct 2015 22:04:51 -0400 Subject: [PATCH 13/89] 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 14/89] 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 490ed5db403dff5a0e3d7cd0417c1f4e74dae273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Thu, 8 Oct 2015 18:55:30 +0200 Subject: [PATCH 15/89] 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 e4fa276de2b29eec9a8ad640653f43781f15051e Mon Sep 17 00:00:00 2001 From: rodtoll Date: Sun, 11 Oct 2015 08:14:48 -0700 Subject: [PATCH 16/89] 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 17/89] 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 18/89] 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 c897913005bf3a0f19ae77c72d3918c346c262cd Mon Sep 17 00:00:00 2001 From: Kraig McConaghy Date: Sat, 17 Oct 2015 20:51:57 -0500 Subject: [PATCH 19/89] Initial work. Still in progress --- platforms/Nest.js | 488 ++++++++++++++++++++-------------------------- 1 file changed, 211 insertions(+), 277 deletions(-) diff --git a/platforms/Nest.js b/platforms/Nest.js index c1ef3bd..8803b22 100644 --- a/platforms/Nest.js +++ b/platforms/Nest.js @@ -1,5 +1,11 @@ var types = require("HAP-NodeJS/accessories/types.js"); var nest = require('unofficial-nest-api'); +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 NestPlatform(log, config){ @@ -29,8 +35,9 @@ NestPlatform.prototype = { // it's a thermostat, adjust this to detect other accessories if (data.shared[deviceId].hasOwnProperty('current_temperature')) { - var name = data.shared[deviceId].name - var accessory = new NestThermostatAccessory(that.log, name, device, deviceId); + var initialData = data.shared[deviceId]; + var name = initialData.name + var accessory = new NestThermostatAccessory(that.log, name, device, deviceId, initialData); foundAccessories.push(accessory); } } @@ -42,7 +49,7 @@ NestPlatform.prototype = { } } -function NestThermostatAccessory(log, name, device, deviceId) { +function NestThermostatAccessory(log, name, device, deviceId, initialData) { // device info if (name) { this.name = name; @@ -53,124 +60,226 @@ function NestThermostatAccessory(log, name, device, deviceId) { this.serial = device.serial_number; this.deviceId = deviceId; this.log = log; + Accessory.call(this, name, uuid.generate(deviceId)); + + this.getService(Service.AccessoryInformation) + .setCharacteristic(Characteristic.Manufacturer, "Nest") + .setCharacteristic(Characteristic.Model, this.model) + .setCharacteristic(Characteristic.SerialNumber, this.serial); + + this.addService(Service.Thermostat, name); + + this.getService(Service.Thermostat) + .setCharacteristic(Characteristic.TemperatureDisplayUnits, this.extractTemperatureUnits(device)) + .on('get', this.getTemperatureUnits); + + this.getService(Service.Thermostat) + .setCharacteristic(Characteristic.CurrentTemperature, this.extractCurrentTemperature(device)) + //.getCharacteristic(Characteristic.CurrentTemperature) + .on('get', this.getCurrentTemperature); + + this.getService(Service.Thermostat) + .setCharacteristic(Characteristic.TargetTemperature, this.extractTargetTemperature(device)) + .on('get', this.getTargetTemperature) + .on('set', this.setTargetTemperature); + + this.getService(Service.Thermostat) + .setCharacteristic(Characteristic.CurrentHeatingCoolingState, this.extractCurrentHeatingCooling(device)) + .on('get', this.getCurrentHeatingCooling); + + this.getService(Service.Thermostat) + .setCharacteristic(Characteristic.TargetHeatingCoolingState, this.extractTargetHeatingCooling(device)) + .on('get', this.getTargetHeatingCoooling) + .on('set', this.setTargetHeatingCooling); + + //this.getService(Service.Thermostat) + // .getCharacteristic(Characteristic.CurrentRelativeHumidity) + // .on('get', function(callback) { + // that.getCurrentRelativeHumidity(function(currentRelativeHumidity){ + // callback(currentRelativeHumidity); + // }); + // }); + + + } - -NestThermostatAccessory.prototype = { - getCurrentHeatingCooling: function(callback){ - - var that = this; - - this.log("Checking current heating cooling for: " + this.name); - nest.fetchStatus(function (data) { - var device = data.device[that.deviceId]; - - var currentHeatingCooling = 0; - switch(device.current_schedule_mode) { - case "OFF": - targetHeatingCooling = 0; - break; - case "HEAT": - currentHeatingCooling = 1; - break; - case "COOL": - currentHeatingCooling = 2; - break; - case "RANGE": - currentHeatingCooling = 3; - break; - default: - currentHeatingCooling = 0; +inherits(NestThermostatAccessory, Accessory); +//NestThermostatAccessory.prototype.parent = Accessory.prototype; +Service.prototype.getCharacteristic = function(name) { + // returns a characteristic object from the service + // If Service.prototype.getCharacteristic(Characteristic.Type) does not find the characteristic, + // but the type is in optionalCharacteristics, it adds the characteristic.type to the service and returns it. + var index, characteristic; + for (index in this.characteristics) { + characteristic = this.characteristics[index]; + if (typeof name === 'string' && characteristic.displayName === name) { + return characteristic; + } + else if (typeof name === 'function' && characteristic instanceof name) { + return characteristic; + } + } + if (typeof name === 'function') { + for (index in this.optionalCharacteristics) { + characteristic = this.optionalCharacteristics[index]; + if (characteristic instanceof name) { + return this.addCharacteristic(name); } - that.log("Current heating for " + this.name + "is: " + currentHeatingCooling); - callback(currentHeatingCooling); - }); + } + } +}; +NestThermostatAccessory.prototype.getServices = function() { + return this.services; +}; - }, - - getTargetHeatingCoooling: function(callback){ - +NestThermostatAccessory.prototype.extractCurrentHeatingCooling = function(device){ + var currentHeatingCooling = 0; + switch(device.current_schedule_mode) { + case "OFF": + currentHeatingCooling = 0; + break; + case "HEAT": + currentHeatingCooling = 1; + break; + case "COOL": + currentHeatingCooling = 2; + break; + case "RANGE": + currentHeatingCooling = 3; + break; + default: + currentHeatingCooling = 0; + } + this.log("Current heating for " + this.name + "is: " + currentHeatingCooling); + return currentHeatingCooling; +}; +NestThermostatAccessory.prototype.getCurrentHeatingCooling = function(callback){ + var that = this; + this.log("Checking current heating cooling for: " + this.name); + nest.fetchStatus(function (data) { + var device = data.device[that.deviceId]; + var currentHeatingCooling = that.extractCurrentHeatingCooling(device); + callback(currentHeatingCooling); + }); +}; +NestThermostatAccessory.prototype.extractTargetHeatingCooling = function(device){ + var targetHeatingCooling = 0; + switch(device.target_temperature_type) { + case "off": + targetHeatingCooling = 0; + break; + case "heat": + targetHeatingCooling = 1; + break; + case "cool": + targetHeatingCooling = 2; + break; + case "range": + targetHeatingCooling = 3; + break; + default: + targetHeatingCooling = 0; + } + this.log("Current target heating for " + this.name + " is: " + targetHeatingCooling); + return targetHeatingCooling; +}; +NestThermostatAccessory.prototype.getTargetHeatingCoooling = function(callback){ var that = this; - this.log("Checking target heating cooling for: " + this.name); nest.fetchStatus(function (data) { var device = data.device[that.deviceId]; - - var targetHeatingCooling = 0; - switch(device.target_temperature_type) { - case "off": - targetHeatingCooling = 0; - break; - case "heat": - targetHeatingCooling = 1; - break; - case "cool": - targetHeatingCooling = 2; - break; - case "range": - targetHeatingCooling = 3; - break; - default: - targetHeatingCooling = 0; - } - that.log("Current target heating for " + this.name + " is: " + targetHeatingCooling); + var targetHeatingCooling = that.extractTargetHeatingCooling(device); callback(targetHeatingCooling); }); - }, + }; - getCurrentTemperature: function(callback){ +NestThermostatAccessory.prototype.extractCurrentTemperature = function(device){ + var curTemp = this.extractAsDisplayUnit(device.current_temperature, device); + this.log("Current temperature for " + this.name + " is: " + curTemp); + return curTemp; +}; +NestThermostatAccessory.prototype.getCurrentTemperature = function(callback){ var that = this; - nest.fetchStatus(function (data) { var device = data.shared[that.deviceId]; - that.log("Current temperature for " + this.name + " is: " + device.current_temperature); - callback(device.current_temperature); + var curTemp = this.extractCurrentTemperature(device); + callback(curTemp); }); + }; +NestThermostatAccessory.prototype.extractTargetTemperature = function(device){ + var targetTemp; + if (device.target_temperature != undefined) { + targetTemp = device.target_temperature; + } else if (device.temperature_lock_high_temp != undefined) { + targetTemp = device.temperature_lock_high_temp; + } else { + return null; + } - }, - - getTargetTemperature: function(callback){ - + targetTemp = this.extractAsDisplayUnit(targetTemp, device); + this.log("Target temperature for " + this.name + " is: " + targetTemp); + return targetTemp; +}; +NestThermostatAccessory.prototype.getTargetTemperature = function(callback){ var that = this; - nest.fetchStatus(function (data) { var device = data.shared[that.deviceId]; - that.log("Target temperature for " + this.name + " is: " + device.target_temperature); - callback(device.target_temperature); + var targetTemp = this.extractTargetTemperature(device); + callback(targetTemp); }); + }; +NestThermostatAccessory.prototype.extractTemperatureUnits = function(device) { + var temperatureUnits = 0; + switch(device.temperature_scale) { + case "F": + this.log("Tempature unit for " + this.name + " is: " + "Fahrenheit"); + temperatureUnits = 1; + break; + case "C": + this.log("Tempature unit for " + this.name + " is: " + "Celsius"); + temperatureUnits = 0; + break; + default: + temperatureUnits = 0; + } + return temperatureUnits; +}; - }, +NestThermostatAccessory.prototype.isFahrenheitUnit = function(unit) { + return unit == 1; +}; - getTemperatureUnits: function(callback){ +NestThermostatAccessory.prototype.convertToDisplayUnit = function(value, displayUnit) { + return this.isFahrenheitUnit(displayUnit) ? nest.ctof(value) : value; +}; +NestThermostatAccessory.prototype.convertToValueUnit = function(value, displayUnit) { + return this.isFahrenheitUnit(displayUnit) ? nest.ftoc(value) : value; +}; + +NestThermostatAccessory.prototype.extractAsDisplayUnit = function(value, device) { + var tempUnit = this.extractTemperatureUnits(device); + return this.convertToDisplayUnit(value, tempUnit); +}; + +NestThermostatAccessory.prototype.extractAsValueUnit = function(value, device) { + return this.convertToValueUnit(value, this.extractTemperatureUnits(device)); +}; + +NestThermostatAccessory.prototype.getTemperatureUnits = function(callback){ var that = this; - nest.fetchStatus(function (data) { var device = data.device[that.deviceId]; - var temperatureUnits = 0; - switch(device.temperature_scale) { - case "F": - that.log("Tempature unit for " + this.name + " is: " + "Fahrenheit"); - temperatureUnits = 1; - break; - case "C": - that.log("Tempature unit for " + this.name + " is: " + "Celsius"); - temperatureUnits = 0; - break; - default: - temperatureUnits = 0; - } - + var temperatureUnits = that.extractTemperatureUnits(device); callback(temperatureUnits); }); + }; - - }, - - getCurrentRelativeHumidity: function(callback){ +NestThermostatAccessory.prototype.getCurrentRelativeHumidity = function(callback){ var that = this; @@ -181,12 +290,9 @@ NestThermostatAccessory.prototype = { }) - }, - - setTargetHeatingCooling: function(targetHeatingCooling){ - - var that = this; + }; +NestThermostatAccessory.prototype.setTargetHeatingCooling = function(targetHeatingCooling, callback){ var targetTemperatureType = 'off'; switch(targetHeatingCooling) { case 0: @@ -208,190 +314,18 @@ NestThermostatAccessory.prototype = { this.log("Setting target heating cooling for " + this.name + " to: " + targetTemperatureType); nest.setTargetTemperatureType(this.deviceId, targetTemperatureType); - - }, - - setTargetTemperature: function(targetTemperature){ - - var that = this; - - this.log("Setting target temperature for " + this.name + " to: " + targetTemperature); - nest.setTemperature(this.deviceId, targetTemperature); - - - }, - - 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: "Nest", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.model, - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.serial, - 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 thermostat", - designedMaxLength: 255 - },{ - cType: types.CURRENTHEATINGCOOLING_CTYPE, - onUpdate: null, - onRead: function(callback) { - that.getCurrentHeatingCooling(function(currentHeatingCooling){ - callback(currentHeatingCooling); - }); - }, - 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, - onUpdate: function(value) { - that.setTargetHeatingCooling(value); - }, - onRead: function(callback) { - that.getTargetHeatingCoooling(function(targetHeatingCooling){ - callback(targetHeatingCooling); - }); - }, - 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, - onUpdate: null, - onRead: function(callback) { - that.getCurrentTemperature(function(currentTemperature){ - callback(currentTemperature); - }); - }, - perms: ["pr","ev"], - format: "int", - initialValue: 20, - 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(function(targetTemperature){ - callback(targetTemperature); - }); - }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 20, - supportEvents: false, - supportBonjour: false, - manfDescription: "Target Temperature", - designedMinValue: 16, - designedMaxValue: 38, - designedMinStep: 1, - unit: "celsius" - },{ - cType: types.TEMPERATURE_UNITS_CTYPE, - onUpdate: null, - onRead: function(callback) { - that.getTemperatureUnits(function(temperatureUnits){ - callback(temperatureUnits); - }); - }, - perms: ["pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Unit", - },{ - cType: types.CURRENT_RELATIVE_HUMIDITY_CTYPE, - onUpdate: null, - onRead: function(callback) { - that.getCurrentRelativeHumidity(function(currentRelativeHumidity){ - callback(currentRelativeHumidity); - }); - }, - perms: ["pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Humidity", - }] - }]; + if (callback) { + callback(); } -} +}; + +NestThermostatAccessory.prototype.setTargetTemperature = function(targetTemperature, callback){ + this.log("Setting target temperature for " + this.name + " to: " + targetTemperature); + nest.setTemperature(this.deviceId, targetTemperature); + if (callback) { + callback(); + } +}; module.exports.accessory = NestThermostatAccessory; module.exports.platform = NestPlatform; 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 20/89] 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 21/89] 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 22/89] 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 23/89] 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 24/89] 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 6604c2b69aff3b4156102e0aaa20487413aa4a0f Mon Sep 17 00:00:00 2001 From: Kraig McConaghy Date: Sun, 18 Oct 2015 19:08:46 -0500 Subject: [PATCH 25/89] 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 26/89] 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 27/89] 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 28/89] 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 29/89] 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 30/89] 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 31/89] 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 32/89] 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 33/89] 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 34/89] 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 b5210f424f62371f8528d83d33b0d5f7f8cbf77d Mon Sep 17 00:00:00 2001 From: Kraig McConaghy Date: Mon, 19 Oct 2015 23:16:31 -0500 Subject: [PATCH 35/89] More fixes --- platforms/Nest.js | 82 +++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 45 deletions(-) diff --git a/platforms/Nest.js b/platforms/Nest.js index 8803b22..682fdea 100644 --- a/platforms/Nest.js +++ b/platforms/Nest.js @@ -14,6 +14,7 @@ function NestPlatform(log, config){ this.password = config["password"]; this.log = log; + this.accessoryLookup = { }; } NestPlatform.prototype = { @@ -28,6 +29,8 @@ NestPlatform.prototype = { that.log("There was a problem authenticating with Nest."); } else { + + nest.fetchStatus(function (data) { for (var deviceId in data.device) { if (data.device.hasOwnProperty(deviceId)) { @@ -36,12 +39,27 @@ NestPlatform.prototype = { if (data.shared[deviceId].hasOwnProperty('current_temperature')) { var initialData = data.shared[deviceId]; - var name = initialData.name + var name = initialData.name; var accessory = new NestThermostatAccessory(that.log, name, device, deviceId, initialData); + that.accessoryLookup[deviceId] = accessory; foundAccessories.push(accessory); } } } + function subscribe() { + nest.subscribe(subscribeDone, ['shared']); + } + + function subscribeDone(deviceId, data, type) { + // data if set, is also stored here: nest.lastStatus.shared[thermostatID] + if (deviceId && that.accessoryLookup[deviceId]) { + that.log('Update to Device: ' + deviceId + " type: " + type); + that.accessoryLookup[deviceId].updateData(data); + } + setTimeout(subscribe, 2000); + } + + subscribe(); callback(foundAccessories) }); } @@ -74,34 +92,16 @@ function NestThermostatAccessory(log, name, device, deviceId, initialData) { .on('get', this.getTemperatureUnits); this.getService(Service.Thermostat) - .setCharacteristic(Characteristic.CurrentTemperature, this.extractCurrentTemperature(device)) - //.getCharacteristic(Characteristic.CurrentTemperature) - .on('get', this.getCurrentTemperature); - - this.getService(Service.Thermostat) - .setCharacteristic(Characteristic.TargetTemperature, this.extractTargetTemperature(device)) + .setCharacteristic(Characteristic.TargetTemperature, this.extractTargetTemperature(initialData)) .on('get', this.getTargetTemperature) .on('set', this.setTargetTemperature); this.getService(Service.Thermostat) - .setCharacteristic(Characteristic.CurrentHeatingCoolingState, this.extractCurrentHeatingCooling(device)) - .on('get', this.getCurrentHeatingCooling); - - this.getService(Service.Thermostat) - .setCharacteristic(Characteristic.TargetHeatingCoolingState, this.extractTargetHeatingCooling(device)) - .on('get', this.getTargetHeatingCoooling) + .setCharacteristic(Characteristic.TargetHeatingCoolingState, this.extractTargetHeatingCooling(initialData)) + .on('get', this.getTargetHeatingCooling) .on('set', this.setTargetHeatingCooling); - //this.getService(Service.Thermostat) - // .getCharacteristic(Characteristic.CurrentRelativeHumidity) - // .on('get', function(callback) { - // that.getCurrentRelativeHumidity(function(currentRelativeHumidity){ - // callback(currentRelativeHumidity); - // }); - // }); - - - + this.updateData(initialData); } inherits(NestThermostatAccessory, Accessory); //NestThermostatAccessory.prototype.parent = Accessory.prototype; @@ -133,9 +133,16 @@ NestThermostatAccessory.prototype.getServices = function() { return this.services; }; +NestThermostatAccessory.prototype.updateData = function(data) { + var thermostat = this.getService(Service.Thermostat); + thermostat.setCharacteristic(Characteristic.CurrentTemperature, this.extractCurrentTemperature(data)); + thermostat.setCharacteristic(Characteristic.CurrentHeatingCoolingState, this.extractCurrentHeatingCooling(data)); + thermostat.setCharacteristic(Characteristic.CurrentRelativeHumidity, this.extractCurrentRelativeHumidity(data)); +}; + NestThermostatAccessory.prototype.extractCurrentHeatingCooling = function(device){ var currentHeatingCooling = 0; - switch(device.current_schedule_mode) { + switch(device.target_temperature_type) { case "OFF": currentHeatingCooling = 0; break; @@ -184,7 +191,7 @@ NestThermostatAccessory.prototype.extractTargetHeatingCooling = function(device) this.log("Current target heating for " + this.name + " is: " + targetHeatingCooling); return targetHeatingCooling; }; -NestThermostatAccessory.prototype.getTargetHeatingCoooling = function(callback){ +NestThermostatAccessory.prototype.getTargetHeatingCooling = function(callback){ var that = this; this.log("Checking target heating cooling for: " + this.name); nest.fetchStatus(function (data) { @@ -200,14 +207,6 @@ NestThermostatAccessory.prototype.extractCurrentTemperature = function(device){ this.log("Current temperature for " + this.name + " is: " + curTemp); return curTemp; }; -NestThermostatAccessory.prototype.getCurrentTemperature = function(callback){ - var that = this; - nest.fetchStatus(function (data) { - var device = data.shared[that.deviceId]; - var curTemp = this.extractCurrentTemperature(device); - callback(curTemp); - }); - }; NestThermostatAccessory.prototype.extractTargetTemperature = function(device){ var targetTemp; @@ -279,18 +278,11 @@ NestThermostatAccessory.prototype.getTemperatureUnits = function(callback){ }); }; -NestThermostatAccessory.prototype.getCurrentRelativeHumidity = function(callback){ - - var that = this; - - nest.fetchStatus(function (data) { - var device = data.device[that.deviceId]; - that.log("Humidity for " + this.name + " is: " + device.current_humidity); - callback(device.current_humidity); - }) - - - }; +NestThermostatAccessory.prototype.extractCurrentRelativeHumidity = function(device) { + var humidity = device.current_humidity; + this.log("Humidity for " + this.name + " is: " + humidity); + return humidity; +}; NestThermostatAccessory.prototype.setTargetHeatingCooling = function(targetHeatingCooling, callback){ var targetTemperatureType = 'off'; From 13e884613898164d4abf11e06522110aad2a37fc Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Tue, 20 Oct 2015 07:04:18 +0200 Subject: [PATCH 36/89] 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 de06a2b12de7b914ef3ac77540fec5113bd7a89b Mon Sep 17 00:00:00 2001 From: Pierre-Julien Cazaux Date: Wed, 21 Oct 2015 10:59:13 +0200 Subject: [PATCH 37/89] Semi working HTTP garage door implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, when use « Siri, open/close garage door » got the correct answer on logs but my iPhone reply something like « The garage door in now wrong » and the Insteon+ app icon is not updated. I quite beginner I would like a hint :) THX --- accessories/HttpGarageDoorOpener.js | 115 ++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 accessories/HttpGarageDoorOpener.js diff --git a/accessories/HttpGarageDoorOpener.js b/accessories/HttpGarageDoorOpener.js new file mode 100644 index 0000000..d6320a2 --- /dev/null +++ b/accessories/HttpGarageDoorOpener.js @@ -0,0 +1,115 @@ +/* +{ + "bridge": { + "name": "Homebridge", + "username": "CC:22:3D:E3:CE:30", + "port": 51826, + "pin": "031-45-154" + }, + + "description": "This is an example configuration file with all supported devices. You can use this as a template for creating your own configuration file containing devices you actually own.", + + "platforms": [], + "accessories": [ + { + "accessory": "HttpGarageDoorOpener", + "name": "Porte de Garage", + "description": "", + "open_url": "http://0.0.0.0:3000", + "http_method": "GET" + } + ] +} +*/ + +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; + +var request = require("request"); + +module.exports = { + accessory: HttpGarageDoorOpener +} + +function HttpGarageDoorOpener(log, config) { + this.log = log; + this.open_url = config["open_url"]; + this.http_method = config["http_method"]; + this.garageDoorStatus = Characteristic.CurrentDoorState.CLOSED; +} + +HttpGarageDoorOpener.prototype = { + close: function (callback) { + this.garageDoorStatus = Characteristic.CurrentDoorState.CLOSED; + this.log("Door is", this.getCurrentDoorStateReadable()); + callback(); + }, + + open: function (callback) { + this.garageDoorStatus = Characteristic.CurrentDoorState.OPEN; + this.log("Door is", this.getCurrentDoorStateReadable()); + callback(); + }, + + identify: function() { + console.log("Identify the Door!"); + }, + + getServices: function () { + this.garageDoorOpenerService = new Service.GarageDoorOpener(); + + this.garageDoorOpenerService + .getCharacteristic(Characteristic.CurrentDoorState) + .on('get', this.getCurrentDoorState.bind(this)); + + this.garageDoorOpenerService + .getCharacteristic(Characteristic.TargetDoorState) + .on('set', this.setTargetDoorState.bind(this)); + + /* + garageDoorOpenerService + .getCharacteristic(Characteristic.ObstructionDetected) + .on('get', this.getObstructionDetected.bind(this)) + .on('set', this.setObstructionDetected.bind(this)); + */ + + var informationService = new Service.AccessoryInformation(); + informationService + .setCharacteristic(Characteristic.Manufacturer, "HTTP Manufacturer") + .setCharacteristic(Characteristic.Model, "HTTP Model") + .setCharacteristic(Characteristic.SerialNumber, "HTTP Serial Number"); + + return [informationService, this.garageDoorOpenerService]; + }, + + getCurrentDoorStateReadable: function () { + var textState = ""; + switch (this.garageDoorStatus) { + case 0: textState = "OPEN"; break; + case 1: textState = "CLOSED"; break; + case 2: textState = "OPENING"; break; + case 3: textState = "CLOSING"; break; + case 4: textState = "STOPPED"; break; + default: this.log("Unhandled CurrentDoorState"); + } + return textState; + }, + + getCurrentDoorState: function(callback) { + + this.log("The door is now", this.getCurrentDoorStateReadable() ,"("+ this.garageDoorStatus + ")"); + + var error = null; + var returnValue = this.state; + + callback(null, returnValue); + }, + + setTargetDoorState: function(value, callback) { + if(value === Characteristic.TargetDoorState.OPEN) { + this.open(callback); + } else { + this.close(callback); + }; + } +}; \ No newline at end of file From 997906bab8aa65e0dc092ed9ec4cc1cdbf18850c Mon Sep 17 00:00:00 2001 From: Mario Drengner Date: Wed, 21 Oct 2015 14:35:35 +0200 Subject: [PATCH 38/89] 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 39/89] 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 40/89] 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 41/89] 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 42/89] 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 43/89] 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 44/89] 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; From 6e156e48fb3d7841d7a2acebe5cad6fb9a0850af Mon Sep 17 00:00:00 2001 From: Stefan Kuper Date: Sat, 24 Oct 2015 20:06:30 +0200 Subject: [PATCH 45/89] netatmo first imply --- config-sample.json | 10 ++ package.json | 2 + platforms/Netatmo.js | 394 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 406 insertions(+) create mode 100644 platforms/Netatmo.js diff --git a/config-sample.json b/config-sample.json index 05b14ca..0e04cfe 100644 --- a/config-sample.json +++ b/config-sample.json @@ -105,6 +105,16 @@ "platform": "LIFx", "name": "LIFx", "access_token": "XXXXXXXX generate at https://cloud.lifx.com/settings" + }, + { + "platform": "Netatmo", + "name": "Netatmo Weather", + "auth": { + "client_id": "XXXXX Create at https://dev.netatmo.com/", + "client_secret": "XXXXX Create at https://dev.netatmo.com/", + "username": "your netatmo username", + "password": "your netatmo password" + } } ], diff --git a/package.json b/package.json index aa5c82d..b920048 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,8 @@ "lifx": "git+https://github.com/magicmonkey/lifxjs.git", "lifx-api": "^1.0.1", "mdns": "^2.2.4", + "netatmo": "1.3.0", + "node-cache": "3.0.0", "node-hue-api": "^1.0.5", "node-icontrol": "^0.1.5", "node-milight-promise": "0.0.x", diff --git a/platforms/Netatmo.js b/platforms/Netatmo.js new file mode 100644 index 0000000..24fe572 --- /dev/null +++ b/platforms/Netatmo.js @@ -0,0 +1,394 @@ +'use strict'; + +// Netatmo weatherstation for HomeBridge +// Wriiten by planetk (https://github.com/planetk) +// +// Remember to add platform to config.json. Example: +// "platforms": [ +// { +// "platform": "Netatmo", +// "name": "Netatmo Weather", +// "auth": { +// "client_id": "", +// "client_secret": "", +// "username": "", +// "password": "" +// } +// } +// ], +// +// The default code for all HomeBridge accessories is 031-45-154. + +var types = require('hap-nodejs/accessories/types.js'); + +////////////////////////////////////////////////////////////////////////////// +// DECLARE SOME UUIDS WHICH SHOUL BE IN HAP-NODEJS TYPES LIB, BUT ARE NOT YET +// REMOVE WHEN HAP LIB IS UPDATED!! +////////////////////////////////////////////////////////////////////////////// +var stPre = "000000"; +var stPost = "-0000-1000-8000-0026BB765291"; + +types.BATTERY_SERVICE_STYPE = stPre + "96" + stPost; +types.AIR_QUALITY_SENSOR_STYPE = stPre + "8D" + stPost; +types.CARBON_DIOXIDE_SENSOR_STYPE = stPre + "97" + stPost; + +types.AIR_PARTICULATE_DENISITY_CTYPE = stPre + "64" + stPost; +types.CARBON_DIOXIDE_DETECTED_CTYPE = stPre + "92" + stPost; +types.CARBON_DIOXIDE_LEVEL_CTYPE = stPre + "93" + stPost; +types.AIR_QUALITY_CTYPE = stPre + "95" + stPost; +////////////////////////////////////////////////////////////////////////////// + +var netatmo = require('netatmo'); +var NodeCache = require( "node-cache" ); + +function NetAtmoRepository(log, api) { + this.api = api; + this.log = log; + this.cache = new NodeCache(); +} + +NetAtmoRepository.prototype = { + refresh: function(callback) { + var datasource={ + devices: {}, + modules: {} + }; + var that = this; + that.api.getDevicelist(function(err, devices, modules) { + for (var device of devices) { + that.log("refreshing device " + device._id + " (" + device.module_name + ")"); + datasource.devices[device._id] = device; + } + for (var module of modules) { + that.log("refreshing module " + module._id + " (" + module.module_name + ")"); + datasource.modules[module._id] = module; + } + that.cache.set( "datasource", datasource, 20 ); + callback(datasource); + }); + }, + load: function(callback) { + var that = this; + this.cache.get( "datasource", function( err, datasource ){ + if( !err ){ + if ( datasource == undefined ){ + that.refresh(callback); + } else { + callback(datasource) + } + } + }); + } +} + +function NetatmoPlatform(log, config) { + this.log = log; + var api = new netatmo(config["auth"]); + this.repository = new NetAtmoRepository(this.log, api); + api.on("error", function(error) { + this.log('ERROR - Netatmo: ' + error); + }); + api.on("warning", function(error) { + this.log('WARN - Netatmo: ' + error); + }); +} + +NetatmoPlatform.prototype = { + accessories: function(callback) { + + var that = this; + var foundAccessories = []; + + this.repository.load(function(datasource) { + for (var id in datasource.devices) { + var device = datasource.devices[id]; + that.log("Adding accessory for device " + id + " (" + device.module_name + ")"); + var accessory = new NetatmoAccessory(that.log, that.repository, device._id, null, device); + foundAccessories.push(accessory); + } + for (var id in datasource.modules) { + var module = datasource.modules[id]; + that.log("Adding accessory for module " + module._id + " (" + module.module_name + ")"); + var accessory = new NetatmoAccessory(that.log, that.repository, module.main_device, module._id, module); + foundAccessories.push(accessory); + } + callback(foundAccessories); + }); + } +} + +function NetatmoAccessory(log, repository, deviceId, moduleId, device) { + this.log = log; + this.repository = repository; + this.deviceId = deviceId; + this.moduleId = moduleId; + this.serial = deviceId; + if (moduleId) { + this.serial = moduleId; + } + this.name = device.module_name; + this.model = device.type; + this.serviceTypes = device.data_type; + if (device.battery_vp) { + this.serviceTypes.push("Battery"); + } +} + +NetatmoAccessory.prototype = { + + getData: function(callback) { + var that = this; + this.repository.load(function(datasource) { + if(that.moduleId) { + callback(datasource.modules[that.moduleId]); + } else { + callback(datasource.devices[that.deviceId]); + } + }); + }, + + getCurrentTemperature: function(callback) { + this.getData(function(deviceData) { + callback(deviceData.dashboard_data.Temperature); + }); + }, + + getCurrentHumidity: function(callback) { + this.getData(function(deviceData) { + callback(deviceData.dashboard_data.Humidity); + }); + }, + + getAirQuality: function(callback) { + this.getData(function(deviceData) { + var level = deviceData.dashboard_data.CO2; + var quality = 0; + if (level > 2000) quality = 5; + else if (level > 1500) quality = 4; + else if (level > 1000) quality = 3; + else if (level > 500) quality = 2; + else if (level > 250) quality = 1; + callback(quality); + }); + }, + getCurrentCO2Level: function(callback) { + this.log("fetching co2"); + this.getData(function(deviceData) { + callback(deviceData.dashboard_data.CO2); + }); + }, + + informationCharacteristics: function() { + return [ + { + 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: "Netatmo", + supportEvents: false, + supportBonjour: false, + manfDescription: "Manufacturer", + designedMaxLength: 255 + },{ + cType: types.MODEL_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.model, + supportEvents: false, + supportBonjour: false, + manfDescription: "Model", + designedMaxLength: 255 + },{ + cType: types.SERIAL_NUMBER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.serial, + 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 + } + ] + }, + + humidityCharacteristics: function(that) { + var cTypes = [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name +" Humidity", + supportEvents: true, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + },{ + cType: types.CURRENT_RELATIVE_HUMIDITY_CTYPE, + onRead: function(callback) { that.getCurrentHumidity(callback); }, + onUpdate: null, + perms: ["pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Humidity" + }]; + return cTypes; + }, + + temperatureCharacteristics: function(that) { + var cTypes = [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name + " Temperature", + supportEvents: true, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + },{ + cType: types.CURRENT_TEMPERATURE_CTYPE, + onRead: function(callback) { that.getCurrentTemperature(callback); }, + onUpdate: null, + perms: ["pr","ev"], + format: "float", + initialValue: 0.0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Temperature", + unit: "celsius" + }]; + return cTypes; + }, + + co2Characteristics: function(that) { + var cTypes = [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name + "Carbon Dioxide Level", + supportEvents: true, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + },{ + cType: types.CARBON_DIOXIDE_DETECTED_CTYPE, + //onRead: function(callback) { that.getCurrentTemperature(callback); }, + onRead: function(callback) { callback(0); }, + onUpdate: null, + perms: ["pr","ev"], + format: "uint8", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "CO2 detected" + },{ + cType: types.CARBON_DIOXIDE_LEVEL_CTYPE, + onRead: function(callback) { that.getCurrentCO2Level(callback); }, + onUpdate: null, + perms: ["pr","ev"], + format: "float", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "CO2 level " + }]; + return cTypes; + }, + + airQualityCharacteristics: function(that) { + var cTypes = [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name + " Air Quality", + supportEvents: true, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + },{ + cType: types.AIR_QUALITY_CTYPE, + onRead: function(callback) { that.getAirQuality(callback); }, + onUpdate: null, + perms: ["pr","ev"], + format: "float", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Air Quality", + }]; + return cTypes; + }, + + + getServices: function() { + var that = this; + var services = [{ + sType: types.ACCESSORY_INFORMATION_STYPE, + characteristics: this.informationCharacteristics(), + }]; + + // TEMPERATURE ////////////////////////////////////////////////// + if (this.serviceTypes.indexOf("Temperature") > -1) { + var tempSensorSvc = { + sType: types.TEMPERATURE_SENSOR_STYPE, + characteristics: this.temperatureCharacteristics(that) + } + services.push(tempSensorSvc); + } + // HUMIDITY //////////////////////////////////////////////////// + if (this.serviceTypes.indexOf("Humidity") > -1) { + services.push({ + sType: types.HUMIDITY_SENSOR_STYPE, + characteristics: this.humidityCharacteristics(that) + }); + } + // CO2 SENSOR ///////////////////////////////////////////////// + if (this.serviceTypes.indexOf("CO2") > -1) { + services.push({ + sType: types.CARBON_DIOXIDE_SENSOR_STYPE, + characteristics: this.co2Characteristics(that) + }); + services.push({ + sType: types.AIR_QUALITY_SENSOR_STYPE, + characteristics: this.airQualityCharacteristics(that) + }); + } + + // TODO: Pressure + // TODO: Noise + // TODO: Battery + // TODO: Check Elgato Eve Characteristics (map min, max, time series, etc.)! + + return services; + } +}; + +module.exports.accessory = NetatmoAccessory; +module.exports.platform = NetatmoPlatform; \ No newline at end of file From b4acdf38654a4bb63792cf0b2746a059e33de789 Mon Sep 17 00:00:00 2001 From: Francesco Carucci Date: Sat, 24 Oct 2015 00:19:13 +0000 Subject: [PATCH 46/89] Change the garage door accessory name --- accessories/LiftMaster.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accessories/LiftMaster.js b/accessories/LiftMaster.js index 1f35847..020f85c 100644 --- a/accessories/LiftMaster.js +++ b/accessories/LiftMaster.js @@ -258,7 +258,7 @@ LiftMasterAccessory.prototype = { onUpdate: null, perms: ["pr"], format: "string", - initialValue: "Garage Door Opener Control", + initialValue: "Garage Door", supportEvents: false, supportBonjour: false, manfDescription: "Name of service", From 76136589f4da53a50b243ec7b13d8e28eb621b0b Mon Sep 17 00:00:00 2001 From: Francesco Carucci Date: Sat, 24 Oct 2015 21:45:22 +0000 Subject: [PATCH 47/89] LiftMaster: return current door state --- accessories/LiftMaster.js | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/accessories/LiftMaster.js b/accessories/LiftMaster.js index 020f85c..4a61380 100644 --- a/accessories/LiftMaster.js +++ b/accessories/LiftMaster.js @@ -16,9 +16,17 @@ LiftMasterAccessory.prototype = { setState: function(state) { this.targetState = state; + this.callback = undefined; this.login(); }, + getState: function(callback) { + this.targetState = undefined; + this.callback = callback; + this.login(); + return true; + }, + login: function() { var that = this; @@ -95,15 +103,21 @@ LiftMasterAccessory.prototype = { if (!that.requiredDeviceId) { var thisDeviceId = device.MyQDeviceId; var thisDoorName = "Unknown"; + var thisDoorState = 2; + for (var j = 0; j < device.Attributes.length; j ++) { var thisAttributeSet = device.Attributes[j]; if (thisAttributeSet.AttributeDisplayName == "desc") { thisDoorName = thisAttributeSet.Value; break; } + if (thisAttributeSet.AttributeDisplayName == "doorstate") { + thisDoorState = thisAttributeSet.Value; + } } foundDoors.push(thisDeviceId + " - " + thisDoorName); that.deviceId = thisDeviceId; + that.deviceState = thisDoorState; } // We specified a door ID, sanity check to make sure it's the one we expected @@ -111,9 +125,7 @@ LiftMasterAccessory.prototype = { that.deviceId = device.MyQDeviceId; break; } - } - } // If we have multiple found doors, refuse to proceed @@ -132,8 +144,14 @@ LiftMasterAccessory.prototype = { // Did we get a device ID? if (that.deviceId) { - that.log("Found an opener with ID " + that.deviceId +". Ready to send command..."); - that.setTargetState(); + if (that.targetState != undefined) { + that.log("Found an opener with ID " + that.deviceId +". Ready to send command..."); + that.setTargetState(); + } + if (that.callback != undefined) { + that.log("Found an opener with ID " + that.deviceId + " [doorstate: " + that.deviceState + "]"); + that.getCurrentState(that.callback); + } } else { @@ -146,6 +164,11 @@ LiftMasterAccessory.prototype = { }); }, + getCurrentState: function(callback) { + this.log("Getting current state: " + this.deviceState); + callback(this.deviceState == 2); + }, + setTargetState: function() { var that = this; @@ -181,7 +204,6 @@ LiftMasterAccessory.prototype = { body: body, json: true }, function(err, response, json) { - if (!err && response.statusCode == 200) { if (json["ReturnCode"] == "0") @@ -266,6 +288,7 @@ LiftMasterAccessory.prototype = { },{ cType: types.CURRENT_DOOR_STATE_CTYPE, onUpdate: function(value) { that.log("Update current state to " + value); }, + onRead: function(callback) { that.getState(callback); }, perms: ["pr","ev"], format: "int", initialValue: 0, @@ -281,7 +304,7 @@ LiftMasterAccessory.prototype = { onUpdate: function(value) { that.setState(value); }, perms: ["pr","pw","ev"], format: "int", - initialValue: 0, + initialValue: 1, supportEvents: false, supportBonjour: false, manfDescription: "BlaBla", From ecdffbef23114d62516360a61f61359edabe8bc3 Mon Sep 17 00:00:00 2001 From: Mark Van Holstyn Date: Sat, 24 Oct 2015 23:21:42 -0400 Subject: [PATCH 48/89] adding support for usernames and password in http accessories --- accessories/Http.js | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/accessories/Http.js b/accessories/Http.js index fb708e3..d64560f 100644 --- a/accessories/Http.js +++ b/accessories/Http.js @@ -14,14 +14,21 @@ function HttpAccessory(log, config) { this.off_url = config["off_url"]; this.brightness_url = config["brightness_url"]; this.http_method = config["http_method"]; + this.username = config["username"]; + this.password = config["password"]; } HttpAccessory.prototype = { - httpRequest: function(url, method, callback) { + httpRequest: function(url, method, username, password, callback) { request({ url: url, - method: method + method: method, + auth: { + user: username, + pass: password, + sendImmediately: false + } }, function (error, response, body) { callback(error, response, body) @@ -40,13 +47,17 @@ HttpAccessory.prototype = { this.log("Setting power state to off"); } - this.httpRequest(url, this.http_method, function(error, response, body) { + this.httpRequest(url, this.http_method, this.username, this.password, function(error, response, body) { if (error) { this.log('HTTP power function failed: %s', error.message); callback(error); } else { this.log('HTTP power function succeeded!'); + this.log(response); + this.log(body); + this.log(this.username); + this.log(this.password); callback(); } }.bind(this)); @@ -57,7 +68,7 @@ HttpAccessory.prototype = { this.log("Setting brightness to %s", level); - this.httpRequest(url, this.http_method, function(error, response, body) { + this.httpRequest(url, this.http_method, this.username, this.password, function(error, response, body) { if (error) { this.log('HTTP brightness function failed: %s', error); callback(error); @@ -68,33 +79,33 @@ HttpAccessory.prototype = { } }.bind(this)); }, - + identify: function(callback) { this.log("Identify requested!"); callback(); // success }, - + 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 Model") .setCharacteristic(Characteristic.SerialNumber, "HTTP Serial Number"); - + var lightbulbService = new Service.Lightbulb(); - + lightbulbService .getCharacteristic(Characteristic.On) .on('set', this.setPowerState.bind(this)); - + lightbulbService .addCharacteristic(new Characteristic.Brightness()) .on('set', this.setBrightness.bind(this)); - + return [informationService, lightbulbService]; } }; From dc6faa97845ad7d5ba5dbf4acd2545ab3152a156 Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 25 Oct 2015 21:57:46 +0100 Subject: [PATCH 49/89] fixed netatmo problem with duplicate module names --- platforms/Netatmo.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/platforms/Netatmo.js b/platforms/Netatmo.js index 24fe572..3ff216a 100644 --- a/platforms/Netatmo.js +++ b/platforms/Netatmo.js @@ -49,7 +49,7 @@ function NetAtmoRepository(log, api) { NetAtmoRepository.prototype = { refresh: function(callback) { - var datasource={ + var datasource = { devices: {}, modules: {} }; @@ -69,7 +69,7 @@ NetAtmoRepository.prototype = { }, load: function(callback) { var that = this; - this.cache.get( "datasource", function( err, datasource ){ + this.cache.get( "datasource", function( err, datasource ) { if( !err ){ if ( datasource == undefined ){ that.refresh(callback); @@ -126,7 +126,14 @@ function NetatmoAccessory(log, repository, deviceId, moduleId, device) { if (moduleId) { this.serial = moduleId; } - this.name = device.module_name; + + // add station name to devices to avoid duplicate names + if (device.station_name) { + this.name = device.station_name + " " + device.module_name; + } else { + this.name = device.module_name; + } + this.model = device.type; this.serviceTypes = device.data_type; if (device.battery_vp) { @@ -161,7 +168,7 @@ NetatmoAccessory.prototype = { getAirQuality: function(callback) { this.getData(function(deviceData) { - var level = deviceData.dashboard_data.CO2; + var level = deviceData.dashboard_data.CO2; var quality = 0; if (level > 2000) quality = 5; else if (level > 1500) quality = 4; @@ -391,4 +398,4 @@ NetatmoAccessory.prototype = { }; module.exports.accessory = NetatmoAccessory; -module.exports.platform = NetatmoPlatform; \ No newline at end of file +module.exports.platform = NetatmoPlatform; From 65d732415ec72f9c66e36fbd7463773ed5992649 Mon Sep 17 00:00:00 2001 From: Tommaso Marchionni Date: Mon, 26 Oct 2015 00:32:40 +0100 Subject: [PATCH 50/89] Update Openhab.js --- platforms/Openhab.js | 780 ++++++++++++++++++++++++++++--------------- 1 file changed, 503 insertions(+), 277 deletions(-) diff --git a/platforms/Openhab.js b/platforms/Openhab.js index a148240..4c0e6bd 100644 --- a/platforms/Openhab.js +++ b/platforms/Openhab.js @@ -1,4 +1,4 @@ -// OpenHAB Platform Shim for HomeBridge +// OpenHAB 1 Platform Shim for HomeBridge // Written by Tommaso Marchionni // Based on many of the other HomeBridge platform modules // @@ -7,6 +7,9 @@ // 17 October 2015 [tommasomarchionni] // - Initial release // +// 25 October 2015 [tommasomarchionni] +// - Added WS listener and new OOP structure +// // Remember to add platform to config.json. Example: // "platforms": [ // { @@ -23,14 +26,23 @@ // Switch item=Light_1 label="Light 1" // } // +// Rollershutter is tested with this binding in OpenHAB: +// command=SWITCH_MULTILEVEL,invert_percent=true,invert_state=false" // 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"); +//////// LIBS ///////// + +var WebSocket = require('ws'); var request = require("request"); var Service = require("hap-nodejs/lib/Service.js").Service; var Characteristic = require("hap-nodejs").Characteristic; +var currentModule = this; +var util = require('core-util-is'); +util.inherits = require('inherits'); + +//////// PLATFORM ///////// function OpenhabPlatform(log, config){ this.log = log; @@ -38,310 +50,524 @@ function OpenhabPlatform(log, config){ 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."); + accessories: function(callback) { var that = this; - - url = that.sitemapUrl(); - this.log("Connecting to " + url); + this.log("Platform - Fetching OpenHAB devices."); + var itemFactory = new ItemFactory(this); + url = itemFactory.sitemapUrl(); + this.log("Platform - 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."); + callback(itemFactory.parseSitemap(json)); + } else { + that.log("Platform - 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; - } +//////// END PLATFORM ///////// + +///////// ACCESSORY ///////// + +function OpenhabAccessory(widget,platform) {} + +///////// ABSTRACT ITEM ///////// + +function AbstractItem(widget,platform){ + + AbstractItem.super_.call(this,widget,platform); + + this.widget = widget; + this.label = widget.label; + this.name = widget.item.name; + this.url = widget.item.link; + this.state = widget.item.state; + this.platform = platform; + this.log = platform.log; + this.setInitialState = false; + this.setFromOpenHAB = false; + this.informationService = undefined; + this.otherService = undefined; + this.listener = undefined; + this.ws = undefined; +}; + +util.inherits(AbstractItem, OpenhabAccessory); + +AbstractItem.prototype.getInformationServices = function() { + informationService = new Service.AccessoryInformation(); + + informationService + .setCharacteristic(Characteristic.Manufacturer, "OpenHAB") + .setCharacteristic(Characteristic.Model, this.constructor.name) + .setCharacteristic(Characteristic.SerialNumber, "N/A") + .setCharacteristic(Characteristic.Name, this.name); + return informationService; } -OpenhabAccessory.prototype = { +AbstractItem.prototype.checkListener = function() { + + if (typeof this.listener == 'undefined' || typeof this.ws == 'undefined') { + this.ws = undefined; + this.listener = new WSListener(this, this.updateCharacteristics.bind(this)); + this.listener.startListener(); + } +}; - 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); - - }); - }, +///////// END ABSTRACT ITEM ///////// + +///////// SWITCH ITEM ///////// + +function SwitchItem(widget,platform){ + SwitchItem.super_.call(this, widget,platform); +}; + +util.inherits(SwitchItem, AbstractItem); + +SwitchItem.prototype.getServices = function() { + + this.checkListener(); + this.setInitialState = true; + this.informationService = this.getInformationServices(); - 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; - } - }, + this.otherService = new Service.Lightbulb(); + this.otherService.getCharacteristic(Characteristic.On) + .on('set', this.setItem.bind(this)) + .on('get', this.getItemPowerState.bind(this)) + .setValue(this.state === 'ON'); + + return [this.informationService, this.otherService]; +}; + +SwitchItem.prototype.updateCharacteristics = function(message) { + + this.setFromOpenHAB = true; + this.otherService + .getCharacteristic(Characteristic.On) + .setValue(message === 'ON' ? true : false, + function() { + this.setFromOpenHAB = false; + }.bind(this) + ); +}; - 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)); - }, +SwitchItem.prototype.getItemPowerState = function(callback) { - setPowerState: function(powerOn, callback) { - var that = this; + var self = this; + this.checkListener(); + + this.log("iOS - request power state from " + this.name); + request(this.url + '/state?type=json', function (error, response, body) { + if (!error && response.statusCode == 200) { + self.log("OpenHAB HTTP - response from " + self.name + ": " + body); + callback(undefined,body == "ON" ? true : false); + } else { + self.log("OpenHAB HTTP - error from " + self.name + ": " + error); + } + }) +}; + +SwitchItem.prototype.setItem = function(value, callback) { + + var self = this; + this.checkListener(); + + if (this.setInitialState) { + this.setInitialState = false; + callback(); + return; + } + + if (this.setFromOpenHAB) { + callback(); + return; + } + + this.log("iOS - send message to " + this.name + ": " + value); + var command = value ? 'ON' : 'OFF'; + request.post( + this.url, + { body: command }, + function (error, response, body) { + if (!error && response.statusCode == 201) { + self.log("OpenHAB HTTP - response from " + self.name + ": " + body); + } else { + self.log("OpenHAB HTTP - error from " + self.name + ": " + error); + } + callback(); + } + ); +}; + +///////// END SWITCH ITEM ///////// + +///////// DIMMER ITEM ///////// + +function DimmerItem(widget,platform){ + DimmerItem.super_.call(this, widget,platform); +}; + +util.inherits(DimmerItem, AbstractItem); + +DimmerItem.prototype.getServices = function() { + + this.checkListener(); + this.setInitialState = true; + + this.informationService = this.getInformationServices(); - 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); + this.otherService = new Service.Lightbulb(); + this.otherService.getCharacteristic(Characteristic.On) + .on('set', this.setItem.bind(this)) + .on('get', this.getItemPowerState.bind(this)) + .setValue(+this.state > 0); + + this.setInitialState = true; + + this.otherService.addCharacteristic(Characteristic.Brightness) + .on('set', this.setItem.bind(this)) + .on('get', this.getItemBrightnessState.bind(this)) + .setValue(+this.state); + + return [this.informationService, this.otherService]; +}; - 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); - } - - - +DimmerItem.prototype.updateCharacteristics = function(message) { + + this.setFromOpenHAB = true; + var brightness = +message; + var steps = 2; + if (brightness >= 0) { + this.otherService.getCharacteristic(Characteristic.Brightness) + .setValue(brightness, + function() { + steps--; + if (!steps) { + this.setFromOpenHAB = false; + } + }.bind(this)); + this.otherService.getCharacteristic(Characteristic.On) + .setValue(brightness > 0 ? true : false, + function() { + steps--; + if (!steps) { + this.setFromOpenHAB = false; + } }.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]; - } - + } } + +DimmerItem.prototype.getItemPowerState = function(callback) { + + var self = this; + this.checkListener(); + + this.log("iOS - request power state from " + this.name); + request(this.url + '/state?type=json', function (error, response, body) { + if (!error && response.statusCode == 200) { + self.log("OpenHAB HTTP - response from " + self.name + ": " + body); + callback(undefined,+body > 0 ? true : false); + } else { + self.log("OpenHAB HTTP - error from " + self.name + ": " + error); + } + }) +}; + +DimmerItem.prototype.setItem = function(value, callback) { + + var self = this; + this.checkListener(); + + if (this.setInitialState) { + this.setInitialState = false; + callback(); + return; + } + + if (this.setFromOpenHAB) { + callback(); + return; + } + + this.log("iOS - send message to " + this.name + ": " + value); + var command = 0; + if (typeof value === 'boolean') { + command = value ? '100' : '0'; + } else { + command = "" + value; + } + request.post( + this.url, + { + body: command, + headers: {'Content-Type': 'text/plain'} + }, + function (error, response, body) { + if (!error && response.statusCode == 201) { + self.log("OpenHAB HTTP - response from " + self.name + ": " + body); + } else { + self.log("OpenHAB HTTP - error from " + self.name + ": " + error); + } + callback(); + } + ); +}; + +DimmerItem.prototype.getItemBrightnessState = function(callback) { + + var self = this; + + this.log("iOS - request brightness state from " + this.name); + request(this.url + '/state?type=json', function (error, response, body) { + if (!error && response.statusCode == 200) { + self.log("OpenHAB HTTP - response from " + self.name + ": " + body); + callback(undefined,+body); + } else { + self.log("OpenHAB HTTP - error from " + self.name + ": " + error); + } + }) +}; + +///////// END DIMMER ITEM ///////// + +///////// ROLLERSHUTTER ITEM ///////// + +function RollershutterItem(widget,platform){ + RollershutterItem.super_.call(this, widget,platform); + this.positionState = Characteristic.PositionState.STOPPED; + this.currentPosition = 100; + this.targetPosition = 100; + this.startedPosition = 100; +}; + +util.inherits(RollershutterItem, AbstractItem); + +RollershutterItem.prototype.getServices = function() { + + this.checkListener(); + + this.informationService = this.getInformationServices(); + + this.otherService = new Service.WindowCovering(); + + this.otherService.getCharacteristic(Characteristic.CurrentPosition) + .on('get', this.getItemCurrentPosition.bind(this)) + .setValue(this.currentPosition); + + this.setInitialState = true; + + this.otherService.getCharacteristic(Characteristic.TargetPosition) + .on('set', this.setItem.bind(this)) + .on('get', this.getItemTargetPosition.bind(this)) + .setValue(this.currentPosition); + + this.otherService.getCharacteristic(Characteristic.PositionState) + .on('get', this.getItemPositionState.bind(this)) + .setValue(this.positionState); + + return [this.informationService, this.otherService]; +}; + + + +RollershutterItem.prototype.updateCharacteristics = function(message) { + + console.log(message); + console.log(this.targetPosition); + + + + if (parseInt(message) == this.targetPosition) { + var ps = Characteristic.PositionState.STOPPED; + var cs = parseInt(message); + } else if (parseInt(message) > this.targetPosition){ + var ps = Characteristic.PositionState.INCREASING; + var cs = this.startedPosition; + } else { + var ps = Characteristic.PositionState.DECREASING; + var cs = this.startedPosition; + } + + this.otherService + .getCharacteristic(Characteristic.PositionState) + .setValue(ps); + + this.otherService + .getCharacteristic(Characteristic.CurrentPosition) + .setValue(parseInt(cs)); + this.currentPosition = parseInt(cs); +}; + +RollershutterItem.prototype.setItem = function(value, callback) { + + var self = this; + this.checkListener(); + + if (this.setInitialState) { + this.setInitialState = false; + callback(); + return; + } + + this.startedPosition = this.currentPosition; + + this.log("iOS - send message to " + this.name + ": " + value); + + var command = 0; + if (typeof value === 'boolean') { + command = value ? '100' : '0'; + } else { + command = "" + value; + } + request.post( + this.url, + { + body: command, + headers: {'Content-Type': 'text/plain'} + }, + function (error, response, body) { + if (!error && response.statusCode == 201) { + self.log("OpenHAB HTTP - response from " + self.name + ": " + body); + self.targetPosition = parseInt(value); + } else { + self.log("OpenHAB HTTP - error from " + self.name + ": " + error); + } + callback(); + } + ); +}; + +RollershutterItem.prototype.getItemPositionState = function(callback) { + this.log("iOS - request position state from " + this.name); + this.log("Platform - response from " + this.name + ": " + this.positionState); + callback(undefined,this.positionState); +}; + +RollershutterItem.prototype.getItemTargetPosition = function(callback) { + this.log("iOS - get target position state from " + this.name); + this.log("Platform - response from " + this.name + ": " + this.targetPosition); + callback(undefined,this.targetPosition); +} + +RollershutterItem.prototype.getItemCurrentPosition = function(callback) { + var self = this; + this.log("iOS - request current position state from " + this.name); + + request(this.url + '/state?type=json', function (error, response, body) { + if (!error && response.statusCode == 200) { + + self.log("OpenHAB HTTP - response from " + self.name + ": " +body); + self.currentPosition = parseInt(body); + callback(undefined,parseInt(body)); + + } else { + self.log("OpenHAB HTTP - error from " + self.name + ": " + error); + } + }) +}; + +///////// END ROLLERSHUTTER ITEM ///////// + +///////// ITEM UTILITY ///////// + +function ItemFactory(openhabPlatform){ + this.platform = openhabPlatform; + this.log = this.platform.log; +} + +ItemFactory.prototype = { + sitemapUrl: function() { + var serverString = this.platform.server; + //TODO da verificare + if (this.platform.user && this.platform.password) { + serverString = this.platform.user + ":" + this.platform.password + "@" + serverString; + } + + return this.platform.protocol + "://" + serverString + ":" + this.platform.port + "/rest/sitemaps/" + this.platform.sitemap + "?type=json"; + }, + + parseSitemap: function(jsonSitemap) { + var widgets = [].concat(jsonSitemap.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("Platform - The widget '" + widget.label + "' is not an item."); + continue; + } + + if (currentModule[widget.item.type] != undefined) { + var accessory = new currentModule[widget.item.type](widget,this.platform); + } else { + this.log("Platform - The widget '" + widget.label + "' of type "+widget.item.type+" is an item not handled."); + continue; + } + + this.log("Platform - Accessory Found: " + widget.label); + result.push(accessory); + } + return result; + } + +}; + +///////// END ITEM UTILITY ///////// + +///////// WS LISTENER ///////// + +function WSListener(item, callback){ + this.item = item; + this.callback = callback; +} + +WSListener.prototype = { + startListener: function() { + var self = this; + + if (typeof this.item.ws == 'undefined') { + this.item.ws = new WebSocket(this.item.url.replace('http:', 'ws:') + '/state?type=json'); + } + + this.item.ws.on('open', function() { + self.item.log("OpenHAB WS - new connection for "+self.item.name); + }); + + this.item.ws.on('message', function(message) { + self.item.log("OpenHAB WS - message from " +self.item.name+": "+ message); + self.callback(message); + }); + + this.item.ws.on('close', function close() { + self.item.log("OpenHAB WS - closed connection for "+self.item.name); + self.item.listener = undefined; + self.item.ws = undefined; + }); + } + +}; + +///////// END WS LISTENER ///////// + +///////// SUPPORTED ITEMS ///////// +module.exports.SwitchItem = SwitchItem; +module.exports.DimmerItem = DimmerItem; +module.exports.RollershutterItem = RollershutterItem; +///////// END SUPPORTED ITEMS ///////// module.exports.accessory = OpenhabAccessory; module.exports.platform = OpenhabPlatform; From 35ecb9e170b0c58bfa42bfdcfb017e0474eec254 Mon Sep 17 00:00:00 2001 From: Kraig McConaghy Date: Mon, 26 Oct 2015 22:48:02 -0500 Subject: [PATCH 51/89] Switched to use the new HAP APIs, extracted base class WinkAccessory, and changed update system to properly notify changes on a timed basis. Functionality wise, left the LightBulb implementation the same. --- package.json | 2 +- platforms/Wink.js | 393 ++++++++++++++++++++++------------------------ 2 files changed, 188 insertions(+), 207 deletions(-) diff --git a/package.json b/package.json index b920048..a936599 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "debug": "^2.2.0", "eibd": "^0.3.1", "elkington": "kevinohara80/elkington", - "hap-nodejs": "^0.0.2", + "hap-nodejs": "^0.0.3", "harmonyhubjs-client": "^1.1.6", "harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git", "isy-js": "", diff --git a/platforms/Wink.js b/platforms/Wink.js index d9de07f..b8da568 100644 --- a/platforms/Wink.js +++ b/platforms/Wink.js @@ -1,28 +1,56 @@ var types = require("hap-nodejs/accessories/types.js"); var wink = require('wink-js'); +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; + +process.env.WINK_NO_CACHE = true; var model = { light_bulbs: require('wink-js/lib/model/light') }; - function WinkPlatform(log, config){ // auth info this.client_id = config["client_id"]; this.client_secret = config["client_secret"]; + this.username = config["username"]; this.password = config["password"]; this.log = log; + this.deviceLookup = {}; } WinkPlatform.prototype = { + reloadData: function(callback) { + this.log("Refreshing Wink Data"); + var that = this; + wink.user().devices(function(devices) { + for (var i=0; i 0) { + return res.errors[0]; + } else if (res.data) { + this.device = res.data; + this.loadData(); + } +}; + +WinkAccessory.prototype.reloadData = function(callback){ + var that = this; + this.control.get(function(res) { + callback(that.handleResponse(res)); + }); +}; + + +/* + * Light Accessory + */ + +function WinkLightAccessory(log, device) { + // construct base + WinkAccessory.call(this, log, device, 'light_bulb', device.light_bulb_id); + + // accessor + var that = this; + + that.device = device; + that.deviceControl = model.light_bulbs(device, wink); + + this + .addService(Service.Lightbulb) + .getCharacteristic(Characteristic.On) + .on('get', function(callback) { + var powerState = that.device.desired_state.powered; + that.log("power state for " + that.name + " is: " + powerState); + callback(null, powerState != undefined ? powerState : false); + }) + .on('set', function(powerOn, callback) { + if (powerOn) { + that.log("Setting power state on the '"+that.name+"' to on"); + that.deviceControl.power.on(function(response) { + if (response === undefined) { + that.log("Error setting power state on the '"+that.name+"'"); + callback(Error("Error setting power state on the '"+that.name+"'")); + } else { + that.log("Successfully set power state on the '"+that.name+"' to on"); + callback(null, powerOn); + } + }); + }else{ + that.log("Setting power state on the '"+that.name+"' to off"); + that.deviceControl.power.off(function(response) { + if (response === undefined) { + that.log("Error setting power state on the '"+that.name+"'"); + callback(Error("Error setting power state on the '"+that.name+"'")); + } else { + that.log("Successfully set power state on the '"+that.name+"' to off"); + callback(null, powerOn); + } + }); + } + }); + + this + .getService(Service.Lightbulb) + .getCharacteristic(Characteristic.Brightness) + .on('get', function(callback) { + var level = that.device.desired_state.brightness * 100; + that.log("brightness level for " + that.name + " is: " + level); + callback(null, level); + }) + .on('set', function(level, callback) { + that.log("Setting brightness on the '"+this.name+"' to " + level); + that.deviceControl.brightness(level, function(response) { + if (response === undefined) { + that.log("Error setting brightness on the '"+that.name+"'"); + callback(Error("Error setting brightness on the '"+that.name+"'")); + } else { + that.log("Successfully set brightness on the '"+that.name+"' to " + level); + callback(null, level); + } + }); + }); + + WinkLightAccessory.prototype.loadData.call(this); +} + +inherits(WinkLightAccessory, WinkAccessory); +WinkLightAccessory.prototype.parent = WinkAccessory.prototype; + +WinkLightAccessory.prototype.loadData = function() { + this.parent.loadData.call(this); + this.getService(Service.Lightbulb) + .getCharacteristic(Characteristic.On) + .getValue(); + this.getService(Service.Lightbulb) + .getCharacteristic(Characteristic.Brightness) + .getValue(); +}; + module.exports.accessory = WinkAccessory; +module.exports.lightAccessory = WinkLightAccessory; module.exports.platform = WinkPlatform; From 1f8e6e5c8becf06151e9ac569f0f653e000de431 Mon Sep 17 00:00:00 2001 From: Kraig McConaghy Date: Mon, 26 Oct 2015 22:49:36 -0500 Subject: [PATCH 52/89] Added WinkLockAccessory --- platforms/Wink.js | 121 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 2 deletions(-) diff --git a/platforms/Wink.js b/platforms/Wink.js index b8da568..12ff7a8 100644 --- a/platforms/Wink.js +++ b/platforms/Wink.js @@ -9,7 +9,28 @@ var inherits = require('util').inherits; process.env.WINK_NO_CACHE = true; var model = { - light_bulbs: require('wink-js/lib/model/light') + light_bulbs: require('wink-js/lib/model/light'), + refreshUntil: function(that, maxTimes, predicate, callback, interval, incrementInterval) { + if (!interval) { + interval = 500; + } + if (!incrementInterval) { + incrementInterval = 500; + } + setTimeout(function() { + that.reloadData(function() { + if (predicate == undefined || predicate(that.device) == true) { + if (callback) callback(true); + } else if (maxTimes > 0) { + maxTimes = maxTimes - 1; + interval += incrementInterval; + model.refreshUntil(that, maxTimes, predicate, callback, interval, incrementInterval); + } else { + if (callback) callback(false); + } + }); + }, interval); + } }; function WinkPlatform(log, config){ @@ -32,7 +53,7 @@ WinkPlatform.prototype = { wink.user().devices(function(devices) { for (var i=0; i Date: Tue, 27 Oct 2015 15:07:15 +0100 Subject: [PATCH 53/89] Fix case in hap-nodejs module require statements --- platforms/PhilipsHue.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 26726b4..18df91a 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -33,8 +33,8 @@ var hue = require("node-hue-api"), HueApi = hue.HueApi, lightState = hue.lightState; -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; function PhilipsHuePlatform(log, config) { From 06c7356aef3ddb1a6c71f7ed3aa67c28f33c0848 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 27 Oct 2015 19:24:43 +0100 Subject: [PATCH 54/89] updated to new netatmo node module --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a936599..bc8bf99 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "lifx": "git+https://github.com/magicmonkey/lifxjs.git", "lifx-api": "^1.0.1", "mdns": "^2.2.4", - "netatmo": "1.3.0", + "netatmo": "git+https://github.com/patricks/netatmo.git", "node-cache": "3.0.0", "node-hue-api": "^1.0.5", "node-icontrol": "^0.1.5", From 909900d025dc65cfff721dd7ca619bf43c946e52 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 27 Oct 2015 20:41:24 +0100 Subject: [PATCH 55/89] Using the new getStationsData() api call, the station name is saved to every module name, so we avoid duplicate accessories UUIDs --- platforms/Netatmo.js | 69 ++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 41 deletions(-) diff --git a/platforms/Netatmo.js b/platforms/Netatmo.js index 3ff216a..fe532bd 100644 --- a/platforms/Netatmo.js +++ b/platforms/Netatmo.js @@ -19,7 +19,7 @@ // // 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"); ////////////////////////////////////////////////////////////////////////////// // DECLARE SOME UUIDS WHICH SHOUL BE IN HAP-NODEJS TYPES LIB, BUT ARE NOT YET @@ -38,8 +38,8 @@ types.CARBON_DIOXIDE_LEVEL_CTYPE = stPre + "93" + stPost; types.AIR_QUALITY_CTYPE = stPre + "95" + stPost; ////////////////////////////////////////////////////////////////////////////// -var netatmo = require('netatmo'); -var NodeCache = require( "node-cache" ); +var netatmo = require("netatmo"); +var NodeCache = require("node-cache"); function NetAtmoRepository(log, api) { this.api = api; @@ -50,28 +50,36 @@ function NetAtmoRepository(log, api) { NetAtmoRepository.prototype = { refresh: function(callback) { var datasource = { - devices: {}, modules: {} }; var that = this; - that.api.getDevicelist(function(err, devices, modules) { + that.api.getStationsData(function(err, devices) { + + // querying for the device infos and the main module for (var device of devices) { + device.module_name = device.station_name + " " + device.module_name + that.log("refreshing device " + device._id + " (" + device.module_name + ")"); - datasource.devices[device._id] = device; - } - for (var module of modules) { - that.log("refreshing module " + module._id + " (" + module.module_name + ")"); - datasource.modules[module._id] = module; + datasource.modules[device._id] = device; + + // querying for the extra modules + for (var module of device.modules) { + module.module_name = device.station_name + " " + module.module_name + + that.log("refreshing device " + module._id + " (" + module.module_name + ")"); + datasource.modules[module._id] = module; + } } + that.cache.set( "datasource", datasource, 20 ); callback(datasource); }); }, load: function(callback) { var that = this; - this.cache.get( "datasource", function( err, datasource ) { - if( !err ){ - if ( datasource == undefined ){ + this.cache.get( "datasource", function(err, datasource) { + if(!err) { + if (datasource == undefined) { that.refresh(callback); } else { callback(datasource) @@ -100,16 +108,9 @@ NetatmoPlatform.prototype = { var foundAccessories = []; this.repository.load(function(datasource) { - for (var id in datasource.devices) { - var device = datasource.devices[id]; - that.log("Adding accessory for device " + id + " (" + device.module_name + ")"); - var accessory = new NetatmoAccessory(that.log, that.repository, device._id, null, device); - foundAccessories.push(accessory); - } for (var id in datasource.modules) { - var module = datasource.modules[id]; - that.log("Adding accessory for module " + module._id + " (" + module.module_name + ")"); - var accessory = new NetatmoAccessory(that.log, that.repository, module.main_device, module._id, module); + var device = datasource.modules[id]; + var accessory = new NetatmoAccessory(that.log, that.repository, device); foundAccessories.push(accessory); } callback(foundAccessories); @@ -117,22 +118,12 @@ NetatmoPlatform.prototype = { } } -function NetatmoAccessory(log, repository, deviceId, moduleId, device) { +function NetatmoAccessory(log, repository, device) { this.log = log; this.repository = repository; - this.deviceId = deviceId; - this.moduleId = moduleId; - this.serial = deviceId; - if (moduleId) { - this.serial = moduleId; - } - - // add station name to devices to avoid duplicate names - if (device.station_name) { - this.name = device.station_name + " " + device.module_name; - } else { - this.name = device.module_name; - } + this.deviceId = device._id; + this.name = device.module_name + this.serial = device._id; this.model = device.type; this.serviceTypes = device.data_type; @@ -146,11 +137,7 @@ NetatmoAccessory.prototype = { getData: function(callback) { var that = this; this.repository.load(function(datasource) { - if(that.moduleId) { - callback(datasource.modules[that.moduleId]); - } else { - callback(datasource.devices[that.deviceId]); - } + callback(datasource.modules[that.deviceId]); }); }, From 9740a5052038a38da1faec37b6a29fff3f1957eb Mon Sep 17 00:00:00 2001 From: Kraig McConaghy Date: Tue, 27 Oct 2015 23:07:34 -0500 Subject: [PATCH 56/89] Several fixes. Full simulated temperature range support. --- package.json | 4 +- platforms/Nest.js | 360 +++++++++++++++++++--------------------------- 2 files changed, 153 insertions(+), 211 deletions(-) diff --git a/package.json b/package.json index ff5563b..ee1027d 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "color": "0.10.x", "eibd": "^0.3.1", "elkington": "kevinohara80/elkington", - "hap-nodejs": "^0.0.2", + "hap-nodejs": "^0.0.3", "harmonyhubjs-client": "^1.1.4", "harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git", "lifx-api": "^1.0.1", @@ -32,7 +32,7 @@ "request": "2.49.x", "sonos": "0.8.x", "teslams": "1.0.1", - "unofficial-nest-api": "git+https://github.com/hachidorii/unofficial_nodejs_nest.git#d8d48edc952b049ff6320ef99afa7b2f04cdee98", + "unofficial-nest-api": "git+https://github.com/kraigm/unofficial_nodejs_nest.git#3cbd337adc32fab3b481659b38d86f9fcd6a9c02", "wemo": "0.2.x", "wink-js": "0.0.5", "xml2js": "0.4.x", diff --git a/platforms/Nest.js b/platforms/Nest.js index 682fdea..b4dd77f 100644 --- a/platforms/Nest.js +++ b/platforms/Nest.js @@ -6,14 +6,12 @@ var Accessory = require("hap-nodejs").Accessory; var uuid = require("hap-nodejs").uuid; var inherits = require('util').inherits; - function NestPlatform(log, config){ + // auth info + this.username = config["username"]; + this.password = config["password"]; - // auth info - this.username = config["username"]; - this.password = config["password"]; - - this.log = log; + this.log = log; this.accessoryLookup = { }; } @@ -27,10 +25,7 @@ NestPlatform.prototype = { nest.login(this.username, this.password, function (err, data) { if (err) { that.log("There was a problem authenticating with Nest."); - } - else { - - + } else { nest.fetchStatus(function (data) { for (var deviceId in data.device) { if (data.device.hasOwnProperty(deviceId)) { @@ -68,255 +63,202 @@ NestPlatform.prototype = { } function NestThermostatAccessory(log, name, device, deviceId, initialData) { - // device info - if (name) { - this.name = name; - } else { - this.name = "Nest"; - } - this.model = device.model_version; - this.serial = device.serial_number; - this.deviceId = deviceId; - this.log = log; - Accessory.call(this, name, uuid.generate(deviceId)); + // device info + this.name = name || ("Nest" + device.serial_number); + this.deviceId = deviceId; + this.log = log; + this.device = device; + + var id = uuid.generate('nest.thermostat.' + deviceId); + Accessory.call(this, name, id); + this.uuid_base = id; + + this.currentData = initialData; this.getService(Service.AccessoryInformation) .setCharacteristic(Characteristic.Manufacturer, "Nest") - .setCharacteristic(Characteristic.Model, this.model) - .setCharacteristic(Characteristic.SerialNumber, this.serial); + .setCharacteristic(Characteristic.Model, device.model_version) + .setCharacteristic(Characteristic.SerialNumber, device.serial_number); this.addService(Service.Thermostat, name); this.getService(Service.Thermostat) - .setCharacteristic(Characteristic.TemperatureDisplayUnits, this.extractTemperatureUnits(device)) - .on('get', this.getTemperatureUnits); + .getCharacteristic(Characteristic.TemperatureDisplayUnits) + .on('get', function(callback) { + var units = this.getTemperatureUnits(); + var unitsName = units == Characteristic.TemperatureDisplayUnits.FAHRENHEIT ? "Fahrenheit" : "Celsius"; + this.log("Tempature unit for " + this.name + " is: " + unitsName); + if (callback) callback(null, units); + }.bind(this)); this.getService(Service.Thermostat) - .setCharacteristic(Characteristic.TargetTemperature, this.extractTargetTemperature(initialData)) - .on('get', this.getTargetTemperature) - .on('set', this.setTargetTemperature); + .getCharacteristic(Characteristic.CurrentTemperature) + .on('get', function(callback) { + var curTemp = this.getCurrentTemperature(); + this.log("Current temperature for " + this.name + " is: " + curTemp); + if (callback) callback(null, curTemp); + }.bind(this)); this.getService(Service.Thermostat) - .setCharacteristic(Characteristic.TargetHeatingCoolingState, this.extractTargetHeatingCooling(initialData)) - .on('get', this.getTargetHeatingCooling) - .on('set', this.setTargetHeatingCooling); + .getCharacteristic(Characteristic.CurrentHeatingCoolingState) + .on('get', function(callback) { + var curHeatingCooling = this.getCurrentHeatingCooling(); + this.log("Current heating for " + this.name + " is: " + curHeatingCooling); + if (callback) callback(null, curHeatingCooling); + }.bind(this)); + + this.getService(Service.Thermostat) + .getCharacteristic(Characteristic.TargetTemperature) + .on('get', function(callback) { + var targetTemp = this.getTargetTemperature(); + this.log("Target temperature for " + this.name + " is: " + targetTemp); + if (callback) callback(null, targetTemp); + }.bind(this)) + .on('set', this.setTargetTemperature.bind(this)); + + this.getService(Service.Thermostat) + .getCharacteristic(Characteristic.TargetHeatingCoolingState) + .on('get', function(callback) { + var targetHeatingCooling = this.getTargetHeatingCooling(); + this.log("Target heating for " + this.name + " is: " + targetHeatingCooling); + if (callback) callback(null, targetHeatingCooling); + }.bind(this)) + .on('set', this.setTargetHeatingCooling.bind(this)); this.updateData(initialData); } inherits(NestThermostatAccessory, Accessory); -//NestThermostatAccessory.prototype.parent = Accessory.prototype; -Service.prototype.getCharacteristic = function(name) { - // returns a characteristic object from the service - // If Service.prototype.getCharacteristic(Characteristic.Type) does not find the characteristic, - // but the type is in optionalCharacteristics, it adds the characteristic.type to the service and returns it. - var index, characteristic; - for (index in this.characteristics) { - characteristic = this.characteristics[index]; - if (typeof name === 'string' && characteristic.displayName === name) { - return characteristic; - } - else if (typeof name === 'function' && characteristic instanceof name) { - return characteristic; - } - } - if (typeof name === 'function') { - for (index in this.optionalCharacteristics) { - characteristic = this.optionalCharacteristics[index]; - if (characteristic instanceof name) { - return this.addCharacteristic(name); - } - } - } -}; +NestThermostatAccessory.prototype.parent = Accessory.prototype; NestThermostatAccessory.prototype.getServices = function() { return this.services; }; NestThermostatAccessory.prototype.updateData = function(data) { + if (data != undefined) { + this.currentData = data; + } var thermostat = this.getService(Service.Thermostat); - thermostat.setCharacteristic(Characteristic.CurrentTemperature, this.extractCurrentTemperature(data)); - thermostat.setCharacteristic(Characteristic.CurrentHeatingCoolingState, this.extractCurrentHeatingCooling(data)); - thermostat.setCharacteristic(Characteristic.CurrentRelativeHumidity, this.extractCurrentRelativeHumidity(data)); + thermostat.getCharacteristic(Characteristic.TemperatureDisplayUnits).getValue(); + thermostat.getCharacteristic(Characteristic.CurrentTemperature).getValue(); + thermostat.getCharacteristic(Characteristic.CurrentHeatingCoolingState).getValue(); + thermostat.getCharacteristic(Characteristic.TargetHeatingCoolingState).getValue(); + thermostat.getCharacteristic(Characteristic.TargetTemperature).getValue(); }; -NestThermostatAccessory.prototype.extractCurrentHeatingCooling = function(device){ - var currentHeatingCooling = 0; - switch(device.target_temperature_type) { - case "OFF": - currentHeatingCooling = 0; - break; - case "HEAT": - currentHeatingCooling = 1; - break; - case "COOL": - currentHeatingCooling = 2; - break; - case "RANGE": - currentHeatingCooling = 3; - break; - default: - currentHeatingCooling = 0; +NestThermostatAccessory.prototype.getCurrentHeatingCooling = function(){ + var current = this.getCurrentTemperature(); + var state = this.getTargetHeatingCooling(); + + var isRange = state == (Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL); + var high = isRange ? this.currentData.target_temperature_high : this.currentData.target_temperature; + var low = isRange ? this.currentData.target_temperature_low : this.currentData.target_temperature; + + // Add threshold + var threshold = .2; + high += threshold; + low -= threshold; + + if ((state & Characteristic.CurrentHeatingCoolingState.COOL) && this.currentData.can_cool && high < current) { + return Characteristic.CurrentHeatingCoolingState.COOL; } - this.log("Current heating for " + this.name + "is: " + currentHeatingCooling); - return currentHeatingCooling; + if ((state & Characteristic.CurrentHeatingCoolingState.HEAT) && this.currentData.can_heat && low > current) { + return Characteristic.CurrentHeatingCoolingState.HEAT; + } + return Characteristic.CurrentHeatingCoolingState.OFF; }; -NestThermostatAccessory.prototype.getCurrentHeatingCooling = function(callback){ - var that = this; - this.log("Checking current heating cooling for: " + this.name); - nest.fetchStatus(function (data) { - var device = data.device[that.deviceId]; - var currentHeatingCooling = that.extractCurrentHeatingCooling(device); - callback(currentHeatingCooling); - }); -}; -NestThermostatAccessory.prototype.extractTargetHeatingCooling = function(device){ - var targetHeatingCooling = 0; - switch(device.target_temperature_type) { + +NestThermostatAccessory.prototype.getTargetHeatingCooling = function(){ + switch(this.currentData.target_temperature_type) { case "off": - targetHeatingCooling = 0; - break; + return Characteristic.CurrentHeatingCoolingState.OFF; case "heat": - targetHeatingCooling = 1; - break; + return Characteristic.CurrentHeatingCoolingState.HEAT; case "cool": - targetHeatingCooling = 2; - break; + return Characteristic.CurrentHeatingCoolingState.COOL; case "range": - targetHeatingCooling = 3; - break; + return Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL; default: - targetHeatingCooling = 0; + return Characteristic.CurrentHeatingCoolingState.OFF; } - this.log("Current target heating for " + this.name + " is: " + targetHeatingCooling); - return targetHeatingCooling; -}; -NestThermostatAccessory.prototype.getTargetHeatingCooling = function(callback){ - var that = this; - this.log("Checking target heating cooling for: " + this.name); - nest.fetchStatus(function (data) { - var device = data.device[that.deviceId]; - var targetHeatingCooling = that.extractTargetHeatingCooling(device); - callback(targetHeatingCooling); - }); - }; - - -NestThermostatAccessory.prototype.extractCurrentTemperature = function(device){ - var curTemp = this.extractAsDisplayUnit(device.current_temperature, device); - this.log("Current temperature for " + this.name + " is: " + curTemp); - return curTemp; }; -NestThermostatAccessory.prototype.extractTargetTemperature = function(device){ - var targetTemp; - if (device.target_temperature != undefined) { - targetTemp = device.target_temperature; - } else if (device.temperature_lock_high_temp != undefined) { - targetTemp = device.temperature_lock_high_temp; - } else { - return null; +NestThermostatAccessory.prototype.getCurrentTemperature = function(){ + return this.currentData.current_temperature; +}; + +NestThermostatAccessory.prototype.getTargetTemperature = function() { + switch (this.getTargetHeatingCooling()) { + case Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL: + // Choose closest target as single target + var high = this.currentData.target_temperature_high; + var low = this.currentData.target_temperature_low; + var cur = this.currentData.current_temperature; + return Math.abs(high - cur) < Math.abs(cur - low) ? high : low; + default: + return this.currentData.target_temperature; } - - targetTemp = this.extractAsDisplayUnit(targetTemp, device); - this.log("Target temperature for " + this.name + " is: " + targetTemp); - return targetTemp; }; -NestThermostatAccessory.prototype.getTargetTemperature = function(callback){ - var that = this; - nest.fetchStatus(function (data) { - var device = data.shared[that.deviceId]; - var targetTemp = this.extractTargetTemperature(device); - callback(targetTemp); - }); - }; -NestThermostatAccessory.prototype.extractTemperatureUnits = function(device) { - var temperatureUnits = 0; - switch(device.temperature_scale) { +NestThermostatAccessory.prototype.getTemperatureUnits = function() { + switch(this.device.temperature_scale) { case "F": - this.log("Tempature unit for " + this.name + " is: " + "Fahrenheit"); - temperatureUnits = 1; - break; + return Characteristic.TemperatureDisplayUnits.FAHRENHEIT; case "C": - this.log("Tempature unit for " + this.name + " is: " + "Celsius"); - temperatureUnits = 0; - break; + return Characteristic.TemperatureDisplayUnits.CELSIUS; default: - temperatureUnits = 0; + return Characteristic.TemperatureDisplayUnits.CELSIUS; } - return temperatureUnits; -}; - -NestThermostatAccessory.prototype.isFahrenheitUnit = function(unit) { - return unit == 1; -}; - -NestThermostatAccessory.prototype.convertToDisplayUnit = function(value, displayUnit) { - return this.isFahrenheitUnit(displayUnit) ? nest.ctof(value) : value; -}; - -NestThermostatAccessory.prototype.convertToValueUnit = function(value, displayUnit) { - return this.isFahrenheitUnit(displayUnit) ? nest.ftoc(value) : value; -}; - -NestThermostatAccessory.prototype.extractAsDisplayUnit = function(value, device) { - var tempUnit = this.extractTemperatureUnits(device); - return this.convertToDisplayUnit(value, tempUnit); -}; - -NestThermostatAccessory.prototype.extractAsValueUnit = function(value, device) { - return this.convertToValueUnit(value, this.extractTemperatureUnits(device)); -}; - -NestThermostatAccessory.prototype.getTemperatureUnits = function(callback){ - var that = this; - nest.fetchStatus(function (data) { - var device = data.device[that.deviceId]; - var temperatureUnits = that.extractTemperatureUnits(device); - callback(temperatureUnits); - }); - }; - -NestThermostatAccessory.prototype.extractCurrentRelativeHumidity = function(device) { - var humidity = device.current_humidity; - this.log("Humidity for " + this.name + " is: " + humidity); - return humidity; }; NestThermostatAccessory.prototype.setTargetHeatingCooling = function(targetHeatingCooling, callback){ - var targetTemperatureType = 'off'; - switch(targetHeatingCooling) { - case 0: - targetTemperatureType = 'off'; - break; - case 1: - targetTemperatureType = 'heat'; - break; - case 2: - targetTemperatureType = 'cool'; - break; - case 3: - targetTemperatureType = 'range'; - break; - default: - targetTemperatureType = 'off'; - } + var targetTemperatureType = null; - this.log("Setting target heating cooling for " + this.name + " to: " + targetTemperatureType); - nest.setTargetTemperatureType(this.deviceId, targetTemperatureType); - - if (callback) { - callback(); + switch(targetHeatingCooling) { + case Characteristic.CurrentHeatingCoolingState.HEAT: + targetTemperatureType = 'heat'; + break; + case Characteristic.CurrentHeatingCoolingState.COOL: + targetTemperatureType = 'cool'; + break; + case Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL: + targetTemperatureType = 'range'; + break; + default: + targetTemperatureType = 'off'; + break; } + + this.log("Setting target heating cooling for " + this.name + " to: " + targetTemperatureType); + nest.setTargetTemperatureType(this.deviceId, targetTemperatureType); + + if (callback) callback(null, targetTemperatureType); }; NestThermostatAccessory.prototype.setTargetTemperature = function(targetTemperature, callback){ - this.log("Setting target temperature for " + this.name + " to: " + targetTemperature); - nest.setTemperature(this.deviceId, targetTemperature); - if (callback) { - callback(); + + switch (this.getTargetHeatingCooling()) { + case Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL: + // Choose closest target as single target + var high = this.currentData.target_temperature_high; + var low = this.currentData.target_temperature_low; + var cur = this.currentData.current_temperature; + var isHighTemp = Math.abs(high - cur) < Math.abs(cur - low); + if (isHighTemp) { + high = targetTemperature; + } else { + low = targetTemperature; + } + this.log("Setting " + (isHighTemp ? "high" : "low") + " target temperature for " + this.name + " to: " + targetTemperature); + nest.setTemperatureRange(this.deviceId, low, high); + break; + default: + this.log("Setting target temperature for " + this.name + " to: " + targetTemperature); + nest.setTemperature(this.deviceId, targetTemperature); + break; } + + if (callback) callback(null, targetTemperature); }; module.exports.accessory = NestThermostatAccessory; From e944d3ed2a5c01584f8f29057c6f873e4f1088da Mon Sep 17 00:00:00 2001 From: Kraig McConaghy Date: Tue, 27 Oct 2015 23:55:06 -0500 Subject: [PATCH 57/89] Fixed issue on Philips Hue where initial state was not being loaded (quick fix). --- platforms/PhilipsHue.js | 50 ++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 18df91a..9d3394c 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -187,6 +187,20 @@ PhilipsHueAccessory.prototype = { value = Math.round(value); return value; }, + extractValue: function(characteristic, status) { + switch(characteristic.toLowerCase()) { + case 'power': + return status.state.on ? 1 : 0; + case 'hue': + return this.hueToArcDegrees(status.state.hue); + case 'brightness': + return this.bitsToPercentage(status.state.bri); + case 'saturation': + return this.bitsToPercentage(status.state.sat); + default: + return null; + } + }, // Create and set a light state executeChange: function(characteristic, value, callback) { var state = lightState.create(); @@ -254,24 +268,14 @@ PhilipsHueAccessory.prototype = { } else { - switch(characteristic.toLowerCase()) { - case 'power': - callback(null, status.state.on ? 1 : 0); - break; - case 'hue': - callback(null, this.hueToArcDegrees(status.state.hue)); - break; - case 'brightness': - callback(null, this.bitsToPercentage(status.state.bri)); - break; - case 'saturation': - callback(null, this.bitsToPercentage(status.state.sat)); - break; - //default: - // this.log("Device " + that.device.name + " does not support reading characteristic " + characteristic); - // callback(Error("Device " + that.device.name + " does not support reading characteristic " + characteristic) ); + var newValue = this.extractValue(characteristic, status); + if (newValue != undefined) { + callback(null, newValue); + } else { + // this.log("Device " + that.device.name + " does not support reading characteristic " + characteristic); + // callback(Error("Device " + that.device.name + " does not support reading characteristic " + characteristic) ); } - + callback = null; //this.log("Get " + that.device.name + ", characteristic: " + characteristic + ", value: " + value + "."); @@ -295,24 +299,28 @@ PhilipsHueAccessory.prototype = { lightbulbService .getCharacteristic(Characteristic.On) .on('get', function(callback) { that.getState("power", callback);}) - .on('set', function(value, callback) { that.executeChange("power", value, callback);}); + .on('set', function(value, callback) { that.executeChange("power", value, callback);}) + .value = this.extractValue("power", this.device); lightbulbService .addCharacteristic(Characteristic.Brightness) .on('get', function(callback) { that.getState("brightness", callback);}) - .on('set', function(value, callback) { that.executeChange("brightness", value, callback);}); + .on('set', function(value, callback) { that.executeChange("brightness", value, callback);}) + .value = this.extractValue("brightness", this.device); // Handle the Hue/Hue Lux divergence if (this.device.state.hasOwnProperty('hue') && this.device.state.hasOwnProperty('sat')) { lightbulbService .addCharacteristic(Characteristic.Hue) .on('get', function(callback) { that.getState("hue", callback);}) - .on('set', function(value, callback) { that.executeChange("hue", value, callback);}); + .on('set', function(value, callback) { that.executeChange("hue", value, callback);}) + .value = this.extractValue("hue", this.device); lightbulbService .addCharacteristic(Characteristic.Saturation) .on('get', function(callback) { that.getState("saturation", callback);}) - .on('set', function(value, callback) { that.executeChange("saturation", value, callback);}); + .on('set', function(value, callback) { that.executeChange("saturation", value, callback);}) + .value = this.extractValue("saturation", this.device); } var informationService = new Service.AccessoryInformation(); From 2e6a487f80636e7c976d82d978916ec5a1b2573f Mon Sep 17 00:00:00 2001 From: stipus Date: Thu, 29 Oct 2015 12:16:05 +0100 Subject: [PATCH 58/89] SecuritySystem,Window, WindowCovering, obstruction --- platforms/HomeSeer.js | 151 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 147 insertions(+), 4 deletions(-) diff --git a/platforms/HomeSeer.js b/platforms/HomeSeer.js index 7c899f8..b4fa36d 100644 --- a/platforms/HomeSeer.js +++ b/platforms/HomeSeer.js @@ -29,12 +29,22 @@ // - Added offEventGroup && offEventName to events (turn on launches one HS event. turn off can launch another HS event) // - Added GarageDoorOpener support // - Added Lock support +// V0.10 - 2015/10/29 +// - Added Security System support +// - Added Window support +// - Added Window Covering support +// - Added obstruction support to doors, windows, and windowCoverings // // // Remember to add platform to config.json. // // You can get HomeSeer Device References by clicking a HomeSeer device name, then -// choosing the Advanced Tab. +// choosing the Advanced Tab. +// +// The uuid_base parameter is valid for all events and accessories. +// If you set this parameter to some unique identifier, the HomeKit accessory ID will be based on uuid_base instead of the accessory name. +// It is then easier to change the accessory name without messing the HomeKit database. +// // // Example: // "platforms": [ @@ -148,10 +158,31 @@ // "lockValue":1 // Required - HomeSeer device control value to lock // }, // { +// "ref":230, // Required - HomeSeer Device Reference of a Security System +// "type":"SecuritySystem", // Required for a security system +// "name":"Home alarm", // Optional - HomeSeer device name is the default +// "armedStayValues":[0], // Optional - List of the HomeSeer device values for a HomeKit security state=ARMED-STAY +// "armedAwayValues":[1], // Optional - List of the HomeSeer device values for a HomeKit security state=ARMED-AWAY +// "armedNightValues":[2], // Optional - List of the HomeSeer device values for a HomeKit security state=ARMED-NIGHT +// "disarmedValues":[3], // Optional - List of the HomeSeer device values for a HomeKit security state=DISARMED +// "alarmValues":[4], // Optional - List of the HomeSeer device values for a HomeKit security state=ALARM +// "armStayValue":0, // Required - HomeSeer device control value to arm in stay mode. If you don't have this mode, select any value that arms your system +// "armAwayValue":1, // Required - HomeSeer device control value to arm in away mode. If you don't have this mode, select any value that arms your system +// "armNightValue":2, // Required - HomeSeer device control value to arm in night mode. If you don't have this mode, select any value that arms your system +// "disarmValue":3 // Required - HomeSeer device control value to disarm security system +// }, +// { // "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 +// }, +// { +// "ref":240, // Required - HomeSeer Device Reference for a door - HomeSeer values must go from 0 (closed) to 100 (open) +// "type":"Door", // Required for a Door +// "name":"Main door", // Optional - HomeSeer device name is the default +// "obstructionRef":241, // Optional - HomeSeer device reference for your door obstruction state (can be the same as ref) +// "obstructionValues":[1] // Optional - List of the HomeSeer device values for a HomeKit obstruction state=OBSTRUCTION // } // ] // } @@ -177,8 +208,10 @@ // - Battery (batteryThreshold option) // - GarageDoorOpener (state, control, obstruction, lock options) // - Lock (unsecured, secured, jammed options) -// - Door - +// - SecuritySystem (arm, disarm options) +// - Door (obstruction option) +// - Window (obstruction option) +// - WindowCovering (obstruction option) var Service = require("hap-nodejs").Service; var Characteristic = require("hap-nodejs").Characteristic; @@ -672,7 +705,7 @@ HomeSeerAccessory.prototype = { }, setLockTargetState: function(state, callback) { - this.log("Setting target lock state state to %s", state); + this.log("Setting target lock state to %s", state); var ref = this.config.lockRef; var value = 0; @@ -694,6 +727,62 @@ HomeSeerAccessory.prototype = { }.bind(this)); }, + getSecuritySystemCurrentState: function(callback) { + var url = this.access_url + "request=getstatus&ref=" + this.ref; + + httpRequest(url, 'GET', function(error, response, body) { + if (error) { + this.log('HomeSeer get security system 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 security system current state function succeeded: value=' + value ); + if( this.config.armedStayValues && this.config.armedStayValues.indexOf(value) != -1 ) + callback( null, 0 ); + else if( this.config.armedAwayValues && this.config.armedAwayValues.indexOf(value) != -1 ) + callback( null, 1 ); + else if( this.config.armedNightValues && this.config.armedNightValues.indexOf(value) != -1 ) + callback( null, 2 ); + else if( this.config.disarmedValues && this.config.disarmedValues.indexOf(value) != -1 ) + callback( null, 3 ); + else if( this.config.alarmValues && this.config.alarmValues.indexOf(value) != -1 ) + callback( null, 4 ); + else + callback( null, 0 ); + } + }.bind(this)); + }, + + setSecuritySystemTargetState: function(state, callback) { + this.log("Setting security system state to %s", state); + + var value = 0; + if( state == 0 && this.config.armStayValue ) + value = this.config.armStayValue; + else if( state == 1 && this.config.armAwayValue ) + value = this.config.armAwayValue; + else if( state == 2 && this.config.armNightValue ) + value = this.config.armNightValue; + else if( state == 3 && this.config.disarmValue ) + value = this.config.disarmValue; + + var url = this.access_url + "request=controldevicebyvalue&ref=" + this.ref + "&value=" + value; + httpRequest(url, 'GET', function(error, response, body) { + if (error) { + this.log('HomeSeer set target security system state function failed: %s', error.message); + callback(error); + } + else { + this.log('HomeSeer set target security system state function succeeded!'); + callback(); + } + }.bind(this)); + }, + + getPositionState: function(callback) { callback( null, 2 ); // Temporarily return STOPPED. TODO: full door support }, @@ -897,9 +986,52 @@ HomeSeerAccessory.prototype = { doorService .getCharacteristic(Characteristic.PositionState) .on('get', this.getPositionState.bind(this)); + if( this.config.obstructionRef ) { + doorService + .addCharacteristic(new Characteristic.ObstructionDetected()) + .on('get', this.getObstructionDetected.bind(this)); + } services.push( doorService ); break; } + case "Window": { + var windowService = new Service.Window(); + windowService + .getCharacteristic(Characteristic.CurrentPosition) + .on('get', this.getValue.bind(this)); + windowService + .getCharacteristic(Characteristic.TargetPosition) + .on('set', this.setValue.bind(this)); + windowService + .getCharacteristic(Characteristic.PositionState) + .on('get', this.getPositionState.bind(this)); + if( this.config.obstructionRef ) { + windowService + .addCharacteristic(new Characteristic.ObstructionDetected()) + .on('get', this.getObstructionDetected.bind(this)); + } + services.push( windowService ); + break; + } + case "WindowCovering": { + var windowCoveringService = new Service.WindowCovering(); + windowCoveringService + .getCharacteristic(Characteristic.CurrentPosition) + .on('get', this.getValue.bind(this)); + windowCoveringService + .getCharacteristic(Characteristic.TargetPosition) + .on('set', this.setValue.bind(this)); + windowCoveringService + .getCharacteristic(Characteristic.PositionState) + .on('get', this.getPositionState.bind(this)); + services.push( windowCoveringService ); + if( this.config.obstructionRef ) { + windowCoveringService + .addCharacteristic(new Characteristic.ObstructionDetected()) + .on('get', this.getObstructionDetected.bind(this)); + } + break; + } case "Battery": { this.config.batteryRef = this.ref; var batteryService = new Service.BatteryService(); @@ -974,6 +1106,17 @@ HomeSeerAccessory.prototype = { services.push( lockService ); break; } + case "SecuritySystem": { + var securitySystemService = new Service.SecuritySystem(); + securitySystemService + .getCharacteristic(Characteristic.SecuritySystemCurrentState) + .on('get', this.getSecuritySystemCurrentState.bind(this)); + securitySystemService + .getCharacteristic(Characteristic.SecuritySystemTargetState) + .on('set', this.setSecuritySystemTargetState.bind(this)); + services.push( securitySystemService ); + break; + } default:{ var lightbulbService = new Service.Lightbulb(); From 98c61bc72a8191f0fb850f282d03c9c1aa45f06c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20K=C3=B6nig?= Date: Thu, 29 Oct 2015 19:06:47 +0100 Subject: [PATCH 59/89] reintegrated Homematic Platform fork from https://github.com/thkl/homebridge/tree/xmlrpc --- package.json | 1 + platforms/HomeMatic.js | 406 ++++++++++++++++++++++ platforms/HomematicChannel.js | 631 ++++++++++++++++++++++++++++++++++ 3 files changed, 1038 insertions(+) create mode 100644 platforms/HomeMatic.js create mode 100644 platforms/HomematicChannel.js diff --git a/package.json b/package.json index eb9e811..e1de444 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "hap-nodejs": "^0.0.3", "harmonyhubjs-client": "^1.1.6", "harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git", + "homematic-xmlrpc": "git+https://github.com/hobbyquaker/homematic-xmlrpc", "isy-js": "", "komponist": "0.1.0", "lifx": "git+https://github.com/magicmonkey/lifxjs.git", diff --git a/platforms/HomeMatic.js b/platforms/HomeMatic.js new file mode 100644 index 0000000..b22a0f4 --- /dev/null +++ b/platforms/HomeMatic.js @@ -0,0 +1,406 @@ +// +// Homematic Platform Shim for HomeBridge +// +// V0.1 - 2015/10/29 +// - initial version +// - reintegrated Homematic Platform fork from https://github.com/thkl/homebridge/tree/xmlrpc + + +var types = require("hap-nodejs/accessories/types.js"); +var xmlrpc = require('homematic-xmlrpc') + +var request = require("request"); +var http = require("http"); +var path = require("path"); + +var HomeMaticGenericChannel = require(path.resolve(__dirname, 'HomematicChannel.js')); + + + +function RegaRequest(log,ccuip) { + this.log = log; + this.ccuIP = ccuip; +} + +RegaRequest.prototype = { + + script: function (script, callback) { + + var post_options = { + host: this.ccuIP, + port: '80', + path: '/tclrega.exe', + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': script.length + } + }; + + var post_req = http.request(post_options, function(res) { + var data = ""; + res.setEncoding('binary'); + res.on('data', function (chunk) { + data += chunk.toString(); + }); + res.on('end', function () { + var pos = data.lastIndexOf(""); + var response = (data.substring(0, pos)); + callback(response); + }); + }); + + post_req.on('error', function(e) { + callback("{}"); + }); + + post_req.write(script); + post_req.end(); + + + }, + + getValue: function(channel,datapoint,callback) { + var that = this; + + var script = "var d = dom.GetObject(\""+channel+"."+datapoint+"\");if (d){Write(d.State());}"; + //that.log("Rega Request " + script); + var regarequest = this.script(script, function(data) { + that.log("Rega Response" + data); + if (data!=undefined) { + callback(parseFloat(data)); + } + } + ); + }, + + setValue: function(channel,datapoint,value) { + var that = this; + + var script = "var d = dom.GetObject(\""+channel+"."+datapoint+"\");if (d){d.State(\""+value+"\");}"; + //that.log("Rega Request " + script); + var regarequest = this.script(script, function(data) { + }); + } + +} + +function HomematicRPC(log,ccuip,platform) { + this.log = log; + this.ccuip = ccuip; + this.platform = platform; + this.server; + this.client; + this.stopping = false; + this.localIP; +} + +HomematicRPC.prototype= { + + + init:function() { + var that = this; + + var ip = this.getIPAddress(); + if (ip=="0.0.0.0") { + that.log("Can not fetch IP"); + return; + } + + this.localIP = ip; + this.log("Local IP: "+this.localIP) + + this.server = xmlrpc.createServer({ host: this.localIP , port: 9090 }) + + this.server.on('NotFound', function(method, params) { + that.log('Method ' + method + ' does not exist'); + }); + + this.server.on('system.listMethods', function (err, params, callback) { + that.log('Method call params for \'system.listMethods\': ' + params) + callback(null,['system.listMethods', 'system.multicall']); + }); + + + this.server.on('system.multicall', function (err, params, callback) { + params.map(function(events) { + try { + events.map(function(event){ + if ((event["methodName"]=="event") && (event['params'] != undefined)) { + var params = event['params']; + var channel = "BidCos-RF." + params[1]; + var datapoint = params[2]; + var value = params[3]; + that.platform.foundAccessories.map(function(accessory){ + if (accessory.adress == channel) { + accessory.event(datapoint,value); + } + }); + } + }); + } catch(err) {} + }); + callback(null); + }); + + this.log('XML-RPC server listening on port 9090') + this.connect(); + + + process.on('SIGINT', function () { + if (that.stopping) { + return; + } + that.stopping = true; + that.stop(); + }); + + process.on('SIGTERM', function () { + if (that.stopping) { + return; + } + that.stopping = true; + that.stop(); + }); + + }, + + getIPAddress: function() { + var interfaces = require('os').networkInterfaces(); + for (var devName in interfaces) { + var iface = interfaces[devName]; + for (var i = 0; i < iface.length; i++) { + var alias = iface[i]; + if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) + return alias.address; + } + } + return '0.0.0.0'; + }, + + getValue:function(channel,datapoint,callback) { + + var that = this; + if (this.client == undefined) { + that.log("Returning cause client is invalid"); + return; + } + if (channel.indexOf("BidCos-RF.")>-1) { + channel = channel.substr(10); + this.log("Calling rpc getValue"); + this.client.methodCall('getValue', [channel,datapoint], function (error, value) { + callback(value); + }); + return; + } + }, + + setValue:function(channel,datapoint,value) { + + var that = this; + + if (this.client == undefined) return; + + if (channel.indexOf("BidCos-RF.")>-1) { + channel = channel.substr(10); + } + + this.client.methodCall('setValue', [channel,datapoint,value], function (error, value) { + + }); + }, + + connect:function(){ + var that = this; + this.log('Creating Local HTTP Client for CCU RPC Events'); + this.client = xmlrpc.createClient({ host: this.ccuip, port: 2001, path: '/'}); + this.log('CCU RPC Init Call on port 2001'); + this.client.methodCall('init', ['http://'+this.localIP+':9090','homebridge'], function (error, value) { + that.log('CCU Response ....') + }); + }, + + + stop:function() { + this.log("Removing Event Server"); + this.client.methodCall('init', ['http://'+this.localIP+':9090'], function (error, value) { + + }); + setTimeout(process.exit(0), 1000); + } + +} + + +function HomeMaticPlatform(log, config) { + this.log = log; + this.ccuIP = config["ccu_ip"]; + this.filter_device = config["filter_device"]; + this.filter_channel = config["filter_channel"]; + this.outlets = config["outlets"]; + + this.sendQueue = []; + this.timer = 0; + + this.foundAccessories = []; + this.adressesToQuery = []; + + this.xmlrpc = new HomematicRPC(this.log,this.ccuIP,this); + this.xmlrpc.init(); +} + +HomeMaticPlatform.prototype = { + + + + accessories: function(callback) { + this.log("Fetching Homematic devices..."); + var that = this; + that.foundAccessories = []; + + var script = "string sDeviceId;string sChannelId;boolean df = true;Write(\'{\"devices\":[\');foreach(sDeviceId, root.Devices().EnumIDs()){object oDevice = dom.GetObject(sDeviceId);if(oDevice){var oInterface = dom.GetObject(oDevice.Interface());if(df) {df = false;} else { Write(\',\');}Write(\'{\');Write(\'\"id\": \"\' # sDeviceId # \'\",\');Write(\'\"name\": \"\' # oDevice.Name() # \'\",\');Write(\'\"address\": \"\' # oDevice.Address() # \'\",\');Write(\'\"channels\": [\');boolean bcf = true;foreach(sChannelId, oDevice.Channels().EnumIDs()){object oChannel = dom.GetObject(sChannelId);if(bcf) {bcf = false;} else {Write(\',\');}Write(\'{\');Write(\'\"cId\": \' # sChannelId # \',\');Write(\'\"name\": \"\' # oChannel.Name() # \'\",\');if(oInterface){Write(\'\"address\": \"\' # oInterface.Name() #\'.'\ # oChannel.Address() # \'\",\');}Write(\'\"type\": \"\' # oChannel.HssType() # \'\"\');Write(\'}\');}Write(\']}\');}}Write(\']}\');"; + + var regarequest = new RegaRequest(this.log,this.ccuIP).script(script, function(data) { + var json = JSON.parse(data); + if (json['devices'] != undefined) { + json['devices'].map(function(device) { + var isFiltered = false; + + if ((that.filter_device != undefined) && (that.filter_device.indexOf(device.address) > -1)) { + isFiltered = true; + } else { + isFiltered = false; + } + // that.log('device address:', device.address); + + if ((device['channels'] != undefined) && (!isFiltered)) { + + device['channels'].map(function(ch) { + var isChannelFiltered = false; + + if ((that.filter_channel != undefined) && (that.filter_channel.indexOf(ch.address) > -1)) { + isChannelFiltered = true; + } else { + isChannelFiltered = false; + } + // that.log('name', ch.name, ' -> address:', ch.address); + if ((ch.address != undefined) && (!isChannelFiltered)) { + + if ((ch.type=="SWITCH") || (ch.type=="BLIND") || (ch.type=="SHUTTER_CONTACT") + || (ch.type=="DIMMER") || (ch.type=="CLIMATECONTROL_RT_TRANSCEIVER") + || (ch.type=="MOTION_DETECTOR") || (ch.type=="KEYMATIC") + ) { + // Switch found + // Check if marked as Outlet + var special = (that.outlets.indexOf(ch.address) > -1) ? 'OUTLET' : undefined; + accessory = new HomeMaticGenericChannel(that.log, that, ch.id , ch.name , ch.type , ch.address, special); + that.foundAccessories.push(accessory); + } + + + } else { + that.log(device.name + " has no address"); + } + + }); + } else { + that.log(device.name + " has no channels or is filtered"); + } + + }); + +/* + accessory = new HomeMaticGenericChannel(that.log, that, "1234" , "DummyKM" , "KEYMATIC" , "1234"); + that.foundAccessories.push(accessory); + + accessory = new HomeMaticGenericChannel(that.log, that, "5678" , "DummyBLIND" , "BLIND" , "5678"); + that.foundAccessories.push(accessory); + + */ + callback(that.foundAccessories); + } else { + callback(that.foundAccessories); + } + }); + + }, + + setValue:function(channel,datapoint,value) { + if (channel.indexOf("BidCos-RF.")>-1) { + this.xmlrpc.setValue(channel,datapoint,value); + return; + } + + if (channel.indexOf("VirtualDevices.")>-1) { + var rega = new RegaRequest(this.log,this.ccuIP); + rega.setValue(channel,datapoint,value); + return; + } + + }, + + + getValue:function(channel,datapoint,callback) { + + if (channel.indexOf("BidCos-RF.")>-1) { + this.xmlrpc.getValue(channel,datapoint,callback); + return; + } + + if (channel.indexOf("VirtualDevices.")>-1) { + var rega = new RegaRequest(this.log,this.ccuIP); + rega.getValue(channel,datapoint,callback); + return; + } + + }, + + prepareRequest: function(accessory,script) { + var that = this; + this.sendQueue.push(script); + that.delayed(100); + }, + + sendPreparedRequests: function() { + var that = this; + var script = "var d;"; + this.sendQueue.map(function(command) { + script = script + command; + }); + this.sendQueue = []; + //this.log("RegaSend: " + script); + var regarequest = new RegaRequest(this.log,this.ccuIP).script(script, function(data) { + }); + }, + + sendRequest: function(accessory,script,callback) { + var that = this; + var regarequest = new RegaRequest(this.log,this.ccuIP).script(script, function(data) { + if (data != undefined) { + try { + var json = JSON.parse(data); + callback(json); + } catch (err) { + callback(undefined); + } + return; + } + }); + }, + + delayed: function(delay) { + var timer = this.delayed[delay]; + if( timer ) { + this.log("removing old command"); + clearTimeout( timer ); + } + + var that = this; + this.delayed[delay] = setTimeout( function(){clearTimeout(that.delayed[delay]);that.sendPreparedRequests()}, delay?delay:100); + this.log("New Timer was set"); + } +} + + + +module.exports.platform = HomeMaticPlatform; \ No newline at end of file diff --git a/platforms/HomematicChannel.js b/platforms/HomematicChannel.js new file mode 100644 index 0000000..1ef22a5 --- /dev/null +++ b/platforms/HomematicChannel.js @@ -0,0 +1,631 @@ +var types = require("hap-nodejs/accessories/types.js"); + + +function HomeMaticGenericChannel(log,platform, id ,name, type ,adress,special) { + this.name = name; + this.type = type; + this.adress = adress; + this.log = log; + this.platform = platform; + this.state = []; + this.eventupdate = false; + this.special = special; + this.currentStateCharacteristic = []; + this.reverseDP = []; +} + + + + +HomeMaticGenericChannel.prototype = { + + + // Return current States + query: function(dp,callback) { + var that = this; + + if (this.state[dp] != undefined) { + callback(this.state[dp]); + } else { +// that.log("No cached Value found start fetching and send temp 0 back"); + this.remoteGetValue(dp); + callback(0); + } + + }, + + dpvalue:function(dp,fallback) { + if (this.state[dp] != undefined) { + return(this.state[dp]); + } else { + return fallback; + } + }, + + remoteGetValue:function(dp) { + var that = this; + that.platform.getValue(that.adress,dp,function(newValue) { + that.log("Remote Value Response for " + that.adress + "." + dp + "->" + newValue); + that.eventupdate = true; + that.cache(dp,newValue); + that.eventupdate = false; + }); + }, + + + event:function(dp,newValue) { + + if (dp=="LEVEL") { + newValue = newValue*100; + } + + this.eventupdate = true; + this.cache(dp,newValue); + this.eventupdate = false; + }, + + reverse:function(value) { + if (value=="true") return "false"; + if (value=="false") return "true"; + if (value==0) return 1; + if (value==1) return 0; + if (value=="0") return "1"; + if (value=="1") return "0"; + return value; + }, + + cache:function(dp,value) { + var that = this; + + if ((that.reverseDP[dp]!=undefined) && (that.reverseDP[dp]==true)) { + value = that.reverse(value); + } + + if (that.currentStateCharacteristic[dp]!=undefined) { + that.currentStateCharacteristic[dp].updateValue(value, null); + } + this.state[dp] = value; + }, + + + delayed: function(mode, dp,value,delay) { + + if (this.eventupdate==true) { + return; + } + + var timer = this.delayed[delay]; + if( timer ) { + clearTimeout( timer ); + } + + this.log(this.name + " delaying command "+mode + " " + dp +" with value " + value); + var that = this; + this.delayed[delay] = setTimeout( function(){clearTimeout(that.delayed[delay]);that.command(mode,dp,value)}, delay?delay:100 ); + }, + + command: function(mode,dp,value,callback) { + + if (this.eventupdate==true) { + return; + } + var that = this; + + if (mode == "set") { + //this.log("Send " + value + " to Datapoint " + dp + " at " + that.adress); + that.platform.setValue(that.adress,dp,value); + } + }, + + informationCharacteristics: function() { + return [ + { + 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: "EQ-3", + supportEvents: false, + supportBonjour: false, + manfDescription: "Manufacturer", + designedMaxLength: 255 + },{ + cType: types.MODEL_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.type, + supportEvents: false, + supportBonjour: false, + manfDescription: "Model", + designedMaxLength: 255 + },{ + cType: types.SERIAL_NUMBER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.adress , + 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) { + + cTypes = [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name, + supportEvents: true, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + }] + + + if (this.type=="SWITCH") { + cTypes.push({ + cType: types.POWER_STATE_CTYPE, + onUpdate: function(value) { + that.command("set","STATE" , (value==1)?true:false) + }, + + onRead: function(callback) { + that.query("STATE",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["STATE"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("STATE"); + }, + + perms: ["pw","pr","ev"], + format: "bool", + initialValue: that.dpvalue("STATE",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Change the power state", + designedMaxLength: 1 + }); + + if (this.special=="OUTLET") { + cTypes.push({ + cType: types.OUTLET_IN_USE_CTYPE, + + onRead: function(callback) { + callback(true); + }, + perms: ["pr","ev"], + format: "bool", + initialValue: true, + supportEvents: false, + supportBonjour: false, + manfDescription: "Is Outlet in Use", + designedMaxLength: 1 + }) + } + } + + + if (this.type=="KEYMATIC") { + cTypes.push( + { + cType: types.CURRENT_LOCK_MECHANISM_STATE_CTYPE, + + onRead: function(callback) { + that.query("STATE",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["STATE"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("STATE"); + }, + + perms: ["pr","ev"], + format: "bool", + initialValue: that.dpvalue("STATE",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current State of your Lock", + designedMaxLength: 1 + }, + { + cType: types.TARGET_LOCK_MECHANISM_STATE_CTYPE, + + onUpdate: function(value) { + that.command("set","STATE",(value==1)?"true":"false") + }, + + onRead: function(callback) { + that.query("STATE",callback); + }, + + onRegister: function(characteristic) { + that.reverseDP["STATE"] = true; + that.currentStateCharacteristic["STATE"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("STATE"); + }, + + + perms: ["pw","pr","ev"], + format: "bool", + initialValue: that.dpvalue("STATE",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Target State of your Lock", + designedMaxLength: 1 + } + + , + { + cType: types.TARGET_DOORSTATE_CTYPE, + + onUpdate: function(value) { + that.command("set","OPEN" , "true") + }, + + onRead: function(callback) { + callback(1); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["OPEN"] = characteristic; + characteristic.eventEnabled = true; + }, + + perms: ["pw","pr","ev"], + format: "bool", + initialValue: 1, + supportEvents: false, + supportBonjour: false, + manfDescription: "Open the Lock", + designedMaxLength: 1 + } + ); + + + } + + + + if (this.type=="DIMMER") { + cTypes.push({ + cType: types.POWER_STATE_CTYPE, + onUpdate: function(value) { + that.command("set","LEVEL" , (value==true) ? "1" : "0") + }, + + onRead: function(callback) { + that.query("LEVEL",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["LEVEL"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("LEVEL"); + }, + + perms: ["pw","pr","ev"], + format: "bool", + initialValue: (that.dpvalue("LEVEL")>0,0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Change the power state", + designedMaxLength: 1 + }, + { + cType: types.BRIGHTNESS_CTYPE, + onUpdate: function(value) { + that.delayed("set","LEVEL" , String(value/100),100); + }, + + onRead: function(callback) { + that.query("LEVEL",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["LEVEL"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("LEVEL"); + }, + + + perms: ["pw","pr","ev"], + format: "int", + initialValue: that.dpvalue("LEVEL",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Adjust Brightness of Light", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }); + } + + if (this.type=="BLIND") { + cTypes.push( + { + cType: types.WINDOW_COVERING_CURRENT_POSITION_CTYPE, + + onRead: function(callback) { + that.query("LEVEL",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["LEVEL"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("LEVEL"); + }, + + perms: ["pr","ev"], + format: "int", + initialValue: that.dpvalue("LEVEL",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Blind Position", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }, + + { + cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, + + onUpdate: function(value) { + that.delayed("set","LEVEL" , String(value/100),100); + }, + + + onRead: function(callback) { + that.query("LEVEL",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["LEVEL"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("LEVEL"); + }, + + perms: ["pw","pr","ev"], + format: "int", + initialValue: that.dpvalue("LEVEL",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Target Blind Position", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }, + { + cType: types.WINDOW_COVERING_OPERATION_STATE_CTYPE, + + onRead: function(callback) { + that.query("DIRECTION",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["DIRECTION"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("DIRECTION"); + }, + + perms: ["pr","ev"], + format: "int", + initialValue: that.dpvalue("DIRECTION",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Operating State ", + designedMinValue: 0, + designedMaxValue: 2, + designedMinStep: 1 + } + + ); + } + + if (this.type=="SHUTTER_CONTACT") { + cTypes.push( + { + cType: types.CONTACT_SENSOR_STATE_CTYPE, + + onRead: function(callback) { + that.query("STATE",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["STATE"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("STATE"); + }, + + perms: ["pr","ev"], + format: "bool", + initialValue: that.dpvalue("STATE",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current State" + }); + } + + if (this.type=="MOTION_DETECTOR") { + cTypes.push( + { + cType: types.MOTION_DETECTED_CTYPE, + + onRead: function(callback) { + that.query("MOTION",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["MOTION"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("MOTION"); + }, + + perms: ["pr","ev"], + format: "bool", + initialValue: that.dpvalue("MOTION",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Motion State" + }); + } + + if (this.type=="CLIMATECONTROL_RT_TRANSCEIVER") { + + cTypes.push({ + cType: types.NAME_CTYPE,onUpdate: null,perms: ["pr"],format: "string", + initialValue: this.name,supportEvents: true,supportBonjour: false,manfDescription: "Name of service",designedMaxLength: 255 + }, + + { + cType: types.CURRENTHEATINGCOOLING_CTYPE,onUpdate: null, + perms: ["pr"],format: "int",initialValue: 1,supportEvents: false, + supportBonjour: false,manfDescription: "Current Mode",designedMaxLength: 1,designedMinValue: 1,designedMaxValue: 1,designedMinStep: 1 + }, + + { + cType: types.TARGETHEATINGCOOLING_CTYPE,onUpdate: null,perms: ["pw","pr"], + format: "int",initialValue: 1,supportEvents: false,supportBonjour: false,manfDescription: "Target Mode", + designedMinValue: 1,designedMaxValue: 1,designedMinStep: 1 + }, + + { + cType: types.CURRENT_TEMPERATURE_CTYPE, + onUpdate: null, + + onRead: function(callback) { + that.query("ACTUAL_TEMPERATURE",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["ACTUAL_TEMPERATURE"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("ACTUAL_TEMPERATURE"); + }, + perms: ["pw","pr","ev"], perms: ["pr"],format: "double", + initialValue: that.dpvalue("ACTUAL_TEMPERATURE",20), + supportEvents: false,supportBonjour: false,manfDescription: "Current Temperature",unit: "celsius" + }, + + { + cType: types.TARGET_TEMPERATURE_CTYPE, + onUpdate: function(value) { + that.delayed("set", "SET_TEMPERATURE", value,500); + }, + onRead: function(callback) { + that.query("SET_TEMPERATURE",callback); + + }, + onRegister: function(characteristic) { + that.currentStateCharacteristic["SET_TEMPERATURE"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("SET_TEMPERATURE"); + }, + perms: ["pw","pr","ev"],format: "double", + initialValue: that.dpvalue("SET_TEMPERATURE",16), + supportEvents: false,supportBonjour: false, manfDescription: "Target Temperature", + designedMinValue: 16,designedMaxValue: 38,designedMinStep: 1,unit: "celsius" + }, + + { + cType: types.TEMPERATURE_UNITS_CTYPE,onRead: null, + perms: ["pr"],format: "int",initialValue: 0,supportEvents: false, + supportBonjour: false,manfDescription: "Current Temperature Unit",unit: "celsius" + } + + ); + } + + + return cTypes + }, + + sType: function() { + + if (this.type=="SWITCH") { + + if (this.special=="OUTLET") { + return types.OUTLET_STYPE; + } else { + return types.LIGHTBULB_STYPE; + } + } + + if (this.type=="DIMMER") { + return types.LIGHTBULB_STYPE; + } + + if (this.type=="BLIND") { + return types.WINDOW_COVERING_STYPE; + } + + if (this.type=="CLIMATECONTROL_RT_TRANSCEIVER") { + return types.THERMOSTAT_STYPE; + } + + if (this.type=="SHUTTER_CONTACT") { + return types.CONTACT_SENSOR_STYPE; + } + + if (this.type=="MOTION_DETECTOR") { + return types.MOTION_SENSOR_STYPE + } + + + if (this.type=="KEYMATIC") { + return types.LOCK_MECHANISM_STYPE + } + + + + }, + + getServices: function() { + var that = this; + var services = [{ + sType: types.ACCESSORY_INFORMATION_STYPE, + characteristics: this.informationCharacteristics(), + }, + { + sType: this.sType(), + characteristics: this.controlCharacteristics(that) + }]; + this.log("Loaded services for " + this.name) + return services; + } +}; + + +module.exports = HomeMaticGenericChannel; \ No newline at end of file From cbc34897f947037aa4247a0e42ad34dbd9d08ae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20K=C3=B6nig?= Date: Thu, 29 Oct 2015 19:15:35 +0100 Subject: [PATCH 60/89] cleanup formatting --- platforms/HomeMatic.js | 584 +++++++++++----------- platforms/HomematicChannel.js | 912 ++++++++++++++++++---------------- 2 files changed, 766 insertions(+), 730 deletions(-) diff --git a/platforms/HomeMatic.js b/platforms/HomeMatic.js index b22a0f4..1b99729 100644 --- a/platforms/HomeMatic.js +++ b/platforms/HomeMatic.js @@ -17,345 +17,347 @@ var HomeMaticGenericChannel = require(path.resolve(__dirname, 'HomematicChannel. -function RegaRequest(log,ccuip) { - this.log = log; - this.ccuIP = ccuip; +function RegaRequest(log, ccuip) { + this.log = log; + this.ccuIP = ccuip; } RegaRequest.prototype = { - script: function (script, callback) { + script: function(script, callback) { - var post_options = { - host: this.ccuIP, - port: '80', - path: '/tclrega.exe', - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Content-Length': script.length - } - }; + var post_options = { + host: this.ccuIP, + port: '80', + path: '/tclrega.exe', + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': script.length + } + }; - var post_req = http.request(post_options, function(res) { - var data = ""; - res.setEncoding('binary'); - res.on('data', function (chunk) { - data += chunk.toString(); - }); - res.on('end', function () { - var pos = data.lastIndexOf(""); - var response = (data.substring(0, pos)); - callback(response); - }); - }); + var post_req = http.request(post_options, function(res) { + var data = ""; + res.setEncoding('binary'); + res.on('data', function(chunk) { + data += chunk.toString(); + }); + res.on('end', function() { + var pos = data.lastIndexOf(""); + var response = (data.substring(0, pos)); + callback(response); + }); + }); - post_req.on('error', function(e) { - callback("{}"); - }); + post_req.on('error', function(e) { + callback("{}"); + }); - post_req.write(script); - post_req.end(); + post_req.write(script); + post_req.end(); - }, - - getValue: function(channel,datapoint,callback) { - var that = this; - - var script = "var d = dom.GetObject(\""+channel+"."+datapoint+"\");if (d){Write(d.State());}"; - //that.log("Rega Request " + script); - var regarequest = this.script(script, function(data) { - that.log("Rega Response" + data); - if (data!=undefined) { - callback(parseFloat(data)); - } - } - ); }, - - setValue: function(channel,datapoint,value) { - var that = this; - - var script = "var d = dom.GetObject(\""+channel+"."+datapoint+"\");if (d){d.State(\""+value+"\");}"; - //that.log("Rega Request " + script); - var regarequest = this.script(script, function(data) { - }); + + getValue: function(channel, datapoint, callback) { + var that = this; + + var script = "var d = dom.GetObject(\"" + channel + "." + datapoint + "\");if (d){Write(d.State());}"; + //that.log("Rega Request " + script); + var regarequest = this.script(script, function(data) { + that.log("Rega Response" + data); + if (data != undefined) { + callback(parseFloat(data)); + } + }); + }, + + setValue: function(channel, datapoint, value) { + var that = this; + + var script = "var d = dom.GetObject(\"" + channel + "." + datapoint + "\");if (d){d.State(\"" + value + "\");}"; + //that.log("Rega Request " + script); + var regarequest = this.script(script, function(data) {}); } } -function HomematicRPC(log,ccuip,platform) { - this.log = log; - this.ccuip = ccuip; - this.platform = platform; - this.server; - this.client; - this.stopping = false; - this.localIP; +function HomematicRPC(log, ccuip, platform) { + this.log = log; + this.ccuip = ccuip; + this.platform = platform; + this.server; + this.client; + this.stopping = false; + this.localIP; } -HomematicRPC.prototype= { +HomematicRPC.prototype = { - init:function() { - var that = this; - - var ip = this.getIPAddress(); - if (ip=="0.0.0.0") { - that.log("Can not fetch IP"); - return; - } - - this.localIP = ip; - this.log("Local IP: "+this.localIP) - - this.server = xmlrpc.createServer({ host: this.localIP , port: 9090 }) + init: function() { + var that = this; - this.server.on('NotFound', function(method, params) { - that.log('Method ' + method + ' does not exist'); - }); - - this.server.on('system.listMethods', function (err, params, callback) { - that.log('Method call params for \'system.listMethods\': ' + params) - callback(null,['system.listMethods', 'system.multicall']); - }); + var ip = this.getIPAddress(); + if (ip == "0.0.0.0") { + that.log("Can not fetch IP"); + return; + } - - this.server.on('system.multicall', function (err, params, callback) { - params.map(function(events) { - try { - events.map(function(event){ - if ((event["methodName"]=="event") && (event['params'] != undefined)) { - var params = event['params']; - var channel = "BidCos-RF." + params[1]; - var datapoint = params[2]; - var value = params[3]; - that.platform.foundAccessories.map(function(accessory){ - if (accessory.adress == channel) { - accessory.event(datapoint,value); - } - }); - } - }); - } catch(err) {} - }); - callback(null); - }); - - this.log('XML-RPC server listening on port 9090') + this.localIP = ip; + this.log("Local IP: " + this.localIP) + + this.server = xmlrpc.createServer({ + host: this.localIP, + port: 9090 + }) + + this.server.on('NotFound', function(method, params) { + that.log('Method ' + method + ' does not exist'); + }); + + this.server.on('system.listMethods', function(err, params, callback) { + that.log('Method call params for \'system.listMethods\': ' + params) + callback(null, ['system.listMethods', 'system.multicall']); + }); + + + this.server.on('system.multicall', function(err, params, callback) { + params.map(function(events) { + try { + events.map(function(event) { + if ((event["methodName"] == "event") && (event['params'] != undefined)) { + var params = event['params']; + var channel = "BidCos-RF." + params[1]; + var datapoint = params[2]; + var value = params[3]; + that.platform.foundAccessories.map(function(accessory) { + if (accessory.adress == channel) { + accessory.event(datapoint, value); + } + }); + } + }); + } catch (err) {} + }); + callback(null); + }); + + this.log('XML-RPC server listening on port 9090') this.connect(); - - - process.on('SIGINT', function () { - if (that.stopping) { - return; - } - that.stopping = true; - that.stop(); - }); - process.on('SIGTERM', function () { - if (that.stopping) { - return; - } - that.stopping = true; - that.stop(); - }); - }, - - getIPAddress: function() { - var interfaces = require('os').networkInterfaces(); - for (var devName in interfaces) { + process.on('SIGINT', function() { + if (that.stopping) { + return; + } + that.stopping = true; + that.stop(); + }); + + process.on('SIGTERM', function() { + if (that.stopping) { + return; + } + that.stopping = true; + that.stop(); + }); + + }, + + getIPAddress: function() { + var interfaces = require('os').networkInterfaces(); + for (var devName in interfaces) { var iface = interfaces[devName]; for (var i = 0; i < iface.length; i++) { - var alias = iface[i]; - if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) - return alias.address; - } + var alias = iface[i]; + if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) + return alias.address; } - return '0.0.0.0'; - }, + } + return '0.0.0.0'; + }, - getValue:function(channel,datapoint,callback) { - - var that = this; - if (this.client == undefined) { - that.log("Returning cause client is invalid"); - return; - } - if (channel.indexOf("BidCos-RF.")>-1) { - channel = channel.substr(10); - this.log("Calling rpc getValue"); - this.client.methodCall('getValue', [channel,datapoint], function (error, value) { - callback(value); - }); - return; - } - }, + getValue: function(channel, datapoint, callback) { - setValue:function(channel,datapoint,value) { - - var that = this; - - if (this.client == undefined) return; + var that = this; + if (this.client == undefined) { + that.log("Returning cause client is invalid"); + return; + } + if (channel.indexOf("BidCos-RF.") > -1)  { + channel = channel.substr(10); + this.log("Calling rpc getValue"); + this.client.methodCall('getValue', [channel, datapoint], function(error, value) { + callback(value); + }); + return; + } + }, - if (channel.indexOf("BidCos-RF.")>-1) { - channel = channel.substr(10); - } - - this.client.methodCall('setValue', [channel,datapoint,value], function (error, value) { + setValue: function(channel, datapoint, value) { - }); - }, + var that = this; - connect:function(){ - var that = this; - this.log('Creating Local HTTP Client for CCU RPC Events'); - this.client = xmlrpc.createClient({ host: this.ccuip, port: 2001, path: '/'}); - this.log('CCU RPC Init Call on port 2001'); - this.client.methodCall('init', ['http://'+this.localIP+':9090','homebridge'], function (error, value) { - that.log('CCU Response ....') - }); - }, - - - stop:function() { - this.log("Removing Event Server"); - this.client.methodCall('init', ['http://'+this.localIP+':9090'], function (error, value) { + if (this.client == undefined) return; + + if (channel.indexOf("BidCos-RF.") > -1)  { + channel = channel.substr(10); + } + + this.client.methodCall('setValue', [channel, datapoint, value], function(error, value) { }); - setTimeout(process.exit(0), 1000); + }, + + connect: function() { + var that = this; + this.log('Creating Local HTTP Client for CCU RPC Events'); + this.client = xmlrpc.createClient({ + host: this.ccuip, + port: 2001, + path: '/' + }); + this.log('CCU RPC Init Call on port 2001'); + this.client.methodCall('init', ['http://' + this.localIP + ':9090', 'homebridge'], function(error, value) { + that.log('CCU Response ....') + }); + }, + + + stop: function() { + this.log("Removing Event Server"); + this.client.methodCall('init', ['http://' + this.localIP + ':9090'], function(error, value) { + + }); + setTimeout(process.exit(0), 1000); } } function HomeMaticPlatform(log, config) { - this.log = log; - this.ccuIP = config["ccu_ip"]; - this.filter_device = config["filter_device"]; - this.filter_channel = config["filter_channel"]; - this.outlets = config["outlets"]; + this.log = log; + this.ccuIP = config["ccu_ip"]; + this.filter_device = config["filter_device"]; + this.filter_channel = config["filter_channel"]; + this.outlets = config["outlets"]; - this.sendQueue = []; - this.timer = 0; - - this.foundAccessories = []; - this.adressesToQuery = []; - - this.xmlrpc = new HomematicRPC(this.log,this.ccuIP,this); - this.xmlrpc.init(); + this.sendQueue = []; + this.timer = 0; + + this.foundAccessories = []; + this.adressesToQuery = []; + + this.xmlrpc = new HomematicRPC(this.log, this.ccuIP, this); + this.xmlrpc.init(); } HomeMaticPlatform.prototype = { - - + + accessories: function(callback) { this.log("Fetching Homematic devices..."); - var that = this; + var that = this; that.foundAccessories = []; - + var script = "string sDeviceId;string sChannelId;boolean df = true;Write(\'{\"devices\":[\');foreach(sDeviceId, root.Devices().EnumIDs()){object oDevice = dom.GetObject(sDeviceId);if(oDevice){var oInterface = dom.GetObject(oDevice.Interface());if(df) {df = false;} else { Write(\',\');}Write(\'{\');Write(\'\"id\": \"\' # sDeviceId # \'\",\');Write(\'\"name\": \"\' # oDevice.Name() # \'\",\');Write(\'\"address\": \"\' # oDevice.Address() # \'\",\');Write(\'\"channels\": [\');boolean bcf = true;foreach(sChannelId, oDevice.Channels().EnumIDs()){object oChannel = dom.GetObject(sChannelId);if(bcf) {bcf = false;} else {Write(\',\');}Write(\'{\');Write(\'\"cId\": \' # sChannelId # \',\');Write(\'\"name\": \"\' # oChannel.Name() # \'\",\');if(oInterface){Write(\'\"address\": \"\' # oInterface.Name() #\'.'\ # oChannel.Address() # \'\",\');}Write(\'\"type\": \"\' # oChannel.HssType() # \'\"\');Write(\'}\');}Write(\']}\');}}Write(\']}\');"; - var regarequest = new RegaRequest(this.log,this.ccuIP).script(script, function(data) { - var json = JSON.parse(data); - if (json['devices'] != undefined) { - json['devices'].map(function(device) { - var isFiltered = false; + var regarequest = new RegaRequest(this.log, this.ccuIP).script(script, function(data) { + var json = JSON.parse(data); + if (json['devices'] != undefined) { + json['devices'].map(function(device) { + var isFiltered = false; - if ((that.filter_device != undefined) && (that.filter_device.indexOf(device.address) > -1)) { - isFiltered = true; - } else { - isFiltered = false; - } - // that.log('device address:', device.address); + if ((that.filter_device != undefined) && (that.filter_device.indexOf(device.address) > -1)) { + isFiltered = true; + } else { + isFiltered = false; + } + // that.log('device address:', device.address); - if ((device['channels'] != undefined) && (!isFiltered)) { + if ((device['channels'] != undefined) && (!isFiltered)) { - device['channels'].map(function(ch) { - var isChannelFiltered = false; + device['channels'].map(function(ch) { + var isChannelFiltered = false; - if ((that.filter_channel != undefined) && (that.filter_channel.indexOf(ch.address) > -1)) { - isChannelFiltered = true; - } else { - isChannelFiltered = false; - } - // that.log('name', ch.name, ' -> address:', ch.address); - if ((ch.address != undefined) && (!isChannelFiltered)) { - - if ((ch.type=="SWITCH") || (ch.type=="BLIND") || (ch.type=="SHUTTER_CONTACT") - || (ch.type=="DIMMER") || (ch.type=="CLIMATECONTROL_RT_TRANSCEIVER") - || (ch.type=="MOTION_DETECTOR") || (ch.type=="KEYMATIC") - ) { - // Switch found - // Check if marked as Outlet - var special = (that.outlets.indexOf(ch.address) > -1) ? 'OUTLET' : undefined; - accessory = new HomeMaticGenericChannel(that.log, that, ch.id , ch.name , ch.type , ch.address, special); - that.foundAccessories.push(accessory); - } - + if ((that.filter_channel != undefined) && (that.filter_channel.indexOf(ch.address) > -1)) { + isChannelFiltered = true; + } else { + isChannelFiltered = false; + } + // that.log('name', ch.name, ' -> address:', ch.address); + if ((ch.address != undefined) && (!isChannelFiltered)) { - } else { - that.log(device.name + " has no address"); - } + if ((ch.type == "SWITCH") || (ch.type == "BLIND") || (ch.type == "SHUTTER_CONTACT") || (ch.type == "DIMMER") || (ch.type == "CLIMATECONTROL_RT_TRANSCEIVER") ||  (ch.type == "MOTION_DETECTOR") ||  (ch.type == "KEYMATIC")) { + // Switch found + // Check if marked as Outlet + var special = (that.outlets.indexOf(ch.address) > -1) ? 'OUTLET' : undefined; + accessory = new HomeMaticGenericChannel(that.log, that, ch.id, ch.name, ch.type, ch.address, special); + that.foundAccessories.push(accessory); + } - }); - } else { - that.log(device.name + " has no channels or is filtered"); - } - }); + } else { + that.log(device.name + " has no address"); + } -/* - accessory = new HomeMaticGenericChannel(that.log, that, "1234" , "DummyKM" , "KEYMATIC" , "1234"); - that.foundAccessories.push(accessory); + }); + } else { + that.log(device.name + " has no channels or is filtered"); + } - accessory = new HomeMaticGenericChannel(that.log, that, "5678" , "DummyBLIND" , "BLIND" , "5678"); - that.foundAccessories.push(accessory); - - */ - callback(that.foundAccessories); - } else { - callback(that.foundAccessories); - } + }); + + /* + accessory = new HomeMaticGenericChannel(that.log, that, "1234" , "DummyKM" , "KEYMATIC" , "1234"); + that.foundAccessories.push(accessory); + + accessory = new HomeMaticGenericChannel(that.log, that, "5678" , "DummyBLIND" , "BLIND" , "5678"); + that.foundAccessories.push(accessory); + + */ + callback(that.foundAccessories); + } else { + callback(that.foundAccessories); + } }); - + }, - - setValue:function(channel,datapoint,value) { - if (channel.indexOf("BidCos-RF.")>-1) { - this.xmlrpc.setValue(channel,datapoint,value); - return; + + setValue: function(channel, datapoint, value) { + if (channel.indexOf("BidCos-RF.") > -1)  { + this.xmlrpc.setValue(channel, datapoint, value); + return; + } + + if (channel.indexOf("VirtualDevices.") > -1)  { + var rega = new RegaRequest(this.log, this.ccuIP); + rega.setValue(channel, datapoint, value); + return; } - if (channel.indexOf("VirtualDevices.")>-1) { - var rega = new RegaRequest(this.log,this.ccuIP); - rega.setValue(channel,datapoint,value); - return; - } - }, - - - getValue:function(channel,datapoint,callback) { - - if (channel.indexOf("BidCos-RF.")>-1) { - this.xmlrpc.getValue(channel,datapoint,callback); - return; - } - - if (channel.indexOf("VirtualDevices.")>-1) { - var rega = new RegaRequest(this.log,this.ccuIP); - rega.getValue(channel,datapoint,callback); - return; - } - + + + getValue: function(channel, datapoint, callback) { + + if (channel.indexOf("BidCos-RF.") > -1)  { + this.xmlrpc.getValue(channel, datapoint, callback); + return; + } + + if (channel.indexOf("VirtualDevices.") > -1)  { + var rega = new RegaRequest(this.log, this.ccuIP); + rega.getValue(channel, datapoint, callback); + return; + } + }, - - prepareRequest: function(accessory,script) { + + prepareRequest: function(accessory, script) { var that = this; this.sendQueue.push(script); that.delayed(100); @@ -369,34 +371,36 @@ HomeMaticPlatform.prototype = { }); this.sendQueue = []; //this.log("RegaSend: " + script); - var regarequest = new RegaRequest(this.log,this.ccuIP).script(script, function(data) { - }); + var regarequest = new RegaRequest(this.log, this.ccuIP).script(script, function(data) {}); }, - sendRequest: function(accessory,script,callback) { + sendRequest: function(accessory, script, callback) { var that = this; - var regarequest = new RegaRequest(this.log,this.ccuIP).script(script, function(data) { - if (data != undefined) { - try { - var json = JSON.parse(data); - callback(json); - } catch (err) { - callback(undefined); - } - return; - } + var regarequest = new RegaRequest(this.log, this.ccuIP).script(script, function(data) { + if (data != undefined) { + try { + var json = JSON.parse(data); + callback(json); + } catch (err) { + callback(undefined); + } + return; + } }); }, delayed: function(delay) { var timer = this.delayed[delay]; - if( timer ) { + if (timer) { this.log("removing old command"); - clearTimeout( timer ); + clearTimeout(timer); } var that = this; - this.delayed[delay] = setTimeout( function(){clearTimeout(that.delayed[delay]);that.sendPreparedRequests()}, delay?delay:100); + this.delayed[delay] = setTimeout(function() { + clearTimeout(that.delayed[delay]); + that.sendPreparedRequests() + }, delay ? delay : 100); this.log("New Timer was set"); } } diff --git a/platforms/HomematicChannel.js b/platforms/HomematicChannel.js index 1ef22a5..4e5a34b 100644 --- a/platforms/HomematicChannel.js +++ b/platforms/HomematicChannel.js @@ -1,180 +1,180 @@ var types = require("hap-nodejs/accessories/types.js"); -function HomeMaticGenericChannel(log,platform, id ,name, type ,adress,special) { - this.name = name; - this.type = type; - this.adress = adress; - this.log = log; +function HomeMaticGenericChannel(log, platform, id, name, type, adress, special) { + this.name = name; + this.type = type; + this.adress = adress; + this.log = log; this.platform = platform; - this.state = []; + this.state = []; this.eventupdate = false; - this.special = special; + this.special = special; this.currentStateCharacteristic = []; this.reverseDP = []; } - HomeMaticGenericChannel.prototype = { - // Return current States - query: function(dp,callback) { + // Return current States + query: function(dp, callback) { var that = this; - + if (this.state[dp] != undefined) { callback(this.state[dp]); } else { -// that.log("No cached Value found start fetching and send temp 0 back"); + // that.log("No cached Value found start fetching and send temp 0 back"); this.remoteGetValue(dp); callback(0); } }, - dpvalue:function(dp,fallback) { + dpvalue: function(dp, fallback) { if (this.state[dp] != undefined) { - return(this.state[dp]); + return (this.state[dp]); } else { return fallback; } }, - remoteGetValue:function(dp) { - var that = this; - that.platform.getValue(that.adress,dp,function(newValue) { - that.log("Remote Value Response for " + that.adress + "." + dp + "->" + newValue); - that.eventupdate = true; - that.cache(dp,newValue); - that.eventupdate = false; - }); + remoteGetValue: function(dp) { + var that = this; + that.platform.getValue(that.adress, dp, function(newValue) { + that.log("Remote Value Response for " + that.adress + "." + dp + "->" + newValue); + that.eventupdate = true; + that.cache(dp, newValue); + that.eventupdate = false; + }); }, - - event:function(dp,newValue) { - - if (dp=="LEVEL") { - newValue = newValue*100; + + event: function(dp, newValue) { + + if (dp == "LEVEL") { + newValue = newValue * 100; } this.eventupdate = true; - this.cache(dp,newValue); + this.cache(dp, newValue); this.eventupdate = false; }, - reverse:function(value) { - if (value=="true") return "false"; - if (value=="false") return "true"; - if (value==0) return 1; - if (value==1) return 0; - if (value=="0") return "1"; - if (value=="1") return "0"; + reverse: function(value) { + if (value == "true") return "false"; + if (value == "false") return "true"; + if (value == 0) return 1; + if (value == 1) return 0; + if (value == "0") return "1"; + if (value == "1") return "0"; return value; }, - cache:function(dp,value) { + cache: function(dp, value) { var that = this; - if ((that.reverseDP[dp]!=undefined) && (that.reverseDP[dp]==true)) { - value = that.reverse(value); - } - - if (that.currentStateCharacteristic[dp]!=undefined) { - that.currentStateCharacteristic[dp].updateValue(value, null); + if ((that.reverseDP[dp] != undefined) && (that.reverseDP[dp] == true)) { + value = that.reverse(value); + } + + if (that.currentStateCharacteristic[dp] != undefined) { + that.currentStateCharacteristic[dp].updateValue(value, null); } this.state[dp] = value; }, - delayed: function(mode, dp,value,delay) { - - if (this.eventupdate==true) { - return; - } - - var timer = this.delayed[delay]; - if( timer ) { - clearTimeout( timer ); + delayed: function(mode, dp, value, delay) { + + if (this.eventupdate == true) { + return; } - this.log(this.name + " delaying command "+mode + " " + dp +" with value " + value); + var timer = this.delayed[delay]; + if (timer) { + clearTimeout(timer); + } + + this.log(this.name + " delaying command " + mode + " " + dp + " with value " + value); var that = this; - this.delayed[delay] = setTimeout( function(){clearTimeout(that.delayed[delay]);that.command(mode,dp,value)}, delay?delay:100 ); + this.delayed[delay] = setTimeout(function() { + clearTimeout(that.delayed[delay]); + that.command(mode, dp, value) + }, delay ? delay : 100); }, - command: function(mode,dp,value,callback) { - - if (this.eventupdate==true) { - return; - } - var that = this; + command: function(mode, dp, value, callback) { - if (mode == "set") { - //this.log("Send " + value + " to Datapoint " + dp + " at " + that.adress); - that.platform.setValue(that.adress,dp,value); - } + if (this.eventupdate == true) { + return; + } + var that = this; + + if (mode == "set") { + //this.log("Send " + value + " to Datapoint " + dp + " at " + that.adress); + that.platform.setValue(that.adress, dp, value); + } }, informationCharacteristics: function() { - return [ - { - 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: "EQ-3", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.type, - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.adress , - 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 - } - ] + return [{ + 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: "EQ-3", + supportEvents: false, + supportBonjour: false, + manfDescription: "Manufacturer", + designedMaxLength: 255 + }, { + cType: types.MODEL_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.type, + supportEvents: false, + supportBonjour: false, + manfDescription: "Model", + designedMaxLength: 255 + }, { + cType: types.SERIAL_NUMBER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.adress, + 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) { - + cTypes = [{ cType: types.NAME_CTYPE, onUpdate: null, @@ -186,181 +186,177 @@ HomeMaticGenericChannel.prototype = { manfDescription: "Name of service", designedMaxLength: 255 }] - - - if (this.type=="SWITCH") { - cTypes.push({ + + + if (this.type == "SWITCH") { + cTypes.push({ cType: types.POWER_STATE_CTYPE, onUpdate: function(value) { - that.command("set","STATE" , (value==1)?true:false) + that.command("set", "STATE", (value == 1) ? true : false) }, onRead: function(callback) { - that.query("STATE",callback); + that.query("STATE", callback); }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["STATE"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("STATE"); + + onRegister: function(characteristic) { + that.currentStateCharacteristic["STATE"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("STATE"); }, - - perms: ["pw","pr","ev"], + + perms: ["pw", "pr", "ev"], format: "bool", - initialValue: that.dpvalue("STATE",0), + initialValue: that.dpvalue("STATE", 0), supportEvents: false, supportBonjour: false, manfDescription: "Change the power state", designedMaxLength: 1 }); - - if (this.special=="OUTLET") { + + if (this.special == "OUTLET") { cTypes.push({ cType: types.OUTLET_IN_USE_CTYPE, - + onRead: function(callback) { - callback(true); + callback(true); }, - perms: ["pr","ev"], - format: "bool", - initialValue: true, - supportEvents: false, - supportBonjour: false, - manfDescription: "Is Outlet in Use", - designedMaxLength: 1 - }) - } + perms: ["pr", "ev"], + format: "bool", + initialValue: true, + supportEvents: false, + supportBonjour: false, + manfDescription: "Is Outlet in Use", + designedMaxLength: 1 + }) + } } - - - if (this.type=="KEYMATIC") { - cTypes.push( - { - cType: types.CURRENT_LOCK_MECHANISM_STATE_CTYPE, - - onRead: function(callback) { - that.query("STATE",callback); - }, - - onRegister: function(characteristic) { + + + if (this.type == "KEYMATIC") { + cTypes.push({ + cType: types.CURRENT_LOCK_MECHANISM_STATE_CTYPE, + + onRead: function(callback) { + that.query("STATE", callback); + }, + + onRegister: function(characteristic) { that.currentStateCharacteristic["STATE"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("STATE"); - }, + }, - perms: ["pr","ev"], - format: "bool", - initialValue: that.dpvalue("STATE",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Current State of your Lock", - designedMaxLength: 1 - }, - { - cType: types.TARGET_LOCK_MECHANISM_STATE_CTYPE, - - onUpdate: function(value) { - that.command("set","STATE",(value==1)?"true":"false") - }, - - onRead: function(callback) { - that.query("STATE",callback); - }, - - onRegister: function(characteristic) { + perms: ["pr", "ev"], + format: "bool", + initialValue: that.dpvalue("STATE", 0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current State of your Lock", + designedMaxLength: 1 + }, { + cType: types.TARGET_LOCK_MECHANISM_STATE_CTYPE, + + onUpdate: function(value) { + that.command("set", "STATE", (value == 1) ? "true" : "false") + }, + + onRead: function(callback) { + that.query("STATE", callback); + }, + + onRegister: function(characteristic) { that.reverseDP["STATE"] = true; that.currentStateCharacteristic["STATE"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("STATE"); - }, + }, - - perms: ["pw","pr","ev"], - format: "bool", - initialValue: that.dpvalue("STATE",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Target State of your Lock", - designedMaxLength: 1 - } - - , - { - cType: types.TARGET_DOORSTATE_CTYPE, - - onUpdate: function(value) { - that.command("set","OPEN" , "true") - }, - onRead: function(callback) { - callback(1); - }, - - onRegister: function(characteristic) { + perms: ["pw", "pr", "ev"], + format: "bool", + initialValue: that.dpvalue("STATE", 0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Target State of your Lock", + designedMaxLength: 1 + } + + , { + cType: types.TARGET_DOORSTATE_CTYPE, + + onUpdate: function(value) { + that.command("set", "OPEN", "true") + }, + + onRead: function(callback) { + callback(1); + }, + + onRegister: function(characteristic) { that.currentStateCharacteristic["OPEN"] = characteristic; characteristic.eventEnabled = true; - }, - - perms: ["pw","pr","ev"], - format: "bool", - initialValue: 1, - supportEvents: false, - supportBonjour: false, - manfDescription: "Open the Lock", - designedMaxLength: 1 - } + }, + + perms: ["pw", "pr", "ev"], + format: "bool", + initialValue: 1, + supportEvents: false, + supportBonjour: false, + manfDescription: "Open the Lock", + designedMaxLength: 1 + } ); - - + + } - if (this.type=="DIMMER") { - cTypes.push({ + if (this.type == "DIMMER") { + cTypes.push({ cType: types.POWER_STATE_CTYPE, onUpdate: function(value) { - that.command("set","LEVEL" , (value==true) ? "1" : "0") + that.command("set", "LEVEL", (value == true) ? "1" : "0") }, onRead: function(callback) { - that.query("LEVEL",callback); + that.query("LEVEL", callback); }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["LEVEL"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("LEVEL"); + + onRegister: function(characteristic) { + that.currentStateCharacteristic["LEVEL"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("LEVEL"); }, - - perms: ["pw","pr","ev"], + + perms: ["pw", "pr", "ev"], format: "bool", - initialValue: (that.dpvalue("LEVEL")>0,0), + initialValue: (that.dpvalue("LEVEL") > 0, 0), supportEvents: false, supportBonjour: false, manfDescription: "Change the power state", designedMaxLength: 1 - }, - { + }, { cType: types.BRIGHTNESS_CTYPE, onUpdate: function(value) { - that.delayed("set","LEVEL" , String(value/100),100); - }, - - onRead: function(callback) { - that.query("LEVEL",callback); - }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["LEVEL"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("LEVEL"); + that.delayed("set", "LEVEL", String(value / 100), 100); }, - - perms: ["pw","pr","ev"], + onRead: function(callback) { + that.query("LEVEL", callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["LEVEL"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("LEVEL"); + }, + + + perms: ["pw", "pr", "ev"], format: "int", - initialValue: that.dpvalue("LEVEL",0), + initialValue: that.dpvalue("LEVEL", 0), supportEvents: false, supportBonjour: false, manfDescription: "Adjust Brightness of Light", @@ -371,245 +367,282 @@ HomeMaticGenericChannel.prototype = { }); } - if (this.type=="BLIND") { - cTypes.push( - { - cType: types.WINDOW_COVERING_CURRENT_POSITION_CTYPE, - - onRead: function(callback) { - that.query("LEVEL",callback); - }, - - onRegister: function(characteristic) { + if (this.type == "BLIND") { + cTypes.push({ + cType: types.WINDOW_COVERING_CURRENT_POSITION_CTYPE, + + onRead: function(callback) { + that.query("LEVEL", callback); + }, + + onRegister: function(characteristic) { that.currentStateCharacteristic["LEVEL"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("LEVEL"); + }, + + perms: ["pr", "ev"], + format: "int", + initialValue: that.dpvalue("LEVEL", 0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Blind Position", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" }, - perms: ["pr","ev"], - format: "int", - initialValue: that.dpvalue("LEVEL",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Blind Position", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }, - - { - cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, - - onUpdate: function(value) { - that.delayed("set","LEVEL" , String(value/100),100); - }, - + { + cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, - onRead: function(callback) { - that.query("LEVEL",callback); - }, - - onRegister: function(characteristic) { + onUpdate: function(value) { + that.delayed("set", "LEVEL", String(value / 100), 100); + }, + + + onRead: function(callback) { + that.query("LEVEL", callback); + }, + + onRegister: function(characteristic) { that.currentStateCharacteristic["LEVEL"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("LEVEL"); - }, + }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: that.dpvalue("LEVEL",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Target Blind Position", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }, - { - cType: types.WINDOW_COVERING_OPERATION_STATE_CTYPE, - - onRead: function(callback) { - that.query("DIRECTION",callback); - }, - - onRegister: function(characteristic) { + perms: ["pw", "pr", "ev"], + format: "int", + initialValue: that.dpvalue("LEVEL", 0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Target Blind Position", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }, { + cType: types.WINDOW_COVERING_OPERATION_STATE_CTYPE, + + onRead: function(callback) { + that.query("DIRECTION", callback); + }, + + onRegister: function(characteristic) { that.currentStateCharacteristic["DIRECTION"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("DIRECTION"); - }, + }, + + perms: ["pr", "ev"], + format: "int", + initialValue: that.dpvalue("DIRECTION", 0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Operating State ", + designedMinValue: 0, + designedMaxValue: 2, + designedMinStep: 1 + } - perms: ["pr","ev"], - format: "int", - initialValue: that.dpvalue("DIRECTION",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Operating State ", - designedMinValue: 0, - designedMaxValue: 2, - designedMinStep: 1 - } - ); } - - if (this.type=="SHUTTER_CONTACT") { - cTypes.push( - { - cType: types.CONTACT_SENSOR_STATE_CTYPE, - + + if (this.type == "SHUTTER_CONTACT") { + cTypes.push({ + cType: types.CONTACT_SENSOR_STATE_CTYPE, + onRead: function(callback) { - that.query("STATE",callback); + that.query("STATE", callback); }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["STATE"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("STATE"); + + onRegister: function(characteristic) { + that.currentStateCharacteristic["STATE"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("STATE"); }, - - perms: ["pr","ev"], - format: "bool", - initialValue: that.dpvalue("STATE",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Current State" - }); - } - - if (this.type=="MOTION_DETECTOR") { - cTypes.push( - { - cType: types.MOTION_DETECTED_CTYPE, - + + perms: ["pr", "ev"], + format: "bool", + initialValue: that.dpvalue("STATE", 0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current State" + }); + } + + if (this.type == "MOTION_DETECTOR") { + cTypes.push({ + cType: types.MOTION_DETECTED_CTYPE, + onRead: function(callback) { - that.query("MOTION",callback); + that.query("MOTION", callback); }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["MOTION"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("MOTION"); + + onRegister: function(characteristic) { + that.currentStateCharacteristic["MOTION"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("MOTION"); }, - - perms: ["pr","ev"], - format: "bool", - initialValue: that.dpvalue("MOTION",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Motion State" - }); - } - - if (this.type=="CLIMATECONTROL_RT_TRANSCEIVER") { - - cTypes.push({ - cType: types.NAME_CTYPE,onUpdate: null,perms: ["pr"],format: "string", - initialValue: this.name,supportEvents: true,supportBonjour: false,manfDescription: "Name of service",designedMaxLength: 255 - }, - - { - cType: types.CURRENTHEATINGCOOLING_CTYPE,onUpdate: null, - perms: ["pr"],format: "int",initialValue: 1,supportEvents: false, - supportBonjour: false,manfDescription: "Current Mode",designedMaxLength: 1,designedMinValue: 1,designedMaxValue: 1,designedMinStep: 1 - }, - - { - cType: types.TARGETHEATINGCOOLING_CTYPE,onUpdate: null,perms: ["pw","pr"], - format: "int",initialValue: 1,supportEvents: false,supportBonjour: false,manfDescription: "Target Mode", - designedMinValue: 1,designedMaxValue: 1,designedMinStep: 1 - }, - - { - cType: types.CURRENT_TEMPERATURE_CTYPE, - onUpdate: null, - - onRead: function(callback) { - that.query("ACTUAL_TEMPERATURE",callback); - }, - - onRegister: function(characteristic) { + + perms: ["pr", "ev"], + format: "bool", + initialValue: that.dpvalue("MOTION", 0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Motion State" + }); + } + + if (this.type == "CLIMATECONTROL_RT_TRANSCEIVER") { + + cTypes.push({ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name, + supportEvents: true, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + }, + + { + cType: types.CURRENTHEATINGCOOLING_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "int", + initialValue: 1, + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Mode", + designedMaxLength: 1, + designedMinValue: 1, + designedMaxValue: 1, + designedMinStep: 1 + }, + + { + cType: types.TARGETHEATINGCOOLING_CTYPE, + onUpdate: null, + perms: ["pw", "pr"], + format: "int", + initialValue: 1, + supportEvents: false, + supportBonjour: false, + manfDescription: "Target Mode", + designedMinValue: 1, + designedMaxValue: 1, + designedMinStep: 1 + }, + + { + cType: types.CURRENT_TEMPERATURE_CTYPE, + onUpdate: null, + + onRead: function(callback) { + that.query("ACTUAL_TEMPERATURE", callback); + }, + + onRegister: function(characteristic) { that.currentStateCharacteristic["ACTUAL_TEMPERATURE"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("ACTUAL_TEMPERATURE"); - }, - perms: ["pw","pr","ev"], perms: ["pr"],format: "double", - initialValue: that.dpvalue("ACTUAL_TEMPERATURE",20), - supportEvents: false,supportBonjour: false,manfDescription: "Current Temperature",unit: "celsius" - }, - - { - cType: types.TARGET_TEMPERATURE_CTYPE, - onUpdate: function(value) { - that.delayed("set", "SET_TEMPERATURE", value,500); - }, - onRead: function(callback) { - that.query("SET_TEMPERATURE",callback); - - }, - onRegister: function(characteristic) { + }, + perms: ["pw", "pr", "ev"], + perms: ["pr"], + format: "double", + initialValue: that.dpvalue("ACTUAL_TEMPERATURE", 20), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Temperature", + unit: "celsius" + }, + + { + cType: types.TARGET_TEMPERATURE_CTYPE, + onUpdate: function(value) { + that.delayed("set", "SET_TEMPERATURE", value, 500); + }, + onRead: function(callback) { + that.query("SET_TEMPERATURE", callback); + + }, + onRegister: function(characteristic) { that.currentStateCharacteristic["SET_TEMPERATURE"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("SET_TEMPERATURE"); - }, - perms: ["pw","pr","ev"],format: "double", - initialValue: that.dpvalue("SET_TEMPERATURE",16), - supportEvents: false,supportBonjour: false, manfDescription: "Target Temperature", - designedMinValue: 16,designedMaxValue: 38,designedMinStep: 1,unit: "celsius" - }, - - { - cType: types.TEMPERATURE_UNITS_CTYPE,onRead: null, - perms: ["pr"],format: "int",initialValue: 0,supportEvents: false, - supportBonjour: false,manfDescription: "Current Temperature Unit",unit: "celsius" + }, + perms: ["pw", "pr", "ev"], + format: "double", + initialValue: that.dpvalue("SET_TEMPERATURE", 16), + supportEvents: false, + supportBonjour: false, + manfDescription: "Target Temperature", + designedMinValue: 16, + designedMaxValue: 38, + designedMinStep: 1, + unit: "celsius" + }, + + { + cType: types.TEMPERATURE_UNITS_CTYPE, + onRead: null, + perms: ["pr"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Temperature Unit", + unit: "celsius" + } + + ); } - ); - } - - + return cTypes }, sType: function() { - - if (this.type=="SWITCH") { - - if (this.special=="OUTLET") { - return types.OUTLET_STYPE; - } else { - return types.LIGHTBULB_STYPE; - } - } - - if (this.type=="DIMMER") { - return types.LIGHTBULB_STYPE; - } - if (this.type=="BLIND") { + if (this.type == "SWITCH") { + + if (this.special == "OUTLET") { + return types.OUTLET_STYPE; + } else { + return types.LIGHTBULB_STYPE; + } + } + + if (this.type == "DIMMER") { + return types.LIGHTBULB_STYPE; + } + + if (this.type == "BLIND") { return types.WINDOW_COVERING_STYPE; - } + } - if (this.type=="CLIMATECONTROL_RT_TRANSCEIVER") { + if (this.type == "CLIMATECONTROL_RT_TRANSCEIVER") { return types.THERMOSTAT_STYPE; - } - - if (this.type=="SHUTTER_CONTACT") { + } + + if (this.type == "SHUTTER_CONTACT") { return types.CONTACT_SENSOR_STYPE; - } - - if (this.type=="MOTION_DETECTOR") { - return types.MOTION_SENSOR_STYPE - } + } + + if (this.type == "MOTION_DETECTOR") { + return types.MOTION_SENSOR_STYPE + } + + + if (this.type == "KEYMATIC") { + return types.LOCK_MECHANISM_STYPE + } + - if (this.type=="KEYMATIC") { - return types.LOCK_MECHANISM_STYPE - } - - - }, getServices: function() { @@ -617,8 +650,7 @@ HomeMaticGenericChannel.prototype = { var services = [{ sType: types.ACCESSORY_INFORMATION_STYPE, characteristics: this.informationCharacteristics(), - }, - { + }, { sType: this.sType(), characteristics: this.controlCharacteristics(that) }]; From fd49b96d786a0491ab8596817d93d013c79681ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Ko=CC=88nig?= Date: Thu, 29 Oct 2015 20:28:00 +0100 Subject: [PATCH 61/89] cleanup formatting --- platforms/HomeMatic.js | 121 ++++++++++++++++---------------- platforms/HomematicChannel.js | 128 ++++++++++++++++------------------ 2 files changed, 122 insertions(+), 127 deletions(-) diff --git a/platforms/HomeMatic.js b/platforms/HomeMatic.js index 1b99729..a451451 100644 --- a/platforms/HomeMatic.js +++ b/platforms/HomeMatic.js @@ -1,3 +1,4 @@ +"use strict"; // // Homematic Platform Shim for HomeBridge // @@ -7,13 +8,13 @@ var types = require("hap-nodejs/accessories/types.js"); -var xmlrpc = require('homematic-xmlrpc') +var xmlrpc = require("homematic-xmlrpc"); var request = require("request"); var http = require("http"); var path = require("path"); -var HomeMaticGenericChannel = require(path.resolve(__dirname, 'HomematicChannel.js')); +var HomeMaticGenericChannel = require(path.resolve(__dirname, "HomematicChannel.js")); @@ -28,29 +29,29 @@ RegaRequest.prototype = { var post_options = { host: this.ccuIP, - port: '80', - path: '/tclrega.exe', - method: 'POST', + port: "80", + path: "/tclrega.exe", + method: "POST", headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Content-Length': script.length + "Content-Type": "application/x-www-form-urlencoded", + "Content-Length": script.length } }; var post_req = http.request(post_options, function(res) { var data = ""; - res.setEncoding('binary'); - res.on('data', function(chunk) { + res.setEncoding("binary"); + res.on("data", function(chunk) { data += chunk.toString(); }); - res.on('end', function() { + res.on("end", function() { var pos = data.lastIndexOf(""); var response = (data.substring(0, pos)); callback(response); }); }); - post_req.on('error', function(e) { + post_req.on("error", function(e) { callback("{}"); }); @@ -65,23 +66,24 @@ RegaRequest.prototype = { var script = "var d = dom.GetObject(\"" + channel + "." + datapoint + "\");if (d){Write(d.State());}"; //that.log("Rega Request " + script); - var regarequest = this.script(script, function(data) { + this.script(script, function(data) { that.log("Rega Response" + data); - if (data != undefined) { + if (data !== undefined) { callback(parseFloat(data)); } }); }, setValue: function(channel, datapoint, value) { - var that = this; var script = "var d = dom.GetObject(\"" + channel + "." + datapoint + "\");if (d){d.State(\"" + value + "\");}"; - //that.log("Rega Request " + script); - var regarequest = this.script(script, function(data) {}); + //this.log("Rega Request " + script); + this.script(script, function(data) { + + }); } -} +}; function HomematicRPC(log, ccuip, platform) { this.log = log; @@ -106,29 +108,29 @@ HomematicRPC.prototype = { } this.localIP = ip; - this.log("Local IP: " + this.localIP) + this.log("Local IP: " + this.localIP); this.server = xmlrpc.createServer({ host: this.localIP, port: 9090 - }) - - this.server.on('NotFound', function(method, params) { - that.log('Method ' + method + ' does not exist'); }); - this.server.on('system.listMethods', function(err, params, callback) { - that.log('Method call params for \'system.listMethods\': ' + params) - callback(null, ['system.listMethods', 'system.multicall']); + this.server.on("NotFound", function(method, params) { + that.log("Method " + method + " does not exist"); + }); + + this.server.on("system.listMethods", function(err, params, callback) { + that.log("Method call params for 'system.listMethods': " + params); + callback(null, ["system.listMethods", "system.multicall"]); }); - this.server.on('system.multicall', function(err, params, callback) { + this.server.on("system.multicall", function(err, params, callback) { params.map(function(events) { try { events.map(function(event) { - if ((event["methodName"] == "event") && (event['params'] != undefined)) { - var params = event['params']; + if ((event["methodName"] == "event") && (event["params"] !== undefined)) { + var params = event["params"]; var channel = "BidCos-RF." + params[1]; var datapoint = params[2]; var value = params[3]; @@ -144,11 +146,11 @@ HomematicRPC.prototype = { callback(null); }); - this.log('XML-RPC server listening on port 9090') + this.log("XML-RPC server listening on port 9090"); this.connect(); - process.on('SIGINT', function() { + process.on("SIGINT", function() { if (that.stopping) { return; } @@ -156,7 +158,7 @@ HomematicRPC.prototype = { that.stop(); }); - process.on('SIGTERM', function() { + process.on("SIGTERM", function() { if (that.stopping) { return; } @@ -167,29 +169,28 @@ HomematicRPC.prototype = { }, getIPAddress: function() { - var interfaces = require('os').networkInterfaces(); + var interfaces = require("os").networkInterfaces(); for (var devName in interfaces) { var iface = interfaces[devName]; for (var i = 0; i < iface.length; i++) { var alias = iface[i]; - if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) + if (alias.family === "IPv4" && alias.address !== "127.0.0.1" && !alias.internal) return alias.address; } } - return '0.0.0.0'; + return "0.0.0.0"; }, getValue: function(channel, datapoint, callback) { var that = this; - if (this.client == undefined) { + if (this.client === undefined) { that.log("Returning cause client is invalid"); return; } if (channel.indexOf("BidCos-RF.") > -1)  { channel = channel.substr(10); - this.log("Calling rpc getValue"); - this.client.methodCall('getValue', [channel, datapoint], function(error, value) { + this.client.methodCall("getValue", [channel, datapoint], function(error, value) { callback(value); }); return; @@ -200,41 +201,41 @@ HomematicRPC.prototype = { var that = this; - if (this.client == undefined) return; + if (this.client === undefined) return; if (channel.indexOf("BidCos-RF.") > -1)  { channel = channel.substr(10); } - this.client.methodCall('setValue', [channel, datapoint, value], function(error, value) { + this.client.methodCall("setValue", [channel, datapoint, value], function(error, value) { }); }, connect: function() { var that = this; - this.log('Creating Local HTTP Client for CCU RPC Events'); + this.log("Creating Local HTTP Client for CCU RPC Events"); this.client = xmlrpc.createClient({ host: this.ccuip, port: 2001, - path: '/' + path: "/" }); - this.log('CCU RPC Init Call on port 2001'); - this.client.methodCall('init', ['http://' + this.localIP + ':9090', 'homebridge'], function(error, value) { - that.log('CCU Response ....') + this.log("CCU RPC Init Call on port 2001"); + this.client.methodCall("init", ["http://" + this.localIP + ":9090", "homebridge"], function(error, value) { + that.log("CCU Response ...."); }); }, stop: function() { this.log("Removing Event Server"); - this.client.methodCall('init', ['http://' + this.localIP + ':9090'], function(error, value) { + this.client.methodCall("init", ["http://" + this.localIP + ":9090"], function(error, value) { }); setTimeout(process.exit(0), 1000); } -} +}; function HomeMaticPlatform(log, config) { @@ -267,35 +268,35 @@ HomeMaticPlatform.prototype = { var regarequest = new RegaRequest(this.log, this.ccuIP).script(script, function(data) { var json = JSON.parse(data); - if (json['devices'] != undefined) { - json['devices'].map(function(device) { + if (json["devices"] !== undefined) { + json["devices"].map(function(device) { var isFiltered = false; - if ((that.filter_device != undefined) && (that.filter_device.indexOf(device.address) > -1)) { + if ((that.filter_device !== undefined) && (that.filter_device.indexOf(device.address) > -1)) { isFiltered = true; } else { isFiltered = false; } // that.log('device address:', device.address); - if ((device['channels'] != undefined) && (!isFiltered)) { + if ((device["channels"] !== undefined) && (!isFiltered)) { - device['channels'].map(function(ch) { + device["channels"].map(function(ch) { var isChannelFiltered = false; - if ((that.filter_channel != undefined) && (that.filter_channel.indexOf(ch.address) > -1)) { + if ((that.filter_channel !== undefined) && (that.filter_channel.indexOf(ch.address) > -1)) { isChannelFiltered = true; } else { isChannelFiltered = false; } // that.log('name', ch.name, ' -> address:', ch.address); - if ((ch.address != undefined) && (!isChannelFiltered)) { + if ((ch.address !== undefined) && (!isChannelFiltered)) { if ((ch.type == "SWITCH") || (ch.type == "BLIND") || (ch.type == "SHUTTER_CONTACT") || (ch.type == "DIMMER") || (ch.type == "CLIMATECONTROL_RT_TRANSCEIVER") ||  (ch.type == "MOTION_DETECTOR") ||  (ch.type == "KEYMATIC")) { // Switch found // Check if marked as Outlet - var special = (that.outlets.indexOf(ch.address) > -1) ? 'OUTLET' : undefined; - accessory = new HomeMaticGenericChannel(that.log, that, ch.id, ch.name, ch.type, ch.address, special); + var special = (that.outlets.indexOf(ch.address) > -1) ? "OUTLET" : undefined; + var accessory = new HomeMaticGenericChannel(that.log, that, ch.id, ch.name, ch.type, ch.address, special); that.foundAccessories.push(accessory); } @@ -370,14 +371,14 @@ HomeMaticPlatform.prototype = { script = script + command; }); this.sendQueue = []; - //this.log("RegaSend: " + script); + //this.log('RegaSend: ' + script); var regarequest = new RegaRequest(this.log, this.ccuIP).script(script, function(data) {}); }, sendRequest: function(accessory, script, callback) { - var that = this; + var regarequest = new RegaRequest(this.log, this.ccuIP).script(script, function(data) { - if (data != undefined) { + if (data !== undefined) { try { var json = JSON.parse(data); callback(json); @@ -399,11 +400,11 @@ HomeMaticPlatform.prototype = { var that = this; this.delayed[delay] = setTimeout(function() { clearTimeout(that.delayed[delay]); - that.sendPreparedRequests() + that.sendPreparedRequests(); }, delay ? delay : 100); this.log("New Timer was set"); } -} +}; diff --git a/platforms/HomematicChannel.js b/platforms/HomematicChannel.js index 4e5a34b..5b6cff2 100644 --- a/platforms/HomematicChannel.js +++ b/platforms/HomematicChannel.js @@ -1,3 +1,4 @@ +"use strict"; var types = require("hap-nodejs/accessories/types.js"); @@ -21,9 +22,7 @@ HomeMaticGenericChannel.prototype = { // Return current States query: function(dp, callback) { - var that = this; - - if (this.state[dp] != undefined) { + if (this.state[dp] !== undefined) { callback(this.state[dp]); } else { // that.log("No cached Value found start fetching and send temp 0 back"); @@ -34,7 +33,7 @@ HomeMaticGenericChannel.prototype = { }, dpvalue: function(dp, fallback) { - if (this.state[dp] != undefined) { + if (this.state[dp] !== undefined) { return (this.state[dp]); } else { return fallback; @@ -66,8 +65,8 @@ HomeMaticGenericChannel.prototype = { reverse: function(value) { if (value == "true") return "false"; if (value == "false") return "true"; - if (value == 0) return 1; - if (value == 1) return 0; + if (value === 0) return 1; + if (value === 1) return 0; if (value == "0") return "1"; if (value == "1") return "0"; return value; @@ -76,11 +75,11 @@ HomeMaticGenericChannel.prototype = { cache: function(dp, value) { var that = this; - if ((that.reverseDP[dp] != undefined) && (that.reverseDP[dp] == true)) { + if ((that.reverseDP[dp] !== undefined) && (that.reverseDP[dp] === true)) { value = that.reverse(value); } - if (that.currentStateCharacteristic[dp] != undefined) { + if (that.currentStateCharacteristic[dp] !== undefined) { that.currentStateCharacteristic[dp].updateValue(value, null); } this.state[dp] = value; @@ -89,7 +88,7 @@ HomeMaticGenericChannel.prototype = { delayed: function(mode, dp, value, delay) { - if (this.eventupdate == true) { + if (this.eventupdate === true) { return; } @@ -102,13 +101,13 @@ HomeMaticGenericChannel.prototype = { var that = this; this.delayed[delay] = setTimeout(function() { clearTimeout(that.delayed[delay]); - that.command(mode, dp, value) + that.command(mode, dp, value); }, delay ? delay : 100); }, command: function(mode, dp, value, callback) { - if (this.eventupdate == true) { + if (this.eventupdate === true) { return; } var that = this; @@ -170,12 +169,12 @@ HomeMaticGenericChannel.prototype = { supportBonjour: false, manfDescription: "Identify Accessory", designedMaxLength: 1 - }] + }]; }, controlCharacteristics: function(that) { - cTypes = [{ + var cTypes = [{ cType: types.NAME_CTYPE, onUpdate: null, perms: ["pr"], @@ -185,14 +184,14 @@ HomeMaticGenericChannel.prototype = { supportBonjour: false, manfDescription: "Name of service", designedMaxLength: 255 - }] + }]; if (this.type == "SWITCH") { cTypes.push({ cType: types.POWER_STATE_CTYPE, onUpdate: function(value) { - that.command("set", "STATE", (value == 1) ? true : false) + that.command("set", "STATE", (value == 1) ? true : false); }, onRead: function(callback) { @@ -228,37 +227,37 @@ HomeMaticGenericChannel.prototype = { supportBonjour: false, manfDescription: "Is Outlet in Use", designedMaxLength: 1 - }) + }); } } if (this.type == "KEYMATIC") { cTypes.push({ - cType: types.CURRENT_LOCK_MECHANISM_STATE_CTYPE, + cType: types.CURRENT_LOCK_MECHANISM_STATE_CTYPE, - onRead: function(callback) { + onRead: function(callback) { that.query("STATE", callback); }, - onRegister: function(characteristic) { + onRegister: function(characteristic) { that.currentStateCharacteristic["STATE"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("STATE"); }, - perms: ["pr", "ev"], - format: "bool", - initialValue: that.dpvalue("STATE", 0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Current State of your Lock", - designedMaxLength: 1 - }, { + perms: ["pr", "ev"], + format: "bool", + initialValue: that.dpvalue("STATE", 0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current State of your Lock", + designedMaxLength: 1 + }, { cType: types.TARGET_LOCK_MECHANISM_STATE_CTYPE, onUpdate: function(value) { - that.command("set", "STATE", (value == 1) ? "true" : "false") + that.command("set", "STATE", (value == 1) ? "true" : "false"); }, onRead: function(callback) { @@ -280,13 +279,13 @@ HomeMaticGenericChannel.prototype = { supportBonjour: false, manfDescription: "Target State of your Lock", designedMaxLength: 1 - } + }, - , { + { cType: types.TARGET_DOORSTATE_CTYPE, onUpdate: function(value) { - that.command("set", "OPEN", "true") + that.command("set", "OPEN", "true"); }, onRead: function(callback) { @@ -307,17 +306,13 @@ HomeMaticGenericChannel.prototype = { designedMaxLength: 1 } ); - - } - - if (this.type == "DIMMER") { cTypes.push({ cType: types.POWER_STATE_CTYPE, onUpdate: function(value) { - that.command("set", "LEVEL", (value == true) ? "1" : "0") + that.command("set", "LEVEL", (value == true) ? "1" : "0"); }, onRead: function(callback) { @@ -369,29 +364,29 @@ HomeMaticGenericChannel.prototype = { if (this.type == "BLIND") { cTypes.push({ - cType: types.WINDOW_COVERING_CURRENT_POSITION_CTYPE, + cType: types.WINDOW_COVERING_CURRENT_POSITION_CTYPE, - onRead: function(callback) { + onRead: function(callback) { that.query("LEVEL", callback); }, - onRegister: function(characteristic) { + onRegister: function(characteristic) { that.currentStateCharacteristic["LEVEL"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("LEVEL"); }, - perms: ["pr", "ev"], - format: "int", - initialValue: that.dpvalue("LEVEL", 0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Blind Position", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }, + perms: ["pr", "ev"], + format: "int", + initialValue: that.dpvalue("LEVEL", 0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Blind Position", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }, { cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, @@ -497,16 +492,16 @@ HomeMaticGenericChannel.prototype = { if (this.type == "CLIMATECONTROL_RT_TRANSCEIVER") { cTypes.push({ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - }, + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name, + supportEvents: true, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + }, { cType: types.CURRENTHEATINGCOOLING_CTYPE, @@ -550,8 +545,7 @@ HomeMaticGenericChannel.prototype = { characteristic.eventEnabled = true; that.remoteGetValue("ACTUAL_TEMPERATURE"); }, - perms: ["pw", "pr", "ev"], - perms: ["pr"], + perms: ["pr", "ev"], format: "double", initialValue: that.dpvalue("ACTUAL_TEMPERATURE", 20), supportEvents: false, @@ -602,7 +596,7 @@ HomeMaticGenericChannel.prototype = { } - return cTypes + return cTypes; }, sType: function() { @@ -633,12 +627,12 @@ HomeMaticGenericChannel.prototype = { } if (this.type == "MOTION_DETECTOR") { - return types.MOTION_SENSOR_STYPE + return types.MOTION_SENSOR_STYPE; } if (this.type == "KEYMATIC") { - return types.LOCK_MECHANISM_STYPE + return types.LOCK_MECHANISM_STYPE; } @@ -649,12 +643,12 @@ HomeMaticGenericChannel.prototype = { var that = this; var services = [{ sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: this.informationCharacteristics(), + characteristics: this.informationCharacteristics() }, { sType: this.sType(), characteristics: this.controlCharacteristics(that) }]; - this.log("Loaded services for " + this.name) + this.log("Loaded services for " + this.name); return services; } }; From 5f82fa6fb6d7f0e1d54959f4443c4fd430b66025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20K=C3=B6nig?= Date: Thu, 29 Oct 2015 21:04:32 +0100 Subject: [PATCH 62/89] Add HomeMatic Platform sample --- config-sample.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/config-sample.json b/config-sample.json index 0e04cfe..a199ea6 100644 --- a/config-sample.json +++ b/config-sample.json @@ -115,7 +115,15 @@ "username": "your netatmo username", "password": "your netatmo password" } - } + }, + { + "platform": "HomeMatic", + "name": "HomeMatic CCU", + "ccu_ip": "192.168.0.100", + "filter_device":[], + "filter_channel":["BidCos-RF.KEQXXXXXXX:4", "BidCos-RF.LEQXXXXXXX:2"], + "outlets":[ "BidCos-RF.KEQXXXXXXX:4","BidCos-RF.IEQXXXXXXX:1"] + }, ], "accessories": [ From ead491fb4c4bf8f18822e0341837264e689146d5 Mon Sep 17 00:00:00 2001 From: Thomas Kluge Date: Fri, 30 Oct 2015 16:50:31 +0100 Subject: [PATCH 63/89] switch back from xmlrpc branch to master --- platforms/HomeMatic.js | 16 + platforms/HomematicChannel.js | 949 +++++++++++++++++----------------- 2 files changed, 498 insertions(+), 467 deletions(-) diff --git a/platforms/HomeMatic.js b/platforms/HomeMatic.js index a451451..219ebdf 100644 --- a/platforms/HomeMatic.js +++ b/platforms/HomeMatic.js @@ -2,9 +2,25 @@ // // Homematic Platform Shim for HomeBridge // +// to add the homematic platform add this to config.json. Example: +// "platforms": [ +// { +// "platform": "HomeMatic", +// "name": "HomeMatic", +// "filter_device":[], +// "filter_channel":["BidCos-RF.KEQXXXXXXX:4", "BidCos-RF.LEQXXXXXXX:2"], +// "outlets":[ "BidCos-RF.KEQXXXXXXX:4","BidCos-RF.IEQXXXXXXX:1"] +// +// } +// // V0.1 - 2015/10/29 // - initial version // - reintegrated Homematic Platform fork from https://github.com/thkl/homebridge/tree/xmlrpc +// 2015/10/30 thkl +// - added Rotary Sensors ; fixed thermostat + +// ], + var types = require("hap-nodejs/accessories/types.js"); diff --git a/platforms/HomematicChannel.js b/platforms/HomematicChannel.js index 5b6cff2..e2818a5 100644 --- a/platforms/HomematicChannel.js +++ b/platforms/HomematicChannel.js @@ -1,180 +1,185 @@ -"use strict"; var types = require("hap-nodejs/accessories/types.js"); -function HomeMaticGenericChannel(log, platform, id, name, type, adress, special) { - this.name = name; - this.type = type; - this.adress = adress; - this.log = log; +function HomeMaticGenericChannel(log,platform, id ,name, type ,adress,special) { + this.name = name; + this.type = type; + this.adress = adress; + this.log = log; this.platform = platform; - this.state = []; + this.state = []; this.eventupdate = false; - this.special = special; + this.special = special; this.currentStateCharacteristic = []; - this.reverseDP = []; + this.datapointMappings = []; } + HomeMaticGenericChannel.prototype = { - // Return current States - query: function(dp, callback) { - if (this.state[dp] !== undefined) { + addValueMapping: function(dp,value,mappedvalue) { + if (this.datapointMappings[dp]==undefined) { + this.datapointMappings[dp] = []; + } + this.datapointMappings[dp][value] = mappedvalue; + } , + + // Return current States + query: function(dp,callback) { + var that = this; + + if (this.state[dp] != undefined) { callback(this.state[dp]); } else { - // that.log("No cached Value found start fetching and send temp 0 back"); +// that.log("No cached Value found start fetching and send temp 0 back"); this.remoteGetValue(dp); callback(0); } }, - dpvalue: function(dp, fallback) { - if (this.state[dp] !== undefined) { - return (this.state[dp]); + dpvalue:function(dp,fallback) { + if (this.state[dp] != undefined) { + return(this.state[dp]); } else { return fallback; } }, - remoteGetValue: function(dp) { - var that = this; - that.platform.getValue(that.adress, dp, function(newValue) { - that.log("Remote Value Response for " + that.adress + "." + dp + "->" + newValue); - that.eventupdate = true; - that.cache(dp, newValue); - that.eventupdate = false; - }); + remoteGetValue:function(dp) { + var that = this; + that.platform.getValue(that.adress,dp,function(newValue) { + that.log("Remote Value Response for " + that.adress + "." + dp + "->" + newValue); + that.eventupdate = true; + that.cache(dp,newValue); + that.eventupdate = false; + }); }, - - event: function(dp, newValue) { - - if (dp == "LEVEL") { - newValue = newValue * 100; + + event:function(dp,newValue) { + + if (dp=="LEVEL") { + newValue = newValue*100; } this.eventupdate = true; - this.cache(dp, newValue); + this.cache(dp,newValue); this.eventupdate = false; }, - reverse: function(value) { - if (value == "true") return "false"; - if (value == "false") return "true"; - if (value === 0) return 1; - if (value === 1) return 0; - if (value == "0") return "1"; - if (value == "1") return "0"; - return value; - }, - - cache: function(dp, value) { + cache:function(dp,value) { var that = this; - if ((that.reverseDP[dp] !== undefined) && (that.reverseDP[dp] === true)) { - value = that.reverse(value); - } - if (that.currentStateCharacteristic[dp] !== undefined) { - that.currentStateCharacteristic[dp].updateValue(value, null); + // Check custom Mapping from HM to HomeKit + var map = this.datapointMappings[dp]; + if (map != undefined) { + this.log("Mapping found for " + dp); + if (map[value]!=undefined) { + this.log("Mapping found for " + dp + " " + value); + value = map[value]; + } + } + + if (that.currentStateCharacteristic[dp]!=undefined) { + that.currentStateCharacteristic[dp].updateValue(value, null); } this.state[dp] = value; }, - delayed: function(mode, dp, value, delay) { - - if (this.eventupdate === true) { - return; - } - + delayed: function(mode, dp,value,delay) { + + if (this.eventupdate==true) { + return; + } + var timer = this.delayed[delay]; - if (timer) { - clearTimeout(timer); + if( timer ) { + clearTimeout( timer ); } - this.log(this.name + " delaying command " + mode + " " + dp + " with value " + value); + this.log(this.name + " delaying command "+mode + " " + dp +" with value " + value); var that = this; - this.delayed[delay] = setTimeout(function() { - clearTimeout(that.delayed[delay]); - that.command(mode, dp, value); - }, delay ? delay : 100); + this.delayed[delay] = setTimeout( function(){clearTimeout(that.delayed[delay]);that.command(mode,dp,value)}, delay?delay:100 ); }, - command: function(mode, dp, value, callback) { + command: function(mode,dp,value,callback) { + + if (this.eventupdate==true) { + return; + } + var that = this; - if (this.eventupdate === true) { - return; - } - var that = this; - - if (mode == "set") { - //this.log("Send " + value + " to Datapoint " + dp + " at " + that.adress); - that.platform.setValue(that.adress, dp, value); - } + if (mode == "set") { + //this.log("Send " + value + " to Datapoint " + dp + " at " + that.adress); + that.platform.setValue(that.adress,dp,value); + } }, informationCharacteristics: function() { - return [{ - 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: "EQ-3", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - }, { - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.type, - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - }, { - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.adress, - 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 - }]; + return [ + { + 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: "EQ-3", + supportEvents: false, + supportBonjour: false, + manfDescription: "Manufacturer", + designedMaxLength: 255 + },{ + cType: types.MODEL_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.type, + supportEvents: false, + supportBonjour: false, + manfDescription: "Model", + designedMaxLength: 255 + },{ + cType: types.SERIAL_NUMBER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.adress , + 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 = [{ + + cTypes = [{ cType: types.NAME_CTYPE, onUpdate: null, perms: ["pr"], @@ -184,174 +189,183 @@ HomeMaticGenericChannel.prototype = { supportBonjour: false, manfDescription: "Name of service", designedMaxLength: 255 - }]; - - - if (this.type == "SWITCH") { - cTypes.push({ + }] + + + if (this.type=="SWITCH") { + cTypes.push({ cType: types.POWER_STATE_CTYPE, onUpdate: function(value) { - that.command("set", "STATE", (value == 1) ? true : false); + that.command("set","STATE" , (value==1)?true:false) }, onRead: function(callback) { - that.query("STATE", callback); + that.query("STATE",callback); }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["STATE"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("STATE"); + + onRegister: function(characteristic) { + that.currentStateCharacteristic["STATE"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("STATE"); }, - - perms: ["pw", "pr", "ev"], + + perms: ["pw","pr","ev"], format: "bool", - initialValue: that.dpvalue("STATE", 0), + initialValue: that.dpvalue("STATE",0), supportEvents: false, supportBonjour: false, manfDescription: "Change the power state", designedMaxLength: 1 }); - - if (this.special == "OUTLET") { + + if (this.special=="OUTLET") { cTypes.push({ cType: types.OUTLET_IN_USE_CTYPE, - + onRead: function(callback) { - callback(true); + callback(true); }, - perms: ["pr", "ev"], - format: "bool", - initialValue: true, - supportEvents: false, - supportBonjour: false, - manfDescription: "Is Outlet in Use", - designedMaxLength: 1 - }); - } + perms: ["pr","ev"], + format: "bool", + initialValue: true, + supportEvents: false, + supportBonjour: false, + manfDescription: "Is Outlet in Use", + designedMaxLength: 1 + }) + } } - - - if (this.type == "KEYMATIC") { - cTypes.push({ + + + if (this.type=="KEYMATIC") { + cTypes.push( + { cType: types.CURRENT_LOCK_MECHANISM_STATE_CTYPE, - + onRead: function(callback) { - that.query("STATE", callback); - }, - - onRegister: function(characteristic) { + that.query("STATE",callback); + }, + + onRegister: function(characteristic) { that.currentStateCharacteristic["STATE"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("STATE"); - }, + }, - perms: ["pr", "ev"], + perms: ["pr","ev"], format: "bool", - initialValue: that.dpvalue("STATE", 0), + initialValue: that.dpvalue("STATE",0), supportEvents: false, supportBonjour: false, manfDescription: "Current State of your Lock", - designedMaxLength: 1 - }, { - cType: types.TARGET_LOCK_MECHANISM_STATE_CTYPE, - - onUpdate: function(value) { - that.command("set", "STATE", (value == 1) ? "true" : "false"); - }, - - onRead: function(callback) { - that.query("STATE", callback); - }, - - onRegister: function(characteristic) { - that.reverseDP["STATE"] = true; + designedMaxLength: 1 + }, + { + cType: types.TARGET_LOCK_MECHANISM_STATE_CTYPE, + + onUpdate: function(value) { + that.command("set","STATE",(value==1)?"true":"false") + }, + + onRead: function(callback) { + that.query("STATE",callback); + }, + + onRegister: function(characteristic) { + that.addValueMapping("STATE","1",0); + that.addValueMapping("STATE","0",1); that.currentStateCharacteristic["STATE"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("STATE"); - }, - - - perms: ["pw", "pr", "ev"], - format: "bool", - initialValue: that.dpvalue("STATE", 0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Target State of your Lock", - designedMaxLength: 1 }, - { - cType: types.TARGET_DOORSTATE_CTYPE, - - onUpdate: function(value) { - that.command("set", "OPEN", "true"); - }, - - onRead: function(callback) { - callback(1); - }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["OPEN"] = characteristic; - characteristic.eventEnabled = true; - }, - - perms: ["pw", "pr", "ev"], - format: "bool", - initialValue: 1, - supportEvents: false, - supportBonjour: false, - manfDescription: "Open the Lock", - designedMaxLength: 1 - } - ); - } - - if (this.type == "DIMMER") { - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - onUpdate: function(value) { - that.command("set", "LEVEL", (value == true) ? "1" : "0"); + + perms: ["pw","pr","ev"], + format: "bool", + initialValue: that.dpvalue("STATE",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Target State of your Lock", + designedMaxLength: 1 + } + + , + { + cType: types.TARGET_DOORSTATE_CTYPE, + + onUpdate: function(value) { + that.command("set","OPEN" , "true") }, onRead: function(callback) { - that.query("LEVEL", callback); + callback(1); }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["LEVEL"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("LEVEL"); + + onRegister: function(characteristic) { + that.currentStateCharacteristic["OPEN"] = characteristic; + characteristic.eventEnabled = true; }, - - perms: ["pw", "pr", "ev"], + + perms: ["pw","pr","ev"], format: "bool", - initialValue: (that.dpvalue("LEVEL") > 0, 0), + initialValue: 1, + supportEvents: false, + supportBonjour: false, + manfDescription: "Open the Lock", + designedMaxLength: 1 + } + ); + + + } + + + + if (this.type=="DIMMER") { + cTypes.push({ + cType: types.POWER_STATE_CTYPE, + onUpdate: function(value) { + that.command("set","LEVEL" , (value==true) ? "1" : "0") + }, + + onRead: function(callback) { + that.query("LEVEL",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["LEVEL"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("LEVEL"); + }, + + perms: ["pw","pr","ev"], + format: "bool", + initialValue: (that.dpvalue("LEVEL")>0,0), supportEvents: false, supportBonjour: false, manfDescription: "Change the power state", designedMaxLength: 1 - }, { + }, + { cType: types.BRIGHTNESS_CTYPE, onUpdate: function(value) { - that.delayed("set", "LEVEL", String(value / 100), 100); + that.delayed("set","LEVEL" , String(value/100),100); }, - + onRead: function(callback) { - that.query("LEVEL", callback); + that.query("LEVEL",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["LEVEL"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("LEVEL"); }, - onRegister: function(characteristic) { - that.currentStateCharacteristic["LEVEL"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("LEVEL"); - }, - - - perms: ["pw", "pr", "ev"], + + perms: ["pw","pr","ev"], format: "int", - initialValue: that.dpvalue("LEVEL", 0), + initialValue: that.dpvalue("LEVEL",0), supportEvents: false, supportBonjour: false, manfDescription: "Adjust Brightness of Light", @@ -362,23 +376,26 @@ HomeMaticGenericChannel.prototype = { }); } - if (this.type == "BLIND") { - cTypes.push({ + + + if (this.type=="BLIND") { + cTypes.push( + { cType: types.WINDOW_COVERING_CURRENT_POSITION_CTYPE, - + onRead: function(callback) { - that.query("LEVEL", callback); - }, - - onRegister: function(characteristic) { + that.query("LEVEL",callback); + }, + + onRegister: function(characteristic) { that.currentStateCharacteristic["LEVEL"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("LEVEL"); - }, + }, - perms: ["pr", "ev"], + perms: ["pr","ev"], format: "int", - initialValue: that.dpvalue("LEVEL", 0), + initialValue: that.dpvalue("LEVEL",0), supportEvents: false, supportBonjour: false, manfDescription: "Current Blind Position", @@ -387,271 +404,269 @@ HomeMaticGenericChannel.prototype = { designedMinStep: 1, unit: "%" }, + + { + cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, + + onUpdate: function(value) { + that.delayed("set","LEVEL" , String(value/100),100); + }, + - { - cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, - - onUpdate: function(value) { - that.delayed("set", "LEVEL", String(value / 100), 100); - }, - - - onRead: function(callback) { - that.query("LEVEL", callback); - }, - - onRegister: function(characteristic) { + onRead: function(callback) { + that.query("LEVEL",callback); + }, + + onRegister: function(characteristic) { that.currentStateCharacteristic["LEVEL"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("LEVEL"); - }, + }, - perms: ["pw", "pr", "ev"], - format: "int", - initialValue: that.dpvalue("LEVEL", 0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Target Blind Position", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }, { - cType: types.WINDOW_COVERING_OPERATION_STATE_CTYPE, - - onRead: function(callback) { - that.query("DIRECTION", callback); - }, - - onRegister: function(characteristic) { + perms: ["pw","pr","ev"], + format: "int", + initialValue: that.dpvalue("LEVEL",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Target Blind Position", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }, + { + cType: types.WINDOW_COVERING_OPERATION_STATE_CTYPE, + + onRead: function(callback) { + that.query("DIRECTION",callback); + }, + + onRegister: function(characteristic) { that.currentStateCharacteristic["DIRECTION"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("DIRECTION"); - }, - - perms: ["pr", "ev"], - format: "int", - initialValue: that.dpvalue("DIRECTION", 0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Operating State ", - designedMinValue: 0, - designedMaxValue: 2, - designedMinStep: 1 - } + }, + perms: ["pr","ev"], + format: "int", + initialValue: that.dpvalue("DIRECTION",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Operating State ", + designedMinValue: 0, + designedMaxValue: 2, + designedMinStep: 1 + } + ); } - - if (this.type == "SHUTTER_CONTACT") { - cTypes.push({ - cType: types.CONTACT_SENSOR_STATE_CTYPE, - + + // Simple Contact (Magnet) + + if (this.type=="SHUTTER_CONTACT") { + cTypes.push( + { + cType: types.CONTACT_SENSOR_STATE_CTYPE, + onRead: function(callback) { - that.query("STATE", callback); + that.query("STATE",callback); }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["STATE"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("STATE"); + + onRegister: function(characteristic) { + that.currentStateCharacteristic["STATE"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("STATE"); }, - - perms: ["pr", "ev"], - format: "bool", - initialValue: that.dpvalue("STATE", 0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Current State" - }); - } - - if (this.type == "MOTION_DETECTOR") { - cTypes.push({ - cType: types.MOTION_DETECTED_CTYPE, - + + perms: ["pr","ev"], + format: "bool", + initialValue: that.dpvalue("STATE",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current State" + }); + } + + // Rotary Handle + if (this.type=="ROTARY_HANDLE_SENSOR") { + cTypes.push( + { + cType: types.CONTACT_SENSOR_STATE_CTYPE, + onRead: function(callback) { - that.query("MOTION", callback); + that.query("STATE",callback); }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["MOTION"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("MOTION"); + + onRegister: function(characteristic) { + that.addValueMapping("STATE","2",1); + that.currentStateCharacteristic["STATE"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("STATE"); }, - - perms: ["pr", "ev"], - format: "bool", - initialValue: that.dpvalue("MOTION", 0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Motion State" - }); - } - - if (this.type == "CLIMATECONTROL_RT_TRANSCEIVER") { - - cTypes.push({ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 + + perms: ["pr","ev"], + format: "bool", + initialValue: that.dpvalue("STATE",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current State" + }); + } + + + // Motion Detector + + if (this.type=="MOTION_DETECTOR") { + cTypes.push( + { + cType: types.MOTION_DETECTED_CTYPE, + + onRead: function(callback) { + that.query("MOTION",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["MOTION"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("MOTION"); + }, + + perms: ["pr","ev"], + format: "bool", + initialValue: that.dpvalue("MOTION",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Motion State" + }); + } + + // Heating Device + + if ((this.type=="CLIMATECONTROL_RT_TRANSCEIVER") || (this.type=="THERMALCONTROL_TRANSMIT")) { + + cTypes.push({ + cType: types.NAME_CTYPE,onUpdate: null,perms: ["pr"],format: "string", + initialValue: this.name,supportEvents: true,supportBonjour: false,manfDescription: "Name of service",designedMaxLength: 255 + }, + + { + cType: types.CURRENTHEATINGCOOLING_CTYPE,onUpdate: null, + perms: ["pr"],format: "int",initialValue: 1,supportEvents: false, + supportBonjour: false,manfDescription: "Current Mode",designedMaxLength: 1,designedMinValue: 1,designedMaxValue: 1,designedMinStep: 1 + }, + + { + cType: types.TARGETHEATINGCOOLING_CTYPE,onUpdate: null,perms: ["pw","pr"], + format: "int",initialValue: 1,supportEvents: false,supportBonjour: false,manfDescription: "Target Mode", + designedMinValue: 1,designedMaxValue: 1,designedMinStep: 1 + }, + + { + cType: types.CURRENT_TEMPERATURE_CTYPE, + onUpdate: null, + + onRead: function(callback) { + that.query("ACTUAL_TEMPERATURE",callback); }, - - { - cType: types.CURRENTHEATINGCOOLING_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "int", - initialValue: 1, - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Mode", - designedMaxLength: 1, - designedMinValue: 1, - designedMaxValue: 1, - designedMinStep: 1 - }, - - { - cType: types.TARGETHEATINGCOOLING_CTYPE, - onUpdate: null, - perms: ["pw", "pr"], - format: "int", - initialValue: 1, - supportEvents: false, - supportBonjour: false, - manfDescription: "Target Mode", - designedMinValue: 1, - designedMaxValue: 1, - designedMinStep: 1 - }, - - { - cType: types.CURRENT_TEMPERATURE_CTYPE, - onUpdate: null, - - onRead: function(callback) { - that.query("ACTUAL_TEMPERATURE", callback); - }, - - onRegister: function(characteristic) { + + onRegister: function(characteristic) { that.currentStateCharacteristic["ACTUAL_TEMPERATURE"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("ACTUAL_TEMPERATURE"); - }, - perms: ["pr", "ev"], - format: "double", - initialValue: that.dpvalue("ACTUAL_TEMPERATURE", 20), - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Temperature", - unit: "celsius" - }, - - { - cType: types.TARGET_TEMPERATURE_CTYPE, - onUpdate: function(value) { - that.delayed("set", "SET_TEMPERATURE", value, 500); - }, - onRead: function(callback) { - that.query("SET_TEMPERATURE", callback); - - }, - onRegister: function(characteristic) { + }, + perms: ["pw","pr","ev"], perms: ["pr"],format: "double", + initialValue: that.dpvalue("ACTUAL_TEMPERATURE",20), + supportEvents: false,supportBonjour: false,manfDescription: "Current Temperature",unit: "celsius" + }, + + { + cType: types.TARGET_TEMPERATURE_CTYPE, + onUpdate: function(value) { + //that.delayed("set", "SET_TEMPERATURE", value,500); + that.delayed("set", "MANU_MODE", value,500); + }, + onRead: function(callback) { + that.query("SET_TEMPERATURE",callback); + + }, + onRegister: function(characteristic) { that.currentStateCharacteristic["SET_TEMPERATURE"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("SET_TEMPERATURE"); - }, - perms: ["pw", "pr", "ev"], - format: "double", - initialValue: that.dpvalue("SET_TEMPERATURE", 16), - supportEvents: false, - supportBonjour: false, - manfDescription: "Target Temperature", - designedMinValue: 16, - designedMaxValue: 38, - designedMinStep: 1, - unit: "celsius" - }, - - { - cType: types.TEMPERATURE_UNITS_CTYPE, - onRead: null, - perms: ["pr"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Temperature Unit", - unit: "celsius" - } - - ); + }, + perms: ["pw","pr","ev"],format: "double", + initialValue: that.dpvalue("SET_TEMPERATURE",16), + supportEvents: false,supportBonjour: false, manfDescription: "Target Temperature", + designedMinValue: 16,designedMaxValue: 38,designedMinStep: 1,unit: "celsius" + }, + + { + cType: types.TEMPERATURE_UNITS_CTYPE,onRead: null, + perms: ["pr"],format: "int",initialValue: 0,supportEvents: false, + supportBonjour: false,manfDescription: "Current Temperature Unit",unit: "celsius" } - - return cTypes; + ); + } + + + return cTypes }, sType: function() { - - if (this.type == "SWITCH") { - - if (this.special == "OUTLET") { - return types.OUTLET_STYPE; - } else { - return types.LIGHTBULB_STYPE; - } - } - - if (this.type == "DIMMER") { + + if (this.type=="SWITCH") { + + if (this.special=="OUTLET") { + return types.OUTLET_STYPE; + } else { return types.LIGHTBULB_STYPE; - } + } + } + + if (this.type=="DIMMER") { + return types.LIGHTBULB_STYPE; + } - if (this.type == "BLIND") { + if (this.type=="BLIND") { return types.WINDOW_COVERING_STYPE; - } + } - if (this.type == "CLIMATECONTROL_RT_TRANSCEIVER") { + if ((this.type=="CLIMATECONTROL_RT_TRANSCEIVER") || (this.type=="THERMALCONTROL_TRANSMIT")) { return types.THERMOSTAT_STYPE; - } - - if (this.type == "SHUTTER_CONTACT") { + } + + if ((this.type=="SHUTTER_CONTACT") ||(this.type=="ROTARY_HANDLE_SENSOR")) { return types.CONTACT_SENSOR_STYPE; - } - - if (this.type == "MOTION_DETECTOR") { - return types.MOTION_SENSOR_STYPE; - } - - - if (this.type == "KEYMATIC") { - return types.LOCK_MECHANISM_STYPE; - } - + } + + if (this.type=="MOTION_DETECTOR") { + return types.MOTION_SENSOR_STYPE + } + if (this.type=="KEYMATIC") { + return types.LOCK_MECHANISM_STYPE + } + + + }, getServices: function() { var that = this; var services = [{ sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: this.informationCharacteristics() - }, { + characteristics: this.informationCharacteristics(), + }, + { sType: this.sType(), characteristics: this.controlCharacteristics(that) }]; - this.log("Loaded services for " + this.name); + this.log("Loaded services for " + this.name) return services; } }; -module.exports = HomeMaticGenericChannel; \ No newline at end of file +module.exports = HomeMaticGenericChannel; From ea1f75abb0da4c17e0a7ca27180cde5754a8a319 Mon Sep 17 00:00:00 2001 From: Thomas Kluge Date: Fri, 30 Oct 2015 18:13:31 +0100 Subject: [PATCH 64/89] setup.sh complete installation on a pi2 --- setup.sh | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 setup.sh diff --git a/setup.sh b/setup.sh new file mode 100644 index 0000000..8bb9164 --- /dev/null +++ b/setup.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env + +# Check if we can use colours in our output +use_colour=0 +[ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null && use_colour=1 + +# Some useful functions +progress() { + [ $use_colour -eq 1 ] && echo -ne "\033[01;32m" + echo "$@" >&2 + [ $use_colour -eq 1 ] && echo -ne "\033[00m" +} + +info() { + [ $use_colour -eq 1 ] && echo -ne "\033[01;34m" + echo "$@" >&2 + [ $use_colour -eq 1 ] && echo -ne "\033[00m" +} + +die () { + [ $use_colour -eq 1 ] && echo -ne "\033[01;31m" + echo "$@" >&2 + [ $use_colour -eq 1 ] && echo -ne "\033[00m" + exit 1 +} + +install_package() { + package=$1 + info "install ${package}" + sudo apt-get -y --force-yes install $package 2>&1 > /dev/null + return $? +} + +# check architecture +sudo test "`dpkg --print-architecture`" == "armhf" || die "This Repos is only for armhf." + +# set timezone and update system +info "Setting up locale and keyboard" +sudo dpkg-reconfigure locales + +TIMEZONE="Europe/Berlin" +echo $TIMEZONE | sudo tee /etc/timezone +sudo cp /usr/share/zoneinfo/${TIMEZONE} /etc/localtime +sudo dpkg-reconfigure -f noninteractive tzdata + +info "Setting up Hostname" +echo 'Homebridge' | sudo tee /etc/hostname + +info "Cleaning up" +sudo dpkg --configure -a + +info "Update Package Lists this may take some time (10-20 min) depending on your internet connection" +sudo apt-get update -y +sudo apt-get dist-upgrade -y +info "Done" + +info "Installing Zeroconf" + +install_package "libavahi-compat-libdnssd-dev" +install_package "gcc-4.8 g++-4.8" +install_package "libkrb5-dev" + +info "Installing node" +wget https://s3-eu-west-1.amazonaws.com/conoroneill.net/wp-content/uploads/2015/03/node-v0.12.1-linux-arm-pi.tar.gz +tar -zxvf node-v0.12.1-linux-arm-pi.tar.gz +cd node-v0.12.1-linux-arm-pi +sudo cp -R * /usr/local/ + + +info "Cloning Repository" +cd /home/pi +git clone -b master --single-branch https://github.com/thkl/homebridge.git +cd homebridge + +info "Installing Node Modules" +npm install + +info "Setup" + +hazconfig="$(cat /home/pi/homebridge/config.json| grep 'bridge' | wc -l)" +if [ "$hazconfig" = "0" ]; then + + CCUIP=$(whiptail --inputbox "Please enter your CCU IP" 20 60 "000.000.000.000" 3>&1 1>&2 2>&3) + if [ $? -eq 0 ]; then + echo "{\"bridge\": {\"name\": \"Homebridge\", \"username\": \"CC:22:3D:E3:CE:30\",\"port\": 51826,\"pin\": \"031-45-154\"}," >> /home/pi/homebridge/config.json; + echo "\"description\": \"This is an autogenerated config. only the homematic platform is enabled. see the sample for more\"," >> /home/pi/homebridge/config.json; + echo "\"platforms\": [" >> /home/pi/homebridge/config.json; + echo "{\"platform\": \"HomeMaticPlatform\",\"name\": \"HomeMatic CCU\",\"ccu_ip\": \"$CCUIP\"," >> /home/pi/homebridge/config.json; + echo "\"filter_device\":[],\"filter_channel\":[],\"outlets\":[]}" >> /home/pi/homebridge/config.json; + echo "],\"accessories\": []}" >> /home/pi/homebridge/config.json; + fi +fi + +whiptail --yesno "Would you like to start homebridge at boot by default?" $DEFAULT 20 60 2 +RET=$? +if [ $RET -eq 0 ]; then + sudo cp /home/pi/homebridge/homebridge.txt /etc/init.d/homebridge + sudo chmod 755 /etc/init.d/homebridge + sudo update-rc.d homebridge defaults +fi + +info "Done. If there are no error messages you are done." +info "Your config is ready to use" +info "to start the homebridge goto /home/pi/homebridge and call npm run start." From 727809e9b4ff188cf8325a0ae18990fe2234f676 Mon Sep 17 00:00:00 2001 From: Thomas Kluge Date: Fri, 30 Oct 2015 18:30:07 +0100 Subject: [PATCH 65/89] removed some logs changed to autodetection if hm channel is supported yet --- platforms/HomeMatic.js | 9 +++++---- platforms/HomematicChannel.js | 4 +--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/platforms/HomeMatic.js b/platforms/HomeMatic.js index 219ebdf..30244e5 100644 --- a/platforms/HomeMatic.js +++ b/platforms/HomeMatic.js @@ -308,14 +308,15 @@ HomeMaticPlatform.prototype = { // that.log('name', ch.name, ' -> address:', ch.address); if ((ch.address !== undefined) && (!isChannelFiltered)) { - if ((ch.type == "SWITCH") || (ch.type == "BLIND") || (ch.type == "SHUTTER_CONTACT") || (ch.type == "DIMMER") || (ch.type == "CLIMATECONTROL_RT_TRANSCEIVER") ||  (ch.type == "MOTION_DETECTOR") ||  (ch.type == "KEYMATIC")) { + // Switch found // Check if marked as Outlet var special = (that.outlets.indexOf(ch.address) > -1) ? "OUTLET" : undefined; var accessory = new HomeMaticGenericChannel(that.log, that, ch.id, ch.name, ch.type, ch.address, special); - that.foundAccessories.push(accessory); - } - + if (accessory.sType()!=undefined) { + // support exists for this channel + that.foundAccessories.push(accessory); + } } else { that.log(device.name + " has no address"); diff --git a/platforms/HomematicChannel.js b/platforms/HomematicChannel.js index e2818a5..2322fed 100644 --- a/platforms/HomematicChannel.js +++ b/platforms/HomematicChannel.js @@ -78,9 +78,7 @@ HomeMaticGenericChannel.prototype = { // Check custom Mapping from HM to HomeKit var map = this.datapointMappings[dp]; if (map != undefined) { - this.log("Mapping found for " + dp); if (map[value]!=undefined) { - this.log("Mapping found for " + dp + " " + value); value = map[value]; } } @@ -636,7 +634,7 @@ HomeMaticGenericChannel.prototype = { return types.THERMOSTAT_STYPE; } - if ((this.type=="SHUTTER_CONTACT") ||(this.type=="ROTARY_HANDLE_SENSOR")) { + if ((this.type=="SHUTTER_CONTACT") || (this.type=="ROTARY_HANDLE_SENSOR")) { return types.CONTACT_SENSOR_STYPE; } From 255a064c081bd796cd913e8acee4b46c1acdb5ec Mon Sep 17 00:00:00 2001 From: Kevin Mathy Date: Fri, 30 Oct 2015 22:55:53 +0100 Subject: [PATCH 66/89] Http Accessory improvements : accessory type (switch or light), and ligtht brightness handling toggle --- accessories/Http.js | 180 ++++++++++++++++++++++++-------------------- 1 file changed, 97 insertions(+), 83 deletions(-) diff --git a/accessories/Http.js b/accessories/Http.js index d64560f..a5475a7 100644 --- a/accessories/Http.js +++ b/accessories/Http.js @@ -3,109 +3,123 @@ var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); module.exports = { - accessory: HttpAccessory + accessory: HttpAccessory } function HttpAccessory(log, config) { - this.log = log; + this.log = log; - // url info - this.on_url = config["on_url"]; - this.off_url = config["off_url"]; - this.brightness_url = config["brightness_url"]; - this.http_method = config["http_method"]; - this.username = config["username"]; - this.password = config["password"]; + // url info + this.on_url = config["on_url"]; + this.off_url = config["off_url"]; + this.brightness_url = config["brightness_url"]; + this.http_method = config["http_method"]; + this.username = config["username"]; + this.password = config["password"]; + this.service = config["service"] || "Switch"; + this.name = config["name"]; + this.brightnessHandling = config["brightnessHandling"] || "no"; } HttpAccessory.prototype = { - httpRequest: function(url, method, username, password, callback) { - request({ - url: url, - method: method, - auth: { - user: username, - pass: password, - sendImmediately: false - } - }, - function (error, response, body) { - callback(error, response, body) - }) - }, + httpRequest: function(url, method, username, password, callback) { + request({ + url: url, + method: method, + auth: { + user: username, + pass: password, + sendImmediately: false + } + }, + function(error, response, body) { + callback(error, response, body) + }) + }, - setPowerState: function(powerOn, callback) { - var url; + setPowerState: function(powerOn, callback) { + var url; - if (powerOn) { - url = this.on_url; - this.log("Setting power state to on"); - } - else { - url = this.off_url; - this.log("Setting power state to off"); - } + if (powerOn) { + url = this.on_url; + this.log("Setting power state to on"); + } else { + url = this.off_url; + this.log("Setting power state to off"); + } - this.httpRequest(url, this.http_method, this.username, this.password, function(error, response, body) { - if (error) { - this.log('HTTP power function failed: %s', error.message); - callback(error); - } - else { - this.log('HTTP power function succeeded!'); - this.log(response); - this.log(body); - this.log(this.username); - this.log(this.password); - callback(); - } - }.bind(this)); - }, + this.httpRequest(url, this.http_method, this.username, this.password, function(error, response, body) { + if (error) { + this.log('HTTP power function failed: %s', error.message); + callback(error); + } else { + this.log('HTTP power function succeeded!'); + this.log(response); + this.log(body); + this.log(this.username); + this.log(this.password); + callback(); + } + }.bind(this)); + }, - setBrightness: function(level, callback) { - var url = this.brightness_url.replace("%b", level) + setBrightness: function(level, callback) { + var url = this.brightness_url.replace("%b", level) - this.log("Setting brightness to %s", level); + this.log("Setting brightness to %s", level); - this.httpRequest(url, this.http_method, this.username, this.password, function(error, response, body) { - if (error) { - this.log('HTTP brightness function failed: %s', error); - callback(error); - } - else { - this.log('HTTP brightness function succeeded!'); - callback(); - } - }.bind(this)); - }, + this.httpRequest(url, this.http_method, this.username, this.password, function(error, response, body) { + if (error) { + this.log('HTTP brightness function failed: %s', error); + callback(error); + } else { + this.log('HTTP brightness function succeeded!'); + callback(); + } + }.bind(this)); + }, - identify: function(callback) { - this.log("Identify requested!"); - callback(); // success - }, + identify: function(callback) { + this.log("Identify requested!"); + callback(); // success + }, - getServices: function() { + 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(); + // 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 Model") - .setCharacteristic(Characteristic.SerialNumber, "HTTP Serial Number"); + informationService + .setCharacteristic(Characteristic.Manufacturer, "HTTP Manufacturer") + .setCharacteristic(Characteristic.Model, "HTTP Model") + .setCharacteristic(Characteristic.SerialNumber, "HTTP Serial Number"); - var lightbulbService = new Service.Lightbulb(); + if (this.service == "Switch") { + var switchService = new Service.Switch(this.name); - lightbulbService - .getCharacteristic(Characteristic.On) - .on('set', this.setPowerState.bind(this)); + switchService + .getCharacteristic(Characteristic.On) + .on('get', this.getPowerOn.bind(this)) + .on('set', this.setPowerOn.bind(this)); - lightbulbService - .addCharacteristic(new Characteristic.Brightness()) - .on('set', this.setBrightness.bind(this)); + return [switchService]; + } else if (this.service == "Light") { + var lightbulbService = new Service.Lightbulb(this.name); - return [informationService, lightbulbService]; - } + lightbulbService + .getCharacteristic(Characteristic.On) + .on('set', this.setPowerState.bind(this)); + + if (this.brightnessHandling == "yes") { + + lightbulbService + .addCharacteristic(new Characteristic.Brightness()) + .on('set', this.setBrightness.bind(this)); + } + + return [informationService, lightbulbService]; + } + } }; From bee6d03d80826ec6533652d70030ad83d31257b7 Mon Sep 17 00:00:00 2001 From: Kevin Mathy Date: Fri, 30 Oct 2015 23:02:46 +0100 Subject: [PATCH 67/89] New fields for Http Accessory --- config-sample.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/config-sample.json b/config-sample.json index 0e04cfe..a2a0d5f 100644 --- a/config-sample.json +++ b/config-sample.json @@ -195,7 +195,11 @@ "on_url": "https://192.168.1.22:3030/devices/23222/on", "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" + "username": "", + "password": "", + "http_method": "POST", + "service": "Switch", + "brightnessHandling": "no" }, { "accessory": "HttpHygrometer", From ec015e58a32b302121f1894bd85f91e19b159861 Mon Sep 17 00:00:00 2001 From: bwilliot Date: Sat, 31 Oct 2015 11:30:01 +0100 Subject: [PATCH 68/89] Update PhilipsHue.js fix issue --- platforms/PhilipsHue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 9d3394c..81f36b8 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -239,7 +239,7 @@ PhilipsHueAccessory.prototype = { if (err.code == "ECONNRESET") { setTimeout(function() { this.executeChange(characteristic, value, callback); - }, 300); + }.bind(this), 300); } else { this.log(err); callback(new Error(err)); From 04f48ecbaba6d40f0c76ae899f5bf45dc683bd26 Mon Sep 17 00:00:00 2001 From: Thomas Kluge Date: Sat, 31 Oct 2015 12:02:06 +0100 Subject: [PATCH 69/89] fixed thermostats --- platforms/HomeMatic.js | 6 ++++++ platforms/HomematicChannel.js | 22 ++++++++++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/platforms/HomeMatic.js b/platforms/HomeMatic.js index 30244e5..f140f3d 100644 --- a/platforms/HomeMatic.js +++ b/platforms/HomeMatic.js @@ -360,6 +360,12 @@ HomeMaticPlatform.prototype = { }, + setRegaValue: function(channel, datapoint, value) { + var rega = new RegaRequest(this.log, this.ccuIP); + rega.setValue(channel, datapoint, value); + return; + }, + getValue: function(channel, datapoint, callback) { if (channel.indexOf("BidCos-RF.") > -1)  { diff --git a/platforms/HomematicChannel.js b/platforms/HomematicChannel.js index 2322fed..786b556 100644 --- a/platforms/HomematicChannel.js +++ b/platforms/HomematicChannel.js @@ -32,11 +32,11 @@ HomeMaticGenericChannel.prototype = { var that = this; if (this.state[dp] != undefined) { - callback(this.state[dp]); + if (callback!=undefined){callback(this.state[dp]);} } else { // that.log("No cached Value found start fetching and send temp 0 back"); this.remoteGetValue(dp); - callback(0); + if (callback!=undefined){callback(0);} } }, @@ -107,16 +107,22 @@ HomeMaticGenericChannel.prototype = { }, command: function(mode,dp,value,callback) { - + if (this.eventupdate==true) { return; } var that = this; if (mode == "set") { - //this.log("Send " + value + " to Datapoint " + dp + " at " + that.adress); + this.log("Send " + value + " to Datapoint " + dp + " at " + that.adress); that.platform.setValue(that.adress,dp,value); } + + if (mode == "setrega") { + this.log("Send " + value + " to Datapoint " + dp + " at " + that.adress); + that.platform.setRegaValue(that.adress,dp,value); + } + }, informationCharacteristics: function() { @@ -580,11 +586,15 @@ HomeMaticGenericChannel.prototype = { { cType: types.TARGET_TEMPERATURE_CTYPE, onUpdate: function(value) { - //that.delayed("set", "SET_TEMPERATURE", value,500); - that.delayed("set", "MANU_MODE", value,500); + if (that.state["CONTROL_MODE"]!=1) { + that.delayed("setrega", "MANU_MODE",value,500); + } else { + that.delayed("set", "SET_TEMPERATURE", value,500); + } }, onRead: function(callback) { that.query("SET_TEMPERATURE",callback); + that.query("CONTROL_MODE",undefined); }, onRegister: function(characteristic) { From cfed5d7a6648a707dc4fbbc19b26c139977efbf7 Mon Sep 17 00:00:00 2001 From: pedroserano Date: Sat, 31 Oct 2015 09:22:12 -0400 Subject: [PATCH 70/89] Fix for Issue 343 Fix for Issue 343 as described here: https://github.com/nfarina/homebridge/issues/343 --- platforms/Nest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platforms/Nest.js b/platforms/Nest.js index 5fc45d3..1e81fa2 100644 --- a/platforms/Nest.js +++ b/platforms/Nest.js @@ -69,7 +69,7 @@ function NestThermostatAccessory(log, name, device, deviceId, initialData) { this.device = device; var id = uuid.generate('nest.thermostat.' + deviceId); - Accessory.call(this, name, id); + Accessory.call(this, this.name, id); this.uuid_base = id; this.currentData = initialData; From 48bec8a1bc805cceafd589e277d74283e5817840 Mon Sep 17 00:00:00 2001 From: Stefan Kuper Date: Sat, 31 Oct 2015 14:56:24 +0100 Subject: [PATCH 71/89] new API and added noise and atmospheric pressure --- platforms/Netatmo.js | 455 +++++++++++++++++++++---------------------- 1 file changed, 225 insertions(+), 230 deletions(-) diff --git a/platforms/Netatmo.js b/platforms/Netatmo.js index fe532bd..ca10f10 100644 --- a/platforms/Netatmo.js +++ b/platforms/Netatmo.js @@ -8,6 +8,7 @@ // { // "platform": "Netatmo", // "name": "Netatmo Weather", +// "ttl": 10, // "auth": { // "client_id": "", // "client_secret": "", @@ -19,32 +20,99 @@ // // The default code for all HomeBridge accessories is 031-45-154. -var types = require("hap-nodejs/accessories/types.js"); +var DEFAULT_CACHE_TTL = 10; // 10 seconds caching - use config["ttl"] to override -////////////////////////////////////////////////////////////////////////////// -// DECLARE SOME UUIDS WHICH SHOUL BE IN HAP-NODEJS TYPES LIB, BUT ARE NOT YET -// REMOVE WHEN HAP LIB IS UPDATED!! -////////////////////////////////////////////////////////////////////////////// -var stPre = "000000"; -var stPost = "-0000-1000-8000-0026BB765291"; +// CUSTOM SERVICE AND CHARACTERISTIC IDS +var ATMOSPHERIC_PRESSURE_STYPE_ID = "B77831FD-D66A-46A4-B66D-FD7EE8DFE3CE"; +var ATMOSPHERIC_PRESSURE_CTYPE_ID = "28FDA6BC-9C2A-4DEA-AAFD-B49DB6D155AB"; +var NOISE_LEVEL_STYPE_ID = "8C85FD40-EB20-45EE-86C5-BCADC773E580"; +var NOISE_LEVEL_CTYPE_ID = "2CD7B6FD-419A-4740-8995-E3BFE43735AB"; -types.BATTERY_SERVICE_STYPE = stPre + "96" + stPost; -types.AIR_QUALITY_SENSOR_STYPE = stPre + "8D" + stPost; -types.CARBON_DIOXIDE_SENSOR_STYPE = stPre + "97" + stPost; +var Service; +try { + Service = require("hap-nodejs").Service; +} catch(err) { + Service = require("HAP-NodeJS").Service; +} -types.AIR_PARTICULATE_DENISITY_CTYPE = stPre + "64" + stPost; -types.CARBON_DIOXIDE_DETECTED_CTYPE = stPre + "92" + stPost; -types.CARBON_DIOXIDE_LEVEL_CTYPE = stPre + "93" + stPost; -types.AIR_QUALITY_CTYPE = stPre + "95" + stPost; -////////////////////////////////////////////////////////////////////////////// +var Characteristic; +try { + Characteristic = require("hap-nodejs").Characteristic; +} catch(err) { + Characteristic = require("HAP-NodeJS").Characteristic; +} var netatmo = require("netatmo"); var NodeCache = require("node-cache"); +var inherits = require('util').inherits; -function NetAtmoRepository(log, api) { +Characteristic.AtmosphericPressureLevel = function() { + Characteristic.call(this, 'Atmospheric Pressure', ATMOSPHERIC_PRESSURE_CTYPE_ID); + this.setProps({ + format: Characteristic.Formats.UINT8, + unit: "mbar", + minValue: 800, + maxValue: 1200, + minStep: 1, + perms: [ + Characteristic.Perms.READ, + Characteristic.Perms.NOTIFY + ] + }); + this.value = this.getDefaultValue(); +}; +inherits(Characteristic.AtmosphericPressureLevel, Characteristic); +Characteristic.NoiseLevel = function() { + Characteristic.call(this, 'Noise Level', NOISE_LEVEL_CTYPE_ID); + this.setProps({ + format: Characteristic.Formats.UINT8, + unit: "dB", + minValue: 0, + maxValue: 200, + minStep: 1, + perms: [ + Characteristic.Perms.READ, + Characteristic.Perms.NOTIFY + ] + }); + this.value = this.getDefaultValue(); +}; +inherits(Characteristic.NoiseLevel, Characteristic); + +Service.AtmosphericPressureSensor = function(displayName, subtype) { + Service.call(this, displayName, ATMOSPHERIC_PRESSURE_STYPE_ID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.AtmosphericPressureLevel); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.StatusActive); + this.addOptionalCharacteristic(Characteristic.StatusFault); + this.addOptionalCharacteristic(Characteristic.StatusLowBattery); + this.addOptionalCharacteristic(Characteristic.StatusTampered); + this.addOptionalCharacteristic(Characteristic.Name); +}; +inherits(Service.AtmosphericPressureSensor, Service); + +Service.NoiseLevelSensor = function(displayName, subtype) { + Service.call(this, displayName, NOISE_LEVEL_STYPE_ID, subtype); + + // Required Characteristics + this.addCharacteristic(Characteristic.NoiseLevel); + + // Optional Characteristics + this.addOptionalCharacteristic(Characteristic.StatusActive); + this.addOptionalCharacteristic(Characteristic.StatusFault); + this.addOptionalCharacteristic(Characteristic.StatusLowBattery); + this.addOptionalCharacteristic(Characteristic.StatusTampered); + this.addOptionalCharacteristic(Characteristic.Name); +}; +inherits(Service.NoiseLevelSensor, Service); + +function NetAtmoRepository(log, api, ttl) { this.api = api; this.log = log; - this.cache = new NodeCache(); + this.cache = new NodeCache( { stdTTL: ttl } ); } NetAtmoRepository.prototype = { @@ -71,7 +139,7 @@ NetAtmoRepository.prototype = { } } - that.cache.set( "datasource", datasource, 20 ); + that.cache.set( "datasource", datasource ); callback(datasource); }); }, @@ -92,7 +160,8 @@ NetAtmoRepository.prototype = { function NetatmoPlatform(log, config) { this.log = log; var api = new netatmo(config["auth"]); - this.repository = new NetAtmoRepository(this.log, api); + var ttl = typeof config["ttl"] !== 'undefined' ? config["ttl"] : DEFAULT_CACHE_TTL; + this.repository = new NetAtmoRepository(this.log, api, ttl); api.on("error", function(error) { this.log('ERROR - Netatmo: ' + error); }); @@ -124,7 +193,7 @@ function NetatmoAccessory(log, repository, device) { this.deviceId = device._id; this.name = device.module_name this.serial = device._id; - + this.firmware = device.firmware; this.model = device.type; this.serviceTypes = device.data_type; if (device.battery_vp) { @@ -141,243 +210,169 @@ NetatmoAccessory.prototype = { }); }, - getCurrentTemperature: function(callback) { + identify: function(callback) { + this.log("Identify requested!"); + callback(); // success + }, + + currentTemperature: function (callback) { this.getData(function(deviceData) { - callback(deviceData.dashboard_data.Temperature); - }); - }, +/* + if (error) { + callback(error); + } else { +*/ + callback(null, deviceData.dashboard_data.Temperature); + }.bind(this)); + }, - getCurrentHumidity: function(callback) { + currentRelativeHumidity: function(callback) { this.getData(function(deviceData) { - callback(deviceData.dashboard_data.Humidity); - }); + callback(null, deviceData.dashboard_data.Humidity); + }.bind(this)); }, - getAirQuality: function(callback) { + carbonDioxideDetected: function(callback) { + var that = this; + that.log ("getting CO2" + Characteristic.CarbonDioxideDetected.CO2_LEVELS_ABNORMAL); + this.getData(function(deviceData) { - var level = deviceData.dashboard_data.CO2; - var quality = 0; - if (level > 2000) quality = 5; - else if (level > 1500) quality = 4; - else if (level > 1000) quality = 3; - else if (level > 500) quality = 2; - else if (level > 250) quality = 1; - callback(quality); - }); + var result = (deviceData.dashboard_data.CO2 > 1000 ? Characteristic.CarbonDioxideDetected.CO2_LEVELS_ABNORMAL : Characteristic.CarbonDioxideDetected.CO2_LEVELS_NORMAL); + callback(null, result); + }.bind(this)); }, - getCurrentCO2Level: function(callback) { - this.log("fetching co2"); + + carbonDioxideLevel: function(callback) { this.getData(function(deviceData) { - callback(deviceData.dashboard_data.CO2); - }); + callback(null, deviceData.dashboard_data.CO2); + }.bind(this)); }, - informationCharacteristics: function() { - return [ - { - 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: "Netatmo", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.model, - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.serial, - 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 - } - ] + airQuality: function(callback) { + this.getData(function(deviceData) { + var level = deviceData.dashboard_data.CO2; + var quality = Characteristic.AirQuality.UNKNOWN; + if (level > 2000) quality = Characteristic.AirQuality.POOR; + else if (level > 1500) quality = Characteristic.AirQuality.INFERIOR; + else if (level > 1000) quality = Characteristic.AirQuality.FAIR; + else if (level > 500) quality = Characteristic.AirQuality.GOOD; + else if (level > 250) quality = Characteristic.AirQuality.EXCELLENT; + callback(null, quality); + }.bind(this)); }, - humidityCharacteristics: function(that) { - var cTypes = [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name +" Humidity", - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.CURRENT_RELATIVE_HUMIDITY_CTYPE, - onRead: function(callback) { that.getCurrentHumidity(callback); }, - onUpdate: null, - perms: ["pr","ev"], - format: "int", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Humidity" - }]; - return cTypes; + batteryLevel: function(callback) { + this.getData(function(deviceData) { + var charge = deviceData.battery_vp; + var level = charge < 3000 ? 0 : (charge - 3000)/30; + callback(null, level); + }.bind(this)); }, - temperatureCharacteristics: function(that) { - var cTypes = [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name + " Temperature", - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.CURRENT_TEMPERATURE_CTYPE, - onRead: function(callback) { that.getCurrentTemperature(callback); }, - onUpdate: null, - perms: ["pr","ev"], - format: "float", - initialValue: 0.0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Temperature", - unit: "celsius" - }]; - return cTypes; + statusLowBattery: function(callback) { + this.getData(function(deviceData) { + var charge = deviceData.battery_vp; + var level = charge < 4600 ? Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; + callback(null, level); + }.bind(this)); }, - co2Characteristics: function(that) { - var cTypes = [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name + "Carbon Dioxide Level", - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.CARBON_DIOXIDE_DETECTED_CTYPE, - //onRead: function(callback) { that.getCurrentTemperature(callback); }, - onRead: function(callback) { callback(0); }, - onUpdate: null, - perms: ["pr","ev"], - format: "uint8", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "CO2 detected" - },{ - cType: types.CARBON_DIOXIDE_LEVEL_CTYPE, - onRead: function(callback) { that.getCurrentCO2Level(callback); }, - onUpdate: null, - perms: ["pr","ev"], - format: "float", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "CO2 level " - }]; - return cTypes; + atmosphericPressure: function(callback) { + this.getData(function(deviceData) { + callback(null, deviceData.dashboard_data.Pressure); + }.bind(this)); }, - airQualityCharacteristics: function(that) { - var cTypes = [{ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name + " Air Quality", - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - },{ - cType: types.AIR_QUALITY_CTYPE, - onRead: function(callback) { that.getAirQuality(callback); }, - onUpdate: null, - perms: ["pr","ev"], - format: "float", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Air Quality", - }]; - return cTypes; + noiseLevel: function(callback) { + this.getData(function(deviceData) { + callback(null, deviceData.dashboard_data.Noise); + }.bind(this)); }, - getServices: function() { var that = this; - var services = [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: this.informationCharacteristics(), - }]; + var services = []; + + this.log("creating services for " + this.name) + + // INFORMATION /////////////////////////////////////////////////// + + var informationService = new Service.AccessoryInformation(); + var firmwareCharacteristic = informationService.getCharacteristic(Characteristic.FirmwareRevision) + || informationService.addCharacteristic(Characteristic.FirmwareRevision); + services.push( informationService ); + + informationService + .setCharacteristic(Characteristic.Manufacturer, "Netatmo") + .setCharacteristic(Characteristic.Model, this.model) + .setCharacteristic(Characteristic.Name, this.name) + .setCharacteristic(Characteristic.SerialNumber, this.serial) + .setCharacteristic(Characteristic.FirmwareRevision, this.firmware); // TEMPERATURE ////////////////////////////////////////////////// if (this.serviceTypes.indexOf("Temperature") > -1) { - var tempSensorSvc = { - sType: types.TEMPERATURE_SENSOR_STYPE, - characteristics: this.temperatureCharacteristics(that) - } - services.push(tempSensorSvc); - } - // HUMIDITY //////////////////////////////////////////////////// - if (this.serviceTypes.indexOf("Humidity") > -1) { - services.push({ - sType: types.HUMIDITY_SENSOR_STYPE, - characteristics: this.humidityCharacteristics(that) - }); - } - // CO2 SENSOR ///////////////////////////////////////////////// - if (this.serviceTypes.indexOf("CO2") > -1) { - services.push({ - sType: types.CARBON_DIOXIDE_SENSOR_STYPE, - characteristics: this.co2Characteristics(that) - }); - services.push({ - sType: types.AIR_QUALITY_SENSOR_STYPE, - characteristics: this.airQualityCharacteristics(that) - }); + var temperatureSensor = new Service.TemperatureSensor(this.name + " Temperature"); + services.push( temperatureSensor ); + temperatureSensor.getCharacteristic(Characteristic.CurrentTemperature) + .on('get', this.currentTemperature.bind(this)); } - // TODO: Pressure - // TODO: Noise - // TODO: Battery + // HUMIDITY //////////////////////////////////////////////////// + if (this.serviceTypes.indexOf("Humidity") > -1) { + var humiditySensor = new Service.HumiditySensor(this.name + " Humidity"); + services.push( humiditySensor ); + humiditySensor.getCharacteristic(Characteristic.CurrentRelativeHumidity) + .on('get', this.currentRelativeHumidity.bind(this)); + } + + + // CO2 SENSOR ///////////////////////////////////////////////// + if (this.serviceTypes.indexOf("CO2") > -1) { + var carbonDioxideSensor = new Service.CarbonDioxideSensor(this.name + " Carbon Dioxide"); + var carbonDioxideLevelCharacteristic = carbonDioxideSensor.getCharacteristic(Characteristic.CarbonDioxideLevel) + || carbonDioxideSensor.addCharacteristic(Characteristic.CarbonDioxideLevel); + + services.push( carbonDioxideSensor ); + carbonDioxideSensor.getCharacteristic(Characteristic.CarbonDioxideDetected) + .on('get', this.carbonDioxideDetected.bind(this)); + carbonDioxideLevelCharacteristic + .on('get', this.carbonDioxideLevel.bind(this)); + + var airQualitySensor = new Service.AirQualitySensor(this.name + " Air Quality"); + services.push( airQualitySensor ); + airQualitySensor.getCharacteristic(Characteristic.AirQuality) + .on('get', this.airQuality.bind(this)); + } + + // BATTERY SERVICE //////////////////////////////////////////// + + if (this.serviceTypes.indexOf("Battery") > -1) { + var batteryService = new Service.BatteryService(this.name + " Battery Level"); + services.push( batteryService ); + batteryService.getCharacteristic(Characteristic.BatteryLevel) + .on('get', this.batteryLevel.bind(this)); + batteryService.getCharacteristic(Characteristic.StatusLowBattery) + .on('get', this.statusLowBattery.bind(this)); + } + + // ATMOSPHERIC PRESSURE ///////////////////////////////////////////////////// + + if (this.serviceTypes.indexOf("Pressure") > -1) { + var atmosphericPressureSensor = new Service.AtmosphericPressureSensor(this.name + " Atmospheric Pressure"); + services.push( atmosphericPressureSensor ); + atmosphericPressureSensor.getCharacteristic(Characteristic.AtmosphericPressureLevel) + .on('get', this.atmosphericPressure.bind(this)); + } + + // NOISE LEVEL ////////////////////////////////////////////////////////////// + + if (this.serviceTypes.indexOf("Noise") > -1) { + var noiseLevelSensor = new Service.NoiseLevelSensor(this.name + " Noise Level"); + services.push( noiseLevelSensor ); + noiseLevelSensor.getCharacteristic(Characteristic.NoiseLevel) + .on('get', this.noiseLevel.bind(this)); + } + // TODO: Check Elgato Eve Characteristics (map min, max, time series, etc.)! return services; From 3b19613528b249e2fa2c1d18986883da844e5453 Mon Sep 17 00:00:00 2001 From: stipus Date: Thu, 29 Oct 2015 12:16:05 +0100 Subject: [PATCH 72/89] SecuritySystem,Window, WindowCovering, obstruction --- platforms/HomeSeer.js | 151 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 147 insertions(+), 4 deletions(-) diff --git a/platforms/HomeSeer.js b/platforms/HomeSeer.js index 7c899f8..b4fa36d 100644 --- a/platforms/HomeSeer.js +++ b/platforms/HomeSeer.js @@ -29,12 +29,22 @@ // - Added offEventGroup && offEventName to events (turn on launches one HS event. turn off can launch another HS event) // - Added GarageDoorOpener support // - Added Lock support +// V0.10 - 2015/10/29 +// - Added Security System support +// - Added Window support +// - Added Window Covering support +// - Added obstruction support to doors, windows, and windowCoverings // // // Remember to add platform to config.json. // // You can get HomeSeer Device References by clicking a HomeSeer device name, then -// choosing the Advanced Tab. +// choosing the Advanced Tab. +// +// The uuid_base parameter is valid for all events and accessories. +// If you set this parameter to some unique identifier, the HomeKit accessory ID will be based on uuid_base instead of the accessory name. +// It is then easier to change the accessory name without messing the HomeKit database. +// // // Example: // "platforms": [ @@ -148,10 +158,31 @@ // "lockValue":1 // Required - HomeSeer device control value to lock // }, // { +// "ref":230, // Required - HomeSeer Device Reference of a Security System +// "type":"SecuritySystem", // Required for a security system +// "name":"Home alarm", // Optional - HomeSeer device name is the default +// "armedStayValues":[0], // Optional - List of the HomeSeer device values for a HomeKit security state=ARMED-STAY +// "armedAwayValues":[1], // Optional - List of the HomeSeer device values for a HomeKit security state=ARMED-AWAY +// "armedNightValues":[2], // Optional - List of the HomeSeer device values for a HomeKit security state=ARMED-NIGHT +// "disarmedValues":[3], // Optional - List of the HomeSeer device values for a HomeKit security state=DISARMED +// "alarmValues":[4], // Optional - List of the HomeSeer device values for a HomeKit security state=ALARM +// "armStayValue":0, // Required - HomeSeer device control value to arm in stay mode. If you don't have this mode, select any value that arms your system +// "armAwayValue":1, // Required - HomeSeer device control value to arm in away mode. If you don't have this mode, select any value that arms your system +// "armNightValue":2, // Required - HomeSeer device control value to arm in night mode. If you don't have this mode, select any value that arms your system +// "disarmValue":3 // Required - HomeSeer device control value to disarm security system +// }, +// { // "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 +// }, +// { +// "ref":240, // Required - HomeSeer Device Reference for a door - HomeSeer values must go from 0 (closed) to 100 (open) +// "type":"Door", // Required for a Door +// "name":"Main door", // Optional - HomeSeer device name is the default +// "obstructionRef":241, // Optional - HomeSeer device reference for your door obstruction state (can be the same as ref) +// "obstructionValues":[1] // Optional - List of the HomeSeer device values for a HomeKit obstruction state=OBSTRUCTION // } // ] // } @@ -177,8 +208,10 @@ // - Battery (batteryThreshold option) // - GarageDoorOpener (state, control, obstruction, lock options) // - Lock (unsecured, secured, jammed options) -// - Door - +// - SecuritySystem (arm, disarm options) +// - Door (obstruction option) +// - Window (obstruction option) +// - WindowCovering (obstruction option) var Service = require("hap-nodejs").Service; var Characteristic = require("hap-nodejs").Characteristic; @@ -672,7 +705,7 @@ HomeSeerAccessory.prototype = { }, setLockTargetState: function(state, callback) { - this.log("Setting target lock state state to %s", state); + this.log("Setting target lock state to %s", state); var ref = this.config.lockRef; var value = 0; @@ -694,6 +727,62 @@ HomeSeerAccessory.prototype = { }.bind(this)); }, + getSecuritySystemCurrentState: function(callback) { + var url = this.access_url + "request=getstatus&ref=" + this.ref; + + httpRequest(url, 'GET', function(error, response, body) { + if (error) { + this.log('HomeSeer get security system 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 security system current state function succeeded: value=' + value ); + if( this.config.armedStayValues && this.config.armedStayValues.indexOf(value) != -1 ) + callback( null, 0 ); + else if( this.config.armedAwayValues && this.config.armedAwayValues.indexOf(value) != -1 ) + callback( null, 1 ); + else if( this.config.armedNightValues && this.config.armedNightValues.indexOf(value) != -1 ) + callback( null, 2 ); + else if( this.config.disarmedValues && this.config.disarmedValues.indexOf(value) != -1 ) + callback( null, 3 ); + else if( this.config.alarmValues && this.config.alarmValues.indexOf(value) != -1 ) + callback( null, 4 ); + else + callback( null, 0 ); + } + }.bind(this)); + }, + + setSecuritySystemTargetState: function(state, callback) { + this.log("Setting security system state to %s", state); + + var value = 0; + if( state == 0 && this.config.armStayValue ) + value = this.config.armStayValue; + else if( state == 1 && this.config.armAwayValue ) + value = this.config.armAwayValue; + else if( state == 2 && this.config.armNightValue ) + value = this.config.armNightValue; + else if( state == 3 && this.config.disarmValue ) + value = this.config.disarmValue; + + var url = this.access_url + "request=controldevicebyvalue&ref=" + this.ref + "&value=" + value; + httpRequest(url, 'GET', function(error, response, body) { + if (error) { + this.log('HomeSeer set target security system state function failed: %s', error.message); + callback(error); + } + else { + this.log('HomeSeer set target security system state function succeeded!'); + callback(); + } + }.bind(this)); + }, + + getPositionState: function(callback) { callback( null, 2 ); // Temporarily return STOPPED. TODO: full door support }, @@ -897,9 +986,52 @@ HomeSeerAccessory.prototype = { doorService .getCharacteristic(Characteristic.PositionState) .on('get', this.getPositionState.bind(this)); + if( this.config.obstructionRef ) { + doorService + .addCharacteristic(new Characteristic.ObstructionDetected()) + .on('get', this.getObstructionDetected.bind(this)); + } services.push( doorService ); break; } + case "Window": { + var windowService = new Service.Window(); + windowService + .getCharacteristic(Characteristic.CurrentPosition) + .on('get', this.getValue.bind(this)); + windowService + .getCharacteristic(Characteristic.TargetPosition) + .on('set', this.setValue.bind(this)); + windowService + .getCharacteristic(Characteristic.PositionState) + .on('get', this.getPositionState.bind(this)); + if( this.config.obstructionRef ) { + windowService + .addCharacteristic(new Characteristic.ObstructionDetected()) + .on('get', this.getObstructionDetected.bind(this)); + } + services.push( windowService ); + break; + } + case "WindowCovering": { + var windowCoveringService = new Service.WindowCovering(); + windowCoveringService + .getCharacteristic(Characteristic.CurrentPosition) + .on('get', this.getValue.bind(this)); + windowCoveringService + .getCharacteristic(Characteristic.TargetPosition) + .on('set', this.setValue.bind(this)); + windowCoveringService + .getCharacteristic(Characteristic.PositionState) + .on('get', this.getPositionState.bind(this)); + services.push( windowCoveringService ); + if( this.config.obstructionRef ) { + windowCoveringService + .addCharacteristic(new Characteristic.ObstructionDetected()) + .on('get', this.getObstructionDetected.bind(this)); + } + break; + } case "Battery": { this.config.batteryRef = this.ref; var batteryService = new Service.BatteryService(); @@ -974,6 +1106,17 @@ HomeSeerAccessory.prototype = { services.push( lockService ); break; } + case "SecuritySystem": { + var securitySystemService = new Service.SecuritySystem(); + securitySystemService + .getCharacteristic(Characteristic.SecuritySystemCurrentState) + .on('get', this.getSecuritySystemCurrentState.bind(this)); + securitySystemService + .getCharacteristic(Characteristic.SecuritySystemTargetState) + .on('set', this.setSecuritySystemTargetState.bind(this)); + services.push( securitySystemService ); + break; + } default:{ var lightbulbService = new Service.Lightbulb(); From b277b76c39b702e56ed4682e8f1be9bf35699bc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20K=C3=B6nig?= Date: Thu, 29 Oct 2015 19:06:47 +0100 Subject: [PATCH 73/89] reintegrated Homematic Platform fork from https://github.com/thkl/homebridge/tree/xmlrpc --- package.json | 1 + platforms/HomeMatic.js | 406 ++++++++++++++++++++++ platforms/HomematicChannel.js | 631 ++++++++++++++++++++++++++++++++++ 3 files changed, 1038 insertions(+) create mode 100644 platforms/HomeMatic.js create mode 100644 platforms/HomematicChannel.js diff --git a/package.json b/package.json index eb9e811..e1de444 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "hap-nodejs": "^0.0.3", "harmonyhubjs-client": "^1.1.6", "harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git", + "homematic-xmlrpc": "git+https://github.com/hobbyquaker/homematic-xmlrpc", "isy-js": "", "komponist": "0.1.0", "lifx": "git+https://github.com/magicmonkey/lifxjs.git", diff --git a/platforms/HomeMatic.js b/platforms/HomeMatic.js new file mode 100644 index 0000000..b22a0f4 --- /dev/null +++ b/platforms/HomeMatic.js @@ -0,0 +1,406 @@ +// +// Homematic Platform Shim for HomeBridge +// +// V0.1 - 2015/10/29 +// - initial version +// - reintegrated Homematic Platform fork from https://github.com/thkl/homebridge/tree/xmlrpc + + +var types = require("hap-nodejs/accessories/types.js"); +var xmlrpc = require('homematic-xmlrpc') + +var request = require("request"); +var http = require("http"); +var path = require("path"); + +var HomeMaticGenericChannel = require(path.resolve(__dirname, 'HomematicChannel.js')); + + + +function RegaRequest(log,ccuip) { + this.log = log; + this.ccuIP = ccuip; +} + +RegaRequest.prototype = { + + script: function (script, callback) { + + var post_options = { + host: this.ccuIP, + port: '80', + path: '/tclrega.exe', + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': script.length + } + }; + + var post_req = http.request(post_options, function(res) { + var data = ""; + res.setEncoding('binary'); + res.on('data', function (chunk) { + data += chunk.toString(); + }); + res.on('end', function () { + var pos = data.lastIndexOf(""); + var response = (data.substring(0, pos)); + callback(response); + }); + }); + + post_req.on('error', function(e) { + callback("{}"); + }); + + post_req.write(script); + post_req.end(); + + + }, + + getValue: function(channel,datapoint,callback) { + var that = this; + + var script = "var d = dom.GetObject(\""+channel+"."+datapoint+"\");if (d){Write(d.State());}"; + //that.log("Rega Request " + script); + var regarequest = this.script(script, function(data) { + that.log("Rega Response" + data); + if (data!=undefined) { + callback(parseFloat(data)); + } + } + ); + }, + + setValue: function(channel,datapoint,value) { + var that = this; + + var script = "var d = dom.GetObject(\""+channel+"."+datapoint+"\");if (d){d.State(\""+value+"\");}"; + //that.log("Rega Request " + script); + var regarequest = this.script(script, function(data) { + }); + } + +} + +function HomematicRPC(log,ccuip,platform) { + this.log = log; + this.ccuip = ccuip; + this.platform = platform; + this.server; + this.client; + this.stopping = false; + this.localIP; +} + +HomematicRPC.prototype= { + + + init:function() { + var that = this; + + var ip = this.getIPAddress(); + if (ip=="0.0.0.0") { + that.log("Can not fetch IP"); + return; + } + + this.localIP = ip; + this.log("Local IP: "+this.localIP) + + this.server = xmlrpc.createServer({ host: this.localIP , port: 9090 }) + + this.server.on('NotFound', function(method, params) { + that.log('Method ' + method + ' does not exist'); + }); + + this.server.on('system.listMethods', function (err, params, callback) { + that.log('Method call params for \'system.listMethods\': ' + params) + callback(null,['system.listMethods', 'system.multicall']); + }); + + + this.server.on('system.multicall', function (err, params, callback) { + params.map(function(events) { + try { + events.map(function(event){ + if ((event["methodName"]=="event") && (event['params'] != undefined)) { + var params = event['params']; + var channel = "BidCos-RF." + params[1]; + var datapoint = params[2]; + var value = params[3]; + that.platform.foundAccessories.map(function(accessory){ + if (accessory.adress == channel) { + accessory.event(datapoint,value); + } + }); + } + }); + } catch(err) {} + }); + callback(null); + }); + + this.log('XML-RPC server listening on port 9090') + this.connect(); + + + process.on('SIGINT', function () { + if (that.stopping) { + return; + } + that.stopping = true; + that.stop(); + }); + + process.on('SIGTERM', function () { + if (that.stopping) { + return; + } + that.stopping = true; + that.stop(); + }); + + }, + + getIPAddress: function() { + var interfaces = require('os').networkInterfaces(); + for (var devName in interfaces) { + var iface = interfaces[devName]; + for (var i = 0; i < iface.length; i++) { + var alias = iface[i]; + if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) + return alias.address; + } + } + return '0.0.0.0'; + }, + + getValue:function(channel,datapoint,callback) { + + var that = this; + if (this.client == undefined) { + that.log("Returning cause client is invalid"); + return; + } + if (channel.indexOf("BidCos-RF.")>-1) { + channel = channel.substr(10); + this.log("Calling rpc getValue"); + this.client.methodCall('getValue', [channel,datapoint], function (error, value) { + callback(value); + }); + return; + } + }, + + setValue:function(channel,datapoint,value) { + + var that = this; + + if (this.client == undefined) return; + + if (channel.indexOf("BidCos-RF.")>-1) { + channel = channel.substr(10); + } + + this.client.methodCall('setValue', [channel,datapoint,value], function (error, value) { + + }); + }, + + connect:function(){ + var that = this; + this.log('Creating Local HTTP Client for CCU RPC Events'); + this.client = xmlrpc.createClient({ host: this.ccuip, port: 2001, path: '/'}); + this.log('CCU RPC Init Call on port 2001'); + this.client.methodCall('init', ['http://'+this.localIP+':9090','homebridge'], function (error, value) { + that.log('CCU Response ....') + }); + }, + + + stop:function() { + this.log("Removing Event Server"); + this.client.methodCall('init', ['http://'+this.localIP+':9090'], function (error, value) { + + }); + setTimeout(process.exit(0), 1000); + } + +} + + +function HomeMaticPlatform(log, config) { + this.log = log; + this.ccuIP = config["ccu_ip"]; + this.filter_device = config["filter_device"]; + this.filter_channel = config["filter_channel"]; + this.outlets = config["outlets"]; + + this.sendQueue = []; + this.timer = 0; + + this.foundAccessories = []; + this.adressesToQuery = []; + + this.xmlrpc = new HomematicRPC(this.log,this.ccuIP,this); + this.xmlrpc.init(); +} + +HomeMaticPlatform.prototype = { + + + + accessories: function(callback) { + this.log("Fetching Homematic devices..."); + var that = this; + that.foundAccessories = []; + + var script = "string sDeviceId;string sChannelId;boolean df = true;Write(\'{\"devices\":[\');foreach(sDeviceId, root.Devices().EnumIDs()){object oDevice = dom.GetObject(sDeviceId);if(oDevice){var oInterface = dom.GetObject(oDevice.Interface());if(df) {df = false;} else { Write(\',\');}Write(\'{\');Write(\'\"id\": \"\' # sDeviceId # \'\",\');Write(\'\"name\": \"\' # oDevice.Name() # \'\",\');Write(\'\"address\": \"\' # oDevice.Address() # \'\",\');Write(\'\"channels\": [\');boolean bcf = true;foreach(sChannelId, oDevice.Channels().EnumIDs()){object oChannel = dom.GetObject(sChannelId);if(bcf) {bcf = false;} else {Write(\',\');}Write(\'{\');Write(\'\"cId\": \' # sChannelId # \',\');Write(\'\"name\": \"\' # oChannel.Name() # \'\",\');if(oInterface){Write(\'\"address\": \"\' # oInterface.Name() #\'.'\ # oChannel.Address() # \'\",\');}Write(\'\"type\": \"\' # oChannel.HssType() # \'\"\');Write(\'}\');}Write(\']}\');}}Write(\']}\');"; + + var regarequest = new RegaRequest(this.log,this.ccuIP).script(script, function(data) { + var json = JSON.parse(data); + if (json['devices'] != undefined) { + json['devices'].map(function(device) { + var isFiltered = false; + + if ((that.filter_device != undefined) && (that.filter_device.indexOf(device.address) > -1)) { + isFiltered = true; + } else { + isFiltered = false; + } + // that.log('device address:', device.address); + + if ((device['channels'] != undefined) && (!isFiltered)) { + + device['channels'].map(function(ch) { + var isChannelFiltered = false; + + if ((that.filter_channel != undefined) && (that.filter_channel.indexOf(ch.address) > -1)) { + isChannelFiltered = true; + } else { + isChannelFiltered = false; + } + // that.log('name', ch.name, ' -> address:', ch.address); + if ((ch.address != undefined) && (!isChannelFiltered)) { + + if ((ch.type=="SWITCH") || (ch.type=="BLIND") || (ch.type=="SHUTTER_CONTACT") + || (ch.type=="DIMMER") || (ch.type=="CLIMATECONTROL_RT_TRANSCEIVER") + || (ch.type=="MOTION_DETECTOR") || (ch.type=="KEYMATIC") + ) { + // Switch found + // Check if marked as Outlet + var special = (that.outlets.indexOf(ch.address) > -1) ? 'OUTLET' : undefined; + accessory = new HomeMaticGenericChannel(that.log, that, ch.id , ch.name , ch.type , ch.address, special); + that.foundAccessories.push(accessory); + } + + + } else { + that.log(device.name + " has no address"); + } + + }); + } else { + that.log(device.name + " has no channels or is filtered"); + } + + }); + +/* + accessory = new HomeMaticGenericChannel(that.log, that, "1234" , "DummyKM" , "KEYMATIC" , "1234"); + that.foundAccessories.push(accessory); + + accessory = new HomeMaticGenericChannel(that.log, that, "5678" , "DummyBLIND" , "BLIND" , "5678"); + that.foundAccessories.push(accessory); + + */ + callback(that.foundAccessories); + } else { + callback(that.foundAccessories); + } + }); + + }, + + setValue:function(channel,datapoint,value) { + if (channel.indexOf("BidCos-RF.")>-1) { + this.xmlrpc.setValue(channel,datapoint,value); + return; + } + + if (channel.indexOf("VirtualDevices.")>-1) { + var rega = new RegaRequest(this.log,this.ccuIP); + rega.setValue(channel,datapoint,value); + return; + } + + }, + + + getValue:function(channel,datapoint,callback) { + + if (channel.indexOf("BidCos-RF.")>-1) { + this.xmlrpc.getValue(channel,datapoint,callback); + return; + } + + if (channel.indexOf("VirtualDevices.")>-1) { + var rega = new RegaRequest(this.log,this.ccuIP); + rega.getValue(channel,datapoint,callback); + return; + } + + }, + + prepareRequest: function(accessory,script) { + var that = this; + this.sendQueue.push(script); + that.delayed(100); + }, + + sendPreparedRequests: function() { + var that = this; + var script = "var d;"; + this.sendQueue.map(function(command) { + script = script + command; + }); + this.sendQueue = []; + //this.log("RegaSend: " + script); + var regarequest = new RegaRequest(this.log,this.ccuIP).script(script, function(data) { + }); + }, + + sendRequest: function(accessory,script,callback) { + var that = this; + var regarequest = new RegaRequest(this.log,this.ccuIP).script(script, function(data) { + if (data != undefined) { + try { + var json = JSON.parse(data); + callback(json); + } catch (err) { + callback(undefined); + } + return; + } + }); + }, + + delayed: function(delay) { + var timer = this.delayed[delay]; + if( timer ) { + this.log("removing old command"); + clearTimeout( timer ); + } + + var that = this; + this.delayed[delay] = setTimeout( function(){clearTimeout(that.delayed[delay]);that.sendPreparedRequests()}, delay?delay:100); + this.log("New Timer was set"); + } +} + + + +module.exports.platform = HomeMaticPlatform; \ No newline at end of file diff --git a/platforms/HomematicChannel.js b/platforms/HomematicChannel.js new file mode 100644 index 0000000..1ef22a5 --- /dev/null +++ b/platforms/HomematicChannel.js @@ -0,0 +1,631 @@ +var types = require("hap-nodejs/accessories/types.js"); + + +function HomeMaticGenericChannel(log,platform, id ,name, type ,adress,special) { + this.name = name; + this.type = type; + this.adress = adress; + this.log = log; + this.platform = platform; + this.state = []; + this.eventupdate = false; + this.special = special; + this.currentStateCharacteristic = []; + this.reverseDP = []; +} + + + + +HomeMaticGenericChannel.prototype = { + + + // Return current States + query: function(dp,callback) { + var that = this; + + if (this.state[dp] != undefined) { + callback(this.state[dp]); + } else { +// that.log("No cached Value found start fetching and send temp 0 back"); + this.remoteGetValue(dp); + callback(0); + } + + }, + + dpvalue:function(dp,fallback) { + if (this.state[dp] != undefined) { + return(this.state[dp]); + } else { + return fallback; + } + }, + + remoteGetValue:function(dp) { + var that = this; + that.platform.getValue(that.adress,dp,function(newValue) { + that.log("Remote Value Response for " + that.adress + "." + dp + "->" + newValue); + that.eventupdate = true; + that.cache(dp,newValue); + that.eventupdate = false; + }); + }, + + + event:function(dp,newValue) { + + if (dp=="LEVEL") { + newValue = newValue*100; + } + + this.eventupdate = true; + this.cache(dp,newValue); + this.eventupdate = false; + }, + + reverse:function(value) { + if (value=="true") return "false"; + if (value=="false") return "true"; + if (value==0) return 1; + if (value==1) return 0; + if (value=="0") return "1"; + if (value=="1") return "0"; + return value; + }, + + cache:function(dp,value) { + var that = this; + + if ((that.reverseDP[dp]!=undefined) && (that.reverseDP[dp]==true)) { + value = that.reverse(value); + } + + if (that.currentStateCharacteristic[dp]!=undefined) { + that.currentStateCharacteristic[dp].updateValue(value, null); + } + this.state[dp] = value; + }, + + + delayed: function(mode, dp,value,delay) { + + if (this.eventupdate==true) { + return; + } + + var timer = this.delayed[delay]; + if( timer ) { + clearTimeout( timer ); + } + + this.log(this.name + " delaying command "+mode + " " + dp +" with value " + value); + var that = this; + this.delayed[delay] = setTimeout( function(){clearTimeout(that.delayed[delay]);that.command(mode,dp,value)}, delay?delay:100 ); + }, + + command: function(mode,dp,value,callback) { + + if (this.eventupdate==true) { + return; + } + var that = this; + + if (mode == "set") { + //this.log("Send " + value + " to Datapoint " + dp + " at " + that.adress); + that.platform.setValue(that.adress,dp,value); + } + }, + + informationCharacteristics: function() { + return [ + { + 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: "EQ-3", + supportEvents: false, + supportBonjour: false, + manfDescription: "Manufacturer", + designedMaxLength: 255 + },{ + cType: types.MODEL_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.type, + supportEvents: false, + supportBonjour: false, + manfDescription: "Model", + designedMaxLength: 255 + },{ + cType: types.SERIAL_NUMBER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.adress , + 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) { + + cTypes = [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name, + supportEvents: true, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + }] + + + if (this.type=="SWITCH") { + cTypes.push({ + cType: types.POWER_STATE_CTYPE, + onUpdate: function(value) { + that.command("set","STATE" , (value==1)?true:false) + }, + + onRead: function(callback) { + that.query("STATE",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["STATE"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("STATE"); + }, + + perms: ["pw","pr","ev"], + format: "bool", + initialValue: that.dpvalue("STATE",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Change the power state", + designedMaxLength: 1 + }); + + if (this.special=="OUTLET") { + cTypes.push({ + cType: types.OUTLET_IN_USE_CTYPE, + + onRead: function(callback) { + callback(true); + }, + perms: ["pr","ev"], + format: "bool", + initialValue: true, + supportEvents: false, + supportBonjour: false, + manfDescription: "Is Outlet in Use", + designedMaxLength: 1 + }) + } + } + + + if (this.type=="KEYMATIC") { + cTypes.push( + { + cType: types.CURRENT_LOCK_MECHANISM_STATE_CTYPE, + + onRead: function(callback) { + that.query("STATE",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["STATE"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("STATE"); + }, + + perms: ["pr","ev"], + format: "bool", + initialValue: that.dpvalue("STATE",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current State of your Lock", + designedMaxLength: 1 + }, + { + cType: types.TARGET_LOCK_MECHANISM_STATE_CTYPE, + + onUpdate: function(value) { + that.command("set","STATE",(value==1)?"true":"false") + }, + + onRead: function(callback) { + that.query("STATE",callback); + }, + + onRegister: function(characteristic) { + that.reverseDP["STATE"] = true; + that.currentStateCharacteristic["STATE"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("STATE"); + }, + + + perms: ["pw","pr","ev"], + format: "bool", + initialValue: that.dpvalue("STATE",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Target State of your Lock", + designedMaxLength: 1 + } + + , + { + cType: types.TARGET_DOORSTATE_CTYPE, + + onUpdate: function(value) { + that.command("set","OPEN" , "true") + }, + + onRead: function(callback) { + callback(1); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["OPEN"] = characteristic; + characteristic.eventEnabled = true; + }, + + perms: ["pw","pr","ev"], + format: "bool", + initialValue: 1, + supportEvents: false, + supportBonjour: false, + manfDescription: "Open the Lock", + designedMaxLength: 1 + } + ); + + + } + + + + if (this.type=="DIMMER") { + cTypes.push({ + cType: types.POWER_STATE_CTYPE, + onUpdate: function(value) { + that.command("set","LEVEL" , (value==true) ? "1" : "0") + }, + + onRead: function(callback) { + that.query("LEVEL",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["LEVEL"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("LEVEL"); + }, + + perms: ["pw","pr","ev"], + format: "bool", + initialValue: (that.dpvalue("LEVEL")>0,0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Change the power state", + designedMaxLength: 1 + }, + { + cType: types.BRIGHTNESS_CTYPE, + onUpdate: function(value) { + that.delayed("set","LEVEL" , String(value/100),100); + }, + + onRead: function(callback) { + that.query("LEVEL",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["LEVEL"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("LEVEL"); + }, + + + perms: ["pw","pr","ev"], + format: "int", + initialValue: that.dpvalue("LEVEL",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Adjust Brightness of Light", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }); + } + + if (this.type=="BLIND") { + cTypes.push( + { + cType: types.WINDOW_COVERING_CURRENT_POSITION_CTYPE, + + onRead: function(callback) { + that.query("LEVEL",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["LEVEL"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("LEVEL"); + }, + + perms: ["pr","ev"], + format: "int", + initialValue: that.dpvalue("LEVEL",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Blind Position", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }, + + { + cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, + + onUpdate: function(value) { + that.delayed("set","LEVEL" , String(value/100),100); + }, + + + onRead: function(callback) { + that.query("LEVEL",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["LEVEL"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("LEVEL"); + }, + + perms: ["pw","pr","ev"], + format: "int", + initialValue: that.dpvalue("LEVEL",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Target Blind Position", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }, + { + cType: types.WINDOW_COVERING_OPERATION_STATE_CTYPE, + + onRead: function(callback) { + that.query("DIRECTION",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["DIRECTION"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("DIRECTION"); + }, + + perms: ["pr","ev"], + format: "int", + initialValue: that.dpvalue("DIRECTION",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Operating State ", + designedMinValue: 0, + designedMaxValue: 2, + designedMinStep: 1 + } + + ); + } + + if (this.type=="SHUTTER_CONTACT") { + cTypes.push( + { + cType: types.CONTACT_SENSOR_STATE_CTYPE, + + onRead: function(callback) { + that.query("STATE",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["STATE"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("STATE"); + }, + + perms: ["pr","ev"], + format: "bool", + initialValue: that.dpvalue("STATE",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current State" + }); + } + + if (this.type=="MOTION_DETECTOR") { + cTypes.push( + { + cType: types.MOTION_DETECTED_CTYPE, + + onRead: function(callback) { + that.query("MOTION",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["MOTION"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("MOTION"); + }, + + perms: ["pr","ev"], + format: "bool", + initialValue: that.dpvalue("MOTION",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Motion State" + }); + } + + if (this.type=="CLIMATECONTROL_RT_TRANSCEIVER") { + + cTypes.push({ + cType: types.NAME_CTYPE,onUpdate: null,perms: ["pr"],format: "string", + initialValue: this.name,supportEvents: true,supportBonjour: false,manfDescription: "Name of service",designedMaxLength: 255 + }, + + { + cType: types.CURRENTHEATINGCOOLING_CTYPE,onUpdate: null, + perms: ["pr"],format: "int",initialValue: 1,supportEvents: false, + supportBonjour: false,manfDescription: "Current Mode",designedMaxLength: 1,designedMinValue: 1,designedMaxValue: 1,designedMinStep: 1 + }, + + { + cType: types.TARGETHEATINGCOOLING_CTYPE,onUpdate: null,perms: ["pw","pr"], + format: "int",initialValue: 1,supportEvents: false,supportBonjour: false,manfDescription: "Target Mode", + designedMinValue: 1,designedMaxValue: 1,designedMinStep: 1 + }, + + { + cType: types.CURRENT_TEMPERATURE_CTYPE, + onUpdate: null, + + onRead: function(callback) { + that.query("ACTUAL_TEMPERATURE",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["ACTUAL_TEMPERATURE"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("ACTUAL_TEMPERATURE"); + }, + perms: ["pw","pr","ev"], perms: ["pr"],format: "double", + initialValue: that.dpvalue("ACTUAL_TEMPERATURE",20), + supportEvents: false,supportBonjour: false,manfDescription: "Current Temperature",unit: "celsius" + }, + + { + cType: types.TARGET_TEMPERATURE_CTYPE, + onUpdate: function(value) { + that.delayed("set", "SET_TEMPERATURE", value,500); + }, + onRead: function(callback) { + that.query("SET_TEMPERATURE",callback); + + }, + onRegister: function(characteristic) { + that.currentStateCharacteristic["SET_TEMPERATURE"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("SET_TEMPERATURE"); + }, + perms: ["pw","pr","ev"],format: "double", + initialValue: that.dpvalue("SET_TEMPERATURE",16), + supportEvents: false,supportBonjour: false, manfDescription: "Target Temperature", + designedMinValue: 16,designedMaxValue: 38,designedMinStep: 1,unit: "celsius" + }, + + { + cType: types.TEMPERATURE_UNITS_CTYPE,onRead: null, + perms: ["pr"],format: "int",initialValue: 0,supportEvents: false, + supportBonjour: false,manfDescription: "Current Temperature Unit",unit: "celsius" + } + + ); + } + + + return cTypes + }, + + sType: function() { + + if (this.type=="SWITCH") { + + if (this.special=="OUTLET") { + return types.OUTLET_STYPE; + } else { + return types.LIGHTBULB_STYPE; + } + } + + if (this.type=="DIMMER") { + return types.LIGHTBULB_STYPE; + } + + if (this.type=="BLIND") { + return types.WINDOW_COVERING_STYPE; + } + + if (this.type=="CLIMATECONTROL_RT_TRANSCEIVER") { + return types.THERMOSTAT_STYPE; + } + + if (this.type=="SHUTTER_CONTACT") { + return types.CONTACT_SENSOR_STYPE; + } + + if (this.type=="MOTION_DETECTOR") { + return types.MOTION_SENSOR_STYPE + } + + + if (this.type=="KEYMATIC") { + return types.LOCK_MECHANISM_STYPE + } + + + + }, + + getServices: function() { + var that = this; + var services = [{ + sType: types.ACCESSORY_INFORMATION_STYPE, + characteristics: this.informationCharacteristics(), + }, + { + sType: this.sType(), + characteristics: this.controlCharacteristics(that) + }]; + this.log("Loaded services for " + this.name) + return services; + } +}; + + +module.exports = HomeMaticGenericChannel; \ No newline at end of file From 6b36ec9dc417c2138e2e643b62a6c9a9f6c2df1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20K=C3=B6nig?= Date: Thu, 29 Oct 2015 19:15:35 +0100 Subject: [PATCH 74/89] cleanup formatting --- platforms/HomeMatic.js | 584 +++++++++++----------- platforms/HomematicChannel.js | 912 ++++++++++++++++++---------------- 2 files changed, 766 insertions(+), 730 deletions(-) diff --git a/platforms/HomeMatic.js b/platforms/HomeMatic.js index b22a0f4..1b99729 100644 --- a/platforms/HomeMatic.js +++ b/platforms/HomeMatic.js @@ -17,345 +17,347 @@ var HomeMaticGenericChannel = require(path.resolve(__dirname, 'HomematicChannel. -function RegaRequest(log,ccuip) { - this.log = log; - this.ccuIP = ccuip; +function RegaRequest(log, ccuip) { + this.log = log; + this.ccuIP = ccuip; } RegaRequest.prototype = { - script: function (script, callback) { + script: function(script, callback) { - var post_options = { - host: this.ccuIP, - port: '80', - path: '/tclrega.exe', - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Content-Length': script.length - } - }; + var post_options = { + host: this.ccuIP, + port: '80', + path: '/tclrega.exe', + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': script.length + } + }; - var post_req = http.request(post_options, function(res) { - var data = ""; - res.setEncoding('binary'); - res.on('data', function (chunk) { - data += chunk.toString(); - }); - res.on('end', function () { - var pos = data.lastIndexOf(""); - var response = (data.substring(0, pos)); - callback(response); - }); - }); + var post_req = http.request(post_options, function(res) { + var data = ""; + res.setEncoding('binary'); + res.on('data', function(chunk) { + data += chunk.toString(); + }); + res.on('end', function() { + var pos = data.lastIndexOf(""); + var response = (data.substring(0, pos)); + callback(response); + }); + }); - post_req.on('error', function(e) { - callback("{}"); - }); + post_req.on('error', function(e) { + callback("{}"); + }); - post_req.write(script); - post_req.end(); + post_req.write(script); + post_req.end(); - }, - - getValue: function(channel,datapoint,callback) { - var that = this; - - var script = "var d = dom.GetObject(\""+channel+"."+datapoint+"\");if (d){Write(d.State());}"; - //that.log("Rega Request " + script); - var regarequest = this.script(script, function(data) { - that.log("Rega Response" + data); - if (data!=undefined) { - callback(parseFloat(data)); - } - } - ); }, - - setValue: function(channel,datapoint,value) { - var that = this; - - var script = "var d = dom.GetObject(\""+channel+"."+datapoint+"\");if (d){d.State(\""+value+"\");}"; - //that.log("Rega Request " + script); - var regarequest = this.script(script, function(data) { - }); + + getValue: function(channel, datapoint, callback) { + var that = this; + + var script = "var d = dom.GetObject(\"" + channel + "." + datapoint + "\");if (d){Write(d.State());}"; + //that.log("Rega Request " + script); + var regarequest = this.script(script, function(data) { + that.log("Rega Response" + data); + if (data != undefined) { + callback(parseFloat(data)); + } + }); + }, + + setValue: function(channel, datapoint, value) { + var that = this; + + var script = "var d = dom.GetObject(\"" + channel + "." + datapoint + "\");if (d){d.State(\"" + value + "\");}"; + //that.log("Rega Request " + script); + var regarequest = this.script(script, function(data) {}); } } -function HomematicRPC(log,ccuip,platform) { - this.log = log; - this.ccuip = ccuip; - this.platform = platform; - this.server; - this.client; - this.stopping = false; - this.localIP; +function HomematicRPC(log, ccuip, platform) { + this.log = log; + this.ccuip = ccuip; + this.platform = platform; + this.server; + this.client; + this.stopping = false; + this.localIP; } -HomematicRPC.prototype= { +HomematicRPC.prototype = { - init:function() { - var that = this; - - var ip = this.getIPAddress(); - if (ip=="0.0.0.0") { - that.log("Can not fetch IP"); - return; - } - - this.localIP = ip; - this.log("Local IP: "+this.localIP) - - this.server = xmlrpc.createServer({ host: this.localIP , port: 9090 }) + init: function() { + var that = this; - this.server.on('NotFound', function(method, params) { - that.log('Method ' + method + ' does not exist'); - }); - - this.server.on('system.listMethods', function (err, params, callback) { - that.log('Method call params for \'system.listMethods\': ' + params) - callback(null,['system.listMethods', 'system.multicall']); - }); + var ip = this.getIPAddress(); + if (ip == "0.0.0.0") { + that.log("Can not fetch IP"); + return; + } - - this.server.on('system.multicall', function (err, params, callback) { - params.map(function(events) { - try { - events.map(function(event){ - if ((event["methodName"]=="event") && (event['params'] != undefined)) { - var params = event['params']; - var channel = "BidCos-RF." + params[1]; - var datapoint = params[2]; - var value = params[3]; - that.platform.foundAccessories.map(function(accessory){ - if (accessory.adress == channel) { - accessory.event(datapoint,value); - } - }); - } - }); - } catch(err) {} - }); - callback(null); - }); - - this.log('XML-RPC server listening on port 9090') + this.localIP = ip; + this.log("Local IP: " + this.localIP) + + this.server = xmlrpc.createServer({ + host: this.localIP, + port: 9090 + }) + + this.server.on('NotFound', function(method, params) { + that.log('Method ' + method + ' does not exist'); + }); + + this.server.on('system.listMethods', function(err, params, callback) { + that.log('Method call params for \'system.listMethods\': ' + params) + callback(null, ['system.listMethods', 'system.multicall']); + }); + + + this.server.on('system.multicall', function(err, params, callback) { + params.map(function(events) { + try { + events.map(function(event) { + if ((event["methodName"] == "event") && (event['params'] != undefined)) { + var params = event['params']; + var channel = "BidCos-RF." + params[1]; + var datapoint = params[2]; + var value = params[3]; + that.platform.foundAccessories.map(function(accessory) { + if (accessory.adress == channel) { + accessory.event(datapoint, value); + } + }); + } + }); + } catch (err) {} + }); + callback(null); + }); + + this.log('XML-RPC server listening on port 9090') this.connect(); - - - process.on('SIGINT', function () { - if (that.stopping) { - return; - } - that.stopping = true; - that.stop(); - }); - process.on('SIGTERM', function () { - if (that.stopping) { - return; - } - that.stopping = true; - that.stop(); - }); - }, - - getIPAddress: function() { - var interfaces = require('os').networkInterfaces(); - for (var devName in interfaces) { + process.on('SIGINT', function() { + if (that.stopping) { + return; + } + that.stopping = true; + that.stop(); + }); + + process.on('SIGTERM', function() { + if (that.stopping) { + return; + } + that.stopping = true; + that.stop(); + }); + + }, + + getIPAddress: function() { + var interfaces = require('os').networkInterfaces(); + for (var devName in interfaces) { var iface = interfaces[devName]; for (var i = 0; i < iface.length; i++) { - var alias = iface[i]; - if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) - return alias.address; - } + var alias = iface[i]; + if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) + return alias.address; } - return '0.0.0.0'; - }, + } + return '0.0.0.0'; + }, - getValue:function(channel,datapoint,callback) { - - var that = this; - if (this.client == undefined) { - that.log("Returning cause client is invalid"); - return; - } - if (channel.indexOf("BidCos-RF.")>-1) { - channel = channel.substr(10); - this.log("Calling rpc getValue"); - this.client.methodCall('getValue', [channel,datapoint], function (error, value) { - callback(value); - }); - return; - } - }, + getValue: function(channel, datapoint, callback) { - setValue:function(channel,datapoint,value) { - - var that = this; - - if (this.client == undefined) return; + var that = this; + if (this.client == undefined) { + that.log("Returning cause client is invalid"); + return; + } + if (channel.indexOf("BidCos-RF.") > -1)  { + channel = channel.substr(10); + this.log("Calling rpc getValue"); + this.client.methodCall('getValue', [channel, datapoint], function(error, value) { + callback(value); + }); + return; + } + }, - if (channel.indexOf("BidCos-RF.")>-1) { - channel = channel.substr(10); - } - - this.client.methodCall('setValue', [channel,datapoint,value], function (error, value) { + setValue: function(channel, datapoint, value) { - }); - }, + var that = this; - connect:function(){ - var that = this; - this.log('Creating Local HTTP Client for CCU RPC Events'); - this.client = xmlrpc.createClient({ host: this.ccuip, port: 2001, path: '/'}); - this.log('CCU RPC Init Call on port 2001'); - this.client.methodCall('init', ['http://'+this.localIP+':9090','homebridge'], function (error, value) { - that.log('CCU Response ....') - }); - }, - - - stop:function() { - this.log("Removing Event Server"); - this.client.methodCall('init', ['http://'+this.localIP+':9090'], function (error, value) { + if (this.client == undefined) return; + + if (channel.indexOf("BidCos-RF.") > -1)  { + channel = channel.substr(10); + } + + this.client.methodCall('setValue', [channel, datapoint, value], function(error, value) { }); - setTimeout(process.exit(0), 1000); + }, + + connect: function() { + var that = this; + this.log('Creating Local HTTP Client for CCU RPC Events'); + this.client = xmlrpc.createClient({ + host: this.ccuip, + port: 2001, + path: '/' + }); + this.log('CCU RPC Init Call on port 2001'); + this.client.methodCall('init', ['http://' + this.localIP + ':9090', 'homebridge'], function(error, value) { + that.log('CCU Response ....') + }); + }, + + + stop: function() { + this.log("Removing Event Server"); + this.client.methodCall('init', ['http://' + this.localIP + ':9090'], function(error, value) { + + }); + setTimeout(process.exit(0), 1000); } } function HomeMaticPlatform(log, config) { - this.log = log; - this.ccuIP = config["ccu_ip"]; - this.filter_device = config["filter_device"]; - this.filter_channel = config["filter_channel"]; - this.outlets = config["outlets"]; + this.log = log; + this.ccuIP = config["ccu_ip"]; + this.filter_device = config["filter_device"]; + this.filter_channel = config["filter_channel"]; + this.outlets = config["outlets"]; - this.sendQueue = []; - this.timer = 0; - - this.foundAccessories = []; - this.adressesToQuery = []; - - this.xmlrpc = new HomematicRPC(this.log,this.ccuIP,this); - this.xmlrpc.init(); + this.sendQueue = []; + this.timer = 0; + + this.foundAccessories = []; + this.adressesToQuery = []; + + this.xmlrpc = new HomematicRPC(this.log, this.ccuIP, this); + this.xmlrpc.init(); } HomeMaticPlatform.prototype = { - - + + accessories: function(callback) { this.log("Fetching Homematic devices..."); - var that = this; + var that = this; that.foundAccessories = []; - + var script = "string sDeviceId;string sChannelId;boolean df = true;Write(\'{\"devices\":[\');foreach(sDeviceId, root.Devices().EnumIDs()){object oDevice = dom.GetObject(sDeviceId);if(oDevice){var oInterface = dom.GetObject(oDevice.Interface());if(df) {df = false;} else { Write(\',\');}Write(\'{\');Write(\'\"id\": \"\' # sDeviceId # \'\",\');Write(\'\"name\": \"\' # oDevice.Name() # \'\",\');Write(\'\"address\": \"\' # oDevice.Address() # \'\",\');Write(\'\"channels\": [\');boolean bcf = true;foreach(sChannelId, oDevice.Channels().EnumIDs()){object oChannel = dom.GetObject(sChannelId);if(bcf) {bcf = false;} else {Write(\',\');}Write(\'{\');Write(\'\"cId\": \' # sChannelId # \',\');Write(\'\"name\": \"\' # oChannel.Name() # \'\",\');if(oInterface){Write(\'\"address\": \"\' # oInterface.Name() #\'.'\ # oChannel.Address() # \'\",\');}Write(\'\"type\": \"\' # oChannel.HssType() # \'\"\');Write(\'}\');}Write(\']}\');}}Write(\']}\');"; - var regarequest = new RegaRequest(this.log,this.ccuIP).script(script, function(data) { - var json = JSON.parse(data); - if (json['devices'] != undefined) { - json['devices'].map(function(device) { - var isFiltered = false; + var regarequest = new RegaRequest(this.log, this.ccuIP).script(script, function(data) { + var json = JSON.parse(data); + if (json['devices'] != undefined) { + json['devices'].map(function(device) { + var isFiltered = false; - if ((that.filter_device != undefined) && (that.filter_device.indexOf(device.address) > -1)) { - isFiltered = true; - } else { - isFiltered = false; - } - // that.log('device address:', device.address); + if ((that.filter_device != undefined) && (that.filter_device.indexOf(device.address) > -1)) { + isFiltered = true; + } else { + isFiltered = false; + } + // that.log('device address:', device.address); - if ((device['channels'] != undefined) && (!isFiltered)) { + if ((device['channels'] != undefined) && (!isFiltered)) { - device['channels'].map(function(ch) { - var isChannelFiltered = false; + device['channels'].map(function(ch) { + var isChannelFiltered = false; - if ((that.filter_channel != undefined) && (that.filter_channel.indexOf(ch.address) > -1)) { - isChannelFiltered = true; - } else { - isChannelFiltered = false; - } - // that.log('name', ch.name, ' -> address:', ch.address); - if ((ch.address != undefined) && (!isChannelFiltered)) { - - if ((ch.type=="SWITCH") || (ch.type=="BLIND") || (ch.type=="SHUTTER_CONTACT") - || (ch.type=="DIMMER") || (ch.type=="CLIMATECONTROL_RT_TRANSCEIVER") - || (ch.type=="MOTION_DETECTOR") || (ch.type=="KEYMATIC") - ) { - // Switch found - // Check if marked as Outlet - var special = (that.outlets.indexOf(ch.address) > -1) ? 'OUTLET' : undefined; - accessory = new HomeMaticGenericChannel(that.log, that, ch.id , ch.name , ch.type , ch.address, special); - that.foundAccessories.push(accessory); - } - + if ((that.filter_channel != undefined) && (that.filter_channel.indexOf(ch.address) > -1)) { + isChannelFiltered = true; + } else { + isChannelFiltered = false; + } + // that.log('name', ch.name, ' -> address:', ch.address); + if ((ch.address != undefined) && (!isChannelFiltered)) { - } else { - that.log(device.name + " has no address"); - } + if ((ch.type == "SWITCH") || (ch.type == "BLIND") || (ch.type == "SHUTTER_CONTACT") || (ch.type == "DIMMER") || (ch.type == "CLIMATECONTROL_RT_TRANSCEIVER") ||  (ch.type == "MOTION_DETECTOR") ||  (ch.type == "KEYMATIC")) { + // Switch found + // Check if marked as Outlet + var special = (that.outlets.indexOf(ch.address) > -1) ? 'OUTLET' : undefined; + accessory = new HomeMaticGenericChannel(that.log, that, ch.id, ch.name, ch.type, ch.address, special); + that.foundAccessories.push(accessory); + } - }); - } else { - that.log(device.name + " has no channels or is filtered"); - } - }); + } else { + that.log(device.name + " has no address"); + } -/* - accessory = new HomeMaticGenericChannel(that.log, that, "1234" , "DummyKM" , "KEYMATIC" , "1234"); - that.foundAccessories.push(accessory); + }); + } else { + that.log(device.name + " has no channels or is filtered"); + } - accessory = new HomeMaticGenericChannel(that.log, that, "5678" , "DummyBLIND" , "BLIND" , "5678"); - that.foundAccessories.push(accessory); - - */ - callback(that.foundAccessories); - } else { - callback(that.foundAccessories); - } + }); + + /* + accessory = new HomeMaticGenericChannel(that.log, that, "1234" , "DummyKM" , "KEYMATIC" , "1234"); + that.foundAccessories.push(accessory); + + accessory = new HomeMaticGenericChannel(that.log, that, "5678" , "DummyBLIND" , "BLIND" , "5678"); + that.foundAccessories.push(accessory); + + */ + callback(that.foundAccessories); + } else { + callback(that.foundAccessories); + } }); - + }, - - setValue:function(channel,datapoint,value) { - if (channel.indexOf("BidCos-RF.")>-1) { - this.xmlrpc.setValue(channel,datapoint,value); - return; + + setValue: function(channel, datapoint, value) { + if (channel.indexOf("BidCos-RF.") > -1)  { + this.xmlrpc.setValue(channel, datapoint, value); + return; + } + + if (channel.indexOf("VirtualDevices.") > -1)  { + var rega = new RegaRequest(this.log, this.ccuIP); + rega.setValue(channel, datapoint, value); + return; } - if (channel.indexOf("VirtualDevices.")>-1) { - var rega = new RegaRequest(this.log,this.ccuIP); - rega.setValue(channel,datapoint,value); - return; - } - }, - - - getValue:function(channel,datapoint,callback) { - - if (channel.indexOf("BidCos-RF.")>-1) { - this.xmlrpc.getValue(channel,datapoint,callback); - return; - } - - if (channel.indexOf("VirtualDevices.")>-1) { - var rega = new RegaRequest(this.log,this.ccuIP); - rega.getValue(channel,datapoint,callback); - return; - } - + + + getValue: function(channel, datapoint, callback) { + + if (channel.indexOf("BidCos-RF.") > -1)  { + this.xmlrpc.getValue(channel, datapoint, callback); + return; + } + + if (channel.indexOf("VirtualDevices.") > -1)  { + var rega = new RegaRequest(this.log, this.ccuIP); + rega.getValue(channel, datapoint, callback); + return; + } + }, - - prepareRequest: function(accessory,script) { + + prepareRequest: function(accessory, script) { var that = this; this.sendQueue.push(script); that.delayed(100); @@ -369,34 +371,36 @@ HomeMaticPlatform.prototype = { }); this.sendQueue = []; //this.log("RegaSend: " + script); - var regarequest = new RegaRequest(this.log,this.ccuIP).script(script, function(data) { - }); + var regarequest = new RegaRequest(this.log, this.ccuIP).script(script, function(data) {}); }, - sendRequest: function(accessory,script,callback) { + sendRequest: function(accessory, script, callback) { var that = this; - var regarequest = new RegaRequest(this.log,this.ccuIP).script(script, function(data) { - if (data != undefined) { - try { - var json = JSON.parse(data); - callback(json); - } catch (err) { - callback(undefined); - } - return; - } + var regarequest = new RegaRequest(this.log, this.ccuIP).script(script, function(data) { + if (data != undefined) { + try { + var json = JSON.parse(data); + callback(json); + } catch (err) { + callback(undefined); + } + return; + } }); }, delayed: function(delay) { var timer = this.delayed[delay]; - if( timer ) { + if (timer) { this.log("removing old command"); - clearTimeout( timer ); + clearTimeout(timer); } var that = this; - this.delayed[delay] = setTimeout( function(){clearTimeout(that.delayed[delay]);that.sendPreparedRequests()}, delay?delay:100); + this.delayed[delay] = setTimeout(function() { + clearTimeout(that.delayed[delay]); + that.sendPreparedRequests() + }, delay ? delay : 100); this.log("New Timer was set"); } } diff --git a/platforms/HomematicChannel.js b/platforms/HomematicChannel.js index 1ef22a5..4e5a34b 100644 --- a/platforms/HomematicChannel.js +++ b/platforms/HomematicChannel.js @@ -1,180 +1,180 @@ var types = require("hap-nodejs/accessories/types.js"); -function HomeMaticGenericChannel(log,platform, id ,name, type ,adress,special) { - this.name = name; - this.type = type; - this.adress = adress; - this.log = log; +function HomeMaticGenericChannel(log, platform, id, name, type, adress, special) { + this.name = name; + this.type = type; + this.adress = adress; + this.log = log; this.platform = platform; - this.state = []; + this.state = []; this.eventupdate = false; - this.special = special; + this.special = special; this.currentStateCharacteristic = []; this.reverseDP = []; } - HomeMaticGenericChannel.prototype = { - // Return current States - query: function(dp,callback) { + // Return current States + query: function(dp, callback) { var that = this; - + if (this.state[dp] != undefined) { callback(this.state[dp]); } else { -// that.log("No cached Value found start fetching and send temp 0 back"); + // that.log("No cached Value found start fetching and send temp 0 back"); this.remoteGetValue(dp); callback(0); } }, - dpvalue:function(dp,fallback) { + dpvalue: function(dp, fallback) { if (this.state[dp] != undefined) { - return(this.state[dp]); + return (this.state[dp]); } else { return fallback; } }, - remoteGetValue:function(dp) { - var that = this; - that.platform.getValue(that.adress,dp,function(newValue) { - that.log("Remote Value Response for " + that.adress + "." + dp + "->" + newValue); - that.eventupdate = true; - that.cache(dp,newValue); - that.eventupdate = false; - }); + remoteGetValue: function(dp) { + var that = this; + that.platform.getValue(that.adress, dp, function(newValue) { + that.log("Remote Value Response for " + that.adress + "." + dp + "->" + newValue); + that.eventupdate = true; + that.cache(dp, newValue); + that.eventupdate = false; + }); }, - - event:function(dp,newValue) { - - if (dp=="LEVEL") { - newValue = newValue*100; + + event: function(dp, newValue) { + + if (dp == "LEVEL") { + newValue = newValue * 100; } this.eventupdate = true; - this.cache(dp,newValue); + this.cache(dp, newValue); this.eventupdate = false; }, - reverse:function(value) { - if (value=="true") return "false"; - if (value=="false") return "true"; - if (value==0) return 1; - if (value==1) return 0; - if (value=="0") return "1"; - if (value=="1") return "0"; + reverse: function(value) { + if (value == "true") return "false"; + if (value == "false") return "true"; + if (value == 0) return 1; + if (value == 1) return 0; + if (value == "0") return "1"; + if (value == "1") return "0"; return value; }, - cache:function(dp,value) { + cache: function(dp, value) { var that = this; - if ((that.reverseDP[dp]!=undefined) && (that.reverseDP[dp]==true)) { - value = that.reverse(value); - } - - if (that.currentStateCharacteristic[dp]!=undefined) { - that.currentStateCharacteristic[dp].updateValue(value, null); + if ((that.reverseDP[dp] != undefined) && (that.reverseDP[dp] == true)) { + value = that.reverse(value); + } + + if (that.currentStateCharacteristic[dp] != undefined) { + that.currentStateCharacteristic[dp].updateValue(value, null); } this.state[dp] = value; }, - delayed: function(mode, dp,value,delay) { - - if (this.eventupdate==true) { - return; - } - - var timer = this.delayed[delay]; - if( timer ) { - clearTimeout( timer ); + delayed: function(mode, dp, value, delay) { + + if (this.eventupdate == true) { + return; } - this.log(this.name + " delaying command "+mode + " " + dp +" with value " + value); + var timer = this.delayed[delay]; + if (timer) { + clearTimeout(timer); + } + + this.log(this.name + " delaying command " + mode + " " + dp + " with value " + value); var that = this; - this.delayed[delay] = setTimeout( function(){clearTimeout(that.delayed[delay]);that.command(mode,dp,value)}, delay?delay:100 ); + this.delayed[delay] = setTimeout(function() { + clearTimeout(that.delayed[delay]); + that.command(mode, dp, value) + }, delay ? delay : 100); }, - command: function(mode,dp,value,callback) { - - if (this.eventupdate==true) { - return; - } - var that = this; + command: function(mode, dp, value, callback) { - if (mode == "set") { - //this.log("Send " + value + " to Datapoint " + dp + " at " + that.adress); - that.platform.setValue(that.adress,dp,value); - } + if (this.eventupdate == true) { + return; + } + var that = this; + + if (mode == "set") { + //this.log("Send " + value + " to Datapoint " + dp + " at " + that.adress); + that.platform.setValue(that.adress, dp, value); + } }, informationCharacteristics: function() { - return [ - { - 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: "EQ-3", - supportEvents: false, - supportBonjour: false, - manfDescription: "Manufacturer", - designedMaxLength: 255 - },{ - cType: types.MODEL_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.type, - supportEvents: false, - supportBonjour: false, - manfDescription: "Model", - designedMaxLength: 255 - },{ - cType: types.SERIAL_NUMBER_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.adress , - 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 - } - ] + return [{ + 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: "EQ-3", + supportEvents: false, + supportBonjour: false, + manfDescription: "Manufacturer", + designedMaxLength: 255 + }, { + cType: types.MODEL_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.type, + supportEvents: false, + supportBonjour: false, + manfDescription: "Model", + designedMaxLength: 255 + }, { + cType: types.SERIAL_NUMBER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.adress, + 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) { - + cTypes = [{ cType: types.NAME_CTYPE, onUpdate: null, @@ -186,181 +186,177 @@ HomeMaticGenericChannel.prototype = { manfDescription: "Name of service", designedMaxLength: 255 }] - - - if (this.type=="SWITCH") { - cTypes.push({ + + + if (this.type == "SWITCH") { + cTypes.push({ cType: types.POWER_STATE_CTYPE, onUpdate: function(value) { - that.command("set","STATE" , (value==1)?true:false) + that.command("set", "STATE", (value == 1) ? true : false) }, onRead: function(callback) { - that.query("STATE",callback); + that.query("STATE", callback); }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["STATE"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("STATE"); + + onRegister: function(characteristic) { + that.currentStateCharacteristic["STATE"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("STATE"); }, - - perms: ["pw","pr","ev"], + + perms: ["pw", "pr", "ev"], format: "bool", - initialValue: that.dpvalue("STATE",0), + initialValue: that.dpvalue("STATE", 0), supportEvents: false, supportBonjour: false, manfDescription: "Change the power state", designedMaxLength: 1 }); - - if (this.special=="OUTLET") { + + if (this.special == "OUTLET") { cTypes.push({ cType: types.OUTLET_IN_USE_CTYPE, - + onRead: function(callback) { - callback(true); + callback(true); }, - perms: ["pr","ev"], - format: "bool", - initialValue: true, - supportEvents: false, - supportBonjour: false, - manfDescription: "Is Outlet in Use", - designedMaxLength: 1 - }) - } + perms: ["pr", "ev"], + format: "bool", + initialValue: true, + supportEvents: false, + supportBonjour: false, + manfDescription: "Is Outlet in Use", + designedMaxLength: 1 + }) + } } - - - if (this.type=="KEYMATIC") { - cTypes.push( - { - cType: types.CURRENT_LOCK_MECHANISM_STATE_CTYPE, - - onRead: function(callback) { - that.query("STATE",callback); - }, - - onRegister: function(characteristic) { + + + if (this.type == "KEYMATIC") { + cTypes.push({ + cType: types.CURRENT_LOCK_MECHANISM_STATE_CTYPE, + + onRead: function(callback) { + that.query("STATE", callback); + }, + + onRegister: function(characteristic) { that.currentStateCharacteristic["STATE"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("STATE"); - }, + }, - perms: ["pr","ev"], - format: "bool", - initialValue: that.dpvalue("STATE",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Current State of your Lock", - designedMaxLength: 1 - }, - { - cType: types.TARGET_LOCK_MECHANISM_STATE_CTYPE, - - onUpdate: function(value) { - that.command("set","STATE",(value==1)?"true":"false") - }, - - onRead: function(callback) { - that.query("STATE",callback); - }, - - onRegister: function(characteristic) { + perms: ["pr", "ev"], + format: "bool", + initialValue: that.dpvalue("STATE", 0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current State of your Lock", + designedMaxLength: 1 + }, { + cType: types.TARGET_LOCK_MECHANISM_STATE_CTYPE, + + onUpdate: function(value) { + that.command("set", "STATE", (value == 1) ? "true" : "false") + }, + + onRead: function(callback) { + that.query("STATE", callback); + }, + + onRegister: function(characteristic) { that.reverseDP["STATE"] = true; that.currentStateCharacteristic["STATE"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("STATE"); - }, + }, - - perms: ["pw","pr","ev"], - format: "bool", - initialValue: that.dpvalue("STATE",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Target State of your Lock", - designedMaxLength: 1 - } - - , - { - cType: types.TARGET_DOORSTATE_CTYPE, - - onUpdate: function(value) { - that.command("set","OPEN" , "true") - }, - onRead: function(callback) { - callback(1); - }, - - onRegister: function(characteristic) { + perms: ["pw", "pr", "ev"], + format: "bool", + initialValue: that.dpvalue("STATE", 0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Target State of your Lock", + designedMaxLength: 1 + } + + , { + cType: types.TARGET_DOORSTATE_CTYPE, + + onUpdate: function(value) { + that.command("set", "OPEN", "true") + }, + + onRead: function(callback) { + callback(1); + }, + + onRegister: function(characteristic) { that.currentStateCharacteristic["OPEN"] = characteristic; characteristic.eventEnabled = true; - }, - - perms: ["pw","pr","ev"], - format: "bool", - initialValue: 1, - supportEvents: false, - supportBonjour: false, - manfDescription: "Open the Lock", - designedMaxLength: 1 - } + }, + + perms: ["pw", "pr", "ev"], + format: "bool", + initialValue: 1, + supportEvents: false, + supportBonjour: false, + manfDescription: "Open the Lock", + designedMaxLength: 1 + } ); - - + + } - if (this.type=="DIMMER") { - cTypes.push({ + if (this.type == "DIMMER") { + cTypes.push({ cType: types.POWER_STATE_CTYPE, onUpdate: function(value) { - that.command("set","LEVEL" , (value==true) ? "1" : "0") + that.command("set", "LEVEL", (value == true) ? "1" : "0") }, onRead: function(callback) { - that.query("LEVEL",callback); + that.query("LEVEL", callback); }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["LEVEL"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("LEVEL"); + + onRegister: function(characteristic) { + that.currentStateCharacteristic["LEVEL"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("LEVEL"); }, - - perms: ["pw","pr","ev"], + + perms: ["pw", "pr", "ev"], format: "bool", - initialValue: (that.dpvalue("LEVEL")>0,0), + initialValue: (that.dpvalue("LEVEL") > 0, 0), supportEvents: false, supportBonjour: false, manfDescription: "Change the power state", designedMaxLength: 1 - }, - { + }, { cType: types.BRIGHTNESS_CTYPE, onUpdate: function(value) { - that.delayed("set","LEVEL" , String(value/100),100); - }, - - onRead: function(callback) { - that.query("LEVEL",callback); - }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["LEVEL"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("LEVEL"); + that.delayed("set", "LEVEL", String(value / 100), 100); }, - - perms: ["pw","pr","ev"], + onRead: function(callback) { + that.query("LEVEL", callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["LEVEL"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("LEVEL"); + }, + + + perms: ["pw", "pr", "ev"], format: "int", - initialValue: that.dpvalue("LEVEL",0), + initialValue: that.dpvalue("LEVEL", 0), supportEvents: false, supportBonjour: false, manfDescription: "Adjust Brightness of Light", @@ -371,245 +367,282 @@ HomeMaticGenericChannel.prototype = { }); } - if (this.type=="BLIND") { - cTypes.push( - { - cType: types.WINDOW_COVERING_CURRENT_POSITION_CTYPE, - - onRead: function(callback) { - that.query("LEVEL",callback); - }, - - onRegister: function(characteristic) { + if (this.type == "BLIND") { + cTypes.push({ + cType: types.WINDOW_COVERING_CURRENT_POSITION_CTYPE, + + onRead: function(callback) { + that.query("LEVEL", callback); + }, + + onRegister: function(characteristic) { that.currentStateCharacteristic["LEVEL"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("LEVEL"); + }, + + perms: ["pr", "ev"], + format: "int", + initialValue: that.dpvalue("LEVEL", 0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Blind Position", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" }, - perms: ["pr","ev"], - format: "int", - initialValue: that.dpvalue("LEVEL",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Blind Position", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }, - - { - cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, - - onUpdate: function(value) { - that.delayed("set","LEVEL" , String(value/100),100); - }, - + { + cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, - onRead: function(callback) { - that.query("LEVEL",callback); - }, - - onRegister: function(characteristic) { + onUpdate: function(value) { + that.delayed("set", "LEVEL", String(value / 100), 100); + }, + + + onRead: function(callback) { + that.query("LEVEL", callback); + }, + + onRegister: function(characteristic) { that.currentStateCharacteristic["LEVEL"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("LEVEL"); - }, + }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: that.dpvalue("LEVEL",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Target Blind Position", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }, - { - cType: types.WINDOW_COVERING_OPERATION_STATE_CTYPE, - - onRead: function(callback) { - that.query("DIRECTION",callback); - }, - - onRegister: function(characteristic) { + perms: ["pw", "pr", "ev"], + format: "int", + initialValue: that.dpvalue("LEVEL", 0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Target Blind Position", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }, { + cType: types.WINDOW_COVERING_OPERATION_STATE_CTYPE, + + onRead: function(callback) { + that.query("DIRECTION", callback); + }, + + onRegister: function(characteristic) { that.currentStateCharacteristic["DIRECTION"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("DIRECTION"); - }, + }, + + perms: ["pr", "ev"], + format: "int", + initialValue: that.dpvalue("DIRECTION", 0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Operating State ", + designedMinValue: 0, + designedMaxValue: 2, + designedMinStep: 1 + } - perms: ["pr","ev"], - format: "int", - initialValue: that.dpvalue("DIRECTION",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Operating State ", - designedMinValue: 0, - designedMaxValue: 2, - designedMinStep: 1 - } - ); } - - if (this.type=="SHUTTER_CONTACT") { - cTypes.push( - { - cType: types.CONTACT_SENSOR_STATE_CTYPE, - + + if (this.type == "SHUTTER_CONTACT") { + cTypes.push({ + cType: types.CONTACT_SENSOR_STATE_CTYPE, + onRead: function(callback) { - that.query("STATE",callback); + that.query("STATE", callback); }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["STATE"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("STATE"); + + onRegister: function(characteristic) { + that.currentStateCharacteristic["STATE"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("STATE"); }, - - perms: ["pr","ev"], - format: "bool", - initialValue: that.dpvalue("STATE",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Current State" - }); - } - - if (this.type=="MOTION_DETECTOR") { - cTypes.push( - { - cType: types.MOTION_DETECTED_CTYPE, - + + perms: ["pr", "ev"], + format: "bool", + initialValue: that.dpvalue("STATE", 0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current State" + }); + } + + if (this.type == "MOTION_DETECTOR") { + cTypes.push({ + cType: types.MOTION_DETECTED_CTYPE, + onRead: function(callback) { - that.query("MOTION",callback); + that.query("MOTION", callback); }, - - onRegister: function(characteristic) { - that.currentStateCharacteristic["MOTION"] = characteristic; - characteristic.eventEnabled = true; - that.remoteGetValue("MOTION"); + + onRegister: function(characteristic) { + that.currentStateCharacteristic["MOTION"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("MOTION"); }, - - perms: ["pr","ev"], - format: "bool", - initialValue: that.dpvalue("MOTION",0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Motion State" - }); - } - - if (this.type=="CLIMATECONTROL_RT_TRANSCEIVER") { - - cTypes.push({ - cType: types.NAME_CTYPE,onUpdate: null,perms: ["pr"],format: "string", - initialValue: this.name,supportEvents: true,supportBonjour: false,manfDescription: "Name of service",designedMaxLength: 255 - }, - - { - cType: types.CURRENTHEATINGCOOLING_CTYPE,onUpdate: null, - perms: ["pr"],format: "int",initialValue: 1,supportEvents: false, - supportBonjour: false,manfDescription: "Current Mode",designedMaxLength: 1,designedMinValue: 1,designedMaxValue: 1,designedMinStep: 1 - }, - - { - cType: types.TARGETHEATINGCOOLING_CTYPE,onUpdate: null,perms: ["pw","pr"], - format: "int",initialValue: 1,supportEvents: false,supportBonjour: false,manfDescription: "Target Mode", - designedMinValue: 1,designedMaxValue: 1,designedMinStep: 1 - }, - - { - cType: types.CURRENT_TEMPERATURE_CTYPE, - onUpdate: null, - - onRead: function(callback) { - that.query("ACTUAL_TEMPERATURE",callback); - }, - - onRegister: function(characteristic) { + + perms: ["pr", "ev"], + format: "bool", + initialValue: that.dpvalue("MOTION", 0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Motion State" + }); + } + + if (this.type == "CLIMATECONTROL_RT_TRANSCEIVER") { + + cTypes.push({ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name, + supportEvents: true, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + }, + + { + cType: types.CURRENTHEATINGCOOLING_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "int", + initialValue: 1, + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Mode", + designedMaxLength: 1, + designedMinValue: 1, + designedMaxValue: 1, + designedMinStep: 1 + }, + + { + cType: types.TARGETHEATINGCOOLING_CTYPE, + onUpdate: null, + perms: ["pw", "pr"], + format: "int", + initialValue: 1, + supportEvents: false, + supportBonjour: false, + manfDescription: "Target Mode", + designedMinValue: 1, + designedMaxValue: 1, + designedMinStep: 1 + }, + + { + cType: types.CURRENT_TEMPERATURE_CTYPE, + onUpdate: null, + + onRead: function(callback) { + that.query("ACTUAL_TEMPERATURE", callback); + }, + + onRegister: function(characteristic) { that.currentStateCharacteristic["ACTUAL_TEMPERATURE"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("ACTUAL_TEMPERATURE"); - }, - perms: ["pw","pr","ev"], perms: ["pr"],format: "double", - initialValue: that.dpvalue("ACTUAL_TEMPERATURE",20), - supportEvents: false,supportBonjour: false,manfDescription: "Current Temperature",unit: "celsius" - }, - - { - cType: types.TARGET_TEMPERATURE_CTYPE, - onUpdate: function(value) { - that.delayed("set", "SET_TEMPERATURE", value,500); - }, - onRead: function(callback) { - that.query("SET_TEMPERATURE",callback); - - }, - onRegister: function(characteristic) { + }, + perms: ["pw", "pr", "ev"], + perms: ["pr"], + format: "double", + initialValue: that.dpvalue("ACTUAL_TEMPERATURE", 20), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Temperature", + unit: "celsius" + }, + + { + cType: types.TARGET_TEMPERATURE_CTYPE, + onUpdate: function(value) { + that.delayed("set", "SET_TEMPERATURE", value, 500); + }, + onRead: function(callback) { + that.query("SET_TEMPERATURE", callback); + + }, + onRegister: function(characteristic) { that.currentStateCharacteristic["SET_TEMPERATURE"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("SET_TEMPERATURE"); - }, - perms: ["pw","pr","ev"],format: "double", - initialValue: that.dpvalue("SET_TEMPERATURE",16), - supportEvents: false,supportBonjour: false, manfDescription: "Target Temperature", - designedMinValue: 16,designedMaxValue: 38,designedMinStep: 1,unit: "celsius" - }, - - { - cType: types.TEMPERATURE_UNITS_CTYPE,onRead: null, - perms: ["pr"],format: "int",initialValue: 0,supportEvents: false, - supportBonjour: false,manfDescription: "Current Temperature Unit",unit: "celsius" + }, + perms: ["pw", "pr", "ev"], + format: "double", + initialValue: that.dpvalue("SET_TEMPERATURE", 16), + supportEvents: false, + supportBonjour: false, + manfDescription: "Target Temperature", + designedMinValue: 16, + designedMaxValue: 38, + designedMinStep: 1, + unit: "celsius" + }, + + { + cType: types.TEMPERATURE_UNITS_CTYPE, + onRead: null, + perms: ["pr"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Temperature Unit", + unit: "celsius" + } + + ); } - ); - } - - + return cTypes }, sType: function() { - - if (this.type=="SWITCH") { - - if (this.special=="OUTLET") { - return types.OUTLET_STYPE; - } else { - return types.LIGHTBULB_STYPE; - } - } - - if (this.type=="DIMMER") { - return types.LIGHTBULB_STYPE; - } - if (this.type=="BLIND") { + if (this.type == "SWITCH") { + + if (this.special == "OUTLET") { + return types.OUTLET_STYPE; + } else { + return types.LIGHTBULB_STYPE; + } + } + + if (this.type == "DIMMER") { + return types.LIGHTBULB_STYPE; + } + + if (this.type == "BLIND") { return types.WINDOW_COVERING_STYPE; - } + } - if (this.type=="CLIMATECONTROL_RT_TRANSCEIVER") { + if (this.type == "CLIMATECONTROL_RT_TRANSCEIVER") { return types.THERMOSTAT_STYPE; - } - - if (this.type=="SHUTTER_CONTACT") { + } + + if (this.type == "SHUTTER_CONTACT") { return types.CONTACT_SENSOR_STYPE; - } - - if (this.type=="MOTION_DETECTOR") { - return types.MOTION_SENSOR_STYPE - } + } + + if (this.type == "MOTION_DETECTOR") { + return types.MOTION_SENSOR_STYPE + } + + + if (this.type == "KEYMATIC") { + return types.LOCK_MECHANISM_STYPE + } + - if (this.type=="KEYMATIC") { - return types.LOCK_MECHANISM_STYPE - } - - - }, getServices: function() { @@ -617,8 +650,7 @@ HomeMaticGenericChannel.prototype = { var services = [{ sType: types.ACCESSORY_INFORMATION_STYPE, characteristics: this.informationCharacteristics(), - }, - { + }, { sType: this.sType(), characteristics: this.controlCharacteristics(that) }]; From 7623bf4204fdc672a5a18398ad3374dced027844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Ko=CC=88nig?= Date: Thu, 29 Oct 2015 20:28:00 +0100 Subject: [PATCH 75/89] cleanup formatting --- platforms/HomeMatic.js | 121 ++++++++++++++++---------------- platforms/HomematicChannel.js | 128 ++++++++++++++++------------------ 2 files changed, 122 insertions(+), 127 deletions(-) diff --git a/platforms/HomeMatic.js b/platforms/HomeMatic.js index 1b99729..a451451 100644 --- a/platforms/HomeMatic.js +++ b/platforms/HomeMatic.js @@ -1,3 +1,4 @@ +"use strict"; // // Homematic Platform Shim for HomeBridge // @@ -7,13 +8,13 @@ var types = require("hap-nodejs/accessories/types.js"); -var xmlrpc = require('homematic-xmlrpc') +var xmlrpc = require("homematic-xmlrpc"); var request = require("request"); var http = require("http"); var path = require("path"); -var HomeMaticGenericChannel = require(path.resolve(__dirname, 'HomematicChannel.js')); +var HomeMaticGenericChannel = require(path.resolve(__dirname, "HomematicChannel.js")); @@ -28,29 +29,29 @@ RegaRequest.prototype = { var post_options = { host: this.ccuIP, - port: '80', - path: '/tclrega.exe', - method: 'POST', + port: "80", + path: "/tclrega.exe", + method: "POST", headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Content-Length': script.length + "Content-Type": "application/x-www-form-urlencoded", + "Content-Length": script.length } }; var post_req = http.request(post_options, function(res) { var data = ""; - res.setEncoding('binary'); - res.on('data', function(chunk) { + res.setEncoding("binary"); + res.on("data", function(chunk) { data += chunk.toString(); }); - res.on('end', function() { + res.on("end", function() { var pos = data.lastIndexOf(""); var response = (data.substring(0, pos)); callback(response); }); }); - post_req.on('error', function(e) { + post_req.on("error", function(e) { callback("{}"); }); @@ -65,23 +66,24 @@ RegaRequest.prototype = { var script = "var d = dom.GetObject(\"" + channel + "." + datapoint + "\");if (d){Write(d.State());}"; //that.log("Rega Request " + script); - var regarequest = this.script(script, function(data) { + this.script(script, function(data) { that.log("Rega Response" + data); - if (data != undefined) { + if (data !== undefined) { callback(parseFloat(data)); } }); }, setValue: function(channel, datapoint, value) { - var that = this; var script = "var d = dom.GetObject(\"" + channel + "." + datapoint + "\");if (d){d.State(\"" + value + "\");}"; - //that.log("Rega Request " + script); - var regarequest = this.script(script, function(data) {}); + //this.log("Rega Request " + script); + this.script(script, function(data) { + + }); } -} +}; function HomematicRPC(log, ccuip, platform) { this.log = log; @@ -106,29 +108,29 @@ HomematicRPC.prototype = { } this.localIP = ip; - this.log("Local IP: " + this.localIP) + this.log("Local IP: " + this.localIP); this.server = xmlrpc.createServer({ host: this.localIP, port: 9090 - }) - - this.server.on('NotFound', function(method, params) { - that.log('Method ' + method + ' does not exist'); }); - this.server.on('system.listMethods', function(err, params, callback) { - that.log('Method call params for \'system.listMethods\': ' + params) - callback(null, ['system.listMethods', 'system.multicall']); + this.server.on("NotFound", function(method, params) { + that.log("Method " + method + " does not exist"); + }); + + this.server.on("system.listMethods", function(err, params, callback) { + that.log("Method call params for 'system.listMethods': " + params); + callback(null, ["system.listMethods", "system.multicall"]); }); - this.server.on('system.multicall', function(err, params, callback) { + this.server.on("system.multicall", function(err, params, callback) { params.map(function(events) { try { events.map(function(event) { - if ((event["methodName"] == "event") && (event['params'] != undefined)) { - var params = event['params']; + if ((event["methodName"] == "event") && (event["params"] !== undefined)) { + var params = event["params"]; var channel = "BidCos-RF." + params[1]; var datapoint = params[2]; var value = params[3]; @@ -144,11 +146,11 @@ HomematicRPC.prototype = { callback(null); }); - this.log('XML-RPC server listening on port 9090') + this.log("XML-RPC server listening on port 9090"); this.connect(); - process.on('SIGINT', function() { + process.on("SIGINT", function() { if (that.stopping) { return; } @@ -156,7 +158,7 @@ HomematicRPC.prototype = { that.stop(); }); - process.on('SIGTERM', function() { + process.on("SIGTERM", function() { if (that.stopping) { return; } @@ -167,29 +169,28 @@ HomematicRPC.prototype = { }, getIPAddress: function() { - var interfaces = require('os').networkInterfaces(); + var interfaces = require("os").networkInterfaces(); for (var devName in interfaces) { var iface = interfaces[devName]; for (var i = 0; i < iface.length; i++) { var alias = iface[i]; - if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) + if (alias.family === "IPv4" && alias.address !== "127.0.0.1" && !alias.internal) return alias.address; } } - return '0.0.0.0'; + return "0.0.0.0"; }, getValue: function(channel, datapoint, callback) { var that = this; - if (this.client == undefined) { + if (this.client === undefined) { that.log("Returning cause client is invalid"); return; } if (channel.indexOf("BidCos-RF.") > -1)  { channel = channel.substr(10); - this.log("Calling rpc getValue"); - this.client.methodCall('getValue', [channel, datapoint], function(error, value) { + this.client.methodCall("getValue", [channel, datapoint], function(error, value) { callback(value); }); return; @@ -200,41 +201,41 @@ HomematicRPC.prototype = { var that = this; - if (this.client == undefined) return; + if (this.client === undefined) return; if (channel.indexOf("BidCos-RF.") > -1)  { channel = channel.substr(10); } - this.client.methodCall('setValue', [channel, datapoint, value], function(error, value) { + this.client.methodCall("setValue", [channel, datapoint, value], function(error, value) { }); }, connect: function() { var that = this; - this.log('Creating Local HTTP Client for CCU RPC Events'); + this.log("Creating Local HTTP Client for CCU RPC Events"); this.client = xmlrpc.createClient({ host: this.ccuip, port: 2001, - path: '/' + path: "/" }); - this.log('CCU RPC Init Call on port 2001'); - this.client.methodCall('init', ['http://' + this.localIP + ':9090', 'homebridge'], function(error, value) { - that.log('CCU Response ....') + this.log("CCU RPC Init Call on port 2001"); + this.client.methodCall("init", ["http://" + this.localIP + ":9090", "homebridge"], function(error, value) { + that.log("CCU Response ...."); }); }, stop: function() { this.log("Removing Event Server"); - this.client.methodCall('init', ['http://' + this.localIP + ':9090'], function(error, value) { + this.client.methodCall("init", ["http://" + this.localIP + ":9090"], function(error, value) { }); setTimeout(process.exit(0), 1000); } -} +}; function HomeMaticPlatform(log, config) { @@ -267,35 +268,35 @@ HomeMaticPlatform.prototype = { var regarequest = new RegaRequest(this.log, this.ccuIP).script(script, function(data) { var json = JSON.parse(data); - if (json['devices'] != undefined) { - json['devices'].map(function(device) { + if (json["devices"] !== undefined) { + json["devices"].map(function(device) { var isFiltered = false; - if ((that.filter_device != undefined) && (that.filter_device.indexOf(device.address) > -1)) { + if ((that.filter_device !== undefined) && (that.filter_device.indexOf(device.address) > -1)) { isFiltered = true; } else { isFiltered = false; } // that.log('device address:', device.address); - if ((device['channels'] != undefined) && (!isFiltered)) { + if ((device["channels"] !== undefined) && (!isFiltered)) { - device['channels'].map(function(ch) { + device["channels"].map(function(ch) { var isChannelFiltered = false; - if ((that.filter_channel != undefined) && (that.filter_channel.indexOf(ch.address) > -1)) { + if ((that.filter_channel !== undefined) && (that.filter_channel.indexOf(ch.address) > -1)) { isChannelFiltered = true; } else { isChannelFiltered = false; } // that.log('name', ch.name, ' -> address:', ch.address); - if ((ch.address != undefined) && (!isChannelFiltered)) { + if ((ch.address !== undefined) && (!isChannelFiltered)) { if ((ch.type == "SWITCH") || (ch.type == "BLIND") || (ch.type == "SHUTTER_CONTACT") || (ch.type == "DIMMER") || (ch.type == "CLIMATECONTROL_RT_TRANSCEIVER") ||  (ch.type == "MOTION_DETECTOR") ||  (ch.type == "KEYMATIC")) { // Switch found // Check if marked as Outlet - var special = (that.outlets.indexOf(ch.address) > -1) ? 'OUTLET' : undefined; - accessory = new HomeMaticGenericChannel(that.log, that, ch.id, ch.name, ch.type, ch.address, special); + var special = (that.outlets.indexOf(ch.address) > -1) ? "OUTLET" : undefined; + var accessory = new HomeMaticGenericChannel(that.log, that, ch.id, ch.name, ch.type, ch.address, special); that.foundAccessories.push(accessory); } @@ -370,14 +371,14 @@ HomeMaticPlatform.prototype = { script = script + command; }); this.sendQueue = []; - //this.log("RegaSend: " + script); + //this.log('RegaSend: ' + script); var regarequest = new RegaRequest(this.log, this.ccuIP).script(script, function(data) {}); }, sendRequest: function(accessory, script, callback) { - var that = this; + var regarequest = new RegaRequest(this.log, this.ccuIP).script(script, function(data) { - if (data != undefined) { + if (data !== undefined) { try { var json = JSON.parse(data); callback(json); @@ -399,11 +400,11 @@ HomeMaticPlatform.prototype = { var that = this; this.delayed[delay] = setTimeout(function() { clearTimeout(that.delayed[delay]); - that.sendPreparedRequests() + that.sendPreparedRequests(); }, delay ? delay : 100); this.log("New Timer was set"); } -} +}; diff --git a/platforms/HomematicChannel.js b/platforms/HomematicChannel.js index 4e5a34b..5b6cff2 100644 --- a/platforms/HomematicChannel.js +++ b/platforms/HomematicChannel.js @@ -1,3 +1,4 @@ +"use strict"; var types = require("hap-nodejs/accessories/types.js"); @@ -21,9 +22,7 @@ HomeMaticGenericChannel.prototype = { // Return current States query: function(dp, callback) { - var that = this; - - if (this.state[dp] != undefined) { + if (this.state[dp] !== undefined) { callback(this.state[dp]); } else { // that.log("No cached Value found start fetching and send temp 0 back"); @@ -34,7 +33,7 @@ HomeMaticGenericChannel.prototype = { }, dpvalue: function(dp, fallback) { - if (this.state[dp] != undefined) { + if (this.state[dp] !== undefined) { return (this.state[dp]); } else { return fallback; @@ -66,8 +65,8 @@ HomeMaticGenericChannel.prototype = { reverse: function(value) { if (value == "true") return "false"; if (value == "false") return "true"; - if (value == 0) return 1; - if (value == 1) return 0; + if (value === 0) return 1; + if (value === 1) return 0; if (value == "0") return "1"; if (value == "1") return "0"; return value; @@ -76,11 +75,11 @@ HomeMaticGenericChannel.prototype = { cache: function(dp, value) { var that = this; - if ((that.reverseDP[dp] != undefined) && (that.reverseDP[dp] == true)) { + if ((that.reverseDP[dp] !== undefined) && (that.reverseDP[dp] === true)) { value = that.reverse(value); } - if (that.currentStateCharacteristic[dp] != undefined) { + if (that.currentStateCharacteristic[dp] !== undefined) { that.currentStateCharacteristic[dp].updateValue(value, null); } this.state[dp] = value; @@ -89,7 +88,7 @@ HomeMaticGenericChannel.prototype = { delayed: function(mode, dp, value, delay) { - if (this.eventupdate == true) { + if (this.eventupdate === true) { return; } @@ -102,13 +101,13 @@ HomeMaticGenericChannel.prototype = { var that = this; this.delayed[delay] = setTimeout(function() { clearTimeout(that.delayed[delay]); - that.command(mode, dp, value) + that.command(mode, dp, value); }, delay ? delay : 100); }, command: function(mode, dp, value, callback) { - if (this.eventupdate == true) { + if (this.eventupdate === true) { return; } var that = this; @@ -170,12 +169,12 @@ HomeMaticGenericChannel.prototype = { supportBonjour: false, manfDescription: "Identify Accessory", designedMaxLength: 1 - }] + }]; }, controlCharacteristics: function(that) { - cTypes = [{ + var cTypes = [{ cType: types.NAME_CTYPE, onUpdate: null, perms: ["pr"], @@ -185,14 +184,14 @@ HomeMaticGenericChannel.prototype = { supportBonjour: false, manfDescription: "Name of service", designedMaxLength: 255 - }] + }]; if (this.type == "SWITCH") { cTypes.push({ cType: types.POWER_STATE_CTYPE, onUpdate: function(value) { - that.command("set", "STATE", (value == 1) ? true : false) + that.command("set", "STATE", (value == 1) ? true : false); }, onRead: function(callback) { @@ -228,37 +227,37 @@ HomeMaticGenericChannel.prototype = { supportBonjour: false, manfDescription: "Is Outlet in Use", designedMaxLength: 1 - }) + }); } } if (this.type == "KEYMATIC") { cTypes.push({ - cType: types.CURRENT_LOCK_MECHANISM_STATE_CTYPE, + cType: types.CURRENT_LOCK_MECHANISM_STATE_CTYPE, - onRead: function(callback) { + onRead: function(callback) { that.query("STATE", callback); }, - onRegister: function(characteristic) { + onRegister: function(characteristic) { that.currentStateCharacteristic["STATE"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("STATE"); }, - perms: ["pr", "ev"], - format: "bool", - initialValue: that.dpvalue("STATE", 0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Current State of your Lock", - designedMaxLength: 1 - }, { + perms: ["pr", "ev"], + format: "bool", + initialValue: that.dpvalue("STATE", 0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current State of your Lock", + designedMaxLength: 1 + }, { cType: types.TARGET_LOCK_MECHANISM_STATE_CTYPE, onUpdate: function(value) { - that.command("set", "STATE", (value == 1) ? "true" : "false") + that.command("set", "STATE", (value == 1) ? "true" : "false"); }, onRead: function(callback) { @@ -280,13 +279,13 @@ HomeMaticGenericChannel.prototype = { supportBonjour: false, manfDescription: "Target State of your Lock", designedMaxLength: 1 - } + }, - , { + { cType: types.TARGET_DOORSTATE_CTYPE, onUpdate: function(value) { - that.command("set", "OPEN", "true") + that.command("set", "OPEN", "true"); }, onRead: function(callback) { @@ -307,17 +306,13 @@ HomeMaticGenericChannel.prototype = { designedMaxLength: 1 } ); - - } - - if (this.type == "DIMMER") { cTypes.push({ cType: types.POWER_STATE_CTYPE, onUpdate: function(value) { - that.command("set", "LEVEL", (value == true) ? "1" : "0") + that.command("set", "LEVEL", (value == true) ? "1" : "0"); }, onRead: function(callback) { @@ -369,29 +364,29 @@ HomeMaticGenericChannel.prototype = { if (this.type == "BLIND") { cTypes.push({ - cType: types.WINDOW_COVERING_CURRENT_POSITION_CTYPE, + cType: types.WINDOW_COVERING_CURRENT_POSITION_CTYPE, - onRead: function(callback) { + onRead: function(callback) { that.query("LEVEL", callback); }, - onRegister: function(characteristic) { + onRegister: function(characteristic) { that.currentStateCharacteristic["LEVEL"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("LEVEL"); }, - perms: ["pr", "ev"], - format: "int", - initialValue: that.dpvalue("LEVEL", 0), - supportEvents: false, - supportBonjour: false, - manfDescription: "Current Blind Position", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }, + perms: ["pr", "ev"], + format: "int", + initialValue: that.dpvalue("LEVEL", 0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Blind Position", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }, { cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, @@ -497,16 +492,16 @@ HomeMaticGenericChannel.prototype = { if (this.type == "CLIMATECONTROL_RT_TRANSCEIVER") { cTypes.push({ - cType: types.NAME_CTYPE, - onUpdate: null, - perms: ["pr"], - format: "string", - initialValue: this.name, - supportEvents: true, - supportBonjour: false, - manfDescription: "Name of service", - designedMaxLength: 255 - }, + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name, + supportEvents: true, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + }, { cType: types.CURRENTHEATINGCOOLING_CTYPE, @@ -550,8 +545,7 @@ HomeMaticGenericChannel.prototype = { characteristic.eventEnabled = true; that.remoteGetValue("ACTUAL_TEMPERATURE"); }, - perms: ["pw", "pr", "ev"], - perms: ["pr"], + perms: ["pr", "ev"], format: "double", initialValue: that.dpvalue("ACTUAL_TEMPERATURE", 20), supportEvents: false, @@ -602,7 +596,7 @@ HomeMaticGenericChannel.prototype = { } - return cTypes + return cTypes; }, sType: function() { @@ -633,12 +627,12 @@ HomeMaticGenericChannel.prototype = { } if (this.type == "MOTION_DETECTOR") { - return types.MOTION_SENSOR_STYPE + return types.MOTION_SENSOR_STYPE; } if (this.type == "KEYMATIC") { - return types.LOCK_MECHANISM_STYPE + return types.LOCK_MECHANISM_STYPE; } @@ -649,12 +643,12 @@ HomeMaticGenericChannel.prototype = { var that = this; var services = [{ sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: this.informationCharacteristics(), + characteristics: this.informationCharacteristics() }, { sType: this.sType(), characteristics: this.controlCharacteristics(that) }]; - this.log("Loaded services for " + this.name) + this.log("Loaded services for " + this.name); return services; } }; From 1dfa190b18815dbf3ba8d5846763da3b728aa1f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20K=C3=B6nig?= Date: Thu, 29 Oct 2015 21:04:32 +0100 Subject: [PATCH 76/89] Add HomeMatic Platform sample --- config-sample.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/config-sample.json b/config-sample.json index 0e04cfe..a199ea6 100644 --- a/config-sample.json +++ b/config-sample.json @@ -115,7 +115,15 @@ "username": "your netatmo username", "password": "your netatmo password" } - } + }, + { + "platform": "HomeMatic", + "name": "HomeMatic CCU", + "ccu_ip": "192.168.0.100", + "filter_device":[], + "filter_channel":["BidCos-RF.KEQXXXXXXX:4", "BidCos-RF.LEQXXXXXXX:2"], + "outlets":[ "BidCos-RF.KEQXXXXXXX:4","BidCos-RF.IEQXXXXXXX:1"] + }, ], "accessories": [ From 70f2dc6230da7e5e16c199fd8352dad537882164 Mon Sep 17 00:00:00 2001 From: bwilliot Date: Sat, 31 Oct 2015 11:30:01 +0100 Subject: [PATCH 77/89] Update PhilipsHue.js fix issue --- platforms/PhilipsHue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 9d3394c..81f36b8 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -239,7 +239,7 @@ PhilipsHueAccessory.prototype = { if (err.code == "ECONNRESET") { setTimeout(function() { this.executeChange(characteristic, value, callback); - }, 300); + }.bind(this), 300); } else { this.log(err); callback(new Error(err)); From 5c921570097d44f251126b4683fa945c1312e133 Mon Sep 17 00:00:00 2001 From: Thomas Kluge Date: Sat, 31 Oct 2015 16:28:51 +0100 Subject: [PATCH 78/89] removed setup --- setup.sh | 104 ------------------------------------------------------- 1 file changed, 104 deletions(-) delete mode 100644 setup.sh diff --git a/setup.sh b/setup.sh deleted file mode 100644 index 8bb9164..0000000 --- a/setup.sh +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env - -# Check if we can use colours in our output -use_colour=0 -[ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null && use_colour=1 - -# Some useful functions -progress() { - [ $use_colour -eq 1 ] && echo -ne "\033[01;32m" - echo "$@" >&2 - [ $use_colour -eq 1 ] && echo -ne "\033[00m" -} - -info() { - [ $use_colour -eq 1 ] && echo -ne "\033[01;34m" - echo "$@" >&2 - [ $use_colour -eq 1 ] && echo -ne "\033[00m" -} - -die () { - [ $use_colour -eq 1 ] && echo -ne "\033[01;31m" - echo "$@" >&2 - [ $use_colour -eq 1 ] && echo -ne "\033[00m" - exit 1 -} - -install_package() { - package=$1 - info "install ${package}" - sudo apt-get -y --force-yes install $package 2>&1 > /dev/null - return $? -} - -# check architecture -sudo test "`dpkg --print-architecture`" == "armhf" || die "This Repos is only for armhf." - -# set timezone and update system -info "Setting up locale and keyboard" -sudo dpkg-reconfigure locales - -TIMEZONE="Europe/Berlin" -echo $TIMEZONE | sudo tee /etc/timezone -sudo cp /usr/share/zoneinfo/${TIMEZONE} /etc/localtime -sudo dpkg-reconfigure -f noninteractive tzdata - -info "Setting up Hostname" -echo 'Homebridge' | sudo tee /etc/hostname - -info "Cleaning up" -sudo dpkg --configure -a - -info "Update Package Lists this may take some time (10-20 min) depending on your internet connection" -sudo apt-get update -y -sudo apt-get dist-upgrade -y -info "Done" - -info "Installing Zeroconf" - -install_package "libavahi-compat-libdnssd-dev" -install_package "gcc-4.8 g++-4.8" -install_package "libkrb5-dev" - -info "Installing node" -wget https://s3-eu-west-1.amazonaws.com/conoroneill.net/wp-content/uploads/2015/03/node-v0.12.1-linux-arm-pi.tar.gz -tar -zxvf node-v0.12.1-linux-arm-pi.tar.gz -cd node-v0.12.1-linux-arm-pi -sudo cp -R * /usr/local/ - - -info "Cloning Repository" -cd /home/pi -git clone -b master --single-branch https://github.com/thkl/homebridge.git -cd homebridge - -info "Installing Node Modules" -npm install - -info "Setup" - -hazconfig="$(cat /home/pi/homebridge/config.json| grep 'bridge' | wc -l)" -if [ "$hazconfig" = "0" ]; then - - CCUIP=$(whiptail --inputbox "Please enter your CCU IP" 20 60 "000.000.000.000" 3>&1 1>&2 2>&3) - if [ $? -eq 0 ]; then - echo "{\"bridge\": {\"name\": \"Homebridge\", \"username\": \"CC:22:3D:E3:CE:30\",\"port\": 51826,\"pin\": \"031-45-154\"}," >> /home/pi/homebridge/config.json; - echo "\"description\": \"This is an autogenerated config. only the homematic platform is enabled. see the sample for more\"," >> /home/pi/homebridge/config.json; - echo "\"platforms\": [" >> /home/pi/homebridge/config.json; - echo "{\"platform\": \"HomeMaticPlatform\",\"name\": \"HomeMatic CCU\",\"ccu_ip\": \"$CCUIP\"," >> /home/pi/homebridge/config.json; - echo "\"filter_device\":[],\"filter_channel\":[],\"outlets\":[]}" >> /home/pi/homebridge/config.json; - echo "],\"accessories\": []}" >> /home/pi/homebridge/config.json; - fi -fi - -whiptail --yesno "Would you like to start homebridge at boot by default?" $DEFAULT 20 60 2 -RET=$? -if [ $RET -eq 0 ]; then - sudo cp /home/pi/homebridge/homebridge.txt /etc/init.d/homebridge - sudo chmod 755 /etc/init.d/homebridge - sudo update-rc.d homebridge defaults -fi - -info "Done. If there are no error messages you are done." -info "Your config is ready to use" -info "to start the homebridge goto /home/pi/homebridge and call npm run start." From 73b61fd73091e764a3d259ef648a66fdc4d0479a Mon Sep 17 00:00:00 2001 From: ilcato Date: Sat, 31 Oct 2015 20:02:29 +0100 Subject: [PATCH 79/89] Added Fibaro Virtual devices management --- platforms/FibaroHC2.js | 47 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/platforms/FibaroHC2.js b/platforms/FibaroHC2.js index 57c880c..cdaae6c 100644 --- a/platforms/FibaroHC2.js +++ b/platforms/FibaroHC2.js @@ -66,6 +66,29 @@ FibaroHC2Platform.prototype = { accessory = new FibaroAccessory(new Service.LightSensor(s.name), [Characteristic.CurrentAmbientLightLevel]); else if (s.type == "com.fibaro.FGWP101") accessory = new FibaroAccessory(new Service.Outlet(s.name), [Characteristic.On, Characteristic.OutletInUse]); + else if (s.type == "virtual_device") { + for (var r = 0; r < s.properties.rows.length; r++) { + if (s.properties.rows[r].type == "button") { + for (var e = 0; e < s.properties.rows[r].elements.length; e++) { + var name = s.properties.rows[r].elements[e].caption; + var virtualButton = new FibaroAccessory(new Service.Switch(name), [Characteristic.On]); + virtualButton.buttonId = s.properties.rows[r].elements[e].id; + virtualButton.getServices = function() { + return that.getServices(this); + }; + virtualButton.platform = that; + virtualButton.remoteAccessory = s; + virtualButton.id = s.id; + virtualButton.name = name; + virtualButton.model = "Virtual Button"; + virtualButton.manufacturer = "Fibaro"; + virtualButton.serialNumber = ""; + foundAccessories.push(virtualButton); + that.log("Service name: " + virtualButton.controlService.displayName + ", Accessory name: " + virtualButton.name); + } + } + } + } if (accessory != null) { accessory.getServices = function() { return that.getServices(accessory); @@ -159,18 +182,29 @@ FibaroHC2Platform.prototype = { if (!readOnly) { characteristic .on('set', function(value, callback, context) { - if( context !== 'fromFibaro' ) { - if (onOff) + if( context !== 'fromFibaro' && context !== 'fromSetValue') { + if (homebridgeAccessory.buttonId != null) { + homebridgeAccessory.platform.command("pressButton", homebridgeAccessory.buttonId, homebridgeAccessory); + // In order to behave like a push button reset the status to off + setTimeout( function(){ + characteristic.setValue(false, undefined, 'fromSetValue'); + }, 100 ); + } else if (onOff) { homebridgeAccessory.platform.command(value == 0 ? "turnOff": "turnOn", null, homebridgeAccessory); - else + } else homebridgeAccessory.platform.command("setValue", value, homebridgeAccessory); - } - callback(); + } + callback(); }.bind(this) ); } characteristic .on('get', function(callback) { - homebridgeAccessory.platform.getAccessoryValue(callback, onOff, homebridgeAccessory, powerValue); + if (homebridgeAccessory.buttonId != null) { + // a push button is normally off + callback(undefined, false); + } else { + homebridgeAccessory.platform.getAccessoryValue(callback, onOff, homebridgeAccessory, powerValue); + } }.bind(this) ); }, getServices: function(homebridgeAccessory) { @@ -251,3 +285,4 @@ function subscribeUpdate(characteristic, accessory, onOff) } module.exports.platform = FibaroHC2Platform; + From 9f426892568736b651ae9e7498c569df91b3e8e6 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Sun, 1 Nov 2015 02:29:11 +0100 Subject: [PATCH 80/89] added serialport library as a dependency --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index e1de444..7bca23e 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "wink-js": "0.0.5", "xml2js": "0.4.x", "xmldoc": "0.1.x", - "yamaha-nodejs": "0.4.x" + "yamaha-nodejs": "0.4.x", + "serialport": "2.0.x" } } From c1cc8be8fabc13baa398c515f31f35b3d0001b9d Mon Sep 17 00:00:00 2001 From: Thomas Kluge Date: Sun, 1 Nov 2015 12:57:29 +0100 Subject: [PATCH 81/89] new Devices : Smokedetector --- platforms/HomeMatic.js | 2 +- platforms/HomematicChannel.js | 32 +++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/platforms/HomeMatic.js b/platforms/HomeMatic.js index f140f3d..6f3a6aa 100644 --- a/platforms/HomeMatic.js +++ b/platforms/HomeMatic.js @@ -330,7 +330,7 @@ HomeMaticPlatform.prototype = { }); /* - accessory = new HomeMaticGenericChannel(that.log, that, "1234" , "DummyKM" , "KEYMATIC" , "1234"); + var accessory = new HomeMaticGenericChannel(that.log, that, "1234" , "DummyKM" , "SMOKE_DETECTOR" , "1234"); that.foundAccessories.push(accessory); accessory = new HomeMaticGenericChannel(that.log, that, "5678" , "DummyBLIND" , "BLIND" , "5678"); diff --git a/platforms/HomematicChannel.js b/platforms/HomematicChannel.js index 786b556..f15eb58 100644 --- a/platforms/HomematicChannel.js +++ b/platforms/HomematicChannel.js @@ -544,6 +544,31 @@ HomeMaticGenericChannel.prototype = { }); } + // Smoke Detector + if (this.type=="SMOKE_DETECTOR") { + cTypes.push( + { + cType: "00000076-0000-1000-8000-0026BB765291", + + onRead: function(callback) { + that.query("STATE",callback); + }, + + onRegister: function(characteristic) { + that.currentStateCharacteristic["STATE"] = characteristic; + characteristic.eventEnabled = true; + that.remoteGetValue("STATE"); + }, + + perms: ["pr","ev"], + format: "bool", + initialValue: that.dpvalue("STATE",0), + supportEvents: false, + supportBonjour: false, + manfDescription: "Smoke detected" + }); + } + // Heating Device if ((this.type=="CLIMATECONTROL_RT_TRANSCEIVER") || (this.type=="THERMALCONTROL_TRANSMIT")) { @@ -588,8 +613,9 @@ HomeMaticGenericChannel.prototype = { onUpdate: function(value) { if (that.state["CONTROL_MODE"]!=1) { that.delayed("setrega", "MANU_MODE",value,500); + that.state["CONTROL_MODE"]=1; // set to Manual Mode } else { - that.delayed("set", "SET_TEMPERATURE", value,500); + that.delayed("setrega", "SET_TEMPERATURE", value,500); } }, onRead: function(callback) { @@ -601,6 +627,7 @@ HomeMaticGenericChannel.prototype = { that.currentStateCharacteristic["SET_TEMPERATURE"] = characteristic; characteristic.eventEnabled = true; that.remoteGetValue("SET_TEMPERATURE"); + that.remoteGetValue("CONTROL_MODE"); }, perms: ["pw","pr","ev"],format: "double", initialValue: that.dpvalue("SET_TEMPERATURE",16), @@ -657,6 +684,9 @@ HomeMaticGenericChannel.prototype = { return types.LOCK_MECHANISM_STYPE } + if (this.type=="SMOKE_DETECTOR") { + return "00000087-0000-1000-8000-0026BB765291"; + } }, From bc1c879412be875bc28c6d0efa077b0c9adc6654 Mon Sep 17 00:00:00 2001 From: Kraig McConaghy Date: Sun, 1 Nov 2015 10:16:54 -0600 Subject: [PATCH 82/89] Fixed humidity and added change handling of settings (like Celsius/Fahrenheit) --- platforms/Nest.js | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/platforms/Nest.js b/platforms/Nest.js index 1e81fa2..6f90e7c 100644 --- a/platforms/Nest.js +++ b/platforms/Nest.js @@ -41,14 +41,26 @@ NestPlatform.prototype = { } } function subscribe() { - nest.subscribe(subscribeDone, ['shared']); + nest.subscribe(subscribeDone, ['device','shared']); } function subscribeDone(deviceId, data, type) { // data if set, is also stored here: nest.lastStatus.shared[thermostatID] if (deviceId && that.accessoryLookup[deviceId]) { that.log('Update to Device: ' + deviceId + " type: " + type); - that.accessoryLookup[deviceId].updateData(data); + var accessory = that.accessoryLookup[deviceId]; + if (accessory) { + switch (type) { + case 'shared': + accessory.updateData(data); + break; + case 'device': + accessory.device = data; + accessory.updateData(); + break; + } + } + } setTimeout(subscribe, 2000); } @@ -106,6 +118,14 @@ function NestThermostatAccessory(log, name, device, deviceId, initialData) { if (callback) callback(null, curHeatingCooling); }.bind(this)); + this.getService(Service.Thermostat) + .getCharacteristic(Characteristic.CurrentRelativeHumidity) + .on('get', function(callback) { + var curHumidity = this.getCurrentRelativeHumidity(); + this.log("Current humidity for " + this.name + " is: " + curHumidity); + if (callback) callback(null, curHumidity); + }.bind(this)); + this.getService(Service.Thermostat) .getCharacteristic(Characteristic.TargetTemperature) .on('get', function(callback) { @@ -141,6 +161,7 @@ NestThermostatAccessory.prototype.updateData = function(data) { thermostat.getCharacteristic(Characteristic.TemperatureDisplayUnits).getValue(); thermostat.getCharacteristic(Characteristic.CurrentTemperature).getValue(); thermostat.getCharacteristic(Characteristic.CurrentHeatingCoolingState).getValue(); + thermostat.getCharacteristic(Characteristic.CurrentRelativeHumidity).getValue(); thermostat.getCharacteristic(Characteristic.TargetHeatingCoolingState).getValue(); thermostat.getCharacteristic(Characteristic.TargetTemperature).getValue(); }; @@ -186,6 +207,10 @@ NestThermostatAccessory.prototype.getCurrentTemperature = function(){ return this.currentData.current_temperature; }; +NestThermostatAccessory.prototype.getCurrentRelativeHumidity = function(){ + return this.device.current_humidity; +}; + NestThermostatAccessory.prototype.getTargetTemperature = function() { switch (this.getTargetHeatingCooling()) { case Characteristic.CurrentHeatingCoolingState.HEAT | Characteristic.CurrentHeatingCoolingState.COOL: From 82c62f78edcf19927ec4c24b98f567c199ffe670 Mon Sep 17 00:00:00 2001 From: Kraig McConaghy Date: Sun, 1 Nov 2015 12:42:03 -0600 Subject: [PATCH 83/89] Fixed unreachable PhilipsHue bulbs to show up as off. If you have a manual switch hooked up to a hue bulb, turning it off the switch makes it unreachable but doesn't mark it as off. --- platforms/PhilipsHue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 81f36b8..7ba1f0e 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -190,7 +190,7 @@ PhilipsHueAccessory.prototype = { extractValue: function(characteristic, status) { switch(characteristic.toLowerCase()) { case 'power': - return status.state.on ? 1 : 0; + return status.state.reachable && status.state.on ? 1 : 0; case 'hue': return this.hueToArcDegrees(status.state.hue); case 'brightness': From 91044f0d9256bbfce7580105414a5deb2d589414 Mon Sep 17 00:00:00 2001 From: Kevin Mathy Date: Sun, 1 Nov 2015 22:00:17 +0100 Subject: [PATCH 84/89] Http.js shim modification corrections --- accessories/Http.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/accessories/Http.js b/accessories/Http.js index a5475a7..96d8846 100644 --- a/accessories/Http.js +++ b/accessories/Http.js @@ -101,8 +101,7 @@ HttpAccessory.prototype = { switchService .getCharacteristic(Characteristic.On) - .on('get', this.getPowerOn.bind(this)) - .on('set', this.setPowerOn.bind(this)); + .on('set', this.setPowerState.bind(this)); return [switchService]; } else if (this.service == "Light") { From 6011ad3d2ed7aac7e0574a865e21618af2fff2ed Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 2 Nov 2015 23:55:15 +0100 Subject: [PATCH 85/89] The HTTP accessory now supports a body value. --- accessories/Http.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/accessories/Http.js b/accessories/Http.js index 96d8846..24d01df 100644 --- a/accessories/Http.js +++ b/accessories/Http.js @@ -11,7 +11,9 @@ function HttpAccessory(log, config) { // url info this.on_url = config["on_url"]; + this.on_body = config["on_body"]; this.off_url = config["off_url"]; + this.off_body = config["off_body"]; this.brightness_url = config["brightness_url"]; this.http_method = config["http_method"]; this.username = config["username"]; @@ -23,9 +25,10 @@ function HttpAccessory(log, config) { HttpAccessory.prototype = { - httpRequest: function(url, method, username, password, callback) { + httpRequest: function(url, body, method, username, password, callback) { request({ url: url, + body: body, method: method, auth: { user: username, @@ -40,16 +43,19 @@ HttpAccessory.prototype = { setPowerState: function(powerOn, callback) { var url; + var body; if (powerOn) { url = this.on_url; + body = this.on_body; this.log("Setting power state to on"); } else { url = this.off_url; + body = this.off_body; this.log("Setting power state to off"); } - this.httpRequest(url, this.http_method, this.username, this.password, function(error, response, body) { + this.httpRequest(url, body, this.http_method, this.username, this.password, function(error, response, body) { if (error) { this.log('HTTP power function failed: %s', error.message); callback(error); @@ -69,7 +75,7 @@ HttpAccessory.prototype = { this.log("Setting brightness to %s", level); - this.httpRequest(url, this.http_method, this.username, this.password, function(error, response, body) { + this.httpRequest(url, "", this.http_method, this.username, this.password, function(error, response, body) { if (error) { this.log('HTTP brightness function failed: %s', error); callback(error); From 5440694243cf80a819cb0f519e6cc60bb009621a Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 3 Nov 2015 00:04:46 +0100 Subject: [PATCH 86/89] Renamed duplicate body variable. --- accessories/Http.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/accessories/Http.js b/accessories/Http.js index 24d01df..13d07c2 100644 --- a/accessories/Http.js +++ b/accessories/Http.js @@ -55,14 +55,14 @@ HttpAccessory.prototype = { this.log("Setting power state to off"); } - this.httpRequest(url, body, this.http_method, this.username, this.password, function(error, response, body) { + this.httpRequest(url, body, this.http_method, this.username, this.password, function(error, response, responseBody) { if (error) { this.log('HTTP power function failed: %s', error.message); callback(error); } else { this.log('HTTP power function succeeded!'); this.log(response); - this.log(body); + this.log(responseBody); this.log(this.username); this.log(this.password); callback(); From a682e1dee5d064f90ae0b2a30925b392bffa7632 Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 3 Nov 2015 00:05:38 +0100 Subject: [PATCH 87/89] Updated the example configuration. --- config-sample.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config-sample.json b/config-sample.json index 9ade805..9f7d4d1 100644 --- a/config-sample.json +++ b/config-sample.json @@ -201,7 +201,9 @@ "accessory": "Http", "name": "Kitchen Lamp", "on_url": "https://192.168.1.22:3030/devices/23222/on", + "on_body": "{\"state\":\"On\"}", "off_url": "https://192.168.1.22:3030/devices/23222/off", + "off_body": "{\"state\":\"Off\"}", "brightness_url": "https://192.168.1.22:3030/devices/23222/brightness/%b", "username": "", "password": "", From eabf45526dd287ddd83745632c649b9dbaab0b77 Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Tue, 3 Nov 2015 06:37:53 -0800 Subject: [PATCH 88/89] Guard against multiple WeMo callbacks Closes #296 --- accessories/WeMo.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/accessories/WeMo.js b/accessories/WeMo.js index 5cbc3f4..48e0808 100644 --- a/accessories/WeMo.js +++ b/accessories/WeMo.js @@ -89,7 +89,15 @@ WeMoAccessory.prototype.setPowerOn = function(powerOn, callback) { var binaryState = powerOn ? 1 : 0; // wemo langauge this.log("Setting power state on the '%s' to %s", this.wemoName, binaryState); + var callbackWasCalled = false; + this.device.setBinaryState(binaryState, function(err, result) { + if (callbackWasCalled) { + this.log("WARNING: setBinaryState called its callback more than once! Discarding the second one."); + } + + callbackWasCalled = true; + if (!err) { this.log("Successfully set power state on the '%s' to %s", this.wemoName, binaryState); callback(null); From 7db78a50b5782a49431cae1412f9f5f1892cfbce Mon Sep 17 00:00:00 2001 From: ilcato Date: Tue, 3 Nov 2015 17:26:04 +0100 Subject: [PATCH 89/89] Virtual device optimization Managed Fibaro Virtual Devices push buttons with a single HomeKit Bridged Accessory with multiple Switches --- platforms/FibaroHC2.js | 83 +++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/platforms/FibaroHC2.js b/platforms/FibaroHC2.js index cdaae6c..52b7490 100644 --- a/platforms/FibaroHC2.js +++ b/platforms/FibaroHC2.js @@ -51,43 +51,36 @@ FibaroHC2Platform.prototype = { if (s.visible == true) { var accessory = null; if (s.type == "com.fibaro.multilevelSwitch") - accessory = new FibaroAccessory(new Service.Lightbulb(s.name), [Characteristic.On, Characteristic.Brightness]); + accessory = new FibaroBridgedAccessory([{controlService: new Service.Lightbulb(s.name), characteristics: [Characteristic.On, Characteristic.Brightness]}]); else if (s.type == "com.fibaro.FGRM222" || s.type == "com.fibaro.FGR221") - accessory = new FibaroAccessory(new Service.WindowCovering(s.name), [Characteristic.CurrentPosition, Characteristic.TargetPosition, Characteristic.PositionState]); + accessory = new FibaroBridgedAccessory([{controlService: new Service.WindowCovering(s.name), characteristics: [Characteristic.CurrentPosition, Characteristic.TargetPosition, Characteristic.PositionState]}]); else if (s.type == "com.fibaro.binarySwitch" || s.type == "com.fibaro.developer.bxs.virtualBinarySwitch") - accessory = new FibaroAccessory(new Service.Switch(s.name), [Characteristic.On]); + accessory = new FibaroBridgedAccessory([{controlService: new Service.Switch(s.name), characteristics: [Characteristic.On]}]); else if (s.type == "com.fibaro.FGMS001" || s.type == "com.fibaro.motionSensor") - accessory = new FibaroAccessory(new Service.MotionSensor(s.name), [Characteristic.MotionDetected]); + accessory = new FibaroBridgedAccessory([{controlService: new Service.MotionSensor(s.name), characteristics: [Characteristic.MotionDetected]}]); else if (s.type == "com.fibaro.temperatureSensor") - accessory = new FibaroAccessory(new Service.TemperatureSensor(s.name), [Characteristic.CurrentTemperature]); + accessory = new FibaroBridgedAccessory([{controlService: new Service.TemperatureSensor(s.name), characteristics: [Characteristic.CurrentTemperature]}]); else if (s.type == "com.fibaro.doorSensor") - accessory = new FibaroAccessory(new Service.ContactSensor(s.name), [Characteristic.ContactSensorState]); + accessory = new FibaroBridgedAccessory([{controlService: new Service.ContactSensor(s.name), characteristics: [Characteristic.ContactSensorState]}]); else if (s.type == "com.fibaro.lightSensor") - accessory = new FibaroAccessory(new Service.LightSensor(s.name), [Characteristic.CurrentAmbientLightLevel]); + accessory = new FibaroBridgedAccessory([{controlService: new Service.LightSensor(s.name), characteristics: [Characteristic.CurrentAmbientLightLevel]}]); else if (s.type == "com.fibaro.FGWP101") - accessory = new FibaroAccessory(new Service.Outlet(s.name), [Characteristic.On, Characteristic.OutletInUse]); - else if (s.type == "virtual_device") { + accessory = new FibaroBridgedAccessory([{ controlService: new Service.Outlet(s.name), characteristics: [Characteristic.On, Characteristic.OutletInUse]}]); + else if (s.type == "virtual_device" && s.name.charAt(0) != "_") { + var services = []; for (var r = 0; r < s.properties.rows.length; r++) { if (s.properties.rows[r].type == "button") { for (var e = 0; e < s.properties.rows[r].elements.length; e++) { - var name = s.properties.rows[r].elements[e].caption; - var virtualButton = new FibaroAccessory(new Service.Switch(name), [Characteristic.On]); - virtualButton.buttonId = s.properties.rows[r].elements[e].id; - virtualButton.getServices = function() { - return that.getServices(this); - }; - virtualButton.platform = that; - virtualButton.remoteAccessory = s; - virtualButton.id = s.id; - virtualButton.name = name; - virtualButton.model = "Virtual Button"; - virtualButton.manufacturer = "Fibaro"; - virtualButton.serialNumber = ""; - foundAccessories.push(virtualButton); - that.log("Service name: " + virtualButton.controlService.displayName + ", Accessory name: " + virtualButton.name); + var service = { + controlService: new Service.Switch(s.properties.rows[r].elements[e].caption), + characteristics: [Characteristic.On] + }; + service.controlService.subtype = s.properties.rows[r].elements[e].id; + services.push(service); } } } + accessory = new FibaroBridgedAccessory(services); } if (accessory != null) { accessory.getServices = function() { @@ -171,20 +164,22 @@ FibaroHC2Platform.prototype = { .setCharacteristic(Characteristic.SerialNumber, homebridgeAccessory.serialNumber); return informationService; }, - bindCharacteristicEvents: function(characteristic, homebridgeAccessory) { + bindCharacteristicEvents: function(characteristic, service, homebridgeAccessory) { 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 (service.controlService.subtype != null) { + subscribeUpdate(characteristic, homebridgeAccessory, onOff); + } if (!readOnly) { characteristic .on('set', function(value, callback, context) { if( context !== 'fromFibaro' && context !== 'fromSetValue') { - if (homebridgeAccessory.buttonId != null) { - homebridgeAccessory.platform.command("pressButton", homebridgeAccessory.buttonId, homebridgeAccessory); + if (service.controlService.subtype != null) { + homebridgeAccessory.platform.command("pressButton", service.controlService.subtype, homebridgeAccessory); // In order to behave like a push button reset the status to off setTimeout( function(){ characteristic.setValue(false, undefined, 'fromSetValue'); @@ -199,7 +194,7 @@ FibaroHC2Platform.prototype = { } characteristic .on('get', function(callback) { - if (homebridgeAccessory.buttonId != null) { + if (service.controlService.subtype != null) { // a push button is normally off callback(undefined, false); } else { @@ -208,23 +203,28 @@ FibaroHC2Platform.prototype = { }.bind(this) ); }, getServices: function(homebridgeAccessory) { + var services = []; 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]; + services.push(informationService); + for (var s = 0; s < homebridgeAccessory.services.length; s++) { + var service = homebridgeAccessory.services[s]; + for (var i=0; i < service.characteristics.length; i++) { + var characteristic = service.controlService.getCharacteristic(service.characteristics[i]); + if (characteristic == undefined) + characteristic = service.controlService.addCharacteristic(service.characteristics[i]); + homebridgeAccessory.platform.bindCharacteristicEvents(characteristic, service, homebridgeAccessory); + } + services.push(service.controlService); + } + return services; } } -function FibaroAccessory(controlService, characteristics) { - this.controlService = controlService; - this.characteristics = characteristics; +function FibaroBridgedAccessory(services) { + this.services = services; } + var lastPoll=0; var pollingUpdateRunning = false; @@ -253,7 +253,7 @@ function startPollingUpdate( platform ) var value=parseInt(s.value); if (isNaN(value)) value=(s.value === "true"); - for (i=0;i