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 1/7] 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 2/7] 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 3/7] 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 4/7] 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 5/7] 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 6/7] 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 7/7] 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); });