From c98e7a1d54a77a98164e5e2fda3f8a04a64f5cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Fri, 14 Aug 2015 10:13:23 +0200 Subject: [PATCH 01/10] added zwave model more cleanups --- platforms/FHEM.js | 76 +++++++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 26 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index ada3c47..f16f72b 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -38,17 +38,17 @@ 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( 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 ); + // 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_internal['.'+accessory.device+'-homekitID'] = info.username; + // } + //} FHEM_cached[inform_id] = value; //FHEM_cached[inform_id] = { 'value': value, 'timestamp': Date.now() }; @@ -60,9 +60,9 @@ FHEM_update(inform_id, value, no_update) { } -var FHEM_lastEventTimestamp; +var FHEM_lastEventTime; var FHEM_longpoll_running = false; -//FIXME: force reconnect on xxx bytes received ?, add filter, add since +//FIXME: add filter function FHEM_startLongpoll(connection) { if( FHEM_longpoll_running ) return; @@ -70,6 +70,8 @@ function FHEM_startLongpoll(connection) { var filter = ".*"; var since = "null"; + if( FHEM_lastEventTime ) + since = FHEM_lastEventTime/1000; var query = "/fhem.pl?XHR=1"+ "&inform=type=status;filter="+filter+";since="+since+";fmt=JSON"+ "×tamp="+Date.now() @@ -85,8 +87,9 @@ function FHEM_startLongpoll(connection) { return; input += data; + var lastEventTime = Date.now(); for(;;) { - var nOff = input.indexOf("\n", FHEM_longpollOffset); + var nOff = input.indexOf('\n', FHEM_longpollOffset); if(nOff < 0) break; var l = input.substr(FHEM_longpollOffset, nOff-FHEM_longpollOffset); @@ -113,7 +116,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(); + FHEM_lastEventTime = lastEventTime; var accessory = subscription.accessory; var value = d[1]; @@ -393,11 +396,12 @@ FHEMPlatform.prototype = { } - if( accessory ) + if( accessory && Object.getOwnPropertyNames(accessory).length ) foundAccessories.push(accessory); }); } + callback(foundAccessories); } else { @@ -417,6 +421,20 @@ FHEMAccessory(log, connection, s) { //log("got json: " + util.inspect(s) ); //log("got json: " + util.inspect(s.Internals) ); + if( !(this instanceof FHEMAccessory) ) + return new FHEMAccessory(log, connection, s); + + if( s.Attributes.disable == 1 ) { + that.log( s.Internals.NAME + ' is disabled'); + return null; + + } else if( s.Internals.TYPE == 'structure' ) { + that.log( s.Internals.NAME + ' is a structure'); + return null; + + } + + this.mappings = {}; var match; @@ -570,12 +588,14 @@ FHEMAccessory(log, connection, s) { log( s.Internals.NAME + ' is dimable [0-'+ s.pctMax +']' ); else if( s.isLight ) log( s.Internals.NAME + ' is light' ); - else + else if( this.mappings.onOff || s.isSwitch ) log( s.Internals.NAME + ' is switchable' ); + else + return {}; - if( this.hasOnOff ) - log( s.Internals.NAME + ' has OnOff [' + this.hasOnOff + ']' ); + if( this.mappings.onOff ) + log( s.Internals.NAME + ' has onOff [' + this.mappings.onOff + ']' ); if( this.mappings.hue ) log( s.Internals.NAME + ' has hue [0-' + this.mappings.hue.max +']' ); if( this.mappings.sat ) @@ -594,7 +614,7 @@ FHEMAccessory(log, connection, s) { 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.model = s.Attributes.model ? s.Attributes.model : (s.Internals.model ? s.Internals.model : s.Readings.model.Value); this.PossibleSets = s.PossibleSets; if( this.type == 'CUL_HM' ) { @@ -635,6 +655,7 @@ FHEMAccessory(log, connection, s) { if( value != undefined ) { var inform_id = that.device +'-'+ reading; that.mappings[key].informId = inform_id; + if( !that.mappings[key].nocache ) FHEM_cached[inform_id] = value; } @@ -643,6 +664,10 @@ FHEMAccessory(log, connection, s) { this.log = log; this.connection = connection; + + this.onRegister = function(accessory) { +console.log( ">>>>>>>>>>>>:" + util.inspect(accessory) ); + }; } FHEM_dim_values = [ 'dim06%', 'dim12%', 'dim18%', 'dim25%', 'dim31%', 'dim37%', 'dim43%', 'dim50%', 'dim56%', 'dim62%', 'dim68%', 'dim75%', 'dim81%', 'dim87%', 'dim93%' ]; @@ -732,7 +757,7 @@ FHEMAccessory.prototype = { value = 0; else if( value == '000000' ) value = 0; - else if( value.match( /^[A-D]0$/ ) ) // FIXME: should be handled by event_map now + else if( value.match( /^[A-D]0$/ ) ) //FIXME: is handled by event_map now value = 0; else value = 1; @@ -760,7 +785,7 @@ FHEMAccessory.prototype = { if( this.type == 'HUEDevice' ) cmd = "set " + this.device + "alert select"; else - cmd = "set " + this.device + " toggle;; sleep 1;; set "+ this.device + " toggle"; + cmd = "set " + this.device + " toggle; sleep 1; set "+ this.device + " toggle"; } else if( c == 'set' ) { cmd = "set " + this.device + " " + value; @@ -971,7 +996,7 @@ FHEMAccessory.prototype = { } ); }, - informationCharacteristics: function() { + informationCharacteristics: function(that) { return [ { cType: types.NAME_CTYPE, @@ -1016,7 +1041,7 @@ FHEMAccessory.prototype = { },{ cType: types.IDENTIFY_CTYPE, onUpdate: function(value) { - if( this.mappings.onOff ) + if( that.mappings.onOff ) that.command( 'identify' ); }, perms: ["pw"], @@ -1244,7 +1269,6 @@ FHEMAccessory.prototype = { }); } - //FIXME: use mapping.volume if( this.mappings.volume ) { cTypes.push({ cType: types.OUTPUTVOLUME_CTYPE, @@ -1658,7 +1682,7 @@ FHEMAccessory.prototype = { var that = this; var services = [{ sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: this.informationCharacteristics(), + characteristics: this.informationCharacteristics(that), }, { sType: this.sType(), @@ -1684,8 +1708,8 @@ function FHEMdebug_handleRequest(request, response){ if( request.url == "/cached" ) { response.write( "home

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

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

