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/81] 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/81] 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 656a8057acbd177995f5a3dd9b63262082387d2b Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Sat, 19 Sep 2015 13:00:01 +0200 Subject: [PATCH 03/81] Initial work on reading, seems to work okay. --- platforms/ZWayServer.js | 71 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index 5b79c8e..7c8b925 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -11,6 +11,7 @@ function ZWayServerPlatform(log, config){ this.url = config["url"]; this.login = config["login"]; this.password = config["password"]; + this.opt_in = config["opt_in"]; this.name_overrides = config["name_overrides"]; this.batteryLow = config["battery_low_level"] || 15; this.pollInterval = config["poll_interval"] || 2; @@ -110,6 +111,7 @@ ZWayServerPlatform.prototype = { for(var i = 0; i < devices.length; i++){ var vdev = devices[i]; if(vdev.tags.indexOf("Homebridge:Skip") >= 0) { debug("Tag says skip!"); continue; } + if(this.opt_in && vdev.tags.indexOf("Homebridge:Include") < 0) continue; var gdid = vdev.id.replace(/^(.*?)_zway_(\d+-\d+)-\d.*/, '$1_$2'); var gd = groupedDevices[gdid] || (groupedDevices[gdid] = {devices: [], types: {}, primary: undefined}); gd.devices.push(vdev); @@ -222,6 +224,27 @@ ZWayServerAccessory.prototype = { }); }, + rgb2hsv: function(obj) { + var r = obj.r/255, g = obj.g/255, b = obj.b/255; + var max, min, d, h, s, v; + + if (min === max) { + // shade of gray + return [0, 0, r]; + } + + min = Math.min(r, Math.min(g, b)); + max = Math.max(r, Math.max(g, b)); + + var d = (r === min) ? g - b : ((b === min) ? r - g : b - r); + h = (r === min) ? 3 : ((b === min) ? 1 : 5); + h = 60 * (h - d/(max - min)); + s = (max - min) / max; + v = max; + return {"h": h, "s": s * 100, "v": v}; + } + , + getVDevServices: function(vdev){ var typeKey = ZWayServerPlatform.getVDevTypeKey(vdev); var services = [], service; @@ -272,6 +295,8 @@ ZWayServerAccessory.prototype = { this.uuidToTypeKeyMap = map = {}; map[(new Characteristic.On).UUID] = ["switchBinary","switchMultilevel"]; map[(new Characteristic.Brightness).UUID] = ["switchMultilevel"]; + map[(new Characteristic.Hue).UUID] = ["switchRGBW"]; + map[(new Characteristic.Saturation).UUID] = ["switchRGBW"]; map[(new Characteristic.CurrentTemperature).UUID] = ["sensorMultilevel.Temperature","thermostat"]; map[(new Characteristic.TargetTemperature).UUID] = ["thermostat"]; map[(new Characteristic.TemperatureDisplayUnits).UUID] = ["sensorMultilevel.Temperature","thermostat"]; //TODO: Always a fixed result @@ -310,7 +335,7 @@ ZWayServerAccessory.prototype = { } , configureCharacteristic: function(cx, vdev){ - var that = this; + var that, accessory = this; // Add this combination to the maps... if(!this.platform.cxVDevMap[vdev.id]) this.platform.cxVDevMap[vdev.id] = []; @@ -381,6 +406,50 @@ ZWayServerAccessory.prototype = { return cx; } + if(cx instanceof Characteristic.Hue){ + cx.zway_getValueFromVDev = function(vdev){ + return accessory.rgb2hsv(vdev.metrics.color).h; + }; + cx.value = cx.zway_getValueFromVDev(vdev); + cx.on('get', function(callback, context){ + debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); + this.getVDev(vdev).then(function(result){ + debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); + callback(false, cx.zway_getValueFromVDev(result.data)); + }); + }.bind(this)); + + cx.writeable = false; + //cx.on('set', function(level, callback){ + // this.command(vdev, "exact", {level: "on", "color.r": 255, "color.g": 0, "color.b": 0}).then(function(result){ + // callback(); + // }); + //}.bind(this)); + return cx; + } + + if(cx instanceof Characteristic.Saturation){ + cx.zway_getValueFromVDev = function(vdev){ + return accessory.rgb2hsv(vdev.metrics.color).s; + }; + cx.value = cx.zway_getValueFromVDev(vdev); + cx.on('get', function(callback, context){ + debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); + this.getVDev(vdev).then(function(result){ + debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); + callback(false, cx.zway_getValueFromVDev(result.data)); + }); + }.bind(this)); + + cx.writeable = false; + //cx.on('set', function(level, callback){ + // this.command(vdev, "exact", {level: "on", "color.r": 255, "color.g": 0, "color.b": 0}).then(function(result){ + // callback(); + // }); + //}.bind(this)); + return cx; + } + if(cx instanceof Characteristic.CurrentTemperature){ cx.zway_getValueFromVDev = function(vdev){ return vdev.metrics.level; From 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 04/81] 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 05/81] 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 06/81] 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 07/81] 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 08/81] 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 09/81] 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 7aa758cb042bac0de651107a4e64eb28a82313cb Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Tue, 29 Sep 2015 06:53:22 +0200 Subject: [PATCH 10/81] Working towards getting the extra dimmers in one accessory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Having trouble with service subtypes…hmm… --- platforms/ZWayServer.js | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index 7c8b925..4915f89 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -113,10 +113,16 @@ ZWayServerPlatform.prototype = { if(vdev.tags.indexOf("Homebridge:Skip") >= 0) { debug("Tag says skip!"); continue; } if(this.opt_in && vdev.tags.indexOf("Homebridge:Include") < 0) continue; var gdid = vdev.id.replace(/^(.*?)_zway_(\d+-\d+)-\d.*/, '$1_$2'); - var gd = groupedDevices[gdid] || (groupedDevices[gdid] = {devices: [], types: {}, primary: undefined}); + var gd = groupedDevices[gdid] || (groupedDevices[gdid] = {devices: [], types: {}, extras: {}, primary: undefined}); gd.devices.push(vdev); - gd.types[ZWayServerPlatform.getVDevTypeKey(vdev)] = gd.devices.length - 1; - gd.types[vdev.deviceType] = gd.devices.length - 1; // also include the deviceType only as a possibility + var tk = ZWayServerPlatform.getVDevTypeKey(vdev); + if(gd.types[tk] === undefined){ + gd.types[tk] = gd.devices.length - 1; + } else { + gd.extras[tk] = gd.extras[tk] || []; + gd.extras[tk].push(gd.devices.length - 1); + } + if(tk !== vdev.deviceType) gd.types[vdev.deviceType] = gd.devices.length - 1; // also include the deviceType only as a possibility } //TODO: Make a second pass, re-splitting any devices that don't make sense together for(var gdid in groupedDevices) { @@ -250,25 +256,25 @@ ZWayServerAccessory.prototype = { var services = [], service; switch (typeKey) { case "switchBinary": - services.push(new Service.Switch(vdev.metrics.title)); + services.push(new Service.Switch(vdev.metrics.title, vdev.id)); break; case "switchMultilevel": - services.push(new Service.Lightbulb(vdev.metrics.title)); + services.push(new Service.Lightbulb(vdev.metrics.title, vdev.id)); break; case "thermostat": - services.push(new Service.Thermostat(vdev.metrics.title)); + services.push(new Service.Thermostat(vdev.metrics.title, vdev.id)); break; case "sensorMultilevel.Temperature": - services.push(new Service.TemperatureSensor(vdev.metrics.title)); + services.push(new Service.TemperatureSensor(vdev.metrics.title, vdev.id)); break; case "sensorBinary.Door/Window": - services.push(new Service.GarageDoorOpener(vdev.metrics.title)); + services.push(new Service.GarageDoorOpener(vdev.metrics.title, vdev.id)); break; case "battery.Battery": - services.push(new Service.BatteryService(vdev.metrics.title)); + services.push(new Service.BatteryService(vdev.metrics.title, vdev.id)); break; case "sensorMultilevel.Luminiscence": - services.push(new Service.LightSensor(vdev.metrics.title)); + services.push(new Service.LightSensor(vdev.metrics.title, vdev.id)); break; } @@ -335,7 +341,7 @@ ZWayServerAccessory.prototype = { } , configureCharacteristic: function(cx, vdev){ - var that, accessory = this; + var accessory = this; // Add this combination to the maps... if(!this.platform.cxVDevMap[vdev.id]) this.platform.cxVDevMap[vdev.id] = []; @@ -349,7 +355,7 @@ ZWayServerAccessory.prototype = { cx.value = cx.zway_getValueFromVDev(vdev); cx.on('get', function(callback, context){ debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); - callback(false, that.name); + callback(false, accessory.name); }); cx.writable = false; return cx; @@ -597,7 +603,7 @@ ZWayServerAccessory.prototype = { if(cx instanceof Characteristic.StatusLowBattery){ cx.zway_getValueFromVDev = function(vdev){ - return vdev.metrics.level <= that.platform.batteryLow ? Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; + return vdev.metrics.level <= accessory.platform.batteryLow ? Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; }; cx.value = cx.zway_getValueFromVDev(vdev); cx.on('get', function(callback, context){ @@ -686,6 +692,12 @@ ZWayServerAccessory.prototype = { var services = [informationService]; services = services.concat(this.getVDevServices(this.devDesc.devices[this.devDesc.primary])); + + // Any extra switchMultilevels? Could be a RGBW+W bulb, add them as additional services... + if(this.devDesc.extras["switchMultilevel"]) for(var i = 0; i < this.devDesc.extras["switchMultilevel"].length; i++){ + var xvdev = this.devDesc.devices[this.devDesc.extras["switchMultilevel"][i]]; + services = services.concat(this.getVDevServices(xvdev)); + } if(this.platform.splitServices){ if(this.devDesc.types["battery.Battery"]){ From 0b3930e458054fbbd93144dc7c8420b4120a39bd Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Tue, 29 Sep 2015 06:59:40 +0200 Subject: [PATCH 11/81] Erm...oops. --- platforms/ZWayServer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index 33c5ac7..fcab891 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -93,8 +93,8 @@ ZWayServerPlatform.prototype = { "thermostat", "switchMultilevel", "switchBinary", - "sensorBinary.Door/Window" - "sensorMultilevel.Temperature", + "sensorBinary.Door/Window", + "sensorMultilevel.Temperature" ]; var that = this; From 99da61d30a1295b17e24bd8f1f8566d01eb29226 Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Tue, 29 Sep 2015 07:06:05 +0200 Subject: [PATCH 12/81] Lost some subtype parameters in deconfliction --- platforms/ZWayServer.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index fcab891..c86d4ac 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -256,16 +256,16 @@ ZWayServerAccessory.prototype = { var services = [], service; switch (typeKey) { case "thermostat": - services.push(new Service.Thermostat(vdev.metrics.title)); + services.push(new Service.Thermostat(vdev.metrics.title, vdev.id)); break; case "switchBinary": - services.push(new Service.Switch(vdev.metrics.title)); + services.push(new Service.Switch(vdev.metrics.title, vdev.id)); break; case "switchMultilevel": - services.push(new Service.Lightbulb(vdev.metrics.title)); + services.push(new Service.Lightbulb(vdev.metrics.title, vdev.id)); break; case "sensorBinary.Door/Window": - services.push(new Service.GarageDoorOpener(vdev.metrics.title)); + services.push(new Service.GarageDoorOpener(vdev.metrics.title, vdev.id)); break; case "sensorMultilevel.Temperature": services.push(new Service.TemperatureSensor(vdev.metrics.title, vdev.id)); From 2d88dafa11b1ebda2c2523e334c273ed89b5a802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Tue, 29 Sep 2015 22:45:34 +0200 Subject: [PATCH 13/81] 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 14/81] 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 15/81] Fix crash when trying to get/set power state - Call getPowerState on the object instead of passing the function to fix undefined isActivity error - Remove callback param from setPowerState since it is not called with a callback param. - startActivity expects the activity id as a parameter. We can't actually toggle activities off and on. There is a "PowerOff" activity that will turn everything off. --- platforms/LogitechHarmony.js | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/platforms/LogitechHarmony.js b/platforms/LogitechHarmony.js index a5c5e22..845f7f8 100644 --- a/platforms/LogitechHarmony.js +++ b/platforms/LogitechHarmony.js @@ -163,9 +163,9 @@ LogitechHarmonyAccessory.prototype = { var self = this; if (this.isActivity) { - hub.getCurrentActivity().then(function (currentActivity) { - callback(currentActivity.id === self.id); - }).except(function (err) { + this.hub.getCurrentActivity().then(function (currentActivity) { + callback(currentActivity === self.id); + }).catch(function (err) { self.log('Unable to get current activity with error', err); callback(false); }); @@ -175,28 +175,23 @@ LogitechHarmonyAccessory.prototype = { } }, - setPowerState: function (state, callback) { + setPowerState: function (state) { var self = this; if (this.isActivity) { this.log('Set activity ' + this.name + ' power state to ' + state); - // Activity id -1 is turn off all devices - var id = state ? this.id : -1; - - this.hub.startActivity(id) + this.hub.startActivity(self.id) .then(function () { self.log('Finished setting activity ' + self.name + ' power state to ' + state); - callback(); }) .catch(function (err) { self.log('Failed setting activity ' + self.name + ' power state to ' + state + ' with error ' + err); - callback(err); }); } else { // TODO: Support setting device power this.log('TODO: Support setting device power'); - callback(); + // callback(); } }, @@ -284,7 +279,9 @@ LogitechHarmonyAccessory.prototype = { onUpdate: function (value) { self.setPowerState(value) }, - onRead: self.getPowerState, + onRead: function(callback) { + self.getPowerState(callback) + }, perms: ["pw","pr","ev"], format: "bool", initialValue: 0, @@ -300,6 +297,5 @@ LogitechHarmonyAccessory.prototype = { }; -module.exports.accessory = LogitechHarmonyAccessory; module.exports.platform = LogitechHarmonyPlatform; From d04d41734477256875717ab4d7a0c2a4457e5ca7 Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Fri, 2 Oct 2015 06:19:59 +0200 Subject: [PATCH 16/81] Initial read/write support for RGB bulbs complete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Needs testing, but seems to work with my Aeon bulb…taking into account the wonkiness of that bulb with Z-Way, at least. --- platforms/ZWayServer.js | 108 +++++++++++++++++++++++++++++++++++----- 1 file changed, 95 insertions(+), 13 deletions(-) diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index c86d4ac..730eb9b 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -180,6 +180,11 @@ ZWayServerPlatform.prototype = { if(this.cxVDevMap[upd.id]){ var vdev = this.vDevStore[upd.id]; vdev.metrics.level = upd.metrics.level; + if(upd.metrics.color){ + vdev.metrics.r = upd.metrics.r; + vdev.metrics.g = upd.metrics.g; + vdev.metrics.b = upd.metrics.b; + } vdev.updateTime = upd.updateTime; var cxs = this.cxVDevMap[upd.id]; for(var j = 0; j < cxs.length; j++){ @@ -231,26 +236,62 @@ ZWayServerAccessory.prototype = { }, rgb2hsv: function(obj) { + // RGB: 0-255; H: 0-360, S,V: 0-100 var r = obj.r/255, g = obj.g/255, b = obj.b/255; var max, min, d, h, s, v; - if (min === max) { - // shade of gray - return [0, 0, r]; - } - min = Math.min(r, Math.min(g, b)); max = Math.max(r, Math.max(g, b)); + if (min === max) { + // shade of gray + return {h: 0, s: 0, v: r * 100}; + } + var d = (r === min) ? g - b : ((b === min) ? r - g : b - r); h = (r === min) ? 3 : ((b === min) ? 1 : 5); h = 60 * (h - d/(max - min)); s = (max - min) / max; v = max; - return {"h": h, "s": s * 100, "v": v}; + return {"h": h, "s": s * 100, "v": v * 100}; + } + , + hsv2rgb: function(obj) { + // H: 0-360; S,V: 0-100; RGB: 0-255 + var r, g, b; + var sfrac = obj.s / 100; + var vfrac = obj.v / 100; + + if(sfrac === 0){ + var vbyte = Math.round(vfrac*255); + return { r: vbyte, g: vbyte, b: vbyte }; + } + + var hdb60 = (obj.h % 360) / 60; + var sector = Math.floor(hdb60); + var fpart = hdb60 - sector; + var c = vfrac * (1 - sfrac); + var x1 = vfrac * (1 - sfrac * fpart); + var x2 = vfrac * (1 - sfrac * (1 - fpart)); + switch(sector){ + case 0: + r = vfrac; g = x2; b = c; break; + case 1: + r = x1; g = vfrac; b = c; break; + case 2: + r = c; g = vfrac; b = x2; break; + case 3: + r = c; g = x1; b = vfrac; break; + case 4: + r = x2; g = c; b = vfrac; break; + case 5: + default: + r = vfrac; g = c; b = x1; break; + } + + return { "r": Math.round(255 * r), "g": Math.round(255 * g), "b": Math.round(255 * b) }; } , - getVDevServices: function(vdev){ var typeKey = ZWayServerPlatform.getVDevTypeKey(vdev); var services = [], service; @@ -340,7 +381,7 @@ ZWayServerAccessory.prototype = { return null; } , - configureCharacteristic: function(cx, vdev){ + configureCharacteristic: function(cx, vdev, service){ var accessory = this; // Add this combination to the maps... @@ -414,6 +455,7 @@ ZWayServerAccessory.prototype = { if(cx instanceof Characteristic.Hue){ cx.zway_getValueFromVDev = function(vdev){ + debug("Derived value " + accessory.rgb2hsv(vdev.metrics.color).h + " for hue."); return accessory.rgb2hsv(vdev.metrics.color).h; }; cx.value = cx.zway_getValueFromVDev(vdev); @@ -424,6 +466,18 @@ ZWayServerAccessory.prototype = { callback(false, cx.zway_getValueFromVDev(result.data)); }); }.bind(this)); + cx.on('set', function(hue, callback){ + var scx = service.getCharacteristic(Characteristic.Saturation); + var vcx = service.getCharacteristic(Characteristic.Brightness); + if(!scx || !vcx){ + debug("Hue without Saturation and Brightness is not supported! Cannot set value!") + callback(true, cx.value); + } + var rgb = this.hsv2rgb({ h: hue, s: scx.value, v: vcx.value }); + this.command(vdev, "exact", { red: rgb.r, green: rgb.g, blue: rgb.b }).then(function(result){ + callback(); + }); + }.bind(this)); cx.writeable = false; //cx.on('set', function(level, callback){ @@ -436,6 +490,7 @@ ZWayServerAccessory.prototype = { if(cx instanceof Characteristic.Saturation){ cx.zway_getValueFromVDev = function(vdev){ + debug("Derived value " + accessory.rgb2hsv(vdev.metrics.color).s + " for saturation."); return accessory.rgb2hsv(vdev.metrics.color).s; }; cx.value = cx.zway_getValueFromVDev(vdev); @@ -446,6 +501,18 @@ ZWayServerAccessory.prototype = { callback(false, cx.zway_getValueFromVDev(result.data)); }); }.bind(this)); + cx.on('set', function(saturation, callback){ + var hcx = service.getCharacteristic(Characteristic.Hue); + var vcx = service.getCharacteristic(Characteristic.Brightness); + if(!hcx || !vcx){ + debug("Saturation without Hue and Brightness is not supported! Cannot set value!") + callback(true, cx.value); + } + var rgb = this.hsv2rgb({ h: hcx.value, s: saturation, v: vcx.value }); + this.command(vdev, "exact", { red: rgb.r, green: rgb.g, blue: rgb.b }).then(function(result){ + callback(); + }); + }.bind(this)); cx.writeable = false; //cx.on('set', function(level, callback){ @@ -666,14 +733,29 @@ ZWayServerAccessory.prototype = { success = false; debug("ERROR! Failed to configure required characteristic \"" + service.characteristics[i].displayName + "\"!"); } - cx = this.configureCharacteristic(cx, vdev); + cx = this.configureCharacteristic(cx, vdev, service); } for(var i = 0; i < service.optionalCharacteristics.length; i++){ var cx = service.optionalCharacteristics[i]; - var vdev = this.getVDevForCharacteristic(cx); + var vdev = this.getVDevForCharacteristic(cx, vdev); if(!vdev) continue; - cx = this.configureCharacteristic(cx, vdev); - if(cx) service.addCharacteristic(cx); + + //NOTE: Questionable logic, but if the vdev has already been used for the same + // characteristic type elsewhere, lets not duplicate it just for the sake of an + // optional characteristic. This eliminates the problem with RGB+W+W bulbs + // having the HSV controls shown again, but might have unintended consequences... + var othercx, othercxs = this.platform.cxVDevMap[vdev.id]; + if(othercxs) for(var j = 0; j < othercxs.length; j++) if(othercxs[j].UUID === cx.UUID) othercx = othercxs[j]; + if(othercx) + continue; + + cx = this.configureCharacteristic(cx, vdev, service); + try { + if(cx) service.addCharacteristic(cx); + } + catch (ex) { + debug('Adding Characteristic "' + cx.displayName + '" failed with message "' + ex.message + '". This may be expected.'); + } } return success; } @@ -736,7 +818,7 @@ ZWayServerAccessory.prototype = { extraCxs = []; // to wipe out any already setup cxs. break; } - this.configureCharacteristic(cx, vdev2); + this.configureCharacteristic(cx, vdev2, service); extraCxs.push(cx); } for(var j = 0; j < extraCxs.length; j++) From 2652f33a0a2de8566df0d346ec19c2bc1ceaff9d Mon Sep 17 00:00:00 2001 From: Mike Enriquez Date: Sat, 3 Oct 2015 22:04:51 -0400 Subject: [PATCH 17/81] 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 18/81] 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 af79ea4fbf5b172cdf0f076307da91e0b37fdd55 Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Wed, 7 Oct 2015 05:38:24 +0200 Subject: [PATCH 19/81] Beginning tag support enhancements Generalized tag recognition, tags are now `Homebridge.*` instead of `Homebridge:*`, initial attempt at `IsPrimary` but probably not working right yet. --- platforms/ZWayServer.js | 49 +++++++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index 730eb9b..f21f39b 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -83,7 +83,20 @@ ZWayServerPlatform.prototype = { return deferred.promise; } , - + getTagValue: function(vdev, tagStem){ + if(!(vdev.tags && vdev.tags.length > 0)) return false; + var tagStem = "Homebridge." + tagStem; + if(vdev.tags.indexOf(tagStem) >= 0) return true; + var tags = vdev.tags, l = tags.length, tag; + for(var i = 0; i < l; i++){ + tag = tags[i]; + if(tag.indexOf(tagStem + ":") === 0){ + return tag.substr(tagStem.length + 1); + } + } + return false; + } + , accessories: function(callback) { debug("Fetching Z-Way devices..."); @@ -110,12 +123,24 @@ ZWayServerPlatform.prototype = { var groupedDevices = {}; for(var i = 0; i < devices.length; i++){ var vdev = devices[i]; - if(vdev.tags.indexOf("Homebridge:Skip") >= 0) { debug("Tag says skip!"); continue; } - if(this.opt_in && vdev.tags.indexOf("Homebridge:Include") < 0) continue; + if(this.getTagValue("Skip")) { debug("Tag says skip!"); continue; } + if(this.opt_in && !this.getTagValue(vdev, "Include")) continue; var gdid = vdev.id.replace(/^(.*?)_zway_(\d+-\d+)-\d.*/, '$1_$2'); var gd = groupedDevices[gdid] || (groupedDevices[gdid] = {devices: [], types: {}, extras: {}, primary: undefined}); gd.devices.push(vdev); var tk = ZWayServerPlatform.getVDevTypeKey(vdev); + + // If this is explicitly set as primary, set it now... + if(this.getTagValue("IsPrimary")){ + gd.primary = gd.devices.length - 1; + if(gd.types[tk] !== undefined){ + // everybody out of the way! + gd.extras[tk] = gd.extras[tk] || []; + gd.extras[tk].push(gd.types[tk]); + } + gd.types[tk] = gd.primary; + } + if(gd.types[tk] === undefined){ gd.types[tk] = gd.devices.length - 1; } else { @@ -136,12 +161,17 @@ ZWayServerPlatform.prototype = { } var accessory = null; - for(var ti = 0; ti < primaryDeviceClasses.length; ti++){ + if(gd.primary !== undefined){ + var pd = gd.devices[gd.primary]; + var name = pd.metrics && pd.metrics.title ? pd.metrics.title : pd.id; + accessory = new ZWayServerAccessory(name, gd, that); + } + else for(var ti = 0; ti < primaryDeviceClasses.length; ti++){ if(gd.types[primaryDeviceClasses[ti]] !== undefined){ gd.primary = gd.types[primaryDeviceClasses[ti]]; var pd = gd.devices[gd.primary]; var name = pd.metrics && pd.metrics.title ? pd.metrics.title : pd.id; - debug("Using primary device with type " + primaryDeviceClasses[ti] + ", " + name + " (" + pd.id + ") as primary."); + //debug("Using primary device with type " + primaryDeviceClasses[ti] + ", " + name + " (" + pd.id + ") as primary."); accessory = new ZWayServerAccessory(name, gd, that); break; } @@ -303,7 +333,11 @@ ZWayServerAccessory.prototype = { services.push(new Service.Switch(vdev.metrics.title, vdev.id)); break; case "switchMultilevel": - services.push(new Service.Lightbulb(vdev.metrics.title, vdev.id)); + if(this.platform.getTagValue(vdev, "ServiceType") === "Switch"){ + services.push(new Service.Switch(vdev.metrics.title, vdev.id)); + } else { + services.push(new Service.Lightbulb(vdev.metrics.title, vdev.id)); + } break; case "sensorBinary.Door/Window": services.push(new Service.GarageDoorOpener(vdev.metrics.title, vdev.id)); @@ -778,7 +812,8 @@ ZWayServerAccessory.prototype = { // Any extra switchMultilevels? Could be a RGBW+W bulb, add them as additional services... if(this.devDesc.extras["switchMultilevel"]) for(var i = 0; i < this.devDesc.extras["switchMultilevel"].length; i++){ var xvdev = this.devDesc.devices[this.devDesc.extras["switchMultilevel"][i]]; - services = services.concat(this.getVDevServices(xvdev)); + var xservice = this.getVDevServices(xvdev); + services = services.concat(xservice); } if(this.platform.splitServices){ From a15c026f2f5503ca80ed47881c5bf102d5bf28b0 Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Wed, 7 Oct 2015 06:59:04 +0200 Subject: [PATCH 20/81] Manual address specification working now --- platforms/YamahaAVR.js | 54 +++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/platforms/YamahaAVR.js b/platforms/YamahaAVR.js index 1659c0f..f08fa96 100644 --- a/platforms/YamahaAVR.js +++ b/platforms/YamahaAVR.js @@ -23,6 +23,7 @@ function YamahaAVRPlatform(log, config){ this.setMainInputTo = config["setMainInputTo"]; this.expectedDevices = config["expected_devices"] || 100; this.discoveryTimeout = config["discovery_timeout"] || 30; + this.manualAddresses = config["manual_addresses"] || {}; this.browser = mdns.createBrowser(mdns.tcp('http'), {resolverSequence: sequence}); } @@ -75,25 +76,44 @@ YamahaAVRPlatform.prototype = { var accessories = []; var timer, timeElapsed = 0, checkCyclePeriod = 5000; - browser.on('serviceUp', function(service){ + // Hmm... seems we need to prevent double-listing via manual and Bonjour... + var sysIds = {}; + + var setupFromService = function(service){ var name = service.name; //console.log('Found HTTP service "' + name + '"'); // We can't tell just from mdns if this is an AVR... if (service.port != 80) return; // yamaha-nodejs assumes this, so finding one on another port wouldn't do any good anyway. var yamaha = new Yamaha(service.host); - yamaha.getSystemConfig().then(function(sysConfig){ - var sysModel = sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0]; - var sysId = sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0]; - that.log("Found Yamaha " + sysModel + " - " + sysId + ", \"" + name + "\""); - var accessory = new YamahaAVRAccessory(that.log, that.config, service, yamaha, sysConfig); - accessories.push(accessory); - if(accessories.length >= this.expectedDevices) - timeoutFunction(); // We're done, call the timeout function now. - //callback([accessory]); - }, function(err){ - return; + yamaha.getSystemConfig().then( + function(sysConfig){ + var sysModel = sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0]; + var sysId = sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0]; + if(sysIds[sysId]){ + this.log("WARN: Got multiple systems with ID " + sysId + "! Omitting duplicate!"); + return; + } + sysIds[sysId] = true; + this.log("Found Yamaha " + sysModel + " - " + sysId + ", \"" + name + "\""); + var accessory = new YamahaAVRAccessory(this.log, this.config, name, yamaha, sysConfig); + accessories.push(accessory); + if(accessories.length >= this.expectedDevices) + timeoutFunction(); // We're done, call the timeout function now. + }.bind(this) + ); + }.bind(this); + + // process manually specified devices... + for(var key in this.manualAddresses){ + if(!this.manualAddresses.hasOwnProperty(key)) continue; + setupFromService({ + name: key, + host: this.manualAddresses[key], + port: 80 }); - }); + } + + browser.on('serviceUp', setupFromService); browser.start(); // The callback can only be called once...so we'll have to find as many as we can @@ -119,15 +139,15 @@ YamahaAVRPlatform.prototype = { } }; -function YamahaAVRAccessory(log, config, mdnsService, yamaha, sysConfig) { +function YamahaAVRAccessory(log, config, name, yamaha, sysConfig) { this.log = log; this.config = config; - this.mdnsService = mdnsService; this.yamaha = yamaha; this.sysConfig = sysConfig; - this.name = mdnsService.name; - this.serviceName = mdnsService.name + " Speakers"; + this.nameSuffix = config["name_suffix"] || " Speakers"; + this.name = name; + this.serviceName = name + this.nameSuffix; this.setMainInputTo = config["setMainInputTo"]; this.playVolume = this.config["play_volume"]; this.minVolume = config["min_volume"] || -50.0; From 772f35efac270edde65cb6a4fe8af2cc02573bb2 Mon Sep 17 00:00:00 2001 From: stipus Date: Thu, 8 Oct 2015 09:07:33 +0200 Subject: [PATCH 21/81] Create HomeSeer.js --- platforms/HomeSeer.js | 370 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 370 insertions(+) create mode 100644 platforms/HomeSeer.js diff --git a/platforms/HomeSeer.js b/platforms/HomeSeer.js new file mode 100644 index 0000000..4618efe --- /dev/null +++ b/platforms/HomeSeer.js @@ -0,0 +1,370 @@ +'use strict'; + +// +// HomeSeer Platform Shim for HomeBridge +// V0.1 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/07 - Initial version +// +// +// Remember to add platform to config.json. +// +// You can get HomeSeer Device References by clicking a HomeSeer device name, then +// choosing the Advanced Tab. +// +// Example: +// "platforms": [ +// { +// "platform": "HomeSeer", // required +// "name": "HomeSeer", // required +// "url": "http://192.168.3.4:81", // required +// "accessories":[ +// { +// "ref":8, // required - HomeSeer Device Reference (To get it, select the HS Device - then Advanced Tab) +// "type":"Lightbulb", // Optional - Lightbulb is the default +// "name":"My Light", // Optional - HomeSeer device name is the default +// "offValue":"0", // Optional - 0 is the default +// "onValue":"100", // Optional - 100 is the default +// "can_dim":true // Optional - true is the default - false for a non dimmable lightbulb +// }, +// { +// "ref":9 // This is a dimmable Lightbulb by default +// }, +// { +// "ref":58, // This is an controllable outlet +// "type":"Outlet" +// } +// ] +// } +// ], +// +// +// SUPORTED TYPES: +// - Lightbulb (can_dim, onValue, offValue options) +// - Fan (onValue, offValue options) +// - Switch (onValue, offValue options) +// - Outlet (onValue, offValue options) +// - TemperatureSensor +// - ContactSensor +// - MotionSensor +// - LeakSensor +// - LightSensor +// - OccupancySensor +// - SmokeSensor +// - Door + + +var Service = require("HAP-NodeJS").Service; +var Characteristic = require("HAP-NodeJS").Characteristic; +var request = require("request"); + + +function httpRequest(url, method, callback) { + request({ + url: url, + method: method + }, + function (error, response, body) { + callback(error, response, body) + }) +} + + + +function HomeSeerPlatform(log, config){ + this.log = log; + this.config = config; +} + +HomeSeerPlatform.prototype = { + accessories: function(callback) { + this.log("Fetching HomeSeer devices."); + + var refList = ""; + for( var i=0; i Date: Thu, 8 Oct 2015 18:55:30 +0200 Subject: [PATCH 22/81] added EnOcean blinds --- platforms/FHEM.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index 5033dfa..d591fc2 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -727,7 +727,10 @@ FHEMAccessory(log, connection, s) { else if( genericType == 'blind' || s.Attributes.subType == 'blindActuator' ) { delete this.mappings.pct; - this.mappings.blind = { reading: 'pct', cmd: 'pct' }; + if( s.PossibleSets.match(/[\^ ]position\b/) ) + this.mappings.blind = { reading: 'position', cmd: 'position' }; + else + this.mappings.blind = { reading: 'pct', cmd: 'pct' }; } else if( genericType == 'window' || s.Attributes.model == 'HM-SEC-WIN' ) @@ -917,6 +920,8 @@ FHEMAccessory(log, connection, s) { this.serial = s.Internals.uniqueid; else if( this.type == 'SONOSPLAYER' ) this.serial = s.Internals.UDN; + else if( this.type == 'EnOcean' ) + this.serial = this.type + '.' + s.Internals.DEF; this.uuid_base = this.serial; @@ -967,6 +972,9 @@ FHEMAccessory.prototype = { } else if( reading == 'pct' ) { value = parseInt( value ); + } else if( reading == 'position' ) { + value = parseInt( value ); + } else if(reading == 'motor') { if( value.match(/^up/)) value = Characteristic.PositionState.INCREASING; From d8a21133e9c74bd423c521df9aa7a8b4340206a4 Mon Sep 17 00:00:00 2001 From: Ethan Gill Date: Thu, 8 Oct 2015 13:50:34 -0400 Subject: [PATCH 23/81] Simple fix for undefined Nest device names --- platforms/Nest.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/platforms/Nest.js b/platforms/Nest.js index 414fcef..c1ef3bd 100644 --- a/platforms/Nest.js +++ b/platforms/Nest.js @@ -44,7 +44,11 @@ NestPlatform.prototype = { function NestThermostatAccessory(log, name, device, deviceId) { // device info - this.name = name; + if (name) { + this.name = name; + } else { + this.name = "Nest"; + } this.model = device.model_version; this.serial = device.serial_number; this.deviceId = deviceId; @@ -390,4 +394,4 @@ NestThermostatAccessory.prototype = { } module.exports.accessory = NestThermostatAccessory; -module.exports.platform = NestPlatform; \ No newline at end of file +module.exports.platform = NestPlatform; From c0dfc9a8cdc95badc21c1956feadf1204786c2ad Mon Sep 17 00:00:00 2001 From: Theodor Tonum Date: Sat, 10 Oct 2015 01:14:10 +0200 Subject: [PATCH 24/81] Add support for local Telldus control --- config-sample.json | 4 + package.json | 1 + platforms/Telldus.js | 265 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 270 insertions(+) create mode 100644 platforms/Telldus.js diff --git a/config-sample.json b/config-sample.json index 7af74db..b837f61 100644 --- a/config-sample.json +++ b/config-sample.json @@ -23,6 +23,10 @@ "token" : "telldus token", "token_secret" : "telldus token secret" }, + { + "platform" : "Telldus", + "name" : "Telldus" + }, { "platform": "Wink", "name": "Wink", diff --git a/package.json b/package.json index 3a3ca74..8f88a35 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "tough-cookie": "^2.0.0", "request": "2.49.x", "sonos": "0.8.x", + "telldus": "0.0.9", "telldus-live": "0.2.x", "teslams": "1.0.1", "unofficial-nest-api": "git+https://github.com/hachidorii/unofficial_nodejs_nest.git#d8d48edc952b049ff6320ef99afa7b2f04cdee98", diff --git a/platforms/Telldus.js b/platforms/Telldus.js new file mode 100644 index 0000000..87d37f1 --- /dev/null +++ b/platforms/Telldus.js @@ -0,0 +1,265 @@ +var types = require("HAP-NodeJS/accessories/types.js"); +var telldus = require('telldus'); + +function TelldusPlatform(log, config) { + var that = this; + that.log = log; +} + +TelldusPlatform.prototype = { + + accessories: function(callback) { + var that = this; + + that.log("Fetching devices..."); + + var devices = telldus.getDevicesSync(); + + that.log("Found " + devices.length + " devices..."); + + var foundAccessories = []; + + // Clean non device + for (var i = 0; i < devices.length; i++) { + if (devices[i].type != 'DEVICE') { + devices.splice(i, 1); + } + } + + for (var i = 0; i < devices.length; i++) { + if (devices[i].type === 'DEVICE') { + TelldusAccessory.create(that.log, devices[i], function(err, accessory) { + if (!!err) that.log("Couldn't load device info"); + foundAccessories.push(accessory); + if (foundAccessories.length >= devices.length) { + callback(foundAccessories); + } + }); + } + } + } +}; + +var TelldusAccessory = function TelldusAccessory(log, device) { + + this.log = log; + + var m = device.model.split(':'); + + this.dimTimeout = false; + + // Set accessory info + this.device = device; + this.id = device.id; + this.name = device.name; + this.manufacturer = "Telldus"; // NOTE: Change this later + this.model = device.model; + this.status = device.status; + switch (device.status.name) { + case 'OFF': + this.state = 0; + this.stateValue = 0; + break; + case 'ON': + this.state = 2; + this.stateValue = 1; + break; + case 'DIM': + this.state = 16; + this.stateValue = device.status.level; + break; + } +}; + +TelldusAccessory.create = function (log, device, callback) { + + callback(null, new TelldusAccessory(log, device)); + +}; + +TelldusAccessory.prototype = { + + dimmerValue: function() { + + if (this.state === 1) { + return 100; + } + + if (this.state === 16 && this.stateValue != "unde") { + return parseInt(this.stateValue * 100 / 255); + } + + return 0; + }, + + informationCharacteristics: function() { + var that = this; + + informationCharacteristics = [ + { + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: that.name, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of the accessory", + designedMaxLength: 255 + },{ + cType: types.MANUFACTURER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: that.manufacturer, + supportEvents: false, + supportBonjour: false, + manfDescription: "Manufacturer", + designedMaxLength: 255 + },{ + cType: types.MODEL_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: that.model, + supportEvents: false, + supportBonjour: false, + manfDescription: "Model", + designedMaxLength: 255 + },{ + cType: types.SERIAL_NUMBER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: "A1S2NASF88EW", + supportEvents: false, + supportBonjour: false, + manfDescription: "SN", + designedMaxLength: 255 + },{ + cType: types.IDENTIFY_CTYPE, + onUpdate: function () { + telldus.turnOff(that.id, function(err){ + if (!!err) that.log("Error: " + err.message); + telldus.turnOn(that.id, function(err){ + if (!!err) that.log("Error: " + err.message); + telldus.turnOff(that.id, function(err){ + if (!!err) that.log("Error: " + err.message); + telldus.turnOn(that.id, function(err){ + if (!!err) that.log("Error: " + err.message); + }); + }); + }); + }); + }, + perms: ["pw"], + format: "bool", + initialValue: false, + supportEvents: false, + supportBonjour: false, + manfDescription: "Identify Accessory", + designedMaxLength: 1 + } + ]; + return informationCharacteristics; + }, + + controlCharacteristics: function() { + var that = this; + + cTypes = [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: that.name, + supportEvents: true, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + }] + + cTypes.push({ + cType: types.POWER_STATE_CTYPE, + onUpdate: function(value) { + if (value) { + telldus.turnOn(that.id, function(err){ + if (!!err) { + that.log("Error: " + err.message) + } else { + that.log(that.name + " - Updated power state: ON"); + } + }); + } else { + telldus.turnOff(that.id, function(err){ + if (!!err) { + that.log("Error: " + err.message) + } else { + that.log(that.name + " - Updated power state: OFF"); + } + }); + } + }, + perms: ["pw","pr","ev"], + format: "bool", + initialValue: (that.state != 2 && (that.state === 16 && that.stateValue != 0)) ? 1 : 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Change the power state", + designedMaxLength: 1 + }) + + if (that.model === "selflearning-dimmer") { + cTypes.push({ + cType: types.BRIGHTNESS_CTYPE, + onUpdate: function (value) { + if (that.dimTimeout) { + clearTimeout(that.dimTimeout); + } + + that.dimTimeout = setTimeout(function(){ + telldus.dim(that.id, (255 * (value / 100)), function(err, result){ + if (!!err) { + that.log("Error: " + err.message); + } else { + that.log(that.name + " - Updated brightness: " + value); + } + }); + that.dimTimeout = false; + }, 250); + }, + perms: ["pw", "pr", "ev"], + format: "int", + initialValue: that.dimmerValue(), + supportEvents: true, + supportBonjour: false, + manfDescription: "Adjust Brightness of Light", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }) + } + + return cTypes + }, + + getServices: function() { + + var services = [ + { + sType: types.ACCESSORY_INFORMATION_STYPE, + characteristics: this.informationCharacteristics() + }, + { + sType: types.LIGHTBULB_STYPE, + characteristics: this.controlCharacteristics() + } + ]; + + return services; + } +}; + +module.exports.platform = TelldusPlatform; +module.exports.accessory = TelldusAccessory; From 4fbd7eb775a1137b8b88dfd674d4c6769c8ad54e Mon Sep 17 00:00:00 2001 From: stipus Date: Sat, 10 Oct 2015 12:38:46 +0200 Subject: [PATCH 25/81] Fix for HomeSeer occupancy sensor --- platforms/HomeSeer.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platforms/HomeSeer.js b/platforms/HomeSeer.js index 4618efe..1ecbd43 100644 --- a/platforms/HomeSeer.js +++ b/platforms/HomeSeer.js @@ -3,6 +3,7 @@ // // HomeSeer Platform Shim for HomeBridge // V0.1 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/07 - Initial version +// V0.2 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/10 - occupancy sensor fix // // // Remember to add platform to config.json. @@ -320,7 +321,7 @@ HomeSeerAccessory.prototype = { } case "OccupancySensor": { var occupancySensorService = new Service.OccupancySensor(); - motionSensorService + occupancySensorService .getCharacteristic(Characteristic.OccupancyDetected) .on('get', this.getPowerState.bind(this)); services.push( occupancySensorService ); From b4f4f58519b0f428218d094fc9591caf727a78cc Mon Sep 17 00:00:00 2001 From: iRaven Date: Sat, 10 Oct 2015 14:05:16 +0200 Subject: [PATCH 26/81] Added Get-State Function --- accessories/HomeMatic.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/accessories/HomeMatic.js b/accessories/HomeMatic.js index ce575b1..89e43b0 100644 --- a/accessories/HomeMatic.js +++ b/accessories/HomeMatic.js @@ -30,7 +30,31 @@ HomeMatic.prototype = { } }); }, + getPowerState: function(callback) { + var that = this; + + this.log("Getting Power State of CCU"); + request.get({ + url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuID, + }, function(err, response, body) { + if (!err && response.statusCode == 200) { + + //that.log("Response:"+response.body); + var responseString = response.body.substring(83,87); + //that.log(responseString); + switch(responseString){ + case "true": {modvalue = "1";break;} + case "fals": {modvalue = "0";break;} + } + callback(parseInt(modvalue)); + that.log("Getting Power State complete."); + } + else { + that.log("Error '"+err+"' getting Power State: " + body); + } + }); + }, getServices: function() { var that = this; return [{ @@ -101,6 +125,7 @@ HomeMatic.prototype = { },{ cType: types.POWER_STATE_CTYPE, onUpdate: function(value) { that.setPowerState(value); }, + onRead: function(callback) { that.getPowerState(callback); }, perms: ["pw","pr","ev"], format: "bool", initialValue: false, From 4a831422eb3c565bb33c965992196f982f88a1fa Mon Sep 17 00:00:00 2001 From: "stevetrease@gmail.com" Date: Sat, 10 Oct 2015 14:41:00 +0100 Subject: [PATCH 27/81] Added two new accessories for a readonly thermometer and hygrometer (humidity meter) based on HttpAccessory. --- accessories/HttpHygrometer.js | 71 ++++++++++++++++++++++++++++++ accessories/HttpThermometer.js | 79 ++++++++++++++++++++++++++++++++++ config-sample.json | 15 ++++++- 3 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 accessories/HttpHygrometer.js create mode 100644 accessories/HttpThermometer.js diff --git a/accessories/HttpHygrometer.js b/accessories/HttpHygrometer.js new file mode 100644 index 0000000..61ad3b9 --- /dev/null +++ b/accessories/HttpHygrometer.js @@ -0,0 +1,71 @@ +var Service = require("HAP-NodeJS").Service; +var Characteristic = require("HAP-NodeJS").Characteristic; +var request = require("request"); + +module.exports = { + accessory: HygrometerAccessory +} + +function HygrometerAccessory(log, config) { + this.log = log; + + // url info + this.url = config["url"]; + this.http_method = config["http_method"]; +} + + +HygrometerAccessory.prototype = { + + httpRequest: function(url, method, callback) { + request({ + url: url, + method: method + }, + function (error, response, body) { + callback(error, response, body) + }) + }, + + + identify: function(callback) { + this.log("Identify requested!"); + callback(); // success + }, + + getCurrentRelativeHumidity: function (callback) { + var that = this; + that.log ("getting CurrentCurrentRelativeHumidity"); + + this.httpRequest(this.url, this.http_method, function(error, response, body) { + if (error) { + this.log('HTTP function failed: %s', error); + callback(error); + } + else { + this.log('HTTP function succeeded - %s', body); + callback(null, Number(body)); + } + }.bind(this)); + }, + + getServices: function() { + + // you can OPTIONALLY create an information service if you wish to override + // the default values for things like serial number, model, etc. + var informationService = new Service.AccessoryInformation(); + + informationService + .setCharacteristic(Characteristic.Manufacturer, "HTTP Manufacturer") + .setCharacteristic(Characteristic.Model, "HTTP Hygrometer") + .setCharacteristic(Characteristic.SerialNumber, "HTTP Serial Number"); + + var humidityService = new Service.HumiditySensor(); + + humidityService + .getCharacteristic(Characteristic.CurrentRelativeHumidity) + .on('get', this.getCurrentRelativeHumidity.bind(this)); + + return [informationService, humidityService]; + } +}; diff --git a/accessories/HttpThermometer.js b/accessories/HttpThermometer.js new file mode 100644 index 0000000..ac9bdc2 --- /dev/null +++ b/accessories/HttpThermometer.js @@ -0,0 +1,79 @@ +var Service = require("HAP-NodeJS").Service; +var Characteristic = require("HAP-NodeJS").Characteristic; +var request = require("request"); + +module.exports = { + accessory: ThermometerAccessory +} + +function ThermometerAccessory(log, config) { + this.log = log; + + // url info + this.url = config["url"]; + this.http_method = config["http_method"]; +} + + +ThermometerAccessory.prototype = { + + httpRequest: function(url, method, callback) { + request({ + url: url, + method: method + }, + function (error, response, body) { + callback(error, response, body) + }) + }, + + + identify: function(callback) { + this.log("Identify requested!"); + callback(); // success + }, + + getCurrentTemperature: function (callback) { + var that = this; + that.log ("getting CurrentTemperature"); + + + this.httpRequest(this.url, this.http_method, function(error, response, body) { + if (error) { + this.log('HTTP function failed: %s', error); + callback(error); + } + else { + this.log('HTTP function succeeded - %s', body); + callback(null, Number(body)); + } + }.bind(this)); + }, + + getTemperatureUnits: function (callback) { + var that = this; + that.log ("getTemperature Units"); + // 1 = F and 0 = C + callback (null, 0); + }, + + getServices: function() { + + // you can OPTIONALLY create an information service if you wish to override + // the default values for things like serial number, model, etc. + var informationService = new Service.AccessoryInformation(); + + informationService + .setCharacteristic(Characteristic.Manufacturer, "HTTP Manufacturer") + .setCharacteristic(Characteristic.Model, "HTTP Thermometer") + .setCharacteristic(Characteristic.SerialNumber, "HTTP Serial Number"); + + var temperatureService = new Service.TemperatureSensor(); + + temperatureService + .getCharacteristic(Characteristic.CurrentTemperature) + .on('get', this.getCurrentTemperature.bind(this)); + + return [informationService, temperatureService]; + } +}; diff --git a/config-sample.json b/config-sample.json index 7af74db..85fd6e3 100644 --- a/config-sample.json +++ b/config-sample.json @@ -164,7 +164,20 @@ "off_url": "https://192.168.1.22:3030/devices/23222/off", "brightness_url": "https://192.168.1.22:3030/devices/23222/brightness/%b", "http_method": "POST" - },{ + }, + { + "accessory": "HttpHygrometer", + "name": "Kitchen", + "url": "http://host/URL", + "http_method": "GET" + }, + { + "accessory": "HttpThermometer", + "name": "Garage", + "url": "http://home/URL", + "http_method": "GET" + }, + { "accessory": "ELKM1", "name": "Security System", "description": "Allows basic control of Elk M1 security system. You can use 1 of 3 arm modes: Away, Stay, Night. If you need to access all 3, create 3 accessories with different names.", From 25299a7863455ed83b653e3a95419e609f48314a Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Sat, 10 Oct 2015 15:45:27 +0200 Subject: [PATCH 28/81] =?UTF-8?q?Last=20bits=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- platforms/ZWayServer.js | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index f21f39b..0b81e88 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -125,20 +125,26 @@ ZWayServerPlatform.prototype = { var vdev = devices[i]; if(this.getTagValue("Skip")) { debug("Tag says skip!"); continue; } if(this.opt_in && !this.getTagValue(vdev, "Include")) continue; - var gdid = vdev.id.replace(/^(.*?)_zway_(\d+-\d+)-\d.*/, '$1_$2'); + + var gdid = this.getTagValue(vdev, "Accessory.Id"); + if(!gdid){ + gdid = vdev.id.replace(/^(.*?)_zway_(\d+-\d+)-\d.*/, '$1_$2'); + } + var gd = groupedDevices[gdid] || (groupedDevices[gdid] = {devices: [], types: {}, extras: {}, primary: undefined}); gd.devices.push(vdev); var tk = ZWayServerPlatform.getVDevTypeKey(vdev); // If this is explicitly set as primary, set it now... - if(this.getTagValue("IsPrimary")){ - gd.primary = gd.devices.length - 1; + if(this.getTagValue(vdev, "IsPrimary")){ + // everybody out of the way! Can't be in "extras" if you're the primary... if(gd.types[tk] !== undefined){ - // everybody out of the way! gd.extras[tk] = gd.extras[tk] || []; gd.extras[tk].push(gd.types[tk]); + delete gd.types[tk]; // clear the way for this one to be set here below... } - gd.types[tk] = gd.primary; + gd.primary = gd.devices.length - 1; + //gd.types[tk] = gd.primary; } if(gd.types[tk] === undefined){ @@ -149,7 +155,7 @@ ZWayServerPlatform.prototype = { } if(tk !== vdev.deviceType) gd.types[vdev.deviceType] = gd.devices.length - 1; // also include the deviceType only as a possibility } - //TODO: Make a second pass, re-splitting any devices that don't make sense together + for(var gdid in groupedDevices) { if(!groupedDevices.hasOwnProperty(gdid)) continue; @@ -183,7 +189,6 @@ ZWayServerPlatform.prototype = { foundAccessories.push(accessory); } -//foundAccessories = foundAccessories.slice(0, 10); // Limit to a few devices for testing... callback(foundAccessories); // Start the polling process... @@ -332,8 +337,9 @@ ZWayServerAccessory.prototype = { case "switchBinary": services.push(new Service.Switch(vdev.metrics.title, vdev.id)); break; + case "switchRGBW": case "switchMultilevel": - if(this.platform.getTagValue(vdev, "ServiceType") === "Switch"){ + if(this.platform.getTagValue(vdev, "Service.Type") === "Switch"){ services.push(new Service.Switch(vdev.metrics.title, vdev.id)); } else { services.push(new Service.Lightbulb(vdev.metrics.title, vdev.id)); @@ -436,6 +442,12 @@ ZWayServerAccessory.prototype = { return cx; } + // We don't want to override "Name"'s name...so we just move this below that block. + var descOverride = this.platform.getTagValue(vdev, "Characteristic.Description"); + if(descOverride){ + cx.displayName = descOverride; + } + if(cx instanceof Characteristic.On){ cx.zway_getValueFromVDev = function(vdev){ var val = false; @@ -797,17 +809,23 @@ ZWayServerAccessory.prototype = { getServices: function() { var that = this; + var vdevPrimary = this.devDesc.devices[this.devDesc.primary]; + var accId = this.platform.getTagValue(vdevPrimary, "Accessory.Id"); + if(!accId){ + accId = "VDev-" + vdevPrimary.h; //FIXME: Is this valid? + } + var informationService = new Service.AccessoryInformation(); informationService .setCharacteristic(Characteristic.Name, this.name) .setCharacteristic(Characteristic.Manufacturer, "Z-Wave.me") .setCharacteristic(Characteristic.Model, "Virtual Device (VDev version 1)") - .setCharacteristic(Characteristic.SerialNumber, "VDev-" + this.devDesc.devices[this.devDesc.primary].h) //FIXME: Is this valid?); + .setCharacteristic(Characteristic.SerialNumber, accId); var services = [informationService]; - services = services.concat(this.getVDevServices(this.devDesc.devices[this.devDesc.primary])); + services = services.concat(this.getVDevServices(vdevPrimary)); // Any extra switchMultilevels? Could be a RGBW+W bulb, add them as additional services... if(this.devDesc.extras["switchMultilevel"]) for(var i = 0; i < this.devDesc.extras["switchMultilevel"].length; i++){ From 05e811cdd66c25c12d60dad03236613750834cce Mon Sep 17 00:00:00 2001 From: iRaven Date: Sat, 10 Oct 2015 15:56:51 +0200 Subject: [PATCH 29/81] Added HM-Sec-RHS Support as contact --- accessories/HomeMaticWindow.js | 123 +++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 accessories/HomeMaticWindow.js diff --git a/accessories/HomeMaticWindow.js b/accessories/HomeMaticWindow.js new file mode 100644 index 0000000..b7e585d --- /dev/null +++ b/accessories/HomeMaticWindow.js @@ -0,0 +1,123 @@ +var types = require("HAP-NodeJS/accessories/types.js"); +var Characteristic = require("HAP-NodeJS").Characteristic; +var request = require("request"); + +function HomeMaticWindow(log, config) { + this.log = log; + this.name = config["name"]; + this.ccuID = config["ccu_id"]; + this.ccuIP = config["ccu_ip"]; +} + +HomeMaticWindow.prototype = { + + + getPowerState: function(callback) { + var that = this; + + this.log("Getting Window State of CCU"); + request.get({ + url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuID, + }, function(err, response, body) { + + if (!err && response.statusCode == 200) { + + //that.log("Response:"+response.body); + var responseString = response.body.substring(83,84); + //that.log(responseString); + switch(responseString){ + case "0": {callback(Characteristic.ContactSensorState.CONTACT_DETECTED);break;} + case "1": {callback(Characteristic.ContactSensorState.CONTACT_NOT_DETECTED);break;} + case "2": {callback(Characteristic.ContactSensorState.CONTACT_NOT_DETECTED);break;} + } + that.log("Getting Window State complete."); + } + else { + that.log("Error '"+err+"' getting Window State: " + body); + } + }); + }, + + getServices: function() { + var that = this; + return [{ + sType: types.ACCESSORY_INFORMATION_STYPE, + characteristics: [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of the accessory", + designedMaxLength: 255 + },{ + cType: types.MANUFACTURER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: "Homematic", + supportEvents: false, + supportBonjour: false, + manfDescription: "Manufacturer", + designedMaxLength: 255 + },{ + cType: types.MODEL_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: "HM-Sec-RHS", + supportEvents: false, + supportBonjour: false, + manfDescription: "Model", + designedMaxLength: 255 + },{ + cType: types.SERIAL_NUMBER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: "A1S2NASF88EW", + supportEvents: false, + supportBonjour: false, + manfDescription: "SN", + designedMaxLength: 255 + },{ + cType: types.IDENTIFY_CTYPE, + onUpdate: null, + perms: ["pw"], + format: "bool", + initialValue: false, + supportEvents: false, + supportBonjour: false, + manfDescription: "Identify Accessory", + designedMaxLength: 1 + }] + },{ + sType: types.CONTACT_SENSOR_STYPE, + characteristics: [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + },{ + cType: types.CONTACT_SENSOR_STATE_CTYPE, + onRead: function(callback) { that.getPowerState(callback); }, + perms: ["pr","ev"], + format: "bool", + initialValue: false, + supportEvents: false, + supportBonjour: false, + manfDescription: "Get Window state of a Variable", + designedMaxLength: 1 + }] + }]; + } +}; + +module.exports.accessory = HomeMaticWindow; From a274ae4edab182a22e08b1bab875a1a15fe37646 Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Sun, 11 Oct 2015 05:44:44 +0200 Subject: [PATCH 30/81] Switches to the *new* new Characteristics API format for the two custom Characteristics. Fixes #247 --- platforms/YamahaAVR.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/platforms/YamahaAVR.js b/platforms/YamahaAVR.js index f08fa96..0dde24f 100644 --- a/platforms/YamahaAVR.js +++ b/platforms/YamahaAVR.js @@ -31,24 +31,24 @@ function YamahaAVRPlatform(log, config){ YamahaAVRPlatform.AudioVolume = function() { Characteristic.call(this, 'Audio Volume', '00001001-0000-1000-8000-135D67EC4377'); - this.format = 'uint8'; - this.unit = 'percentage'; - this.maximumValue = 100; - this.minimumValue = 0; - this.stepValue = 1; - this.readable = true; - this.writable = true; - this.supportsEventNotification = true; + this.setProps({ + format: Characteristic.Formats.UINT8, + unit: Characteristic.Units.PERCENTAGE, + maxValue: 100, + minValue: 0, + minStep: 1, + perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] + }); this.value = this.getDefaultValue(); }; inherits(YamahaAVRPlatform.AudioVolume, Characteristic); YamahaAVRPlatform.Muting = function() { Characteristic.call(this, 'Muting', '00001002-0000-1000-8000-135D67EC4377'); - this.format = 'bool'; - this.readable = true; - this.writable = true; - this.supportsEventNotification = true; + this.setProps({ + format: Characteristic.Formats.UINT8, + perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY] + }); this.value = this.getDefaultValue(); }; inherits(YamahaAVRPlatform.Muting, Characteristic); From e4fa276de2b29eec9a8ad640653f43781f15051e Mon Sep 17 00:00:00 2001 From: rodtoll Date: Sun, 11 Oct 2015 08:14:48 -0700 Subject: [PATCH 31/81] 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 32/81] 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 33/81] Fixed elk panel state transitions. --- platforms/isy-js.js | 77 ++++++++++++--------------------------------- 1 file changed, 20 insertions(+), 57 deletions(-) diff --git a/platforms/isy-js.js b/platforms/isy-js.js index f28c0c0..5f01a11 100644 --- a/platforms/isy-js.js +++ b/platforms/isy-js.js @@ -573,51 +573,30 @@ ISYElkAlarmPanelAccessory.prototype.identify = function(callback) { } ISYElkAlarmPanelAccessory.prototype.setAlarmTargetState = function(targetStateHK,callback) { - this.log("Sending command to set alarm panel state to: "+targetStateHK); - var targetState = this.translateHKToAlarmTargetState(targetState); - if(this.alarmTargetState != targetState) { + this.log("***** Sending command to set alarm panel state to: "+targetStateHK); + var targetState = this.translateHKToAlarmTargetState(targetStateHK); + this.log("***** Would send the target state of: "+targetState); + if(this.device.getAlarmMode() != targetState) { this.device.sendSetAlarmModeCommand(targetState, function(result) { callback(); }); } else { + this.log("***** Redundant command, already in that state."); callback(); } } -////// Current State - -/* -ELKAlarmPanelDevice.prototype.ALARM_STATE_NOT_READY_TO_ARM = 0; -ELKAlarmPanelDevice.prototype.ALARM_STATE_READY_TO_ARM = 1; -ELKAlarmPanelDevice.prototype.ALARM_STATE_READY_TO_ARM_VIOLATION = 2; -ELKAlarmPanelDevice.prototype.ALARM_STATE_ARMED_WITH_TIMER = 3; -ELKAlarmPanelDevice.prototype.ALARM_STATE_ARMED_FULLY = 4; -ELKAlarmPanelDevice.prototype.ALARM_STATE_FORCE_ARMED_VIOLATION = 5; -ELKAlarmPanelDevice.prototype.ALARM_STATE_ARMED_WITH_BYPASS = 6; -*/ - -/* -ELKAlarmPanelDevice.prototype.ALARM_TRIP_STATE_DISARMED = 0; -ELKAlarmPanelDevice.prototype.ALARM_TRIP_STATE_EXIT_DELAY = 1; -ELKAlarmPanelDevice.prototype.ALARM_TRIP_STATE_TRIPPED = 2; -*/ - -/* -Characteristic.SecuritySystemCurrentState.STAY_ARM = 0; -Characteristic.SecuritySystemCurrentState.AWAY_ARM = 1; -Characteristic.SecuritySystemCurrentState.NIGHT_ARM = 2; -Characteristic.SecuritySystemCurrentState.DISARMED = 3; -Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED = 4; -*/ - ISYElkAlarmPanelAccessory.prototype.translateAlarmCurrentStateToHK = function() { var tripState = this.device.getAlarmTripState(); - if(tripState == this.device.ALARM_TRIP_STATE_DISARMED || tripState == this.device.ALARM_TRIP_STATE_EXIT_DELAY) { - return Characteristic.SecuritySystemCurrentState.DISARMED; - } else if(tripState ==this.device.ALARM_TRIP_STATE_TRIPPED) { - return Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED; + var sourceAlarmState = this.device.getAlarmState(); + + if(tripState >= this.device.ALARM_TRIP_STATE_TRIPPED) { + return Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED; + } else if(sourceAlarmState == this.device.ALARM_STATE_NOT_READY_TO_ARM || + sourceAlarmState == this.device.ALARM_STATE_READY_TO_ARM || + sourceAlarmState == this.device.ALARM_STATE_READY_TO_ARM_VIOLATION) { + return Characteristic.SecuritySystemCurrentState.DISARMED; } else { - var sourceAlarmState = this.device.getAlarmMode(); if(sourceAlarmState == this.device.ALARM_MODE_STAY || sourceAlarmState == this.device.ALARM_MODE_STAY_INSTANT ) { return Characteristic.SecuritySystemCurrentState.STAY_ARM; } else if(sourceAlarmState == this.device.ALARM_MODE_AWAY || sourceAlarmState == this.device.ALARM_MODE_VACATION) { @@ -625,31 +604,12 @@ ISYElkAlarmPanelAccessory.prototype.translateAlarmCurrentStateToHK = function() } else if(sourceAlarmState == this.device.ALARM_MODE_NIGHT || sourceAlarmState == this.device.ALARM_MODE_NIGHT_INSTANT) { return Characteristic.SecuritySystemCurrentState.NIGHT_ARM; } else { - return Characteristic.SecuritySystemCurrentState.DISARM; + this.log("***** Setting to disarmed because sourceAlarmState is "+sourceAlarmState); + return Characteristic.SecuritySystemCurrentState.DISARMED; } } } -////// Target Mode - -/* -ELKAlarmPanelDevice.prototype.ALARM_MODE_DISARMED = 0; -ELKAlarmPanelDevice.prototype.ALARM_MODE_AWAY = 1; -ELKAlarmPanelDevice.prototype.ALARM_MODE_STAY = 2; -ELKAlarmPanelDevice.prototype.ALARM_MODE_STAY_INSTANT = 3; -ELKAlarmPanelDevice.prototype.ALARM_MODE_NIGHT = 4; -ELKAlarmPanelDevice.prototype.ALARM_MODE_NIGHT_INSTANT = 5; -ELKAlarmPanelDevice.prototype.ALARM_MODE_VACATION = 6; -*/ - -/* -Characteristic.SecuritySystemTargetState.STAY_ARM = 0; -Characteristic.SecuritySystemTargetState.AWAY_ARM = 1; -Characteristic.SecuritySystemTargetState.NIGHT_ARM = 2; -Characteristic.SecuritySystemTargetState.DISARM = 3; -*/ - - ISYElkAlarmPanelAccessory.prototype.translateAlarmTargetStateToHK = function() { var sourceAlarmState = this.device.getAlarmMode(); if(sourceAlarmState == this.device.ALARM_MODE_STAY || sourceAlarmState == this.device.ALARM_MODE_STAY_INSTANT ) { @@ -669,9 +629,9 @@ ISYElkAlarmPanelAccessory.prototype.translateHKToAlarmTargetState = function(sta } else if(state == Characteristic.SecuritySystemTargetState.AWAY_ARM) { return this.device.ALARM_MODE_AWAY; } else if(state == Characteristic.SecuritySystemTargetState.NIGHT_ARM) { - return this.device.NIGHT_ARM; + return this.device.ALARM_MODE_NIGHT; } else { - return this.device.DISARM; + return this.device.ALARM_MODE_DISARMED; } } @@ -684,6 +644,8 @@ ISYElkAlarmPanelAccessory.prototype.getAlarmCurrentState = function(callback) { } ISYElkAlarmPanelAccessory.prototype.handleExternalChange = function() { + this.log("***** Source device. Currenty state locally -"+this.device.getAlarmStatusAsText()); + this.log("***** Got alarm change notification. Setting HK target state to: "+this.translateAlarmTargetStateToHK()+" Setting HK Current state to: "+this.translateAlarmCurrentStateToHK()); this.alarmPanelService .setCharacteristic(Characteristic.SecuritySystemTargetState, this.translateAlarmTargetStateToHK()); this.alarmPanelService @@ -724,3 +686,4 @@ module.exports.accessory = ISYLightAccessory; module.exports.accessory = ISYLockAccessory; module.exports.accessory = ISYOutletAccessory; module.exports.accessory = ISYDoorWindowSensorAccessory; +module.exports.accessory = ISYElkAlarmPanelAccessory; From c506c44d6091b05b550b6fc901f4a1c69a7b2605 Mon Sep 17 00:00:00 2001 From: stipus Date: Mon, 12 Oct 2015 02:02:37 +0200 Subject: [PATCH 34/81] Update HomeSeer.js --- platforms/HomeSeer.js | 248 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 230 insertions(+), 18 deletions(-) diff --git a/platforms/HomeSeer.js b/platforms/HomeSeer.js index 1ecbd43..8cb371a 100644 --- a/platforms/HomeSeer.js +++ b/platforms/HomeSeer.js @@ -2,8 +2,15 @@ // // HomeSeer Platform Shim for HomeBridge -// V0.1 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/07 - Initial version -// V0.2 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/10 - occupancy sensor fix +// V0.1 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/07 +// - Initial version +// V0.2 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/10 +// - Occupancy sensor fix +// V0.3 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/11 +// - Added TemperatureUnit=F|C option to temperature sensors +// - Added negative temperature support to temperature sensors +// V0.4 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/11 +// - Added thermostat support // // // Remember to add platform to config.json. @@ -14,24 +21,50 @@ // Example: // "platforms": [ // { -// "platform": "HomeSeer", // required -// "name": "HomeSeer", // required -// "url": "http://192.168.3.4:81", // required +// "platform": "HomeSeer", // Required +// "name": "HomeSeer", // Required +// "host": "http://192.168.3.4:81", // Required - If you did setup HomeSeer authentication, use "http://user:password@ip_address:port" // "accessories":[ // { -// "ref":8, // required - HomeSeer Device Reference (To get it, select the HS Device - then Advanced Tab) -// "type":"Lightbulb", // Optional - Lightbulb is the default -// "name":"My Light", // Optional - HomeSeer device name is the default -// "offValue":"0", // Optional - 0 is the default -// "onValue":"100", // Optional - 100 is the default -// "can_dim":true // Optional - true is the default - false for a non dimmable lightbulb +// "ref":8, // Required - HomeSeer Device Reference (To get it, select the HS Device - then Advanced Tab) +// "type":"Lightbulb", // Optional - Lightbulb is the default +// "name":"My Light", // Optional - HomeSeer device name is the default +// "offValue":"0", // Optional - 0 is the default +// "onValue":"100", // Optional - 100 is the default +// "can_dim":true // Optional - true is the default - false for a non dimmable lightbulb // }, // { -// "ref":9 // This is a dimmable Lightbulb by default +// "ref":9 // This is a dimmable Lightbulb by default // }, // { -// "ref":58, // This is an controllable outlet +// "ref":58, // This is an controllable outlet // "type":"Outlet" +// }, +// { +// "ref":111, +// "type":"TemperatureSensor", // Required for a temperature sensor +// "temperatureUnit":"F", // Optional - C is the default +// "name":"Bedroom temp" // Optional - HomeSeer device name is the default +// }, +// { +// "ref":113, // Required - HomeSeer Device Reference of the Current Temperature Device +// "type":"Thermostat", // Required for a Thermostat +// "name":"Température Salon", // Optional - HomeSeer device name is the default +// "temperatureUnit":"C", // Optional - F for Fahrenheit, C for Celsius, C is the default +// "setPointRef":167, // Required - HomeSeer device reference for your thermostat Set Point. +// "setPointReadOnly":true, // Optional - Set to false if your SetPoint is read/write. true is the default +// "stateRef":166, // Required - HomeSeer device reference for your thermostat current state +// "stateOffValues":[0,4,5], // Required - List of the HomeSeer device values for a HomeKit state=OFF +// "stateHeatValues":[1], // Required - List of the HomeSeer device values for a HomeKit state=HEAT +// "stateCoolValues":[2], // Required - List of the HomeSeer device values for a HomeKit state=COOL +// "stateAutoValues":[3], // Required - List of the HomeSeer device values for a HomeKit state=AUTO +// "controlRef":168, // Required - HomeSeer device reference for your thermostat mode control (It can be the same as stateRef for some thermostats) +// "controlOffValue":0, // Required - Value for OFF +// "controlHeatValue":1, // Required - Value for HEAT +// "controlCoolValue":2, // Required - Value for COOL +// "controlAutoValue":3, // Required - Value for AUTO +// "coolingThresholdRef":169, // Optional - Not-implemented-yet - HomeSeer device reference for your thermostat cooling threshold +// "heatingThresholdRef":170 // Optional - Not-implemented-yet - HomeSeer device reference for your thermostat heating threshold // } // ] // } @@ -43,7 +76,8 @@ // - Fan (onValue, offValue options) // - Switch (onValue, offValue options) // - Outlet (onValue, offValue options) -// - TemperatureSensor +// - TemperatureSensor (temperatureUnit=C|F) +// - Thermostat (temperatureUnit, setPoint, state, control options) // - ContactSensor // - MotionSensor // - LeakSensor @@ -89,6 +123,7 @@ HomeSeerPlatform.prototype = { var that = this; var foundAccessories = []; var url = this.config["host"] + "/JSON?request=getstatus&ref=" + refList; + httpRequest( url, "GET", function(error, response, body) { if (error) { this.log('HomeSeer status function failed: %s', error.message); @@ -115,8 +150,9 @@ function HomeSeerAccessory(log, platformConfig, status ) { this.onValue = "100"; this.offValue = "0"; - this.control_url = platformConfig["host"] + "/JSON?request=controldevicebyvalue&ref=" + this.ref + "&value="; - this.status_url = platformConfig["host"] + "/JSON?request=getstatus&ref=" + this.ref; + this.access_url = platformConfig["host"] + "/JSON?"; + this.control_url = this.access_url + "request=controldevicebyvalue&ref=" + this.ref + "&value="; + this.status_url = this.access_url + "request=getstatus&ref=" + this.ref; for( var i=0; i Date: Mon, 12 Oct 2015 02:11:24 +0200 Subject: [PATCH 35/81] Update HomeSeer.js --- platforms/HomeSeer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platforms/HomeSeer.js b/platforms/HomeSeer.js index 8cb371a..e691b68 100644 --- a/platforms/HomeSeer.js +++ b/platforms/HomeSeer.js @@ -505,7 +505,7 @@ HomeSeerAccessory.prototype = { } case "OccupancySensor": { var occupancySensorService = new Service.OccupancySensor(); - motionSensorService + occupancySensorService .getCharacteristic(Characteristic.OccupancyDetected) .on('get', this.getPowerState.bind(this)); services.push( occupancySensorService ); From c59b87d17d3fb032ce087ca7cb7119e46c4b5ce0 Mon Sep 17 00:00:00 2001 From: stipus Date: Mon, 12 Oct 2015 02:15:41 +0200 Subject: [PATCH 36/81] Update HomeSeer.js --- platforms/HomeSeer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platforms/HomeSeer.js b/platforms/HomeSeer.js index e691b68..e3c289b 100644 --- a/platforms/HomeSeer.js +++ b/platforms/HomeSeer.js @@ -9,7 +9,7 @@ // V0.3 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/11 // - Added TemperatureUnit=F|C option to temperature sensors // - Added negative temperature support to temperature sensors -// V0.4 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/11 +// V0.4 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/12 // - Added thermostat support // // From d4279c5ef5be6880c093dee570ab51e999ae35f7 Mon Sep 17 00:00:00 2001 From: iRaven Date: Mon, 12 Oct 2015 18:12:14 +0200 Subject: [PATCH 37/81] Added Support for Thermostat HM-CC-RT-DN --- accessories/HomeMaticThermo | 264 ++++++++++++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 accessories/HomeMaticThermo diff --git a/accessories/HomeMaticThermo b/accessories/HomeMaticThermo new file mode 100644 index 0000000..f3d300f --- /dev/null +++ b/accessories/HomeMaticThermo @@ -0,0 +1,264 @@ +var types = require("HAP-NodeJS/accessories/types.js"); +var request = require("request"); + +function HomeMaticThermo(log, config) { + this.log = log; + this.name = config["name"]; + this.ccuIDTargetTemp = config["ccu_id_TargetTemp"]; + this.ccuIDCurrentTemp = config["ccu_id_CurrentTemp"]; + this.ccuIDControlMode = config["ccu_id_ControlMode"]; + this.ccuIDManuMode = config["ccu_id_ManuMode"]; + this.ccuIDAutoMode = config["ccu_id_AutoMode"]; + this.ccuIP = config["ccu_ip"]; +} + +HomeMaticThermo.prototype = { + + setTargetTemperature: function(value) { + + var that = this; + + this.log("Setting target Temperature of CCU to " + value); + this.log(this.ccuIDTargetTemp + " " + value); + + request.put({ + url: "http://"+this.ccuIP+"/config/xmlapi/statechange.cgi?ise_id="+this.ccuIDTargetTemp+"&new_value="+ value, + }, function(err, response, body) { + + if (!err && response.statusCode == 200) { + that.log("State change complete."); + } + else { + that.log("Error '"+err+"' setting Temperature: " + body); + } + }); + }, + getCurrentTemperature: function(callback) { + + var that = this; + + this.log("Getting current Temperature of CCU"); + request.get({ + url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuIDCurrentTemp, + }, function(err, response, body) { + + if (!err && response.statusCode == 200) { + + //that.log("Response:"+response.body); + var responseString = response.body.substring(83,87); + //that.log(responseString); + callback(parseFloat(responseString)); + //that.log("Getting current temperature complete."); + } + else { + that.log("Error '"+err+"' getting Temperature: " + body); + } + }); + }, + getTargetTemperature: function(callback) { + + var that = this; + +this.log("Getting target Temperature of CCU"); + request.get({ + url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuIDTargetTemp, + }, function(err, response, body) { + + if (!err && response.statusCode == 200) { + + //that.log("Response:"+response.body); + var responseString = response.body.substring(83,87); + //that.log(responseString); + callback(parseFloat(responseString)); + //that.log("Getting target temperature complete."); + } + else { + that.log("Error '"+err+"' getting Temperature: " + body); + } + }); + }, + getMode: function(callback) { + + var that = this; + + //this.log("Getting target Mode of CCU"); + //this.log(this.ccuID+ value); + + request.get({ + url: "http://"+this.ccuIP+"/config/xmlapi/state.cgi?datapoint_id="+this.ccuIDControlMode, + }, function(err, response, body) { + + if (!err && response.statusCode == 200) { + + //that.log("Response:"+response.body); + var responseInt = response.body.substring(83,84); + //that.log(responseString); + if (responseInt == 1) + { callback(parseInt("0")); } + if (responseInt == 0) + { callback(parseInt("1")); } + //that.log("Getting mode complete."); + } + else { + that.log("Error '"+err+"' getting Mode: " + body); + } + }); + }, + setMode: function(value) { + + var that = this; + + //this.log("Seting target Mode of CCU:" + value); + var modvalue; + var dpID; + switch(value) { + case 3: {modvalue = "true";dpID=this.ccuIDAutoMode;break;} //auto + case 1: {modvalue = "true";dpID=this.ccuIDAutoMode;break;} //heating => auto + default: {modvalue = "1";dpID=this.ccuIDManuMode;} //default => off (manual) + } + + request.put({ + url: "http://"+this.ccuIP+"/config/xmlapi/statechange.cgi?ise_id="+dpID+"&new_value="+ modvalue, + }, function(err, response, body) { + + if (!err && response.statusCode == 200) { + //that.log("Setting Mode complete."); + } + else { + that.log("Error '"+err+"' setting Mode: " + body); + } + }); + }, + getServices: function() { + var that = this; + return [{ + sType: types.ACCESSORY_INFORMATION_STYPE, + characteristics: [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of the accessory", + designedMaxLength: 255 + },{ + cType: types.MANUFACTURER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: "test", + supportEvents: false, + supportBonjour: false, + manfDescription: "Manufacturer", + designedMaxLength: 255 + },{ + cType: types.MODEL_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: "test", + supportEvents: false, + supportBonjour: false, + manfDescription: "Model", + designedMaxLength: 255 + },{ + cType: types.SERIAL_NUMBER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: "A1S2NREF88EW", + supportEvents: false, + supportBonjour: false, + manfDescription: "SN", + designedMaxLength: 255 + },{ + cType: types.IDENTIFY_CTYPE, + onUpdate: null, + perms: ["pw"], + format: "bool", + initialValue: false, + supportEvents: false, + supportBonjour: false, + manfDescription: "Identify Accessory", + designedMaxLength: 1 + }] + },{ + sType: types.THERMOSTAT_STYPE, + characteristics: [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.name, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + },{ + cType: types.CURRENTHEATINGCOOLING_CTYPE, + onRead: function(callback) { that.getMode(callback); }, + perms: ["pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Mode", + designedMaxLength: 1, + designedMinValue: 0, + designedMaxValue: 2, + designedMinStep: 1, + },{ + cType: types.TARGETHEATINGCOOLING_CTYPE, + onRead: function(callback) { that.getMode(callback); }, + onUpdate: function(value) { that.setMode(value);}, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Target Mode", + designedMinValue: 0, + designedMaxValue: 3, + designedMinStep: 1, + },{ + cType: types.CURRENT_TEMPERATURE_CTYPE, + onRead: function(callback) { that.getCurrentTemperature(callback); }, + onUpdate: null, + perms: ["pr","ev"], + format: "float", + initialValue: 13.0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Temperature", + unit: "celsius" + },{ + cType: types.TARGET_TEMPERATURE_CTYPE, + onUpdate: function(value) { that.setTargetTemperature(value); }, + onRead: function(callback) { that.getTargetTemperature(callback); }, + perms: ["pw","pr","ev"], + format: "float", + initialValue: 19.0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Target Temperature", + designedMinValue: 4, + designedMaxValue: 25, + designedMinStep: 0.1, + unit: "celsius" + },{ + cType: types.TEMPERATURE_UNITS_CTYPE, + onUpdate: null, + perms: ["pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Unit" + }] + }]; + } +}; + +module.exports.accessory = HomeMaticThermo; From 1f8db79324ad2935d143a43b9d4f23a786c89a9a Mon Sep 17 00:00:00 2001 From: stipus Date: Tue, 13 Oct 2015 00:53:19 +0200 Subject: [PATCH 38/81] Added support for humidity sensors, battery level sensors, low battery flag for all sensors, and ability to run HomeSeer events - Added Humidity sensor support - Added Battery support - Added low battery support for all sensors - Added HomeSeer event support (using HomeKit switches...) --- platforms/HomeSeer.js | 214 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 193 insertions(+), 21 deletions(-) diff --git a/platforms/HomeSeer.js b/platforms/HomeSeer.js index e3c289b..e0c5c79 100644 --- a/platforms/HomeSeer.js +++ b/platforms/HomeSeer.js @@ -11,6 +11,12 @@ // - Added negative temperature support to temperature sensors // V0.4 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/12 // - Added thermostat support +// V0.5 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/12 +// - Added Humidity sensor support +// V0.6 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/12 +// - Added Battery support +// - Added low battery support for all sensors +// - Added HomeSeer event support (using HomeKit switches...) // // // Remember to add platform to config.json. @@ -24,7 +30,16 @@ // "platform": "HomeSeer", // Required // "name": "HomeSeer", // Required // "host": "http://192.168.3.4:81", // Required - If you did setup HomeSeer authentication, use "http://user:password@ip_address:port" -// "accessories":[ +// +// "events":[ // Optional - List of Events - Currently they are imported into HomeKit as switches +// { +// "eventGroup":"My Group", // Required - The HomeSeer event group +// "eventName":"My Event", // Required - The HomeSeer event name +// "name":"Test" // Optional - HomeSeer event name is the default +// } +// ], +// +// "accessories":[ // Required - List of Accessories // { // "ref":8, // Required - HomeSeer Device Reference (To get it, select the HS Device - then Advanced Tab) // "type":"Lightbulb", // Optional - Lightbulb is the default @@ -37,14 +52,16 @@ // "ref":9 // This is a dimmable Lightbulb by default // }, // { -// "ref":58, // This is an controllable outlet +// "ref":58, // This is a controllable outlet // "type":"Outlet" // }, // { -// "ref":111, +// "ref":111, // Required - HomeSeer Device Reference for your sensor // "type":"TemperatureSensor", // Required for a temperature sensor // "temperatureUnit":"F", // Optional - C is the default -// "name":"Bedroom temp" // Optional - HomeSeer device name is the default +// "name":"Bedroom temp", // Optional - HomeSeer device name is the default +// "batteryRef":112, // Optional - HomeSeer device reference for the sensor battery level +// "batteryThreshold":15 // Optional - If sensor battery level is below this value, the HomeKit LowBattery characteristic is set to 1. Default is 10 // }, // { // "ref":113, // Required - HomeSeer Device Reference of the Current Temperature Device @@ -65,6 +82,12 @@ // "controlAutoValue":3, // Required - Value for AUTO // "coolingThresholdRef":169, // Optional - Not-implemented-yet - HomeSeer device reference for your thermostat cooling threshold // "heatingThresholdRef":170 // Optional - Not-implemented-yet - HomeSeer device reference for your thermostat heating threshold +// }, +// { +// "ref":115, // Required - HomeSeer Device Reference for a device holding battery level (0-100) +// "type":"Battery", // Required for a Battery +// "name":"Roomba battery", // Optional - HomeSeer device name is the default +// "batteryThreshold":15 // Optional - If the level is below this value, the HomeKit LowBattery characteristic is set to 1. Default is 10 // } // ] // } @@ -72,18 +95,20 @@ // // // SUPORTED TYPES: -// - Lightbulb (can_dim, onValue, offValue options) -// - Fan (onValue, offValue options) -// - Switch (onValue, offValue options) -// - Outlet (onValue, offValue options) +// - Lightbulb (can_dim, onValue, offValue options) +// - Fan (onValue, offValue options) +// - Switch (onValue, offValue options) +// - Outlet (onValue, offValue options) +// - Thermostat (temperatureUnit, setPoint, state, control options) // - TemperatureSensor (temperatureUnit=C|F) -// - Thermostat (temperatureUnit, setPoint, state, control options) -// - ContactSensor -// - MotionSensor -// - LeakSensor -// - LightSensor -// - OccupancySensor -// - SmokeSensor +// - ContactSensor (0=no contact, 1=contact - batteryRef, batteryThreshold option) +// - MotionSensor (0=no motion, 1=motion - batteryRef, batteryThreshold option) +// - LeakSensor (0=no leak, 1=leak - batteryRef, batteryThreshold option) +// - LightSensor (HomeSeer device value in Lux - batteryRef, batteryThreshold option) +// - HumiditySensor (HomeSeer device value in % - batteryRef, batteryThreshold option) +// - OccupancySensor (0=no occupancy, 1=occupancy - batteryRef, batteryThreshold option) +// - SmokeSensor (0=no smoke, 1=smoke - batteryRef, batteryThreshold option) +// - Battery (batteryThreshold option) // - Door @@ -111,19 +136,25 @@ function HomeSeerPlatform(log, config){ HomeSeerPlatform.prototype = { accessories: function(callback) { - this.log("Fetching HomeSeer devices."); + var that = this; + var foundAccessories = []; + if( this.config.events ) { + this.log("Creating HomeSeer events."); + for( var i=0; i Date: Tue, 13 Oct 2015 05:37:44 +0200 Subject: [PATCH 39/81] MotionDetector added, with new needed configuration tags. --- platforms/ZWayServer.js | 70 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index 0b81e88..92938a3 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -131,8 +131,11 @@ ZWayServerPlatform.prototype = { gdid = vdev.id.replace(/^(.*?)_zway_(\d+-\d+)-\d.*/, '$1_$2'); } - var gd = groupedDevices[gdid] || (groupedDevices[gdid] = {devices: [], types: {}, extras: {}, primary: undefined}); + var gd = groupedDevices[gdid] || (groupedDevices[gdid] = { devices: [], types: {}, extras: {}, primary: undefined, cxmap: {} }); + gd.devices.push(vdev); + var vdevIndex = gd.devices.length - 1; + var tk = ZWayServerPlatform.getVDevTypeKey(vdev); // If this is explicitly set as primary, set it now... @@ -143,17 +146,24 @@ ZWayServerPlatform.prototype = { gd.extras[tk].push(gd.types[tk]); delete gd.types[tk]; // clear the way for this one to be set here below... } - gd.primary = gd.devices.length - 1; + gd.primary = vdevIndex; //gd.types[tk] = gd.primary; } if(gd.types[tk] === undefined){ - gd.types[tk] = gd.devices.length - 1; + gd.types[tk] = vdevIndex; } else { gd.extras[tk] = gd.extras[tk] || []; - gd.extras[tk].push(gd.devices.length - 1); + gd.extras[tk].push(vdevIndex); + } + if(tk !== vdev.deviceType) gd.types[vdev.deviceType] = vdevIndex; // also include the deviceType only as a possibility + + // Create a map entry when Homebridge.Characteristic.Type is set... + var ctype = this.getTagValue(vdev, "Characteristic.Type"); + if(ctype && Characteristic[ctype]){ + var cx = new Characteristic[ctype](); + gd.cxmap[cx.UUID] = vdevIndex; } - if(tk !== vdev.deviceType) gd.types[vdev.deviceType] = gd.devices.length - 1; // also include the deviceType only as a possibility } for(var gdid in groupedDevices) { @@ -357,6 +367,11 @@ ZWayServerAccessory.prototype = { case "sensorMultilevel.Luminiscence": services.push(new Service.LightSensor(vdev.metrics.title, vdev.id)); break; + case "sensorBinary": + var stype = this.platform.getTagValue(vdev, "Service.Type"); + if(stype === "MotionSensor"){ + services.push(new Service.MotionSensor(vdev.metrics.title, vdev.id)); + } } var validServices =[]; @@ -377,6 +392,12 @@ ZWayServerAccessory.prototype = { } , getVDevForCharacteristic: function(cx, vdevPreferred){ + + // If we know which vdev should be used for this Characteristic, we're done! + if(this.devDesc.cxmap[cx.UUID] !== undefined){ + return this.devDesc.devices[this.devDesc.cxmap[cx.UUID]]; + } + var map = this.uuidToTypeKeyMap; if(!map){ this.uuidToTypeKeyMap = map = {}; @@ -399,7 +420,7 @@ ZWayServerAccessory.prototype = { } if(cx instanceof Characteristic.Name) return vdevPreferred; - + // Special case!: If cx is a CurrentTemperature, ignore the preferred device...we want the sensor if available! if(cx instanceof Characteristic.CurrentTemperature) vdevPreferred = null; // @@ -768,6 +789,43 @@ ZWayServerAccessory.prototype = { }); return cx; } + + if(cx instanceof Characteristic.MotionDetected){ + cx.zway_getValueFromVDev = function(vdev){ + return vdev.metrics.level === "off" ? false : true; + }; + cx.value = cx.zway_getValueFromVDev(vdev); + cx.on('get', function(callback, context){ + debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); + this.getVDev(vdev).then(function(result){ + debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); + callback(false, cx.zway_getValueFromVDev(result.data)); + }); + }.bind(this)); + cx.on('change', function(ev){ + debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); + }); + return cx; + } + + if(cx instanceof Characteristic.StatusTampered){ + cx.zway_getValueFromVDev = function(vdev){ + return vdev.metrics.level === "off" ? Characteristic.StatusTampered.NOT_TAMPERED : Characteristic.StatusTampered.TAMPERED; + }; + cx.value = cx.zway_getValueFromVDev(vdev); + cx.on('get', function(callback, context){ + debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); + this.getVDev(vdev).then(function(result){ + debug("Got value: " + cx.zway_getValueFromVDev(result.data) + ", for " + vdev.metrics.title + "."); + callback(false, cx.zway_getValueFromVDev(result.data)); + }); + }.bind(this)); + cx.on('change', function(ev){ + debug("Device " + vdev.metrics.title + ", characteristic " + cx.displayName + " changed from " + ev.oldValue + " to " + ev.newValue); + }); + return cx; + } + } , configureService: function(service, vdev){ From fdbd33f29dc97476745770a36af2d6481c084e1e Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Tue, 13 Oct 2015 06:50:23 +0200 Subject: [PATCH 40/81] Updated for new tweaked "setProps" API, removed cruft. --- platforms/ZWayServer.js | 43 +++++++++++++++-------------------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index 92938a3..e50336e 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -459,7 +459,6 @@ ZWayServerAccessory.prototype = { debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); callback(false, accessory.name); }); - cx.writable = false; return cx; } @@ -546,12 +545,6 @@ ZWayServerAccessory.prototype = { }); }.bind(this)); - cx.writeable = false; - //cx.on('set', function(level, callback){ - // this.command(vdev, "exact", {level: "on", "color.r": 255, "color.g": 0, "color.b": 0}).then(function(result){ - // callback(); - // }); - //}.bind(this)); return cx; } @@ -581,12 +574,6 @@ ZWayServerAccessory.prototype = { }); }.bind(this)); - cx.writeable = false; - //cx.on('set', function(level, callback){ - // this.command(vdev, "exact", {level: "on", "color.r": 255, "color.g": 0, "color.b": 0}).then(function(result){ - // callback(); - // }); - //}.bind(this)); return cx; } @@ -602,8 +589,10 @@ ZWayServerAccessory.prototype = { callback(false, cx.zway_getValueFromVDev(result.data)); }); }.bind(this)); - cx.minimumValue = vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : -40; - cx.maximumValue = vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 999; + cx.setProps({ + minValue: vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : -40, + maxValue: vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 999 + }); return cx; } @@ -625,8 +614,10 @@ ZWayServerAccessory.prototype = { callback(); }); }.bind(this)); - cx.minimumValue = vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : 5; - cx.maximumValue = vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 40; + cx.setProps({ + minValue: vdev.metrics && vdev.metrics.min !== undefined ? vdev.metrics.min : 5, + maxValue: vdev.metrics && vdev.metrics.max !== undefined ? vdev.metrics.max : 40 + }); return cx; } @@ -640,7 +631,9 @@ ZWayServerAccessory.prototype = { debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); callback(false, Characteristic.TemperatureDisplayUnits.CELSIUS); }); - cx.writable = false; + cx.setProps({ + perms: [Characteristic.Perms.READ] + }); return cx; } @@ -668,7 +661,6 @@ ZWayServerAccessory.prototype = { callback(false, Characteristic.TargetHeatingCoolingState.HEAT); }); // Hmm... apparently if this is not setable, we can't add a thermostat change to a scene. So, make it writable but a no-op. - cx.writable = true; cx.on('set', function(newValue, callback){ debug("WARN: Set of TargetHeatingCoolingState not yet implemented, resetting to HEAT!") callback(undefined, Characteristic.TargetHeatingCoolingState.HEAT); @@ -703,8 +695,9 @@ ZWayServerAccessory.prototype = { debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); callback(false, Characteristic.TargetDoorState.CLOSED); }); - //cx.readable = false; - cx.writable = false; + cx.setProps({ + perms: [Characteristic.Perms.READ] + }); } if(cx instanceof Characteristic.ObstructionDetected){ @@ -717,8 +710,6 @@ ZWayServerAccessory.prototype = { debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); callback(false, false); }); - //cx.readable = false; - cx.writable = false; } if(cx instanceof Characteristic.BatteryLevel){ @@ -759,8 +750,6 @@ ZWayServerAccessory.prototype = { debug("Getting value for " + vdev.metrics.title + ", characteristic \"" + cx.displayName + "\"..."); callback(false, Characteristic.ChargingState.NOT_CHARGING); }); - //cx.readable = false; - cx.writable = false; } if(cx instanceof Characteristic.CurrentAmbientLightLevel){ @@ -769,8 +758,8 @@ ZWayServerAccessory.prototype = { // Completely unscientific guess, based on test-fit data and Wikipedia real-world lux values. // This will probably change! var lux = 0.0005 * (vdev.metrics.level^3.6); - if(lux < cx.minimumValue) return cx.minimumValue; - if(lux > cx.maximumValue) return cx.maximumValue; + // Bounds checking now done upstream! + //if(lux < cx.minimumValue) return cx.minimumValue; if(lux > cx.maximumValue) return cx.maximumValue; return lux; } else { return vdev.metrics.level; From 7c7ceb64534eeaf875ddaf18b685aa2053e19e21 Mon Sep 17 00:00:00 2001 From: iRaven Date: Tue, 13 Oct 2015 13:06:11 +0200 Subject: [PATCH 41/81] Rename HomeMaticThermo to HomeMaticThermo.js --- accessories/{HomeMaticThermo => HomeMaticThermo.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename accessories/{HomeMaticThermo => HomeMaticThermo.js} (100%) diff --git a/accessories/HomeMaticThermo b/accessories/HomeMaticThermo.js similarity index 100% rename from accessories/HomeMaticThermo rename to accessories/HomeMaticThermo.js From d8a35963266e6a2db4ff271cd57c8caa22da098b Mon Sep 17 00:00:00 2001 From: iRaven Date: Tue, 13 Oct 2015 17:39:28 +0200 Subject: [PATCH 42/81] Added HomematicThermo, HomematicWindow --- config-sample.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/config-sample.json b/config-sample.json index 7af74db..1e9d872 100644 --- a/config-sample.json +++ b/config-sample.json @@ -149,6 +149,24 @@ "ccu_id": "The XMP-API id of your HomeMatic device", "ccu_ip": "The IP-Adress of your HomeMatic CCU device" }, + { + "accessory": "HomeMaticWindow", + "name": "Contact", + "description": "Control HomeMatic devices (The XMP-API addon for the CCU is required)", + "ccu_id": "The XMP-API id of your HomeMatic device (type HM-Sec-RHS)", + "ccu_ip": "The IP-Adress of your HomeMatic CCU device" + }, + { + "accessory": "HomeMaticThermo", + "name": "Contact", + "description": "Control HomeMatic devices (The XMP-API addon for the CCU is required)", + "ccu_id_TargetTemp": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )", + "ccu_id_CurrentTemp": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )", + "ccu_id_ControlMode": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )", + "ccu_id_ManuMode": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )", + "ccu_id_AutoMode": "The XMP-API id of your HomeMatic device (type HM-CC-RT-DN )", + "ccu_ip": "The IP-Adress of your HomeMatic CCU device" + }, { "accessory": "X10", "name": "Lamp", From a056b16c35e0e6c6dfc91bbd73fca2ae01f7009a Mon Sep 17 00:00:00 2001 From: stipus Date: Wed, 14 Oct 2015 22:06:11 +0200 Subject: [PATCH 43/81] Added CarbonMonoxide and CarbonDioxide support - Added CarbonMonoxide and CarbonDioxide sensor support - Added onValues option to all binary sensors - Added uuid_base parameter to all HomeSeer accessories --- platforms/HomeSeer.js | 212 +++++++++++++++++++++++++++++------------- 1 file changed, 149 insertions(+), 63 deletions(-) diff --git a/platforms/HomeSeer.js b/platforms/HomeSeer.js index e0c5c79..c4ccde7 100644 --- a/platforms/HomeSeer.js +++ b/platforms/HomeSeer.js @@ -17,6 +17,13 @@ // - Added Battery support // - Added low battery support for all sensors // - Added HomeSeer event support (using HomeKit switches...) +// V0.7 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/13 +// - You can add multiple HomeKit devices for the same HomeSeer device reference +// - Added CarbonMonoxide sensor +// - Added CarbonDioxide sensor +// - Added onValues option to all binary sensors +// V0.8 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/14 +// - Added uuid_base parameter to all accessories // // // Remember to add platform to config.json. @@ -35,7 +42,8 @@ // { // "eventGroup":"My Group", // Required - The HomeSeer event group // "eventName":"My Event", // Required - The HomeSeer event name -// "name":"Test" // Optional - HomeSeer event name is the default +// "name":"Test", // Optional - HomeSeer event name is the default +// "uuid_base":"SomeUniqueId" // Optional - HomeKit identifier will be derived from this parameter instead of the name // } // ], // @@ -46,7 +54,8 @@ // "name":"My Light", // Optional - HomeSeer device name is the default // "offValue":"0", // Optional - 0 is the default // "onValue":"100", // Optional - 100 is the default -// "can_dim":true // Optional - true is the default - false for a non dimmable lightbulb +// "can_dim":true, // Optional - true is the default - false for a non dimmable lightbulb +// "uuid_base":"SomeUniqueId2" // Optional - HomeKit identifier will be derived from this parameter instead of the name // }, // { // "ref":9 // This is a dimmable Lightbulb by default @@ -64,6 +73,22 @@ // "batteryThreshold":15 // Optional - If sensor battery level is below this value, the HomeKit LowBattery characteristic is set to 1. Default is 10 // }, // { +// "ref":34, // Required - HomeSeer Device Reference for your sensor +// "type":"SmokeSensor", // Required for a smoke sensor +// "name":"Kichen smoke detector", // Optional - HomeSeer device name is the default +// "batteryRef":35, // Optional - HomeSeer device reference for the sensor battery level +// "batteryThreshold":15, // Optional - If sensor battery level is below this value, the HomeKit LowBattery characteristic is set to 1. Default is 10 +// "onValues":[1,1.255] // Optional - List of all HomeSeer values triggering a "ON" sensor state - Default is any value different than 0 +// }, +// { +// "ref":34, // Required - HomeSeer Device Reference for your sensor (Here it's the same device as the SmokeSensor above) +// "type":"CarbonMonoxideSensor", // Required for a carbon monoxide sensor +// "name":"Kichen CO detector", // Optional - HomeSeer device name is the default +// "batteryRef":35, // Optional - HomeSeer device reference for the sensor battery level +// "batteryThreshold":15, // Optional - If sensor battery level is below this value, the HomeKit LowBattery characteristic is set to 1. Default is 10 +// "onValues":[2,2.255] // Optional - List of all HomeSeer values triggering a "ON" sensor state - Default is any value different than 0 +// }, +// { // "ref":113, // Required - HomeSeer Device Reference of the Current Temperature Device // "type":"Thermostat", // Required for a Thermostat // "name":"Température Salon", // Optional - HomeSeer device name is the default @@ -76,10 +101,10 @@ // "stateCoolValues":[2], // Required - List of the HomeSeer device values for a HomeKit state=COOL // "stateAutoValues":[3], // Required - List of the HomeSeer device values for a HomeKit state=AUTO // "controlRef":168, // Required - HomeSeer device reference for your thermostat mode control (It can be the same as stateRef for some thermostats) -// "controlOffValue":0, // Required - Value for OFF -// "controlHeatValue":1, // Required - Value for HEAT -// "controlCoolValue":2, // Required - Value for COOL -// "controlAutoValue":3, // Required - Value for AUTO +// "controlOffValue":0, // Required - HomeSeer device control value for OFF +// "controlHeatValue":1, // Required - HomeSeer device control value for HEAT +// "controlCoolValue":2, // Required - HomeSeer device control value for COOL +// "controlAutoValue":3, // Required - HomeSeer device control value for AUTO // "coolingThresholdRef":169, // Optional - Not-implemented-yet - HomeSeer device reference for your thermostat cooling threshold // "heatingThresholdRef":170 // Optional - Not-implemented-yet - HomeSeer device reference for your thermostat heating threshold // }, @@ -95,20 +120,22 @@ // // // SUPORTED TYPES: -// - Lightbulb (can_dim, onValue, offValue options) -// - Fan (onValue, offValue options) -// - Switch (onValue, offValue options) -// - Outlet (onValue, offValue options) -// - Thermostat (temperatureUnit, setPoint, state, control options) -// - TemperatureSensor (temperatureUnit=C|F) -// - ContactSensor (0=no contact, 1=contact - batteryRef, batteryThreshold option) -// - MotionSensor (0=no motion, 1=motion - batteryRef, batteryThreshold option) -// - LeakSensor (0=no leak, 1=leak - batteryRef, batteryThreshold option) -// - LightSensor (HomeSeer device value in Lux - batteryRef, batteryThreshold option) -// - HumiditySensor (HomeSeer device value in % - batteryRef, batteryThreshold option) -// - OccupancySensor (0=no occupancy, 1=occupancy - batteryRef, batteryThreshold option) -// - SmokeSensor (0=no smoke, 1=smoke - batteryRef, batteryThreshold option) -// - Battery (batteryThreshold option) +// - Lightbulb (can_dim, onValue, offValue options) +// - Fan (onValue, offValue options) +// - Switch (onValue, offValue options) +// - Outlet (onValue, offValue options) +// - Thermostat (temperatureUnit, setPoint, state, control options) +// - TemperatureSensor (temperatureUnit=C|F) +// - HumiditySensor (HomeSeer device value in % - batteryRef, batteryThreshold options) +// - LightSensor (HomeSeer device value in Lux - batteryRef, batteryThreshold options) +// - ContactSensor (onValues, batteryRef, batteryThreshold options) +// - MotionSensor (onValues, batteryRef, batteryThreshold options) +// - LeakSensor (onValues, batteryRef, batteryThreshold options) +// - OccupancySensor (onValues, batteryRef, batteryThreshold options) +// - SmokeSensor (onValues, batteryRef, batteryThreshold options) +// - CarbonMonoxideSensor (onValues, batteryRef, batteryThreshold options) +// - CarbonDioxideSensor (onValues, batteryRef, batteryThreshold options) +// - Battery (batteryThreshold option) // - Door @@ -163,9 +190,14 @@ HomeSeerPlatform.prototype = { else { this.log('HomeSeer status function succeeded!'); var response = JSON.parse( body ); - for( var i=0; i Date: Thu, 15 Oct 2015 14:25:20 -0700 Subject: [PATCH 44/81] 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 45/81] 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 46/81] Removed tabs --- platforms/Indigo.js | 436 ++++++++++++++++++++++---------------------- 1 file changed, 218 insertions(+), 218 deletions(-) diff --git a/platforms/Indigo.js b/platforms/Indigo.js index f77813a..1332004 100644 --- a/platforms/Indigo.js +++ b/platforms/Indigo.js @@ -8,10 +8,10 @@ // { // "platform": "Indigo", // required // "name": "Indigo", // required -// "host": "127.0.0.1", // required -// "port": "8176", // required -// "username": "username", // optional -// "password": "password" // optional +// "host": "127.0.0.1", // required +// "port": "8176", // required +// "username": "username", // optional +// "password": "password" // optional // } // ], // @@ -28,19 +28,19 @@ var async = require('async'); function IndigoPlatform(log, config) { this.log = log; - this.baseURL = "http://" + config["host"] + ":" + config["port"]; + this.baseURL = "http://" + config["host"] + ":" + config["port"]; - if (config["username"] && config["password"]) { - this.auth = { - 'user': config["username"], - 'pass': config["password"], - 'sendImmediately': false - }; - } + if (config["username"] && config["password"]) { + this.auth = { + 'user': config["username"], + 'pass': config["password"], + 'sendImmediately': false + }; + } } IndigoPlatform.prototype = { - accessories: function(callback) { + accessories: function(callback) { var that = this; this.log("Discovering Indigo Devices."); @@ -48,11 +48,11 @@ IndigoPlatform.prototype = { url: this.baseURL + "/devices.json/", method: 'GET' }; - if (this.auth) { - options['auth'] = this.auth; - } + if (this.auth) { + options['auth'] = this.auth; + } this.foundAccessories = []; - this.callback = callback; + this.callback = callback; request(options, function(error, response, body) { if (error) { @@ -61,73 +61,73 @@ IndigoPlatform.prototype = { return error; } - // Cheesy hack because response may have an extra comma at the start of the array, which is invalid - var firstComma = body.indexOf(","); - if (firstComma < 10) { - body = "[" + body.substr(firstComma + 1); - } + // Cheesy hack because response may have an extra comma at the start of the array, which is invalid + var firstComma = body.indexOf(","); + if (firstComma < 10) { + body = "[" + body.substr(firstComma + 1); + } - var json = JSON.parse(body); - async.each(json, function(item, asyncCallback) { - var deviceURL = that.baseURL + item.restURL; - var deviceOptions = { - url: deviceURL, - method: 'GET' - }; - if (that.auth) { - deviceOptions['auth'] = that.auth; - } + var json = JSON.parse(body); + async.each(json, function(item, asyncCallback) { + var deviceURL = that.baseURL + item.restURL; + var deviceOptions = { + url: deviceURL, + method: 'GET' + }; + if (that.auth) { + deviceOptions['auth'] = that.auth; + } - request(deviceOptions, function(deviceError, deviceResponse, deviceBody) { - if (deviceError) { - asyncCallback(deviceError); - } + request(deviceOptions, function(deviceError, deviceResponse, deviceBody) { + if (deviceError) { + asyncCallback(deviceError); + } - var deviceJson = JSON.parse(deviceBody); - that.log("Discovered " + deviceJson.type + ": " + deviceJson.name); - that.foundAccessories.push( - new IndigoAccessory(that.log, that.auth, deviceURL, deviceJson)); - asyncCallback(); - }); - }, function(asyncError) { - // This will be called after all the requests complete - if (asyncError) { - console.trace("Requesting Indigo device info."); - that.log(asyncError); - } else { - that.callback(that.foundAccessories.sort(function (a,b) { - return (a.name > b.name) - (a.name < b.name); - })); - } - }); - }); - } + var deviceJson = JSON.parse(deviceBody); + that.log("Discovered " + deviceJson.type + ": " + deviceJson.name); + that.foundAccessories.push( + new IndigoAccessory(that.log, that.auth, deviceURL, deviceJson)); + asyncCallback(); + }); + }, function(asyncError) { + // This will be called after all the requests complete + if (asyncError) { + console.trace("Requesting Indigo device info."); + that.log(asyncError); + } else { + that.callback(that.foundAccessories.sort(function (a,b) { + return (a.name > b.name) - (a.name < b.name); + })); + } + }); + }); + } } function IndigoAccessory(log, auth, deviceURL, json) { this.log = log; - this.auth = auth; + this.auth = auth; this.deviceURL = deviceURL; - for (var prop in json) { - if (json.hasOwnProperty(prop)) { - this[prop] = json[prop]; - } - } + for (var prop in json) { + if (json.hasOwnProperty(prop)) { + this[prop] = json[prop]; + } + } } IndigoAccessory.prototype = { getStatus: function(callback) { - var that = this; + var that = this; var options = { - url: this.deviceURL, + url: this.deviceURL, method: 'GET' }; - if (this.auth) { - options['auth'] = this.auth; - } + if (this.auth) { + options['auth'] = this.auth; + } request(options, function(error, response, body) { if (error) { @@ -136,22 +136,22 @@ IndigoAccessory.prototype = { return error; } - that.log("getStatus of " + that.name + ": " + body); - callback(JSON.parse(body)); + that.log("getStatus of " + that.name + ": " + body); + callback(JSON.parse(body)); }); }, updateStatus: function(params) { - var that = this; + var that = this; var options = { - url: this.deviceURL + "?" + params, + url: this.deviceURL + "?" + params, method: 'PUT' }; - if (this.auth) { - options['auth'] = this.auth; - } + if (this.auth) { + options['auth'] = this.auth; + } - this.log("updateStatus of " + that.name + ": " + params); + this.log("updateStatus of " + that.name + ": " + params); request(options, function(error, response, body) { if (error) { console.trace("Updating Device Status."); @@ -162,101 +162,101 @@ IndigoAccessory.prototype = { }, query: function(prop, callback) { - this.getStatus(function(json) { - callback(json[prop]); - }); + this.getStatus(function(json) { + callback(json[prop]); + }); }, - turnOn: function() { - if (this.typeSupportsOnOff) { - this.updateStatus("isOn=1"); - } - }, - - turnOff: function() { - if (this.typeSupportsOnOff) { - this.updateStatus("isOn=0"); - } - }, + turnOn: function() { + if (this.typeSupportsOnOff) { + this.updateStatus("isOn=1"); + } + }, + + turnOff: function() { + if (this.typeSupportsOnOff) { + this.updateStatus("isOn=0"); + } + }, - setBrightness: function(brightness) { - if (this.typeSupportsDim && brightness >= 0 && brightness <= 100) { - this.updateStatus("brightness=" + brightness); - } - }, - - setSpeedIndex: function(speedIndex) { - if (this.typeSupportsSpeedControl && speedIndex >= 0 && speedIndex <= 3) { - this.updateStatus("speedIndex=" + speedIndex); - } - }, - + setBrightness: function(brightness) { + if (this.typeSupportsDim && brightness >= 0 && brightness <= 100) { + this.updateStatus("brightness=" + brightness); + } + }, + + setSpeedIndex: function(speedIndex) { + if (this.typeSupportsSpeedControl && speedIndex >= 0 && speedIndex <= 3) { + this.updateStatus("speedIndex=" + speedIndex); + } + }, + getCurrentHeatingCooling: function(callback) { - this.getStatus(function(json) { - var mode = 0; - if (json["hvacOperatonModeIsHeat"]) { - mode = 1; - } - else if (json["hvacOperationModeIsCool"]) { - mode = 2; - } - else if (json["hvacOperationModeIsAuto"]) { - mode = 3; - } - callback(mode); - }); + this.getStatus(function(json) { + var mode = 0; + if (json["hvacOperatonModeIsHeat"]) { + mode = 1; + } + else if (json["hvacOperationModeIsCool"]) { + mode = 2; + } + else if (json["hvacOperationModeIsAuto"]) { + mode = 3; + } + callback(mode); + }); }, setTargetHeatingCooling: function(mode) { - if (mode == 0) { - param = "Off"; - } - else if (mode == 1) { - param = "Heat"; - } - else if (mode == 2) { - param = "Cool"; - } - else if (mode == 3) { - param = "Auto"; - } + if (mode == 0) { + param = "Off"; + } + else if (mode == 1) { + param = "Heat"; + } + else if (mode == 2) { + param = "Cool"; + } + else if (mode == 3) { + param = "Auto"; + } - if (param) { - this.updateStatus("hvacOperationModeIs" + param + "=true"); - } + if (param) { + this.updateStatus("hvacOperationModeIs" + param + "=true"); + } }, getTargetTemperature: function(callback) { - this.getStatus(function(json) { - var result; - if (json["hvacOperatonModeIsHeat"]) { - result = json["setpointHeat"]; - } - else if (json["hvacOperationModeIsCool"]) { - result = json["setpointCool"]; - } - else { - result = (json["setpointHeat"] + json["setpointCool"]) / 2; - } - callback(result); - }); + this.getStatus(function(json) { + var result; + if (json["hvacOperatonModeIsHeat"]) { + result = json["setpointHeat"]; + } + else if (json["hvacOperationModeIsCool"]) { + result = json["setpointCool"]; + } + else { + result = (json["setpointHeat"] + json["setpointCool"]) / 2; + } + callback(result); + }); }, setTargetTemperature: function(temperature) { - var that = this; - this.getStatus(function(json) { - if (json["hvacOperatonModeIsHeat"]) { - that.updateStatus("setpointHeat=" + temperature); - } - else if (json["hvacOperationModeIsCool"]) { - that.updateStatus("setpointCool=" + temperature); - } - else { - var cool = temperature + 5; - var heat = temperature - 5; - that.updateStatus("setpointCool=" + cool + "&setpointHeat=" + heat); - } - }); + var that = this; + this.getStatus(function(json) { + if (json["hvacOperatonModeIsHeat"]) { + that.updateStatus("setpointHeat=" + temperature); + } + else if (json["hvacOperationModeIsCool"]) { + that.updateStatus("setpointCool=" + temperature); + } + else { + var cool = temperature + 5; + var heat = temperature - 5; + that.updateStatus("setpointCool=" + cool + "&setpointHeat=" + heat); + } + }); }, informationCharacteristics: function() { @@ -316,7 +316,7 @@ IndigoAccessory.prototype = { }, controlCharacteristics: function(that) { - var cTypes = [{ + var cTypes = [{ cType: types.NAME_CTYPE, onUpdate: null, perms: [Characteristic.Perms.READ], @@ -326,15 +326,15 @@ IndigoAccessory.prototype = { supportBonjour: false, manfDescription: "Name of the accessory", designedMaxLength: 255 - }]; + }]; /* if (that.typeSupportsOnOff) - { */ + { */ cTypes.push({ cType: types.POWER_STATE_CTYPE, perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], format: Characteristic.Formats.BOOL, - initialValue: (that.isOn) ? 1 : 0, + initialValue: (that.isOn) ? 1 : 0, supportEvents: true, supportBonjour: false, manfDescription: "Change the power state", @@ -347,14 +347,14 @@ IndigoAccessory.prototype = { } }, onRead: function(callback) { - that.query("isOn", function(isOn) { - callback((isOn) ? 1 : 0); - }); + that.query("isOn", function(isOn) { + callback((isOn) ? 1 : 0); + }); } }); -// } - if (that.typeSupportsDim) - { +// } + if (that.typeSupportsDim) + { cTypes.push({ cType: types.BRIGHTNESS_CTYPE, perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], @@ -368,10 +368,10 @@ IndigoAccessory.prototype = { designedMinStep: 1, unit: Characteristic.Units.PERCENTAGE, onUpdate: function(value) { - that.setBrightness(value); + that.setBrightness(value); }, onRead: function(callback) { - that.query("brightness", callback); + that.query("brightness", callback); } }); } @@ -386,11 +386,11 @@ IndigoAccessory.prototype = { supportBonjour: false, manfDescription: "Change the speed of the fan", designedMaxLength: 1, - designedMinValue: 0, - designedMaxValue: 3, - designedMinStep: 1, + designedMinValue: 0, + designedMaxValue: 3, + designedMinStep: 1, onUpdate: function(value) { - that.setSpeedIndex(value); + that.setSpeedIndex(value); }, onRead: function(callback) { that.query("speedIndex", callback); @@ -400,44 +400,44 @@ IndigoAccessory.prototype = { if (that.typeSupportsHVAC) { cTypes.push({ - cType: types.CURRENTHEATINGCOOLING_CTYPE, - perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], - format: Characteristic.Formats.INT, - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Current Mode", - designedMaxLength: 1, - designedMinValue: 0, - designedMaxValue: 3, - designedMinStep: 1, - onUpdate: null, - onRead: function(callback) { - that.getCurrentHeatingCooling(callback); - } + cType: types.CURRENTHEATINGCOOLING_CTYPE, + perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.INT, + initialValue: 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Current Mode", + designedMaxLength: 1, + designedMinValue: 0, + designedMaxValue: 3, + designedMinStep: 1, + onUpdate: null, + onRead: function(callback) { + that.getCurrentHeatingCooling(callback); + } }); cTypes.push({ - cType: types.TARGETHEATINGCOOLING_CTYPE, - perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], - format: Characteristic.Formats.INT, - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Target Mode", - designedMaxLength: 1, - designedMinValue: 0, - designedMaxValue: 3, - designedMinStep: 1, - onUpdate: function(value) { - that.setTargetHeatingCooling(value); - }, - onRead: function(callback) { - that.getCurrentHeatingCooling(callback); - } + cType: types.TARGETHEATINGCOOLING_CTYPE, + perms: [Characteristic.Perms.WRITE,Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + format: Characteristic.Formats.INT, + initialValue: 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Target Mode", + designedMaxLength: 1, + designedMinValue: 0, + designedMaxValue: 3, + designedMinStep: 1, + onUpdate: function(value) { + that.setTargetHeatingCooling(value); + }, + onRead: function(callback) { + that.getCurrentHeatingCooling(callback); + } }); cTypes.push({ - cType: types.CURRENT_TEMPERATURE_CTYPE, - perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], + cType: types.CURRENT_TEMPERATURE_CTYPE, + perms: [Characteristic.Perms.READ,Characteristic.Perms.NOTIFY], format: Characteristic.Formats.INT, designedMinValue: 0, designedMaxValue: 110, @@ -446,11 +446,11 @@ IndigoAccessory.prototype = { supportEvents: true, supportBonjour: false, manfDescription: "Current Temperature", - unit: Characteristic.Units.FAHRENHEIT, - onUpdate: null, + unit: Characteristic.Units.FAHRENHEIT, + onUpdate: null, onRead: function(callback) { that.query("displayRawState", callback); - } + } }); cTypes.push({ cType: types.TARGET_TEMPERATURE_CTYPE, @@ -463,12 +463,12 @@ IndigoAccessory.prototype = { supportEvents: true, supportBonjour: false, manfDescription: "Target Temperature", - unit: Characteristic.Units.FAHRENHEIT, - onUpdate: function(value) { + unit: Characteristic.Units.FAHRENHEIT, + onUpdate: function(value) { that.setTargetTemperature(value); }, onRead: function(callback) { - that.getTargetTemperature(callback); + that.getTargetTemperature(callback); } }); cTypes.push({ @@ -479,9 +479,9 @@ IndigoAccessory.prototype = { supportEvents: false, supportBonjour: false, manfDescription: "Unit", - onUpdate: null, + onUpdate: null, onRead: function(callback) { - callback(Characteristic.Units.FAHRENHEIT); + callback(Characteristic.Units.FAHRENHEIT); } }); } @@ -490,13 +490,13 @@ IndigoAccessory.prototype = { }, sType: function() { - if (this.typeSupportsHVAC) { - return types.THERMOSTAT_STYPE; - } else if (this.typeSupportsDim) { + if (this.typeSupportsHVAC) { + return types.THERMOSTAT_STYPE; + } else if (this.typeSupportsDim) { return types.LIGHTBULB_STYPE; } else if (this.typeSupportsSpeedControl) { return types.FAN_STYPE; - } else if (this.typeSupportsOnOff) { + } else if (this.typeSupportsOnOff) { return types.SWITCH_STYPE; } @@ -504,7 +504,7 @@ IndigoAccessory.prototype = { }, getServices: function() { - var that = this; + var that = this; var services = [{ sType: types.ACCESSORY_INFORMATION_STYPE, characteristics: that.informationCharacteristics(), From bab1eb730e332b1e946ccc70709554d74a106447 Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Thu, 15 Oct 2015 20:08:25 -0700 Subject: [PATCH 47/81] Version bump to node-icontrol --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8f88a35..feb0370 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "lifx": "git+https://github.com/magicmonkey/lifxjs.git", "mdns": "^2.2.4", "node-hue-api": "^1.0.5", - "node-icontrol": "^0.1.4", + "node-icontrol": "^0.1.5", "node-milight-promise": "0.0.x", "node-persist": "0.0.x", "q": "1.4.x", From aad811fe6e794b37e253c4bc39e94bef77a559a6 Mon Sep 17 00:00:00 2001 From: Mike Riccio Date: Thu, 15 Oct 2015 21:31:08 -0700 Subject: [PATCH 48/81] 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 49/81] Support for case-sensitive file systems and current HAP-NodeJS with nodejs 4 support --- accessories/FileSensor.js | 4 ++-- accessories/GenericRS232Device.js | 4 ++-- accessories/HomeMaticWindow.js | 2 +- accessories/Http.js | 4 ++-- accessories/HttpHygrometer.js | 4 ++-- accessories/HttpThermometer.js | 4 ++-- accessories/Lockitron.js | 4 ++-- accessories/WeMo.js | 4 ++-- accessories/iControl.js | 4 ++-- accessories/knxdevice.js | 4 ++-- accessories/mpdclient.js | 4 ++-- app.js | 14 +++++++------- package.json | 2 +- platforms/FHEM.js | 4 ++-- platforms/FibaroHC2.js | 4 ++-- platforms/HomeAssistant.js | 4 ++-- platforms/HomeSeer.js | 4 ++-- platforms/LIFx.js | 4 ++-- platforms/MiLight.js | 4 ++-- platforms/YamahaAVR.js | 4 ++-- platforms/ZWayServer.js | 4 ++-- 21 files changed, 45 insertions(+), 45 deletions(-) diff --git a/accessories/FileSensor.js b/accessories/FileSensor.js index e377dc6..112089e 100644 --- a/accessories/FileSensor.js +++ b/accessories/FileSensor.js @@ -1,5 +1,5 @@ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var chokidar = require("chokidar"); var debug = require("debug")("FileSensorAccessory"); var crypto = require("crypto"); diff --git a/accessories/GenericRS232Device.js b/accessories/GenericRS232Device.js index b84e4cc..01fe80c 100644 --- a/accessories/GenericRS232Device.js +++ b/accessories/GenericRS232Device.js @@ -1,5 +1,5 @@ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var SerialPort = require("serialport").SerialPort; module.exports = { diff --git a/accessories/HomeMaticWindow.js b/accessories/HomeMaticWindow.js index b7e585d..865b24a 100644 --- a/accessories/HomeMaticWindow.js +++ b/accessories/HomeMaticWindow.js @@ -1,5 +1,5 @@ var types = require("HAP-NodeJS/accessories/types.js"); -var Characteristic = require("HAP-NodeJS").Characteristic; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); function HomeMaticWindow(log, config) { diff --git a/accessories/Http.js b/accessories/Http.js index e1859cf..fb708e3 100644 --- a/accessories/Http.js +++ b/accessories/Http.js @@ -1,5 +1,5 @@ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); module.exports = { diff --git a/accessories/HttpHygrometer.js b/accessories/HttpHygrometer.js index 61ad3b9..f722fe8 100644 --- a/accessories/HttpHygrometer.js +++ b/accessories/HttpHygrometer.js @@ -1,5 +1,5 @@ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); module.exports = { diff --git a/accessories/HttpThermometer.js b/accessories/HttpThermometer.js index ac9bdc2..9235a57 100644 --- a/accessories/HttpThermometer.js +++ b/accessories/HttpThermometer.js @@ -1,5 +1,5 @@ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); module.exports = { diff --git a/accessories/Lockitron.js b/accessories/Lockitron.js index 8088d14..ed95b7e 100644 --- a/accessories/Lockitron.js +++ b/accessories/Lockitron.js @@ -1,5 +1,5 @@ -var Service = require('HAP-NodeJS').Service; -var Characteristic = require('HAP-NodeJS').Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); module.exports = { diff --git a/accessories/WeMo.js b/accessories/WeMo.js index b97b041..5cbc3f4 100644 --- a/accessories/WeMo.js +++ b/accessories/WeMo.js @@ -1,5 +1,5 @@ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var wemo = require('wemo'); module.exports = { diff --git a/accessories/iControl.js b/accessories/iControl.js index d948867..a4299b5 100644 --- a/accessories/iControl.js +++ b/accessories/iControl.js @@ -1,6 +1,6 @@ var iControl = require('node-icontrol').iControl; -var Service = require('HAP-NodeJS').Service; -var Characteristic = require('HAP-NodeJS').Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; module.exports = { accessory: iControlAccessory diff --git a/accessories/knxdevice.js b/accessories/knxdevice.js index 6aa4ffd..92bb88c 100644 --- a/accessories/knxdevice.js +++ b/accessories/knxdevice.js @@ -21,8 +21,8 @@ New 2015-10-07: - Accept uuid_base parameter from config.json to use as unique identifier in UUIDs instead of name (optional) * */ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var knxd = require("eibd"); var knxd_registerGA = require('../platforms/KNX.js').registerGA; var knxd_startMonitor = require('../platforms/KNX.js').startMonitor; diff --git a/accessories/mpdclient.js b/accessories/mpdclient.js index 8fee5eb..ac6bf5d 100644 --- a/accessories/mpdclient.js +++ b/accessories/mpdclient.js @@ -1,5 +1,5 @@ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); var komponist = require('komponist') diff --git a/app.js b/app.js index 126b7e3..ffc203e 100644 --- a/app.js +++ b/app.js @@ -1,13 +1,13 @@ var fs = require('fs'); var path = require('path'); var storage = require('node-persist'); -var hap = require('HAP-NodeJS'); -var uuid = require('HAP-NodeJS').uuid; -var Bridge = require('HAP-NodeJS').Bridge; -var Accessory = require('HAP-NodeJS').Accessory; -var Service = require('HAP-NodeJS').Service; -var Characteristic = require('HAP-NodeJS').Characteristic; -var accessoryLoader = require('HAP-NodeJS').AccessoryLoader; +var hap = require("hap-nodejs"); +var uuid = require("hap-nodejs").uuid; +var Bridge = require("hap-nodejs").Bridge; +var Accessory = require("hap-nodejs").Accessory; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; +var accessoryLoader = require("hap-nodejs").AccessoryLoader; var once = require('HAP-NodeJS/lib/util/once').once; console.log("Starting HomeBridge server..."); diff --git a/package.json b/package.json index feb0370..95db873 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "color": "0.10.x", "eibd": "^0.3.1", "elkington": "kevinohara80/elkington", - "hap-nodejs": "git+https://github.com/KhaosT/HAP-NodeJS#4650e771f356a220868d873d16564a6be6603ff7", + "hap-nodejs": "git+https://github.com/KhaosT/HAP-NodeJS#215a3bb1d603097d63ba73d4f7d731813c6b87e5", "harmonyhubjs-client": "^1.1.4", "harmonyhubjs-discover": "git+https://github.com/swissmanu/harmonyhubjs-discover.git", "lifx-api": "^1.0.1", diff --git a/platforms/FHEM.js b/platforms/FHEM.js index 03f3ee1..7ef5e37 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -16,8 +16,8 @@ // When you attempt to add a device, it will ask for a "PIN code". // The default code for all HomeBridge accessories is 031-45-154. -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var types = require('HAP-NodeJS/accessories/types.js'); diff --git a/platforms/FibaroHC2.js b/platforms/FibaroHC2.js index 4567b40..8293645 100644 --- a/platforms/FibaroHC2.js +++ b/platforms/FibaroHC2.js @@ -15,8 +15,8 @@ // The default code for all HomeBridge accessories is 031-45-154. var types = require("HAP-NodeJS/accessories/types.js"); -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); function FibaroHC2Platform(log, config){ diff --git a/platforms/HomeAssistant.js b/platforms/HomeAssistant.js index 61d3bc2..1bfdc6c 100644 --- a/platforms/HomeAssistant.js +++ b/platforms/HomeAssistant.js @@ -67,8 +67,8 @@ // When you attempt to add a device, it will ask for a "PIN code". // The default code for all HomeBridge accessories is 031-45-154. -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var url = require('url') var request = require("request"); diff --git a/platforms/HomeSeer.js b/platforms/HomeSeer.js index c4ccde7..ac0bfa0 100644 --- a/platforms/HomeSeer.js +++ b/platforms/HomeSeer.js @@ -139,8 +139,8 @@ // - Door -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var request = require("request"); diff --git a/platforms/LIFx.js b/platforms/LIFx.js index 79988eb..89de156 100644 --- a/platforms/LIFx.js +++ b/platforms/LIFx.js @@ -16,8 +16,8 @@ // The default code for all HomeBridge accessories is 031-45-154. // -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var lifxRemoteObj = require('lifx-api'); var lifx_remote; diff --git a/platforms/MiLight.js b/platforms/MiLight.js index 3869e74..0325352 100644 --- a/platforms/MiLight.js +++ b/platforms/MiLight.js @@ -47,8 +47,8 @@ TODO: */ -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var Milight = require('node-milight-promise').MilightController; var commands = require('node-milight-promise').commands; diff --git a/platforms/YamahaAVR.js b/platforms/YamahaAVR.js index 0dde24f..be73b77 100644 --- a/platforms/YamahaAVR.js +++ b/platforms/YamahaAVR.js @@ -1,8 +1,8 @@ var types = require("HAP-NodeJS/accessories/types.js"); var inherits = require('util').inherits; var debug = require('debug')('YamahaAVR'); -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var Yamaha = require('yamaha-nodejs'); var Q = require('q'); var mdns = require('mdns'); diff --git a/platforms/ZWayServer.js b/platforms/ZWayServer.js index e50336e..9800ab6 100644 --- a/platforms/ZWayServer.js +++ b/platforms/ZWayServer.js @@ -1,6 +1,6 @@ var debug = require('debug')('ZWayServer'); -var Service = require("HAP-NodeJS").Service; -var Characteristic = require("HAP-NodeJS").Characteristic; +var Service = require("hap-nodejs").Service; +var Characteristic = require("hap-nodejs").Characteristic; var types = require("HAP-NodeJS/accessories/types.js"); var request = require("request"); var tough = require('tough-cookie'); From 23f190e3d8d82d0663a996d30cca1ec655800ea7 Mon Sep 17 00:00:00 2001 From: stipus Date: Fri, 16 Oct 2015 13:00:18 +0200 Subject: [PATCH 50/81] GarageDoorOpener and Lock support - Smoke sensor battery fix - Added offEventGroup && offEventName to events (turn on launches one HS event. turn off can launch another HS event) - Added GarageDoorOpener support - Added Lock support --- platforms/HomeSeer.js | 253 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 239 insertions(+), 14 deletions(-) diff --git a/platforms/HomeSeer.js b/platforms/HomeSeer.js index c4ccde7..1ebf307 100644 --- a/platforms/HomeSeer.js +++ b/platforms/HomeSeer.js @@ -1,29 +1,34 @@ 'use strict'; // -// HomeSeer Platform Shim for HomeBridge -// V0.1 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/07 +// HomeSeer Platform Shim for HomeBridge by Jean-Michel Joudrier - (stipus at stipus dot com) +// V0.1 - 2015/10/07 // - Initial version -// V0.2 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/10 +// V0.2 - 2015/10/10 // - Occupancy sensor fix -// V0.3 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/11 +// V0.3 - 2015/10/11 // - Added TemperatureUnit=F|C option to temperature sensors // - Added negative temperature support to temperature sensors -// V0.4 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/12 +// V0.4 - 2015/10/12 // - Added thermostat support -// V0.5 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/12 +// V0.5 - 2015/10/12 // - Added Humidity sensor support -// V0.6 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/12 +// V0.6 - 2015/10/12 // - Added Battery support // - Added low battery support for all sensors // - Added HomeSeer event support (using HomeKit switches...) -// V0.7 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/13 +// V0.7 - 2015/10/13 // - You can add multiple HomeKit devices for the same HomeSeer device reference // - Added CarbonMonoxide sensor // - Added CarbonDioxide sensor // - Added onValues option to all binary sensors -// V0.8 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/14 +// V0.8 - 2015/10/14 // - Added uuid_base parameter to all accessories +// V0.9 - 2015/10/16 +// - Smoke sensor battery fix +// - Added offEventGroup && offEventName to events (turn on launches one HS event. turn off can launch another HS event) +// - Added GarageDoorOpener support +// - Added Lock support // // // Remember to add platform to config.json. @@ -41,7 +46,9 @@ // "events":[ // Optional - List of Events - Currently they are imported into HomeKit as switches // { // "eventGroup":"My Group", // Required - The HomeSeer event group -// "eventName":"My Event", // Required - The HomeSeer event name +// "eventName":"My On Event", // Required - The HomeSeer event name +// "offEventGroup":"My Group", // Optional - The HomeSeer event group for turn-off +// "offEventName":"My Off Event", // Optional - The HomeSeer event name for turn-off // "name":"Test", // Optional - HomeSeer event name is the default // "uuid_base":"SomeUniqueId" // Optional - HomeKit identifier will be derived from this parameter instead of the name // } @@ -55,7 +62,7 @@ // "offValue":"0", // Optional - 0 is the default // "onValue":"100", // Optional - 100 is the default // "can_dim":true, // Optional - true is the default - false for a non dimmable lightbulb -// "uuid_base":"SomeUniqueId2" // Optional - HomeKit identifier will be derived from this parameter instead of the name +// "uuid_base":"SomeUniqueId2" // Optional - HomeKit identifier will be derived from this parameter instead of the name. You SHOULD add this parameter to all accessories ! // }, // { // "ref":9 // This is a dimmable Lightbulb by default @@ -109,6 +116,38 @@ // "heatingThresholdRef":170 // Optional - Not-implemented-yet - HomeSeer device reference for your thermostat heating threshold // }, // { +// "ref":200, // Required - HomeSeer Device Reference of a garage door opener +// "type":"GarageDoorOpener", // Required for a Garage Door Opener +// "name":"Garage Door", // Optional - HomeSeer device name is the default +// "stateRef":201, // Required - HomeSeer device reference for your garage door opener current state (can be the same as ref) +// "stateOpenValues":[0], // Required - List of the HomeSeer device values for a HomeKit state=OPEN +// "stateClosedValues":[1], // Required - List of the HomeSeer device values for a HomeKit state=CLOSED +// "stateOpeningValues":[2], // Optional - List of the HomeSeer device values for a HomeKit state=OPENING +// "stateClosingValues":[3], // Optional - List of the HomeSeer device values for a HomeKit state=CLOSING +// "stateStoppedValues":[4], // Optional - List of the HomeSeer device values for a HomeKit state=STOPPED +// "controlRef":201, // Required - HomeSeer device reference for your garage door opener control (can be the same as ref and stateRef) +// "controlOpenValue":0, // Required - HomeSeer device control value for OPEN +// "controlCloseValue":1, // Required - HomeSeer device control value for CLOSE +// "obstructionRef":201, // Optional - HomeSeer device reference for your garage door opener obstruction state (can be the same as ref) +// "obstructionValues":[5], // Optional - List of the HomeSeer device values for a HomeKit obstruction state=OBSTRUCTION +// "lockRef":202, // Optional - HomeSeer device reference for your garage door lock (can be the same as ref) +// "lockUnsecuredValues":[0], // Optional - List of the HomeSeer device values for a HomeKit lock state=UNSECURED +// "lockSecuredValues":[1], // Optional - List of the HomeSeer device values for a HomeKit lock state=SECURED +// "lockJammedValues":[2], // Optional - List of the HomeSeer device values for a HomeKit lock state=JAMMED +// "unlockValue":0, // Optional - HomeSeer device control value to unlock the garage door opener +// "lockValue":1 // Optional - HomeSeer device control value to lock the garage door opener +// }, +// { +// "ref":210, // Required - HomeSeer Device Reference of a Lock +// "type":"Lock", // Required for a Lock +// "name":"Main Door Lock", // Optional - HomeSeer device name is the default +// "lockUnsecuredValues":[0], // Required - List of the HomeSeer device values for a HomeKit lock state=UNSECURED +// "lockSecuredValues":[1], // Required - List of the HomeSeer device values for a HomeKit lock state=SECURED +// "lockJammedValues":[2], // Optional - List of the HomeSeer device values for a HomeKit lock state=JAMMED +// "unlockValue":0, // Required - HomeSeer device control value to unlock +// "lockValue":1 // Required - HomeSeer device control value to lock +// }, +// { // "ref":115, // Required - HomeSeer Device Reference for a device holding battery level (0-100) // "type":"Battery", // Required for a Battery // "name":"Roomba battery", // Optional - HomeSeer device name is the default @@ -136,6 +175,8 @@ // - CarbonMonoxideSensor (onValues, batteryRef, batteryThreshold options) // - CarbonDioxideSensor (onValues, batteryRef, batteryThreshold options) // - Battery (batteryThreshold option) +// - GarageDoorOpener (state, control, obstruction, lock options) +// - Lock (unsecured, secured, jammed options) // - Door @@ -520,6 +561,143 @@ HomeSeerAccessory.prototype = { }.bind(this)); }, + getCurrentDoorState: function(callback) { + var ref = this.config.stateRef; + var url = this.access_url + "request=getstatus&ref=" + ref; + + httpRequest(url, 'GET', function(error, response, body) { + if (error) { + this.log('HomeSeer get current door state function failed: %s', error.message); + callback( error, 0 ); + } + else { + var status = JSON.parse( body ); + var value = status.Devices[0].value; + + this.log('HomeSeer get target door state function succeeded: value=' + value ); + if( this.config.stateOpenValues.indexOf(value) != -1 ) + callback( null, 0 ); + else if( this.config.stateClosedValues.indexOf(value) != -1 ) + callback( null, 1 ); + else if( this.config.stateOpeningValues && this.config.stateOpeningValues.indexOf(value) != -1 ) + callback( null, 2 ); + else if( this.config.stateClosingValues && this.config.stateClosingValues.indexOf(value) != -1 ) + callback( null, 3 ); + else if( this.config.stateStoppedValues && this.config.stateStoppedValues.indexOf(value) != -1 ) + callback( null, 4 ); + else { + this.log( "Error: value for current door state not in stateO0penValues, stateClosedValues, stateOpeningValues, stateClosingValues, stateStoppedValues" ); + callback( null, 0 ); + } + } + }.bind(this)); + }, + + setTargetDoorState: function(state, callback) { + this.log("Setting target door state state to %s", state); + + var ref = this.config.controlRef; + var value = 0; + if( state == 0 ) + value = this.config.controlOpenValue; + else if( state == 1 ) + value = this.config.controlCloseValue; + + var url = this.access_url + "request=controldevicebyvalue&ref=" + ref + "&value=" + value; + httpRequest(url, 'GET', function(error, response, body) { + if (error) { + this.log('HomeSeer set target door state function failed: %s', error.message); + callback(error); + } + else { + this.log('HomeSeer set target door state function succeeded!'); + callback(); + } + }.bind(this)); + }, + + getObstructionDetected: function(callback) { + if( this.config.obstructionRef ) { + var ref = this.config.obstructionRef; + var url = this.access_url + "request=getstatus&ref=" + ref; + + httpRequest(url, 'GET', function(error, response, body) { + if (error) { + this.log('HomeSeer get obstruction detected function failed: %s', error.message); + callback( error, 0 ); + } + else { + var status = JSON.parse( body ); + var value = status.Devices[0].value; + + this.log('HomeSeer get obstruction detected function succeeded: value=' + value ); + if( this.config.obstructionValues && this.config.obstructionValues.indexOf(value) != -1 ) + callback( null, 1 ); + else { + callback( null, 0 ); + } + } + }.bind(this)); + } + else { + callback( null, 0 ); + } + }, + + getLockCurrentState: function(callback) { + var ref = this.config.lockRef; + var url = this.access_url + "request=getstatus&ref=" + ref; + + httpRequest(url, 'GET', function(error, response, body) { + if (error) { + this.log('HomeSeer get lock current state function failed: %s', error.message); + callback( error, 3 ); + } + else { + var status = JSON.parse( body ); + var value = status.Devices[0].value; + + this.log('HomeSeer get lock current state function succeeded: value=' + value ); + if( this.config.lockUnsecuredValues && this.config.lockUnsecuredValues.indexOf(value) != -1 ) + callback( null, 0 ); + else if( this.config.lockSecuredValues && this.config.lockSecuredValues.indexOf(value) != -1 ) + callback( null, 1 ); + else if( this.config.lockJammedValues && this.config.lockJammedValues.indexOf(value) != -1 ) + callback( null, 2 ); + else { + callback( null, 3 ); + } + } + }.bind(this)); + }, + + setLockTargetState: function(state, callback) { + this.log("Setting target lock state state to %s", state); + + var ref = this.config.lockRef; + var value = 0; + if( state == 0 && this.config.unlockValue ) + value = this.config.unlockValue; + else if( state == 1 && this.config.lockValue ) + value = this.config.lockValue; + + var url = this.access_url + "request=controldevicebyvalue&ref=" + ref + "&value=" + value; + httpRequest(url, 'GET', function(error, response, body) { + if (error) { + this.log('HomeSeer set target lock state function failed: %s', error.message); + callback(error); + } + else { + this.log('HomeSeer set target lock state function succeeded!'); + callback(); + } + }.bind(this)); + }, + + getPositionState: function(callback) { + callback( null, 2 ); // Temporarily return STOPPED. TODO: full door support + }, + getServices: function() { var services = [] @@ -675,7 +853,7 @@ HomeSeerAccessory.prototype = { .getCharacteristic(Characteristic.SmokeDetected) .on('get', this.getBinarySensorState.bind(this)); if( this.config.batteryRef ) { - temperatureSensorService + smokeSensorService .addCharacteristic(new Characteristic.StatusLowBattery()) .on('get', this.getLowBatteryStatus.bind(this)); } @@ -716,6 +894,9 @@ HomeSeerAccessory.prototype = { doorService .getCharacteristic(Characteristic.TargetPosition) .on('set', this.setValue.bind(this)); + doorService + .getCharacteristic(Characteristic.PositionState) + .on('get', this.getPositionState.bind(this)); services.push( doorService ); break; } @@ -759,6 +940,41 @@ HomeSeerAccessory.prototype = { services.push( thermostatService ); break; } + case "GarageDoorOpener": { + var garageDoorOpenerService = new Service.GarageDoorOpener(); + garageDoorOpenerService + .getCharacteristic(Characteristic.CurrentDoorState) + .on('get', this.getCurrentDoorState.bind(this)); + garageDoorOpenerService + .getCharacteristic(Characteristic.TargetDoorState) + .on('set', this.setTargetDoorState.bind(this)); + garageDoorOpenerService + .getCharacteristic(Characteristic.ObstructionDetected) + .on('get', this.getObstructionDetected.bind(this)); + if( this.config.lockRef ) { + garageDoorOpenerService + .addCharacteristic(new Characteristic.LockCurrentState()) + .on('get', this.getLockCurrentState.bind(this)); + garageDoorOpenerService + .addCharacteristic(new Characteristic.LockTargetState()) + .on('set', this.setLockTargetState.bind(this)); + } + services.push( garageDoorOpenerService ); + break; + } + case "Lock": { + this.config.lockRef = this.ref; + var lockService = new Service.LockMechanism(); + lockService + .getCharacteristic(Characteristic.LockCurrentState) + .on('get', this.getLockCurrentState.bind(this)); + lockService + .getCharacteristic(Characteristic.LockTargetState) + .on('set', this.setLockTargetState.bind(this)); + services.push( lockService ); + break; + } + default:{ var lightbulbService = new Service.Lightbulb(); lightbulbService @@ -787,7 +1003,11 @@ function HomeSeerEvent(log, platformConfig, eventConfig ) { this.model = "HomeSeer Event"; this.access_url = platformConfig["host"] + "/JSON?"; - this.launch_url = this.access_url + "request=runevent&group=" + encodeURIComponent(this.config.eventGroup) + "&name=" + encodeURIComponent(this.config.eventName); + this.on_url = this.access_url + "request=runevent&group=" + encodeURIComponent(this.config.eventGroup) + "&name=" + encodeURIComponent(this.config.eventName); + + if( this.config.offEventGroup && this.config.offEventName ) { + this.off_url = this.access_url + "request=runevent&group=" + encodeURIComponent(this.config.offEventGroup) + "&name=" + encodeURIComponent(this.config.offEventName); + } if( this.config.name ) this.name = this.config.name; @@ -805,7 +1025,12 @@ HomeSeerEvent.prototype = { launchEvent: function(value, callback) { this.log("Setting event value to %s", value); - httpRequest(this.launch_url, 'GET', function(error, response, body) { + var url = this.on_url; + if( value == 0 && this.off_url ) { + url = this.off_url; + } + + httpRequest(url, 'GET', function(error, response, body) { if (error) { this.log('HomeSeer run event function failed: %s', error.message); callback(error); From c22c14584dab3b8d592184aa4852a540885f6121 Mon Sep 17 00:00:00 2001 From: Mike Riccio Date: Fri, 16 Oct 2015 09:36:18 -0700 Subject: [PATCH 51/81] 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 52/81] 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 53/81] 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 54/81] 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 55/81] 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 56/81] 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 57/81] 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 58/81] 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 59/81] 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 60/81] add async dependency for Indigo --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index ff5563b..f8bf8e4 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "license": "ISC", "dependencies": { "ad2usb": "git+https://github.com/alistairg/node-ad2usb.git#local", + "async": "^1.4.2", "carwingsjs": "0.0.x", "chokidar": "^1.0.5", "color": "0.10.x", From ad62bd4b15a954defbd3a211de588f5058db6847 Mon Sep 17 00:00:00 2001 From: Mike Riccio Date: Sat, 17 Oct 2015 20:27:04 -0700 Subject: [PATCH 61/81] 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 62/81] 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 63/81] 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 64/81] 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 65/81] 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 66/81] 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 67/81] 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 68/81] 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 69/81] 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 70/81] 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 71/81] 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 72/81] 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 73/81] 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 74/81] 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 75/81] 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 76/81] 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 77/81] 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 78/81] Fixed bug in alarm state. Was using alarm state instead of mode to translate current state. --- platforms/isy-js.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/platforms/isy-js.js b/platforms/isy-js.js index 1db30b4..172bd32 100644 --- a/platforms/isy-js.js +++ b/platforms/isy-js.js @@ -656,6 +656,7 @@ ISYElkAlarmPanelAccessory.prototype.setAlarmTargetState = function(targetStateHK ISYElkAlarmPanelAccessory.prototype.translateAlarmCurrentStateToHK = function() { var tripState = this.device.getAlarmTripState(); var sourceAlarmState = this.device.getAlarmState(); + var sourceAlarmMode = this.device.getAlarmMode(); if(tripState >= this.device.ALARM_TRIP_STATE_TRIPPED) { return Characteristic.SecuritySystemCurrentState.ALARM_TRIGGERED; @@ -664,14 +665,14 @@ ISYElkAlarmPanelAccessory.prototype.translateAlarmCurrentStateToHK = function() sourceAlarmState == this.device.ALARM_STATE_READY_TO_ARM_VIOLATION) { return Characteristic.SecuritySystemCurrentState.DISARMED; } else { - if(sourceAlarmState == this.device.ALARM_MODE_STAY || sourceAlarmState == this.device.ALARM_MODE_STAY_INSTANT ) { + if(sourceAlarmMode == this.device.ALARM_MODE_STAY || sourceAlarmMode == this.device.ALARM_MODE_STAY_INSTANT ) { return Characteristic.SecuritySystemCurrentState.STAY_ARM; - } else if(sourceAlarmState == this.device.ALARM_MODE_AWAY || sourceAlarmState == this.device.ALARM_MODE_VACATION) { + } else if(sourceAlarmMode == this.device.ALARM_MODE_AWAY || sourceAlarmMode == this.device.ALARM_MODE_VACATION) { return Characteristic.SecuritySystemCurrentState.AWAY_ARM; - } else if(sourceAlarmState == this.device.ALARM_MODE_NIGHT || sourceAlarmState == this.device.ALARM_MODE_NIGHT_INSTANT) { + } else if(sourceAlarmMode == this.device.ALARM_MODE_NIGHT || sourceAlarmMode == this.device.ALARM_MODE_NIGHT_INSTANT) { return Characteristic.SecuritySystemCurrentState.NIGHT_ARM; } else { - ISYJSDebugMessage(this,"Setting to disarmed because sourceAlarmState is "+sourceAlarmState); + ISYJSDebugMessage(this,"Setting to disarmed because sourceAlarmMode is "+sourceAlarmMode); return Characteristic.SecuritySystemCurrentState.DISARMED; } } From 13e884613898164d4abf11e06522110aad2a37fc Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Tue, 20 Oct 2015 07:04:18 +0200 Subject: [PATCH 79/81] 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 80/81] 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 81/81] 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",