From 0fb6fc736e06fd2fae7fe2e3b9cc9a2b59e0812b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Mon, 27 Jul 2015 09:38:53 +0200 Subject: [PATCH 01/15] added FHEM.js platform shim for the FHEM home automation system. supports switches, lightbulbs (dimmers and rgb/colored lights), homematic window blinds, max and homematic thermostats, homematic door&window sensors, PRESENCE and sonos (including volume) the shim is bidirectional and uses longpoll to push fhem events back to homekit. fhem events are cached so homekit querys for known values will not cause a roundtrip to fhem more: http://forum.fhem.de/index.php/topic,32652.msg314166.html#msg314166 --- platforms/FHEM.js | 1321 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1321 insertions(+) create mode 100644 platforms/FHEM.js diff --git a/platforms/FHEM.js b/platforms/FHEM.js new file mode 100644 index 0000000..9035b8f --- /dev/null +++ b/platforms/FHEM.js @@ -0,0 +1,1321 @@ +// FHEM Platform Shim for HomeBridge +// +// Remember to add platform to config.json. Example: +// "platforms": [ +// { +// "platform": "FHEM", +// "name": "FHEM", +// "server": "127.0.0.1", +// "port": "8083", +// "ssl": "true", +// "auth": {"user": "fhem", "pass": "fhempassword"}, +// "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 types = require('HAP-NodeJS/accessories/types.js'); +var util = require('util'); + + +// cached readings from longpoll & query +var FHEM_cached = {}; + + +// subscriptions to fhem longpoll evens +var FHEM_subscriptions = {}; +function +FHEM_subscribe(characteristic, inform_id, accessory) { + FHEM_subscriptions[inform_id] = { 'characteristic': characteristic, 'accessory': accessory }; + //FHEM_subscriptions[inform_id] = characteristic; +} +function +FHEM_update(inform_id, value, no_update) { + var subscription = FHEM_subscriptions[inform_id]; + if( subscription != undefined ) { + FHEM_cached[inform_id] = value; + console.log(" caching: " + inform_id + ": " + value + " as " + typeof(value) ); + + if( !no_update ) + subscription.characteristic.updateValue(value, null); + } +} + + +var FHEM_longpoll_running = false; +//FIXME: add reconnect, force reconnect on xxx bytes received, add filter, add since +function FHEM_startLongpoll(connection) { + if( FHEM_longpoll_running ) + return; + FHEM_longpoll_running = true; + + var filter = ".*"; + var since = "null"; + var query = "/fhem.pl?XHR=1"+ + "&inform=type=status;filter="+filter+";since="+since+";fmt=JSON"+ + "×tamp="+new Date().getTime(); + + var url = encodeURI( connection.base_url + query ); +console.log( 'starting longpoll: ' + url ); + + var FHEM_longpollOffset = 0; + var input = ""; + connection.request.get( { url: url } ).on( 'data', function(data) { +//console.log( 'data: '+ data ); + if( !data ) + return; + + input += data; + for(;;) { + var nOff = input.indexOf("\n", FHEM_longpollOffset); + if(nOff < 0) + break; + var l = input.substr(FHEM_longpollOffset, nOff-FHEM_longpollOffset); + FHEM_longpollOffset = nOff+1; +//console.log( "Rcvd: "+(l.length>132 ? l.substring(0,132)+"...("+l.length+")":l) ); + if(!l.length) + continue; + + var d; + if( l.substr(0,1) == '[' ) + d = JSON.parse(l); + else + d = l.split("<<", 3); + + //console.log(d); + + if(d.length != 3) + continue; + if(d[0].match(/-ts$/)) + continue; + +//console.log( "Rcvd: "+(l.length>132 ? l.substring(0,132)+"...("+l.length+")":l) ); + + //FIXME: create reading2value + //FIXME: redirect device reading to homekit reading: rgb->hue,... + var subscription = FHEM_subscriptions[d[0]]; + if( subscription != undefined ) { +//console.log( "Rcvd: "+(l.length>132 ? l.substring(0,132)+"...("+l.length+")":l) ); + var accessory = subscription.accessory; + var value = d[1]; + + if(d[0].match(/-state$/)) { + if( match = d[1].match(/dim(\d*)%/ ) ) { + var pct = parseInt( match[1] ); + + FHEM_update( d[0].replace( '-state', '-pct' ), pct ); + } + + value = 1; + if( d[1] == 'off' ) + value = 0; + else if( d[1] == '000000' ) + value = 0; + else if( d[1] == 'absent' ) + value = 0; + else if( d[1] == 'A0' ) + value = 0; + + value = parseInt(value); + + } else if(d[0].match(/-motor$/)) { + value = 2; + if( d[1].match(/^opening/)) + value = 1; + else if( d[1].match(/^up/)) + value = 1; + else if( d[1].match(/^closing/)) + value = 0; + else if( d[1].match(/^down/)) + value = 0; + + value = parseInt(value); + + } else if(d[0].match(/-transportState$/)) { + value = 0; + if( d[1] == 'PLAYING' ) + value = 1; + + value = parseInt(value); + + } else if(d[0].match(/-Volume$/)) { + value = parseInt( d[1] ); + + } else if(d[0].match(/-contact$/)) { + value = 0; + if( d[1].match( /^closed/ ) ) + value = 1; + + value = parseInt(value); + + } else if(d[0].match(/-pct$/)) { + value = parseInt( d[1] ); + + } else if(d[0].match(/-hue$/)) { + value = Math.round(d[1] * 360 / accessory.hueMax); + + } else if(d[0].match(/-sat$/)) { + result = Math.round(d[1] * 100 / accessory.satMax); + + } else if(d[0].match(/-rgb$/)) { + var hue = parseInt( FHEM_rgb2h(d[1]) * 360 ); + + FHEM_update( d[0].replace( '-rgb', '-hue' ), hue ); + + } else if(d[0].match(/-temperature$/) + || d[0].match(/-measured-temp$/) + || d[0].match(/-desired-temp$/) + || d[0].match(/-desiredTemperature$/) ) { + value = parseFloat( d[1] ); + + } else if(d[0].match(/-humidity$/)) { + value = parseInt( d[1] ); + + } + + FHEM_update( d[0], value ); + } + + } + + input = input.substr(FHEM_longpollOffset); + FHEM_longpollOffset = 0; + + } ).on( 'error', function(err) { + console.log( "longpoll error: " + err ); + + FHEM_longpoll_running = false; + setTimeout( function(){FHEM_startLongpoll(connection)}, 5000 ); + } ); +} + + +function FHEMPlatform(log, config) { + this.log = log; + this.server = config['server']; + this.port = config['port']; + this.filter = config['filter']; + + var base_url; + if( config['ssl'] ) + base_url = 'https://'; + else + base_url = 'http://'; + base_url += this.server + ':' + this.port; + + var request = require('request'); + var auth = config['auth']; + if( auth ) { + if( auth.sendImmediately == undefined ) + auth.sendImmediately = false; +console.log( "auth: "+ auth ); + + request = request.defaults( { 'auth': auth, 'rejectUnauthorized': false } ); + } + + this.connection = { 'base_url': base_url, 'request': request }; + + FHEM_startLongpoll( this.connection ); +} + +function +FHEM_sortByKey(array, key) { + return array.sort( function(a, b) { + var x = a[key]; var y = b[key]; + return ((x < y) ? -1 : ((x > y) ? 1 : 0)); + }); +} + +function +FHEM_rgb2hex(r,g,b) { + if( g == undefined ) + return Number(0x1000000 + r[0]*0x10000 + r[1]*0x100 + r[2]).toString(16).substring(1); + + return Number(0x1000000 + r*0x10000 + g*0x100 + b).toString(16).substring(1); +} + +function +FHEM_hsv2rgb(h,s,v) { + var r = 0.0; + var g = 0.0; + var b = 0.0; + + if( s == 0 ) { + r = v; + g = v; + b = v; + + } else { + var i = Math.floor( h * 6.0 ); + var f = ( h * 6.0 ) - i; + var p = v * ( 1.0 - s ); + var q = v * ( 1.0 - s * f ); + var t = v * ( 1.0 - s * ( 1.0 - f ) ); + i = i % 6; + + if( i == 0 ) { + r = v; + g = t; + b = p; + } else if( i == 1 ) { + r = q; + g = v; + b = p; + } else if( i == 2 ) { + r = p; + g = v; + b = t; + } else if( i == 3 ) { + r = p; + g = q; + b = v; + } else if( i == 4 ) { + r = t; + g = p; + b = v; + } else if( i == 5 ) { + r = v; + g = p; + b = q; + } + } + + return FHEM_rgb2hex( Math.round(r*255),Math.round(g*255),Math.round(b*255) ); +} + +function +FHEM_rgb2h(r,g,b){ + if( r == undefined ) + return; + + if( g == undefined ) { + var str = r; + r = parseInt( str.substr(0,2), 16 ); + g = parseInt( str.substr(2,2), 16 ); + b = parseInt( str.substr(4,2), 16 ); + } + + var M = Math.max( r, g, b ); + var m = Math.min( r, g, b ); + var c = M - m; + + var h, s, v; + if( c == 0 ) { + h = 0; + } else if( M == r ) { + h = ( 60 * ( ( g - b ) / c ) % 360 ) / 360; + } else if( M == g ) { + h = ( 60 * ( ( b - r ) / c ) + 120 ) / 360; + } else if( M == b ) { + h = ( 60 * ( ( r - g ) / c ) + 240 ) / 360; + } + + return h; + + if( M == 0 ) { + s = 0; + } else { + s = c / M; + } + + v = M; + + return h; +} + + +FHEMPlatform.prototype = { + accessories: function(callback) { + this.log("Fetching FHEM switchable devices..."); + + var foundAccessories = []; + + var cmd = 'jsonlist2'; + if( this.filter ) + cmd += " " + this.filter; + var url = encodeURI( this.connection.base_url + "/fhem?cmd=" + cmd + "&XHR=1"); + this.log( 'fetching: ' + url ); + + 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) ); + if( json['totalResultsReturned'] ) { + var sArray=FHEM_sortByKey(json['Results'],"Name"); + sArray.map(function(s) { + if( s.Attributes.disable == 1 ) { + that.log( s.Internals.NAME + ' is disabled'); + + } else if( s.Internals.TYPE == 'structure' ) { + that.log( s.Internals.NAME + ' is a structure'); + + } else if( s.PossibleSets.match(/\bon\b/) + && s.PossibleSets.match(/\boff\b/) ) { + accessory = new FHEMAccessory(that.log, that.connection, s); + foundAccessories.push(accessory); + + } else if( s.PossibleSets.match(/\bvolume\b/) ) { + that.log( s.Internals.NAME + ' has volume'); + accessory = new FHEMAccessory(that.log, that.connection, s); + foundAccessories.push(accessory); + + } else if( s.Attributes.genericDisplayType + || s.Attributes.genericDeviceType ) { + accessory = new FHEMAccessory(that.log, that.connection, s); + foundAccessories.push(accessory); + + } else if( s.Attributes.subType == 'thermostat' + || s.Attributes.subType == 'blindActuator' + || s.Attributes.subType == 'threeStateSensor' ) { + accessory = new FHEMAccessory(that.log, that.connection, s); + foundAccessories.push(accessory); + + } else if( s.Internals.TYPE == 'PRESENCE' ) { + accessory = new FHEMAccessory(that.log, that.connection, s); + foundAccessories.push(accessory); + + + } else if( s.Readings.temperature ) { + accessory = new FHEMAccessory(that.log, that.connection, s); + foundAccessories.push(accessory); + + } else if( s.Readings.humidity ) { + accessory = new FHEMAccessory(that.log, that.connection, s); + foundAccessories.push(accessory); + + } else { + that.log( 'ignoring ' + s.Internals.NAME ); + + } + }); + } + callback(foundAccessories); + + } else { + that.log("There was a problem connecting to FHEM (1)."); + if( response ) + that.log( " " + response.statusCode + ": " + response.statusMessage ); + + } + + }); + } +} + +function +FHEMAccessory(log, connection, s) { +//log( 'sets: ' + s.PossibleSets ); +//log("got json: " + util.inspect(s) ); +//log("got json: " + util.inspect(s.Internals) ); + + //FIXME: replace hasPct(true/false) by hasBri(reading) + var match; + if( match = s.PossibleSets.match(/\bpct\b/) ) { + s.hasPct = true; + s.pctMax = 100; + } else if( match = s.PossibleSets.match(/\bdim\d*%/) ) { + s.hasDim = true; + s.pctMax = 100; + } + if( match = s.PossibleSets.match(/\bhue[^\b\s]*(,(\d*)?)+\b/) ) { + s.isLight = true; + s.hasHue = true; + s.hueMax = 360; + if( match[2] != undefined ) + s.hueMax = match[2]; + } + if( match = s.PossibleSets.match(/\bsat[^\b\s]*(,(\d*)?)+\b/) ) { + s.isLight = true; + s.hasSat = true; + s.satMax = 100; + if( match[2] != undefined ) + s.satMax = match[2]; + } + if( s.PossibleSets.match(/\brgb\b/) ) { + s.isLight = true; + s.hasRGB = true; + } + + if( s.Readings['measured-temp'] ) + s.hasTemperature = "measured-temp"; + else if( s.Readings.temperature ) + s.hasTemperature = "temperature"; + + if( s.Readings.humidity ) + s.hasHumidity = true; + + if( s.Readings.motor ) + s.hasMotor = 'motor'; + + + var genericType = s.Attributes.genericDeviceType; + if( !genericType ) + genericType = s.Attributes.genericDisplayType; + + if( genericType == 'light' ) + s.isLight = true; + else if( genericType == 'blind' ) { + s.hasPct = false; + s.isBlind = 'pct'; + } else if( genericType == 'thermostat' ) + s.isThermostat = true; + else if( s.Attributes.subType == 'thermostat' ) + s.isThermostat = true; + else if( s.Attributes.subType == 'blindActuator' ) { + s.hasPct = false; + s.isBlind = 'pct'; + } else if( s.Attributes.subType == 'threeStateSensor' ) { + s.isContactSensor = true; + if( s.Attributes.model == 'HM-SEC-RHS' ) + s.isWindow = true; + } else if( s.Internals.TYPE == 'PRESENCE' ) + s.isOccupancySensor = true; + //else if( s.PossibleSets.match(/\bon\b/) + // && s.PossibleSets.match(/\boff\b/) ) + // s.isSwitch = true; + + if( s.PossibleSets.match(/\bdesired-temp\b/) ) + s.isThermostat = 'desired-temp'; + else if( s.PossibleSets.match(/\bdesiredTemperature\b/) ) + s.isThermostat = 'desiredTemperature'; + + if( s.hasHue ) + log( s.Internals.NAME + ' has hue [0-' + s.hueMax +']' ); + else if( s.hasRGB ) + log( s.Internals.NAME + ' has RGB'); + else if( s.hasPct ) + log( s.Internals.NAME + ' is dimable [0-'+ s.pctMax +']' ); + else if( s.hasDim ) + log( s.Internals.NAME + ' is dimable [0-'+ s.pctMax +']' ); + else if( s.isThermostat ) + log( s.Internals.NAME + ' is thermostat ['+ s.isThermostat +']' ); + else if( s.isContactSensor ) + log( s.Internals.NAME + ' is contactsensor' ); + else if( s.isOccupancySensor ) + log( s.Internals.NAME + ' is occupancysensor' ); + else if( s.isBlind ) + log( s.Internals.NAME + ' is blind ['+ s.isBlind +']' ); + else if( s.isLight ) + log( s.Internals.NAME + ' is light' ); + else + log( s.Internals.NAME + ' is switchable' ); + + if( s.hasTemperature ) + log( s.Internals.NAME + ' has temperature ['+ s.hasTemperature +']' ); + if( s.hasHumidity ) + log( s.Internals.NAME + ' has humidity' ); + if( s.hasMotor ) + log( s.Internals.NAME + ' has motor' ); + + //FIXME: create redirectReading() / redirectSet(): on/off -> play/pause + + // device info + this.name = s.Internals.NAME; + this.alias = s.Attributes.alias ? s.Attributes.alias : s.Internals.NAME; + this.device = s.Internals.NAME; + this.type = s.Internals.TYPE; + this.model = s.Attributes.model ? s.Attributes.model : s.Internals.model; + this.PossibleSets = s.PossibleSets; + + if( this.type == 'CUL_HM' ) { + this.serial = s.Internals.DEF; + if( s.Attributes.serialNr ) + this.serial = s.Attributes.serialNr; + } else if( this.type == 'CUL_WS' ) + this.serial = s.Internals.DEF; + else if( this.type == 'FS20' ) + this.serial = s.Internals.DEF; + else if( this.type == 'IT' ) + this.serial = s.Internals.DEF; + else if( this.type == 'HUEDevice' ) + this.serial = s.Internals.uniqueid; + else if( this.type == 'SONOSPLAYER' ) + this.serial = s.Internals.UDN; + + this.hasPct = s.hasPct; + this.hasDim = s.hasDim; + this.pctMax = s.pctMax; + this.hasHue = s.hasHue; + this.hueMax = s.hueMax; + this.hasSat = s.hasSat; + this.satMax = s.satMax; + this.hasRGB = s.hasRGB; + + this.hasTemperature = s.hasTemperature; + this.hasHumidity = s.hasHumidity; + this.hasMotor = s.hasMotor; + + this.isLight = s.isLight; + this.isBlind = s.isBlind; + this.isThermostat = s.isThermostat; + this.isContactSensor = s.isContactSensor; + this.isOccupancySensor = s.isOccupancySensor; + this.isWindow = s.isWindow; + +//log( util.inspect(s.Readings) ); + + this.log = log; + this.connection = connection; +} + +FHEM_dim_values = [ 'dim06%', 'dim12%', 'dim18%', 'dim25%', 'dim31%', 'dim37%', 'dim43%', 'dim50%', 'dim56%', 'dim62%', 'dim68%', 'dim75%', 'dim81%', 'dim87%', 'dim93%' ]; + +FHEMAccessory.prototype = { + delayed: function(c,value,delay) { + var timer = this.delayed[c]; + if( timer ) { + //this.log(this.name + " removing old command " + c); + clearTimeout( timer ); + } + + 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 ); + }, + + command: function(c,value) { + this.log(this.name + " sending command " + c + " with value " + value); + if( c == 'on' ) { + if( this.PossibleSets.match(/\bplay\b/i) ) + url = this.connection.base_url + "/fhem?cmd=set " + this.device + " play&XHR=1"; + else if( this.PossibleSets.match(/\bon\b/) ) + url = this.connection.base_url + "/fhem?cmd=set " + this.device + " on&XHR=1"; + else + this.log(this.name + " Unhandled command! cmd=" + c + ", value=" + value); + + } else if( c == 'off' ) { + if( this.PossibleSets.match(/\bpause\b/i) ) + url = this.connection.base_url + "/fhem?cmd=set " + this.device + " pause&XHR=1"; + else if( this.PossibleSets.match(/\boff\b/) ) + url = this.connection.base_url + "/fhem?cmd=set " + this.device + " off&XHR=1"; + else + this.log(this.device + " Unhandled command! cmd=" + c + ", value=" + value); + + } else if( c == 'volume' ) { + url = this.connection.base_url + "/fhem?cmd=set " + this.device + " volume " + value + "&XHR=1"; + + } else if( c == 'pct' ) { + url = this.connection.base_url + "/fhem?cmd=set " + this.device + " pct " + value + "&XHR=1"; + + } else if( c == 'dim' ) { + //if( value < 3 ) + // url = this.connection.base_url + "/fhem?cmd=set " + this.device + " off&XHR=1"; + //else + if( value > 97 ) + url = this.connection.base_url + "/fhem?cmd=set " + this.device + " on&XHR=1"; + else + url = this.connection.base_url + "/fhem?cmd=set " + this.device + " " + FHEM_dim_values[Math.round(value/6.25)] + "&XHR=1"; + + } else if( c == 'hue' ) { + if( !this.hasHue ) { + value = FHEM_hsv2rgb( value/360.0, this.sat?this.sat/100.0:1.0, this.pct?this.pct/100.0:1.0 ); + url = this.connection.base_url + "/fhem?cmd=set " + this.device + " rgb " + value + "&XHR=1"; + + } else { + value = Math.round(value * this.hueMax / 360); + url = this.connection.base_url + "/fhem?cmd=set " + this.device + " hue " + value + "&XHR=1"; + } + + } else if( c == 'sat' ) { + value = value / 100 * this.satMax; + url = this.connection.base_url + "/fhem?cmd=set " + this.device + " sat " + value + "&XHR=1"; + + } else if( c == 'targetTemperature' ) { + url = this.connection.base_url + "/fhem?cmd=set " + this.device + " " + this.isThermostat + " " + value + "&XHR=1"; + + } else if( c == 'targetPosition' ) { + url = this.connection.base_url + "/fhem?cmd=set " + this.device + " " + this.isBlind + " " + value + "&XHR=1"; + + } else if( value != undefined ) { + this.log(this.name + " Unhandled command! cmd=" + c + ", value=" + value); + return; + + } + + var that = this; + this.connection.request.put( { url: encodeURI(url), gzip: true }, + function(err, response) { + if( err ) { + that.log("There was a problem sending command " + c + " to" + that.name); + that.log(url); + if( response ) + that.log( " " + response.statusCode + ": " + response.statusMessage ); + + } else { + that.log(that.name + " sent command " + c); + that.log(url); + + } + + } ); + }, + + query: function(reading, callback) { + this.log("query: " + reading); + + var orig_reading = reading; + + //FIXME: create redirectReading() + var rgb_to_hue = false; + if( reading == 'hue' && !this.hasHue && this.hasRGB ) { + reading = 'rgb'; + rgb_to_hue = true; + + } else if( reading == 'pct' && !this.hasPct ) { + reading = 'state'; + + } + + if( reading == 'rgb' + && this.type == 'SWAP_0000002200000003' ) { + reading = '0B-RGBlevel'; + + } + + var result = FHEM_cached[this.device + '-' + orig_reading]; + if( result != undefined ) { + this.log(" cached: " + result); + if( callback != undefined ) + callback(result); + return(result); + } else + this.log(" not cached" ); + + var cmd = '{ReadingsVal("'+this.device+'","'+reading+'","")}'; + var url = encodeURI( this.connection.base_url + "/fhem?cmd=" + cmd + "&XHR=1"); + this.log( ' querying: ' + url ); + + var that = this; + that.connection.request.get( { url: url, gzip: true }, + function(err, response, result) { + if( !err && response.statusCode == 200 ) { + result = result.replace(/[\r\n]/g, ""); + that.log(" result: " + result); + + //FIXME: create reading2value + if( rgb_to_hue ) { + result = parseInt( FHEM_rgb2h(result) * 360 ); + + } else if( reading == 'hue' ) { + result = Math.round(result * 360 / that.hueMax); + + } else if( reading == 'sat' ) { + result = Math.round(result * 100 / that.satMax); + + } else if( reading == 'pct' ) { + result = parseInt( result ); + + } else if(reading.match(/-motor$/)) { + if( result.match(/^opening/)) + result = 1; + else if( result.match(/^up/)) + result = 1; + else if( result.match(/^closing/)) + result = 0; + else if( result.match(/^down/)) + result = 0; + else + result = 2; + + result = parseInt(result); + + } else if( reading == 'transportState' ) { + if( result == 'PLAYING' ) + result = 1; + else + result = 0; + + result = parseInt(result); + + } else if( reading == 'Volume' ) { + result = parseInt( result ); + + } else if( reading == 'contact' ) { + if( result.match( /^closed/ ) ) + result = 1; + else + result = 0; + + result = parseInt(result); + + } else if( reading == 'temperature' + || reading == 'measured-temp' + || reading == 'desired-temp' + || reading == 'desiredTemperature' ) { + result = parseFloat( result ); + + } else if( reading == 'humidity' ) { + result = parseInt( result ); + + } else if( reading == 'state' ) { + if( orig_reading == 'pct' ) { + if( match = result.match(/dim(\d*)%/ ) ) { + result = match[1]; + } else if( result == 'off' ) + result = 0; + else + result = 100; + + } else if( result == 'off' ) + result = 0; + else if( result == 'absent' ) + result = 0; + else if( result == '000000' ) + result = 0; + else if( result == 'A0' ) + result = 0; + else + result = 1; + + result = parseInt( result ); + + } + that.log(" mapped: " + result); + + if( !rgb_to_hue && reading != 'transportState' && reading != '0B-RGBlevel' ) + FHEM_update( that.device + '-' + orig_reading, result, true ); + + if( callback != undefined ) + callback(result); + return(result); + + } else { + that.log("There was a problem connecting to FHEM (2)."); + if( response ) + that.log( " " + response.statusCode + ": " + response.statusMessage ); + + } + + } ); + }, + + informationCharacteristics: function() { + return [ + { + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.alias, + supportEvents: false, + supportBonjour: false, + manfDescription: "Name of the accessory", + designedMaxLength: 255 + },{ + cType: types.MANUFACTURER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: "FHEM:"+this.type, + supportEvents: false, + supportBonjour: false, + manfDescription: "Manufacturer", + designedMaxLength: 255 + },{ + cType: types.MODEL_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.model ? this.model : '', + supportEvents: false, + supportBonjour: false, + manfDescription: "Model", + designedMaxLength: 255 + },{ + cType: types.SERIAL_NUMBER_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.serial ? this.serial : "A1S2NASF88EW", + supportEvents: false, + supportBonjour: false, + manfDescription: "SN", + designedMaxLength: 255 + },{ + cType: types.IDENTIFY_CTYPE, + onUpdate: null, + perms: ["pw"], + format: "bool", + initialValue: false, + supportEvents: false, + supportBonjour: false, + manfDescription: "Identify Accessory", + designedMaxLength: 1 + } + ] + }, + + controlCharacteristics: function(that) { + cTypes = [{ + cType: types.NAME_CTYPE, + onUpdate: null, + perms: ["pr"], + format: "string", + initialValue: this.alias, + supportEvents: true, + supportBonjour: false, + manfDescription: "Name of service", + designedMaxLength: 255 + }] + + if( this.name != undefined + && !this.hasTemperature + && !this.hasHumidity + && !this.isBlind + && !this.isThermostat + && !this.isContactSensor + && !this.isOccupancySensor ) { + cTypes.push({ + cType: types.POWER_STATE_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + if( that.type == 'SONOSPLAYER' ) + FHEM_subscribe(characteristic, that.name+'-transportState', that); + else + FHEM_subscribe(characteristic, that.name+'-state', that); + }, + onUpdate: function(value) { + that.command( value == 0 ? 'off' : 'on' ); + }, + onRead: function(callback) { + that.query( that.type == 'SONOSPLAYER' ? 'transportState' : 'state', function(state){ callback(state) } ); + }, + perms: ["pw","pr","ev"], + format: "bool", + initialValue: 0, + //initialValue: that.query( that.type == 'SONOSPLAYER' ? 'transportState' : 'state' ), + supportEvents: true, + supportBonjour: false, + manfDescription: "Change the power state", + designedMaxLength: 1 + }); + } + + if( this.hasPct ) { + cTypes.push({ + cType: types.BRIGHTNESS_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.name+'-pct', that); + }, + onUpdate: function(value) { that.command('pct', value); }, + onRead: function(callback) { + that.query('pct', function(pct){ + callback(pct); + }); + }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 0, + //initialValue: that.query( 'pct' ), + supportEvents: true, + supportBonjour: false, + manfDescription: "Adjust Brightness of the Light", + designedMinValue: 0, + designedMaxValue: this.pctMax, + designedMinStep: 1, + unit: "%" + }); + } else if( this.hasDim ) { + cTypes.push({ + cType: types.BRIGHTNESS_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.name+'-pct', that); + }, + onUpdate: function(value) { that.delayed('dim', value); }, + onRead: function(callback) { + that.query('pct', function(pct){ + callback(pct); + }); + }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 0, + //initialValue: that.query( 'state' ), + supportEvents: true, + supportBonjour: false, + manfDescription: "Adjust Brightness of the Light", + designedMinValue: 0, + designedMaxValue: this.pctMax, + designedMinStep: 1, + unit: "%" + }); + } + + if( this.hasHue == true || this.hasRGB == true ) { + cTypes.push({ + cType: types.HUE_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.name+'-hue', that); + }, + onUpdate: function(value) { that.command('hue', value); }, + onRead: function(callback) { + that.query('hue', function(hue){ + callback(hue); + }); + }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Adjust the Hue of the Light", + designedMinValue: 0, + designedMaxValue: 360, + designedMinStep: 1, + unit: "arcdegrees" + }); + } + + if( this.hasSat == true ) { + cTypes.push({ + cType: types.SATURATION_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.name+'-sat', that); + }, + onUpdate: function(value) { that.command('sat', value); }, + onRead: function(callback) { + that.query('sat', function(sat){ + callback(sat); + }); + }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 100, + supportEvents: true, + supportBonjour: false, + manfDescription: "Adjust the Saturation of the Light", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }); + } + + //FIXME: parse range and set designedMinValue & designedMaxValue & designedMinStep + if( match = this.PossibleSets.match(/\bVolume\b/) ) { + cTypes.push({ + cType: types.OUTPUTVOLUME_CTYPE, + onUpdate: function(value) { that.delayed('volume', value); }, + onRegister: function(characteristic) { + //characteristic.eventEnabled = true; + //FHEM_subscribe(characteristic, that.name+'-Volume', that); + }, + onRead: function(callback) { + that.query('Volume', function(vol){ + callback(vol); + }); + }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 10, + supportEvents: true, + supportBonjour: false, + manfDescription: "Adjust the Volume of this device", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1 + //unit: "%" + }); + } + + //FIXME: parse range and set designedMinValue & designedMaxValue & designedMinStep + if( this.isBlind ) { + cTypes.push({ + cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, + onUpdate: function(value) { that.delayed('targetPosition', value, 1500); }, + //onRegister: function(characteristic) { + // characteristic.eventEnabled = true; + // FHEM_subscribe(characteristic, that.name+'-'+that.isBlind, that); + //}, + onRead: function(callback) { + that.query(that.isBlind, function(pct){ + callback(pct); + }); + }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 0, + //initialValue: that.query( that.isBlind ), + supportEvents: false, + supportBonjour: false, + manfDescription: "Target Blind Position", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }); + cTypes.push({ + cType: types.WINDOW_COVERING_CURRENT_POSITION_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.name+'-'+that.isBlind, that); + }, + onRead: function(callback) { + that.query(that.isBlind, function(pos){ + callback(pos); + }); + }, + perms: ["pr","ev"], + format: "int", + initialValue: 0, + //initialValue: that.query( that.isBlind ), + supportEvents: true, + supportBonjour: false, + manfDescription: "Current Blind Position", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }); + cTypes.push({ + cType: types.WINDOW_COVERING_OPERATION_STATE_CTYPE, + onRegister: function(characteristic) { + if( that.hasMotor ) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.name+'-'+that.hasMotor, that); + } + }, + onRead: function(callback) { + if( that.hasMotor ) + that.query(that.hasMotor, function(state){ + callback(state); + }); + }, + perms: ["pr","ev"], + format: "int", + initialValue: 2, + supportEvents: false, + supportBonjour: false, + manfDescription: "Position State", + designedMinValue: 0, + designedMaxValue: 2, + designedMinStep: 1, + }); + } + + //FIXME: parse range and set designedMinValue & designedMaxValue & designedMinStep + if( this.isThermostat ) { + cTypes.push({ + cType: types.TARGET_TEMPERATURE_CTYPE, + onUpdate: function(value) { that.delayed('targetTemperature', value, 1500); }, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.name+'-'+that.isThermostat, that); + }, + onRead: function(callback) { + that.query(that.isThermostat, function(temperature){ + callback(temperature); + }); + }, + perms: ["pw","pr","ev"], + format: "float", + initialValue: 20, + supportEvents: false, + supportBonjour: false, + manfDescription: "Target Temperature", + designedMinValue: 5.0, + designedMaxValue: 30.0, + //designedMinStep: 0.5, + unit: "celsius" + }); + cTypes.push({ + cType: types.CURRENTHEATINGCOOLING_CTYPE, + perms: ["pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Current Mode", + designedMaxLength: 1, + designedMinValue: 0, + designedMaxValue: 2, + designedMinStep: 1, + }); + cTypes.push({ + cType: types.TARGETHEATINGCOOLING_CTYPE, + onUpdate: function(value) { that.command('targetMode', value); }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Target Mode", + designedMinValue: 0, + designedMaxValue: 3, + designedMinStep: 1, + }); + + cTypes.push({ + cType: types.TEMPERATURE_UNITS_CTYPE, + perms: ["pr","ev"], + format: "int", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Unit", + }); + } + + if( this.isWindow ) { + cTypes.push({ + cType: types.CONTACT_SENSOR_STATE_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.name+'-contact', that); + }, + onRead: function(callback) { + that.query('contact', function(state){ + callback(state); + }); + }, + perms: ["pr","ev"], + format: "bool", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Contact State", + designedMaxLength: 1 + }); + } else if( this.isContactSensor ) { + cTypes.push({ + cType: types.CONTACT_SENSOR_STATE_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.name+'-contact', that); + }, + onRead: function(callback) { + that.query('contact', function(state){ + callback(state); + }); + }, + perms: ["pr","ev"], + format: "bool", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Contact State", + designedMaxLength: 1 + }); + } + + if( this.isOccupancySensor ) { + cTypes.push({ + cType: types.OCCUPANCY_DETECTED_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.name+'-state', that); + }, + onRead: function(callback) { + that.query('state', function(state){ + callback(state); + }); + }, + perms: ["pr","ev"], + format: "bool", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Occupancy State", + designedMaxLength: 1 + }); + } + + if( this.hasTemperature ) { + cTypes.push({ + cType: types.CURRENT_TEMPERATURE_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.name+'-'+that.hasTemperature, that); + }, + onRead: function(callback) { + that.query(that.hasTemperature, function(temperature){ + callback(temperature); + }); + }, + perms: ["pr","ev"], + format: "float", + initialValue: 20, + supportEvents: true, + supportBonjour: false, + manfDescription: "Current Temperature", + unit: "celsius" + }); + } + + if( this.hasHumidity ) { + cTypes.push({ + cType: types.CURRENT_RELATIVE_HUMIDITY_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.name+'-humidity', that); + }, + onRead: function(callback) { + that.query('humidity', function(humidity){ + callback(humidity); + }); + }, + perms: ["pr","ev"], + format: "int", + initialValue: 50, + designedMinValue: 0, + designedMaxValue: 100, + supportEvents: true, + supportBonjour: false, + manfDescription: "Current Humidity", + unit: "%" + }); + + } + + return cTypes; + }, + + sType: function() { + if( match = this.PossibleSets.match(/\bvolume\b/) ) { + return types.SPEAKER_STYPE; + } else if( this.isBlind ) { + return types.WINDOW_COVERING_STYPE; + } else if( this.isThermostat ) { + return types.THERMOSTAT_STYPE; + } else if( this.isWindow ) { + return types.CONTACT_SENSOR_STYPE; + } else if( this.isContactSensor ) { + return types.CONTACT_SENSOR_STYPE; + } else if( this.isOccupancySensor ) { + return types.OCCUPANCY_SENSOR_STYPE; + } else if( this.isLight || this.hasPct || this.hasHue || this.hasRGB ) { + return types.LIGHTBULB_STYPE; + } else if( this.hasTemperature ) { + return types.TEMPERATURE_SENSOR_STYPE; + } else if( this.hasHumidity ) { + return types.HUMIDITY_SENSOR_STYPE; + } else { + return types.SWITCH_STYPE; + } + }, + + getServices: function() { + var that = this; + var services = [{ + sType: types.ACCESSORY_INFORMATION_STYPE, + characteristics: this.informationCharacteristics(), + }, + { + sType: this.sType(), + characteristics: this.controlCharacteristics(that) + }]; + this.log("Loaded services for " + this.name) + return services; + } +}; + +//module.exports.accessory = FHEMAccessory; +module.exports.platform = FHEMPlatform; From ed6d4baba21843787a52b75d34dc8ab48b47ebe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Tue, 28 Jul 2015 21:34:05 +0200 Subject: [PATCH 02/15] internal cleanups --- platforms/FHEM.js | 528 ++++++++++++++++++++++++++++------------------ 1 file changed, 324 insertions(+), 204 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index 9035b8f..185adfa 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -3,13 +3,13 @@ // Remember to add platform to config.json. Example: // "platforms": [ // { -// "platform": "FHEM", -// "name": "FHEM", -// "server": "127.0.0.1", -// "port": "8083", -// "ssl": "true", -// "auth": {"user": "fhem", "pass": "fhempassword"}, -// "filter": "room=xyz" +// 'platform': "FHEM", +// 'name': "FHEM", +// 'server': "127.0.0.1", +// 'port': 8083, +// 'ssl': true, +// 'auth': {'user': "fhem", 'pass': "fhempassword"}, +// 'filter': "room=xyz" // } // ], // @@ -35,6 +35,10 @@ function FHEM_update(inform_id, value, no_update) { var subscription = FHEM_subscriptions[inform_id]; if( subscription != undefined ) { + if( value == undefined + || FHEM_cached[inform_id] === value ) + return; + FHEM_cached[inform_id] = value; console.log(" caching: " + inform_id + ": " + value + " as " + typeof(value) ); @@ -45,7 +49,7 @@ FHEM_update(inform_id, value, no_update) { var FHEM_longpoll_running = false; -//FIXME: add reconnect, force reconnect on xxx bytes received, add filter, add since +//FIXME: force reconnect on xxx bytes received ?, add filter, add since function FHEM_startLongpoll(connection) { if( FHEM_longpoll_running ) return; @@ -58,7 +62,7 @@ function FHEM_startLongpoll(connection) { "×tamp="+new Date().getTime(); var url = encodeURI( connection.base_url + query ); -console.log( 'starting longpoll: ' + url ); + console.log( 'starting longpoll: ' + url ); var FHEM_longpollOffset = 0; var input = ""; @@ -93,89 +97,44 @@ console.log( 'starting longpoll: ' + url ); //console.log( "Rcvd: "+(l.length>132 ? l.substring(0,132)+"...("+l.length+")":l) ); - //FIXME: create reading2value - //FIXME: redirect device reading to homekit reading: rgb->hue,... var subscription = FHEM_subscriptions[d[0]]; if( subscription != undefined ) { //console.log( "Rcvd: "+(l.length>132 ? l.substring(0,132)+"...("+l.length+")":l) ); var accessory = subscription.accessory; - var value = d[1]; - if(d[0].match(/-state$/)) { - if( match = d[1].match(/dim(\d*)%/ ) ) { + var value = d[1]; + if( value.match( /^set-/ ) ) + continue; + + var match = d[0].match(/([^-]*)-(.*)/); + var device = match[1]; + var reading = match[2]; + if( reading == undefined ) + continue; + + if( reading == 'state') { + if( match = value.match(/dim(\d*)%/ ) ) { var pct = parseInt( match[1] ); - FHEM_update( d[0].replace( '-state', '-pct' ), pct ); + FHEM_update( device+'-pct', pct ); } - value = 1; - if( d[1] == 'off' ) - value = 0; - else if( d[1] == '000000' ) - value = 0; - else if( d[1] == 'absent' ) - value = 0; - else if( d[1] == 'A0' ) - value = 0; - - value = parseInt(value); - - } else if(d[0].match(/-motor$/)) { - value = 2; - if( d[1].match(/^opening/)) - value = 1; - else if( d[1].match(/^up/)) - value = 1; - else if( d[1].match(/^closing/)) - value = 0; - else if( d[1].match(/^down/)) - value = 0; - - value = parseInt(value); - - } else if(d[0].match(/-transportState$/)) { - value = 0; - if( d[1] == 'PLAYING' ) - value = 1; - - value = parseInt(value); - - } else if(d[0].match(/-Volume$/)) { - value = parseInt( d[1] ); - - } else if(d[0].match(/-contact$/)) { - value = 0; - if( d[1].match( /^closed/ ) ) - value = 1; - - value = parseInt(value); - - } else if(d[0].match(/-pct$/)) { - value = parseInt( d[1] ); - - } else if(d[0].match(/-hue$/)) { - value = Math.round(d[1] * 360 / accessory.hueMax); - - } else if(d[0].match(/-sat$/)) { - result = Math.round(d[1] * 100 / accessory.satMax); - - } else if(d[0].match(/-rgb$/)) { - var hue = parseInt( FHEM_rgb2h(d[1]) * 360 ); - - FHEM_update( d[0].replace( '-rgb', '-hue' ), hue ); - - } else if(d[0].match(/-temperature$/) - || d[0].match(/-measured-temp$/) - || d[0].match(/-desired-temp$/) - || d[0].match(/-desiredTemperature$/) ) { - value = parseFloat( d[1] ); - - } else if(d[0].match(/-humidity$/)) { - value = parseInt( d[1] ); + } else if(reading == accessory.hasRGB) { + 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; } - FHEM_update( d[0], value ); + value = accessory.reading2homekit(reading, value); + FHEM_update( device+'-'+reading, value ); + } } @@ -210,7 +169,6 @@ function FHEMPlatform(log, config) { if( auth ) { if( auth.sendImmediately == undefined ) auth.sendImmediately = false; -console.log( "auth: "+ auth ); request = request.defaults( { 'auth': auth, 'rejectUnauthorized': false } ); } @@ -286,7 +244,7 @@ FHEM_hsv2rgb(h,s,v) { } function -FHEM_rgb2h(r,g,b){ +FHEM_rgb2hsv(r,g,b){ if( r == undefined ) return; @@ -312,17 +270,15 @@ FHEM_rgb2h(r,g,b){ h = ( 60 * ( ( r - g ) / c ) + 240 ) / 360; } - return h; - if( M == 0 ) { s = 0; } else { s = c / M; } - v = M; + v = M/255; - return h; + return [h,s,v]; } @@ -437,7 +393,12 @@ FHEMAccessory(log, connection, s) { } if( s.PossibleSets.match(/\brgb\b/) ) { s.isLight = true; - s.hasRGB = true; + s.hasRGB = 'rgb'; + if( s.Internals.TYPE == 'SWAP_0000002200000003' ) + s.hasRGB = '0B-RGBlevel'; + } else if( s.PossibleSets.match(/\bRGB\b/) ) { + s.isLight = true; + s.hasRGB = 'RGB'; } if( s.Readings['measured-temp'] ) @@ -446,7 +407,7 @@ FHEMAccessory(log, connection, s) { s.hasTemperature = "temperature"; if( s.Readings.humidity ) - s.hasHumidity = true; + s.hasHumidity = 'humidity'; if( s.Readings.motor ) s.hasMotor = 'motor'; @@ -456,17 +417,17 @@ FHEMAccessory(log, connection, s) { if( !genericType ) genericType = s.Attributes.genericDisplayType; - if( genericType == 'light' ) + if( genericType == 'switch' ) + s.isSwitch = true; + else if( genericType == 'light' ) s.isLight = true; else if( genericType == 'blind' ) { - s.hasPct = false; s.isBlind = 'pct'; } else if( genericType == 'thermostat' ) s.isThermostat = true; else if( s.Attributes.subType == 'thermostat' ) s.isThermostat = true; else if( s.Attributes.subType == 'blindActuator' ) { - s.hasPct = false; s.isBlind = 'pct'; } else if( s.Attributes.subType == 'threeStateSensor' ) { s.isContactSensor = true; @@ -474,6 +435,9 @@ FHEMAccessory(log, connection, s) { s.isWindow = true; } else if( s.Internals.TYPE == 'PRESENCE' ) s.isOccupancySensor = true; + else if( s.Attributes.model == 'fs20di' ) + s.isLight = true; + //FIXME: set isSwitch to reading: state/transportState/... (on/off / play/pause) //else if( s.PossibleSets.match(/\bon\b/) // && s.PossibleSets.match(/\boff\b/) ) // s.isSwitch = true; @@ -482,11 +446,15 @@ FHEMAccessory(log, connection, s) { s.isThermostat = 'desired-temp'; else if( s.PossibleSets.match(/\bdesiredTemperature\b/) ) s.isThermostat = 'desiredTemperature'; + else if( s.isThermostat ) { + s.isThermostat = false; + log( s.Internals.NAME + ' is NOT a thermostat. set for target temperature missing' ); + } if( s.hasHue ) log( s.Internals.NAME + ' has hue [0-' + s.hueMax +']' ); else if( s.hasRGB ) - log( s.Internals.NAME + ' has RGB'); + log( s.Internals.NAME + ' has RGB [0-' + s.hasRGB +']'); else if( s.hasPct ) log( s.Internals.NAME + ' is dimable [0-'+ s.pctMax +']' ); else if( s.hasDim ) @@ -507,12 +475,10 @@ FHEMAccessory(log, connection, s) { if( s.hasTemperature ) log( s.Internals.NAME + ' has temperature ['+ s.hasTemperature +']' ); if( s.hasHumidity ) - log( s.Internals.NAME + ' has humidity' ); + log( s.Internals.NAME + ' has humidity ['+ s.hasHumidity +']' ); if( s.hasMotor ) log( s.Internals.NAME + ' has motor' ); - //FIXME: create redirectReading() / redirectSet(): on/off -> play/pause - // device info this.name = s.Internals.NAME; this.alias = s.Attributes.alias ? s.Attributes.alias : s.Internals.NAME; @@ -565,6 +531,80 @@ FHEMAccessory(log, connection, s) { FHEM_dim_values = [ 'dim06%', 'dim12%', 'dim18%', 'dim25%', 'dim31%', 'dim37%', 'dim43%', 'dim50%', 'dim56%', 'dim62%', 'dim68%', 'dim75%', 'dim81%', 'dim87%', 'dim93%' ]; FHEMAccessory.prototype = { + reading2homekit: function(reading,value) { + if( reading == 'hue' ) { + value = Math.round(value * 360 / this.hueMax); + + } else if( reading == 'sat' ) { + value = Math.round(value * 100 / this.satMax); + + } else if( reading == 'pct' ) { + value = parseInt( value ); + + } else if(reading.match(/motor$/)) { + if( value.match(/^opening/)) + value = 1; + else if( value.match(/^up/)) + value = 1; + else if( value.match(/^closing/)) + value = 0; + else if( value.match(/^down/)) + value = 0; + else + value = 2; + + value = parseInt(value); + + } else if( reading == 'transportState' ) { + if( value == 'PLAYING' ) + value = 1; + else + value = 0; + + value = parseInt(value); + + } else if( reading == 'Volume' ) { + value = parseInt( value ); + + } else if( reading == 'contact' ) { + if( value.match( /^closed/ ) ) + value = 1; + else + value = 0; + + value = parseInt(value); + + } else if( reading == 'temperature' + || reading == 'measured-temp' + || reading == 'desired-temp' + || reading == 'desiredTemperature' ) { + value = parseFloat( value ); + + } else if( reading == 'humidity' ) { + value = parseInt( value ); + + } else if( reading == 'state' ) { + if( value.match(/^set-/ ) ) + return undefined; + + if( value == 'off' ) + value = 0; + else if( value == 'absent' ) + value = 0; + else if( value == '000000' ) + value = 0; + else if( value == 'A0' ) + value = 0; + else + value = 1; + + value = parseInt( value ); + + } + + return(value); + }, + delayed: function(c,value,delay) { var timer = this.delayed[c]; if( timer ) { @@ -610,15 +650,38 @@ FHEMAccessory.prototype = { else url = this.connection.base_url + "/fhem?cmd=set " + this.device + " " + FHEM_dim_values[Math.round(value/6.25)] + "&XHR=1"; - } else if( c == 'hue' ) { - if( !this.hasHue ) { - value = FHEM_hsv2rgb( value/360.0, this.sat?this.sat/100.0:1.0, this.pct?this.pct/100.0:1.0 ); - url = this.connection.base_url + "/fhem?cmd=set " + this.device + " rgb " + value + "&XHR=1"; + } else if( c == 'H-rgb' || c == 'S-rgb' || c == 'B-rgb' ) { + var h = FHEM_cached[this.device + '-hue' ] / 360; + var s = FHEM_cached[this.device + '-sat' ] / 100; + var v = FHEM_cached[this.device + '-bri' ] / 100; + //this.log( this.name + ' cached : [' + h + ',' + s + ',' + v + ']' ); + if( h == undefined ) h = 0.0; + if( s == undefined ) s = 1.0; + if( v == undefined ) v = 1.0; + //this.log( this.name + ' old : [' + h + ',' + s + ',' + v + ']' ); - } else { + if( c == 'H-rgb' ) { + FHEM_cached[this.device + '-hue' ] = value; + h = value / 360; + } else if( c == 'S-rgb' ) { + FHEM_cached[this.device + '-sat' ] = value; + s = value / 100; + } else if( c == 'B-rgb' ) { + FHEM_cached[this.device + '-bri' ] = value; + v = value / 100; + } + //this.log( this.name + ' new : [' + h + ',' + s + ',' + v + ']' ); + + value = FHEM_hsv2rgb( h, s, v ); + //this.log( this.name + ' rgb : [' + value + ']' ); + if( this.PossibleSets.match(/\bRGB\b/) ) + url = this.connection.base_url + "/fhem?cmd=set " + this.device + " RGB " + value + "&XHR=1"; + else + url = this.connection.base_url + "/fhem?cmd=set " + this.device + " rgb " + value + "&XHR=1"; + + } else if( c == 'hue' ) { value = Math.round(value * this.hueMax / 360); url = this.connection.base_url + "/fhem?cmd=set " + this.device + " hue " + value + "&XHR=1"; - } } else if( c == 'sat' ) { value = value / 100 * this.satMax; @@ -630,7 +693,7 @@ FHEMAccessory.prototype = { } else if( c == 'targetPosition' ) { url = this.connection.base_url + "/fhem?cmd=set " + this.device + " " + this.isBlind + " " + value + "&XHR=1"; - } else if( value != undefined ) { + } else { this.log(this.name + " Unhandled command! cmd=" + c + ", value=" + value); return; @@ -655,29 +718,10 @@ FHEMAccessory.prototype = { }, query: function(reading, callback) { - this.log("query: " + reading); + this.log("query: " + this.name + ": " + reading); - var orig_reading = reading; - - //FIXME: create redirectReading() - var rgb_to_hue = false; - if( reading == 'hue' && !this.hasHue && this.hasRGB ) { - reading = 'rgb'; - rgb_to_hue = true; - - } else if( reading == 'pct' && !this.hasPct ) { - reading = 'state'; - - } - - if( reading == 'rgb' - && this.type == 'SWAP_0000002200000003' ) { - reading = '0B-RGBlevel'; - - } - - var result = FHEM_cached[this.device + '-' + orig_reading]; - if( result != undefined ) { + var result = FHEM_cached[this.device + '-' + reading]; + if( result != undefined ) { this.log(" cached: " + result); if( callback != undefined ) callback(result); @@ -685,7 +729,22 @@ FHEMAccessory.prototype = { } else this.log(" not cached" ); - var cmd = '{ReadingsVal("'+this.device+'","'+reading+'","")}'; + if( reading == 'hue' && !this.hasHue && this.hasRGB ) { + query_reading = this.hasRGB; + + } else if( reading == 'sat' && !this.hasSat && this.hasRGB ) { + query_reading = this.hasRGB; + + } else if( reading == 'bri' && !this.hasPct && this.hasRGB ) { + query_reading = this.hasRGB; + + } else if( reading == 'pct' && !this.hasPct && this.hasDim ) { + query_reading = 'state'; + + } else + query_reading = reading; + + var cmd = '{ReadingsVal("'+this.device+'","'+query_reading+'","")}'; var url = encodeURI( this.connection.base_url + "/fhem?cmd=" + cmd + "&XHR=1"); this.log( ' querying: ' + url ); @@ -693,95 +752,51 @@ FHEMAccessory.prototype = { that.connection.request.get( { url: url, gzip: true }, function(err, response, result) { if( !err && response.statusCode == 200 ) { - result = result.replace(/[\r\n]/g, ""); - that.log(" result: " + result); + value = result.replace(/[\r\n]/g, ""); + that.log(" value: " + value); - //FIXME: create reading2value - if( rgb_to_hue ) { - result = parseInt( FHEM_rgb2h(result) * 360 ); + if( value == undefined ) + return value; - } else if( reading == 'hue' ) { - result = Math.round(result * 360 / that.hueMax); + if( reading != query_reading ) { + if( reading == 'pct' + && query_reading == 'state') { + FHEM_update( that.device+'-'+query_reading, value ); - } else if( reading == 'sat' ) { - result = Math.round(result * 100 / that.satMax); - - } else if( reading == 'pct' ) { - result = parseInt( result ); - - } else if(reading.match(/-motor$/)) { - if( result.match(/^opening/)) - result = 1; - else if( result.match(/^up/)) - result = 1; - else if( result.match(/^closing/)) - result = 0; - else if( result.match(/^down/)) - result = 0; - else - result = 2; - - result = parseInt(result); - - } else if( reading == 'transportState' ) { - if( result == 'PLAYING' ) - result = 1; + if( match = value.match(/dim(\d*)%/ ) ) + value = parseInt( match[1] ); + else if( value == 'off' ) + value = 0; else - result = 0; + value = 100; - result = parseInt(result); + } else if(reading == 'hue' && query_reading == that.hasRGB) { + //FHEM_update( that.device+'-'+query_reading, value ); - } else if( reading == 'Volume' ) { - result = parseInt( result ); + value = parseInt( FHEM_rgb2hsv(value)[0] * 360 ); - } else if( reading == 'contact' ) { - if( result.match( /^closed/ ) ) - result = 1; - else - result = 0; + } else if(reading == 'sat' && query_reading == that.hasRGB) { + //FHEM_update( that.device+'-'+query_reading, value ); - result = parseInt(result); + value = parseInt( FHEM_rgb2hsv(value)[1] * 100 ); - } else if( reading == 'temperature' - || reading == 'measured-temp' - || reading == 'desired-temp' - || reading == 'desiredTemperature' ) { - result = parseFloat( result ); + } else if(reading == 'bri' && query_reading == that.hasRGB) { + //FHEM_update( that.device+'-'+query_reading, value ); - } else if( reading == 'humidity' ) { - result = parseInt( result ); + value = parseInt( FHEM_rgb2hsv(value)[2] * 100 ); - } else if( reading == 'state' ) { - if( orig_reading == 'pct' ) { - if( match = result.match(/dim(\d*)%/ ) ) { - result = match[1]; - } else if( result == 'off' ) - result = 0; - else - result = 100; + } + } else + value = that.reading2homekit(reading, value); - } else if( result == 'off' ) - result = 0; - else if( result == 'absent' ) - result = 0; - else if( result == '000000' ) - result = 0; - else if( result == 'A0' ) - result = 0; - else - result = 1; - - result = parseInt( result ); - - } - that.log(" mapped: " + result); - - if( !rgb_to_hue && reading != 'transportState' && reading != '0B-RGBlevel' ) - FHEM_update( that.device + '-' + orig_reading, result, true ); + that.log(" mapped: " + value); + FHEM_update( that.device + '-' + reading, value, true ); + if( value == undefined ) + return; if( callback != undefined ) - callback(result); - return(result); + callback(value); + return(value); } else { that.log("There was a problem connecting to FHEM (2)."); @@ -895,7 +910,7 @@ FHEMAccessory.prototype = { }); } - if( this.hasPct ) { + if( this.hasPct && !this.isBlind ) { cTypes.push({ cType: types.BRIGHTNESS_CTYPE, onRegister: function(characteristic) { @@ -947,7 +962,7 @@ FHEMAccessory.prototype = { }); } - if( this.hasHue == true || this.hasRGB == true ) { + if( this.hasHue ) { cTypes.push({ cType: types.HUE_CTYPE, onRegister: function(characteristic) { @@ -971,6 +986,84 @@ FHEMAccessory.prototype = { designedMinStep: 1, unit: "arcdegrees" }); + } else if( this.hasRGB ) { + cTypes.push({ + cType: types.HUE_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.name+'-hue', that); + FHEM_subscribe(characteristic, that.name+'-'+that.hasRGB, that); + }, + onUpdate: function(value) { that.command('H-rgb', value); }, + onRead: function(callback) { + that.query('hue', function(hue){ + callback(hue); + }); + }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 0, + //initialValue: that.query( 'hue' ), + supportEvents: true, + supportBonjour: false, + manfDescription: "Adjust the Hue of the Light", + designedMinValue: 0, + designedMaxValue: 360, + designedMinStep: 1, + unit: "arcdegrees" + }); + + if( !this.hasSat ) + cTypes.push({ + cType: types.SATURATION_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.name+'-sat', that); + }, + onUpdate: function(value) { that.command('S-rgb', value); }, + onRead: function(callback) { + that.query('sat', function(sat){ + callback(sat); + }); + }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 100, + //initialValue: that.query( 'sat' ), + supportEvents: true, + supportBonjour: false, + manfDescription: "Adjust the Saturation of the Light", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }); + + if( !this.hasPct ) + cTypes.push({ + cType: types.BRIGHTNESS_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.name+'-bri', that); + }, + onUpdate: function(value) { that.command('B-rgb', value); }, + onRead: function(callback) { + that.query('bri', function(bri){ + callback(bri); + }); + }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 0, + //initialValue: that.query( 'bri' ), + supportEvents: true, + supportBonjour: false, + manfDescription: "Adjust Brightness of the Light", + designedMinValue: 0, + designedMaxValue: this.pctMax, + designedMinStep: 1, + unit: "%" + }); } if( this.hasSat == true ) { @@ -1243,6 +1336,7 @@ FHEMAccessory.prototype = { perms: ["pr","ev"], format: "float", initialValue: 20, + //initialValue: that.query(that.hasTemperature), supportEvents: true, supportBonjour: false, manfDescription: "Current Temperature", @@ -1255,16 +1349,17 @@ FHEMAccessory.prototype = { cType: types.CURRENT_RELATIVE_HUMIDITY_CTYPE, onRegister: function(characteristic) { characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.name+'-humidity', that); + FHEM_subscribe(characteristic, that.name+'-'+that.hasHumidity, that); }, onRead: function(callback) { - that.query('humidity', function(humidity){ + that.query(that.hasHumidity, function(humidity){ callback(humidity); }); }, perms: ["pr","ev"], format: "int", initialValue: 50, + //initialValue: that.query(that.hasHumidity), designedMinValue: 0, designedMaxValue: 100, supportEvents: true, @@ -1281,6 +1376,8 @@ FHEMAccessory.prototype = { sType: function() { if( match = this.PossibleSets.match(/\bvolume\b/) ) { return types.SPEAKER_STYPE; + } else if( this.isSwitch ) { + return types.SWITCH_STYPE; } else if( this.isBlind ) { return types.WINDOW_COVERING_STYPE; } else if( this.isThermostat ) { @@ -1319,3 +1416,26 @@ FHEMAccessory.prototype = { //module.exports.accessory = FHEMAccessory; module.exports.platform = FHEMPlatform; + + + +//http server for debugging +var http = require('http'); + +const FHEMdebug_PORT=8080; + +function FHEMdebug_handleRequest(request, response){ + //response.write( "subscriptions: " + util.inspect(FHEM_subscriptions) + "\n\n" ); + + response.end( "cached: " + util.inspect(FHEM_cached) ); +} + +//Create a server +var FHEMdebug_server = http.createServer( FHEMdebug_handleRequest ); + +//Lets start our server +FHEMdebug_server.listen(FHEMdebug_PORT, function(){ + //Callback triggered when server is successfully listening. Hurray! + console.log("Server listening on: http://:%s", FHEMdebug_PORT); +}); + From f050e5151875b4bb1b15ac2e1e4e8c67dae5e4a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Wed, 29 Jul 2015 21:40:29 +0200 Subject: [PATCH 03/15] more on EnOcean switches --- platforms/FHEM.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index 185adfa..d12e2e4 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -593,7 +593,7 @@ FHEMAccessory.prototype = { value = 0; else if( value == '000000' ) value = 0; - else if( value == 'A0' ) + else if( value.match( /^[A-D]0$/ ) ) value = 0; else value = 1; @@ -1433,6 +1433,11 @@ function FHEMdebug_handleRequest(request, response){ //Create a server var FHEMdebug_server = http.createServer( FHEMdebug_handleRequest ); +FHEMdebug_server.on('error', function (e) { + // Handle your error here + console.log("Server error: " + e); +}); + //Lets start our server FHEMdebug_server.listen(FHEMdebug_PORT, function(){ //Callback triggered when server is successfully listening. Hurray! From a268583803f04c1fdf058a129299c754ae014b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Wed, 29 Jul 2015 23:41:18 +0200 Subject: [PATCH 04/15] fixed racecondition --- platforms/FHEM.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index d12e2e4..1fdd6ef 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -541,7 +541,7 @@ FHEMAccessory.prototype = { } else if( reading == 'pct' ) { value = parseInt( value ); - } else if(reading.match(/motor$/)) { + } else if(reading == 'motor') { if( value.match(/^opening/)) value = 1; else if( value.match(/^up/)) @@ -551,7 +551,7 @@ FHEMAccessory.prototype = { else if( value.match(/^down/)) value = 0; else - value = 2; + value = 2; value = parseInt(value); @@ -718,7 +718,7 @@ FHEMAccessory.prototype = { }, query: function(reading, callback) { - this.log("query: " + this.name + ": " + reading); + this.log("query: " + this.name + "-" + reading); var result = FHEM_cached[this.device + '-' + reading]; if( result != undefined ) { @@ -729,6 +729,7 @@ FHEMAccessory.prototype = { } else this.log(" not cached" ); + var query_reading = reading; if( reading == 'hue' && !this.hasHue && this.hasRGB ) { query_reading = this.hasRGB; @@ -740,9 +741,7 @@ FHEMAccessory.prototype = { } else if( reading == 'pct' && !this.hasPct && this.hasDim ) { query_reading = 'state'; - - } else - query_reading = reading; + } var cmd = '{ReadingsVal("'+this.device+'","'+query_reading+'","")}'; var url = encodeURI( this.connection.base_url + "/fhem?cmd=" + cmd + "&XHR=1"); @@ -761,7 +760,7 @@ FHEMAccessory.prototype = { if( reading != query_reading ) { if( reading == 'pct' && query_reading == 'state') { - FHEM_update( that.device+'-'+query_reading, value ); + //FHEM_update( that.device+'-'+query_reading, that.reading2homekit(query_reading, value) ); if( match = value.match(/dim(\d*)%/ ) ) value = parseInt( match[1] ); @@ -786,8 +785,9 @@ FHEMAccessory.prototype = { value = parseInt( FHEM_rgb2hsv(value)[2] * 100 ); } - } else + } else { value = that.reading2homekit(reading, value); + } that.log(" mapped: " + value); FHEM_update( that.device + '-' + reading, value, true ); From 431199a3882139b6f1db9d2ddbc300a5c85a2692 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Thu, 30 Jul 2015 12:37:46 +0200 Subject: [PATCH 05/15] added event map for state on/off --- platforms/FHEM.js | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index 1fdd6ef..c29813f 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -451,6 +451,20 @@ FHEMAccessory(log, connection, s) { log( s.Internals.NAME + ' is NOT a thermostat. set for target temperature missing' ); } + var event_map = s.Attributes.eventMap; + if( event_map ) { + var parts = event_map.split( ' ' ); + for( var p = 0; p < parts.length; p++ ) { + var map = parts[p].split( ':' ); + if( map[1] == 'on' + || map[1] == 'off' ) { + if( !this.event_map ) + this.event_map = {} + this.event_map[map[0]] = map[1]; + } + } + } + if( s.hasHue ) log( s.Internals.NAME + ' has hue [0-' + s.hueMax +']' ); else if( s.hasRGB ) @@ -587,6 +601,12 @@ FHEMAccessory.prototype = { if( value.match(/^set-/ ) ) return undefined; + if( this.event_map != undefined ) { + var mapped = this.event_map[value]; + if( mapped != undefined ) + value = mapped; + } + if( value == 'off' ) value = 0; else if( value == 'absent' ) @@ -1425,9 +1445,14 @@ var http = require('http'); const FHEMdebug_PORT=8080; function FHEMdebug_handleRequest(request, response){ - //response.write( "subscriptions: " + util.inspect(FHEM_subscriptions) + "\n\n" ); + //console.log( request ); + if( request.url == "/cached" ) response.end( "cached: " + util.inspect(FHEM_cached) ); + else if( request.url == "/subscriptions" ) + response.end( "subscriptions: " + util.inspect(FHEM_subscriptions, {depth: 3}) ); + else + response.end( "cached
subscriptions" ); } //Create a server From 4dcc26bf5051e12de2cbd6c4d83552b9a67117ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Thu, 30 Jul 2015 17:10:29 +0200 Subject: [PATCH 06/15] fix for rgb vs RGB issue --- platforms/FHEM.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index c29813f..5c27f3f 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -694,10 +694,10 @@ FHEMAccessory.prototype = { value = FHEM_hsv2rgb( h, s, v ); //this.log( this.name + ' rgb : [' + value + ']' ); - if( this.PossibleSets.match(/\bRGB\b/) ) - url = this.connection.base_url + "/fhem?cmd=set " + this.device + " RGB " + value + "&XHR=1"; - else + if( this.PossibleSets.match(/\brgb\b/) ) url = this.connection.base_url + "/fhem?cmd=set " + this.device + " rgb " + value + "&XHR=1"; + else + url = this.connection.base_url + "/fhem?cmd=set " + this.device + " RGB " + value + "&XHR=1"; } else if( c == 'hue' ) { value = Math.round(value * this.hueMax / 360); From a29cabcd9130d95dcf8504b987d6cbc93f9e4b8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Fri, 31 Jul 2015 22:44:50 +0200 Subject: [PATCH 07/15] enhanced debug browser --- platforms/FHEM.js | 49 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index 5c27f3f..f3c995e 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -451,6 +451,12 @@ FHEMAccessory(log, connection, s) { log( s.Internals.NAME + ' is NOT a thermostat. set for target temperature missing' ); } + if( s.Internals.TYPE == 'SONOSPLAYER' ) + this.hasOnOff = { reading: 'transportState', cmdOn: 'play', cmdOff: 'pause' }; + else if( s.PossibleSets.match(/\bon\b/) + && s.PossibleSets.match(/\boff\b/) ) + this.hasOnOff = { reading: 'state', cmdOn: 'on', cmdOff: 'off' }; + var event_map = s.Attributes.eventMap; if( event_map ) { var parts = event_map.split( ' ' ); @@ -486,6 +492,9 @@ FHEMAccessory(log, connection, s) { else log( s.Internals.NAME + ' is switchable' ); + if( this.hasOnOff ) + log( s.Internals.NAME + ' has OnOff [' + this.hasOnOff + ']' ); + if( s.hasTemperature ) log( s.Internals.NAME + ' has temperature ['+ s.hasTemperature +']' ); if( s.hasHumidity ) @@ -505,6 +514,8 @@ FHEMAccessory(log, connection, s) { this.serial = 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; else if( this.type == 'FS20' ) @@ -1447,25 +1458,45 @@ const FHEMdebug_PORT=8080; function FHEMdebug_handleRequest(request, response){ //console.log( request ); - if( request.url == "/cached" ) - response.end( "cached: " + util.inspect(FHEM_cached) ); - else if( request.url == "/subscriptions" ) - response.end( "subscriptions: " + util.inspect(FHEM_subscriptions, {depth: 3}) ); - else - response.end( "cached
subscriptions" ); + if( request.url == "/cached" ) { + response.write( "home
" ); + response.end( "cached: " + util.inspect(FHEM_cached).replace(/\n/g, '
') ); + + } else if( request.url == "/subscriptions" ) { + response.write( "home
" ); + response.end( "subscriptions: " + util.inspect(FHEM_subscriptions, {depth: 3}).replace(/\n/g, '
') ); + + } else if( request.url == "/persist" ) { + response.write( "home
" ); + var unique = {}; + Object.keys(FHEM_subscriptions).forEach(function(key) { + var characteristic = FHEM_subscriptions[key].characteristic; + var info = characteristic.accessoryController.tcpServer.accessoryInfo; + if( unique[info.displayName] ) + return; + unique[info.displayName] = info.username; + } ); + + var keys = Object.keys(unique); + keys.sort(); + for( i = 0; i < keys.length; i++ ) { + var k = keys[i]; + response.write( k +': '+ unique[k] +'
' ); + } + response.end( "" ); + + } else + response.end( "cached
persist
subscriptions" ); } -//Create a server var FHEMdebug_server = http.createServer( FHEMdebug_handleRequest ); FHEMdebug_server.on('error', function (e) { - // Handle your error here console.log("Server error: " + e); }); //Lets start our server FHEMdebug_server.listen(FHEMdebug_PORT, function(){ - //Callback triggered when server is successfully listening. Hurray! console.log("Server listening on: http://:%s", FHEMdebug_PORT); }); From 939f79b2b09960397c2d1c1c87415aaa269c2a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Sat, 1 Aug 2015 17:47:42 +0200 Subject: [PATCH 08/15] some cleanups try to detect closed longpoll connection added last event timestamp --- platforms/FHEM.js | 66 ++++++++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index f3c995e..d635db3 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -40,6 +40,7 @@ FHEM_update(inform_id, value, no_update) { return; FHEM_cached[inform_id] = value; + //FHEM_cached[inform_id] = { 'value': value, 'timestamp': Date.now() }; console.log(" caching: " + inform_id + ": " + value + " as " + typeof(value) ); if( !no_update ) @@ -48,6 +49,7 @@ FHEM_update(inform_id, value, no_update) { } +var FHEM_lastEventTimestamp; var FHEM_longpoll_running = false; //FIXME: force reconnect on xxx bytes received ?, add filter, add since function FHEM_startLongpoll(connection) { @@ -59,7 +61,7 @@ function FHEM_startLongpoll(connection) { var since = "null"; var query = "/fhem.pl?XHR=1"+ "&inform=type=status;filter="+filter+";since="+since+";fmt=JSON"+ - "×tamp="+new Date().getTime(); + "×tamp="+Date.now() var url = encodeURI( connection.base_url + query ); console.log( 'starting longpoll: ' + url ); @@ -100,6 +102,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_lastEventTimestamp = Date.now(); var accessory = subscription.accessory; var value = d[1]; @@ -135,6 +138,7 @@ function FHEM_startLongpoll(connection) { value = accessory.reading2homekit(reading, value); FHEM_update( device+'-'+reading, value ); + } else { } } @@ -142,6 +146,23 @@ function FHEM_startLongpoll(connection) { input = input.substr(FHEM_longpollOffset); FHEM_longpollOffset = 0; + } ).on( 'end', function() { + console.log( "longpoll ended" ); + + FHEM_longpoll_running = false; + setTimeout( function(){FHEM_startLongpoll(connection)}, 5000 ); + + } ).on( 'close', function() { + console.log( "longpoll closed" ); + + FHEM_longpoll_running = false; + setTimeout( function(){FHEM_startLongpoll(connection)}, 5000 ); + + } ).on( 'finish', function() { + console.log( "longpoll finished" ); + + FHEM_longpoll_running = false; + setTimeout( function(){FHEM_startLongpoll(connection)}, 5000 ); } ).on( 'error', function(err) { console.log( "longpoll error: " + err ); @@ -437,10 +458,6 @@ FHEMAccessory(log, connection, s) { s.isOccupancySensor = true; else if( s.Attributes.model == 'fs20di' ) s.isLight = true; - //FIXME: set isSwitch to reading: state/transportState/... (on/off / play/pause) - //else if( s.PossibleSets.match(/\bon\b/) - // && s.PossibleSets.match(/\boff\b/) ) - // s.isSwitch = true; if( s.PossibleSets.match(/\bdesired-temp\b/) ) s.isThermostat = 'desired-temp'; @@ -451,11 +468,12 @@ FHEMAccessory(log, connection, s) { log( s.Internals.NAME + ' is NOT a thermostat. set for target temperature missing' ); } + this.endpoints = {}; if( s.Internals.TYPE == 'SONOSPLAYER' ) - this.hasOnOff = { reading: 'transportState', cmdOn: 'play', cmdOff: 'pause' }; + this.endpoints.onOff = { reading: 'transportState', cmdOn: 'play', cmdOff: 'pause' }; else if( s.PossibleSets.match(/\bon\b/) && s.PossibleSets.match(/\boff\b/) ) - this.hasOnOff = { reading: 'state', cmdOn: 'on', cmdOff: 'off' }; + this.endpoints.onOff = { reading: 'state', cmdOn: 'on', cmdOff: 'off' }; var event_map = s.Attributes.eventMap; if( event_map ) { @@ -692,13 +710,13 @@ FHEMAccessory.prototype = { //this.log( this.name + ' old : [' + h + ',' + s + ',' + v + ']' ); if( c == 'H-rgb' ) { - FHEM_cached[this.device + '-hue' ] = value; + FHEM_update(this.device + '-hue', value, false ); h = value / 360; } else if( c == 'S-rgb' ) { - FHEM_cached[this.device + '-sat' ] = value; + FHEM_update(this.device + '-sat', value, false ); s = value / 100; } else if( c == 'B-rgb' ) { - FHEM_cached[this.device + '-bri' ] = value; + FHEM_update(this.device + '-bri', value, false ); v = value / 100; } //this.log( this.name + ' new : [' + h + ',' + s + ',' + v + ']' ); @@ -908,32 +926,30 @@ FHEMAccessory.prototype = { designedMaxLength: 255 }] - if( this.name != undefined - && !this.hasTemperature - && !this.hasHumidity - && !this.isBlind - && !this.isThermostat - && !this.isContactSensor - && !this.isOccupancySensor ) { + //if( this.name != undefined + // && !this.hasTemperature + // && !this.hasHumidity + // && !this.isBlind + // && !this.isThermostat + // && !this.isContactSensor + // && !this.isOccupancySensor ) { + if( this.endpoints.onOff ) { cTypes.push({ cType: types.POWER_STATE_CTYPE, onRegister: function(characteristic) { characteristic.eventEnabled = true; - if( that.type == 'SONOSPLAYER' ) - FHEM_subscribe(characteristic, that.name+'-transportState', that); - else - FHEM_subscribe(characteristic, that.name+'-state', that); + FHEM_subscribe(characteristic, that.name+'-'+that.endpoints.onOff.reading, that); }, onUpdate: function(value) { that.command( value == 0 ? 'off' : 'on' ); }, onRead: function(callback) { - that.query( that.type == 'SONOSPLAYER' ? 'transportState' : 'state', function(state){ callback(state) } ); + that.query( that.endpoints.onOff.reading, function(state){ callback(state) } ); }, perms: ["pw","pr","ev"], format: "bool", initialValue: 0, - //initialValue: that.query( that.type == 'SONOSPLAYER' ? 'transportState' : 'state' ), + //initialValue: that.query( that.endpoints.onOff.reading ); supportEvents: true, supportBonjour: false, manfDescription: "Change the power state", @@ -1460,6 +1476,8 @@ function FHEMdebug_handleRequest(request, response){ if( request.url == "/cached" ) { response.write( "home
" ); + if( FHEM_lastEventTimestamp ) + response.write( "FHEM_lastEventTime: "+ new Date(FHEM_lastEventTimestamp) +"
" ); response.end( "cached: " + util.inspect(FHEM_cached).replace(/\n/g, '
') ); } else if( request.url == "/subscriptions" ) { @@ -1469,7 +1487,7 @@ function FHEMdebug_handleRequest(request, response){ } else if( request.url == "/persist" ) { response.write( "home
" ); var unique = {}; - Object.keys(FHEM_subscriptions).forEach(function(key) { + Object.keys(FHEM_subscriptions).forEach(function(key) { var characteristic = FHEM_subscriptions[key].characteristic; var info = characteristic.accessoryController.tcpServer.accessoryInfo; if( unique[info.displayName] ) From e0de3f2a82f6110212d1a345af129ce75cb7d59d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Sat, 1 Aug 2015 20:15:59 +0200 Subject: [PATCH 09/15] don't show on/off for blinds --- platforms/FHEM.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index d635db3..787e57d 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -614,6 +614,7 @@ FHEMAccessory.prototype = { value = 1; else value = 0; + //value = 2; value = parseInt(value); @@ -926,14 +927,13 @@ FHEMAccessory.prototype = { designedMaxLength: 255 }] - //if( this.name != undefined - // && !this.hasTemperature - // && !this.hasHumidity - // && !this.isBlind - // && !this.isThermostat - // && !this.isContactSensor - // && !this.isOccupancySensor ) { - if( this.endpoints.onOff ) { + if( this.endpoints.onOff + && !this.hasTemperature + && !this.hasHumidity + && !this.isBlind + && !this.isThermostat + && !this.isContactSensor + && !this.isOccupancySensor ) { cTypes.push({ cType: types.POWER_STATE_CTYPE, onRegister: function(characteristic) { From 40e264a79d2e2f24a157769c50e4cdbaf57253c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Sun, 2 Aug 2015 01:09:53 +0200 Subject: [PATCH 10/15] started cleanup of has... use endpoints instead --- platforms/FHEM.js | 397 +++++++++++++++++++++++----------------------- 1 file changed, 199 insertions(+), 198 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index 787e57d..fcabfec 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -335,7 +335,7 @@ FHEMPlatform.prototype = { accessory = new FHEMAccessory(that.log, that.connection, s); foundAccessories.push(accessory); - } else if( s.PossibleSets.match(/\bvolume\b/) ) { + } else if( s.PossibleSets.match(/[\^ ]Volume\b/) ) { that.log( s.Internals.NAME + ' has volume'); accessory = new FHEMAccessory(that.log, that.connection, s); foundAccessories.push(accessory); @@ -389,49 +389,50 @@ FHEMAccessory(log, connection, s) { //log("got json: " + util.inspect(s) ); //log("got json: " + util.inspect(s.Internals) ); - //FIXME: replace hasPct(true/false) by hasBri(reading) + this.endpoints = {}; + var match; - if( match = s.PossibleSets.match(/\bpct\b/) ) { - s.hasPct = true; - s.pctMax = 100; - } else if( match = s.PossibleSets.match(/\bdim\d*%/) ) { + if( match = s.PossibleSets.match(/[\^ ]pct\b/) ) { + this.endpoints.pct = { reading: 'pct', cmd: 'pct', min: 0, max: 100 }; + } 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; - s.hasHue = true; - s.hueMax = 360; + var max = 360; if( match[2] != undefined ) - s.hueMax = match[2]; + max = match[2]; + this.endpoints.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; - s.hasSat = true; - s.satMax = 100; + var max = 100; if( match[2] != undefined ) - s.satMax = match[2]; + max = match[2]; + this.endpoints.sat = { reading: 'sat', cmd: 'sat', min: 0, max: max }; } - if( s.PossibleSets.match(/\brgb\b/) ) { + + if( s.PossibleSets.match(/[\^ ]rgb\b/) ) { s.isLight = true; s.hasRGB = 'rgb'; if( s.Internals.TYPE == 'SWAP_0000002200000003' ) s.hasRGB = '0B-RGBlevel'; - } else if( s.PossibleSets.match(/\bRGB\b/) ) { + } else if( s.PossibleSets.match(/[\^ ]RGB\b/) ) { s.isLight = true; s.hasRGB = 'RGB'; } if( s.Readings['measured-temp'] ) - s.hasTemperature = "measured-temp"; + this.endpoints.temperature = { reading: 'measured-temp' }; else if( s.Readings.temperature ) - s.hasTemperature = "temperature"; + this.endpoints.temperature = { reading: 'temperature' }; if( s.Readings.humidity ) - s.hasHumidity = 'humidity'; + this.endpoints.humidity = { reading: 'humidity' }; if( s.Readings.motor ) - s.hasMotor = 'motor'; + this.endpoints.motor = { reading: 'motor' }; var genericType = s.Attributes.genericDeviceType; @@ -459,20 +460,19 @@ FHEMAccessory(log, connection, s) { else if( s.Attributes.model == 'fs20di' ) s.isLight = true; - if( s.PossibleSets.match(/\bdesired-temp\b/) ) + if( s.PossibleSets.match(/[\^ ]desired-temp\b/) ) s.isThermostat = 'desired-temp'; - else if( s.PossibleSets.match(/\bdesiredTemperature\b/) ) + else if( s.PossibleSets.match(/[\^ ]desiredTemperature\b/) ) s.isThermostat = 'desiredTemperature'; else if( s.isThermostat ) { s.isThermostat = false; log( s.Internals.NAME + ' is NOT a thermostat. set for target temperature missing' ); } - this.endpoints = {}; if( s.Internals.TYPE == 'SONOSPLAYER' ) this.endpoints.onOff = { reading: 'transportState', cmdOn: 'play', cmdOff: 'pause' }; - 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.endpoints.onOff = { reading: 'state', cmdOn: 'on', cmdOff: 'off' }; var event_map = s.Attributes.eventMap; @@ -489,22 +489,20 @@ FHEMAccessory(log, connection, s) { } } - if( s.hasHue ) - log( s.Internals.NAME + ' has hue [0-' + s.hueMax +']' ); - else if( s.hasRGB ) - log( s.Internals.NAME + ' has RGB [0-' + s.hasRGB +']'); - else if( s.hasPct ) - log( s.Internals.NAME + ' is dimable [0-'+ s.pctMax +']' ); - else if( s.hasDim ) - log( s.Internals.NAME + ' is dimable [0-'+ s.pctMax +']' ); + if( s.isBlind ) + log( s.Internals.NAME + ' is blind ['+ s.isBlind +']' ); else if( s.isThermostat ) log( s.Internals.NAME + ' is thermostat ['+ s.isThermostat +']' ); else if( s.isContactSensor ) log( s.Internals.NAME + ' is contactsensor' ); else if( s.isOccupancySensor ) log( s.Internals.NAME + ' is occupancysensor' ); - else if( s.isBlind ) - log( s.Internals.NAME + ' is blind ['+ s.isBlind +']' ); + else if( s.hasRGB ) + log( s.Internals.NAME + ' has RGB [0-' + s.hasRGB +']'); + else if( this.endpoints.pct ) + log( s.Internals.NAME + ' is dimable [0-'+ this.endpoints.pct.max +']' ); + else if( s.hasDim ) + log( s.Internals.NAME + ' is dimable [0-'+ s.pctMax +']' ); else if( s.isLight ) log( s.Internals.NAME + ' is light' ); else @@ -513,11 +511,15 @@ FHEMAccessory(log, connection, s) { if( this.hasOnOff ) log( s.Internals.NAME + ' has OnOff [' + this.hasOnOff + ']' ); - if( s.hasTemperature ) - log( s.Internals.NAME + ' has temperature ['+ s.hasTemperature +']' ); - if( s.hasHumidity ) - log( s.Internals.NAME + ' has humidity ['+ s.hasHumidity +']' ); - if( s.hasMotor ) + if( this.endpoints.hue ) + log( s.Internals.NAME + ' has hue [0-' + this.endpoints.hue.max +']' ); + if( this.endpoints.sat ) + log( s.Internals.NAME + ' has sat [0-' + this.endpoints.sat.max +']' ); + if( this.endpoints.temperature ) + log( s.Internals.NAME + ' has temperature ['+ this.endpoints.temperature.reading +']' ); + if( this.endpoints.humidity ) + log( s.Internals.NAME + ' has humidity ['+ this.endpoints.humidity.reading +']' ); + if( this.endpoints.motor ) log( s.Internals.NAME + ' has motor' ); // device info @@ -545,19 +547,10 @@ FHEMAccessory(log, connection, s) { else if( this.type == 'SONOSPLAYER' ) this.serial = s.Internals.UDN; - this.hasPct = s.hasPct; this.hasDim = s.hasDim; this.pctMax = s.pctMax; - this.hasHue = s.hasHue; - this.hueMax = s.hueMax; - this.hasSat = s.hasSat; - this.satMax = s.satMax; this.hasRGB = s.hasRGB; - this.hasTemperature = s.hasTemperature; - this.hasHumidity = s.hasHumidity; - this.hasMotor = s.hasMotor; - this.isLight = s.isLight; this.isBlind = s.isBlind; this.isThermostat = s.isThermostat; @@ -567,6 +560,24 @@ FHEMAccessory(log, connection, s) { //log( util.inspect(s.Readings) ); + if( this.isBlind || this.isDoor || this.isWindow || this.isThermostat ) + delete this.endpoints.onOff; + + var that = this; + Object.keys(this.endpoints).forEach(function(key) { + var reading = that.endpoints[key].reading; + if( s.Readings[reading] && s.Readings[reading].Value ) { + var value = s.Readings[reading].Value; + value = that.reading2homekit(reading, value); + + if( value != undefined ) { + var inform_id = that.device +'-'+ reading; + that.endpoints[key].informId = inform_id; + FHEM_cached[inform_id] = value; + } + } + } ); + this.log = log; this.connection = connection; } @@ -576,10 +587,10 @@ FHEM_dim_values = [ 'dim06%', 'dim12%', 'dim18%', 'dim25%', 'dim31%', 'dim37%', FHEMAccessory.prototype = { reading2homekit: function(reading,value) { if( reading == 'hue' ) { - value = Math.round(value * 360 / this.hueMax); + value = Math.round(value * 360 / this.endpoints.hue.max); } else if( reading == 'sat' ) { - value = Math.round(value * 100 / this.satMax); + value = Math.round(value * 100 / this.endpoints.sat.max); } else if( reading == 'pct' ) { value = parseInt( value ); @@ -670,35 +681,35 @@ FHEMAccessory.prototype = { command: function(c,value) { this.log(this.name + " sending command " + c + " with value " + value); if( c == 'on' ) { - if( this.PossibleSets.match(/\bplay\b/i) ) - url = this.connection.base_url + "/fhem?cmd=set " + this.device + " play&XHR=1"; - else if( this.PossibleSets.match(/\bon\b/) ) - url = this.connection.base_url + "/fhem?cmd=set " + this.device + " on&XHR=1"; + if( this.PossibleSets.match(/[\^ ]play\b/i) ) + cmd = "set " + this.device + " play"; + else if( this.PossibleSets.match(/[\^ ]on\b/) ) + cmd = "set " + this.device + " on"; else this.log(this.name + " Unhandled command! cmd=" + c + ", value=" + value); } else if( c == 'off' ) { - if( this.PossibleSets.match(/\bpause\b/i) ) - url = this.connection.base_url + "/fhem?cmd=set " + this.device + " pause&XHR=1"; - else if( this.PossibleSets.match(/\boff\b/) ) - url = this.connection.base_url + "/fhem?cmd=set " + this.device + " off&XHR=1"; + if( this.PossibleSets.match(/[\^ ]pause\b/i) ) + cmd = "set " + this.device + " pause"; + else if( this.PossibleSets.match(/[\^ ]off\b/) ) + cmd = "set " + this.device + " off"; else this.log(this.device + " Unhandled command! cmd=" + c + ", value=" + value); } else if( c == 'volume' ) { - url = this.connection.base_url + "/fhem?cmd=set " + this.device + " volume " + value + "&XHR=1"; + cmd = "set " + this.device + " volume " + value; } else if( c == 'pct' ) { - url = this.connection.base_url + "/fhem?cmd=set " + this.device + " pct " + value + "&XHR=1"; + cmd = "set " + this.device + " pct " + value; } else if( c == 'dim' ) { //if( value < 3 ) - // url = this.connection.base_url + "/fhem?cmd=set " + this.device + " off&XHR=1"; + // cmd = "set " + this.device + " off"; //else if( value > 97 ) - url = this.connection.base_url + "/fhem?cmd=set " + this.device + " on&XHR=1"; + cmd = "set " + this.device + " on"; else - url = this.connection.base_url + "/fhem?cmd=set " + this.device + " " + FHEM_dim_values[Math.round(value/6.25)] + "&XHR=1"; + cmd = "set " + this.device + " " + FHEM_dim_values[Math.round(value/6.25)]; } else if( c == 'H-rgb' || c == 'S-rgb' || c == 'B-rgb' ) { var h = FHEM_cached[this.device + '-hue' ] / 360; @@ -711,37 +722,37 @@ FHEMAccessory.prototype = { //this.log( this.name + ' old : [' + h + ',' + s + ',' + v + ']' ); if( c == 'H-rgb' ) { - FHEM_update(this.device + '-hue', value, false ); + FHEM_update(this.device + '-hue', value, false ); h = value / 360; } else if( c == 'S-rgb' ) { - FHEM_update(this.device + '-sat', value, false ); + FHEM_update(this.device + '-sat', value, false ); s = value / 100; } else if( c == 'B-rgb' ) { - FHEM_update(this.device + '-bri', value, false ); + FHEM_update(this.device + '-bri', value, false ); v = value / 100; } //this.log( this.name + ' new : [' + h + ',' + s + ',' + v + ']' ); value = FHEM_hsv2rgb( h, s, v ); //this.log( this.name + ' rgb : [' + value + ']' ); - if( this.PossibleSets.match(/\brgb\b/) ) - url = this.connection.base_url + "/fhem?cmd=set " + this.device + " rgb " + value + "&XHR=1"; + if( this.PossibleSets.match(/[\^ ]RGB\b/) ) + cmd = "set " + this.device + " RGB " + value; else - url = this.connection.base_url + "/fhem?cmd=set " + this.device + " RGB " + value + "&XHR=1"; + cmd = "set " + this.device + " rgb " + value; } else if( c == 'hue' ) { - value = Math.round(value * this.hueMax / 360); - url = this.connection.base_url + "/fhem?cmd=set " + this.device + " hue " + value + "&XHR=1"; + value = Math.round(value * this.endpoints.hue.max / 360); + cmd = "set " + this.device + " hue " + value; } else if( c == 'sat' ) { - value = value / 100 * this.satMax; - url = this.connection.base_url + "/fhem?cmd=set " + this.device + " sat " + value + "&XHR=1"; + value = value / 100 * this.endpoints.sat.max; + cmd = "set " + this.device + " sat " + value; } else if( c == 'targetTemperature' ) { - url = this.connection.base_url + "/fhem?cmd=set " + this.device + " " + this.isThermostat + " " + value + "&XHR=1"; + cmd = "set " + this.device + " " + this.isThermostat + " " + value; } else if( c == 'targetPosition' ) { - url = this.connection.base_url + "/fhem?cmd=set " + this.device + " " + this.isBlind + " " + value + "&XHR=1"; + cmd = "set " + this.device + " " + this.isBlind + " " + value; } else { this.log(this.name + " Unhandled command! cmd=" + c + ", value=" + value); @@ -749,22 +760,31 @@ FHEMAccessory.prototype = { } + this.execute(cmd); + }, + + execute: function(cmd,callback) { + var url = encodeURI( this.connection.base_url + "/fhem?cmd=" + cmd + "&XHR=1"); + this.log( ' executing: ' + url ); + var that = this; - this.connection.request.put( { url: encodeURI(url), gzip: true }, - function(err, response) { - if( err ) { - that.log("There was a problem sending command " + c + " to" + that.name); - that.log(url); - if( response ) - that.log( " " + response.statusCode + ": " + response.statusMessage ); + this.connection.request.get( { url: url, gzip: true }, + function(err, response, result) { + if( !err && response.statusCode == 200 ) { + if( callback ) + callback( result ); - } else { - that.log(that.name + " sent command " + c); - that.log(url); + } else { + that.log("There was a problem connecting to FHEM ("+ url +")."); + if( response ) + that.log( " " + response.statusCode + ": " + response.statusMessage ); - } + } - } ); + } ).on( 'error', function(err) { + that.log("There was a problem connecting to FHEM ("+ url +"):"+ err); + + } ); }, query: function(reading, callback) { @@ -774,88 +794,78 @@ FHEMAccessory.prototype = { if( result != undefined ) { this.log(" cached: " + result); if( callback != undefined ) - callback(result); - return(result); + callback( result ); + return( result ); } else this.log(" not cached" ); var query_reading = reading; - if( reading == 'hue' && !this.hasHue && this.hasRGB ) { + if( reading == 'hue' && !this.endpoints.hue && this.hasRGB ) { query_reading = this.hasRGB; - } else if( reading == 'sat' && !this.hasSat && this.hasRGB ) { + } else if( reading == 'sat' && !this.endpoints.sat && this.hasRGB ) { query_reading = this.hasRGB; - } else if( reading == 'bri' && !this.hasPct && this.hasRGB ) { + } else if( reading == 'bri' && !this.endpoints.pct && this.hasRGB ) { query_reading = this.hasRGB; - } else if( reading == 'pct' && !this.hasPct && this.hasDim ) { + } else if( reading == 'pct' && !this.endpoints.pct && this.hasDim ) { query_reading = 'state'; } var cmd = '{ReadingsVal("'+this.device+'","'+query_reading+'","")}'; - var url = encodeURI( this.connection.base_url + "/fhem?cmd=" + cmd + "&XHR=1"); - this.log( ' querying: ' + url ); var that = this; - that.connection.request.get( { url: url, gzip: true }, - function(err, response, result) { - if( !err && response.statusCode == 200 ) { - value = result.replace(/[\r\n]/g, ""); - that.log(" value: " + value); + this.execute( cmd, + function(result) { + value = result.replace(/[\r\n]/g, ""); + that.log(" value: " + value); - if( value == undefined ) - return value; + if( value == undefined ) + return value; - if( reading != query_reading ) { - if( reading == 'pct' - && query_reading == 'state') { - //FHEM_update( that.device+'-'+query_reading, that.reading2homekit(query_reading, value) ); + if( reading != query_reading ) { + if( reading == 'pct' + && query_reading == 'state') { + //FHEM_update( that.device+'-'+query_reading, that.reading2homekit(query_reading, value) ); - if( match = value.match(/dim(\d*)%/ ) ) - value = parseInt( match[1] ); - else if( value == 'off' ) - value = 0; - else - value = 100; + if( match = value.match(/dim(\d*)%/ ) ) + value = parseInt( match[1] ); + else if( value == 'off' ) + value = 0; + else + value = 100; - } else if(reading == 'hue' && query_reading == that.hasRGB) { - //FHEM_update( that.device+'-'+query_reading, value ); + } else if(reading == 'hue' && query_reading == that.hasRGB) { + //FHEM_update( that.device+'-'+query_reading, value ); - value = parseInt( FHEM_rgb2hsv(value)[0] * 360 ); + value = parseInt( FHEM_rgb2hsv(value)[0] * 360 ); - } else if(reading == 'sat' && query_reading == that.hasRGB) { - //FHEM_update( that.device+'-'+query_reading, value ); + } else if(reading == 'sat' && query_reading == that.hasRGB) { + //FHEM_update( that.device+'-'+query_reading, value ); - value = parseInt( FHEM_rgb2hsv(value)[1] * 100 ); + value = parseInt( FHEM_rgb2hsv(value)[1] * 100 ); - } else if(reading == 'bri' && query_reading == that.hasRGB) { - //FHEM_update( that.device+'-'+query_reading, value ); + } else if(reading == 'bri' && query_reading == that.hasRGB) { + //FHEM_update( that.device+'-'+query_reading, value ); - value = parseInt( FHEM_rgb2hsv(value)[2] * 100 ); + value = parseInt( FHEM_rgb2hsv(value)[2] * 100 ); - } - } else { - value = that.reading2homekit(reading, value); - } + } + } else { + value = that.reading2homekit(reading, value); + } - that.log(" mapped: " + value); - FHEM_update( that.device + '-' + reading, value, true ); + that.log(" mapped: " + value); + FHEM_update( that.device + '-' + reading, value, true ); - if( value == undefined ) - return; - if( callback != undefined ) - callback(value); - return(value); + if( value == undefined ) + return; + if( callback != undefined ) + callback(value); + return(value); - } else { - that.log("There was a problem connecting to FHEM (2)."); - if( response ) - that.log( " " + response.statusCode + ": " + response.statusMessage ); - - } - - } ); + } ); }, informationCharacteristics: function() { @@ -927,18 +937,12 @@ FHEMAccessory.prototype = { designedMaxLength: 255 }] - if( this.endpoints.onOff - && !this.hasTemperature - && !this.hasHumidity - && !this.isBlind - && !this.isThermostat - && !this.isContactSensor - && !this.isOccupancySensor ) { + if( this.endpoints.onOff ) { cTypes.push({ cType: types.POWER_STATE_CTYPE, onRegister: function(characteristic) { characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.name+'-'+that.endpoints.onOff.reading, that); + FHEM_subscribe(characteristic, that.endpoints.onOff.informId, that); }, onUpdate: function(value) { that.command( value == 0 ? 'off' : 'on' ); @@ -948,8 +952,7 @@ FHEMAccessory.prototype = { }, perms: ["pw","pr","ev"], format: "bool", - initialValue: 0, - //initialValue: that.query( that.endpoints.onOff.reading ); + initialValue: FHEM_cached[that.endpoints.onOff.informId], supportEvents: true, supportBonjour: false, manfDescription: "Change the power state", @@ -957,28 +960,27 @@ FHEMAccessory.prototype = { }); } - if( this.hasPct && !this.isBlind ) { + if( this.endpoints.pct && !this.isBlind ) { cTypes.push({ cType: types.BRIGHTNESS_CTYPE, onRegister: function(characteristic) { characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.name+'-pct', that); + FHEM_subscribe(characteristic, that.endpoints.pct.informId, that); }, onUpdate: function(value) { that.command('pct', value); }, onRead: function(callback) { - that.query('pct', function(pct){ + that.query(that.endpoints.pct.reading, function(pct){ callback(pct); }); }, perms: ["pw","pr","ev"], format: "int", - initialValue: 0, - //initialValue: that.query( 'pct' ), + initialValue: FHEM_cached[that.endpoints.pct.informId], supportEvents: true, supportBonjour: false, manfDescription: "Adjust Brightness of the Light", designedMinValue: 0, - designedMaxValue: this.pctMax, + designedMaxValue: 100, designedMinStep: 1, unit: "%" }); @@ -998,7 +1000,7 @@ FHEMAccessory.prototype = { perms: ["pw","pr","ev"], format: "int", initialValue: 0, - //initialValue: that.query( 'state' ), + //initialValue: FHEM_cached[that.endpoints.dim.informId], supportEvents: true, supportBonjour: false, manfDescription: "Adjust Brightness of the Light", @@ -1009,22 +1011,22 @@ FHEMAccessory.prototype = { }); } - if( this.hasHue ) { + if( that.endpoints.hue ) { cTypes.push({ cType: types.HUE_CTYPE, onRegister: function(characteristic) { characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.name+'-hue', that); + FHEM_subscribe(characteristic, that.endpoints.hue.informId, that); }, onUpdate: function(value) { that.command('hue', value); }, onRead: function(callback) { - that.query('hue', function(hue){ + that.query(that.endpoints.hue.reading, function(hue){ callback(hue); }); }, perms: ["pw","pr","ev"], format: "int", - initialValue: 0, + initialValue: FHEM_cached[that.endpoints.hue.informId], supportEvents: true, supportBonjour: false, manfDescription: "Adjust the Hue of the Light", @@ -1050,7 +1052,6 @@ FHEMAccessory.prototype = { perms: ["pw","pr","ev"], format: "int", initialValue: 0, - //initialValue: that.query( 'hue' ), supportEvents: true, supportBonjour: false, manfDescription: "Adjust the Hue of the Light", @@ -1060,7 +1061,7 @@ FHEMAccessory.prototype = { unit: "arcdegrees" }); - if( !this.hasSat ) + if( !this.endpoints.sat ) cTypes.push({ cType: types.SATURATION_CTYPE, onRegister: function(characteristic) { @@ -1075,8 +1076,7 @@ FHEMAccessory.prototype = { }, perms: ["pw","pr","ev"], format: "int", - initialValue: 100, - //initialValue: that.query( 'sat' ), + initialValue: 100, supportEvents: true, supportBonjour: false, manfDescription: "Adjust the Saturation of the Light", @@ -1086,7 +1086,7 @@ FHEMAccessory.prototype = { unit: "%" }); - if( !this.hasPct ) + if( !this.endpoints.pct ) cTypes.push({ cType: types.BRIGHTNESS_CTYPE, onRegister: function(characteristic) { @@ -1102,7 +1102,6 @@ FHEMAccessory.prototype = { perms: ["pw","pr","ev"], format: "int", initialValue: 0, - //initialValue: that.query( 'bri' ), supportEvents: true, supportBonjour: false, manfDescription: "Adjust Brightness of the Light", @@ -1113,22 +1112,22 @@ FHEMAccessory.prototype = { }); } - if( this.hasSat == true ) { + if( this.endpoints.sat ) { cTypes.push({ cType: types.SATURATION_CTYPE, onRegister: function(characteristic) { characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.name+'-sat', that); + FHEM_subscribe(characteristic, that.endpoints.sat.informId, that); }, onUpdate: function(value) { that.command('sat', value); }, onRead: function(callback) { - that.query('sat', function(sat){ + that.query(that.endpoints.sat.reading, function(sat){ callback(sat); }); }, perms: ["pw","pr","ev"], format: "int", - initialValue: 100, + initialValue: FHEM_cached[that.endpoints.sat.informId], supportEvents: true, supportBonjour: false, manfDescription: "Adjust the Saturation of the Light", @@ -1140,7 +1139,7 @@ FHEMAccessory.prototype = { } //FIXME: parse range and set designedMinValue & designedMaxValue & designedMinStep - if( match = this.PossibleSets.match(/\bVolume\b/) ) { + if( match = this.PossibleSets.match(/[\^ ]Volume\b/) ) { cTypes.push({ cType: types.OUTPUTVOLUME_CTYPE, onUpdate: function(value) { that.delayed('volume', value); }, @@ -1166,7 +1165,6 @@ FHEMAccessory.prototype = { }); } - //FIXME: parse range and set designedMinValue & designedMaxValue & designedMinStep if( this.isBlind ) { cTypes.push({ cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, @@ -1182,8 +1180,8 @@ FHEMAccessory.prototype = { }, perms: ["pw","pr","ev"], format: "int", - initialValue: 0, - //initialValue: that.query( that.isBlind ), + //initialValue: 100, + initialValue: FHEM_cached[that.device +'-'+ that.isBlind], supportEvents: false, supportBonjour: false, manfDescription: "Target Blind Position", @@ -1205,8 +1203,7 @@ FHEMAccessory.prototype = { }, perms: ["pr","ev"], format: "int", - initialValue: 0, - //initialValue: that.query( that.isBlind ), + initialValue: FHEM_cached[that.name+'-'+that.isBlind], supportEvents: true, supportBonjour: false, manfDescription: "Current Blind Position", @@ -1215,23 +1212,24 @@ FHEMAccessory.prototype = { designedMinStep: 1, unit: "%" }); + cTypes.push({ cType: types.WINDOW_COVERING_OPERATION_STATE_CTYPE, onRegister: function(characteristic) { - if( that.hasMotor ) { + if( that.endpoints.motor ) { characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.name+'-'+that.hasMotor, that); + FHEM_subscribe(characteristic, that.endpoints.motor.informId, that); } }, onRead: function(callback) { - if( that.hasMotor ) - that.query(that.hasMotor, function(state){ + if( that.endpoints.motor ) + that.query(that.endpoints.motor.reading, function(state){ callback(state); }); }, perms: ["pr","ev"], format: "int", - initialValue: 2, + initialValue: that.endpoints.motor?FHEM_cached[that.endpoints.motor.informId]:2, supportEvents: false, supportBonjour: false, manfDescription: "Position State", @@ -1368,22 +1366,21 @@ FHEMAccessory.prototype = { }); } - if( this.hasTemperature ) { + if( this.endpoints.temperature ) { cTypes.push({ cType: types.CURRENT_TEMPERATURE_CTYPE, onRegister: function(characteristic) { characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.name+'-'+that.hasTemperature, that); + FHEM_subscribe(characteristic, that.endpoints.temperature.informId, that); }, onRead: function(callback) { - that.query(that.hasTemperature, function(temperature){ + that.query(that.endpoints.temperature.reading, function(temperature){ callback(temperature); }); }, perms: ["pr","ev"], format: "float", - initialValue: 20, - //initialValue: that.query(that.hasTemperature), + initialValue: FHEM_cached[that.endpoints.temperature.informId], supportEvents: true, supportBonjour: false, manfDescription: "Current Temperature", @@ -1391,22 +1388,21 @@ FHEMAccessory.prototype = { }); } - if( this.hasHumidity ) { + if( this.endpoints.humidity ) { cTypes.push({ cType: types.CURRENT_RELATIVE_HUMIDITY_CTYPE, onRegister: function(characteristic) { characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.name+'-'+that.hasHumidity, that); + FHEM_subscribe(characteristic, that.endpoints.humidity.informId, that); }, onRead: function(callback) { - that.query(that.hasHumidity, function(humidity){ + that.query(that.endpoints.humidity.reading, function(humidity){ callback(humidity); }); }, perms: ["pr","ev"], format: "int", - initialValue: 50, - //initialValue: that.query(that.hasHumidity), + initialValue: FHEM_cached[that.endpoints.humidity.informId], designedMinValue: 0, designedMaxValue: 100, supportEvents: true, @@ -1421,7 +1417,7 @@ FHEMAccessory.prototype = { }, sType: function() { - if( match = this.PossibleSets.match(/\bvolume\b/) ) { + if( match = this.PossibleSets.match(/[\^ ]volume\b/) ) { return types.SPEAKER_STYPE; } else if( this.isSwitch ) { return types.SWITCH_STYPE; @@ -1435,11 +1431,11 @@ FHEMAccessory.prototype = { return types.CONTACT_SENSOR_STYPE; } else if( this.isOccupancySensor ) { return types.OCCUPANCY_SENSOR_STYPE; - } else if( this.isLight || this.hasPct || this.hasHue || this.hasRGB ) { + } else if( this.isLight || this.endpoints.pct || this.endpoints.hue || this.hasRGB ) { return types.LIGHTBULB_STYPE; - } else if( this.hasTemperature ) { + } else if( this.endpoints.temperature ) { return types.TEMPERATURE_SENSOR_STYPE; - } else if( this.hasHumidity ) { + } else if( this.endpoints.humidity ) { return types.HUMIDITY_SENSOR_STYPE; } else { return types.SWITCH_STYPE; @@ -1482,7 +1478,7 @@ function FHEMdebug_handleRequest(request, response){ } else if( request.url == "/subscriptions" ) { response.write( "home
" ); - response.end( "subscriptions: " + util.inspect(FHEM_subscriptions, {depth: 3}).replace(/\n/g, '
') ); + response.end( "subscriptions: " + util.inspect(FHEM_subscriptions, {depth: 4}).replace(/\n/g, '
') ); } else if( request.url == "/persist" ) { response.write( "home
" ); @@ -1493,6 +1489,11 @@ function FHEMdebug_handleRequest(request, response){ if( unique[info.displayName] ) return; unique[info.displayName] = info.username; + + var accessory = FHEM_subscriptions[key].accessory; + + //var cmd = '{$defs{'+ accessory.device +'}->{homekitID} = "'+info.username+'" if(defined($defs{'+ accessory.device +'}));;}'; + //accessory.execute( cmd ); } ); var keys = Object.keys(unique); From 2e3a26c32f6a30c447e820c40695292184cbb0a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Sun, 2 Aug 2015 13:37:12 +0200 Subject: [PATCH 11/15] more cleanups (renamed endpoints to mappings) added longpoll reconnect on close added winmatic (HM-SEC-WIN) --- platforms/FHEM.js | 367 +++++++++++++++++++++++++++++----------------- 1 file changed, 233 insertions(+), 134 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index fcabfec..92fc974 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -116,7 +116,17 @@ function FHEM_startLongpoll(connection) { continue; if( reading == 'state') { - if( match = value.match(/dim(\d*)%/ ) ) { + if( accessory.isWindow ) { + var level = 50; + if( match = value.match(/^(\d+)/ ) ) + level = parseInt( match[1] ); + else if( value == 'locked' ) + value = 0; + + FHEM_update( device+'-level', level ); + continue; + + } else if( match = value.match(/dim(\d+)%/ ) ) { var pct = parseInt( match[1] ); FHEM_update( device+'-pct', pct ); @@ -150,24 +160,14 @@ function FHEM_startLongpoll(connection) { console.log( "longpoll ended" ); FHEM_longpoll_running = false; - setTimeout( function(){FHEM_startLongpoll(connection)}, 5000 ); + setTimeout( function(){FHEM_startLongpoll(connection)}, 2000 ); - } ).on( 'close', function() { - console.log( "longpoll closed" ); - - FHEM_longpoll_running = false; - setTimeout( function(){FHEM_startLongpoll(connection)}, 5000 ); - - } ).on( 'finish', function() { - console.log( "longpoll finished" ); - - FHEM_longpoll_running = false; - setTimeout( function(){FHEM_startLongpoll(connection)}, 5000 ); } ).on( 'error', function(err) { console.log( "longpoll error: " + err ); FHEM_longpoll_running = false; setTimeout( function(){FHEM_startLongpoll(connection)}, 5000 ); + } ); } @@ -324,50 +324,52 @@ FHEMPlatform.prototype = { 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'); } else if( s.Internals.TYPE == 'structure' ) { that.log( s.Internals.NAME + ' is a structure'); - } else if( s.PossibleSets.match(/\bon\b/) - && s.PossibleSets.match(/\boff\b/) ) { + } else if( s.Attributes.genericDisplayType + || 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); - foundAccessories.push(accessory); } else if( s.PossibleSets.match(/[\^ ]Volume\b/) ) { that.log( s.Internals.NAME + ' has volume'); accessory = new FHEMAccessory(that.log, that.connection, s); - foundAccessories.push(accessory); - - } else if( s.Attributes.genericDisplayType - || s.Attributes.genericDeviceType ) { - accessory = new FHEMAccessory(that.log, that.connection, s); - foundAccessories.push(accessory); } else if( s.Attributes.subType == 'thermostat' || s.Attributes.subType == 'blindActuator' || s.Attributes.subType == 'threeStateSensor' ) { accessory = new FHEMAccessory(that.log, that.connection, s); - foundAccessories.push(accessory); + + } else if( s.Attributes.model == 'HM-SEC-WIN' ) { + accessory = new FHEMAccessory(that.log, that.connection, s); } else if( s.Internals.TYPE == 'PRESENCE' ) { accessory = new FHEMAccessory(that.log, that.connection, s); - foundAccessories.push(accessory); } else if( s.Readings.temperature ) { accessory = new FHEMAccessory(that.log, that.connection, s); - foundAccessories.push(accessory); } else if( s.Readings.humidity ) { accessory = new FHEMAccessory(that.log, that.connection, s); - foundAccessories.push(accessory); } else { that.log( 'ignoring ' + s.Internals.NAME ); } + + if( accessory ) + foundAccessories.push(accessory); + }); } callback(foundAccessories); @@ -389,28 +391,28 @@ FHEMAccessory(log, connection, s) { //log("got json: " + util.inspect(s) ); //log("got json: " + util.inspect(s.Internals) ); - this.endpoints = {}; + this.mappings = {}; var match; if( match = s.PossibleSets.match(/[\^ ]pct\b/) ) { - this.endpoints.pct = { reading: 'pct', cmd: 'pct', min: 0, max: 100 }; - } else if( match = s.PossibleSets.match(/[\^ ]dim\d*%/) ) { + this.mappings.pct = { reading: 'pct', cmd: 'pct', min: 0, max: 100 }; + } else if( match = s.PossibleSets.match(/[\^ ]dim\d+%/) ) { s.hasDim = true; s.pctMax = 100; } - if( match = s.PossibleSets.match(/[\^ ]hue[^\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]; - this.endpoints.hue = { reading: 'hue', cmd: 'hue', min: 0, max: max }; + 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(/[\^ ]sat[^\b\s]*(,(\d+)?)+\b/) ) { s.isLight = true; var max = 100; if( match[2] != undefined ) max = match[2]; - this.endpoints.sat = { reading: 'sat', cmd: 'sat', min: 0, max: max }; + this.mappings.sat = { reading: 'sat', cmd: 'sat', min: 0, max: max }; } if( s.PossibleSets.match(/[\^ ]rgb\b/) ) { @@ -424,15 +426,18 @@ FHEMAccessory(log, connection, s) { } if( s.Readings['measured-temp'] ) - this.endpoints.temperature = { reading: 'measured-temp' }; + this.mappings.temperature = { reading: 'measured-temp' }; else if( s.Readings.temperature ) - this.endpoints.temperature = { reading: 'temperature' }; + this.mappings.temperature = { reading: 'temperature' }; if( s.Readings.humidity ) - this.endpoints.humidity = { reading: 'humidity' }; + this.mappings.humidity = { reading: 'humidity' }; if( s.Readings.motor ) - this.endpoints.motor = { reading: 'motor' }; + this.mappings.motor = { reading: 'motor' }; + + if( s.Readings.direction ) + this.mappings.direction = { reading: 'direction' }; var genericType = s.Attributes.genericDeviceType; @@ -441,22 +446,28 @@ FHEMAccessory(log, connection, s) { if( genericType == 'switch' ) s.isSwitch = true; + else if( genericType == 'light' ) s.isLight = true; - else if( genericType == 'blind' ) { + + else if( genericType == 'blind' + || s.Attributes.subType == 'blindActuator' ) { s.isBlind = 'pct'; - } else if( genericType == 'thermostat' ) + + } else if( genericType == 'window' + || s.Attributes.model == 'HM-SEC-WIN' ) { + s.isWindow = 'level'; + + } else if( genericType == 'thermostat' + || s.Attributes.subType == 'thermostat' ) { s.isThermostat = true; - else if( s.Attributes.subType == 'thermostat' ) - s.isThermostat = true; - else if( s.Attributes.subType == 'blindActuator' ) { - s.isBlind = 'pct'; + } else if( s.Attributes.subType == 'threeStateSensor' ) { s.isContactSensor = true; - if( s.Attributes.model == 'HM-SEC-RHS' ) - s.isWindow = true; + } else if( s.Internals.TYPE == 'PRESENCE' ) s.isOccupancySensor = true; + else if( s.Attributes.model == 'fs20di' ) s.isLight = true; @@ -470,10 +481,10 @@ FHEMAccessory(log, connection, s) { } if( s.Internals.TYPE == 'SONOSPLAYER' ) - this.endpoints.onOff = { reading: 'transportState', cmdOn: 'play', cmdOff: 'pause' }; + this.mappings.onOff = { reading: 'transportState', cmdOn: 'play', cmdOff: 'pause' }; else if( s.PossibleSets.match(/[\^ ]on\b/) && s.PossibleSets.match(/[\^ ]off\b/) ) - this.endpoints.onOff = { reading: 'state', cmdOn: 'on', cmdOff: 'off' }; + this.mappings.onOff = { reading: 'state', cmdOn: 'on', cmdOff: 'off' }; var event_map = s.Attributes.eventMap; if( event_map ) { @@ -491,6 +502,8 @@ FHEMAccessory(log, connection, s) { if( s.isBlind ) log( s.Internals.NAME + ' is blind ['+ s.isBlind +']' ); + else if( s.isWindow ) + log( s.Internals.NAME + ' is window' ); else if( s.isThermostat ) log( s.Internals.NAME + ' is thermostat ['+ s.isThermostat +']' ); else if( s.isContactSensor ) @@ -499,8 +512,8 @@ FHEMAccessory(log, connection, s) { log( s.Internals.NAME + ' is occupancysensor' ); else if( s.hasRGB ) log( s.Internals.NAME + ' has RGB [0-' + s.hasRGB +']'); - else if( this.endpoints.pct ) - log( s.Internals.NAME + ' is dimable [0-'+ this.endpoints.pct.max +']' ); + else if( this.mappings.pct ) + log( s.Internals.NAME + ' is dimable [0-'+ this.mappings.pct.max +']' ); else if( s.hasDim ) log( s.Internals.NAME + ' is dimable [0-'+ s.pctMax +']' ); else if( s.isLight ) @@ -511,16 +524,18 @@ FHEMAccessory(log, connection, s) { if( this.hasOnOff ) log( s.Internals.NAME + ' has OnOff [' + this.hasOnOff + ']' ); - if( this.endpoints.hue ) - log( s.Internals.NAME + ' has hue [0-' + this.endpoints.hue.max +']' ); - if( this.endpoints.sat ) - log( s.Internals.NAME + ' has sat [0-' + this.endpoints.sat.max +']' ); - if( this.endpoints.temperature ) - log( s.Internals.NAME + ' has temperature ['+ this.endpoints.temperature.reading +']' ); - if( this.endpoints.humidity ) - log( s.Internals.NAME + ' has humidity ['+ this.endpoints.humidity.reading +']' ); - if( this.endpoints.motor ) + if( this.mappings.hue ) + 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.temperature ) + 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.motor ) log( s.Internals.NAME + ' has motor' ); + if( this.mappings.direction ) + log( s.Internals.NAME + ' has direction' ); // device info this.name = s.Internals.NAME; @@ -553,6 +568,7 @@ FHEMAccessory(log, connection, s) { this.isLight = s.isLight; this.isBlind = s.isBlind; + this.isWindow = s.isWindow; this.isThermostat = s.isThermostat; this.isContactSensor = s.isContactSensor; this.isOccupancySensor = s.isOccupancySensor; @@ -561,18 +577,18 @@ FHEMAccessory(log, connection, s) { //log( util.inspect(s.Readings) ); if( this.isBlind || this.isDoor || this.isWindow || this.isThermostat ) - delete this.endpoints.onOff; + delete this.mappings.onOff; var that = this; - Object.keys(this.endpoints).forEach(function(key) { - var reading = that.endpoints[key].reading; + Object.keys(this.mappings).forEach(function(key) { + var reading = that.mappings[key].reading; if( s.Readings[reading] && s.Readings[reading].Value ) { var value = s.Readings[reading].Value; value = that.reading2homekit(reading, value); if( value != undefined ) { var inform_id = that.device +'-'+ reading; - that.endpoints[key].informId = inform_id; + that.mappings[key].informId = inform_id; FHEM_cached[inform_id] = value; } } @@ -587,23 +603,29 @@ FHEM_dim_values = [ 'dim06%', 'dim12%', 'dim18%', 'dim25%', 'dim31%', 'dim37%', FHEMAccessory.prototype = { reading2homekit: function(reading,value) { if( reading == 'hue' ) { - value = Math.round(value * 360 / this.endpoints.hue.max); + value = Math.round(value * 360 / this.mappings.hue.max); } else if( reading == 'sat' ) { - value = Math.round(value * 100 / this.endpoints.sat.max); + value = Math.round(value * 100 / this.mappings.sat.max); } else if( reading == 'pct' ) { value = parseInt( value ); + } else if(reading == 'direction') { + if( value.match(/^up/)) + value = 1; + else if( value.match(/^down/)) + value = 0; + else + value = 2; + + value = parseInt(value); + } else if(reading == 'motor') { if( value.match(/^opening/)) value = 1; - else if( value.match(/^up/)) - value = 1; else if( value.match(/^closing/)) value = 0; - else if( value.match(/^down/)) - value = 0; else value = 2; @@ -741,18 +763,28 @@ FHEMAccessory.prototype = { cmd = "set " + this.device + " rgb " + value; } else if( c == 'hue' ) { - value = Math.round(value * this.endpoints.hue.max / 360); + value = Math.round(value * this.mappings.hue.max / 360); cmd = "set " + this.device + " hue " + value; } else if( c == 'sat' ) { - value = value / 100 * this.endpoints.sat.max; + value = value / 100 * this.mappings.sat.max; cmd = "set " + this.device + " sat " + value; } else if( c == 'targetTemperature' ) { cmd = "set " + this.device + " " + this.isThermostat + " " + value; } else if( c == 'targetPosition' ) { - cmd = "set " + this.device + " " + this.isBlind + " " + value; + if( this.isWindow ) { + if( value == 0 ) + value = 'lock'; + + cmd = "set " + this.device + " level " + value; + + } else if( this.isBlind ) + cmd = "set " + this.device + " " + this.isBlind + " " + value; + + else + this.log(this.name + " Unhandled command! cmd=" + c + ", value=" + value); } else { this.log(this.name + " Unhandled command! cmd=" + c + ", value=" + value); @@ -800,17 +832,21 @@ FHEMAccessory.prototype = { this.log(" not cached" ); var query_reading = reading; - if( reading == 'hue' && !this.endpoints.hue && this.hasRGB ) { + if( reading == 'hue' && !this.mappings.hue && this.hasRGB ) { query_reading = this.hasRGB; - } else if( reading == 'sat' && !this.endpoints.sat && this.hasRGB ) { + } else if( reading == 'sat' && !this.mappings.sat && this.hasRGB ) { query_reading = this.hasRGB; - } else if( reading == 'bri' && !this.endpoints.pct && this.hasRGB ) { + } else if( reading == 'bri' && !this.mappings.pct && this.hasRGB ) { query_reading = this.hasRGB; - } else if( reading == 'pct' && !this.endpoints.pct && this.hasDim ) { + } else if( reading == 'pct' && !this.mappings.pct && this.hasDim ) { query_reading = 'state'; + + } else if( reading == 'level' && this.isWindow ) { + query_reading = 'state'; + } var cmd = '{ReadingsVal("'+this.device+'","'+query_reading+'","")}'; @@ -827,15 +863,24 @@ FHEMAccessory.prototype = { if( reading != query_reading ) { if( reading == 'pct' && query_reading == 'state') { - //FHEM_update( that.device+'-'+query_reading, that.reading2homekit(query_reading, value) ); - if( match = value.match(/dim(\d*)%/ ) ) + if( match = value.match(/dim(\d+)%/ ) ) value = parseInt( match[1] ); else if( value == 'off' ) value = 0; else value = 100; + } else if( reading == 'level' + && query_reading == 'state') { + + if( match = value.match(/^(\d+)/ ) ) + value = parseInt( match[1] ); + else if( value == 'locked' ) + value = 0; + else + value = 50; + } else if(reading == 'hue' && query_reading == that.hasRGB) { //FHEM_update( that.device+'-'+query_reading, value ); @@ -937,22 +982,22 @@ FHEMAccessory.prototype = { designedMaxLength: 255 }] - if( this.endpoints.onOff ) { + if( this.mappings.onOff ) { cTypes.push({ cType: types.POWER_STATE_CTYPE, onRegister: function(characteristic) { characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.endpoints.onOff.informId, that); + FHEM_subscribe(characteristic, that.mappings.onOff.informId, that); }, onUpdate: function(value) { that.command( value == 0 ? 'off' : 'on' ); }, onRead: function(callback) { - that.query( that.endpoints.onOff.reading, function(state){ callback(state) } ); + that.query( that.mappings.onOff.reading, function(state){ callback(state) } ); }, perms: ["pw","pr","ev"], format: "bool", - initialValue: FHEM_cached[that.endpoints.onOff.informId], + initialValue: FHEM_cached[that.mappings.onOff.informId], supportEvents: true, supportBonjour: false, manfDescription: "Change the power state", @@ -960,22 +1005,22 @@ FHEMAccessory.prototype = { }); } - if( this.endpoints.pct && !this.isBlind ) { + if( this.mappings.pct && !this.isBlind ) { cTypes.push({ cType: types.BRIGHTNESS_CTYPE, onRegister: function(characteristic) { characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.endpoints.pct.informId, that); + FHEM_subscribe(characteristic, that.mappings.pct.informId, that); }, onUpdate: function(value) { that.command('pct', value); }, onRead: function(callback) { - that.query(that.endpoints.pct.reading, function(pct){ + that.query(that.mappings.pct.reading, function(pct){ callback(pct); }); }, perms: ["pw","pr","ev"], format: "int", - initialValue: FHEM_cached[that.endpoints.pct.informId], + initialValue: FHEM_cached[that.mappings.pct.informId], supportEvents: true, supportBonjour: false, manfDescription: "Adjust Brightness of the Light", @@ -1000,7 +1045,7 @@ FHEMAccessory.prototype = { perms: ["pw","pr","ev"], format: "int", initialValue: 0, - //initialValue: FHEM_cached[that.endpoints.dim.informId], + //initialValue: FHEM_cached[that.mappings.dim.informId], supportEvents: true, supportBonjour: false, manfDescription: "Adjust Brightness of the Light", @@ -1011,22 +1056,22 @@ FHEMAccessory.prototype = { }); } - if( that.endpoints.hue ) { + if( that.mappings.hue ) { cTypes.push({ cType: types.HUE_CTYPE, onRegister: function(characteristic) { characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.endpoints.hue.informId, that); + FHEM_subscribe(characteristic, that.mappings.hue.informId, that); }, onUpdate: function(value) { that.command('hue', value); }, onRead: function(callback) { - that.query(that.endpoints.hue.reading, function(hue){ + that.query(that.mappings.hue.reading, function(hue){ callback(hue); }); }, perms: ["pw","pr","ev"], format: "int", - initialValue: FHEM_cached[that.endpoints.hue.informId], + initialValue: FHEM_cached[that.mappings.hue.informId], supportEvents: true, supportBonjour: false, manfDescription: "Adjust the Hue of the Light", @@ -1061,7 +1106,7 @@ FHEMAccessory.prototype = { unit: "arcdegrees" }); - if( !this.endpoints.sat ) + if( !this.mappings.sat ) cTypes.push({ cType: types.SATURATION_CTYPE, onRegister: function(characteristic) { @@ -1086,7 +1131,7 @@ FHEMAccessory.prototype = { unit: "%" }); - if( !this.endpoints.pct ) + if( !this.mappings.pct ) cTypes.push({ cType: types.BRIGHTNESS_CTYPE, onRegister: function(characteristic) { @@ -1112,22 +1157,22 @@ FHEMAccessory.prototype = { }); } - if( this.endpoints.sat ) { + if( this.mappings.sat ) { cTypes.push({ cType: types.SATURATION_CTYPE, onRegister: function(characteristic) { characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.endpoints.sat.informId, that); + FHEM_subscribe(characteristic, that.mappings.sat.informId, that); }, onUpdate: function(value) { that.command('sat', value); }, onRead: function(callback) { - that.query(that.endpoints.sat.reading, function(sat){ + that.query(that.mappings.sat.reading, function(sat){ callback(sat); }); }, perms: ["pw","pr","ev"], format: "int", - initialValue: FHEM_cached[that.endpoints.sat.informId], + initialValue: FHEM_cached[that.mappings.sat.informId], supportEvents: true, supportBonjour: false, manfDescription: "Adjust the Saturation of the Light", @@ -1216,20 +1261,94 @@ FHEMAccessory.prototype = { cTypes.push({ cType: types.WINDOW_COVERING_OPERATION_STATE_CTYPE, onRegister: function(characteristic) { - if( that.endpoints.motor ) { + if( that.mappings.motor ) { characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.endpoints.motor.informId, that); + FHEM_subscribe(characteristic, that.mappings.motor.informId, that); } }, onRead: function(callback) { - if( that.endpoints.motor ) - that.query(that.endpoints.motor.reading, function(state){ + if( that.mappings.motor ) + that.query(that.mappings.motor.reading, function(state){ callback(state); }); }, perms: ["pr","ev"], format: "int", - initialValue: that.endpoints.motor?FHEM_cached[that.endpoints.motor.informId]:2, + initialValue: that.mappings.motor?FHEM_cached[that.mappings.motor.informId]:2, + supportEvents: false, + supportBonjour: false, + manfDescription: "Position State", + designedMinValue: 0, + designedMaxValue: 2, + designedMinStep: 1, + }); + } + + if( this.isWindow ) { + cTypes.push({ + cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, + onUpdate: function(value) { that.delayed('targetPosition', value, 1500); }, + //onRegister: function(characteristic) { + // characteristic.eventEnabled = true; + // FHEM_subscribe(characteristic, that.name+'-'+that.isBlind, that); + //}, + onRead: function(callback) { + that.query(that.isWindow, function(level){ + callback(level); + }); + }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 50, + supportEvents: false, + supportBonjour: false, + manfDescription: "Target Window Position", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }); + cTypes.push({ + cType: types.WINDOW_COVERING_CURRENT_POSITION_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.name+'-state', that); + FHEM_subscribe(characteristic, that.name+'-'+that.isWindow, that); + }, + onRead: function(callback) { + that.query(that.isWindow, function(pos){ + callback(pos); + }); + }, + perms: ["pr","ev"], + format: "int", + initialValue: 50, + supportEvents: true, + supportBonjour: false, + manfDescription: "Current Window Position", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }); + + cTypes.push({ + cType: types.WINDOW_COVERING_OPERATION_STATE_CTYPE, + onRegister: function(characteristic) { + if( that.mappings.direction ) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.mappings.direction.informId, that); + } + }, + onRead: function(callback) { + if( that.mappings.direction ) + that.query(that.mappings.direction.reading, function(direction){ + callback(direction); + }); + }, + perms: ["pr","ev"], + format: "int", + initialValue: that.mappings.direction?FHEM_cached[that.mappings.direction.informId]:2, supportEvents: false, supportBonjour: false, manfDescription: "Position State", @@ -1302,27 +1421,7 @@ FHEMAccessory.prototype = { }); } - if( this.isWindow ) { - cTypes.push({ - cType: types.CONTACT_SENSOR_STATE_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.name+'-contact', that); - }, - onRead: function(callback) { - that.query('contact', function(state){ - callback(state); - }); - }, - perms: ["pr","ev"], - format: "bool", - initialValue: 0, - supportEvents: false, - supportBonjour: false, - manfDescription: "Contact State", - designedMaxLength: 1 - }); - } else if( this.isContactSensor ) { + if( this.isContactSensor ) { cTypes.push({ cType: types.CONTACT_SENSOR_STATE_CTYPE, onRegister: function(characteristic) { @@ -1366,21 +1465,21 @@ FHEMAccessory.prototype = { }); } - if( this.endpoints.temperature ) { + if( this.mappings.temperature ) { cTypes.push({ cType: types.CURRENT_TEMPERATURE_CTYPE, onRegister: function(characteristic) { characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.endpoints.temperature.informId, that); + FHEM_subscribe(characteristic, that.mappings.temperature.informId, that); }, onRead: function(callback) { - that.query(that.endpoints.temperature.reading, function(temperature){ + that.query(that.mappings.temperature.reading, function(temperature){ callback(temperature); }); }, perms: ["pr","ev"], format: "float", - initialValue: FHEM_cached[that.endpoints.temperature.informId], + initialValue: FHEM_cached[that.mappings.temperature.informId], supportEvents: true, supportBonjour: false, manfDescription: "Current Temperature", @@ -1388,21 +1487,21 @@ FHEMAccessory.prototype = { }); } - if( this.endpoints.humidity ) { + if( this.mappings.humidity ) { cTypes.push({ cType: types.CURRENT_RELATIVE_HUMIDITY_CTYPE, onRegister: function(characteristic) { characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.endpoints.humidity.informId, that); + FHEM_subscribe(characteristic, that.mappings.humidity.informId, that); }, onRead: function(callback) { - that.query(that.endpoints.humidity.reading, function(humidity){ + that.query(that.mappings.humidity.reading, function(humidity){ callback(humidity); }); }, perms: ["pr","ev"], format: "int", - initialValue: FHEM_cached[that.endpoints.humidity.informId], + initialValue: FHEM_cached[that.mappings.humidity.informId], designedMinValue: 0, designedMaxValue: 100, supportEvents: true, @@ -1426,16 +1525,16 @@ FHEMAccessory.prototype = { } else if( this.isThermostat ) { return types.THERMOSTAT_STYPE; } else if( this.isWindow ) { - return types.CONTACT_SENSOR_STYPE; + return types.WINDOW_STYPE; } else if( this.isContactSensor ) { return types.CONTACT_SENSOR_STYPE; } else if( this.isOccupancySensor ) { return types.OCCUPANCY_SENSOR_STYPE; - } else if( this.isLight || this.endpoints.pct || this.endpoints.hue || this.hasRGB ) { + } else if( this.isLight || this.mappings.pct || this.mappings.hue || this.hasRGB ) { return types.LIGHTBULB_STYPE; - } else if( this.endpoints.temperature ) { + } else if( this.mappings.temperature ) { return types.TEMPERATURE_SENSOR_STYPE; - } else if( this.endpoints.humidity ) { + } else if( this.mappings.humidity ) { return types.HUMIDITY_SENSOR_STYPE; } else { return types.SWITCH_STYPE; From d9d55c34ad2ecd996c6400ff8c4be69a7e8cdd1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Sun, 2 Aug 2015 17:40:10 +0200 Subject: [PATCH 12/15] winmatic bug fix addef FHTTK contact sensor --- platforms/FHEM.js | 47 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index 92fc974..906b8ca 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -121,7 +121,7 @@ function FHEM_startLongpoll(connection) { if( match = value.match(/^(\d+)/ ) ) level = parseInt( match[1] ); else if( value == 'locked' ) - value = 0; + level = 0; FHEM_update( device+'-level', level ); continue; @@ -340,7 +340,7 @@ FHEMPlatform.prototype = { && s.PossibleSets.match(/[\^ ]off\b/) ) { accessory = new FHEMAccessory(that.log, that.connection, s); - } else if( s.PossibleSets.match(/[\^ ]Volume\b/) ) { + } else if( s.PossibleSets.match(/[\^ ]Volume\b/) ) { //FIXME: use sets [Pp]lay/[Pp]ause/[Ss]top that.log( s.Internals.NAME + ' has volume'); accessory = new FHEMAccessory(that.log, that.connection, s); @@ -355,7 +355,6 @@ FHEMPlatform.prototype = { } else if( s.Internals.TYPE == 'PRESENCE' ) { accessory = new FHEMAccessory(that.log, that.connection, s); - } else if( s.Readings.temperature ) { accessory = new FHEMAccessory(that.log, that.connection, s); @@ -430,6 +429,11 @@ FHEMAccessory(log, connection, s) { else if( s.Readings.temperature ) this.mappings.temperature = { reading: 'temperature' }; + if( s.Readings.volume ) + this.mappings.volume = { reading: 'volume', cmd: 'volume' }; + else if( s.Readings.Volume ) + this.mappings.volume = { reading: 'Volume', cmd: 'Volume' }; + if( s.Readings.humidity ) this.mappings.humidity = { reading: 'humidity' }; @@ -462,8 +466,11 @@ FHEMAccessory(log, connection, s) { || s.Attributes.subType == 'thermostat' ) { s.isThermostat = true; + } else if( s.Internals.TYPE == 'CUL_FHTTK' ) { + s.isContactSensor = 'Window'; + } else if( s.Attributes.subType == 'threeStateSensor' ) { - s.isContactSensor = true; + s.isContactSensor = 'contact'; } else if( s.Internals.TYPE == 'PRESENCE' ) s.isOccupancySensor = true; @@ -480,7 +487,7 @@ FHEMAccessory(log, connection, s) { log( s.Internals.NAME + ' is NOT a thermostat. set for target temperature missing' ); } - if( s.Internals.TYPE == 'SONOSPLAYER' ) + 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/) ) @@ -507,7 +514,7 @@ FHEMAccessory(log, connection, s) { else if( s.isThermostat ) log( s.Internals.NAME + ' is thermostat ['+ s.isThermostat +']' ); else if( s.isContactSensor ) - log( s.Internals.NAME + ' is contactsensor' ); + log( s.Internals.NAME + ' is contactsensor [' + s.isContactSensor +']' ); else if( s.isOccupancySensor ) log( s.Internals.NAME + ' is occupancysensor' ); else if( s.hasRGB ) @@ -639,7 +646,8 @@ FHEMAccessory.prototype = { value = parseInt(value); - } else if( reading == 'Volume' ) { + } else if( reading == 'volume' + || reading == 'Volume' ) { value = parseInt( value ); } else if( reading == 'contact' ) { @@ -647,7 +655,14 @@ FHEMAccessory.prototype = { value = 1; else value = 0; - //value = 2; + + value = parseInt(value); + + } else if( reading == 'Window' ) { + if( value.match( /^Closed/ ) ) + value = 1; + else + value = 0; value = parseInt(value); @@ -1183,23 +1198,24 @@ FHEMAccessory.prototype = { }); } - //FIXME: parse range and set designedMinValue & designedMaxValue & designedMinStep - if( match = this.PossibleSets.match(/[\^ ]Volume\b/) ) { + //FIXME: use mapping.volume + if( this.mappings.volume ) { cTypes.push({ cType: types.OUTPUTVOLUME_CTYPE, onUpdate: function(value) { that.delayed('volume', value); }, onRegister: function(characteristic) { //characteristic.eventEnabled = true; - //FHEM_subscribe(characteristic, that.name+'-Volume', that); + //FHEM_subscribe(characteristic, that.mappings.volume.informId, that); }, onRead: function(callback) { - that.query('Volume', function(vol){ - callback(vol); + that.query(that.mappings.volume.reading, function(volume){ + callback(volume); }); }, perms: ["pw","pr","ev"], format: "int", initialValue: 10, + //initialValue: FHEM_cached[that.mappings.volume.informId], supportEvents: true, supportBonjour: false, manfDescription: "Adjust the Volume of this device", @@ -1323,6 +1339,7 @@ FHEMAccessory.prototype = { perms: ["pr","ev"], format: "int", initialValue: 50, + //initialValue: FHEM_cached[that.name+'-'+that.isWindow], supportEvents: true, supportBonjour: false, manfDescription: "Current Window Position", @@ -1426,10 +1443,10 @@ FHEMAccessory.prototype = { cType: types.CONTACT_SENSOR_STATE_CTYPE, onRegister: function(characteristic) { characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.name+'-contact', that); + FHEM_subscribe(characteristic, that.name+'-'+that.isContactSensor, that); }, onRead: function(callback) { - that.query('contact', function(state){ + that.query(that.isContactSensor, function(state){ callback(state); }); }, From 86322c30c8a28d000d3c01406e17684a3e4d0709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Sun, 2 Aug 2015 18:52:03 +0200 Subject: [PATCH 13/15] ignore state set_ in addition to set- --- platforms/FHEM.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index 906b8ca..a4e35f9 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -108,6 +108,8 @@ function FHEM_startLongpoll(connection) { var value = d[1]; if( value.match( /^set-/ ) ) continue; + if( value.match( /^set_/ ) ) + continue; var match = d[0].match(/([^-]*)-(.*)/); var device = match[1]; @@ -678,6 +680,8 @@ FHEMAccessory.prototype = { } else if( reading == 'state' ) { if( value.match(/^set-/ ) ) return undefined; + if( value.match(/^set_/ ) ) + return undefined; if( this.event_map != undefined ) { var mapped = this.event_map[value]; From 7cb835e4ea8141842d3f8dfd0c677ac48dfa0672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Thu, 6 Aug 2015 10:02:09 +0200 Subject: [PATCH 14/15] more cleanups handle sonos volume caching added garage door opener started with homematic keymatic (HM-SEC-KEY) even more cleanups :) rgb hue/sat/bri fix added identify for lightbulbs --- platforms/FHEM.js | 372 +++++++++++++++++++++++++++++----------------- 1 file changed, 232 insertions(+), 140 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index a4e35f9..ada3c47 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -20,17 +20,16 @@ var types = require('HAP-NodeJS/accessories/types.js'); var util = require('util'); -// cached readings from longpoll & query -var FHEM_cached = {}; - - // subscriptions to fhem longpoll evens var FHEM_subscriptions = {}; function FHEM_subscribe(characteristic, inform_id, accessory) { FHEM_subscriptions[inform_id] = { 'characteristic': characteristic, 'accessory': accessory }; - //FHEM_subscriptions[inform_id] = characteristic; } + +// cached readings from longpoll & query +var FHEM_cached = {}; +//var FHEM_internal = {}; function FHEM_update(inform_id, value, no_update) { var subscription = FHEM_subscriptions[inform_id]; @@ -39,6 +38,18 @@ FHEM_update(inform_id, value, no_update) { || FHEM_cached[inform_id] === value ) return; + //if( FHEM_internal['.'+subscription.accessory.device+'-homekitID'] == undefined ) { + // var info = subscription.characteristic.accessoryController.tcpServer.accessoryInfo; + + // if( info.username ) { + // var accessory = subscription.accessory; + // var cmd = '{$defs{'+ accessory.device +'}->{homekitID} = "'+info.username+'" if(defined($defs{'+ accessory.device +'}));;}'; + // //accessory.execute( cmd ); + + // FHEM_internal['.'+accessory.device+'-homekitID'] = info.username; + // } + //} + FHEM_cached[inform_id] = value; //FHEM_cached[inform_id] = { 'value': value, 'timestamp': Date.now() }; console.log(" caching: " + inform_id + ": " + value + " as " + typeof(value) ); @@ -118,14 +129,25 @@ function FHEM_startLongpoll(connection) { continue; if( reading == 'state') { - if( accessory.isWindow ) { + if( accessory.mappings.window ) { var level = 50; if( match = value.match(/^(\d+)/ ) ) level = parseInt( match[1] ); else if( value == 'locked' ) level = 0; - FHEM_update( device+'-level', level ); + FHEM_update( accessory.mappings.window.informId, level ); + continue; + + } else if( accessory.mappings.lock ) { + var lock = 0; + if( value.match( /^locked/ ) ) + lock = 1; + + if( value.match( /uncertain/ ) ) + level = 4; + + FHEM_update( accessory.mappings.lock.informId, lock ); continue; } else if( match = value.match(/dim(\d+)%/ ) ) { @@ -134,7 +156,7 @@ function FHEM_startLongpoll(connection) { FHEM_update( device+'-pct', pct ); } - } else if(reading == accessory.hasRGB) { + } 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 ); @@ -354,6 +376,9 @@ FHEMPlatform.prototype = { } else if( s.Attributes.model == 'HM-SEC-WIN' ) { accessory = new FHEMAccessory(that.log, that.connection, s); + } else if( s.Attributes.model == 'HM-SEC-KEY' ) { + accessory = new FHEMAccessory(that.log, that.connection, s); + } else if( s.Internals.TYPE == 'PRESENCE' ) { accessory = new FHEMAccessory(that.log, that.connection, s); @@ -396,7 +421,7 @@ FHEMAccessory(log, connection, s) { var match; if( match = s.PossibleSets.match(/[\^ ]pct\b/) ) { - this.mappings.pct = { reading: 'pct', cmd: 'pct', min: 0, max: 100 }; + this.mappings.pct = { reading: 'pct', cmd: 'pct' }; } else if( match = s.PossibleSets.match(/[\^ ]dim\d+%/) ) { s.hasDim = true; s.pctMax = 100; @@ -418,12 +443,12 @@ FHEMAccessory(log, connection, s) { if( s.PossibleSets.match(/[\^ ]rgb\b/) ) { s.isLight = true; - s.hasRGB = 'rgb'; + this.mappings.rgb = { reading: 'rgb', cmd: 'rgb' }; if( s.Internals.TYPE == 'SWAP_0000002200000003' ) - s.hasRGB = '0B-RGBlevel'; + this.mappings.rgb = { reading: '0B-RGBlevel', cmd: 'rgb' }; } else if( s.PossibleSets.match(/[\^ ]RGB\b/) ) { s.isLight = true; - s.hasRGB = 'RGB'; + this.mappings.rgb = { reading: 'RGB', cmd: 'RGB' }; } if( s.Readings['measured-temp'] ) @@ -433,8 +458,11 @@ FHEMAccessory(log, connection, s) { if( s.Readings.volume ) this.mappings.volume = { reading: 'volume', cmd: 'volume' }; - else if( s.Readings.Volume ) - this.mappings.volume = { reading: 'Volume', cmd: 'Volume' }; + else if( s.Readings.Volume ) { + this.mappings.volume = { reading: 'Volume', cmd: 'Volume', nocache: true }; + if( s.Attributes.generateVolumeEvent == 1 ) + delete this.mappings.volume.nocache; + } if( s.Readings.humidity ) this.mappings.humidity = { reading: 'humidity' }; @@ -453,39 +481,48 @@ FHEMAccessory(log, connection, s) { if( genericType == 'switch' ) s.isSwitch = true; + else if( genericType == 'garage' ) + this.mappings.garage = { cmdOpen: 'on', cmdClose: 'off' }; + else if( genericType == 'light' ) s.isLight = true; else if( genericType == 'blind' || s.Attributes.subType == 'blindActuator' ) { - s.isBlind = 'pct'; + delete this.mappings.pct; + this.mappings.blind = { reading: 'pct', cmd: 'pct' }; } else if( genericType == 'window' || s.Attributes.model == 'HM-SEC-WIN' ) { - s.isWindow = 'level'; + this.mappings.window = { reading: 'level', cmd: 'level' }; + + } else if( genericType == 'lock' + || s.Attributes.model == 'HM-SEC-KEY' ) { + this.mappings.lock = { reading: 'lock' }; } else if( genericType == 'thermostat' || s.Attributes.subType == 'thermostat' ) { s.isThermostat = true; } else if( s.Internals.TYPE == 'CUL_FHTTK' ) { - s.isContactSensor = 'Window'; + this.mappings.contact = { reading: 'Window' }; } else if( s.Attributes.subType == 'threeStateSensor' ) { - s.isContactSensor = 'contact'; + this.mappings.contact = { reading: 'contact' }; } else if( s.Internals.TYPE == 'PRESENCE' ) - s.isOccupancySensor = true; + this.mappings.occupancy = { reading: 'state' }; else if( s.Attributes.model == 'fs20di' ) s.isLight = true; if( s.PossibleSets.match(/[\^ ]desired-temp\b/) ) - s.isThermostat = 'desired-temp'; + this.mappings.thermostat = { reading: 'desired-temp', cmd: 'desired-temp' }; else if( s.PossibleSets.match(/[\^ ]desiredTemperature\b/) ) - s.isThermostat = 'desiredTemperature'; + this.mappings.thermostat = { reading: 'desiredTemperature', cmd: 'desiredTemperature' }; else if( s.isThermostat ) { s.isThermostat = false; + delete this.mappings.thermostat; log( s.Internals.NAME + ' is NOT a thermostat. set for target temperature missing' ); } @@ -509,20 +546,26 @@ FHEMAccessory(log, connection, s) { } } - if( s.isBlind ) - log( s.Internals.NAME + ' is blind ['+ s.isBlind +']' ); - else if( s.isWindow ) + if( this.mappings.door ) + log( s.Internals.NAME + ' is door' ); + else if( this.mappings.garage ) + log( s.Internals.NAME + ' is garage' ); + else if( this.mappings.lock ) + log( s.Internals.NAME + ' is lock ['+ this.mappings.lock.reading +']' ); + else if( this.mappings.window ) log( s.Internals.NAME + ' is window' ); - else if( s.isThermostat ) - log( s.Internals.NAME + ' is thermostat ['+ s.isThermostat +']' ); - else if( s.isContactSensor ) - log( s.Internals.NAME + ' is contactsensor [' + s.isContactSensor +']' ); - else if( s.isOccupancySensor ) + 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 +']' ); + else if( this.mappings.contact ) + log( s.Internals.NAME + ' is contactsensor [' + this.mappings.contact.reading +']' ); + else if( this.mappings.occupancy ) log( s.Internals.NAME + ' is occupancysensor' ); - else if( s.hasRGB ) - log( s.Internals.NAME + ' has RGB [0-' + s.hasRGB +']'); + else if( this.mappings.rgb ) + log( s.Internals.NAME + ' has RGB [0-' + this.mappings.rgb.reading +']'); else if( this.mappings.pct ) - log( s.Internals.NAME + ' is dimable [0-'+ this.mappings.pct.max +']' ); + log( s.Internals.NAME + ' is dimable ['+ this.mappings.pct.reading +']' ); else if( s.hasDim ) log( s.Internals.NAME + ' is dimable [0-'+ s.pctMax +']' ); else if( s.isLight ) @@ -530,7 +573,7 @@ FHEMAccessory(log, connection, s) { else log( s.Internals.NAME + ' is switchable' ); - if( this.hasOnOff ) + if( this.hasOnOff ) log( s.Internals.NAME + ' has OnOff [' + this.hasOnOff + ']' ); if( this.mappings.hue ) @@ -573,19 +616,13 @@ FHEMAccessory(log, connection, s) { this.hasDim = s.hasDim; this.pctMax = s.pctMax; - this.hasRGB = s.hasRGB; this.isLight = s.isLight; - this.isBlind = s.isBlind; - this.isWindow = s.isWindow; - this.isThermostat = s.isThermostat; - this.isContactSensor = s.isContactSensor; - this.isOccupancySensor = s.isOccupancySensor; - this.isWindow = s.isWindow; + this.isSwitch = s.isSwitch; //log( util.inspect(s.Readings) ); - if( this.isBlind || this.isDoor || this.isWindow || this.isThermostat ) + if( this.mappings.blind || this.mappings.door || this.mappings.garage || this.mappings.window || this.mappings.thermostat ) delete this.mappings.onOff; var that = this; @@ -598,7 +635,8 @@ FHEMAccessory(log, connection, s) { if( value != undefined ) { var inform_id = that.device +'-'+ reading; that.mappings[key].informId = inform_id; - FHEM_cached[inform_id] = value; + if( !that.mappings[key].nocache ) + FHEM_cached[inform_id] = value; } } } ); @@ -612,10 +650,10 @@ FHEM_dim_values = [ 'dim06%', 'dim12%', 'dim18%', 'dim25%', 'dim31%', 'dim37%', FHEMAccessory.prototype = { reading2homekit: function(reading,value) { if( reading == 'hue' ) { - value = Math.round(value * 360 / this.mappings.hue.max); + 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.max); + value = Math.round(value * 100 / this.mappings.sat ? this.mappings.sat.max : 100); } else if( reading == 'pct' ) { value = parseInt( value ); @@ -628,8 +666,6 @@ FHEMAccessory.prototype = { else value = 2; - value = parseInt(value); - } else if(reading == 'motor') { if( value.match(/^opening/)) value = 1; @@ -638,16 +674,12 @@ FHEMAccessory.prototype = { else value = 2; - value = parseInt(value); - } else if( reading == 'transportState' ) { if( value == 'PLAYING' ) value = 1; else value = 0; - value = parseInt(value); - } else if( reading == 'volume' || reading == 'Volume' ) { value = parseInt( value ); @@ -658,15 +690,20 @@ FHEMAccessory.prototype = { else value = 0; - value = parseInt(value); - } else if( reading == 'Window' ) { if( value.match( /^Closed/ ) ) value = 1; else value = 0; - value = parseInt(value); + } else if( reading == 'lock' ) { + if( value.match( /^locked/ ) ) + value = 1; + else + value = 0; + + if( value.match( /uncertain/ ) ) + value = 4; } else if( reading == 'temperature' || reading == 'measured-temp' @@ -695,13 +732,11 @@ FHEMAccessory.prototype = { value = 0; else if( value == '000000' ) value = 0; - else if( value.match( /^[A-D]0$/ ) ) + else if( value.match( /^[A-D]0$/ ) ) // FIXME: should be handled by event_map now value = 0; else value = 1; - value = parseInt( value ); - } return(value); @@ -721,21 +756,14 @@ FHEMAccessory.prototype = { command: function(c,value) { this.log(this.name + " sending command " + c + " with value " + value); - if( c == 'on' ) { - if( this.PossibleSets.match(/[\^ ]play\b/i) ) - cmd = "set " + this.device + " play"; - else if( this.PossibleSets.match(/[\^ ]on\b/) ) - cmd = "set " + this.device + " on"; + if( c == 'identify' ) { + if( this.type == 'HUEDevice' ) + cmd = "set " + this.device + "alert select"; else - this.log(this.name + " Unhandled command! cmd=" + c + ", value=" + value); + cmd = "set " + this.device + " toggle;; sleep 1;; set "+ this.device + " toggle"; - } else if( c == 'off' ) { - if( this.PossibleSets.match(/[\^ ]pause\b/i) ) - cmd = "set " + this.device + " pause"; - else if( this.PossibleSets.match(/[\^ ]off\b/) ) - cmd = "set " + this.device + " off"; - else - this.log(this.device + " Unhandled command! cmd=" + c + ", value=" + value); + } else if( c == 'set' ) { + cmd = "set " + this.device + " " + value; } else if( c == 'volume' ) { cmd = "set " + this.device + " volume " + value; @@ -776,10 +804,7 @@ FHEMAccessory.prototype = { value = FHEM_hsv2rgb( h, s, v ); //this.log( this.name + ' rgb : [' + value + ']' ); - if( this.PossibleSets.match(/[\^ ]RGB\b/) ) - cmd = "set " + this.device + " RGB " + value; - else - cmd = "set " + this.device + " rgb " + value; + cmd = "set " + this.device + " " + this.mappings.rgb.cmd + " " + value; } else if( c == 'hue' ) { value = Math.round(value * this.mappings.hue.max / 360); @@ -790,17 +815,17 @@ FHEMAccessory.prototype = { cmd = "set " + this.device + " sat " + value; } else if( c == 'targetTemperature' ) { - cmd = "set " + this.device + " " + this.isThermostat + " " + value; + cmd = "set " + this.device + " " + this.mappings.thermostat.cmd + " " + value; } else if( c == 'targetPosition' ) { - if( this.isWindow ) { + if( this.mappings.window ) { if( value == 0 ) value = 'lock'; - cmd = "set " + this.device + " level " + value; + cmd = "set " + this.device + " " + this.mappings.window.cmd + " " + value; - } else if( this.isBlind ) - cmd = "set " + this.device + " " + this.isBlind + " " + value; + } else if( this.mappings.blind ) + cmd = "set " + this.device + " " + this.mappings.blind.cmd + " " + value; else this.log(this.name + " Unhandled command! cmd=" + c + ", value=" + value); @@ -851,19 +876,22 @@ FHEMAccessory.prototype = { this.log(" not cached" ); var query_reading = reading; - if( reading == 'hue' && !this.mappings.hue && this.hasRGB ) { - query_reading = this.hasRGB; + if( reading == 'hue' && !this.mappings.hue && this.mappings.rgb ) { + query_reading = this.mappings.rgb.reading; - } else if( reading == 'sat' && !this.mappings.sat && this.hasRGB ) { - query_reading = this.hasRGB; + } else if( reading == 'sat' && !this.mappings.sat && this.mappings.rgb ) { + query_reading = this.mappings.rgb.reading; - } else if( reading == 'bri' && !this.mappings.pct && this.hasRGB ) { - query_reading = this.hasRGB; + } else if( reading == 'bri' && !this.mappings.pct && this.mappings.rgb ) { + query_reading = this.mappings.rgb.reading; } else if( reading == 'pct' && !this.mappings.pct && this.hasDim ) { query_reading = 'state'; - } else if( reading == 'level' && this.isWindow ) { + } else if( reading == 'level' && this.mappings.window ) { + query_reading = 'state'; + + } else if( reading == 'lock' && this.mappings.lock ) { query_reading = 'state'; } @@ -900,17 +928,28 @@ FHEMAccessory.prototype = { else value = 50; - } else if(reading == 'hue' && query_reading == that.hasRGB) { + } else if( reading == 'lock' + && query_reading == 'state') { + + if( value.match( /^locked/ ) ) + value = 1; + else + value = 0; + + if( value.match( /uncertain/ ) ) + value = 4; + + } else if(reading == 'hue' && query_reading == that.mappings.rgb) { //FHEM_update( that.device+'-'+query_reading, value ); value = parseInt( FHEM_rgb2hsv(value)[0] * 360 ); - } else if(reading == 'sat' && query_reading == that.hasRGB) { + } else if(reading == 'sat' && query_reading == that.mappings.rgb) { //FHEM_update( that.device+'-'+query_reading, value ); value = parseInt( FHEM_rgb2hsv(value)[1] * 100 ); - } else if(reading == 'bri' && query_reading == that.hasRGB) { + } else if(reading == 'bri' && query_reading == that.mappings.rgb) { //FHEM_update( that.device+'-'+query_reading, value ); value = parseInt( FHEM_rgb2hsv(value)[2] * 100 ); @@ -969,14 +1008,17 @@ FHEMAccessory.prototype = { onUpdate: null, perms: ["pr"], format: "string", - initialValue: this.serial ? this.serial : "A1S2NASF88EW", + initialValue: this.serial ? this.serial : "", supportEvents: false, supportBonjour: false, manfDescription: "SN", designedMaxLength: 255 },{ cType: types.IDENTIFY_CTYPE, - onUpdate: null, + onUpdate: function(value) { + if( this.mappings.onOff ) + that.command( 'identify' ); + }, perms: ["pw"], format: "bool", initialValue: false, @@ -984,8 +1026,7 @@ FHEMAccessory.prototype = { supportBonjour: false, manfDescription: "Identify Accessory", designedMaxLength: 1 - } - ] + }]; }, controlCharacteristics: function(that) { @@ -1009,7 +1050,7 @@ FHEMAccessory.prototype = { FHEM_subscribe(characteristic, that.mappings.onOff.informId, that); }, onUpdate: function(value) { - that.command( value == 0 ? 'off' : 'on' ); + that.command( 'set', value == 0 ? that.mappings.onOff.cmdOff : that.mappings.onOff.cmdOn ); }, onRead: function(callback) { that.query( that.mappings.onOff.reading, function(state){ callback(state) } ); @@ -1024,7 +1065,7 @@ FHEMAccessory.prototype = { }); } - if( this.mappings.pct && !this.isBlind ) { + if( this.mappings.pct ) { cTypes.push({ cType: types.BRIGHTNESS_CTYPE, onRegister: function(characteristic) { @@ -1053,6 +1094,7 @@ FHEMAccessory.prototype = { cType: types.BRIGHTNESS_CTYPE, onRegister: function(characteristic) { characteristic.eventEnabled = true; + // state is alreadi subscribed from POWER_STATE_CTYPE FHEM_subscribe(characteristic, that.name+'-pct', that); }, onUpdate: function(value) { that.delayed('dim', value); }, @@ -1099,13 +1141,13 @@ FHEMAccessory.prototype = { designedMinStep: 1, unit: "arcdegrees" }); - } else if( this.hasRGB ) { + } else if( this.mappings.rgb ) { cTypes.push({ cType: types.HUE_CTYPE, onRegister: function(characteristic) { characteristic.eventEnabled = true; FHEM_subscribe(characteristic, that.name+'-hue', that); - FHEM_subscribe(characteristic, that.name+'-'+that.hasRGB, that); + FHEM_subscribe(characteristic, that.mappings.rgb.informId, that); }, onUpdate: function(value) { that.command('H-rgb', value); }, onRead: function(callback) { @@ -1208,8 +1250,10 @@ FHEMAccessory.prototype = { cType: types.OUTPUTVOLUME_CTYPE, onUpdate: function(value) { that.delayed('volume', value); }, onRegister: function(characteristic) { - //characteristic.eventEnabled = true; - //FHEM_subscribe(characteristic, that.mappings.volume.informId, that); + if( !that.mappings.volume.nocache ) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.mappings.volume.informId, that); + } }, onRead: function(callback) { that.query(that.mappings.volume.reading, function(volume){ @@ -1230,23 +1274,22 @@ FHEMAccessory.prototype = { }); } - if( this.isBlind ) { + if( this.mappings.blind ) { cTypes.push({ cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, onUpdate: function(value) { that.delayed('targetPosition', value, 1500); }, //onRegister: function(characteristic) { // characteristic.eventEnabled = true; - // FHEM_subscribe(characteristic, that.name+'-'+that.isBlind, that); + // FHEM_subscribe(characteristic, that.mappings.blind.informId, that); //}, onRead: function(callback) { - that.query(that.isBlind, function(pct){ + that.query(that.mappings.blind.reading, function(pct){ callback(pct); }); }, perms: ["pw","pr","ev"], format: "int", - //initialValue: 100, - initialValue: FHEM_cached[that.device +'-'+ that.isBlind], + initialValue: FHEM_cached[that.mappings.blind.informId], supportEvents: false, supportBonjour: false, manfDescription: "Target Blind Position", @@ -1259,16 +1302,16 @@ FHEMAccessory.prototype = { cType: types.WINDOW_COVERING_CURRENT_POSITION_CTYPE, onRegister: function(characteristic) { characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.name+'-'+that.isBlind, that); + FHEM_subscribe(characteristic, that.mappings.blind.informId, that); }, onRead: function(callback) { - that.query(that.isBlind, function(pos){ + that.query(that.mappings.blind.reading, function(pos){ callback(pos); }); }, perms: ["pr","ev"], format: "int", - initialValue: FHEM_cached[that.name+'-'+that.isBlind], + initialValue: FHEM_cached[that.mappings.blind.informId], supportEvents: true, supportBonjour: false, manfDescription: "Current Blind Position", @@ -1304,16 +1347,12 @@ FHEMAccessory.prototype = { }); } - if( this.isWindow ) { + if( this.mappings.window ) { cTypes.push({ cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, onUpdate: function(value) { that.delayed('targetPosition', value, 1500); }, - //onRegister: function(characteristic) { - // characteristic.eventEnabled = true; - // FHEM_subscribe(characteristic, that.name+'-'+that.isBlind, that); - //}, onRead: function(callback) { - that.query(that.isWindow, function(level){ + that.query(that.mappings.window.reading, function(level){ callback(level); }); }, @@ -1333,17 +1372,16 @@ FHEMAccessory.prototype = { onRegister: function(characteristic) { characteristic.eventEnabled = true; FHEM_subscribe(characteristic, that.name+'-state', that); - FHEM_subscribe(characteristic, that.name+'-'+that.isWindow, that); + FHEM_subscribe(characteristic, that.mappings.window.informId, that); }, onRead: function(callback) { - that.query(that.isWindow, function(pos){ + that.query(that.mappings.window.reading, function(pos){ callback(pos); }); }, perms: ["pr","ev"], format: "int", - initialValue: 50, - //initialValue: FHEM_cached[that.name+'-'+that.isWindow], + initialValue: FHEM_cached[that.mappings.window.informId], supportEvents: true, supportBonjour: false, manfDescription: "Current Window Position", @@ -1379,23 +1417,75 @@ FHEMAccessory.prototype = { }); } + if( this.mappings.garage ) { + cTypes.push({ + onUpdate: function(value) { + that.command( 'set', value == 0 ? that.mappings.garage.cmdOpen : that.mappings.garage.cmdClose ); + }, + cType: types.TARGET_DOORSTATE_CTYPE, + onRead: function(callback) { + callback(1); + }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 1, + supportEvents: false, + supportBonjour: false, + manfDescription: "Target GarageDoor Position", + designedMinValue: 0, + designedMaxValue: 1, + designedMinStep: 1, + designedMaxLength: 1 + }); + cTypes.push({ + cType: types.CURRENT_DOOR_STATE_CTYPE, + onRead: function(callback) { + callback(4); + }, + perms: ["pr","ev"], + format: "int", + initialValue: 4, + supportEvents: true, + supportBonjour: false, + manfDescription: "Current GarageDoor State", + designedMinValue: 0, + designedMaxValue: 4, + designedMinStep: 1, + designedMaxLength: 1 + }); + + cTypes.push({ + cType: types.OBSTRUCTION_DETECTED_CTYPE, + onRead: function(callback) { + callback(false); + }, + perms: ["pr","ev"], + format: "bool", + initialValue: 0, + supportEvents: false, + supportBonjour: false, + manfDescription: "Obstruction Detected", + designedMaxLength: 1 + }); + } + //FIXME: parse range and set designedMinValue & designedMaxValue & designedMinStep - if( this.isThermostat ) { + if( this.mappings.thermostat ) { cTypes.push({ cType: types.TARGET_TEMPERATURE_CTYPE, onUpdate: function(value) { that.delayed('targetTemperature', value, 1500); }, onRegister: function(characteristic) { characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.name+'-'+that.isThermostat, that); + FHEM_subscribe(characteristic, that.mappings.thermostat.informId, that); }, onRead: function(callback) { - that.query(that.isThermostat, function(temperature){ + that.query(that.mappings.thermostat.reading, function(temperature){ callback(temperature); }); }, perms: ["pw","pr","ev"], format: "float", - initialValue: 20, + initialValue: FHEM_cached[that.mappings.thermostat.informId], supportEvents: false, supportBonjour: false, manfDescription: "Target Temperature", @@ -1442,21 +1532,21 @@ FHEMAccessory.prototype = { }); } - if( this.isContactSensor ) { + if( this.mappings.contact ) { cTypes.push({ cType: types.CONTACT_SENSOR_STATE_CTYPE, onRegister: function(characteristic) { characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.name+'-'+that.isContactSensor, that); + FHEM_subscribe(characteristic, that.mappings.contact.informId, that); }, onRead: function(callback) { - that.query(that.isContactSensor, function(state){ + that.query(that.mappings.contact.reading, function(state){ callback(state); }); }, perms: ["pr","ev"], format: "bool", - initialValue: 0, + initialValue: FHEM_cached[that.mappings.contact.informId], supportEvents: false, supportBonjour: false, manfDescription: "Contact State", @@ -1464,21 +1554,21 @@ FHEMAccessory.prototype = { }); } - if( this.isOccupancySensor ) { + if( this.mappings.occupancy ) { cTypes.push({ cType: types.OCCUPANCY_DETECTED_CTYPE, onRegister: function(characteristic) { characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.name+'-state', that); + FHEM_subscribe(characteristic, that.mappings.occupancy.informId, that); }, onRead: function(callback) { - that.query('state', function(state){ + that.query(that.mappings.occupancy.reading, function(state){ callback(state); }); }, perms: ["pr","ev"], format: "bool", - initialValue: 0, + initialValue: FHEM_cached[that.mappings.occupancy.informId], supportEvents: false, supportBonjour: false, manfDescription: "Occupancy State", @@ -1541,17 +1631,19 @@ FHEMAccessory.prototype = { return types.SPEAKER_STYPE; } else if( this.isSwitch ) { return types.SWITCH_STYPE; - } else if( this.isBlind ) { - return types.WINDOW_COVERING_STYPE; - } else if( this.isThermostat ) { - return types.THERMOSTAT_STYPE; - } else if( this.isWindow ) { + } else if( this.mappings.garage ) { + return types.GARAGE_DOOR_OPENER_STYPE; + } else if( this.mappings.window ) { return types.WINDOW_STYPE; - } else if( this.isContactSensor ) { + } else if( this.mappings.blind ) { + return types.WINDOW_COVERING_STYPE; + } else if( this.mappings.thermostat ) { + return types.THERMOSTAT_STYPE; + } else if( this.mappings.contact ) { return types.CONTACT_SENSOR_STYPE; - } else if( this.isOccupancySensor ) { + } else if( this.mappings.occupancy ) { return types.OCCUPANCY_SENSOR_STYPE; - } else if( this.isLight || this.mappings.pct || this.mappings.hue || this.hasRGB ) { + } else if( this.isLight || this.mappings.pct || this.mappings.hue || this.mappings.rgb ) { return types.LIGHTBULB_STYPE; } else if( this.mappings.temperature ) { return types.TEMPERATURE_SENSOR_STYPE; @@ -1591,17 +1683,17 @@ function FHEMdebug_handleRequest(request, response){ //console.log( request ); if( request.url == "/cached" ) { - response.write( "home
" ); + response.write( "home

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