" ); response.end( "cached: " + util.inspect(FHEM_cached).replace(/\n/g, '
') ); } else if( request.url == "/subscriptions" ) { From 197311534b15fd9a3cbe5d2cf75b6f235482580f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Sat, 12 Sep 2015 20:07:18 +0200 Subject: [PATCH 02/10] started porting to new bridge api --- platforms/FHEM.js | 1582 +++++++-------------------------------------- 1 file changed, 241 insertions(+), 1341 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index f16f72b..a62e4b6 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -3,227 +3,26 @@ // 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", +// "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 types = require("HAP-NodeJS/accessories/types.js"); +var request = require("request"); var util = require('util'); - -// subscriptions to fhem longpoll evens -var FHEM_subscriptions = {}; -function -FHEM_subscribe(characteristic, inform_id, accessory) { - FHEM_subscriptions[inform_id] = { 'characteristic': characteristic, 'accessory': accessory }; -} - -// 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]; - if( subscription != undefined ) { - if( value == undefined - || 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) ); - - if( !no_update ) - subscription.characteristic.updateValue(value, null); - } -} - - -var FHEM_lastEventTime; -var FHEM_longpoll_running = false; -//FIXME: add filter -function FHEM_startLongpoll(connection) { - if( FHEM_longpoll_running ) - return; - FHEM_longpoll_running = true; - - var filter = ".*"; - var since = "null"; - if( FHEM_lastEventTime ) - since = FHEM_lastEventTime/1000; - var query = "/fhem.pl?XHR=1"+ - "&inform=type=status;filter="+filter+";since="+since+";fmt=JSON"+ - "×tamp="+Date.now() - - 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; - var lastEventTime = Date.now(); - 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) ); - - var subscription = FHEM_subscriptions[d[0]]; - if( subscription != undefined ) { -//console.log( "Rcvd: "+(l.length>132 ? l.substring(0,132)+"...("+l.length+")":l) ); - FHEM_lastEventTime = lastEventTime; - var accessory = subscription.accessory; - - var value = d[1]; - if( value.match( /^set-/ ) ) - continue; - 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( accessory.mappings.window ) { - var level = 50; - if( match = value.match(/^(\d+)/ ) ) - level = parseInt( match[1] ); - else if( value == 'locked' ) - level = 0; - - 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+)%/ ) ) { - var pct = parseInt( match[1] ); - - FHEM_update( device+'-pct', pct ); - } - - } else if(accessory.mappings.rgb && reading == accessory.mappings.rgb.reading) { - var hsv = FHEM_rgb2hsv(value); - var hue = parseInt( hsv[0] * 360 ); - var sat = parseInt( hsv[1] * 100 ); - var bri = parseInt( hsv[2] * 100 ); - - //FHEM_update( device+'-'+reading, value, false ); - FHEM_update( device+'-hue', hue ); - FHEM_update( device+'-sat', sat ); - FHEM_update( device+'-bri', bri ); - continue; - } - - value = accessory.reading2homekit(reading, value); - FHEM_update( device+'-'+reading, value ); - - } else { - } - - } - - input = input.substr(FHEM_longpollOffset); - FHEM_longpollOffset = 0; - - } ).on( 'end', function() { - console.log( "longpoll ended" ); - - FHEM_longpoll_running = false; - setTimeout( function(){FHEM_startLongpoll(connection)}, 2000 ); - - } ).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; - - request = request.defaults( { 'auth': auth, 'rejectUnauthorized': false } ); - } - - this.connection = { 'base_url': base_url, 'request': request }; - - FHEM_startLongpoll( this.connection ); + this.server = config["server"]; + this.port = config["port"]; + this.filter = config["filter"]; } function @@ -292,7 +91,7 @@ FHEM_hsv2rgb(h,s,v) { } function -FHEM_rgb2hsv(r,g,b){ +FHEM_rgb2h(r,g,b){ if( r == undefined ) return; @@ -318,15 +117,17 @@ FHEM_rgb2hsv(r,g,b){ h = ( 60 * ( ( r - g ) / c ) + 240 ) / 360; } + return h; + if( M == 0 ) { s = 0; } else { s = c / M; } - v = M/255; + v = M; - return [h,s,v]; + return h; } @@ -334,16 +135,16 @@ FHEMPlatform.prototype = { accessories: function(callback) { this.log("Fetching FHEM switchable devices..."); + var that = this; var foundAccessories = []; - var cmd = 'jsonlist2'; + var cmd = 'jsonlist'; if( this.filter ) cmd += " " + this.filter; - var url = encodeURI( this.connection.base_url + "/fhem?cmd=" + cmd + "&XHR=1"); + var url = encodeURI("http://" + this.server + ":" + this.port + "/fhem?cmd=" + cmd + "&XHR=1"); this.log( 'fetching: ' + url ); - var that = this; - this.connection.request.get( { url: url, json: true, gzip: true }, + request.get( { url: url, json: true }, function(err, response, json) { if( !err && response.statusCode == 200 ) { that.log( 'got: ' + json['totalResultsReturned'] + ' results' ); @@ -351,63 +152,37 @@ 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.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); - - } 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); - - } else if( s.Attributes.subType == 'thermostat' - || s.Attributes.subType == 'blindActuator' - || s.Attributes.subType == 'threeStateSensor' ) { - accessory = new FHEMAccessory(that.log, that.connection, s); - - } 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); - - } else if( s.Readings.temperature ) { - accessory = new FHEMAccessory(that.log, that.connection, s); - - } else if( s.Readings.humidity ) { - accessory = new FHEMAccessory(that.log, that.connection, s); - - } else { - that.log( 'ignoring ' + s.Internals.NAME ); - - } - - if( accessory && Object.getOwnPropertyNames(accessory).length ) + } else if( s.PossibleSets.match(/\bon\b/) + && s.PossibleSets.match(/\boff\b/) ) { + accessory = new FHEMAccessory(that.log, that.server, that.port, s); foundAccessories.push(accessory); + } else if( s.PossibleSets.match(/\bvolume\b/) ) { + that.log( s.Internals.NAME + ' has volume'); + accessory = new FHEMAccessory(that.log, that.server, that.port, s); + foundAccessories.push(accessory); + + } else if( s.Readings.temperature ) { + accessory = new FHEMAccessory(that.log, that.server, that.port, s); + foundAccessories.push(accessory); + + } else if( s.Readings.humidity ) { + accessory = new FHEMAccessory(that.log, that.server, that.port, s); + foundAccessories.push(accessory); + + } else { + that.log( s.Internals.NAME + ' is not switchable'); + + } }); } - callback(foundAccessories); } else { - that.log("There was a problem connecting to FHEM (1)."); - if( response ) - that.log( " " + response.statusCode + ": " + response.statusMessage ); + that.log("There was a problem connecting to FHEM."); } @@ -416,594 +191,217 @@ FHEMPlatform.prototype = { } function -FHEMAccessory(log, connection, s) { +FHEMAccessory(log, server, port, s) { //log( 'sets: ' + s.PossibleSets ); //log("got json: " + util.inspect(s) ); //log("got json: " + util.inspect(s.Internals) ); - if( !(this instanceof FHEMAccessory) ) - return new FHEMAccessory(log, connection, s); - - if( s.Attributes.disable == 1 ) { - that.log( s.Internals.NAME + ' is disabled'); - return null; - - } else if( s.Internals.TYPE == 'structure' ) { - that.log( s.Internals.NAME + ' is a structure'); - return null; - - } - - - this.mappings = {}; - var match; - if( match = s.PossibleSets.match(/[\^ ]pct\b/) ) { - this.mappings.pct = { reading: 'pct', cmd: 'pct' }; - } else if( match = s.PossibleSets.match(/[\^ ]dim\d+%/) ) { - s.hasDim = true; + if( match = s.PossibleSets.match(/\bpct\b/) ) { + s.hasPct = true; s.pctMax = 100; } - if( match = s.PossibleSets.match(/[\^ ]hue[^\b\s]*(,(\d+)?)+\b/) ) { - s.isLight = true; - var max = 360; + if( match = s.PossibleSets.match(/\bhue[^\b\s]*(,(\d*)?)+\b/) ) { + s.hasHue = true; + s.hueMax = 360; if( match[2] != undefined ) - max = match[2]; - this.mappings.hue = { reading: 'hue', cmd: 'hue', min: 0, max: max }; + s.hueMax = match[2]; } - if( match = s.PossibleSets.match(/[\^ ]sat[^\b\s]*(,(\d+)?)+\b/) ) { - s.isLight = true; - var max = 100; + if( match = s.PossibleSets.match(/\bsat[^\b\s]*(,(\d*)?)+\b/) ) { + s.hasSat = true; + s.satMax = 100; if( match[2] != undefined ) - max = match[2]; - this.mappings.sat = { reading: 'sat', cmd: 'sat', min: 0, max: max }; - } - - if( s.PossibleSets.match(/[\^ ]rgb\b/) ) { - s.isLight = true; - this.mappings.rgb = { reading: 'rgb', cmd: 'rgb' }; - if( s.Internals.TYPE == 'SWAP_0000002200000003' ) - this.mappings.rgb = { reading: '0B-RGBlevel', cmd: 'rgb' }; - } else if( s.PossibleSets.match(/[\^ ]RGB\b/) ) { - s.isLight = true; - this.mappings.rgb = { reading: 'RGB', cmd: 'RGB' }; - } - - if( s.Readings['measured-temp'] ) - this.mappings.temperature = { reading: 'measured-temp' }; - 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', nocache: true }; - if( s.Attributes.generateVolumeEvent == 1 ) - delete this.mappings.volume.nocache; + s.satMax = match[2]; } + if( s.PossibleSets.match(/\brgb\b/) ) { + s.hasRGB = true; + } + if( s.Readings.temperature ) + s.hasTemperature = true; if( s.Readings.humidity ) - this.mappings.humidity = { reading: 'humidity' }; + s.hasHumidity = true; - if( s.Readings.motor ) - this.mappings.motor = { reading: 'motor' }; - - if( s.Readings.direction ) - this.mappings.direction = { reading: 'direction' }; - - - var genericType = s.Attributes.genericDeviceType; - if( !genericType ) - genericType = s.Attributes.genericDisplayType; - - 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' ) { - delete this.mappings.pct; - this.mappings.blind = { reading: 'pct', cmd: 'pct' }; - - } else if( genericType == 'window' - || s.Attributes.model == 'HM-SEC-WIN' ) { - 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' ) { - this.mappings.contact = { reading: 'Window' }; - - } else if( s.Attributes.subType == 'threeStateSensor' ) { - this.mappings.contact = { reading: 'contact' }; - - } else if( s.Internals.TYPE == 'PRESENCE' ) - this.mappings.occupancy = { reading: 'state' }; - - else if( s.Attributes.model == 'fs20di' ) - s.isLight = true; - - if( s.PossibleSets.match(/[\^ ]desired-temp\b/) ) - this.mappings.thermostat = { reading: 'desired-temp', cmd: 'desired-temp' }; - else if( s.PossibleSets.match(/[\^ ]desiredTemperature\b/) ) - 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' ); - } - - 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/) ) - this.mappings.onOff = { reading: 'state', cmdOn: 'on', cmdOff: 'off' }; - - 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( 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( 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( 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 ['+ this.mappings.pct.reading +']' ); - else if( s.hasDim ) - log( s.Internals.NAME + ' is dimable [0-'+ s.pctMax +']' ); - else if( s.isLight ) - log( s.Internals.NAME + ' is light' ); - else if( this.mappings.onOff || s.isSwitch ) - log( s.Internals.NAME + ' is switchable' ); + 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 [' + s.pctMax +']'); + else if( s.hasTemperature ) + log( s.Internals.NAME + ' has temperature' ); else - return {}; + log( s.Internals.NAME + ' is switchable'); - - if( this.mappings.onOff ) - log( s.Internals.NAME + ' has onOff [' + this.mappings.onOff + ']' ); - 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' ); + if( s.hasHumidity ) + log( s.Internals.NAME + ' has humidity' ); // device info + this.name = s.Attributes.alias ? s.Attributes.alias : s.Internals.NAME; 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 ? s.Internals.model : s.Readings.model.Value); + 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( 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' ) - this.serial = s.Internals.DEF; - else if( this.type == 'IT' ) + if( this.type == 'CUL_HM' ) 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.hasDim = s.hasDim; + this.hasPct = s.hasPct; 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.isLight = s.isLight; - this.isSwitch = s.isSwitch; + this.hasTemperature = s.hasTemperature; + this.hasHumidity = s.hasHumidity; -//log( util.inspect(s.Readings) ); +log( util.inspect(s.Readings) ); - if( this.mappings.blind || this.mappings.door || this.mappings.garage || this.mappings.window || this.mappings.thermostat ) - delete this.mappings.onOff; - - var that = this; - Object.keys(this.mappings).forEach(function(key) { - var reading = that.mappings[key].reading; - 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.mappings[key].informId = inform_id; - - if( !that.mappings[key].nocache ) - FHEM_cached[inform_id] = value; - } - } - } ); - - this.log = log; - this.connection = connection; - - this.onRegister = function(accessory) { -console.log( ">>>>>>>>>>>>:" + util.inspect(accessory) ); - }; + this.log = log; + this.server = server; + this.port = port; } -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.mappings.hue ? this.mappings.hue.max : 360); - - } else if( reading == 'sat' ) { - value = Math.round(value * 100 / this.mappings.sat ? this.mappings.sat.max : 100); - - } 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; - - } else if(reading == 'motor') { - if( value.match(/^opening/)) - value = 1; - else if( value.match(/^closing/)) - value = 0; - else - value = 2; - - } else if( reading == 'transportState' ) { - if( value == 'PLAYING' ) - value = 1; - else - value = 0; - - } else if( reading == 'volume' - || reading == 'Volume' ) { - value = parseInt( value ); - - } else if( reading == 'contact' ) { - if( value.match( /^closed/ ) ) - value = 1; - else - value = 0; - - } else if( reading == 'Window' ) { - if( value.match( /^Closed/ ) ) - value = 1; - else - value = 0; - - } 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' - || 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.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' ) - value = 0; - else if( value == '000000' ) - value = 0; - else if( value.match( /^[A-D]0$/ ) ) //FIXME: is handled by event_map now - value = 0; - else - value = 1; - - } - - return(value); - }, - - 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 == 'identify' ) { - if( this.type == 'HUEDevice' ) - cmd = "set " + this.device + "alert select"; - else - cmd = "set " + this.device + " toggle; sleep 1; set "+ this.device + " toggle"; - - } else if( c == 'set' ) { - cmd = "set " + this.device + " " + value; - - } else if( c == 'volume' ) { - cmd = "set " + this.device + " volume " + value; - - } else if( c == 'pct' ) { - cmd = "set " + this.device + " pct " + value; - - } else if( c == 'dim' ) { - //if( value < 3 ) - // cmd = "set " + this.device + " off"; - //else - if( value > 97 ) - cmd = "set " + this.device + " on"; - else - 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; - 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 + ']' ); - - if( c == 'H-rgb' ) { - FHEM_update(this.device + '-hue', value, false ); - h = value / 360; - } else if( c == 'S-rgb' ) { - FHEM_update(this.device + '-sat', value, false ); - s = value / 100; - } else if( c == 'B-rgb' ) { - 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 + ']' ); - cmd = "set " + this.device + " " + this.mappings.rgb.cmd + " " + value; - - } else if( c == 'hue' ) { - value = Math.round(value * this.mappings.hue.max / 360); - cmd = "set " + this.device + " hue " + value; - - } else if( c == 'sat' ) { - value = value / 100 * this.mappings.sat.max; - cmd = "set " + this.device + " sat " + value; - - } else if( c == 'targetTemperature' ) { - cmd = "set " + this.device + " " + this.mappings.thermostat.cmd + " " + value; - - } else if( c == 'targetPosition' ) { - if( this.mappings.window ) { - if( value == 0 ) - value = 'lock'; - - cmd = "set " + this.device + " " + this.mappings.window.cmd + " " + value; - - } else if( this.mappings.blind ) - cmd = "set " + this.device + " " + this.mappings.blind.cmd + " " + value; - + if( c == 'on' ) { + if( this.PossibleSets.match(/\bplay\b/i) ) + url = "http://" + this.server + ":" + this.port + "/fhem?cmd=set " + this.device + " play&XHR=1"; + else if( this.PossibleSets.match(/\bon\b/) ) + url = "http://" + this.server + ":" + this.port + "/fhem?cmd=set " + this.device + " on&XHR=1"; else this.log(this.name + " Unhandled command! cmd=" + c + ", value=" + value); - } else { + } else if( c == 'off' ) { + if( this.PossibleSets.match(/\bpause\b/i) ) + url = "http://" + this.server + ":" + this.port + "/fhem?cmd=set " + this.device + " pause&XHR=1"; + else if( this.PossibleSets.match(/\boff\b/) ) + url = "http://" + this.server + ":" + this.port + "/fhem?cmd=set " + this.device + " off&XHR=1"; + else + this.log(this.device + " Unhandled command! cmd=" + c + ", value=" + value); + + } else if( c == 'pct' ) { + url = "http://" + this.server + ":" + this.port + "/fhem?cmd=set " + this.device + " pct " + value + "&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 = "http://" + this.server + ":" + this.port + "/fhem?cmd=set " + this.device + " rgb " + value + "&XHR=1"; + + } else { + value = Math.round(value * this.hueMax / 360); + url = "http://" + this.server + ":" + this.port + "/fhem?cmd=set " + this.device + " hue " + value + "&XHR=1"; + } + + } else if( c == 'sat' ) { + value = value / 100 * this.satMax; + url = "http://" + this.server + ":" + this.port + "/fhem?cmd=set " + this.device + " sat " + value + "&XHR=1"; + + } else if( value != undefined ) { this.log(this.name + " Unhandled command! cmd=" + c + ", value=" + value); - return; } - 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.get( { url: url, gzip: true }, - function(err, response, result) { - if( !err && response.statusCode == 200 ) { - if( callback ) - callback( result ); - - } else { - that.log("There was a problem connecting to FHEM ("+ url +")."); - if( response ) - that.log( " " + response.statusCode + ": " + response.statusMessage ); - - } - - } ).on( 'error', function(err) { - that.log("There was a problem connecting to FHEM ("+ url +"):"+ err); - - } ); + request.put( { url: encodeURI(url) }, + function(err, response) { + if( err ) { + that.log("There was a problem sending command " + c + " to" + that.name); + that.log(url); + } else { + that.log(that.name + " sent command " + c); + that.log(url); + } + } ); }, query: function(reading, callback) { - this.log("query: " + this.name + "-" + reading); + this.log("query: " + reading); - var result = FHEM_cached[this.device + '-' + reading]; - if( result != undefined ) { - this.log(" cached: " + result); - if( callback != undefined ) - callback( result ); - return( result ); - } else - this.log(" not cached" ); - - var query_reading = reading; - if( reading == 'hue' && !this.mappings.hue && this.mappings.rgb ) { - query_reading = this.mappings.rgb.reading; - - } else if( reading == 'sat' && !this.mappings.sat && this.mappings.rgb ) { - query_reading = this.mappings.rgb.reading; - - } 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.mappings.window ) { - query_reading = 'state'; - - } else if( reading == 'lock' && this.mappings.lock ) { - query_reading = 'state'; + var rgb_to_hue = false; + if( reading == 'hue' && !this.hasHue && this.hasRGB ) { + reading = 'rgb'; + rgb_to_hue = true; + } else if( reading == 'state' + && this.type == 'SONOSPLAYER' ) { + reading = 'transportState'; + } - var cmd = '{ReadingsVal("'+this.device+'","'+query_reading+'","")}'; + if( reading == 'rgb' + && this.type == 'SWAP_0000002200000003' ) { + reading = '0B-RGBlevel'; + + } + + var cmd = '{ReadingsVal("'+this.device+'","'+reading+'","")}'; + var url = encodeURI("http://" + this.server + ":" + this.port + "/fhem?cmd=" + cmd + "&XHR=1"); + this.log( ' querying: ' + url ); var that = this; - this.execute( cmd, - function(result) { - value = result.replace(/[\r\n]/g, ""); - that.log(" value: " + value); + request.get( { url: url }, + function(err, response, result) { + if( !err && response.statusCode == 200 ) { + result = result.replace(/[\r\n]/g, ""); + that.log(" result: " + result); - if( value == undefined ) - return value; + if( rgb_to_hue ) { + result = FHEM_rgb2h(result) * 360; + that.hue = result; + } else if( reading == 'hue' ) { + result = Math.round(result * 360 / that.hueMax); + that.hue = result; + } else if( reading == 'sat' ) { + result = Math.round(result * 100 / that.satMax); + that.sat = result; + } else if( reading == 'pct' ) { + that.pct = result; + } else if( reading == 'transportState' ) { + if( result == 'PLAYING' ) + result = 1; + else + result = 0; + that.state = result; + } else if( reading == 'state' ) { + if( result == 'off' ) + result = 0; + else if( result == 'on' ) + result = 1; + else if( result == '000000' ) + result = 0; + else + result = 1; - if( reading != query_reading ) { - if( reading == 'pct' - && query_reading == 'state') { + that.state = result; + } + that.log(" mapped: " + result); - if( match = value.match(/dim(\d+)%/ ) ) - value = parseInt( match[1] ); - else if( value == 'off' ) - value = 0; - else - value = 100; + callback(result); - } else if( reading == 'level' - && query_reading == 'state') { + } else { + that.log("There was a problem connecting to FHEM."); - if( match = value.match(/^(\d+)/ ) ) - value = parseInt( match[1] ); - else if( value == 'locked' ) - value = 0; - else - value = 50; - - } 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.mappings.rgb) { - //FHEM_update( that.device+'-'+query_reading, value ); - - value = parseInt( FHEM_rgb2hsv(value)[1] * 100 ); - - } else if(reading == 'bri' && query_reading == that.mappings.rgb) { - //FHEM_update( that.device+'-'+query_reading, value ); - - value = parseInt( FHEM_rgb2hsv(value)[2] * 100 ); - - } - } else { - value = that.reading2homekit(reading, value); - } - - that.log(" mapped: " + value); - FHEM_update( that.device + '-' + reading, value, true ); - - if( value == undefined ) - return; - if( callback != undefined ) - callback(value); - return(value); - - } ); + } + } ); }, - informationCharacteristics: function(that) { + informationCharacteristics: function() { return [ { cType: types.NAME_CTYPE, onUpdate: null, perms: ["pr"], format: "string", - initialValue: this.alias, + initialValue: this.name, supportEvents: false, supportBonjour: false, manfDescription: "Name of the accessory", @@ -1033,17 +431,14 @@ FHEMAccessory.prototype = { onUpdate: null, perms: ["pr"], format: "string", - initialValue: this.serial ? this.serial : "", + initialValue: this.serial ? this.serial : "A1S2NASF88EW", supportEvents: false, supportBonjour: false, manfDescription: "SN", designedMaxLength: 255 },{ cType: types.IDENTIFY_CTYPE, - onUpdate: function(value) { - if( that.mappings.onOff ) - that.command( 'identify' ); - }, + onUpdate: null, perms: ["pw"], format: "bool", initialValue: false, @@ -1051,7 +446,8 @@ FHEMAccessory.prototype = { supportBonjour: false, manfDescription: "Identify Accessory", designedMaxLength: 1 - }]; + } + ] }, controlCharacteristics: function(that) { @@ -1060,69 +456,46 @@ FHEMAccessory.prototype = { onUpdate: null, perms: ["pr"], format: "string", - initialValue: this.alias, + initialValue: this.name, supportEvents: true, supportBonjour: false, manfDescription: "Name of service", designedMaxLength: 255 }] - if( this.mappings.onOff ) { + if( this.name != undefined + && !this.hasTemperature ) { cTypes.push({ cType: types.POWER_STATE_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.mappings.onOff.informId, that); + onRegister: function(assignedCharacteristic) { +//that.log("onRegister: " + util.inspect(assignedCharacteristic) ); }, onUpdate: function(value) { - that.command( 'set', value == 0 ? that.mappings.onOff.cmdOff : that.mappings.onOff.cmdOn ); + if( value == 0 ) { + that.command("off") + } else { + that.command("on") + } }, onRead: function(callback) { - that.query( that.mappings.onOff.reading, function(state){ callback(state) } ); + that.query('state', function(powerState){ + callback(powerState); + }); }, perms: ["pw","pr","ev"], format: "bool", - initialValue: FHEM_cached[that.mappings.onOff.informId], + initialValue: 0, supportEvents: true, supportBonjour: false, manfDescription: "Change the power state", designedMaxLength: 1 - }); + }) } - if( this.mappings.pct ) { + if( this.hasPct == true ) { cTypes.push({ cType: types.BRIGHTNESS_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.mappings.pct.informId, that); - }, onUpdate: function(value) { that.command('pct', value); }, - onRead: function(callback) { - that.query(that.mappings.pct.reading, function(pct){ - callback(pct); - }); - }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: FHEM_cached[that.mappings.pct.informId], - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust Brightness of the Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }); - } else if( this.hasDim ) { - cTypes.push({ - 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); }, onRead: function(callback) { that.query('pct', function(pct){ callback(pct); @@ -1131,7 +504,6 @@ FHEMAccessory.prototype = { perms: ["pw","pr","ev"], format: "int", initialValue: 0, - //initialValue: FHEM_cached[that.mappings.dim.informId], supportEvents: true, supportBonjour: false, manfDescription: "Adjust Brightness of the Light", @@ -1139,42 +511,13 @@ FHEMAccessory.prototype = { designedMaxValue: this.pctMax, designedMinStep: 1, unit: "%" - }); + }) } - if( that.mappings.hue ) { + if( this.hasHue == true || this.hasRGB == true ) { cTypes.push({ cType: types.HUE_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.mappings.hue.informId, that); - }, onUpdate: function(value) { that.command('hue', value); }, - onRead: function(callback) { - that.query(that.mappings.hue.reading, function(hue){ - callback(hue); - }); - }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: FHEM_cached[that.mappings.hue.informId], - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust the Hue of the Light", - designedMinValue: 0, - designedMaxValue: 360, - designedMinStep: 1, - unit: "arcdegrees" - }); - } 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.mappings.rgb.informId, that); - }, - onUpdate: function(value) { that.command('H-rgb', value); }, onRead: function(callback) { that.query('hue', function(hue){ callback(hue); @@ -1190,16 +533,13 @@ FHEMAccessory.prototype = { designedMaxValue: 360, designedMinStep: 1, unit: "arcdegrees" - }); + }) + } - if( !this.mappings.sat ) + 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('S-rgb', value); }, + onUpdate: function(value) { that.command('sat', value); }, onRead: function(callback) { that.query('sat', function(sat){ callback(sat); @@ -1207,7 +547,7 @@ FHEMAccessory.prototype = { }, perms: ["pw","pr","ev"], format: "int", - initialValue: 100, + initialValue: 100, supportEvents: true, supportBonjour: false, manfDescription: "Adjust the Saturation of the Light", @@ -1215,435 +555,69 @@ FHEMAccessory.prototype = { designedMaxValue: 100, designedMinStep: 1, unit: "%" - }); - - if( !this.mappings.pct ) - 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, - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust Brightness of the Light", - designedMinValue: 0, - designedMaxValue: this.pctMax, - designedMinStep: 1, - unit: "%" - }); + }) } - if( this.mappings.sat ) { + if( match = this.PossibleSets.match(/\bvolume\b/) ) { cTypes.push({ - cType: types.SATURATION_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.mappings.sat.informId, that); - }, - onUpdate: function(value) { that.command('sat', value); }, + cType: types.OUTPIUTVOLUME_CTYPE, + onUpdate: function(value) { that.command('volume', value); }, onRead: function(callback) { - that.query(that.mappings.sat.reading, function(sat){ - callback(sat); - }); - }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: FHEM_cached[that.mappings.sat.informId], - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust the Saturation of the Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }); - } - - if( this.mappings.volume ) { - cTypes.push({ - cType: types.OUTPUTVOLUME_CTYPE, - onUpdate: function(value) { that.delayed('volume', value); }, - onRegister: function(characteristic) { - 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){ - callback(volume); + that.query('volume', function(vol){ + callback(vol); }); }, 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", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1 - //unit: "%" - }); - } - - 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.mappings.blind.informId, that); - //}, - onRead: function(callback) { - that.query(that.mappings.blind.reading, function(pct){ - callback(pct); - }); - }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: FHEM_cached[that.mappings.blind.informId], - supportEvents: false, - supportBonjour: false, - manfDescription: "Target Blind Position", + manfDescription: "Adjust the Volume of the device", 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.mappings.blind.informId, that); - }, - onRead: function(callback) { - that.query(that.mappings.blind.reading, function(pos){ - callback(pos); - }); - }, - perms: ["pr","ev"], - format: "int", - initialValue: FHEM_cached[that.mappings.blind.informId], - 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.mappings.motor ) { - characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.mappings.motor.informId, that); - } - }, - onRead: function(callback) { - if( that.mappings.motor ) - that.query(that.mappings.motor.reading, function(state){ - callback(state); - }); - }, - perms: ["pr","ev"], - format: "int", - 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.mappings.window ) { - cTypes.push({ - cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, - onUpdate: function(value) { that.delayed('targetPosition', value, 1500); }, - onRead: function(callback) { - that.query(that.mappings.window.reading, 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.mappings.window.informId, that); - }, - onRead: function(callback) { - that.query(that.mappings.window.reading, function(pos){ - callback(pos); - }); - }, - perms: ["pr","ev"], - format: "int", - initialValue: FHEM_cached[that.mappings.window.informId], - 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", - designedMinValue: 0, - designedMaxValue: 2, - designedMinStep: 1, - }); - } - - 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.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.mappings.thermostat.informId, that); - }, - onRead: function(callback) { - that.query(that.mappings.thermostat.reading, function(temperature){ - callback(temperature); - }); - }, - perms: ["pw","pr","ev"], - format: "float", - initialValue: FHEM_cached[that.mappings.thermostat.informId], - 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.mappings.contact ) { - cTypes.push({ - cType: types.CONTACT_SENSOR_STATE_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.mappings.contact.informId, that); - }, - onRead: function(callback) { - that.query(that.mappings.contact.reading, function(state){ - callback(state); - }); - }, - perms: ["pr","ev"], - format: "bool", - initialValue: FHEM_cached[that.mappings.contact.informId], - supportEvents: false, - supportBonjour: false, - manfDescription: "Contact State", - designedMaxLength: 1 - }); - } - - if( this.mappings.occupancy ) { - cTypes.push({ - cType: types.OCCUPANCY_DETECTED_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.mappings.occupancy.informId, that); - }, - onRead: function(callback) { - that.query(that.mappings.occupancy.reading, function(state){ - callback(state); - }); - }, - perms: ["pr","ev"], - format: "bool", - initialValue: FHEM_cached[that.mappings.occupancy.informId], - supportEvents: false, - supportBonjour: false, - manfDescription: "Occupancy State", - designedMaxLength: 1 - }); - } - - if( this.mappings.temperature ) { + if( this.hasTemperature ) { cTypes.push({ cType: types.CURRENT_TEMPERATURE_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.mappings.temperature.informId, that); - }, + //onUpdate: function(value) { console.log("Change:",value); execute("Thermostat", "Current Temperature", value); }, onRead: function(callback) { - that.query(that.mappings.temperature.reading, function(temperature){ - callback(temperature); + that.query('temperature', function(temperature){ + callback(parseFloat(temperature)); }); }, perms: ["pr","ev"], format: "float", - initialValue: FHEM_cached[that.mappings.temperature.informId], + initialValue: 20, supportEvents: true, supportBonjour: false, manfDescription: "Current Temperature", unit: "celsius" - }); + }) } - if( this.mappings.humidity ) { + if( this.hasHumidity ) { cTypes.push({ cType: types.CURRENT_RELATIVE_HUMIDITY_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.mappings.humidity.informId, that); - }, + //onUpdate: function(value) { console.log("Change:",value); execute("Thermostat", "Current Temperature", value); }, onRead: function(callback) { - that.query(that.mappings.humidity.reading, function(humidity){ - callback(humidity); + that.query('humidity', function(humidity){ + callback(parseInt(humidity)); }); }, perms: ["pr","ev"], format: "int", - initialValue: FHEM_cached[that.mappings.humidity.informId], + initialValue: 50, designedMinValue: 0, designedMaxValue: 100, supportEvents: true, supportBonjour: false, manfDescription: "Current Humidity", unit: "%" - }); + }) } @@ -1651,30 +625,16 @@ FHEMAccessory.prototype = { }, sType: function() { - if( match = this.PossibleSets.match(/[\^ ]volume\b/) ) { - return types.SPEAKER_STYPE; - } else if( this.isSwitch ) { - return types.SWITCH_STYPE; - } else if( this.mappings.garage ) { - return types.GARAGE_DOOR_OPENER_STYPE; - } else if( this.mappings.window ) { - return types.WINDOW_STYPE; - } 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.mappings.occupancy ) { - return types.OCCUPANCY_SENSOR_STYPE; - } 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; - } else if( this.mappings.humidity ) { - return types.HUMIDITY_SENSOR_STYPE; + if( match = this.PossibleSets.match(/\bvolume\b/) ) { + return types.SPEAKER_STYPE + } else if( this.hasTemperature ) { + return types.TEMPERATURE_SENSOR_STYPE + } else if( this.hasHumidity ) { + return types.HUMIDITY_SENSOR_STYPE + } else if( this.hasPct || this.hasHue || this.hasRGB ) { + return types.LIGHTBULB_STYPE } else { - return types.SWITCH_STYPE; + return types.SWITCH_STYPE } }, @@ -1682,7 +642,7 @@ FHEMAccessory.prototype = { var that = this; var services = [{ sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: this.informationCharacteristics(that), + characteristics: this.informationCharacteristics(), }, { sType: this.sType(), @@ -1695,63 +655,3 @@ 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){ - //console.log( request ); - - if( request.url == "/cached" ) { - response.write( "home

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

