Files
homebridge/platforms/FHEM.js
2015-09-13 13:30:04 +02:00

1825 lines
58 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// FHEM Platform Shim for HomeBridge
//
// Remember to add platform to config.json. Example:
// "platforms": [
// {
// 'platform': "FHEM",
// 'name': "FHEM",
// 'server': "127.0.0.1",
// 'port': 8083,
// 'ssl': true,
// 'auth': {'user': "fhem", 'pass': "fhempassword"},
// 'filter': "room=xyz"
// }
// ],
//
// When you attempt to add a device, it will ask for a "PIN code".
// The default code for all HomeBridge accessories is 031-45-154.
var types = require('HAP-NodeJS/accessories/types.js');
var util = require('util');
// 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"+
"&timestamp="+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 );
}
function
FHEM_sortByKey(array, key) {
return array.sort( function(a, b) {
var x = a[key]; var y = b[key];
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
}
function
FHEM_rgb2hex(r,g,b) {
if( g == undefined )
return Number(0x1000000 + r[0]*0x10000 + r[1]*0x100 + r[2]).toString(16).substring(1);
return Number(0x1000000 + r*0x10000 + g*0x100 + b).toString(16).substring(1);
}
function
FHEM_hsv2rgb(h,s,v) {
var r = 0.0;
var g = 0.0;
var b = 0.0;
if( s == 0 ) {
r = v;
g = v;
b = v;
} else {
var i = Math.floor( h * 6.0 );
var f = ( h * 6.0 ) - i;
var p = v * ( 1.0 - s );
var q = v * ( 1.0 - s * f );
var t = v * ( 1.0 - s * ( 1.0 - f ) );
i = i % 6;
if( i == 0 ) {
r = v;
g = t;
b = p;
} else if( i == 1 ) {
r = q;
g = v;
b = p;
} else if( i == 2 ) {
r = p;
g = v;
b = t;
} else if( i == 3 ) {
r = p;
g = q;
b = v;
} else if( i == 4 ) {
r = t;
g = p;
b = v;
} else if( i == 5 ) {
r = v;
g = p;
b = q;
}
}
return FHEM_rgb2hex( Math.round(r*255),Math.round(g*255),Math.round(b*255) );
}
function
FHEM_rgb2hsv(r,g,b){
if( r == undefined )
return;
if( g == undefined ) {
var str = r;
r = parseInt( str.substr(0,2), 16 );
g = parseInt( str.substr(2,2), 16 );
b = parseInt( str.substr(4,2), 16 );
}
var M = Math.max( r, g, b );
var m = Math.min( r, g, b );
var c = M - m;
var h, s, v;
if( c == 0 ) {
h = 0;
} else if( M == r ) {
h = ( 60 * ( ( g - b ) / c ) % 360 ) / 360;
} else if( M == g ) {
h = ( 60 * ( ( b - r ) / c ) + 120 ) / 360;
} else if( M == b ) {
h = ( 60 * ( ( r - g ) / c ) + 240 ) / 360;
}
if( M == 0 ) {
s = 0;
} else {
s = c / M;
}
v = M/255;
return [h,s,v];
}
FHEMPlatform.prototype = {
accessories: function(callback) {
this.log("Fetching FHEM switchable devices...");
var foundAccessories = [];
// 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( this.connection.base_url + "/fhem?cmd=" + cmd + "&XHR=1");
this.log( 'fetching: ' + url );
asyncCalls++;
var that = this;
this.connection.request.get( { url: url, json: true, gzip: true },
function(err, response, json) {
if( !err && response.statusCode == 200 ) {
that.log( 'got: ' + json['totalResultsReturned'] + ' results' );
//that.log("got json: " + util.inspect(json) );
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 )
foundAccessories.push(accessory);
});
}
//callback(foundAccessories);
callbackLater();
} else {
that.log("There was a problem connecting to FHEM (1).");
if( response )
that.log( " " + response.statusCode + ": " + response.statusMessage );
}
});
}
}
function
FHEMAccessory(log, connection, s) {
//log( 'sets: ' + s.PossibleSets );
//log("got json: " + util.inspect(s) );
//log("got json: " + util.inspect(s.Internals) );
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;
s.pctMax = 100;
}
if( match = s.PossibleSets.match(/[\^ ]hue[^\b\s]*(,(\d+)?)+\b/) ) {
s.isLight = true;
var max = 360;
if( match[2] != undefined )
max = match[2];
this.mappings.hue = { reading: 'hue', cmd: 'hue', min: 0, max: max };
}
if( match = s.PossibleSets.match(/[\^ ]sat[^\b\s]*(,(\d+)?)+\b/) ) {
s.isLight = true;
var max = 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;
}
if( s.Readings.humidity )
this.mappings.humidity = { reading: 'humidity' };
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
return {};
if( this.mappings.onOff )
log( s.Internals.NAME + ' has onOff [' + this.mappings.onOff.reading + ':' + this.mappings.onOff.cmdOn +',' + this.mappings.onOff.cmdOff + ']' );
if( this.mappings.hue )
log( s.Internals.NAME + ' has hue [0-' + this.mappings.hue.max +']' );
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( util.inspect(s) );
// device info
this.name = s.Internals.NAME;
this.alias = s.Attributes.alias ? s.Attributes.alias : s.Internals.NAME;
this.device = s.Internals.NAME;
this.type = s.Internals.TYPE;
this.model = s.Readings.model ? s.Readings.model.Value
: (s.Attributes.model ? s.Attributes.model
: ( s.Internals.model ? s.Internals.model : '<unknown>' ) );
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' )
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.pctMax = s.pctMax;
this.isLight = s.isLight;
this.isSwitch = s.isSwitch;
//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) );
};
}
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( value == undefined )
return undefined;
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( /uncertain/ ) )
value = 4;
else if( value.match( /^locked/ ) )
value = 1;
else
value = 0;
} 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;
else
this.log(this.name + " Unhandled command! cmd=" + c + ", value=" + value);
} else {
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);
} );
},
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.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 : '<unknown>',
supportEvents: false,
supportBonjour: false,
manfDescription: "Model",
designedMaxLength: 255
},{
cType: types.SERIAL_NUMBER_CTYPE,
onUpdate: null,
perms: ["pr"],
format: "string",
initialValue: this.serial ? this.serial : "<unknown>",
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
}];
},
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;
} 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;
}
},
getServices: function() {
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;
}
};
//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( "<a href='/'>home</a><br><br>" );
if( FHEM_lastEventTime )
response.write( "FHEM_lastEventTime: "+ new Date(FHEM_lastEventTime) +"<br><br>" );
response.end( "cached: " + util.inspect(FHEM_cached).replace(/\n/g, '<br>') );
} else if( request.url == "/subscriptions" ) {
response.write( "<a href='/'>home</a><br><br>" );
response.end( "subscriptions: " + util.inspect(FHEM_subscriptions, {depth: 4}).replace(/\n/g, '<br>') );
} else if( request.url == "/xxpersist" ) {
response.write( "<a href='/'>home</a><br><br>" );
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] +'<br>' );
}
response.end( "" );
} else
response.end( "<a href='/cached'>cached</a><br><a href='/persist'>persist</a><br><a href='/subscriptions'>subscriptions</a>" );
}
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://<ip>:%s", FHEMdebug_PORT);
});