" ); response.end( "cached: " + util.inspect(FHEM_cached).replace(/\n/g, '
') ); } else if( request.url == "/subscriptions" ) { - response.write( "home
" ); + response.write( "home

" ); response.end( "subscriptions: " + util.inspect(FHEM_subscriptions, {depth: 4}).replace(/\n/g, '
') ); } else if( request.url == "/persist" ) { - response.write( "home
" ); + response.write( "home

" ); var unique = {}; Object.keys(FHEM_subscriptions).forEach(function(key) { var characteristic = FHEM_subscriptions[key].characteristic; From 667ad7a3aaca7bbd0f7fbcad422c48d28ebce3c9 Mon Sep 17 00:00:00 2001 From: Nick Farina Date: Sun, 9 Aug 2015 08:22:00 -0700 Subject: [PATCH 15/15] [Sonos] don't add duplicate rooms Fixes #98 --- platforms/Sonos.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/platforms/Sonos.js b/platforms/Sonos.js index 15523c6..5f38944 100644 --- a/platforms/Sonos.js +++ b/platforms/Sonos.js @@ -13,15 +13,27 @@ SonosPlatform.prototype = { this.log("Fetching Sonos devices."); var that = this; + // track found devices so we don't add duplicates + var roomNamesFound = {}; + sonos.search(function (device) { that.log("Found device at " + device.host); device.deviceDescription(function (err, description) { - if (description["zoneType"] != '11') { - that.log("Found playable device - " + description["roomName"]); - // device is an instance of sonos.Sonos - var accessory = new SonosAccessory(that.log, that.config, device, description); - callback([accessory]); + if (description["zoneType"] != '11' && description["zoneType"] != '8') { // 8 is the Sonos SUB + + var roomName = description["roomName"]; + + if (!roomNamesFound[roomName]) { + roomNamesFound[roomName] = true; + that.log("Found playable device - " + roomName); + // device is an instance of sonos.Sonos + var accessory = new SonosAccessory(that.log, that.config, device, description); + callback([accessory]); + } + else { + that.log("Ignoring playable device with duplicate room name - " + roomName); + } } }); });