" ); - 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: 4}).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 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); - 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" ); -} - -var FHEMdebug_server = http.createServer( FHEMdebug_handleRequest ); - -FHEMdebug_server.on('error', function (e) { - console.log("Server error: " + e); -}); - -//Lets start our server -FHEMdebug_server.listen(FHEMdebug_PORT, function(){ - console.log("Server listening on: http://:%s", FHEMdebug_PORT); -}); - From ca941bd3508e1f229389dfc426001e97ba8c286a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Sat, 12 Sep 2015 20:21:37 +0200 Subject: [PATCH 03/10] now for real... --- platforms/FHEM.js | 1649 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 1408 insertions(+), 241 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index a62e4b6..01b9b21 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -3,26 +3,228 @@ // Remember to add platform to config.json. Example: // "platforms": [ // { -// "platform": "FHEM", -// "name": "FHEM", -// "server": "127.0.0.1", -// "port": "8083", -// "filter": "room=xyz" +// '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 request = require("request"); + +var types = require('HAP-NodeJS/accessories/types.js'); var util = require('util'); + +// subscriptions to fhem longpoll evens +var FHEM_subscriptions = {}; +function +FHEM_subscribe(characteristic, inform_id, accessory) { + FHEM_subscriptions[inform_id] = { 'characteristic': characteristic, 'accessory': accessory }; +} + +// 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]; + if( subscription != undefined ) { + if( value == undefined + || 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) ); + + if( !no_update ) + subscription.characteristic.setValue(value, undefined, 'fromFhem'); + //subscription.characteristic.updateValue(value, null); + } +} + + +var FHEM_lastEventTime; +var FHEM_longpoll_running = false; +//FIXME: add filter +function FHEM_startLongpoll(connection) { + if( FHEM_longpoll_running ) + return; + FHEM_longpoll_running = true; + + var filter = ".*"; + var since = "null"; + if( FHEM_lastEventTime ) + since = FHEM_lastEventTime/1000; + var query = "/fhem.pl?XHR=1"+ + "&inform=type=status;filter="+filter+";since="+since+";fmt=JSON"+ + "×tamp="+Date.now() + + 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; + var lastEventTime = Date.now(); + 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) ); + + var subscription = FHEM_subscriptions[d[0]]; + if( subscription != undefined ) { +//console.log( "Rcvd: "+(l.length>132 ? l.substring(0,132)+"...("+l.length+")":l) ); + FHEM_lastEventTime = lastEventTime; + var accessory = subscription.accessory; + + var value = d[1]; + if( value.match( /^set-/ ) ) + continue; + 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( accessory.mappings.window ) { + var level = 50; + if( match = value.match(/^(\d+)/ ) ) + level = parseInt( match[1] ); + else if( value == 'locked' ) + level = 0; + + 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+)%/ ) ) { + var pct = parseInt( match[1] ); + + FHEM_update( device+'-pct', pct ); + } + + } else if(accessory.mappings.rgb && reading == accessory.mappings.rgb.reading) { + var hsv = FHEM_rgb2hsv(value); + var hue = parseInt( hsv[0] * 360 ); + var sat = parseInt( hsv[1] * 100 ); + var bri = parseInt( hsv[2] * 100 ); + + //FHEM_update( device+'-'+reading, value, false ); + FHEM_update( device+'-hue', hue ); + FHEM_update( device+'-sat', sat ); + FHEM_update( device+'-bri', bri ); + continue; + } + + value = accessory.reading2homekit(reading, value); + FHEM_update( device+'-'+reading, value ); + + } else { + } + + } + + input = input.substr(FHEM_longpollOffset); + FHEM_longpollOffset = 0; + + } ).on( 'end', function() { + console.log( "longpoll ended" ); + + FHEM_longpoll_running = false; + setTimeout( function(){FHEM_startLongpoll(connection)}, 2000 ); + + } ).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"]; + 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; + + request = request.defaults( { 'auth': auth, 'rejectUnauthorized': false } ); + } + + this.connection = { 'base_url': base_url, 'request': request }; + + FHEM_startLongpoll( this.connection ); } function @@ -91,7 +293,7 @@ FHEM_hsv2rgb(h,s,v) { } function -FHEM_rgb2h(r,g,b){ +FHEM_rgb2hsv(r,g,b){ if( r == undefined ) return; @@ -117,17 +319,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]; } @@ -135,16 +335,23 @@ FHEMPlatform.prototype = { accessories: function(callback) { this.log("Fetching FHEM switchable devices..."); - var that = this; var foundAccessories = []; - var cmd = 'jsonlist'; + // mechanism to ensure callback is only executed once all requests complete + var asyncCalls = 0; + function callbackLater() { if (--asyncCalls == 0) callback(foundAccessories); } + + var cmd = 'jsonlist2'; if( this.filter ) cmd += " " + this.filter; - var url = encodeURI("http://" + this.server + ":" + this.port + "/fhem?cmd=" + cmd + "&XHR=1"); + var url = encodeURI( this.connection.base_url + "/fhem?cmd=" + cmd + "&XHR=1"); this.log( 'fetching: ' + url ); - request.get( { url: url, json: true }, + + asyncCalls++; + + var that = this; + this.connection.request.get( { url: url, json: true, gzip: true }, function(err, response, json) { if( !err && response.statusCode == 200 ) { that.log( 'got: ' + json['totalResultsReturned'] + ' results' ); @@ -152,37 +359,64 @@ 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.PossibleSets.match(/\bon\b/) - && s.PossibleSets.match(/\boff\b/) ) { - accessory = new FHEMAccessory(that.log, that.server, that.port, s); - foundAccessories.push(accessory); + } else if( s.Internals.TYPE == 'structure' ) { + that.log( s.Internals.NAME + ' is a structure'); - } else if( s.PossibleSets.match(/\bvolume\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); + + } 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.server, that.port, s); - foundAccessories.push(accessory); + accessory = new FHEMAccessory(that.log, that.connection, s); + + } else if( s.Attributes.subType == 'thermostat' + || s.Attributes.subType == 'blindActuator' + || s.Attributes.subType == 'threeStateSensor' ) { + accessory = new FHEMAccessory(that.log, that.connection, s); + + } 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); } else if( s.Readings.temperature ) { - accessory = new FHEMAccessory(that.log, that.server, that.port, s); - foundAccessories.push(accessory); + accessory = new FHEMAccessory(that.log, that.connection, s); } else if( s.Readings.humidity ) { - accessory = new FHEMAccessory(that.log, that.server, that.port, s); - foundAccessories.push(accessory); + accessory = new FHEMAccessory(that.log, that.connection, s); } else { - that.log( s.Internals.NAME + ' is not switchable'); + that.log( 'ignoring ' + s.Internals.NAME ); } + + if( accessory && Object.getOwnPropertyNames(accessory).length ) + foundAccessories.push(accessory); + }); } - callback(foundAccessories); + + //callback(foundAccessories); + callbackLater(); } else { - that.log("There was a problem connecting to FHEM."); + that.log("There was a problem connecting to FHEM (1)."); + if( response ) + that.log( " " + response.statusCode + ": " + response.statusMessage ); } @@ -191,217 +425,598 @@ FHEMPlatform.prototype = { } function -FHEMAccessory(log, server, port, s) { +FHEMAccessory(log, connection, s) { //log( 'sets: ' + s.PossibleSets ); //log("got json: " + util.inspect(s) ); //log("got json: " + util.inspect(s.Internals) ); + if( !(this instanceof FHEMAccessory) ) + return new FHEMAccessory(log, connection, s); + + if( s.Attributes.disable == 1 ) { + that.log( s.Internals.NAME + ' is disabled'); + return null; + + } else if( s.Internals.TYPE == 'structure' ) { + that.log( s.Internals.NAME + ' is a structure'); + return null; + + } + + + this.mappings = {}; + var match; - if( match = s.PossibleSets.match(/\bpct\b/) ) { - s.hasPct = true; + if( match = s.PossibleSets.match(/[\^ ]pct\b/) ) { + this.mappings.pct = { reading: 'pct', cmd: 'pct' }; + } else if( match = s.PossibleSets.match(/[\^ ]dim\d+%/) ) { + s.hasDim = true; s.pctMax = 100; } - if( match = s.PossibleSets.match(/\bhue[^\b\s]*(,(\d*)?)+\b/) ) { - s.hasHue = true; - s.hueMax = 360; + if( match = s.PossibleSets.match(/[\^ ]hue[^\b\s]*(,(\d+)?)+\b/) ) { + s.isLight = true; + var max = 360; if( match[2] != undefined ) - s.hueMax = match[2]; + max = match[2]; + this.mappings.hue = { reading: 'hue', cmd: 'hue', min: 0, max: max }; } - if( match = s.PossibleSets.match(/\bsat[^\b\s]*(,(\d*)?)+\b/) ) { - s.hasSat = true; - s.satMax = 100; + if( match = s.PossibleSets.match(/[\^ ]sat[^\b\s]*(,(\d+)?)+\b/) ) { + s.isLight = true; + var max = 100; if( match[2] != undefined ) - s.satMax = match[2]; + max = match[2]; + this.mappings.sat = { reading: 'sat', cmd: 'sat', min: 0, max: max }; + } + + if( s.PossibleSets.match(/[\^ ]rgb\b/) ) { + s.isLight = true; + this.mappings.rgb = { reading: 'rgb', cmd: 'rgb' }; + if( s.Internals.TYPE == 'SWAP_0000002200000003' ) + this.mappings.rgb = { reading: '0B-RGBlevel', cmd: 'rgb' }; + } else if( s.PossibleSets.match(/[\^ ]RGB\b/) ) { + s.isLight = true; + this.mappings.rgb = { reading: 'RGB', cmd: 'RGB' }; + } + + if( s.Readings['measured-temp'] ) + this.mappings.temperature = { reading: 'measured-temp' }; + 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', nocache: true }; + if( s.Attributes.generateVolumeEvent == 1 ) + delete this.mappings.volume.nocache; } - if( s.PossibleSets.match(/\brgb\b/) ) { - s.hasRGB = true; - } - if( s.Readings.temperature ) - s.hasTemperature = true; if( s.Readings.humidity ) - s.hasHumidity = true; + this.mappings.humidity = { reading: 'humidity' }; - 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 [' + s.pctMax +']'); - else if( s.hasTemperature ) - log( s.Internals.NAME + ' has temperature' ); + if( s.Readings.motor ) + this.mappings.motor = { reading: 'motor' }; + + if( s.Readings.direction ) + this.mappings.direction = { reading: 'direction' }; + + + var genericType = s.Attributes.genericDeviceType; + if( !genericType ) + genericType = s.Attributes.genericDisplayType; + + 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' ) { + delete this.mappings.pct; + this.mappings.blind = { reading: 'pct', cmd: 'pct' }; + + } else if( genericType == 'window' + || s.Attributes.model == 'HM-SEC-WIN' ) { + 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' ) { + this.mappings.contact = { reading: 'Window' }; + + } else if( s.Attributes.subType == 'threeStateSensor' ) { + this.mappings.contact = { reading: 'contact' }; + + } else if( s.Internals.TYPE == 'PRESENCE' ) + this.mappings.occupancy = { reading: 'state' }; + + else if( s.Attributes.model == 'fs20di' ) + s.isLight = true; + + if( s.PossibleSets.match(/[\^ ]desired-temp\b/) ) + this.mappings.thermostat = { reading: 'desired-temp', cmd: 'desired-temp' }; + else if( s.PossibleSets.match(/[\^ ]desiredTemperature\b/) ) + 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' ); + } + + 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/) ) + this.mappings.onOff = { reading: 'state', cmdOn: 'on', cmdOff: 'off' }; + + 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( 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( 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( 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 ['+ this.mappings.pct.reading +']' ); + else if( s.hasDim ) + log( s.Internals.NAME + ' is dimable [0-'+ s.pctMax +']' ); + else if( s.isLight ) + log( s.Internals.NAME + ' is light' ); + else if( this.mappings.onOff || s.isSwitch ) + log( s.Internals.NAME + ' is switchable' ); else - log( s.Internals.NAME + ' is switchable'); + return {}; - if( s.hasHumidity ) - log( s.Internals.NAME + ' has humidity' ); + + if( this.mappings.onOff ) + log( s.Internals.NAME + ' has onOff [' + this.mappings.onOff + ']' ); + 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' ); + +log(s); // device info - this.name = s.Attributes.alias ? s.Attributes.alias : s.Internals.NAME; 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.model = s.Readings.model ? s.Readings.model.Value + : (s.Attributes.model ? s.Attributes.model + : ( s.Internals.model ? s.Internals.model : '' ) ); this.PossibleSets = s.PossibleSets; - if( this.type == 'CUL_HM' ) + if( this.type == 'CUL_HM' ) { + 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' ) + 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.isLight = s.isLight; + this.isSwitch = s.isSwitch; -log( util.inspect(s.Readings) ); +//log( util.inspect(s.Readings) ); - this.log = log; - this.server = server; - this.port = port; + if( this.mappings.blind || this.mappings.door || this.mappings.garage || this.mappings.window || this.mappings.thermostat ) + delete this.mappings.onOff; + + var that = this; + Object.keys(this.mappings).forEach(function(key) { + var reading = that.mappings[key].reading; + 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.mappings[key].informId = inform_id; + + if( !that.mappings[key].nocache ) + FHEM_cached[inform_id] = value; + } + } + } ); + + this.log = log; + this.connection = connection; + + this.onRegister = function(accessory) { +console.log( ">>>>>>>>>>>>:" + util.inspect(accessory) ); + }; } +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.mappings.hue ? this.mappings.hue.max : 360); + + } else if( reading == 'sat' ) { + value = Math.round(value * 100 / this.mappings.sat ? this.mappings.sat.max : 100); + + } 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; + + } else if(reading == 'motor') { + if( value.match(/^opening/)) + value = 1; + else if( value.match(/^closing/)) + value = 0; + else + value = 2; + + } else if( reading == 'transportState' ) { + if( value == 'PLAYING' ) + value = 1; + else + value = 0; + + } else if( reading == 'volume' + || reading == 'Volume' ) { + value = parseInt( value ); + + } else if( reading == 'contact' ) { + if( value.match( /^closed/ ) ) + value = 1; + else + value = 0; + + } else if( reading == 'Window' ) { + if( value.match( /^Closed/ ) ) + value = 1; + else + value = 0; + + } 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' + || 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.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' ) + value = 0; + else if( value == '000000' ) + value = 0; + else if( value.match( /^[A-D]0$/ ) ) //FIXME: is handled by event_map now + value = 0; + else + value = 1; + + } + + return(value); + }, + + 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 = "http://" + this.server + ":" + this.port + "/fhem?cmd=set " + this.device + " play&XHR=1"; - else if( this.PossibleSets.match(/\bon\b/) ) - url = "http://" + this.server + ":" + this.port + "/fhem?cmd=set " + this.device + " on&XHR=1"; + if( c == 'identify' ) { + if( this.type == 'HUEDevice' ) + cmd = "set " + this.device + "alert select"; + else + cmd = "set " + this.device + " toggle; sleep 1; set "+ this.device + " toggle"; + + } else if( c == 'set' ) { + cmd = "set " + this.device + " " + value; + + } else if( c == 'volume' ) { + cmd = "set " + this.device + " volume " + value; + + } else if( c == 'pct' ) { + cmd = "set " + this.device + " pct " + value; + + } else if( c == 'dim' ) { + //if( value < 3 ) + // cmd = "set " + this.device + " off"; + //else + if( value > 97 ) + cmd = "set " + this.device + " on"; + else + 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; + 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 + ']' ); + + if( c == 'H-rgb' ) { + FHEM_update(this.device + '-hue', value, false ); + h = value / 360; + } else if( c == 'S-rgb' ) { + FHEM_update(this.device + '-sat', value, false ); + s = value / 100; + } else if( c == 'B-rgb' ) { + 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 + ']' ); + cmd = "set " + this.device + " " + this.mappings.rgb.cmd + " " + value; + + } else if( c == 'hue' ) { + value = Math.round(value * this.mappings.hue.max / 360); + cmd = "set " + this.device + " hue " + value; + + } else if( c == 'sat' ) { + value = value / 100 * this.mappings.sat.max; + cmd = "set " + this.device + " sat " + value; + + } else if( c == 'targetTemperature' ) { + cmd = "set " + this.device + " " + this.mappings.thermostat.cmd + " " + value; + + } else if( c == 'targetPosition' ) { + if( this.mappings.window ) { + if( value == 0 ) + value = 'lock'; + + cmd = "set " + this.device + " " + this.mappings.window.cmd + " " + 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); - } else if( c == 'off' ) { - if( this.PossibleSets.match(/\bpause\b/i) ) - url = "http://" + this.server + ":" + this.port + "/fhem?cmd=set " + this.device + " pause&XHR=1"; - else if( this.PossibleSets.match(/\boff\b/) ) - url = "http://" + this.server + ":" + this.port + "/fhem?cmd=set " + this.device + " off&XHR=1"; - else - this.log(this.device + " Unhandled command! cmd=" + c + ", value=" + value); - - } else if( c == 'pct' ) { - url = "http://" + this.server + ":" + this.port + "/fhem?cmd=set " + this.device + " pct " + value + "&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 = "http://" + this.server + ":" + this.port + "/fhem?cmd=set " + this.device + " rgb " + value + "&XHR=1"; - - } else { - value = Math.round(value * this.hueMax / 360); - url = "http://" + this.server + ":" + this.port + "/fhem?cmd=set " + this.device + " hue " + value + "&XHR=1"; - } - - } else if( c == 'sat' ) { - value = value / 100 * this.satMax; - url = "http://" + this.server + ":" + this.port + "/fhem?cmd=set " + this.device + " sat " + value + "&XHR=1"; - - } else if( value != undefined ) { + } else { this.log(this.name + " Unhandled command! cmd=" + c + ", value=" + value); + return; } - var that = this; - request.put( { url: encodeURI(url) }, - function(err, response) { - if( err ) { - that.log("There was a problem sending command " + c + " to" + that.name); - that.log(url); - } else { - that.log(that.name + " sent command " + c); - that.log(url); - } - } ); + this.execute(cmd); }, - query: function(reading, callback) { - this.log("query: " + reading); - - var rgb_to_hue = false; - if( reading == 'hue' && !this.hasHue && this.hasRGB ) { - reading = 'rgb'; - rgb_to_hue = true; - - } else if( reading == 'state' - && this.type == 'SONOSPLAYER' ) { - reading = 'transportState'; - - } - - if( reading == 'rgb' - && this.type == 'SWAP_0000002200000003' ) { - reading = '0B-RGBlevel'; - - } - - var cmd = '{ReadingsVal("'+this.device+'","'+reading+'","")}'; - var url = encodeURI("http://" + this.server + ":" + this.port + "/fhem?cmd=" + cmd + "&XHR=1"); - this.log( ' querying: ' + url ); + execute: function(cmd,callback) { + var url = encodeURI( this.connection.base_url + "/fhem?cmd=" + cmd + "&XHR=1"); + this.log( ' executing: ' + url ); var that = this; - request.get( { url: url }, + this.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); - - if( rgb_to_hue ) { - result = FHEM_rgb2h(result) * 360; - that.hue = result; - } else if( reading == 'hue' ) { - result = Math.round(result * 360 / that.hueMax); - that.hue = result; - } else if( reading == 'sat' ) { - result = Math.round(result * 100 / that.satMax); - that.sat = result; - } else if( reading == 'pct' ) { - that.pct = result; - } else if( reading == 'transportState' ) { - if( result == 'PLAYING' ) - result = 1; - else - result = 0; - that.state = result; - } else if( reading == 'state' ) { - if( result == 'off' ) - result = 0; - else if( result == 'on' ) - result = 1; - else if( result == '000000' ) - result = 0; - else - result = 1; - - that.state = result; - } - that.log(" mapped: " + result); - - callback(result); + if( callback ) + callback( result ); } else { - that.log("There was a problem connecting to FHEM."); + 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); + } ); }, - informationCharacteristics: function() { + query: function(reading, callback) { + this.log("query: " + this.name + "-" + reading); + + var result = FHEM_cached[this.device + '-' + reading]; + if( result != undefined ) { + this.log(" cached: " + result); + if( callback != undefined ) + callback( result ); + return( result ); + } else + this.log(" not cached" ); + + var query_reading = reading; + if( reading == 'hue' && !this.mappings.hue && this.mappings.rgb ) { + query_reading = this.mappings.rgb.reading; + + } else if( reading == 'sat' && !this.mappings.sat && this.mappings.rgb ) { + query_reading = this.mappings.rgb.reading; + + } 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.mappings.window ) { + query_reading = 'state'; + + } else if( reading == 'lock' && this.mappings.lock ) { + query_reading = 'state'; + + } + + var cmd = '{ReadingsVal("'+this.device+'","'+query_reading+'","")}'; + + var that = this; + this.execute( cmd, + function(result) { + value = result.replace(/[\r\n]/g, ""); + that.log(" value: " + value); + + if( value == undefined ) + return value; + + if( reading != query_reading ) { + if( reading == 'pct' + && query_reading == 'state') { + + 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 == '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.mappings.rgb) { + //FHEM_update( that.device+'-'+query_reading, value ); + + value = parseInt( FHEM_rgb2hsv(value)[1] * 100 ); + + } else if(reading == 'bri' && query_reading == that.mappings.rgb) { + //FHEM_update( that.device+'-'+query_reading, value ); + + value = parseInt( FHEM_rgb2hsv(value)[2] * 100 ); + + } + } else { + value = that.reading2homekit(reading, value); + } + + that.log(" mapped: " + value); + FHEM_update( that.device + '-' + reading, value, true ); + + if( value == undefined ) + return; + if( callback != undefined ) + callback(value); + return(value); + + } ); + }, + + informationCharacteristics: function(that) { return [ { cType: types.NAME_CTYPE, onUpdate: null, perms: ["pr"], format: "string", - initialValue: this.name, + initialValue: this.alias, supportEvents: false, supportBonjour: false, manfDescription: "Name of the accessory", @@ -431,14 +1046,19 @@ 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, context) { + if( context === 'fromFhem' ) + return; + if( that.mappings.onOff ) + that.command( 'identify' ); + }, perms: ["pw"], format: "bool", initialValue: false, @@ -446,8 +1066,7 @@ FHEMAccessory.prototype = { supportBonjour: false, manfDescription: "Identify Accessory", designedMaxLength: 1 - } - ] + }]; }, controlCharacteristics: function(that) { @@ -456,46 +1075,79 @@ FHEMAccessory.prototype = { onUpdate: null, perms: ["pr"], format: "string", - initialValue: this.name, + initialValue: this.alias, supportEvents: true, supportBonjour: false, manfDescription: "Name of service", designedMaxLength: 255 }] - if( this.name != undefined - && !this.hasTemperature ) { + if( this.mappings.onOff ) { cTypes.push({ cType: types.POWER_STATE_CTYPE, - onRegister: function(assignedCharacteristic) { -//that.log("onRegister: " + util.inspect(assignedCharacteristic) ); + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.mappings.onOff.informId, that); }, - onUpdate: function(value) { - if( value == 0 ) { - that.command("off") - } else { - that.command("on") - } + onUpdate: function(value, context) { + if( context === 'fromFhem' ) + return; + that.command( 'set', value == 0 ? that.mappings.onOff.cmdOff : that.mappings.onOff.cmdOn ); }, onRead: function(callback) { - that.query('state', function(powerState){ - callback(powerState); - }); + that.query( that.mappings.onOff.reading, function(state){ callback(state) } ); }, perms: ["pw","pr","ev"], format: "bool", - initialValue: 0, + initialValue: FHEM_cached[that.mappings.onOff.informId], supportEvents: true, supportBonjour: false, manfDescription: "Change the power state", designedMaxLength: 1 - }) + }); } - if( this.hasPct == true ) { + if( this.mappings.pct ) { cTypes.push({ cType: types.BRIGHTNESS_CTYPE, - onUpdate: function(value) { that.command('pct', value); }, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.mappings.pct.informId, that); + }, + onUpdate: function(value, context) { + if( context === 'fromFhem' ) + return; + that.command('pct', value); + }, + onRead: function(callback) { + that.query(that.mappings.pct.reading, function(pct){ + callback(pct); + }); + }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: FHEM_cached[that.mappings.pct.informId], + supportEvents: true, + supportBonjour: false, + manfDescription: "Adjust Brightness of the Light", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }); + } else if( this.hasDim ) { + cTypes.push({ + 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, context) { + if( context === 'fromFhem' ) + return; + that.delayed('dim', value); + }, onRead: function(callback) { that.query('pct', function(pct){ callback(pct); @@ -504,6 +1156,7 @@ FHEMAccessory.prototype = { perms: ["pw","pr","ev"], format: "int", initialValue: 0, + //initialValue: FHEM_cached[that.mappings.dim.informId], supportEvents: true, supportBonjour: false, manfDescription: "Adjust Brightness of the Light", @@ -511,13 +1164,50 @@ FHEMAccessory.prototype = { designedMaxValue: this.pctMax, designedMinStep: 1, unit: "%" - }) + }); } - if( this.hasHue == true || this.hasRGB == true ) { + if( that.mappings.hue ) { cTypes.push({ cType: types.HUE_CTYPE, - onUpdate: function(value) { that.command('hue', value); }, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.mappings.hue.informId, that); + }, + onUpdate: function(value, context) { + if( context === 'fromFhem' ) + return; + that.command('hue', value); + }, + onRead: function(callback) { + that.query(that.mappings.hue.reading, function(hue){ + callback(hue); + }); + }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: FHEM_cached[that.mappings.hue.informId], + supportEvents: true, + supportBonjour: false, + manfDescription: "Adjust the Hue of the Light", + designedMinValue: 0, + designedMaxValue: 360, + designedMinStep: 1, + unit: "arcdegrees" + }); + } 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.mappings.rgb.informId, that); + }, + onUpdate: function(value, context) { + if( context === 'fromFhem' ) + return; + that.command('H-rgb', value); + }, onRead: function(callback) { that.query('hue', function(hue){ callback(hue); @@ -533,13 +1223,20 @@ FHEMAccessory.prototype = { designedMaxValue: 360, designedMinStep: 1, unit: "arcdegrees" - }) - } + }); - if( this.hasSat == true ) { + if( !this.mappings.sat ) cTypes.push({ cType: types.SATURATION_CTYPE, - onUpdate: function(value) { that.command('sat', value); }, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.name+'-sat', that); + }, + onUpdate: function(value, context) { + if( context === 'fromFhem' ) + return; + that.command('S-rgb', value); + }, onRead: function(callback) { that.query('sat', function(sat){ callback(sat); @@ -547,7 +1244,7 @@ FHEMAccessory.prototype = { }, perms: ["pw","pr","ev"], format: "int", - initialValue: 100, + initialValue: 100, supportEvents: true, supportBonjour: false, manfDescription: "Adjust the Saturation of the Light", @@ -555,69 +1252,465 @@ FHEMAccessory.prototype = { designedMaxValue: 100, designedMinStep: 1, unit: "%" - }) + }); + + if( !this.mappings.pct ) + cTypes.push({ + cType: types.BRIGHTNESS_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.name+'-bri', that); + }, + onUpdate: function(value, context) { + if( context === 'fromFhem' ) + return; + that.command('B-rgb', value); + }, + onRead: function(callback) { + that.query('bri', function(bri){ + callback(bri); + }); + }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: 0, + supportEvents: true, + supportBonjour: false, + manfDescription: "Adjust Brightness of the Light", + designedMinValue: 0, + designedMaxValue: this.pctMax, + designedMinStep: 1, + unit: "%" + }); } - if( match = this.PossibleSets.match(/\bvolume\b/) ) { + if( this.mappings.sat ) { cTypes.push({ - cType: types.OUTPIUTVOLUME_CTYPE, - onUpdate: function(value) { that.command('volume', value); }, + cType: types.SATURATION_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.mappings.sat.informId, that); + }, + onUpdate: function(value, context) { + if( context === 'fromFhem' ) + return; + that.command('sat', value); + }, onRead: function(callback) { - that.query('volume', function(vol){ - callback(vol); + that.query(that.mappings.sat.reading, function(sat){ + callback(sat); + }); + }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: FHEM_cached[that.mappings.sat.informId], + supportEvents: true, + supportBonjour: false, + manfDescription: "Adjust the Saturation of the Light", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1, + unit: "%" + }); + } + + if( this.mappings.volume ) { + cTypes.push({ + cType: types.OUTPUTVOLUME_CTYPE, + onUpdate: function(value, context) { + if( context === 'fromFhem' ) + return; + that.delayed('volume', value); + }, + onRegister: function(characteristic) { + 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){ + 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 the device", + manfDescription: "Adjust the Volume of this device", + designedMinValue: 0, + designedMaxValue: 100, + designedMinStep: 1 + //unit: "%" + }); + } + + if( this.mappings.blind ) { + cTypes.push({ + cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, + onUpdate: function(value, context) { + if( context === 'fromFhem' ) + return; + that.delayed('targetPosition', value, 1500); + }, + //onRegister: function(characteristic) { + // characteristic.eventEnabled = true; + // FHEM_subscribe(characteristic, that.mappings.blind.informId, that); + //}, + onRead: function(callback) { + that.query(that.mappings.blind.reading, function(pct){ + callback(pct); + }); + }, + perms: ["pw","pr","ev"], + format: "int", + initialValue: FHEM_cached[that.mappings.blind.informId], + 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.mappings.blind.informId, that); + }, + onRead: function(callback) { + that.query(that.mappings.blind.reading, function(pos){ + callback(pos); + }); + }, + perms: ["pr","ev"], + format: "int", + initialValue: FHEM_cached[that.mappings.blind.informId], + 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.mappings.motor ) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.mappings.motor.informId, that); + } + }, + onRead: function(callback) { + if( that.mappings.motor ) + that.query(that.mappings.motor.reading, function(state){ + callback(state); + }); + }, + perms: ["pr","ev"], + format: "int", + 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.hasTemperature ) { + if( this.mappings.window ) { + cTypes.push({ + cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, + onUpdate: function(value, context) { + if( context === 'fromFhem' ) + return; + that.delayed('targetPosition', value, 1500); + }, + onRead: function(callback) { + that.query(that.mappings.window.reading, 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.mappings.window.informId, that); + }, + onRead: function(callback) { + that.query(that.mappings.window.reading, function(pos){ + callback(pos); + }); + }, + perms: ["pr","ev"], + format: "int", + initialValue: FHEM_cached[that.mappings.window.informId], + 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", + designedMinValue: 0, + designedMaxValue: 2, + designedMinStep: 1, + }); + } + + if( this.mappings.garage ) { + cTypes.push({ + onUpdate: function(value, context) { + if( context === 'fromFhem' ) + return; + 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.mappings.thermostat ) { + cTypes.push({ + cType: types.TARGET_TEMPERATURE_CTYPE, + onUpdate: function(value, context) { + if( context === 'fromFhem' ) + return; + that.delayed('targetTemperature', value, 1500); + }, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.mappings.thermostat.informId, that); + }, + onRead: function(callback) { + that.query(that.mappings.thermostat.reading, function(temperature){ + callback(temperature); + }); + }, + perms: ["pw","pr","ev"], + format: "float", + initialValue: FHEM_cached[that.mappings.thermostat.informId], + 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, context) { + if( context === 'fromFhem' ) + return; + 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.mappings.contact ) { + cTypes.push({ + cType: types.CONTACT_SENSOR_STATE_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.mappings.contact.informId, that); + }, + onRead: function(callback) { + that.query(that.mappings.contact.reading, function(state){ + callback(state); + }); + }, + perms: ["pr","ev"], + format: "bool", + initialValue: FHEM_cached[that.mappings.contact.informId], + supportEvents: false, + supportBonjour: false, + manfDescription: "Contact State", + designedMaxLength: 1 + }); + } + + if( this.mappings.occupancy ) { + cTypes.push({ + cType: types.OCCUPANCY_DETECTED_CTYPE, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.mappings.occupancy.informId, that); + }, + onRead: function(callback) { + that.query(that.mappings.occupancy.reading, function(state){ + callback(state); + }); + }, + perms: ["pr","ev"], + format: "bool", + initialValue: FHEM_cached[that.mappings.occupancy.informId], + supportEvents: false, + supportBonjour: false, + manfDescription: "Occupancy State", + designedMaxLength: 1 + }); + } + + if( this.mappings.temperature ) { cTypes.push({ cType: types.CURRENT_TEMPERATURE_CTYPE, - //onUpdate: function(value) { console.log("Change:",value); execute("Thermostat", "Current Temperature", value); }, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.mappings.temperature.informId, that); + }, onRead: function(callback) { - that.query('temperature', function(temperature){ - callback(parseFloat(temperature)); + that.query(that.mappings.temperature.reading, function(temperature){ + callback(temperature); }); }, perms: ["pr","ev"], format: "float", - initialValue: 20, + initialValue: FHEM_cached[that.mappings.temperature.informId], supportEvents: true, supportBonjour: false, manfDescription: "Current Temperature", unit: "celsius" - }) + }); } - if( this.hasHumidity ) { + if( this.mappings.humidity ) { cTypes.push({ cType: types.CURRENT_RELATIVE_HUMIDITY_CTYPE, - //onUpdate: function(value) { console.log("Change:",value); execute("Thermostat", "Current Temperature", value); }, + onRegister: function(characteristic) { + characteristic.eventEnabled = true; + FHEM_subscribe(characteristic, that.mappings.humidity.informId, that); + }, onRead: function(callback) { - that.query('humidity', function(humidity){ - callback(parseInt(humidity)); + that.query(that.mappings.humidity.reading, function(humidity){ + callback(humidity); }); }, perms: ["pr","ev"], format: "int", - initialValue: 50, + initialValue: FHEM_cached[that.mappings.humidity.informId], designedMinValue: 0, designedMaxValue: 100, supportEvents: true, supportBonjour: false, manfDescription: "Current Humidity", unit: "%" - }) + }); } @@ -625,16 +1718,30 @@ FHEMAccessory.prototype = { }, sType: function() { - if( match = this.PossibleSets.match(/\bvolume\b/) ) { - return types.SPEAKER_STYPE - } else if( this.hasTemperature ) { - return types.TEMPERATURE_SENSOR_STYPE - } else if( this.hasHumidity ) { - return types.HUMIDITY_SENSOR_STYPE - } else if( this.hasPct || this.hasHue || this.hasRGB ) { - return types.LIGHTBULB_STYPE + if( match = this.PossibleSets.match(/[\^ ]volume\b/) ) { + return types.SPEAKER_STYPE; + } else if( this.isSwitch ) { + return types.SWITCH_STYPE; + } else if( this.mappings.garage ) { + return types.GARAGE_DOOR_OPENER_STYPE; + } else if( this.mappings.window ) { + return types.WINDOW_STYPE; + } 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.mappings.occupancy ) { + return types.OCCUPANCY_SENSOR_STYPE; + } 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; + } else if( this.mappings.humidity ) { + return types.HUMIDITY_SENSOR_STYPE; } else { - return types.SWITCH_STYPE + return types.SWITCH_STYPE; } }, @@ -642,7 +1749,7 @@ FHEMAccessory.prototype = { var that = this; var services = [{ sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: this.informationCharacteristics(), + characteristics: this.informationCharacteristics(that), }, { sType: this.sType(), @@ -655,3 +1762,63 @@ 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){ + //console.log( request ); + + if( request.url == "/cached" ) { + response.write( "home

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

