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/72] 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/72] 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/72] 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/72] 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/72] 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/72] 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/72] 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/72] 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/72] 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/72] 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/72] 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/72] 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/72] 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/72] 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/72] 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/72] 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/72] 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/72] 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 e40f10b82e78dad18516e55ac4d357f4b4468f43 Mon Sep 17 00:00:00 2001 From: Mike Riccio Date: Thu, 15 Oct 2015 14:25:20 -0700 Subject: [PATCH 19/72] Initial support for Indigo server (http://www.indigodomo.com) --- platforms/Indigo.js | 502 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 502 insertions(+) create mode 100644 platforms/Indigo.js diff --git a/platforms/Indigo.js b/platforms/Indigo.js new file mode 100644 index 0000000..e42849a --- /dev/null +++ b/platforms/Indigo.js @@ -0,0 +1,502 @@ +var types = require("HAP-NodeJS/accessories/types.js"); +var Characteristic = require("HAP-NodeJS").Characteristic; +var request = require('request'); +var async = require('async'); + + +function IndigoPlatform(log, config) { + this.log = log; + + this.baseURL = "http://" + config["host"] + ":" + config["port"]; + + if (config["username"] && config["password"]) { + this.auth = { + 'user': config["username"], + 'pass': config["password"], + 'sendImmediately': false + }; + } +} + +IndigoPlatform.prototype = { + accessories: function(callback) { + var that = this; + this.log("Discovering Indigo Devices."); + + var options = { + url: this.baseURL + "/devices.json/", + method: 'GET' + }; + if (this.auth) { + options['auth'] = this.auth; + } + this.foundAccessories = []; + this.callback = callback; + + request(options, function(error, response, body) { + if (error) { + console.trace("Requesting Indigo devices."); + that.log(error); + return error; + } + + // Cheesy hack because response may have an extra comma at the start of the array, which is invalid + var firstComma = body.indexOf(","); + if (firstComma < 10) { + body = "[" + body.substr(firstComma + 1); + } + + var json = JSON.parse(body); + async.each(json, function(item, asyncCallback) { + var deviceURL = that.baseURL + item.restURL; + var deviceOptions = { + url: deviceURL, + method: 'GET' + }; + if (that.auth) { + deviceOptions['auth'] = that.auth; + } + + request(deviceOptions, function(deviceError, deviceResponse, deviceBody) { + if (deviceError) { + asyncCallback(deviceError); + } + + var deviceJson = JSON.parse(deviceBody); + that.log("Discovered " + deviceJson.type + ": " + deviceJson.name); + that.foundAccessories.push( + new IndigoAccessory(that.log, that.auth, deviceURL, deviceJson)); + asyncCallback(); + }); + }, function(asyncError) { + // This will be called after all the requests complete + if (asyncError) { + console.trace("Requesting Indigo device info."); + that.log(asyncError); + } else { + that.callback(that.foundAccessories.sort(function (a,b) { + return (a.name > b.name) - (a.name < b.name); + })); + } + }); + }); + } +} + + +function IndigoAccessory(log, auth, deviceURL, json) { + this.log = log; + this.auth = auth; + this.deviceURL = deviceURL; + + for (var prop in json) { + if (json.hasOwnProperty(prop)) { + this[prop] = json[prop]; + } + } +} + +IndigoAccessory.prototype = { + getStatus: function(callback) { + var that = this; + + var options = { + url: this.deviceURL, + method: 'GET' + }; + if (this.auth) { + options['auth'] = this.auth; + } + + request(options, function(error, response, body) { + if (error) { + console.trace("Requesting Device Status."); + that.log(error); + return error; + } + + that.log("getStatus of " + that.name + ": " + body); + callback(JSON.parse(body)); + }); + }, + + updateStatus: function(params) { + var that = this; + var options = { + url: this.deviceURL + "?" + params, + method: 'PUT' + }; + if (this.auth) { + options['auth'] = this.auth; + } + + this.log("updateStatus of " + that.name + ": " + params); + request(options, function(error, response, body) { + if (error) { + console.trace("Updating Device Status."); + that.log(error); + return error; + } + }); + }, + + query: function(prop, callback) { + this.getStatus(function(json) { + callback(json[prop]); + }); + }, + + turnOn: function() { + if (this.typeSupportsOnOff) { + this.updateStatus("isOn=1"); + } + }, + + turnOff: function() { + if (this.typeSupportsOnOff) { + this.updateStatus("isOn=0"); + } + }, + + setBrightness: function(brightness) { + if (this.typeSupportsDim && brightness >= 0 && brightness <= 100) { + this.updateStatus("brightness=" + brightness); + } + }, + + setSpeedIndex: function(speedIndex) { + if (this.typeSupportsSpeedControl && speedIndex >= 0 && speedIndex <= 3) { + this.updateStatus("speedIndex=" + speedIndex); + } + }, + + getCurrentHeatingCooling: function(callback) { + this.getStatus(function(json) { + var mode = 0; + if (json["hvacOperatonModeIsHeat"]) { + mode = 1; + } + else if (json["hvacOperationModeIsCool"]) { + mode = 2; + } + else if (json["hvacOperationModeIsAuto"]) { + mode = 3; + } + callback(mode); + }); + }, + + setTargetHeatingCooling: function(mode) { + if (mode == 0) { + param = "Off"; + } + else if (mode == 1) { + param = "Heat"; + } + else if (mode == 2) { + param = "Cool"; + } + else if (mode == 3) { + param = "Auto"; + } + + if (param) { + this.updateStatus("hvacOperationModeIs" + param + "=true"); + } + }, + + getTargetTemperature: function(callback) { + this.getStatus(function(json) { + var result; + if (json["hvacOperatonModeIsHeat"]) { + result = json["setpointHeat"]; + } + else if (json["hvacOperationModeIsCool"]) { + result = json["setpointCool"]; + } + else { + result = (json["setpointHeat"] + json["setpointCool"]) / 2; + } + callback(result); + }); + }, + + setTargetTemperature: function(temperature) { + var that = this; + this.getStatus(function(json) { + if (json["hvacOperatonModeIsHeat"]) { + that.updateStatus("setpointHeat=" + temperature); + } + else if (json["hvacOperationModeIsCool"]) { + that.updateStatus("setpointCool=" + temperature); + } + else { + var cool = temperature + 5; + var heat = temperature - 5; + that.updateStatus("setpointCool=" + cool + "&setpointHeat=" + heat); + } + }); + }, + + informationCharacteristics: function() { + return [ + { + cType: types.NAME_CTYPE, + onUpdate: null, + perms: [Characteristic.Perms.READ], + format: Characteristic.Formats.STRING, + initialValue: this.name, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of the accessory", + designedMaxLength: 255 + },{ + cType: types.MANUFACTURER_CTYPE, + onUpdate: null, + perms: [Characteristic.Perms.READ], + format: Characteristic.Formats.STRING, + initialValue: "Indigo", + supportEvents: false, + supportBonjour: false, + manfDescription: "Manufacturer", + designedMaxLength: 255 + },{ + cType: types.MODEL_CTYPE, + onUpdate: null, + perms: [Characteristic.Perms.READ], + format: Characteristic.Formats.STRING, + initialValue: this.type, + supportEvents: false, + supportBonjour: false, + manfDescription: "Model", + designedMaxLength: 255 + },{ + cType: types.SERIAL_NUMBER_CTYPE, + onUpdate: null, + perms: [Characteristic.Perms.READ], + format: Characteristic.Formats.STRING, + initialValue: this.addressStr, + supportEvents: false, + supportBonjour: false, + manfDescription: "SN", + designedMaxLength: 255 + },{ + cType: types.IDENTIFY_CTYPE, + onUpdate: null, + perms: [Characteristic.Perms.WRITE], + format: Characteristic.Formats.BOOL, + initialValue: false, + supportEvents: false, + supportBonjour: false, + manfDescription: "Identify Accessory", + designedMaxLength: 1 + } + ] + }, + + controlCharacteristics: function(that) { + var cTypes = [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: [Characteristic.Perms.READ], + format: Characteristic.Formats.STRING, + initialValue: that.name, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of the accessory", + designedMaxLength: 255 + }]; + +/* if (that.typeSupportsOnOff) + { */ + cTypes.push({ + cType: types.POWER_STATE_CTYPE, + perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.BOOL, + initialValue: (that.isOn) ? 1 : 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Change the power state", + designedMaxLength: 1, + onUpdate: function(value) { + if (value == 0) { + that.turnOff(); + } else { + that.turnOn(); + } + }, + onRead: function(callback) { + that.query("isOn", function(isOn) { + callback((isOn) ? 1 : 0); + }); + } + }); +// } + if (that.typeSupportsDim) + { + cTypes.push({ + cType: types.BRIGHTNESS_CTYPE, + perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.INT, + initialValue: that.brightness, + supportEvents: true, + supportBonjour: false, + manfDescription: "Adjust Brightness of Light", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: Characteristic.Units.PERCENTAGE, + onUpdate: function(value) { + that.setBrightness(value); + }, + onRead: function(callback) { + that.query("brightness", callback); + } + }); + } + if (that.typeSupportsSpeedControl) + { + cTypes.push({ + cType: types.ROTATION_SPEED_CTYPE, + perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.INT, + initialValue: 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Change the speed of the fan", + designedMaxLength: 1, + designedMinValue: 0, + designedMaxValue: 3, + designedMinStep: 1, + onUpdate: function(value) { + that.setSpeedIndex(value); + }, + onRead: function(callback) { + that.query("speedIndex", callback); + } + }); + } + if (that.typeSupportsHVAC) + { + cTypes.push({ + cType: types.CURRENTHEATINGCOOLING_CTYPE, + perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.INT, + initialValue: 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Current Mode", + designedMaxLength: 1, + designedMinValue: 0, + designedMaxValue: 3, + designedMinStep: 1, + onUpdate: null, + onRead: function(callback) { + that.getCurrentHeatingCooling(callback); + } + }); + cTypes.push({ + cType: types.TARGETHEATINGCOOLING_CTYPE, + perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.INT, + initialValue: 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Target Mode", + designedMaxLength: 1, + designedMinValue: 0, + designedMaxValue: 3, + designedMinStep: 1, + onUpdate: function(value) { + that.setTargetHeatingCooling(value); + }, + onRead: function(callback) { + that.getCurrentHeatingCooling(callback); + } + }); + cTypes.push({ + cType: types.CURRENT_TEMPERATURE_CTYPE, + perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.INT, + designedMinValue: 0, + designedMaxValue: 110, + designedMinStep: 1, + initialValue: 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Current Temperature", + unit: Characteristic.Units.FAHRENHEIT, + onUpdate: null, + onRead: function(callback) { + that.query("displayRawState", callback); + } + }); + cTypes.push({ + cType: types.TARGET_TEMPERATURE_CTYPE, + perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.INT, + designedMinValue: 0, + designedMaxValue: 110, + designedMinStep: 1, + initialValue: 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Target Temperature", + unit: Characteristic.Units.FAHRENHEIT, + onUpdate: function(value) { + that.setTargetTemperature(value); + }, + onRead: function(callback) { + that.getTargetTemperature(callback); + } + }); + cTypes.push({ + cType: types.TEMPERATURE_UNITS_CTYPE, + perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.INT, + initialValue: 1, + supportEvents: false, + supportBonjour: false, + manfDescription: "Unit", + onUpdate: null, + onRead: function(callback) { + callback(Characteristic.Units.FAHRENHEIT); + } + }); + } + + return cTypes; + }, + + sType: function() { + if (this.typeSupportsHVAC) { + return types.THERMOSTAT_STYPE; + } else if (this.typeSupportsDim) { + return types.LIGHTBULB_STYPE; + } else if (this.typeSupportsSpeedControl) { + return types.FAN_STYPE; + } else if (this.typeSupportsOnOff) { + return types.SWITCH_STYPE; + } + + return types.SWITCH_STYPE; + }, + + getServices: function() { + var that = this; + var services = [{ + sType: types.ACCESSORY_INFORMATION_STYPE, + characteristics: that.informationCharacteristics(), + }, + { + sType: that.sType(), + characteristics: that.controlCharacteristics(that) + }]; + + that.log("Loaded services for " + that.name); + return services; + } +}; + +module.exports.accessory = IndigoAccessory; +module.exports.platform = IndigoPlatform; From 9ad8a111c68b342069cfbcd1e0ff1bf1090022c4 Mon Sep 17 00:00:00 2001 From: Mike Riccio Date: Thu, 15 Oct 2015 15:07:14 -0700 Subject: [PATCH 20/72] Added intro comment. --- platforms/Indigo.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/platforms/Indigo.js b/platforms/Indigo.js index e42849a..f77813a 100644 --- a/platforms/Indigo.js +++ b/platforms/Indigo.js @@ -1,3 +1,24 @@ +// Indigo Platform Shim for HomeBridge +// Written by Mike Riccio (https://github.com/webdeck) +// Based on many of the other HomeBridge plartform modules +// See http://www.indigodomo.com/ for more info on Indigo +// +// Remember to add platform to config.json. Example: +// "platforms": [ +// { +// "platform": "Indigo", // required +// "name": "Indigo", // required +// "host": "127.0.0.1", // required +// "port": "8176", // required +// "username": "username", // optional +// "password": "password" // optional +// } +// ], +// +// When you attempt to add a device, it will ask for a "PIN code". +// The default code for all HomeBridge accessories is 031-45-154. +// + var types = require("HAP-NodeJS/accessories/types.js"); var Characteristic = require("HAP-NodeJS").Characteristic; var request = require('request'); From 6fa69c5c4bb17cfbae3607cc8ed7eaa92d4f43e0 Mon Sep 17 00:00:00 2001 From: Mike Riccio Date: Thu, 15 Oct 2015 16:47:59 -0700 Subject: [PATCH 21/72] Removed tabs --- platforms/Indigo.js | 436 ++++++++++++++++++++++---------------------- 1 file changed, 218 insertions(+), 218 deletions(-) diff --git a/platforms/Indigo.js b/platforms/Indigo.js index f77813a..1332004 100644 --- a/platforms/Indigo.js +++ b/platforms/Indigo.js @@ -8,10 +8,10 @@ // { // "platform": "Indigo", // required // "name": "Indigo", // required -// "host": "127.0.0.1", // required -// "port": "8176", // required -// "username": "username", // optional -// "password": "password" // optional +// "host": "127.0.0.1", // required +// "port": "8176", // required +// "username": "username", // optional +// "password": "password" // optional // } // ], // @@ -28,19 +28,19 @@ var async = require('async'); function IndigoPlatform(log, config) { this.log = log; - this.baseURL = "http://" + config["host"] + ":" + config["port"]; + this.baseURL = "http://" + config["host"] + ":" + config["port"]; - if (config["username"] && config["password"]) { - this.auth = { - 'user': config["username"], - 'pass': config["password"], - 'sendImmediately': false - }; - } + if (config["username"] && config["password"]) { + this.auth = { + 'user': config["username"], + 'pass': config["password"], + 'sendImmediately': false + }; + } } IndigoPlatform.prototype = { - accessories: function(callback) { + accessories: function(callback) { var that = this; this.log("Discovering Indigo Devices."); @@ -48,11 +48,11 @@ IndigoPlatform.prototype = { url: this.baseURL + "/devices.json/", method: 'GET' }; - if (this.auth) { - options['auth'] = this.auth; - } + if (this.auth) { + options['auth'] = this.auth; + } this.foundAccessories = []; - this.callback = callback; + this.callback = callback; request(options, function(error, response, body) { if (error) { @@ -61,73 +61,73 @@ IndigoPlatform.prototype = { return error; } - // Cheesy hack because response may have an extra comma at the start of the array, which is invalid - var firstComma = body.indexOf(","); - if (firstComma < 10) { - body = "[" + body.substr(firstComma + 1); - } + // Cheesy hack because response may have an extra comma at the start of the array, which is invalid + var firstComma = body.indexOf(","); + if (firstComma < 10) { + body = "[" + body.substr(firstComma + 1); + } - var json = JSON.parse(body); - async.each(json, function(item, asyncCallback) { - var deviceURL = that.baseURL + item.restURL; - var deviceOptions = { - url: deviceURL, - method: 'GET' - }; - if (that.auth) { - deviceOptions['auth'] = that.auth; - } + var json = JSON.parse(body); + async.each(json, function(item, asyncCallback) { + var deviceURL = that.baseURL + item.restURL; + var deviceOptions = { + url: deviceURL, + method: 'GET' + }; + if (that.auth) { + deviceOptions['auth'] = that.auth; + } - request(deviceOptions, function(deviceError, deviceResponse, deviceBody) { - if (deviceError) { - asyncCallback(deviceError); - } + request(deviceOptions, function(deviceError, deviceResponse, deviceBody) { + if (deviceError) { + asyncCallback(deviceError); + } - var deviceJson = JSON.parse(deviceBody); - that.log("Discovered " + deviceJson.type + ": " + deviceJson.name); - that.foundAccessories.push( - new IndigoAccessory(that.log, that.auth, deviceURL, deviceJson)); - asyncCallback(); - }); - }, function(asyncError) { - // This will be called after all the requests complete - if (asyncError) { - console.trace("Requesting Indigo device info."); - that.log(asyncError); - } else { - that.callback(that.foundAccessories.sort(function (a,b) { - return (a.name > b.name) - (a.name < b.name); - })); - } - }); - }); - } + var deviceJson = JSON.parse(deviceBody); + that.log("Discovered " + deviceJson.type + ": " + deviceJson.name); + that.foundAccessories.push( + new IndigoAccessory(that.log, that.auth, deviceURL, deviceJson)); + asyncCallback(); + }); + }, function(asyncError) { + // This will be called after all the requests complete + if (asyncError) { + console.trace("Requesting Indigo device info."); + that.log(asyncError); + } else { + that.callback(that.foundAccessories.sort(function (a,b) { + return (a.name > b.name) - (a.name < b.name); + })); + } + }); + }); + } } function IndigoAccessory(log, auth, deviceURL, json) { this.log = log; - this.auth = auth; + this.auth = auth; this.deviceURL = deviceURL; - for (var prop in json) { - if (json.hasOwnProperty(prop)) { - this[prop] = json[prop]; - } - } + for (var prop in json) { + if (json.hasOwnProperty(prop)) { + this[prop] = json[prop]; + } + } } IndigoAccessory.prototype = { getStatus: function(callback) { - var that = this; + var that = this; var options = { - url: this.deviceURL, + url: this.deviceURL, method: 'GET' }; - if (this.auth) { - options['auth'] = this.auth; - } + if (this.auth) { + options['auth'] = this.auth; + } request(options, function(error, response, body) { if (error) { @@ -136,22 +136,22 @@ IndigoAccessory.prototype = { return error; } - that.log("getStatus of " + that.name + ": " + body); - callback(JSON.parse(body)); + that.log("getStatus of " + that.name + ": " + body); + callback(JSON.parse(body)); }); }, updateStatus: function(params) { - var that = this; + var that = this; var options = { - url: this.deviceURL + "?" + params, + url: this.deviceURL + "?" + params, method: 'PUT' }; - if (this.auth) { - options['auth'] = this.auth; - } + if (this.auth) { + options['auth'] = this.auth; + } - this.log("updateStatus of " + that.name + ": " + params); + this.log("updateStatus of " + that.name + ": " + params); request(options, function(error, response, body) { if (error) { console.trace("Updating Device Status."); @@ -162,101 +162,101 @@ IndigoAccessory.prototype = { }, query: function(prop, callback) { - this.getStatus(function(json) { - callback(json[prop]); - }); + this.getStatus(function(json) { + callback(json[prop]); + }); }, - turnOn: function() { - if (this.typeSupportsOnOff) { - this.updateStatus("isOn=1"); - } - }, - - turnOff: function() { - if (this.typeSupportsOnOff) { - this.updateStatus("isOn=0"); - } - }, + turnOn: function() { + if (this.typeSupportsOnOff) { + this.updateStatus("isOn=1"); + } + }, + + turnOff: function() { + if (this.typeSupportsOnOff) { + this.updateStatus("isOn=0"); + } + }, - setBrightness: function(brightness) { - if (this.typeSupportsDim && brightness >= 0 && brightness <= 100) { - this.updateStatus("brightness=" + brightness); - } - }, - - setSpeedIndex: function(speedIndex) { - if (this.typeSupportsSpeedControl && speedIndex >= 0 && speedIndex <= 3) { - this.updateStatus("speedIndex=" + speedIndex); - } - }, - + setBrightness: function(brightness) { + if (this.typeSupportsDim && brightness >= 0 && brightness <= 100) { + this.updateStatus("brightness=" + brightness); + } + }, + + setSpeedIndex: function(speedIndex) { + if (this.typeSupportsSpeedControl && speedIndex >= 0 && speedIndex <= 3) { + this.updateStatus("speedIndex=" + speedIndex); + } + }, + getCurrentHeatingCooling: function(callback) { - this.getStatus(function(json) { - var mode = 0; - if (json["hvacOperatonModeIsHeat"]) { - mode = 1; - } - else if (json["hvacOperationModeIsCool"]) { - mode = 2; - } - else if (json["hvacOperationModeIsAuto"]) { - mode = 3; - } - callback(mode); - }); + this.getStatus(function(json) { + var mode = 0; + if (json["hvacOperatonModeIsHeat"]) { + mode = 1; + } + else if (json["hvacOperationModeIsCool"]) { + mode = 2; + } + else if (json["hvacOperationModeIsAuto"]) { + mode = 3; + } + callback(mode); + }); }, setTargetHeatingCooling: function(mode) { - if (mode == 0) { - param = "Off"; - } - else if (mode == 1) { - param = "Heat"; - } - else if (mode == 2) { - param = "Cool"; - } - else if (mode == 3) { - param = "Auto"; - } + if (mode == 0) { + param = "Off"; + } + else if (mode == 1) { + param = "Heat"; + } + else if (mode == 2) { + param = "Cool"; + } + else if (mode == 3) { + param = "Auto"; + } - if (param) { - this.updateStatus("hvacOperationModeIs" + param + "=true"); - } + if (param) { + this.updateStatus("hvacOperationModeIs" + param + "=true"); + } }, getTargetTemperature: function(callback) { - this.getStatus(function(json) { - var result; - if (json["hvacOperatonModeIsHeat"]) { - result = json["setpointHeat"]; - } - else if (json["hvacOperationModeIsCool"]) { - result = json["setpointCool"]; - } - else { - result = (json["setpointHeat"] + json["setpointCool"]) / 2; - } - callback(result); - }); + this.getStatus(function(json) { + var result; + if (json["hvacOperatonModeIsHeat"]) { + result = json["setpointHeat"]; + } + else if (json["hvacOperationModeIsCool"]) { + result = json["setpointCool"]; + } + else { + result = (json["setpointHeat"] + json["setpointCool"]) / 2; + } + callback(result); + }); }, setTargetTemperature: function(temperature) { - var that = this; - this.getStatus(function(json) { - if (json["hvacOperatonModeIsHeat"]) { - that.updateStatus("setpointHeat=" + temperature); - } - else if (json["hvacOperationModeIsCool"]) { - that.updateStatus("setpointCool=" + temperature); - } - else { - var cool = temperature + 5; - var heat = temperature - 5; - that.updateStatus("setpointCool=" + cool + "&setpointHeat=" + heat); - } - }); + var that = this; + this.getStatus(function(json) { + if (json["hvacOperatonModeIsHeat"]) { + that.updateStatus("setpointHeat=" + temperature); + } + else if (json["hvacOperationModeIsCool"]) { + that.updateStatus("setpointCool=" + temperature); + } + else { + var cool = temperature + 5; + var heat = temperature - 5; + that.updateStatus("setpointCool=" + cool + "&setpointHeat=" + heat); + } + }); }, informationCharacteristics: function() { @@ -316,7 +316,7 @@ IndigoAccessory.prototype = { }, controlCharacteristics: function(that) { - var cTypes = [{ + var cTypes = [{ cType: types.NAME_CTYPE, onUpdate: null, perms: [Characteristic.Perms.READ], @@ -326,15 +326,15 @@ IndigoAccessory.prototype = { supportBonjour: false, manfDescription: "Name of the accessory", designedMaxLength: 255 - }]; + }]; /* if (that.typeSupportsOnOff) - { */ + { */ cTypes.push({ cType: types.POWER_STATE_CTYPE, perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], format: Characteristic.Formats.BOOL, - initialValue: (that.isOn) ? 1 : 0, + initialValue: (that.isOn) ? 1 : 0, supportEvents: true, supportBonjour: false, manfDescription: "Change the power state", @@ -347,14 +347,14 @@ IndigoAccessory.prototype = { } }, onRead: function(callback) { - that.query("isOn", function(isOn) { - callback((isOn) ? 1 : 0); - }); + that.query("isOn", function(isOn) { + callback((isOn) ? 1 : 0); + }); } }); -// } - if (that.typeSupportsDim) - { +// } + if (that.typeSupportsDim) + { cTypes.push({ cType: types.BRIGHTNESS_CTYPE, perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], @@ -368,10 +368,10 @@ IndigoAccessory.prototype = { designedMinStep: 1, unit: Characteristic.Units.PERCENTAGE, onUpdate: function(value) { - that.setBrightness(value); + that.setBrightness(value); }, onRead: function(callback) { - that.query("brightness", callback); + that.query("brightness", callback); } }); } @@ -386,11 +386,11 @@ IndigoAccessory.prototype = { supportBonjour: false, manfDescription: "Change the speed of the fan", designedMaxLength: 1, - designedMinValue: 0, - designedMaxValue: 3, - designedMinStep: 1, + designedMinValue: 0, + designedMaxValue: 3, + designedMinStep: 1, onUpdate: function(value) { - that.setSpeedIndex(value); + that.setSpeedIndex(value); }, onRead: function(callback) { that.query("speedIndex", callback); @@ -400,44 +400,44 @@ IndigoAccessory.prototype = { if (that.typeSupportsHVAC) { cTypes.push({ - cType: types.CURRENTHEATINGCOOLING_CTYPE, - perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], - format: Characteristic.Formats.INT, - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Current Mode", - designedMaxLength: 1, - designedMinValue: 0, - designedMaxValue: 3, - designedMinStep: 1, - onUpdate: null, - onRead: function(callback) { - that.getCurrentHeatingCooling(callback); - } + cType: types.CURRENTHEATINGCOOLING_CTYPE, + perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.INT, + initialValue: 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Current Mode", + designedMaxLength: 1, + designedMinValue: 0, + designedMaxValue: 3, + designedMinStep: 1, + onUpdate: null, + onRead: function(callback) { + that.getCurrentHeatingCooling(callback); + } }); cTypes.push({ - cType: types.TARGETHEATINGCOOLING_CTYPE, - perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], - format: Characteristic.Formats.INT, - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Target Mode", - designedMaxLength: 1, - designedMinValue: 0, - designedMaxValue: 3, - designedMinStep: 1, - onUpdate: function(value) { - that.setTargetHeatingCooling(value); - }, - onRead: function(callback) { - that.getCurrentHeatingCooling(callback); - } + cType: types.TARGETHEATINGCOOLING_CTYPE, + perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.INT, + initialValue: 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Target Mode", + designedMaxLength: 1, + designedMinValue: 0, + designedMaxValue: 3, + designedMinStep: 1, + onUpdate: function(value) { + that.setTargetHeatingCooling(value); + }, + onRead: function(callback) { + that.getCurrentHeatingCooling(callback); + } }); cTypes.push({ - cType: types.CURRENT_TEMPERATURE_CTYPE, - perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + cType: types.CURRENT_TEMPERATURE_CTYPE, + perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], format: Characteristic.Formats.INT, designedMinValue: 0, designedMaxValue: 110, @@ -446,11 +446,11 @@ IndigoAccessory.prototype = { supportEvents: true, supportBonjour: false, manfDescription: "Current Temperature", - unit: Characteristic.Units.FAHRENHEIT, - onUpdate: null, + unit: Characteristic.Units.FAHRENHEIT, + onUpdate: null, onRead: function(callback) { that.query("displayRawState", callback); - } + } }); cTypes.push({ cType: types.TARGET_TEMPERATURE_CTYPE, @@ -463,12 +463,12 @@ IndigoAccessory.prototype = { supportEvents: true, supportBonjour: false, manfDescription: "Target Temperature", - unit: Characteristic.Units.FAHRENHEIT, - onUpdate: function(value) { + unit: Characteristic.Units.FAHRENHEIT, + onUpdate: function(value) { that.setTargetTemperature(value); }, onRead: function(callback) { - that.getTargetTemperature(callback); + that.getTargetTemperature(callback); } }); cTypes.push({ @@ -479,9 +479,9 @@ IndigoAccessory.prototype = { supportEvents: false, supportBonjour: false, manfDescription: "Unit", - onUpdate: null, + onUpdate: null, onRead: function(callback) { - callback(Characteristic.Units.FAHRENHEIT); + callback(Characteristic.Units.FAHRENHEIT); } }); } @@ -490,13 +490,13 @@ IndigoAccessory.prototype = { }, sType: function() { - if (this.typeSupportsHVAC) { - return types.THERMOSTAT_STYPE; - } else if (this.typeSupportsDim) { + if (this.typeSupportsHVAC) { + return types.THERMOSTAT_STYPE; + } else if (this.typeSupportsDim) { return types.LIGHTBULB_STYPE; } else if (this.typeSupportsSpeedControl) { return types.FAN_STYPE; - } else if (this.typeSupportsOnOff) { + } else if (this.typeSupportsOnOff) { return types.SWITCH_STYPE; } @@ -504,7 +504,7 @@ IndigoAccessory.prototype = { }, getServices: function() { - var that = this; + var that = this; var services = [{ sType: types.ACCESSORY_INFORMATION_STYPE, characteristics: that.informationCharacteristics(), From aad811fe6e794b37e253c4bc39e94bef77a559a6 Mon Sep 17 00:00:00 2001 From: Mike Riccio Date: Thu, 15 Oct 2015 21:31:08 -0700 Subject: [PATCH 22/72] Fixed thermostats to report celsius. --- platforms/Indigo.js | 101 ++++++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 46 deletions(-) diff --git a/platforms/Indigo.js b/platforms/Indigo.js index 1332004..98f259c 100644 --- a/platforms/Indigo.js +++ b/platforms/Indigo.js @@ -226,34 +226,42 @@ IndigoAccessory.prototype = { } }, + // Note: HomeKit wants all temperature values to be in celsius + getCurrentTemperature: function(callback) { + this.query("displayRawState", function(temperature) { + callback((temperature - 32.0) * 5.0 / 9.0); + }); + }, + getTargetTemperature: function(callback) { this.getStatus(function(json) { - var result; + var temperature; if (json["hvacOperatonModeIsHeat"]) { - result = json["setpointHeat"]; + temperature = json["setpointHeat"]; } else if (json["hvacOperationModeIsCool"]) { - result = json["setpointCool"]; + temperature = json["setpointCool"]; } else { - result = (json["setpointHeat"] + json["setpointCool"]) / 2; + temperature = (json["setpointHeat"] + json["setpointCool"]) / 2.0; } - callback(result); + callback((temperature - 32.0) * 5.0 / 9.0); }); }, setTargetTemperature: function(temperature) { var that = this; + var t = (temperature * 9.0 / 5.0) + 32.0; this.getStatus(function(json) { if (json["hvacOperatonModeIsHeat"]) { - that.updateStatus("setpointHeat=" + temperature); + that.updateStatus("setpointHeat=" + t); } else if (json["hvacOperationModeIsCool"]) { - that.updateStatus("setpointCool=" + temperature); + that.updateStatus("setpointCool=" + t); } else { - var cool = temperature + 5; - var heat = temperature - 5; + var cool = t + 5; + var heat = t - 5; that.updateStatus("setpointCool=" + cool + "&setpointHeat=" + heat); } }); @@ -328,39 +336,36 @@ IndigoAccessory.prototype = { designedMaxLength: 255 }]; -/* if (that.typeSupportsOnOff) - { */ - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], - format: Characteristic.Formats.BOOL, - initialValue: (that.isOn) ? 1 : 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1, - onUpdate: function(value) { - if (value == 0) { - that.turnOff(); - } else { - that.turnOn(); - } - }, - onRead: function(callback) { - that.query("isOn", function(isOn) { - callback((isOn) ? 1 : 0); - }); + cTypes.push({ + cType: types.POWER_STATE_CTYPE, + perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.BOOL, + initialValue: (that.isOn) ? 1 : 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Change the power state", + designedMaxLength: 1, + onUpdate: function(value) { + if (value == 0) { + that.turnOff(); + } else { + that.turnOn(); } - }); -// } - if (that.typeSupportsDim) - { + }, + onRead: function(callback) { + that.query("isOn", function(isOn) { + callback((isOn) ? 1 : 0); + }); + } + }); + + if (that.typeSupportsDim) { cTypes.push({ cType: types.BRIGHTNESS_CTYPE, perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], format: Characteristic.Formats.INT, initialValue: that.brightness, - supportEvents: true, + supportEvents: false, supportBonjour: false, manfDescription: "Adjust Brightness of Light", designedMinValue: 0, @@ -375,14 +380,14 @@ IndigoAccessory.prototype = { } }); } - if (that.typeSupportsSpeedControl) - { + + if (that.typeSupportsSpeedControl) { cTypes.push({ cType: types.ROTATION_SPEED_CTYPE, perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], format: Characteristic.Formats.INT, initialValue: 0, - supportEvents: true, + supportEvents: false, supportBonjour: false, manfDescription: "Change the speed of the fan", designedMaxLength: 1, @@ -397,14 +402,14 @@ IndigoAccessory.prototype = { } }); } - if (that.typeSupportsHVAC) - { + + if (that.typeSupportsHVAC) { cTypes.push({ cType: types.CURRENTHEATINGCOOLING_CTYPE, perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], format: Characteristic.Formats.INT, initialValue: 0, - supportEvents: true, + supportEvents: false, supportBonjour: false, manfDescription: "Current Mode", designedMaxLength: 1, @@ -416,12 +421,13 @@ IndigoAccessory.prototype = { that.getCurrentHeatingCooling(callback); } }); + cTypes.push({ cType: types.TARGETHEATINGCOOLING_CTYPE, perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], format: Characteristic.Formats.INT, initialValue: 0, - supportEvents: true, + supportEvents: false, supportBonjour: false, manfDescription: "Target Mode", designedMaxLength: 1, @@ -435,6 +441,7 @@ IndigoAccessory.prototype = { that.getCurrentHeatingCooling(callback); } }); + cTypes.push({ cType: types.CURRENT_TEMPERATURE_CTYPE, perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], @@ -443,15 +450,16 @@ IndigoAccessory.prototype = { designedMaxValue: 110, designedMinStep: 1, initialValue: 0, - supportEvents: true, + supportEvents: false, supportBonjour: false, manfDescription: "Current Temperature", unit: Characteristic.Units.FAHRENHEIT, onUpdate: null, onRead: function(callback) { - that.query("displayRawState", callback); + that.getCurrentTemperature(callback); } }); + cTypes.push({ cType: types.TARGET_TEMPERATURE_CTYPE, perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], @@ -460,7 +468,7 @@ IndigoAccessory.prototype = { designedMaxValue: 110, designedMinStep: 1, initialValue: 0, - supportEvents: true, + supportEvents: false, supportBonjour: false, manfDescription: "Target Temperature", unit: Characteristic.Units.FAHRENHEIT, @@ -471,6 +479,7 @@ IndigoAccessory.prototype = { that.getTargetTemperature(callback); } }); + cTypes.push({ cType: types.TEMPERATURE_UNITS_CTYPE, perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], From 7233c5bf7454008a45fd4c29219e09fec2f4b5cb Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Fri, 16 Oct 2015 10:50:55 +0200 Subject: [PATCH 23/72] Support for case-sensitive file systems and current HAP-NodeJS with nodejs 4 support --- accessories/FileSensor.js | 4 ++-- accessories/GenericRS232Device.js | 4 ++-- accessories/HomeMaticWindow.js | 2 +- accessories/Http.js | 4 ++-- accessories/HttpHygrometer.js | 4 ++-- accessories/HttpThermometer.js | 4 ++-- accessories/Lockitron.js | 4 ++-- accessories/WeMo.js | 4 ++-- accessories/iControl.js | 4 ++-- accessories/knxdevice.js | 4 ++-- accessories/mpdclient.js | 4 ++-- app.js | 14 +++++++------- package.json | 2 +- platforms/FHEM.js | 4 ++-- platforms/FibaroHC2.js | 4 ++-- platforms/HomeAssistant.js | 4 ++-- platforms/HomeSeer.js | 4 ++-- platforms/LIFx.js | 4 ++-- platforms/MiLight.js | 4 ++-- platforms/YamahaAVR.js | 4 ++-- platforms/ZWayServer.js | 4 ++-- 21 files changed, 45 insertions(+), 45 deletions(-) diff --git a/accessories/FileSensor.js b/accessories/FileSensor.js index e377dc6..112089e 100644 --- a/accessories/FileSensor.js +++ b/accessories/FileSensor.js @@ -1,5 +1,5 @@ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var chokidar = require("chokidar"); var debug = require("debug")("FileSensorAccessory"); var crypto = require("crypto"); diff --git a/accessories/GenericRS232Device.js b/accessories/GenericRS232Device.js index b84e4cc..01fe80c 100644 --- a/accessories/GenericRS232Device.js +++ b/accessories/GenericRS232Device.js @@ -1,5 +1,5 @@ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var SerialPort = require("serialport").SerialPort; module.exports = { diff --git a/accessories/HomeMaticWindow.js b/accessories/HomeMaticWindow.js index b7e585d..865b24a 100644 --- a/accessories/HomeMaticWindow.js +++ b/accessories/HomeMaticWindow.js @@ -1,5 +1,5 @@ var types = require("HAP-NodeJS/accessories/types.js"); -var Characteristic = require("HAP-NodeJS").Characteristic; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); function HomeMaticWindow(log, config) { diff --git a/accessories/Http.js b/accessories/Http.js index e1859cf..fb708e3 100644 --- a/accessories/Http.js +++ b/accessories/Http.js @@ -1,5 +1,5 @@ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); module.exports = { diff --git a/accessories/HttpHygrometer.js b/accessories/HttpHygrometer.js index 61ad3b9..f722fe8 100644 --- a/accessories/HttpHygrometer.js +++ b/accessories/HttpHygrometer.js @@ -1,5 +1,5 @@ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); module.exports = { diff --git a/accessories/HttpThermometer.js b/accessories/HttpThermometer.js index ac9bdc2..9235a57 100644 --- a/accessories/HttpThermometer.js +++ b/accessories/HttpThermometer.js @@ -1,5 +1,5 @@ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); module.exports = { diff --git a/accessories/Lockitron.js b/accessories/Lockitron.js index 8088d14..ed95b7e 100644 --- a/accessories/Lockitron.js +++ b/accessories/Lockitron.js @@ -1,5 +1,5 @@ -var Service = require('HAP-NodeJS').Service; -var Characteristic = require('HAP-NodeJS').Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); module.exports = { diff --git a/accessories/WeMo.js b/accessories/WeMo.js index b97b041..5cbc3f4 100644 --- a/accessories/WeMo.js +++ b/accessories/WeMo.js @@ -1,5 +1,5 @@ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var wemo = require('wemo'); module.exports = { diff --git a/accessories/iControl.js b/accessories/iControl.js index d948867..a4299b5 100644 --- a/accessories/iControl.js +++ b/accessories/iControl.js @@ -1,6 +1,6 @@ var iControl = require('node-icontrol').iControl; -var Service = require('HAP-NodeJS').Service; -var Characteristic = require('HAP-NodeJS').Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; module.exports = { accessory: iControlAccessory diff --git a/accessories/knxdevice.js b/accessories/knxdevice.js index 6aa4ffd..92bb88c 100644 --- a/accessories/knxdevice.js +++ b/accessories/knxdevice.js @@ -21,8 +21,8 @@ New 2015-10-07: - Accept uuid_base parameter from config.json to use as unique identifier in UUIDs instead of name (optional) * */ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var knxd = require("eibd"); var knxd_registerGA = require('../platforms/KNX.js').registerGA; var knxd_startMonitor = require('../platforms/KNX.js').startMonitor; diff --git a/accessories/mpdclient.js b/accessories/mpdclient.js index 8fee5eb..ac6bf5d 100644 --- a/accessories/mpdclient.js +++ b/accessories/mpdclient.js @@ -1,5 +1,5 @@ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); var komponist = require('komponist') diff --git a/app.js b/app.js index 126b7e3..ffc203e 100644 --- a/app.js +++ b/app.js @@ -1,13 +1,13 @@ var fs = require('fs'); var path = require('path'); var storage = require('node-persist'); -var hap = require('HAP-NodeJS'); -var uuid = require('HAP-NodeJS').uuid; -var Bridge = require('HAP-NodeJS').Bridge; -var Accessory = require('HAP-NodeJS').Accessory; -var Service = require('HAP-NodeJS').Service; -var Characteristic = require('HAP-NodeJS').Characteristic; -var accessoryLoader = require('HAP-NodeJS').AccessoryLoader; +var hap = require("hap-nodejs"); +var uuid = require("hap-nodejs").uuid; +var Bridge = require("hap-nodejs").Bridge; +var Accessory = require("hap-nodejs").Accessory; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; +var accessoryLoader = require("hap-nodejs").AccessoryLoader; var once = require('HAP-NodeJS/lib/util/once').once; console.log("Starting HomeBridge server..."); diff --git a/package.json b/package.json index feb0370..95db873 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "color": "0.10.x", "eibd": "^0.3.1", "elkington": "kevinohara80/elkington", - "hap-nodejs": "git+https://github.com/KhaosT/HAP-NodeJS#4650e771f356a220868d873d16564a6be6603ff7", + "hap-nodejs": "git+https://github.com/KhaosT/HAP-NodeJS#215a3bb1d603097d63ba73d4f7d731813c6b87e5", "harmonyhubjs-client": "^1.1.4", "harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git", "lifx-api": "^1.0.1", diff --git a/platforms/FHEM.js b/platforms/FHEM.js index 03f3ee1..7ef5e37 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -16,8 +16,8 @@ // When you attempt to add a device, it will ask for a "PIN code". // The default code for all HomeBridge accessories is 031-45-154. -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var types = require('HAP-NodeJS/accessories/types.js'); diff --git a/platforms/FibaroHC2.js b/platforms/FibaroHC2.js index 4567b40..8293645 100644 --- a/platforms/FibaroHC2.js +++ b/platforms/FibaroHC2.js @@ -15,8 +15,8 @@ // The default code for all HomeBridge accessories is 031-45-154. var types = require("HAP-NodeJS/accessories/types.js"); -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); function FibaroHC2Platform(log, config){ diff --git a/platforms/HomeAssistant.js b/platforms/HomeAssistant.js index 61d3bc2..1bfdc6c 100644 --- a/platforms/HomeAssistant.js +++ b/platforms/HomeAssistant.js @@ -67,8 +67,8 @@ // When you attempt to add a device, it will ask for a "PIN code". // The default code for all HomeBridge accessories is 031-45-154. -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var url = require('url') var request = require("request"); diff --git a/platforms/HomeSeer.js b/platforms/HomeSeer.js index c4ccde7..ac0bfa0 100644 --- a/platforms/HomeSeer.js +++ b/platforms/HomeSeer.js @@ -139,8 +139,8 @@ // - Door -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); diff --git a/platforms/LIFx.js b/platforms/LIFx.js index 79988eb..89de156 100644 --- a/platforms/LIFx.js +++ b/platforms/LIFx.js @@ -16,8 +16,8 @@ // The default code for all HomeBridge accessories is 031-45-154. // -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var lifxRemoteObj = require('lifx-api'); var lifx_remote; diff --git a/platforms/MiLight.js b/platforms/MiLight.js index 3869e74..0325352 100644 --- a/platforms/MiLight.js +++ b/platforms/MiLight.js @@ -47,8 +47,8 @@ TODO: */ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var Milight = require('node-milight-promise').MilightController; var commands = require('node-milight-promise').commands; diff --git a/platforms/YamahaAVR.js b/platforms/YamahaAVR.js index 0dde24f..be73b77 100644 --- a/platforms/YamahaAVR.js +++ b/platforms/YamahaAVR.js @@ -1,8 +1,8 @@ var types = require("HAP-NodeJS/accessories/types.js"); var inherits = require('util').inherits; var debug = require('debug')('YamahaAVR'); -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var Yamaha = require('yamaha-nodejs'); var Q = require('q'); var mdns = require('mdns'); diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index e50336e..9800ab6 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -1,6 +1,6 @@ var debug = require('debug')('ZWayServer'); -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var types = require("HAP-NodeJS/accessories/types.js"); var request = require("request"); var tough = require('tough-cookie'); From c22c14584dab3b8d592184aa4852a540885f6121 Mon Sep 17 00:00:00 2001 From: Mike Riccio Date: Fri, 16 Oct 2015 09:36:18 -0700 Subject: [PATCH 24/72] Added more logging when there's an error getting device details. --- platforms/Indigo.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/platforms/Indigo.js b/platforms/Indigo.js index 98f259c..cd20c84 100644 --- a/platforms/Indigo.js +++ b/platforms/Indigo.js @@ -80,14 +80,16 @@ IndigoPlatform.prototype = { request(deviceOptions, function(deviceError, deviceResponse, deviceBody) { if (deviceError) { - asyncCallback(deviceError); + console.trace("Requesting Indigo device info: " + deviceURL + "\nError: " + deviceError + "\nResponse: " + deviceBody); + asyncCallback(deviceError) + } + else { + var deviceJson = JSON.parse(deviceBody); + that.log("Discovered " + deviceJson.type + ": " + deviceJson.name); + that.foundAccessories.push( + new IndigoAccessory(that.log, that.auth, deviceURL, deviceJson)); + asyncCallback(); } - - var deviceJson = JSON.parse(deviceBody); - that.log("Discovered " + deviceJson.type + ": " + deviceJson.name); - that.foundAccessories.push( - new IndigoAccessory(that.log, that.auth, deviceURL, deviceJson)); - asyncCallback(); }); }, function(asyncError) { // This will be called after all the requests complete From bb1c193bc030921ee620ba5d9c75e9e41ac3f90c Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Fri, 16 Oct 2015 19:10:14 +0200 Subject: [PATCH 25/72] Use npm release of hap-nodejs --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 95db873..bbc5669 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "color": "0.10.x", "eibd": "^0.3.1", "elkington": "kevinohara80/elkington", - "hap-nodejs": "git+https://github.com/KhaosT/HAP-NodeJS#215a3bb1d603097d63ba73d4f7d731813c6b87e5", + "hap-nodejs": "^0.0.2", "harmonyhubjs-client": "^1.1.4", "harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git", "lifx-api": "^1.0.1", From 831480d035660ba0a2532af4ebfc7a1cefa915c9 Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Fri, 16 Oct 2015 10:40:01 -0700 Subject: [PATCH 26/72] Remove telldus for now --- package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/package.json b/package.json index bbc5669..ff5563b 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,6 @@ "tough-cookie": "^2.0.0", "request": "2.49.x", "sonos": "0.8.x", - "telldus": "0.0.9", - "telldus-live": "0.2.x", "teslams": "1.0.1", "unofficial-nest-api": "git+https://github.com/hachidorii/unofficial_nodejs_nest.git#d8d48edc952b049ff6320ef99afa7b2f04cdee98", "wemo": "0.2.x", From c5499c122e11bfb3ee130f0670d80f0c38bc7aa1 Mon Sep 17 00:00:00 2001 From: Mike Riccio Date: Fri, 16 Oct 2015 20:21:30 -0700 Subject: [PATCH 27/72] Add devices even if discovery errors. Don't assign everything POWER_STATE_CTYPE. --- platforms/Indigo.js | 67 +++++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/platforms/Indigo.js b/platforms/Indigo.js index cd20c84..1b7f722 100644 --- a/platforms/Indigo.js +++ b/platforms/Indigo.js @@ -19,8 +19,8 @@ // The default code for all HomeBridge accessories is 031-45-154. // -var types = require("HAP-NodeJS/accessories/types.js"); -var Characteristic = require("HAP-NodeJS").Characteristic; +var types = require("hap-nodejs/accessories/types.js"); +var Characteristic = require("hap-nodejs").Characteristic; var request = require('request'); var async = require('async'); @@ -96,11 +96,11 @@ IndigoPlatform.prototype = { if (asyncError) { console.trace("Requesting Indigo device info."); that.log(asyncError); - } else { - that.callback(that.foundAccessories.sort(function (a,b) { - return (a.name > b.name) - (a.name < b.name); - })); } + + that.callback(that.foundAccessories.sort(function (a,b) { + return (a.name > b.name) - (a.name < b.name); + })); }); }); } @@ -326,6 +326,8 @@ IndigoAccessory.prototype = { }, controlCharacteristics: function(that) { + var hasAType = false; + var cTypes = [{ cType: types.NAME_CTYPE, onUpdate: null, @@ -338,30 +340,8 @@ IndigoAccessory.prototype = { designedMaxLength: 255 }]; - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], - format: Characteristic.Formats.BOOL, - initialValue: (that.isOn) ? 1 : 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1, - onUpdate: function(value) { - if (value == 0) { - that.turnOff(); - } else { - that.turnOn(); - } - }, - onRead: function(callback) { - that.query("isOn", function(isOn) { - callback((isOn) ? 1 : 0); - }); - } - }); - if (that.typeSupportsDim) { + hasAType = true; cTypes.push({ cType: types.BRIGHTNESS_CTYPE, perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], @@ -384,6 +364,7 @@ IndigoAccessory.prototype = { } if (that.typeSupportsSpeedControl) { + hasAType = true; cTypes.push({ cType: types.ROTATION_SPEED_CTYPE, perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], @@ -406,6 +387,7 @@ IndigoAccessory.prototype = { } if (that.typeSupportsHVAC) { + hasAType = true; cTypes.push({ cType: types.CURRENTHEATINGCOOLING_CTYPE, perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], @@ -486,7 +468,7 @@ IndigoAccessory.prototype = { cType: types.TEMPERATURE_UNITS_CTYPE, perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], format: Characteristic.Formats.INT, - initialValue: 1, + initialValue: Characteristic.Units.FAHRENHEIT, supportEvents: false, supportBonjour: false, manfDescription: "Unit", @@ -497,6 +479,31 @@ IndigoAccessory.prototype = { }); } + if (that.typeSupportsOnOff || !hasAType) { + cTypes.push({ + cType: types.POWER_STATE_CTYPE, + perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.BOOL, + initialValue: (that.isOn) ? 1 : 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Change the power state", + designedMaxLength: 1, + onUpdate: function(value) { + if (value == 0) { + that.turnOff(); + } else { + that.turnOn(); + } + }, + onRead: function(callback) { + that.query("isOn", function(isOn) { + callback((isOn) ? 1 : 0); + }); + } + }); + } + return cTypes; }, From 896d6b40d958a3e93e12ef807b9c1958d08410cc Mon Sep 17 00:00:00 2001 From: Mike Riccio Date: Fri, 16 Oct 2015 20:36:32 -0700 Subject: [PATCH 28/72] Fixed fahrenheit units, was giving error. --- platforms/Indigo.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/platforms/Indigo.js b/platforms/Indigo.js index 1b7f722..cc77e21 100644 --- a/platforms/Indigo.js +++ b/platforms/Indigo.js @@ -430,10 +430,10 @@ IndigoAccessory.prototype = { cType: types.CURRENT_TEMPERATURE_CTYPE, perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], format: Characteristic.Formats.INT, - designedMinValue: 0, - designedMaxValue: 110, + designedMinValue: 16, + designedMaxValue: 38, designedMinStep: 1, - initialValue: 0, + initialValue: 20, supportEvents: false, supportBonjour: false, manfDescription: "Current Temperature", @@ -448,10 +448,10 @@ IndigoAccessory.prototype = { cType: types.TARGET_TEMPERATURE_CTYPE, perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], format: Characteristic.Formats.INT, - designedMinValue: 0, - designedMaxValue: 110, + designedMinValue: 16, + designedMaxValue: 38, designedMinStep: 1, - initialValue: 0, + initialValue: 20, supportEvents: false, supportBonjour: false, manfDescription: "Target Temperature", @@ -468,13 +468,13 @@ IndigoAccessory.prototype = { cType: types.TEMPERATURE_UNITS_CTYPE, perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], format: Characteristic.Formats.INT, - initialValue: Characteristic.Units.FAHRENHEIT, + initialValue: 1, supportEvents: false, supportBonjour: false, manfDescription: "Unit", onUpdate: null, onRead: function(callback) { - callback(Characteristic.Units.FAHRENHEIT); + callback(1); } }); } From f2d22584fcc79a6c7b59222687ee26d9b844ec3c Mon Sep 17 00:00:00 2001 From: tommasomarchionni Date: Sat, 17 Oct 2015 12:34:40 +0200 Subject: [PATCH 29/72] Update app.js --- app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.js b/app.js index ffc203e..a17f8ed 100644 --- a/app.js +++ b/app.js @@ -8,7 +8,7 @@ var Accessory = require("hap-nodejs").Accessory; var Service = require("hap-nodejs").Service; var Characteristic = require("hap-nodejs").Characteristic; var accessoryLoader = require("hap-nodejs").AccessoryLoader; -var once = require('HAP-NodeJS/lib/util/once').once; +var once = require("hap-nodejs").once; console.log("Starting HomeBridge server..."); From 385908fa8910f8f5a062e5a4042cef2116725286 Mon Sep 17 00:00:00 2001 From: tommasomarchionni Date: Sat, 17 Oct 2015 13:23:30 +0200 Subject: [PATCH 30/72] Update app.js --- app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.js b/app.js index a17f8ed..20279fa 100644 --- a/app.js +++ b/app.js @@ -8,7 +8,7 @@ var Accessory = require("hap-nodejs").Accessory; var Service = require("hap-nodejs").Service; var Characteristic = require("hap-nodejs").Characteristic; var accessoryLoader = require("hap-nodejs").AccessoryLoader; -var once = require("hap-nodejs").once; +var once = require("hap-nodejs/lib/util/once").once; console.log("Starting HomeBridge server..."); From 196cda80634d59582f22ae7d08c28c7050edfdc4 Mon Sep 17 00:00:00 2001 From: William Bout Date: Sat, 17 Oct 2015 18:12:11 +0200 Subject: [PATCH 31/72] Update Hyperion.js Fix require error --- accessories/Hyperion.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accessories/Hyperion.js b/accessories/Hyperion.js index b01db55..8767b19 100644 --- a/accessories/Hyperion.js +++ b/accessories/Hyperion.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var net = require('net'); var Color = require('color'); From 38cb94c012eaf249ca8187e87124aa2633f98e00 Mon Sep 17 00:00:00 2001 From: Mason James Date: Sat, 17 Oct 2015 18:41:57 -0400 Subject: [PATCH 32/72] Update hap-nodejs requires Update remaining platforms and accessories --- accessories/AD2USB.js | 2 +- accessories/Carwings.js | 2 +- accessories/ELKM1.js | 2 +- accessories/HomeMatic.js | 2 +- accessories/HomeMaticThermo.js | 2 +- accessories/HomeMaticWindow.js | 2 +- accessories/LiftMaster.js | 2 +- accessories/Tesla.js | 2 +- accessories/X10.js | 2 +- platforms/Domoticz.js | 2 +- platforms/FHEM.js | 2 +- platforms/FibaroHC2.js | 2 +- platforms/ISY.js | 2 +- platforms/KNX.js | 2 +- platforms/LogitechHarmony.js | 2 +- platforms/Nest.js | 2 +- platforms/PhilipsHue.js | 2 +- platforms/SmartThings.js | 2 +- platforms/Sonos.js | 2 +- platforms/Telldus.js | 2 +- platforms/TelldusLive.js | 2 +- platforms/Wink.js | 2 +- platforms/YamahaAVR.js | 2 +- platforms/ZWayServer.js | 2 +- 24 files changed, 24 insertions(+), 24 deletions(-) diff --git a/accessories/AD2USB.js b/accessories/AD2USB.js index ea05377..bcef82d 100644 --- a/accessories/AD2USB.js +++ b/accessories/AD2USB.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var AD2USB = require('ad2usb'); var CUSTOM_PANEL_LCD_TEXT_CTYPE = "A3E7B8F9-216E-42C1-A21C-97D4E3BE52C8"; diff --git a/accessories/Carwings.js b/accessories/Carwings.js index 150c936..7d0cd82 100644 --- a/accessories/Carwings.js +++ b/accessories/Carwings.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var carwings = require("carwingsjs"); function CarwingsAccessory(log, config) { diff --git a/accessories/ELKM1.js b/accessories/ELKM1.js index d32cc1b..ead0f17 100644 --- a/accessories/ELKM1.js +++ b/accessories/ELKM1.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var elkington = require("elkington"); function ElkM1Accessory(log, config) { diff --git a/accessories/HomeMatic.js b/accessories/HomeMatic.js index 89e43b0..23a7ecb 100644 --- a/accessories/HomeMatic.js +++ b/accessories/HomeMatic.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); function HomeMatic(log, config) { diff --git a/accessories/HomeMaticThermo.js b/accessories/HomeMaticThermo.js index f3d300f..86db229 100644 --- a/accessories/HomeMaticThermo.js +++ b/accessories/HomeMaticThermo.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); function HomeMaticThermo(log, config) { diff --git a/accessories/HomeMaticWindow.js b/accessories/HomeMaticWindow.js index 865b24a..6dcaf78 100644 --- a/accessories/HomeMaticWindow.js +++ b/accessories/HomeMaticWindow.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); diff --git a/accessories/LiftMaster.js b/accessories/LiftMaster.js index baf3be8..c239f24 100644 --- a/accessories/LiftMaster.js +++ b/accessories/LiftMaster.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); // This seems to be the "id" of the official LiftMaster iOS app diff --git a/accessories/Tesla.js b/accessories/Tesla.js index 016a465..8d96e57 100644 --- a/accessories/Tesla.js +++ b/accessories/Tesla.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var tesla = require("teslams"); function TeslaAccessory(log, config) { diff --git a/accessories/X10.js b/accessories/X10.js index 6668a44..0cf781c 100644 --- a/accessories/X10.js +++ b/accessories/X10.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); function X10(log, config) { diff --git a/platforms/Domoticz.js b/platforms/Domoticz.js index 8930011..948167d 100644 --- a/platforms/Domoticz.js +++ b/platforms/Domoticz.js @@ -50,7 +50,7 @@ // When you attempt to add a device, it will ask for a "PIN code". // The default code for all HomeBridge accessories is 031-45-154. // -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); function DomoticzPlatform(log, config){ diff --git a/platforms/FHEM.js b/platforms/FHEM.js index 7ef5e37..b892a0a 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -19,7 +19,7 @@ var Service = require("hap-nodejs").Service; var Characteristic = require("hap-nodejs").Characteristic; -var types = require('HAP-NodeJS/accessories/types.js'); +var types = require('hap-nodejs/accessories/types.js'); var util = require('util'); diff --git a/platforms/FibaroHC2.js b/platforms/FibaroHC2.js index 8293645..57c880c 100644 --- a/platforms/FibaroHC2.js +++ b/platforms/FibaroHC2.js @@ -14,7 +14,7 @@ // When you attempt to add a device, it will ask for a "PIN code". // The default code for all HomeBridge accessories is 031-45-154. -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var Service = require("hap-nodejs").Service; var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); diff --git a/platforms/ISY.js b/platforms/ISY.js index b4b20a8..53c4980 100644 --- a/platforms/ISY.js +++ b/platforms/ISY.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var xml2js = require('xml2js'); var request = require('request'); var util = require('util'); diff --git a/platforms/KNX.js b/platforms/KNX.js index 65f7a13..0ca4309 100644 --- a/platforms/KNX.js +++ b/platforms/KNX.js @@ -2,7 +2,7 @@ * based on Sonos platform */ 'use strict'; -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); //var hardware = require('myHardwareSupport'); //require any additional hardware packages var knxd = require('eibd'); diff --git a/platforms/LogitechHarmony.js b/platforms/LogitechHarmony.js index a5c5e22..0521a65 100644 --- a/platforms/LogitechHarmony.js +++ b/platforms/LogitechHarmony.js @@ -17,7 +17,7 @@ // -var types = require('HAP-NodeJS/accessories/types.js'); +var types = require('hap-nodejs/accessories/types.js'); var harmonyDiscover = require('harmonyhubjs-discover'); var harmony = require('harmonyhubjs-client'); diff --git a/platforms/Nest.js b/platforms/Nest.js index c1ef3bd..9d225c0 100644 --- a/platforms/Nest.js +++ b/platforms/Nest.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var nest = require('unofficial-nest-api'); function NestPlatform(log, config){ diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 9bceaf0..40dcc33 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -33,7 +33,7 @@ var hue = require("node-hue-api"), HueApi = hue.HueApi, lightState = hue.lightState; -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); function PhilipsHuePlatform(log, config) { this.log = log; diff --git a/platforms/SmartThings.js b/platforms/SmartThings.js index 580e70b..470ee55 100644 --- a/platforms/SmartThings.js +++ b/platforms/SmartThings.js @@ -1,7 +1,7 @@ // SmartThings JSON API SmartApp required // https://github.com/jnewland/SmartThings/blob/master/JSON.groovy // -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); function SmartThingsPlatform(log, config){ diff --git a/platforms/Sonos.js b/platforms/Sonos.js index 1d19c2f..812a803 100644 --- a/platforms/Sonos.js +++ b/platforms/Sonos.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var sonos = require('sonos'); function SonosPlatform(log, config){ diff --git a/platforms/Telldus.js b/platforms/Telldus.js index 87d37f1..c192a31 100644 --- a/platforms/Telldus.js +++ b/platforms/Telldus.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var telldus = require('telldus'); function TelldusPlatform(log, config) { diff --git a/platforms/TelldusLive.js b/platforms/TelldusLive.js index b6a88f1..0e861b5 100644 --- a/platforms/TelldusLive.js +++ b/platforms/TelldusLive.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var TellduAPI = require("telldus-live"); function TelldusLivePlatform(log, config) { diff --git a/platforms/Wink.js b/platforms/Wink.js index e2ec455..d9de07f 100644 --- a/platforms/Wink.js +++ b/platforms/Wink.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var wink = require('wink-js'); var model = { diff --git a/platforms/YamahaAVR.js b/platforms/YamahaAVR.js index be73b77..8d79058 100644 --- a/platforms/YamahaAVR.js +++ b/platforms/YamahaAVR.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var inherits = require('util').inherits; var debug = require('debug')('YamahaAVR'); var Service = require("hap-nodejs").Service; diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index 9800ab6..f456cff 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -1,7 +1,7 @@ var debug = require('debug')('ZWayServer'); var Service = require("hap-nodejs").Service; var Characteristic = require("hap-nodejs").Characteristic; -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); var tough = require('tough-cookie'); var Q = require("q"); From 15e9219c80aea5c106af3f775fdec1d42084127b Mon Sep 17 00:00:00 2001 From: David Garozzo Date: Sat, 17 Oct 2015 21:43:43 -0400 Subject: [PATCH 33/72] add async dependency for Indigo --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index ff5563b..f8bf8e4 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "license": "ISC", "dependencies": { "ad2usb": "git+https://github.com/alistairg/node-ad2usb.git#local", + "async": "^1.4.2", "carwingsjs": "0.0.x", "chokidar": "^1.0.5", "color": "0.10.x", From c897913005bf3a0f19ae77c72d3918c346c262cd Mon Sep 17 00:00:00 2001 From: Kraig McConaghy Date: Sat, 17 Oct 2015 20:51:57 -0500 Subject: [PATCH 34/72] 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 ad62bd4b15a954defbd3a211de588f5058db6847 Mon Sep 17 00:00:00 2001 From: Mike Riccio Date: Sat, 17 Oct 2015 20:27:04 -0700 Subject: [PATCH 35/72] Graceful handling of JSON parse errors from Indigo responses. Make initial discover queries serialized to not overwhelm slow Indigo servers. --- platforms/Indigo.js | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/platforms/Indigo.js b/platforms/Indigo.js index cc77e21..4886f29 100644 --- a/platforms/Indigo.js +++ b/platforms/Indigo.js @@ -68,7 +68,7 @@ IndigoPlatform.prototype = { } var json = JSON.parse(body); - async.each(json, function(item, asyncCallback) { + async.eachSeries(json, function(item, asyncCallback) { var deviceURL = that.baseURL + item.restURL; var deviceOptions = { url: deviceURL, @@ -81,15 +81,19 @@ IndigoPlatform.prototype = { request(deviceOptions, function(deviceError, deviceResponse, deviceBody) { if (deviceError) { console.trace("Requesting Indigo device info: " + deviceURL + "\nError: " + deviceError + "\nResponse: " + deviceBody); - asyncCallback(deviceError) } else { - var deviceJson = JSON.parse(deviceBody); - that.log("Discovered " + deviceJson.type + ": " + deviceJson.name); - that.foundAccessories.push( - new IndigoAccessory(that.log, that.auth, deviceURL, deviceJson)); - asyncCallback(); + try { + var deviceJson = JSON.parse(deviceBody); + that.log("Discovered " + deviceJson.type + ": " + deviceJson.name); + that.foundAccessories.push( + new IndigoAccessory(that.log, that.auth, deviceURL, deviceJson)); + } + catch (e) { + that.log("Error parsing Indigo device info: " + deviceURL + "\nException: " + e + "\nResponse: " + deviceBody); + } } + asyncCallback(); }); }, function(asyncError) { // This will be called after all the requests complete @@ -135,11 +139,18 @@ IndigoAccessory.prototype = { if (error) { console.trace("Requesting Device Status."); that.log(error); - return error; } - - that.log("getStatus of " + that.name + ": " + body); - callback(JSON.parse(body)); + else { + that.log("getStatus of " + that.name + ": " + body); + try { + var json = JSON.parse(body); + callback(json); + } + catch (e) { + console.trace("Requesting Device Status."); + that.log("Exception: " + e + "\nResponse: " + body); + } + } }); }, From 915e67458354bc130bf47261827cab228cb6db94 Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Sun, 18 Oct 2015 13:29:50 +0200 Subject: [PATCH 36/72] Change all HAP-NodeJS require calls to lowercase --- accessories/AD2USB.js | 2 +- accessories/Carwings.js | 2 +- accessories/ELKM1.js | 2 +- accessories/HomeMatic.js | 2 +- accessories/HomeMaticThermo.js | 2 +- accessories/HomeMaticWindow.js | 2 +- accessories/LiftMaster.js | 2 +- accessories/Tesla.js | 2 +- accessories/X10.js | 2 +- platforms/Domoticz.js | 2 +- platforms/FHEM.js | 2 +- platforms/FibaroHC2.js | 2 +- platforms/ISY.js | 2 +- platforms/KNX.js | 2 +- platforms/LogitechHarmony.js | 2 +- platforms/Nest.js | 2 +- platforms/PhilipsHue.js | 2 +- platforms/SmartThings.js | 2 +- platforms/Sonos.js | 2 +- platforms/Telldus.js | 2 +- platforms/TelldusLive.js | 2 +- platforms/Wink.js | 2 +- platforms/YamahaAVR.js | 2 +- platforms/ZWayServer.js | 2 +- 24 files changed, 24 insertions(+), 24 deletions(-) diff --git a/accessories/AD2USB.js b/accessories/AD2USB.js index ea05377..bcef82d 100644 --- a/accessories/AD2USB.js +++ b/accessories/AD2USB.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var AD2USB = require('ad2usb'); var CUSTOM_PANEL_LCD_TEXT_CTYPE = "A3E7B8F9-216E-42C1-A21C-97D4E3BE52C8"; diff --git a/accessories/Carwings.js b/accessories/Carwings.js index 150c936..7d0cd82 100644 --- a/accessories/Carwings.js +++ b/accessories/Carwings.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var carwings = require("carwingsjs"); function CarwingsAccessory(log, config) { diff --git a/accessories/ELKM1.js b/accessories/ELKM1.js index d32cc1b..ead0f17 100644 --- a/accessories/ELKM1.js +++ b/accessories/ELKM1.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var elkington = require("elkington"); function ElkM1Accessory(log, config) { diff --git a/accessories/HomeMatic.js b/accessories/HomeMatic.js index 89e43b0..23a7ecb 100644 --- a/accessories/HomeMatic.js +++ b/accessories/HomeMatic.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); function HomeMatic(log, config) { diff --git a/accessories/HomeMaticThermo.js b/accessories/HomeMaticThermo.js index f3d300f..86db229 100644 --- a/accessories/HomeMaticThermo.js +++ b/accessories/HomeMaticThermo.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); function HomeMaticThermo(log, config) { diff --git a/accessories/HomeMaticWindow.js b/accessories/HomeMaticWindow.js index 865b24a..6dcaf78 100644 --- a/accessories/HomeMaticWindow.js +++ b/accessories/HomeMaticWindow.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); diff --git a/accessories/LiftMaster.js b/accessories/LiftMaster.js index baf3be8..c239f24 100644 --- a/accessories/LiftMaster.js +++ b/accessories/LiftMaster.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); // This seems to be the "id" of the official LiftMaster iOS app diff --git a/accessories/Tesla.js b/accessories/Tesla.js index 016a465..8d96e57 100644 --- a/accessories/Tesla.js +++ b/accessories/Tesla.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var tesla = require("teslams"); function TeslaAccessory(log, config) { diff --git a/accessories/X10.js b/accessories/X10.js index 6668a44..0cf781c 100644 --- a/accessories/X10.js +++ b/accessories/X10.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); function X10(log, config) { diff --git a/platforms/Domoticz.js b/platforms/Domoticz.js index 8930011..948167d 100644 --- a/platforms/Domoticz.js +++ b/platforms/Domoticz.js @@ -50,7 +50,7 @@ // When you attempt to add a device, it will ask for a "PIN code". // The default code for all HomeBridge accessories is 031-45-154. // -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); function DomoticzPlatform(log, config){ diff --git a/platforms/FHEM.js b/platforms/FHEM.js index 7ef5e37..b892a0a 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -19,7 +19,7 @@ var Service = require("hap-nodejs").Service; var Characteristic = require("hap-nodejs").Characteristic; -var types = require('HAP-NodeJS/accessories/types.js'); +var types = require('hap-nodejs/accessories/types.js'); var util = require('util'); diff --git a/platforms/FibaroHC2.js b/platforms/FibaroHC2.js index 8293645..57c880c 100644 --- a/platforms/FibaroHC2.js +++ b/platforms/FibaroHC2.js @@ -14,7 +14,7 @@ // When you attempt to add a device, it will ask for a "PIN code". // The default code for all HomeBridge accessories is 031-45-154. -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var Service = require("hap-nodejs").Service; var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); diff --git a/platforms/ISY.js b/platforms/ISY.js index b4b20a8..53c4980 100644 --- a/platforms/ISY.js +++ b/platforms/ISY.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var xml2js = require('xml2js'); var request = require('request'); var util = require('util'); diff --git a/platforms/KNX.js b/platforms/KNX.js index 65f7a13..0ca4309 100644 --- a/platforms/KNX.js +++ b/platforms/KNX.js @@ -2,7 +2,7 @@ * based on Sonos platform */ 'use strict'; -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); //var hardware = require('myHardwareSupport'); //require any additional hardware packages var knxd = require('eibd'); diff --git a/platforms/LogitechHarmony.js b/platforms/LogitechHarmony.js index a5c5e22..0521a65 100644 --- a/platforms/LogitechHarmony.js +++ b/platforms/LogitechHarmony.js @@ -17,7 +17,7 @@ // -var types = require('HAP-NodeJS/accessories/types.js'); +var types = require('hap-nodejs/accessories/types.js'); var harmonyDiscover = require('harmonyhubjs-discover'); var harmony = require('harmonyhubjs-client'); diff --git a/platforms/Nest.js b/platforms/Nest.js index c1ef3bd..9d225c0 100644 --- a/platforms/Nest.js +++ b/platforms/Nest.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var nest = require('unofficial-nest-api'); function NestPlatform(log, config){ diff --git a/platforms/PhilipsHue.js b/platforms/PhilipsHue.js index 9bceaf0..40dcc33 100644 --- a/platforms/PhilipsHue.js +++ b/platforms/PhilipsHue.js @@ -33,7 +33,7 @@ var hue = require("node-hue-api"), HueApi = hue.HueApi, lightState = hue.lightState; -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); function PhilipsHuePlatform(log, config) { this.log = log; diff --git a/platforms/SmartThings.js b/platforms/SmartThings.js index 580e70b..470ee55 100644 --- a/platforms/SmartThings.js +++ b/platforms/SmartThings.js @@ -1,7 +1,7 @@ // SmartThings JSON API SmartApp required // https://github.com/jnewland/SmartThings/blob/master/JSON.groovy // -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); function SmartThingsPlatform(log, config){ diff --git a/platforms/Sonos.js b/platforms/Sonos.js index 1d19c2f..812a803 100644 --- a/platforms/Sonos.js +++ b/platforms/Sonos.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var sonos = require('sonos'); function SonosPlatform(log, config){ diff --git a/platforms/Telldus.js b/platforms/Telldus.js index 87d37f1..c192a31 100644 --- a/platforms/Telldus.js +++ b/platforms/Telldus.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var telldus = require('telldus'); function TelldusPlatform(log, config) { diff --git a/platforms/TelldusLive.js b/platforms/TelldusLive.js index b6a88f1..0e861b5 100644 --- a/platforms/TelldusLive.js +++ b/platforms/TelldusLive.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var TellduAPI = require("telldus-live"); function TelldusLivePlatform(log, config) { diff --git a/platforms/Wink.js b/platforms/Wink.js index e2ec455..d9de07f 100644 --- a/platforms/Wink.js +++ b/platforms/Wink.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var wink = require('wink-js'); var model = { diff --git a/platforms/YamahaAVR.js b/platforms/YamahaAVR.js index be73b77..8d79058 100644 --- a/platforms/YamahaAVR.js +++ b/platforms/YamahaAVR.js @@ -1,4 +1,4 @@ -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var inherits = require('util').inherits; var debug = require('debug')('YamahaAVR'); var Service = require("hap-nodejs").Service; diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index 9800ab6..f456cff 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -1,7 +1,7 @@ var debug = require('debug')('ZWayServer'); var Service = require("hap-nodejs").Service; var Characteristic = require("hap-nodejs").Characteristic; -var types = require("HAP-NodeJS/accessories/types.js"); +var types = require("hap-nodejs/accessories/types.js"); var request = require("request"); var tough = require('tough-cookie'); var Q = require("q"); From bf125078a1264cded3c24914988814370146a206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Sun, 18 Oct 2015 18:25:31 +0200 Subject: [PATCH 37/72] 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 38/72] 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 39/72] 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 40/72] 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 41/72] Added handling to keep track of activity state --- platforms/LogitechHarmony.js | 90 +++++++++++++++++++++--------------- 1 file changed, 54 insertions(+), 36 deletions(-) diff --git a/platforms/LogitechHarmony.js b/platforms/LogitechHarmony.js index a0172a4..2167b1c 100644 --- a/platforms/LogitechHarmony.js +++ b/platforms/LogitechHarmony.js @@ -63,7 +63,7 @@ LogitechHarmonyPlatform.prototype = { setTimeout(function() { setInterval(function() { self.log("Sending command to prevent timeout"); - client.getCurrentActivity(); + client.getCurrentActivity().then(self.updateCurrentActivity.bind(self)); }, 20000); }, 5000); @@ -99,6 +99,13 @@ LogitechHarmonyPlatform.prototype = { discover.start(); }, + updateCurrentActivity: function(currentActivity) { + var actAccessories = this.activityAccessories; + if (actAccessories instanceof Array) { + actAccessories.map(function(a) { a.updateActivityState(currentActivity); }); + } + }, + accessories: function (callback) { var self = this; var foundAccessories = []; @@ -142,30 +149,36 @@ LogitechHarmonyPlatform.prototype = { .then(function (activities) { self.log("Found activities: \n" + activities.map(function (a) { return "\t" + a.label; }).join("\n")); - var sArray = sortByKey(activities, "label"); - - sArray.map(function(s) { - var accessory = new LogitechHarmonyActivityAccessory(self.log, hub, s); - // TODO: Update the initial power state - foundAccessories.push(accessory); + hub.getCurrentActivity().then(function (currentActivity) { + var actAccessories = []; + var sArray = sortByKey(activities, "label"); + sArray.map(function(s) { + var accessory = new LogitechHarmonyActivityAccessory(self.log, hub, s, self.updateCurrentActivity.bind(self)); + accessory.updateActivityState(currentActivity); + actAccessories.push(accessory); + foundAccessories.push(accessory); + }); + self.activityAccessories = actAccessories; + callback(foundAccessories); + }).catch(function (err) { + self.log('Unable to get current activity with error', err); + callback(false); }); - - callback(foundAccessories); }); }; - } - }; - -function LogitechHarmonyActivityAccessory (log, hub, details) { +function LogitechHarmonyActivityAccessory (log, hub, details, updateCurrentActivity) { this.log = log; this.hub = hub; this.details = details; this.id = details.id; this.name = details.label; + this.isOn = false; + this.updateCurrentActivity = updateCurrentActivity; Accessory.call(this, this.name, uuid.generate(this.id)); + var self = this; this.getService(Service.AccessoryInformation) .setCharacteristic(Characteristic.Manufacturer, "Logitech") @@ -175,8 +188,12 @@ function LogitechHarmonyActivityAccessory (log, hub, details) { this.addService(Service.Switch) .getCharacteristic(Characteristic.On) - .on('get', this.getPowerState) - .on('set', this.setPowerState); + .on('get', function(callback) { + // Refreshed automatically by platform + callback(null, self.isOn); + }) + .on('set', this.setPowerState.bind(this)); + }; inherits(LogitechHarmonyActivityAccessory, Accessory); LogitechHarmonyActivityAccessory.prototype.parent = Accessory.prototype; @@ -184,31 +201,32 @@ LogitechHarmonyActivityAccessory.prototype.getServices = function() { return this.services; }; - // TODO: Somehow make this event driven so that it tells the user what activity is on - LogitechHarmonyActivityAccessory.prototype.getPowerState = function (callback) { - var self = this; +LogitechHarmonyActivityAccessory.prototype.updateActivityState = function (currentActivity) { + this.isOn = (currentActivity === this.id); + // Force get to trigger 'change' if needed + this.getService(Service.Switch) + .getCharacteristic(Characteristic.On) + .getValue(); +}; - this.hub.getCurrentActivity().then(function (currentActivity) { - callback(currentActivity === self.id); - }).catch(function (err) { - self.log('Unable to get current activity with error', err); - callback(false); - }); - }; +LogitechHarmonyActivityAccessory.prototype.setPowerState = function (state, callback) { - LogitechHarmonyActivityAccessory.prototype.setPowerState = function (state) { - var self = this; + var self = this; - this.log('Set activity ' + this.name + ' power state to ' + state); + this.log('Set activity ' + this.name + ' power state to ' + state); - this.hub.startActivity(self.id) - .then(function () { - self.log('Finished setting activity ' + self.name + ' power state to ' + state); - }) - .catch(function (err) { - self.log('Failed setting activity ' + self.name + ' power state to ' + state + ' with error ' + err); - }); - }; + var nextActivity = self.id; + this.hub.startActivity(nextActivity) + .then(function () { + self.log('Finished setting activity ' + self.name + ' power state to ' + state); + self.updateCurrentActivity(nextActivity); + if (callback) callback(null, state); + }) + .catch(function (err) { + self.log('Failed setting activity ' + self.name + ' power state to ' + state + ' with error ' + err); + if (callback) callback(err); + }); +}; module.exports.platform = LogitechHarmonyPlatform; From 6e5c35ec8820e47cf1f890189c8506543e7b06fc Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Sun, 18 Oct 2015 14:23:00 -0700 Subject: [PATCH 42/72] Restore telldus-live See #277 for discussion. --- package.json | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f8bf8e4..6bea480 100644 --- a/package.json +++ b/package.json @@ -16,30 +16,31 @@ "carwingsjs": "0.0.x", "chokidar": "^1.0.5", "color": "0.10.x", + "debug": "^2.2.0", "eibd": "^0.3.1", "elkington": "kevinohara80/elkington", "hap-nodejs": "^0.0.2", "harmonyhubjs-client": "^1.1.4", "harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git", - "lifx-api": "^1.0.1", + "komponist": "0.1.0", "lifx": "git+https://github.com/magicmonkey/lifxjs.git", + "lifx-api": "^1.0.1", "mdns": "^2.2.4", "node-hue-api": "^1.0.5", "node-icontrol": "^0.1.5", "node-milight-promise": "0.0.x", "node-persist": "0.0.x", "q": "1.4.x", - "tough-cookie": "^2.0.0", "request": "2.49.x", "sonos": "0.8.x", + "telldus-live": "^0.2.1", "teslams": "1.0.1", + "tough-cookie": "^2.0.0", "unofficial-nest-api": "git+https://github.com/hachidorii/unofficial_nodejs_nest.git#d8d48edc952b049ff6320ef99afa7b2f04cdee98", "wemo": "0.2.x", "wink-js": "0.0.5", "xml2js": "0.4.x", "xmldoc": "0.1.x", - "komponist" : "0.1.0", - "yamaha-nodejs": "0.4.x", - "debug": "^2.2.0" + "yamaha-nodejs": "0.4.x" } } From 6604c2b69aff3b4156102e0aaa20487413aa4a0f Mon Sep 17 00:00:00 2001 From: Kraig McConaghy Date: Sun, 18 Oct 2015 19:08:46 -0500 Subject: [PATCH 43/72] 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 44/72] 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 45/72] 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 46/72] 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 47/72] 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 48/72] 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 49/72] 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 50/72] 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 51/72] 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 52/72] 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 53/72] 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 54/72] Support Door, Window, and ContactSensor for sensorBinary.Door/Window --- platforms/ZWayServer.js | 79 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index e50336e..c367fad 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -356,7 +356,16 @@ ZWayServerAccessory.prototype = { } break; case "sensorBinary.Door/Window": - services.push(new Service.GarageDoorOpener(vdev.metrics.title, vdev.id)); + var stype = this.platform.getTagValue(vdev, "Service.Type"); + if(stype === "ContactSensor"){ + services.push(new Service.ContactSensor(vdev.metrics.title, vdev.id)); + } else if(stype === "GarageDoorOpener"){ + services.push(new Service.GarageDoorOpener(vdev.metrics.title, vdev.id)); + } else if(stype === "Window"){ + services.push(new Service.GarageDoorOpener(vdev.metrics.title, vdev.id)); + } else { + services.push(new Service.Door(vdev.metrics.title, vdev.id)); + } break; case "sensorMultilevel.Temperature": services.push(new Service.TemperatureSensor(vdev.metrics.title, vdev.id)); @@ -371,6 +380,8 @@ ZWayServerAccessory.prototype = { var stype = this.platform.getTagValue(vdev, "Service.Type"); if(stype === "MotionSensor"){ services.push(new Service.MotionSensor(vdev.metrics.title, vdev.id)); + } else { + services.push(new Service.ContactSensor(vdev.metrics.title, vdev.id)); } } @@ -412,6 +423,10 @@ ZWayServerAccessory.prototype = { map[(new Characteristic.TargetHeatingCoolingState).UUID] = ["thermostat"]; //TODO: Always a fixed result map[(new Characteristic.CurrentDoorState).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; map[(new Characteristic.TargetDoorState).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; //TODO: Always a fixed result + map[(new Characteristic.ContactSensorState).UUID] = ["sensorBinary"]; + map[(new Characteristic.CurrentPosition).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; + map[(new Characteristic.TargetPosition).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; + map[(new Characteristic.PositionState).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; map[(new Characteristic.ObstructionDetected).UUID] = ["sensorBinary.Door/Window","sensorBinary"]; //TODO: Always a fixed result map[(new Characteristic.BatteryLevel).UUID] = ["battery.Battery"]; map[(new Characteristic.StatusLowBattery).UUID] = ["battery.Battery"]; @@ -815,6 +830,68 @@ ZWayServerAccessory.prototype = { return cx; } + if(cx instanceof Characteristic.ContactSensorState){ + cx.zway_getValueFromVDev = function(vdev){ + var boolval = vdev.metrics.level === "off" ? false : true; + boolval = accessory.platform.getTagValue(vdev, "ContactSensorState.Invert") ? !boolval : boolval; + return boolval ? Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : Characteristic.ContactSensorState.CONTACT_DETECTED; + }; + cx.value = cx.zway_getValueFromVDev(vdev); + cx.on('get', function(callback, context){ + debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); + this.getVDev(vdev).then(function(result){ + debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); + callback(false, cx.zway_getValueFromVDev(result.data)); + }); + }.bind(this)); + cx.on('change', function(ev){ + debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); + }); + return cx; + } + + if(cx instanceof Characteristic.CurrentPosition){ + cx.zway_getValueFromVDev = function(vdev){ + return vdev.metrics.level === "off" ? 0 : 100 ; + }; + cx.value = cx.zway_getValueFromVDev(vdev); + cx.on('get', function(callback, context){ + debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); + this.getVDev(vdev).then(function(result){ + debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); + callback(false, cx.zway_getValueFromVDev(result.data)); + }); + }.bind(this)); + cx.on('change', function(ev){ + debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); + }); + return cx; + } + + if(cx instanceof Characteristic.TargetPosition){ + //TODO: Currently only Door sensors, so always return 0. + cx.zway_getValueFromVDev = function(vdev){ + return 0; + }; + cx.value = cx.zway_getValueFromVDev(vdev); + cx.on('get', function(callback, context){ + debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); + callback(false, cx.zway_getValueFromVDev(vdev)); + }); + } + + if(cx instanceof Characteristic.PositionState){ + //TODO: Currently only Door sensors, so always return STOPPED. + cx.zway_getValueFromVDev = function(vdev){ + return Characteristic.PositionState.STOPPED; + }; + cx.value = cx.zway_getValueFromVDev(vdev); + cx.on('get', function(callback, context){ + debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); + callback(false, cx.zway_getValueFromVDev(vdev)); + }); + } + } , configureService: function(service, vdev){ From 997906bab8aa65e0dc092ed9ec4cc1cdbf18850c Mon Sep 17 00:00:00 2001 From: Mario Drengner Date: Wed, 21 Oct 2015 14:35:35 +0200 Subject: [PATCH 55/72] 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 56/72] 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 57/72] 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 58/72] 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 59/72] 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 60/72] 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 61/72] 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 62/72] 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 ecdffbef23114d62516360a61f61359edabe8bc3 Mon Sep 17 00:00:00 2001 From: Mark Van Holstyn Date: Sat, 24 Oct 2015 23:21:42 -0400 Subject: [PATCH 63/72] 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 64/72] 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 65/72] 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 66/72] 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 67/72] 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 68/72] 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 69/72] 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 70/72] 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 71/72] 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 72/72] 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();