" ); + 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: 4}).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 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); + 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" ); +} + +var FHEMdebug_server = http.createServer( FHEMdebug_handleRequest ); + +FHEMdebug_server.on('error', function (e) { + console.log("Server error: " + e); +}); + +//Lets start our server +FHEMdebug_server.listen(FHEMdebug_PORT, function(){ + console.log("Server listening on: http://:%s", FHEMdebug_PORT); +}); + From 5358ef94a8631fff3449f0898b5ba35a989b9955 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Sun, 13 Sep 2015 09:14:13 +0200 Subject: [PATCH 04/10] catch undefined values in reading2homekit --- platforms/FHEM.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index 01b9b21..fba5d01 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -687,6 +687,9 @@ FHEM_dim_values = [ 'dim06%', 'dim12%', 'dim18%', 'dim25%', 'dim31%', 'dim37%', FHEMAccessory.prototype = { reading2homekit: function(reading,value) { + if( value == undefined ) + return undefined; + if( reading == 'hue' ) { value = Math.round(value * 360 / this.mappings.hue ? this.mappings.hue.max : 360); @@ -735,14 +738,13 @@ FHEMAccessory.prototype = { value = 0; } else if( reading == 'lock' ) { - if( value.match( /^locked/ ) ) + if( value.match( /uncertain/ ) ) + value = 4; + else if( value.match( /^locked/ ) ) value = 1; else value = 0; - if( value.match( /uncertain/ ) ) - value = 4; - } else if( reading == 'temperature' || reading == 'measured-temp' || reading == 'desired-temp' From 428186eb8624d506d6c3cb46cc086155aee6ef75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Sun, 13 Sep 2015 10:24:28 +0200 Subject: [PATCH 05/10] fixed debug messages --- platforms/FHEM.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index fba5d01..d9f2815 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -604,7 +604,7 @@ FHEMAccessory(log, connection, s) { if( this.mappings.onOff ) - log( s.Internals.NAME + ' has onOff [' + this.mappings.onOff + ']' ); + log( s.Internals.NAME + ' has onOff [' + this.mappings.onOff.reading + ':' + this.mappings.onOff.cmdOn +',' + this.mappings.onOff.cmdOff + ']' ); if( this.mappings.hue ) log( s.Internals.NAME + ' has hue [0-' + this.mappings.hue.max +']' ); if( this.mappings.sat ) @@ -618,7 +618,7 @@ FHEMAccessory(log, connection, s) { if( this.mappings.direction ) log( s.Internals.NAME + ' has direction' ); -log(s); +//log( util.inspect(s) ); // device info this.name = s.Internals.NAME; From 6c34301c138f93521670f35541d2ea64922b0dc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Sun, 13 Sep 2015 12:21:50 +0200 Subject: [PATCH 06/10] sonos volume fix --- platforms/FHEM.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index d9f2815..cd4a60c 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -1318,7 +1318,7 @@ FHEMAccessory.prototype = { if( this.mappings.volume ) { cTypes.push({ - cType: types.OUTPUTVOLUME_CTYPE, + cType: '00000027-0000-1000-8000-0026BB765291', // FIXME!!! onUpdate: function(value, context) { if( context === 'fromFhem' ) return; @@ -1781,11 +1781,11 @@ function FHEMdebug_handleRequest(request, response){ response.write( "FHEM_lastEventTime: "+ new Date(FHEM_lastEventTime) +"

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

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

" ); var unique = {}; Object.keys(FHEM_subscriptions).forEach(function(key) { From 1f4ae9e87558cc9a83782369a5ca08b33f279f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Sun, 13 Sep 2015 13:30:04 +0200 Subject: [PATCH 07/10] removed speaker stype --- platforms/FHEM.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index cd4a60c..d9a561c 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -1720,9 +1720,7 @@ FHEMAccessory.prototype = { }, sType: function() { - if( match = this.PossibleSets.match(/[\^ ]volume\b/) ) { - return types.SPEAKER_STYPE; - } else if( this.isSwitch ) { + if( this.isSwitch ) { return types.SWITCH_STYPE; } else if( this.mappings.garage ) { return types.GARAGE_DOOR_OPENER_STYPE; @@ -1781,7 +1779,7 @@ function FHEMdebug_handleRequest(request, response){ response.write( "FHEM_lastEventTime: "+ new Date(FHEM_lastEventTime) +"

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

" ); response.end( "subscriptions: " + util.inspect(FHEM_subscriptions, {depth: 4}).replace(/\n/g, '
') ); From 9f39b49dd59bf350791d7bb2259b6e88f6bd51c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Mon, 14 Sep 2015 22:10:14 +0200 Subject: [PATCH 08/10] complete switch to new api basic device (light,hue,swap,thermometer,thermostat) are working still problems with custom characteristic volume --- platforms/FHEM.js | 1354 ++++++++++++++++++--------------------------- 1 file changed, 542 insertions(+), 812 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index d9a561c..6c7d45a 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -16,7 +16,11 @@ // When you attempt to add a device, it will ask for a "PIN code". // The default code for all HomeBridge accessories is 031-45-154. +var Service = require("HAP-NodeJS").Service; +var Characteristic = require("HAP-NodeJS").Characteristic; + var types = require('HAP-NodeJS/accessories/types.js'); + var util = require('util'); @@ -38,25 +42,12 @@ 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) ); if( !no_update ) subscription.characteristic.setValue(value, undefined, 'fromFhem'); - //subscription.characteristic.updateValue(value, null); } } @@ -123,8 +114,6 @@ 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]; @@ -144,12 +133,12 @@ function FHEM_startLongpoll(connection) { continue; } else if( accessory.mappings.lock ) { - var lock = 0; + var lock = Characteristic.LockCurrentState.UNSECURED; if( value.match( /^locked/ ) ) - lock = 1; + lock = Characteristic.LockCurrentState.SECURED; if( value.match( /uncertain/ ) ) - level = 4; + level = Characteristic.LockCurrentState.UNKNOWN; FHEM_update( accessory.mappings.lock.informId, lock ); continue; @@ -399,6 +388,9 @@ FHEMPlatform.prototype = { } else if( s.Readings.humidity ) { accessory = new FHEMAccessory(that.log, that.connection, s); + } else if( s.Readings.voc ) { + accessory = new FHEMAccessory(that.log, that.connection, s); + } else { that.log( 'ignoring ' + s.Internals.NAME ); @@ -494,6 +486,9 @@ FHEMAccessory(log, connection, s) { if( s.Readings.humidity ) this.mappings.humidity = { reading: 'humidity' }; + if( s.Readings.voc ) + this.mappings.airquality = { reading: 'voc' }; + if( s.Readings.motor ) this.mappings.motor = { reading: 'motor' }; @@ -586,11 +581,11 @@ FHEMAccessory(log, connection, s) { 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 +']' ); + log( s.Internals.NAME + ' is contact sensor [' + this.mappings.contact.reading +']' ); else if( this.mappings.occupancy ) - log( s.Internals.NAME + ' is occupancysensor' ); + log( s.Internals.NAME + ' is occupancy sensor' ); else if( this.mappings.rgb ) - log( s.Internals.NAME + ' has RGB [0-' + this.mappings.rgb.reading +']'); + log( s.Internals.NAME + ' has RGB [' + this.mappings.rgb.reading +']'); else if( this.mappings.pct ) log( s.Internals.NAME + ' is dimable ['+ this.mappings.pct.reading +']' ); else if( s.hasDim ) @@ -599,12 +594,12 @@ FHEMAccessory(log, connection, s) { log( s.Internals.NAME + ' is light' ); else if( this.mappings.onOff || s.isSwitch ) log( s.Internals.NAME + ' is switchable' ); - else + else if( !this.mappings ) return {}; if( this.mappings.onOff ) - log( s.Internals.NAME + ' has onOff [' + this.mappings.onOff.reading + ':' + this.mappings.onOff.cmdOn +',' + this.mappings.onOff.cmdOff + ']' ); + log( s.Internals.NAME + ' has onOff [' + this.mappings.onOff.reading + ';' + this.mappings.onOff.cmdOn +',' + this.mappings.onOff.cmdOff + ']' ); if( this.mappings.hue ) log( s.Internals.NAME + ' has hue [0-' + this.mappings.hue.max +']' ); if( this.mappings.sat ) @@ -613,6 +608,8 @@ FHEMAccessory(log, connection, s) { log( s.Internals.NAME + ' has temperature ['+ this.mappings.temperature.reading +']' ); if( this.mappings.humidity ) log( s.Internals.NAME + ' has humidity ['+ this.mappings.humidity.reading +']' ); + if( this.mappings.airquality ) + log( s.Internals.NAME + ' has voc ['+ this.mappings.airquality.reading +']' ); if( this.mappings.motor ) log( s.Internals.NAME + ' has motor' ); if( this.mappings.direction ) @@ -677,10 +674,6 @@ FHEMAccessory(log, connection, s) { this.log = log; this.connection = connection; - - this.onRegister = function(accessory) { -console.log( ">>>>>>>>>>>>:" + util.inspect(accessory) ); - }; } FHEM_dim_values = [ 'dim06%', 'dim12%', 'dim18%', 'dim25%', 'dim31%', 'dim37%', 'dim43%', 'dim50%', 'dim56%', 'dim62%', 'dim68%', 'dim75%', 'dim81%', 'dim87%', 'dim93%' ]; @@ -699,21 +692,21 @@ FHEMAccessory.prototype = { } 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; - } else if(reading == 'motor') { - if( value.match(/^opening/)) - value = 1; - else if( value.match(/^closing/)) - value = 0; + if( value.match(/^up/)) + value = Characteristic.PositionState.INCREASING; + else if( value.match(/^down/)) + value = Characteristic.PositionState.DECREASING; else - value = 2; + value = Characteristic.PositionState.STOPPED; + + } else if(reading == 'direction') { + if( value.match(/^opening/)) + value = PositionState.INCREASING; + else if( value.match(/^closing/)) + value = Characteristic.PositionState.DECREASING; + else + value = Characteristic.PositionState.STOPPED; } else if( reading == 'transportState' ) { if( value == 'PLAYING' ) @@ -727,23 +720,23 @@ FHEMAccessory.prototype = { } else if( reading == 'contact' ) { if( value.match( /^closed/ ) ) - value = 1; + value = Characteristic.ContactSensorState.CONTACT_DETECTED; else - value = 0; + value = Characteristic.ContactSensorState.CONTACT_NOT_DETECTED; } else if( reading == 'Window' ) { if( value.match( /^Closed/ ) ) - value = 1; + value = Characteristic.ContactSensorState.CONTACT_DETECTED; else - value = 0; + value = Characteristic.ContactSensorState.CONTACT_NOT_DETECTED; } else if( reading == 'lock' ) { if( value.match( /uncertain/ ) ) - value = 4; + value = Characteristic.LockCurrentState.UNKNOWN; else if( value.match( /^locked/ ) ) - value = 1; + value = Characteristic.LockCurrentState.SECURED; else - value = 0; + value = Characteristic.LockCurrentState.UNSECURED; } else if( reading == 'temperature' || reading == 'measured-temp' @@ -754,11 +747,24 @@ FHEMAccessory.prototype = { } else if( reading == 'humidity' ) { value = parseInt( value ); + } else if( reading == 'voc' ) { + value = parseInt( value ); + if( value > 1500 ) + Characteristic.AirQuality.POOR; + else if( value > 1000 ) + Characteristic.AirQuality.INFERIOR; + else if( value > 800 ) + Characteristic.AirQuality.FAIR; + else if( value > 600 ) + Characteristic.AirQuality.GOOD; + else if( value > 0 ) + Characteristic.AirQuality.EXCELLENT; + else + Characteristic.AirQuality.UNKNOWN; + } 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]; @@ -768,8 +774,10 @@ FHEMAccessory.prototype = { if( value == 'off' ) value = 0; + else if( value == 'present' ) + value = Characteristic.OccupancyDetected.OCCUPANCY_DETECTED; else if( value == 'absent' ) - value = 0; + value = Characteristic.OccupancyDetected.OCCUPANCY_NOT_DETECTED; else if( value == '000000' ) value = 0; else if( value.match( /^[A-D]0$/ ) ) //FIXME: is handled by event_map now @@ -971,13 +979,12 @@ FHEMAccessory.prototype = { } else if( reading == 'lock' && query_reading == 'state') { - if( value.match( /^locked/ ) ) - value = 1; - else - value = 0; - if( value.match( /uncertain/ ) ) - value = 4; + value = Characteristic.LockCurrentState.UNKNOWN; + else if( value.match( /^locked/ ) ) + value = Characteristic.LockCurrentState.SECURED; + else + value = Characteristic.LockCurrentState.UNSECURED; } else if(reading == 'hue' && query_reading == that.mappings.rgb) { //FHEM_update( that.device+'-'+query_reading, value ); @@ -1011,753 +1018,500 @@ FHEMAccessory.prototype = { } ); }, - informationCharacteristics: function(that) { - 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 : "", - supportEvents: false, - supportBonjour: false, - manfDescription: "SN", - designedMaxLength: 255 - },{ - cType: types.IDENTIFY_CTYPE, - onUpdate: function(value, context) { - if( context === 'fromFhem' ) - return; - if( that.mappings.onOff ) - that.command( 'identify' ); - }, - perms: ["pw"], - format: "bool", - initialValue: false, - supportEvents: false, - supportBonjour: false, - manfDescription: "Identify Accessory", - designedMaxLength: 1 - }]; - }, + createDeviceService: function() { + var name = this.alias + 'xxx'; - 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.mappings.onOff ) { - cTypes.push({ - cType: types.POWER_STATE_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.mappings.onOff.informId, that); - }, - onUpdate: function(value, context) { - if( context === 'fromFhem' ) - return; - 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) } ); - }, - perms: ["pw","pr","ev"], - format: "bool", - initialValue: FHEM_cached[that.mappings.onOff.informId], - supportEvents: true, - supportBonjour: false, - manfDescription: "Change the power state", - designedMaxLength: 1 - }); - } - - if( this.mappings.pct ) { - cTypes.push({ - cType: types.BRIGHTNESS_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.mappings.pct.informId, that); - }, - onUpdate: function(value, context) { - if( context === 'fromFhem' ) - return; - that.command('pct', value); - }, - onRead: function(callback) { - that.query(that.mappings.pct.reading, function(pct){ - callback(pct); - }); - }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: FHEM_cached[that.mappings.pct.informId], - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust Brightness of the Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }); - } else if( this.hasDim ) { - cTypes.push({ - 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, context) { - if( context === 'fromFhem' ) - return; - that.delayed('dim', value); - }, - onRead: function(callback) { - that.query('pct', function(pct){ - callback(pct); - }); - }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - //initialValue: FHEM_cached[that.mappings.dim.informId], - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust Brightness of the Light", - designedMinValue: 0, - designedMaxValue: this.pctMax, - designedMinStep: 1, - unit: "%" - }); - } - - if( that.mappings.hue ) { - cTypes.push({ - cType: types.HUE_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.mappings.hue.informId, that); - }, - onUpdate: function(value, context) { - if( context === 'fromFhem' ) - return; - that.command('hue', value); - }, - onRead: function(callback) { - that.query(that.mappings.hue.reading, function(hue){ - callback(hue); - }); - }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: FHEM_cached[that.mappings.hue.informId], - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust the Hue of the Light", - designedMinValue: 0, - designedMaxValue: 360, - designedMinStep: 1, - unit: "arcdegrees" - }); - } 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.mappings.rgb.informId, that); - }, - onUpdate: function(value, context) { - if( context === 'fromFhem' ) - return; - that.command('H-rgb', 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.mappings.sat ) - cTypes.push({ - cType: types.SATURATION_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.name+'-sat', that); - }, - onUpdate: function(value, context) { - if( context === 'fromFhem' ) - return; - that.command('S-rgb', 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: "%" - }); - - if( !this.mappings.pct ) - cTypes.push({ - cType: types.BRIGHTNESS_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.name+'-bri', that); - }, - onUpdate: function(value, context) { - if( context === 'fromFhem' ) - return; - that.command('B-rgb', value); - }, - onRead: function(callback) { - that.query('bri', function(bri){ - callback(bri); - }); - }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: 0, - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust Brightness of the Light", - designedMinValue: 0, - designedMaxValue: this.pctMax, - designedMinStep: 1, - unit: "%" - }); - } - - if( this.mappings.sat ) { - cTypes.push({ - cType: types.SATURATION_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.mappings.sat.informId, that); - }, - onUpdate: function(value, context) { - if( context === 'fromFhem' ) - return; - that.command('sat', value); - }, - onRead: function(callback) { - that.query(that.mappings.sat.reading, function(sat){ - callback(sat); - }); - }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: FHEM_cached[that.mappings.sat.informId], - supportEvents: true, - supportBonjour: false, - manfDescription: "Adjust the Saturation of the Light", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1, - unit: "%" - }); - } - - if( this.mappings.volume ) { - cTypes.push({ - cType: '00000027-0000-1000-8000-0026BB765291', // FIXME!!! - onUpdate: function(value, context) { - if( context === 'fromFhem' ) - return; - that.delayed('volume', value); - }, - onRegister: function(characteristic) { - 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){ - 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", - designedMinValue: 0, - designedMaxValue: 100, - designedMinStep: 1 - //unit: "%" - }); - } - - if( this.mappings.blind ) { - cTypes.push({ - cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, - onUpdate: function(value, context) { - if( context === 'fromFhem' ) - return; - that.delayed('targetPosition', value, 1500); - }, - //onRegister: function(characteristic) { - // characteristic.eventEnabled = true; - // FHEM_subscribe(characteristic, that.mappings.blind.informId, that); - //}, - onRead: function(callback) { - that.query(that.mappings.blind.reading, function(pct){ - callback(pct); - }); - }, - perms: ["pw","pr","ev"], - format: "int", - initialValue: FHEM_cached[that.mappings.blind.informId], - 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.mappings.blind.informId, that); - }, - onRead: function(callback) { - that.query(that.mappings.blind.reading, function(pos){ - callback(pos); - }); - }, - perms: ["pr","ev"], - format: "int", - initialValue: FHEM_cached[that.mappings.blind.informId], - 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.mappings.motor ) { - characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.mappings.motor.informId, that); - } - }, - onRead: function(callback) { - if( that.mappings.motor ) - that.query(that.mappings.motor.reading, function(state){ - callback(state); - }); - }, - perms: ["pr","ev"], - format: "int", - 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.mappings.window ) { - cTypes.push({ - cType: types.WINDOW_COVERING_TARGET_POSITION_CTYPE, - onUpdate: function(value, context) { - if( context === 'fromFhem' ) - return; - that.delayed('targetPosition', value, 1500); - }, - onRead: function(callback) { - that.query(that.mappings.window.reading, 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.mappings.window.informId, that); - }, - onRead: function(callback) { - that.query(that.mappings.window.reading, function(pos){ - callback(pos); - }); - }, - perms: ["pr","ev"], - format: "int", - initialValue: FHEM_cached[that.mappings.window.informId], - 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", - designedMinValue: 0, - designedMaxValue: 2, - designedMinStep: 1, - }); - } - - if( this.mappings.garage ) { - cTypes.push({ - onUpdate: function(value, context) { - if( context === 'fromFhem' ) - return; - 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.mappings.thermostat ) { - cTypes.push({ - cType: types.TARGET_TEMPERATURE_CTYPE, - onUpdate: function(value, context) { - if( context === 'fromFhem' ) - return; - that.delayed('targetTemperature', value, 1500); - }, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.mappings.thermostat.informId, that); - }, - onRead: function(callback) { - that.query(that.mappings.thermostat.reading, function(temperature){ - callback(temperature); - }); - }, - perms: ["pw","pr","ev"], - format: "float", - initialValue: FHEM_cached[that.mappings.thermostat.informId], - 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, context) { - if( context === 'fromFhem' ) - return; - 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.mappings.contact ) { - cTypes.push({ - cType: types.CONTACT_SENSOR_STATE_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.mappings.contact.informId, that); - }, - onRead: function(callback) { - that.query(that.mappings.contact.reading, function(state){ - callback(state); - }); - }, - perms: ["pr","ev"], - format: "bool", - initialValue: FHEM_cached[that.mappings.contact.informId], - supportEvents: false, - supportBonjour: false, - manfDescription: "Contact State", - designedMaxLength: 1 - }); - } - - if( this.mappings.occupancy ) { - cTypes.push({ - cType: types.OCCUPANCY_DETECTED_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.mappings.occupancy.informId, that); - }, - onRead: function(callback) { - that.query(that.mappings.occupancy.reading, function(state){ - callback(state); - }); - }, - perms: ["pr","ev"], - format: "bool", - initialValue: FHEM_cached[that.mappings.occupancy.informId], - supportEvents: false, - supportBonjour: false, - manfDescription: "Occupancy State", - designedMaxLength: 1 - }); - } - - if( this.mappings.temperature ) { - cTypes.push({ - cType: types.CURRENT_TEMPERATURE_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.mappings.temperature.informId, that); - }, - onRead: function(callback) { - that.query(that.mappings.temperature.reading, function(temperature){ - callback(temperature); - }); - }, - perms: ["pr","ev"], - format: "float", - initialValue: FHEM_cached[that.mappings.temperature.informId], - supportEvents: true, - supportBonjour: false, - manfDescription: "Current Temperature", - unit: "celsius" - }); - } - - if( this.mappings.humidity ) { - cTypes.push({ - cType: types.CURRENT_RELATIVE_HUMIDITY_CTYPE, - onRegister: function(characteristic) { - characteristic.eventEnabled = true; - FHEM_subscribe(characteristic, that.mappings.humidity.informId, that); - }, - onRead: function(callback) { - that.query(that.mappings.humidity.reading, function(humidity){ - callback(humidity); - }); - }, - perms: ["pr","ev"], - format: "int", - initialValue: FHEM_cached[that.mappings.humidity.informId], - designedMinValue: 0, - designedMaxValue: 100, - supportEvents: true, - supportBonjour: false, - manfDescription: "Current Humidity", - unit: "%" - }); - - } - - return cTypes; - }, - - sType: function() { if( this.isSwitch ) { - return types.SWITCH_STYPE; + this.log(" switch service for " + this.name) + return new Service.Switch(name); } else if( this.mappings.garage ) { - return types.GARAGE_DOOR_OPENER_STYPE; + this.log(" garage door opener service for " + this.name) + return new Service.GarageDoorOpener(name); } else if( this.mappings.window ) { - return types.WINDOW_STYPE; + this.log(" window service for " + this.name) + return new Service.Window(name); } else if( this.mappings.blind ) { - return types.WINDOW_COVERING_STYPE; + this.log(" window covering service for " + this.name) + return new Service.WindowCovering(name); } else if( this.mappings.thermostat ) { - return types.THERMOSTAT_STYPE; + this.log(" thermostat service for " + this.name) + return new Service.Thermostat(name); } else if( this.mappings.contact ) { - return types.CONTACT_SENSOR_STYPE; + this.log(" contact sensor service for " + this.name) + return new Service.ContactSensorState(name); } else if( this.mappings.occupancy ) { - return types.OCCUPANCY_SENSOR_STYPE; + this.log(" occupancy sensor service for " + this.name) + return new Service.OccupancySensor(name); } else if( this.isLight || this.mappings.pct || this.mappings.hue || this.mappings.rgb ) { - return types.LIGHTBULB_STYPE; + this.log(" lightbulb service for " + this.name) + return new Service.Lightbulb(name); } else if( this.mappings.temperature ) { - return types.TEMPERATURE_SENSOR_STYPE; + this.log(" temperature sensor service for " + this.name) + return new Service.TemperatureSensor(name); } else if( this.mappings.humidity ) { - return types.HUMIDITY_SENSOR_STYPE; - } else { - return types.SWITCH_STYPE; + this.log(" humidity sensor service for " + this.name) + return new Service.HumiditySensor(name); + } else if( this.mappings.airquality ) { + this.log(" humidity sensor service for " + this.name) + return new Service.AirQualitySensor(name); } + + this.log(" switch service for " + this.name) + return new Service.Switch(name); + }, + + identify: function(callback) { + this.log('['+this.name+'] identify requested!'); + callback(); }, getServices: function() { + this.log("creating services for " + this.name) + + this.log(" information service for " + this.name) + var informationService = new Service.AccessoryInformation(); + + informationService + .setCharacteristic(Characteristic.Manufacturer, "FHEM:"+this.type) + .setCharacteristic(Characteristic.Model, "FHEM:"+this.model ? this.model : '') + .setCharacteristic(Characteristic.SerialNumber, this.serial ? this.serial : ''); + + var controlService = this.createDeviceService(); + var that = this; - var services = [{ - sType: types.ACCESSORY_INFORMATION_STYPE, - characteristics: this.informationCharacteristics(that), - }, - { - sType: this.sType(), - characteristics: this.controlCharacteristics(that) - }]; - this.log("Loaded services for " + this.name) - return services; + if( this.mappings.onOff ) { + this.log(" power characteristic for " + this.name) + + var characteristic = controlService.getCharacteristic(Characteristic.On); + + FHEM_subscribe(characteristic, that.mappings.onOff.informId, that); + if( FHEM_cached[that.mappings.onOff.informId] != undefined ) + characteristic.value = FHEM_cached[that.mappings.onOff.informId]; + + characteristic + .on('set', function(value, callback, context) { + if( context !== 'fromFhem' ) + that.command( 'set', value == 0 ? that.mappings.onOff.cmdOff : that.mappings.onOff.cmdOn ); + callback(); + }.bind(this) ) + .on('get', function(callback) { + that.query( that.mappings.onOff.reading, function(state){ callback(undefined, state); } ); + }.bind(this) ); + } + + if( this.mappings.pct ) { + this.log(" brightness characteristic for " + this.name) + + var characteristic = controlService.addCharacteristic(Characteristic.Brightness); + + FHEM_subscribe(characteristic, that.mappings.pct.informId, that); + if( FHEM_cached[that.mappings.pct.informId] != undefined ) + characteristic.value = FHEM_cached[that.mappings.pct.informId]; + + characteristic + .on('set', function(value, callback, context) { + if( context !== 'fromFhem' ) + that.command('pct', value); + callback(); + }.bind(this) ) + .on('get', function(callback) { + that.query(that.mappings.pct.reading, function(pct){ callback(undefined,pct); }); + }.bind(this) ); + + } else if( this.hasDim ) { + this.log(" fake brightness characteristic for " + this.name) + + var characteristic = controlService.addCharacteristic(Characteristic.Brightness); + + FHEM_subscribe(characteristic, that.name+'-pct', that); + characteristic.value = 0; + characteristic.maximumValue = this.pctMax; + + characteristic + .on('set', function(value, callback, context) { + if( context !== 'fromFhem' ) + that.delayed('dim', value); + callback(); + }.bind(this) ) + .on('get', function(callback) { + that.query('pct', function(pct){ callback(undefined,pct); }); + }.bind(this) ); + + } + + if( that.mappings.hue ) { + this.log(" hue characteristic for " + this.name) + + var characteristic = controlService.addCharacteristic(Characteristic.Hue); + + FHEM_subscribe(characteristic, that.mappings.hue.informId, that); + if( FHEM_cached[that.mappings.hue.informId] != undefined ) + characteristic.value = FHEM_cached[that.mappings.hue.informId]; + + characteristic + .on('set', function(value, callback, context) { + if( context !== 'fromFhem' ) + that.command('hue', value); + callback(); + }.bind(this) ) + .on('get', function(callback) { + that.query(that.mappings.hue.reading, function(hue){ callback(undefined,hue); }); + }.bind(this) ); + + } else if( this.mappings.rgb ) { + this.log(" fake hue characteristic for " + this.name) + + var characteristic = controlService.addCharacteristic(Characteristic.Hue); + + FHEM_subscribe(characteristic, that.name+'-hue', that); + FHEM_subscribe(characteristic, that.mappings.rgb.informId, that); + characteristic.value = 0; + + characteristic + .on('set', function(value, callback, context) { + if( context !== 'fromFhem' ) + that.command('H-rgb', value); + callback(); + }.bind(this) ) + .on('get', function(callback) { + that.query('hue', function(hue){ callback(undefined,hue); }); + }.bind(this) ); + + if( !this.mappings.sat ) { + this.log(" fake saturation characteristic for " + this.name) + + var characteristic = controlService.addCharacteristic(Characteristic.Saturation); + + FHEM_subscribe(characteristic, that.name+'-sat', that); + characteristic.value = 100; + + characteristic + .on('set', function(value, callback, context) { + if( context !== 'fromFhem' ) + that.command('S-rgb', value); + callback(); + }.bind(this) ) + .on('get', function(callback) { + that.query('sat', function(sat){ callback(undefined,sat); }); + }.bind(this) ); + } + + if( !this.mappings.pct ) { + this.log(" fake brightness characteristic for " + this.name) + + var characteristic = controlService.addCharacteristic(Characteristic.Brightness); + + FHEM_subscribe(characteristic, that.name+'-bri', that); + characteristic.value = 0; + + characteristic + .on('set', function(value, callback, context) { + if( context !== 'fromFhem' ) + that.command('B-rgb', value); + callback(); + }.bind(this) ) + .on('get', function(callback) { + that.query('bri', function(bri){ callback(undefined,bri); }); + }.bind(this) ); + } + + } + + if( this.mappings.sat ) { + this.log(" saturation characteristic for " + this.name) + + var characteristic = controlService.addCharacteristic(Characteristic.Saturation); + + FHEM_subscribe(characteristic, that.mappings.sat.informId, that); + if( FHEM_cached[that.mappings.sat.informId] != undefined ) + characteristic.value = FHEM_cached[that.mappings.sat.informId]; + + characteristic + .on('set', function(value, callback, context) { + if( context !== 'fromFhem' ) + that.command('sat', value); + callback(); + }.bind(this) ) + .on('get', function(callback) { + that.query(that.mappings.sat.reading, function(sat){ callback(undefined,sat); }); + }.bind(this) ); + } + + if( this.mappings.volume ) { + this.log(" custom volume characteristic for " + this.name) + + var characteristic = new Characteristic('Adjust the Volume of this device', '00000027-0000-1000-8000-0026BB765291'); // FIXME!!! + controlService.addCharacteristic(characteristic); + + if( !that.mappings.volume.nocache ) { + FHEM_subscribe(characteristic, that.mappings.volume.informId, that); + characteristic.value = FHEM_cached[that.mappings.volume.informId]; + } else { + characteristic.value = 10; + } + + characteristic.format = 'int'; + //characteristic.unit = 'percentage'; + characteristic.maximumValue = 100; + characteristic.minimumValue = 0; + characteristic.stepValue = 1; + characteristic.readable = true; + characteristic.writable = true; + characteristic.supportsEventNotification = true; + + characteristic + .on('set', function(value, callback, context) { + if( context !== 'fromFhem' ) + that.delayed('volume', value); + callback(); + }.bind(this) ) + .on('get', function(callback) { + that.query(that.mappings.volume.reading, function(volume){ callback(undefined,volume); }); + }.bind(this) ); + + } + + if( this.mappings.blind ) { + this.log(" current position characteristic for " + this.name) + + var characteristic = controlService.getCharacteristic(Characteristic.CurrentPosition); + + FHEM_subscribe(characteristic, that.mappings.blind.informId, that); + characteristic.value = FHEM_cached[that.mappings.blind.informId]; + + characteristic + .on('get', function(callback) { + that.query(that.mappings.blind.reading, function(pos){ callback(undefined,pos); }); + }.bind(this) ); + + + this.log(" target position characteristic for " + this.name) + + var characteristic = controlService.getCharacteristic(Characteristic.TargetPosition); + + characteristic.value = FHEM_cached[that.mappings.blind.informId]; + + characteristic + .on('set', function(value, callback, context) { + if( context !== 'fromFhem' ) + that.delayed('targetPosition', value, 1500); + callback(); + }.bind(this) ) + .on('get', function(callback) { + that.query(that.mappings.blind.reading, function(pos){ callback(undefined,pos); }); + }.bind(this) ); + + + this.log(" position state characteristic for " + this.name) + + var characteristic = controlService.getCharacteristic(Characteristic.PositionState); + + if( that.mappings.motor ) + FHEM_subscribe(characteristic, that.mappings.motor.informId, that); + characteristic.value = that.mappings.motor?FHEM_cached[that.mappings.motor.informId]:Characteristic.PositionState.STOPPED; + + characteristic + .on('get', function(callback) { + if( that.mappings.motor ) + that.query(that.mappings.motor.reading, function(state){ callback(undefined,state); }); + }.bind(this) ); + + } + + if( this.mappings.window ) { + this.log(" current position characteristic for " + this.name) + + var characteristic = controlService.getCharacteristic(Characteristic.CurrentPosition); + + FHEM_subscribe(characteristic, that.name+'-state', that); + FHEM_subscribe(characteristic, that.mappings.window.informId, that); + characteristic.value = FHEM_cached[that.mappings.window.informId]; + + characteristic + .on('get', function(callback) { + that.query(that.mappings.window.reading, function(pos){ callback(undefined,pos); }); + }.bind(this) ); + + + this.log(" target position characteristic for " + this.name) + + var characteristic = controlService.getCharacteristic(Characteristic.TargetPosition); + + characteristic.value = FHEM_cached[that.mappings.window.informId]; + + characteristic + .on('set', function(value, callback, context) { + if( context !== 'fromFhem' ) + that.delayed('targetPosition', value, 1500); + callback(); + }.bind(this) ) + .on('get', function(callback) { + that.query(that.mappings.window.reading, function(level){ callback(undefined,level); }); + }.bind(this) ); + + + this.log(" position state characteristic for " + this.name) + + var characteristic = controlService.getCharacteristic(Characteristic.PositionState); + + if( that.mappings.direction ) + FHEM_subscribe(characteristic, that.mappings.direction.informId, that); + characteristic.value = that.mappings.direction?FHEM_cached[that.mappings.direction.informId]:Characteristic.PositionState.STOPPED; + + characteristic + .on('get', function(callback) { + if( that.mappings.direction ) + that.query(that.mappings.direction.reading, function(direction){ callback(undefined,direction); }); + }.bind(this) ); + + } + + if( this.mappings.garage ) { + this.log(" current door state characteristic for " + this.name) + + var characteristic = controlService.getCharacteristic(Characteristic.CurrentDoorState); + + characteristic.value = Characteristic.CurrentDoorState.STOPPED; + + characteristic + .on('get', function(callback) { + callback(undefined, Characteristic.CurrentDoorState.STOPPED); + }.bind(this) ); + + + this.log(" target door state characteristic for " + this.name) + + var characteristic = controlService.getCharacteristic(Characteristic.TargetDoorState); + + characteristic.value = 1; + + characteristic + .on('set', function(value, callback, context) { + if( context !== 'fromFhem' ) + that.command( 'set', value == 0 ? that.mappings.garage.cmdOpen : that.mappings.garage.cmdClose ); + callback(); + }.bind(this) ) + .on('get', function(callback) { + callback(undefined,0); + }.bind(this) ); + + + this.log(" obstruction detected characteristic for " + this.name) + + var characteristic = controlService.getCharacteristic(Characteristic.ObstructionDetected); + + //FHEM_subscribe(characteristic, that.mappings.direction.informId, that); + characteristic.value = 0; + + characteristic + .on('get', function(callback) { + callback(undefined,1); + }.bind(this) ); + + } + + if( this.mappings.temperature ) { + this.log(" temperature characteristic for " + this.name) + + var characteristic = controlService.getCharacteristic(Characteristic.CurrentTemperature) + || controlService.addCharacteristic(Characteristic.CurrentTemperature); + + FHEM_subscribe(characteristic, that.mappings.temperature.informId, that); + characteristic.value = FHEM_cached[that.mappings.temperature.informId]; + + characteristic + .on('get', function(callback) { + that.query(that.mappings.temperature.reading, function(temperature){ callback(undefined,temperature); }); + }.bind(this) ); + + } + + if( this.mappings.humidity ) { + this.log(" humidity characteristic for " + this.name) + + var characteristic = controlService.getCharacteristic(Characteristic.CurrentRelativeHumidity) + || controlService.addCharacteristic(Characteristic.CurrentRelativeHumidity); + + FHEM_subscribe(characteristic, that.mappings.humidity.informId, that); + characteristic.value = FHEM_cached[that.mappings.humidity.informId]; + + characteristic + .on('get', function(callback) { + that.query(that.mappings.humidity.reading, function(humidity){ callback(undefined,humidity); }); + }.bind(this) ); + + } + + if( this.mappings.airquality ) { + this.log(" air quality characteristic for " + this.name) + + var characteristic = controlService.getCharacteristic(Characteristic.AirQuality) + || controlService.addCharacteristic(Characteristic.AirQuality); + + FHEM_subscribe(characteristic, that.mappings.airquality.informId, that); + characteristic.value = FHEM_cached[that.mappings.airquality.informId]; + + characteristic + .on('get', function(callback) { + that.query(that.mappings.airquality.reading, function(airquality){ callback(undefined,airquality); }); + }.bind(this) ); + } + + + //FIXME: parse range and set designedMinValue & designedMaxValue & designedMinStep + if( this.mappings.thermostat ) { + this.log(" target temperature characteristic for " + this.name) + + var characteristic = controlService.getCharacteristic(Characteristic.TargetTemperature); + + FHEM_subscribe(characteristic, that.mappings.thermostat.informId, that); + characteristic.value = FHEM_cached[that.mappings.thermostat.informId]; + + characteristic + .on('set', function(value, callback, context) { + if( context !== 'fromFhem' ) + that.delayed('targetTemperature', value, 1500); + callback(); + }.bind(this) ) + .on('get', function(callback) { + that.query(that.mappings.thermostat.reading, function(temperature){ callback(undefined,temperature); }); + }.bind(this) ); + + } + + if( this.mappings.contact ) { + this.log(" contact sensor characteristic for " + this.name) + + var characteristic = controlService.getCharacteristic(Characteristic.ContactSensorState); + + FHEM_subscribe(characteristic, that.mappings.contact.informId, that); + characteristic.value = FHEM_cached[that.mappings.contact.informId]; + + characteristic + .on('get', function(callback) { + that.query(that.mappings.contact.reading, function(state){ callback(undefined,state); }); + }.bind(this) ); + + } + + if( this.mappings.occupancy ) { + this.log(" occupancy detected characteristic for " + this.name) + + var characteristic = controlService.getCharacteristic(Characteristic.OccupancyDetected); + + FHEM_subscribe(characteristic, that.mappings.occupancy.informId, that); + characteristic.value = FHEM_cached[that.mappings.occupancy.informId]; + + characteristic + .on('get', function(callback) { + that.query(that.mappings.occupancy.reading, function(state){ callback(undefined,state); }); + }.bind(this) ); + + } + + return [informationService, controlService]; } + }; //module.exports.accessory = FHEMAccessory; @@ -1768,7 +1522,7 @@ module.exports.platform = FHEMPlatform; //http server for debugging var http = require('http'); -const FHEMdebug_PORT=8080; +const FHEMdebug_PORT=8081; function FHEMdebug_handleRequest(request, response){ //console.log( request ); @@ -1783,32 +1537,8 @@ function FHEMdebug_handleRequest(request, response){ response.write( "home

" ); response.end( "subscriptions: " + util.inspect(FHEM_subscriptions, {depth: 4}).replace(/\n/g, '
') ); - } else if( request.url == "/xxpersist" ) { - 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 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); - 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" ); + response.end( "cached
subscriptions" ); } var FHEMdebug_server = http.createServer( FHEMdebug_handleRequest ); From f4160c2d01246eeaa98b3a7baad721d899d96c18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Mon, 14 Sep 2015 22:43:37 +0200 Subject: [PATCH 09/10] added identify --- platforms/FHEM.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index 6c7d45a..b81342a 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -1062,6 +1062,9 @@ FHEMAccessory.prototype = { identify: function(callback) { this.log('['+this.name+'] identify requested!'); + if( match = this.PossibleSets.match(/[\^ ]toggle\b/) ) { + this.command( 'identify' ); + } callback(); }, From 8ceaccfca43c864b05d8a2f21977d4f65c52c8c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=20Schr=C3=B6ter?= Date: Tue, 15 Sep 2015 22:03:17 +0200 Subject: [PATCH 10/10] cleanups, fixes and more new api stuff --- platforms/FHEM.js | 79 +++++++++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/platforms/FHEM.js b/platforms/FHEM.js index b81342a..03f3ee1 100644 --- a/platforms/FHEM.js +++ b/platforms/FHEM.js @@ -551,8 +551,11 @@ FHEMAccessory(log, connection, s) { if( s.Internals.TYPE == 'SONOSPLAYER' ) //FIXME: use sets [Pp]lay/[Pp]ause/[Ss]top this.mappings.onOff = { reading: 'transportState', cmdOn: 'play', cmdOff: 'pause' }; else if( s.PossibleSets.match(/[\^ ]on\b/) - && s.PossibleSets.match(/[\^ ]off\b/) ) + && s.PossibleSets.match(/[\^ ]off\b/) ) { this.mappings.onOff = { reading: 'state', cmdOn: 'on', cmdOff: 'off' }; + if( !s.Readings.state ) + delete this.mappings.onOff.reading; + } var event_map = s.Attributes.eventMap; if( event_map ) { @@ -912,14 +915,21 @@ FHEMAccessory.prototype = { }, query: function(reading, callback) { + if( reading == undefined ) { + if( callback != undefined ) + callback( 1 ); + return; + } + this.log("query: " + this.name + "-" + reading); var result = FHEM_cached[this.device + '-' + reading]; if( result != undefined ) { this.log(" cached: " + result); if( callback != undefined ) - callback( result ); - return( result ); + callback( undefined, result ); + return result; + } else this.log(" not cached" ); @@ -1009,11 +1019,14 @@ FHEMAccessory.prototype = { that.log(" mapped: " + value); FHEM_update( that.device + '-' + reading, value, true ); - if( value == undefined ) - return; - if( callback != undefined ) - callback(value); - return(value); + if( callback != undefined ) { + if( value == undefined ) + callback(1); + else + callback(undefined, value); + } + + return value ; } ); }, @@ -1038,7 +1051,7 @@ FHEMAccessory.prototype = { return new Service.Thermostat(name); } else if( this.mappings.contact ) { this.log(" contact sensor service for " + this.name) - return new Service.ContactSensorState(name); + return new Service.ContactSensor(name); } else if( this.mappings.occupancy ) { this.log(" occupancy sensor service for " + this.name) return new Service.OccupancySensor(name); @@ -1098,7 +1111,7 @@ FHEMAccessory.prototype = { callback(); }.bind(this) ) .on('get', function(callback) { - that.query( that.mappings.onOff.reading, function(state){ callback(undefined, state); } ); + that.query(that.mappings.onOff.reading, callback); }.bind(this) ); } @@ -1118,7 +1131,7 @@ FHEMAccessory.prototype = { callback(); }.bind(this) ) .on('get', function(callback) { - that.query(that.mappings.pct.reading, function(pct){ callback(undefined,pct); }); + that.query(that.mappings.pct.reading, callback); }.bind(this) ); } else if( this.hasDim ) { @@ -1137,7 +1150,7 @@ FHEMAccessory.prototype = { callback(); }.bind(this) ) .on('get', function(callback) { - that.query('pct', function(pct){ callback(undefined,pct); }); + that.query('pct', callback); }.bind(this) ); } @@ -1158,7 +1171,7 @@ FHEMAccessory.prototype = { callback(); }.bind(this) ) .on('get', function(callback) { - that.query(that.mappings.hue.reading, function(hue){ callback(undefined,hue); }); + that.query(that.mappings.hue.reading, callback); }.bind(this) ); } else if( this.mappings.rgb ) { @@ -1177,7 +1190,7 @@ FHEMAccessory.prototype = { callback(); }.bind(this) ) .on('get', function(callback) { - that.query('hue', function(hue){ callback(undefined,hue); }); + that.query('hue', callback); }.bind(this) ); if( !this.mappings.sat ) { @@ -1195,7 +1208,7 @@ FHEMAccessory.prototype = { callback(); }.bind(this) ) .on('get', function(callback) { - that.query('sat', function(sat){ callback(undefined,sat); }); + that.query('sat', callback); }.bind(this) ); } @@ -1214,7 +1227,7 @@ FHEMAccessory.prototype = { callback(); }.bind(this) ) .on('get', function(callback) { - that.query('bri', function(bri){ callback(undefined,bri); }); + that.query('bri', callback); }.bind(this) ); } @@ -1236,14 +1249,14 @@ FHEMAccessory.prototype = { callback(); }.bind(this) ) .on('get', function(callback) { - that.query(that.mappings.sat.reading, function(sat){ callback(undefined,sat); }); + that.query(that.mappings.sat.reading, callback); }.bind(this) ); } if( this.mappings.volume ) { this.log(" custom volume characteristic for " + this.name) - var characteristic = new Characteristic('Adjust the Volume of this device', '00000027-0000-1000-8000-0026BB765291'); // FIXME!!! + var characteristic = new Characteristic('Volume', '00000027-0000-1000-8000-0026BB765291'); // FIXME!!! controlService.addCharacteristic(characteristic); if( !that.mappings.volume.nocache ) { @@ -1253,8 +1266,8 @@ FHEMAccessory.prototype = { characteristic.value = 10; } - characteristic.format = 'int'; - //characteristic.unit = 'percentage'; + characteristic.format = 'uint8'; + characteristic.unit = 'percentage'; characteristic.maximumValue = 100; characteristic.minimumValue = 0; characteristic.stepValue = 1; @@ -1269,7 +1282,7 @@ FHEMAccessory.prototype = { callback(); }.bind(this) ) .on('get', function(callback) { - that.query(that.mappings.volume.reading, function(volume){ callback(undefined,volume); }); + that.query(that.mappings.volume.reading, callback); }.bind(this) ); } @@ -1284,7 +1297,7 @@ FHEMAccessory.prototype = { characteristic .on('get', function(callback) { - that.query(that.mappings.blind.reading, function(pos){ callback(undefined,pos); }); + that.query(that.mappings.blind.reading, callback); }.bind(this) ); @@ -1301,7 +1314,7 @@ FHEMAccessory.prototype = { callback(); }.bind(this) ) .on('get', function(callback) { - that.query(that.mappings.blind.reading, function(pos){ callback(undefined,pos); }); + that.query(that.mappings.blind.reading, callback); }.bind(this) ); @@ -1316,7 +1329,7 @@ FHEMAccessory.prototype = { characteristic .on('get', function(callback) { if( that.mappings.motor ) - that.query(that.mappings.motor.reading, function(state){ callback(undefined,state); }); + that.query(that.mappings.motor.reading, callback); }.bind(this) ); } @@ -1332,7 +1345,7 @@ FHEMAccessory.prototype = { characteristic .on('get', function(callback) { - that.query(that.mappings.window.reading, function(pos){ callback(undefined,pos); }); + that.query(that.mappings.window.reading, callback); }.bind(this) ); @@ -1349,7 +1362,7 @@ FHEMAccessory.prototype = { callback(); }.bind(this) ) .on('get', function(callback) { - that.query(that.mappings.window.reading, function(level){ callback(undefined,level); }); + that.query(that.mappings.window.reading, callback); }.bind(this) ); @@ -1364,7 +1377,7 @@ FHEMAccessory.prototype = { characteristic .on('get', function(callback) { if( that.mappings.direction ) - that.query(that.mappings.direction.reading, function(direction){ callback(undefined,direction); }); + that.query(that.mappings.direction.reading, callback); }.bind(this) ); } @@ -1424,7 +1437,7 @@ FHEMAccessory.prototype = { characteristic .on('get', function(callback) { - that.query(that.mappings.temperature.reading, function(temperature){ callback(undefined,temperature); }); + that.query(that.mappings.temperature.reading, callback); }.bind(this) ); } @@ -1440,7 +1453,7 @@ FHEMAccessory.prototype = { characteristic .on('get', function(callback) { - that.query(that.mappings.humidity.reading, function(humidity){ callback(undefined,humidity); }); + that.query(that.mappings.humidity.reading, callback); }.bind(this) ); } @@ -1456,7 +1469,7 @@ FHEMAccessory.prototype = { characteristic .on('get', function(callback) { - that.query(that.mappings.airquality.reading, function(airquality){ callback(undefined,airquality); }); + that.query(that.mappings.airquality.reading, callback); }.bind(this) ); } @@ -1477,7 +1490,7 @@ FHEMAccessory.prototype = { callback(); }.bind(this) ) .on('get', function(callback) { - that.query(that.mappings.thermostat.reading, function(temperature){ callback(undefined,temperature); }); + that.query(that.mappings.thermostat.reading, callback); }.bind(this) ); } @@ -1492,7 +1505,7 @@ FHEMAccessory.prototype = { characteristic .on('get', function(callback) { - that.query(that.mappings.contact.reading, function(state){ callback(undefined,state); }); + that.query(that.mappings.contact.reading, callback); }.bind(this) ); } @@ -1507,7 +1520,7 @@ FHEMAccessory.prototype = { characteristic .on('get', function(callback) { - that.query(that.mappings.occupancy.reading, function(state){ callback(undefined,state); }); + that.query(that.mappings.occupancy.reading, callback); }.bind(this) ); }