diff --git a/accessories/Lockitron.js b/accessories/Lockitron.js index 04414af..8088d14 100644 --- a/accessories/Lockitron.js +++ b/accessories/Lockitron.js @@ -11,6 +11,17 @@ function LockitronAccessory(log, config) { this.name = config["name"]; this.accessToken = config["api_token"]; this.lockID = config["lock_id"]; + + this.service = new Service.LockMechanism(this.name); + + this.service + .getCharacteristic(Characteristic.LockCurrentState) + .on('get', this.getState.bind(this)); + + this.service + .getCharacteristic(Characteristic.LockTargetState) + .on('get', this.getState.bind(this)) + .on('set', this.setState.bind(this)); } LockitronAccessory.prototype.getState = function(callback) { @@ -36,7 +47,7 @@ LockitronAccessory.prototype.getState = function(callback) { } LockitronAccessory.prototype.setState = function(state, callback) { - var lockitronState = (state == 1) ? "lock" : "unlock"; + var lockitronState = (state == Characteristic.LockTargetState.SECURED) ? "lock" : "unlock"; this.log("Set state to %s", lockitronState); @@ -47,6 +58,14 @@ LockitronAccessory.prototype.setState = function(state, callback) { if (!err && response.statusCode == 200) { this.log("State change complete."); + + // we succeeded, so update the "current" state as well + var currentState = (state == Characteristic.LockTargetState.SECURED) ? + Characteristic.LockCurrentState.SECURED : Characteristic.LockCurrentState.UNSECURED; + + this.service + .setCharacteristic(Characteristic.LockCurrentState, currentState); + callback(null); // success } else { @@ -57,17 +76,5 @@ LockitronAccessory.prototype.setState = function(state, callback) { }, LockitronAccessory.prototype.getServices = function() { - - var service = new Service.LockMechanism(this.name); - - service - .getCharacteristic(Characteristic.LockCurrentState) - .on('get', this.getState.bind(this)); - - service - .getCharacteristic(Characteristic.LockTargetState) - .on('get', this.getState.bind(this)) - .on('set', this.setState.bind(this)); - - return [service]; + return [this.service]; } diff --git a/accessories/WeMo.js b/accessories/WeMo.js index c19e19f..b97b041 100644 --- a/accessories/WeMo.js +++ b/accessories/WeMo.js @@ -144,6 +144,16 @@ WeMoAccessory.prototype.getServices = function() { return [garageDoorService]; } + else if (this.service == "Light") { + var lightbulbService = new Service.Lightbulb(this.name); + + lightbulbService + .getCharacteristic(Characteristic.On) + .on('get', this.getPowerOn.bind(this)) + .on('set', this.setPowerOn.bind(this)); + + return [lightbulbService]; + } else if (this.service == "MotionSensor") { var motionSensorService = new Service.MotionSensor(this.name); diff --git a/accessories/knxdevice.js b/accessories/knxdevice.js index 5801249..4656522 100644 --- a/accessories/knxdevice.js +++ b/accessories/knxdevice.js @@ -14,6 +14,9 @@ New 2015-09-18: New 2015-09-19: - GarageDoorOpener Service - MotionSensor Service +New 2015-10-02: +- Check for valid group addresses +- new "R" flag allowed for Boolean addresses: 1/2/3R is the boolean not(1/2/3), i.e. 0 and 1 switched on read and write * */ var Service = require("HAP-NodeJS").Service; @@ -24,6 +27,8 @@ var knxd_startMonitor = require('../platforms/KNX.js').startMonitor; var milliTimeout = 300; // used to block responses while swiping +var colorOn = "\x1b[30;47m"; +var colorOff = "\x1b[0m"; function KNXDevice(log, config) { this.log = log; @@ -115,6 +120,7 @@ KNXDevice.prototype = { if (!groupAddress) { return null; } + this.log("[knxdevice:knxread] preparing knx request for "+groupAddress); var knxdConnection = new knxd.Connection(); // this.log("DEBUG in knxread: created empty connection, trying to connect socket to "+this.knxd_ip+":"+this.knxd_port); knxdConnection.socketRemote({ host: this.knxd_ip, port: this.knxd_port }, function() { @@ -130,7 +136,7 @@ KNXDevice.prototype = { if (err) { this.log("[ERROR] knxread:sendAPDU: " + err); } else { - this.log("knx request sent for "+groupAddress); + this.log("[knxdevice:knxread] knx request sent for "+groupAddress); } }.bind(this)); } @@ -143,12 +149,12 @@ KNXDevice.prototype = { // handle multiple addresses for (var i = 0; i < groupAddresses.length; i++) { if (groupAddresses[i]) { // do not bind empty addresses - this.knxread (groupAddresses[i]); + this.knxread (groupAddresses[i].match(/(\d*\/\d*\/\d*)/)[0]); // clean address } } } else { // it's only one - this.knxread (groupAddresses); + this.knxread (groupAddresses.match(/(\d*\/\d*\/\d*)/)[0]); // regex for cleaning address } }, @@ -158,70 +164,80 @@ KNXDevice.prototype = { // boolean: get 0 or 1 from the bus, write boolean knxregister_bool: function(addresses, characteristic) { this.log("knx registering BOOLEAN " + addresses); - knxd_registerGA(addresses, function(val, src, dest, type){ - this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type + " for " + characteristic.displayName); + knxd_registerGA(addresses, function(val, src, dest, type, reverse){ + this.log("[" +this.name + "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type + " for " + characteristic.displayName); // iterate(characteristic); - characteristic.setValue(val ? 1 : 0, undefined, 'fromKNXBus'); - }.bind(this)); - }, - knxregister_boolReverse: function(addresses, characteristic) { - this.log("knx registering BOOLEAN " + addresses); - knxd_registerGA(addresses, function(val, src, dest, type){ - this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type + " for " + characteristic.displayName); -// iterate(characteristic); - characteristic.setValue(val ? 0 : 1, undefined, 'fromKNXBus'); + + characteristic.setValue(val ? (reverse ? 0:1) : (reverse ? 1:0), undefined, 'fromKNXBus'); }.bind(this)); }, +// knxregister_boolReverse: function(addresses, characteristic) { +// this.log("knx registering BOOLEAN REVERSE " + addresses); +// knxd_registerGA(addresses, function(val, src, dest, type, reverse){ +// this.log("[" +this.name + "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type + " for " + characteristic.displayName); +//// iterate(characteristic); +// characteristic.setValue(val ? 0 : 1, undefined, 'fromKNXBus'); +// }.bind(this)); +// }, // percentage: get 0..255 from the bus, write 0..100 to characteristic knxregister_percent: function(addresses, characteristic) { this.log("knx registering PERCENT " + addresses); - knxd_registerGA(addresses, function(val, src, dest, type){ - this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); + knxd_registerGA(addresses, function(val, src, dest, type, reverse){ + this.log("[" +this.name + "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); if (type !== "DPT5") { this.log("[ERROR] Received value cannot be a percentage value"); } else { - if (!characteristic.timeout) { - if (characteristic.timeout < Date.now()) { - characteristic.setValue(Math.round(val/255*100), undefined, 'fromKNXBus'); - } else { - this.log("Blackout time"); - } - } else { - characteristic.setValue(Math.round(val/255*100), undefined, 'fromKNXBus'); - } // todo get the boolean logic right into one OR expresssion - + characteristic.setValue(Math.round(( reverse ? (255-val):val)/255*100), undefined, 'fromKNXBus'); } }.bind(this)); }, // float knxregister_float: function(addresses, characteristic) { - this.log("knx registering FLOAT " + addresses); - knxd_registerGA(addresses, function(val, src, dest, type){ - this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); - var hk_value = Math.round(val*10)/10; - if (hk_value>=characteristic.minimumValue && hk_value<=characteristic.maximumValue) { + // update for props refactor https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR50 + + var validValue = true; + var hk_value = 0.0; + this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:knx registering FLOAT " + addresses); + knxd_registerGA(addresses, function(val, src, dest, type, reverse){ + this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); + // make hk_value compliant to properties + if (characteristic.props.minStep) { + // quantize + hk_value = Math.round(val/characteristic.props.minStep)/(1/characteristic.props.minStep); + } else { + hk_value = val; + } + // range check + validValue = true; // assume validity at beginning + if (characteristic.props.minValue) { + validValue = validValue && (hk_value>=characteristic.props.minValue); + } + if (characteristic.props.maxValue) { + validValue = validValue && (hk_value<=characteristic.props.maxValue); + } + if (validValue) { characteristic.setValue(hk_value, undefined, 'fromKNXBus'); // 1 decimal for HomeKit } else { - this.log("Value %s out of bounds %s...%s ",hk_value, characteristic.minimumValue, characteristic.maximumValue); + this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: Value %s out of bounds %s...%s ",hk_value, characteristic.props.minValue, characteristic.props.maxValue); } }.bind(this)); }, //integer knxregister_int: function(addresses, characteristic) { - this.log("knx registering FLOAT " + addresses); - knxd_registerGA(addresses, function(val, src, dest, type){ - this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); - if (val>=(characteristic.minimumValue || 0) && val<=(characteristic.maximumValue || 255)) { - characteristic.setValue(val, undefined, 'fromKNXBus'); + this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:knx registering INT " + addresses); + knxd_registerGA(addresses, function(val, src, dest, type, reverse){ + this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); + if (val>=(characteristic.props.minValue || 0) && val<=(characteristic.props.maxValue || 255)) { + characteristic.setValue(reverse ? (255-val):val, undefined, 'fromKNXBus'); } else { - this.log("Value %s out of bounds %s...%s ",hk_value, (characteristic.minimumValue || 0), (characteristic.maximumValue || 255)); + this.log("["+ this.name +"]:[" + characteristic.displayName+ "]: Value %s out of bounds %s...%s ",hk_value, (characteristic.props.minValue || 0), (characteristic.props.maxValue || 255)); } }.bind(this)); }, knxregister_HVAC: function(addresses, characteristic) { - this.log("knx registering HVAC " + addresses); + this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:knx registering HVAC " + addresses); knxd_registerGA(addresses, function(val, src, dest, type){ - this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); + this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); var HAPvalue = 0; switch (val){ case 0: @@ -261,9 +277,9 @@ KNXDevice.prototype = { */ // undefined, has to match! knxregister: function(addresses, characteristic) { - this.log("knx registering " + addresses); + this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:knx registering " + addresses); knxd_registerGA(addresses, function(val, src, dest, type){ - this.log("Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); + this.log("["+ this.name +"]:[" + characteristic.displayName+ "]:Received value from bus:"+val+ " for " +dest+ " from "+src+" of type "+type+ " for " + characteristic.displayName); characteristic.setValue(val, undefined, 'fromKNXBus'); }.bind(this)); }, @@ -276,71 +292,74 @@ KNXDevice.prototype = { * }.bind(this)); * */ - setBooleanState: function(value, callback, context, gaddress) { + setBooleanState: function(value, callback, context, gaddress, reverseflag) { if (context === 'fromKNXBus') { - this.log(gaddress + " event ping pong, exit!"); +// this.log(gaddress + " event ping pong, exit!"); if (callback) { callback(); } } else { - var numericValue = 0; + var numericValue = reverseflag ? 1:0; if (value) { - numericValue = 1; // need 0 or 1, not true or something + numericValue = reverseflag ? 0:1; // need 0 or 1, not true or something } - this.log("Setting "+gaddress+" Boolean to %s", numericValue); + this.log("["+ this.name +"]:Setting "+gaddress+" " + reverseflag ? " (reverse)":""+ " Boolean to %s", numericValue); this.knxwrite(callback, gaddress,'DPT1',numericValue); } }, - setBooleanReverseState: function(value, callback, context, gaddress) { +// setBooleanReverseState: function(value, callback, context, gaddress) { +// if (context === 'fromKNXBus') { +//// this.log(gaddress + " event ping pong, exit!"); +// if (callback) { +// callback(); +// } +// } else { +// var numericValue = 0; +// if (!value) { +// numericValue = 1; // need 0 or 1, not true or something +// } +// this.log("["+ this.name +"]:Setting "+gaddress+" Boolean to %s", numericValue); +// this.knxwrite(callback, gaddress,'DPT1',numericValue); +// } +// +// }, + setPercentage: function(value, callback, context, gaddress, reverseflag) { if (context === 'fromKNXBus') { - this.log(gaddress + " event ping pong, exit!"); - if (callback) { - callback(); - } - } else { - var numericValue = 0; - if (!value) { - numericValue = 1; // need 0 or 1, not true or something - } - this.log("Setting "+gaddress+" Boolean to %s", numericValue); - this.knxwrite(callback, gaddress,'DPT1',numericValue); - } - - }, - setPercentage: function(value, callback, context, gaddress) { - if (context === 'fromKNXBus') { - this.log("event ping pong, exit!"); +// this.log(gaddress + "event ping pong, exit!"); if (callback) { callback(); } } else { var numericValue = 0; - if (value) { - numericValue = Math.round(255*value/100); // convert 1..100 to 1..255 for KNX bus + value = ( value>=0 ? (value<=100 ? value:100):0 ); //ensure range 0..100 + if (reverseflag) { + numericValue = 255 - Math.round(255*value/100); // convert 0..100 to 255..0 for KNX bus + } else { + numericValue = Math.round(255*value/100); // convert 0..100 to 0..255 for KNX bus } - this.log("Setting "+gaddress+" percentage to %s (%s)", value, numericValue); + this.log("["+ this.name +"]:Setting "+gaddress+" percentage to %s (%s)", value, numericValue); this.knxwrite(callback, gaddress,'DPT5',numericValue); } }, setInt: function(value, callback, context, gaddress) { if (context === 'fromKNXBus') { - this.log("event ping pong, exit!"); +// this.log(gaddress + "event ping pong, exit!"); if (callback) { callback(); } } else { var numericValue = 0; if (value && value>=0 && value<=255) { - numericValue = value; // assure 1..255 for KNX bus + numericValue = value; // assure 0..255 for KNX bus } - this.log("Setting "+gaddress+" int to %s (%s)", value, numericValue); + this.log("["+ this.name +"]:Setting "+gaddress+" int to %s (%s)", value, numericValue); this.knxwrite(callback, gaddress,'DPT5',numericValue); } }, setFloat: function(value, callback, context, gaddress) { if (context === 'fromKNXBus') { - this.log(gaddress + " event ping pong, exit!"); +// this.log(gaddress + " event ping pong, exit!"); if (callback) { callback(); } @@ -349,13 +368,13 @@ KNXDevice.prototype = { if (value) { numericValue = value; // homekit expects precision of 1 decimal } - this.log("Setting "+gaddress+" Float to %s", numericValue); + this.log("["+ this.name +"]:Setting "+gaddress+" Float to %s", numericValue); this.knxwrite(callback, gaddress,'DPT9',numericValue); } }, setHVACState: function(value, callback, context, gaddress) { if (context === 'fromKNXBus') { - this.log(gaddress + " event ping pong, exit!"); +// this.log(gaddress + " event ping pong, exit!"); if (callback) { callback(); } @@ -378,7 +397,7 @@ KNXDevice.prototype = { KNXvalue = 1; } - this.log("Setting "+gaddress+" HVAC to %s", KNXvalue); + this.log("["+ this.name +"]:Setting "+gaddress+" HVAC to %s", KNXvalue); this.knxwrite(callback, gaddress,'DPT5',KNXvalue); } @@ -387,33 +406,50 @@ KNXDevice.prototype = { * */ identify: function(callback) { - this.log("Identify requested!"); + this.log("["+ this.name +"]:Identify requested!"); callback(); // success }, /** bindCharacteristic * initializes callbacks for 'set' events (from HK) and for KNX bus reads (to HK) */ - bindCharacteristic: function(myService, characteristicType, valueType, config) { + bindCharacteristic: function(myService, characteristicType, valueType, config, defaultValue) { var myCharacteristic = myService.getCharacteristic(characteristicType); + var setGA = ""; + var setReverse = false; if (myCharacteristic === undefined) { throw new Error("unknown characteristics cannot be bound"); } + if (defaultValue) { + myCharacteristic.setValue(defaultValue); + } if (config.Set) { // can write + // extract address and Reverse flag + setGA = config.Set.match(/\d*\/\d*\/\d*/); + if (setGA===null) { + this.log(colorOn + "["+ this.name +"]:["+myCharacteristic.displayName+"] Error in group adress: ["+ config.Set +"] "+colorOff); + throw new Error("EINVGROUPADRESS - Invalid group address given"); + } else { + setGA=setGA[0]; // first element of returned array is the group address + } + + setReverse = config.Set.match(/\d*\/\d*\/\d*(R)/) ? true:false; + switch (valueType) { case "Bool": myCharacteristic.on('set', function(value, callback, context) { - this.setBooleanState(value, callback, context, config.Set); - }.bind(this)); - break; - case "BoolReverse": - myCharacteristic.on('set', function(value, callback, context) { - this.setBooleanReverseState(value, callback, context, config.Set); + this.setBooleanState(value, callback, context, setGA, setReverse); //NEW }.bind(this)); break; +// case "BoolReverse": +// this.log("["+ this.name +"]:["+myCharacteristic.displayName+"] \x1b[30;47m%s\x1b[0mWARNING in group adress: "+ config.Set +": Legacy BoolReverse used. Use " + config.Set +"R instead"); +// myCharacteristic.on('set', function(value, callback, context) { +// this.setBooleanReverseState(value, callback, context, config.Set); +// }.bind(this)); +// break; case "Percent": myCharacteristic.on('set', function(value, callback, context) { - this.setPercentage(value, callback, context, config.Set); + this.setPercentage(value, callback, context, setGA, setReverse); myCharacteristic.timeout = Date.now()+milliTimeout; }.bind(this)); break; @@ -433,7 +469,7 @@ KNXDevice.prototype = { }.bind(this)); break; default: { - this.log("[ERROR] unknown type passed"); + this.log(colorOn + "[ERROR] unknown type passed: [" + valueType+"]"+ colorOff); throw new Error("[ERROR] unknown type passed"); } } @@ -445,9 +481,9 @@ KNXDevice.prototype = { case "Bool": this.knxregister_bool([config.Set].concat(config.Listen || []), myCharacteristic); break; - case "BoolReverse": - this.knxregister_boolReverse([config.Set].concat(config.Listen || []), myCharacteristic); - break; +// case "BoolReverse": +// this.knxregister_boolReverse([config.Set].concat(config.Listen || []), myCharacteristic); +// break; case "Percent": this.knxregister_percent([config.Set].concat(config.Listen || []), myCharacteristic); break; @@ -461,10 +497,10 @@ KNXDevice.prototype = { this.knxregister_HVAC([config.Set].concat(config.Listen || []), myCharacteristic); break; default: - this.log("[ERROR] unknown type passed"); - throw new Error("[ERROR] unknown type passed"); + this.log(colorOn+ "[ERROR] unknown type passed: ["+valueType+"]"+colorOff); + throw new Error("[ERROR] unknown type passed"); } - this.log("Issuing read requests on the KNX bus..."); + this.log("["+ this.name +"]:["+myCharacteristic.displayName+"]: Issuing read requests on the KNX bus..."); this.knxreadarray([config.Set].concat(config.Listen || [])); } return myCharacteristic; // for chaining or whatsoever @@ -494,30 +530,30 @@ KNXDevice.prototype = { var myService = new Service.ContactSensor(config.name,config.name); if (config.ContactSensorState) { - this.log("ContactSensor ContactSensorState characteristic enabled"); + this.log("["+ this.name +"]:ContactSensor ContactSensorState characteristic enabled"); this.bindCharacteristic(myService, Characteristic.ContactSensorState, "Bool", config.ContactSensorState); } else if (config.ContactSensorStateContact1) { - this.log("ContactSensor ContactSensorStateContact1 characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.ContactSensorState, "BoolReverse", config.ContactSensorStateContact1); + this.log(colorOn+ "[ERROR] outdated type passed: [ContactSensorStateContact1]"+colorOff); + throw new Error("[ERROR] outdated type passed"); } //optionals if (config.StatusActive) { - this.log("ContactSensor StatusActive characteristic enabled"); + this.log("["+ this.name +"]:ContactSensor StatusActive characteristic enabled"); myService.addCharacteristic(Characteristic.StatusActive); this.bindCharacteristic(myService, Characteristic.StatusActive, "Bool", config.StatusActive); } if (config.StatusFault) { - this.log("ContactSensor StatusFault characteristic enabled"); + this.log("["+ this.name +"]:ContactSensor StatusFault characteristic enabled"); myService.addCharacteristic(Characteristic.StatusFault); this.bindCharacteristic(myService, Characteristic.StatusFault, "Bool", config.StatusFault); } if (config.StatusTampered) { - this.log("ContactSensor StatusTampered characteristic enabled"); + this.log("["+ this.name +"]:ContactSensor StatusTampered characteristic enabled"); myService.addCharacteristic(Characteristic.StatusTampered); this.bindCharacteristic(myService, Characteristic.StatusTampered, "Bool", config.StatusTampered); } if (config.StatusLowBattery) { - this.log("ContactSensor StatusLowBattery characteristic enabled"); + this.log("["+ this.name +"]:ContactSensor StatusLowBattery characteristic enabled"); myService.addCharacteristic(Characteristic.StatusLowBattery); this.bindCharacteristic(myService, Characteristic.StatusLowBattery, "Bool", config.StatusLowBattery); } @@ -555,27 +591,27 @@ KNXDevice.prototype = { var myService = new Service.GarageDoorOpener(config.name,config.name); if (config.CurrentDoorState) { - this.log("GarageDoorOpener CurrentDoorState characteristic enabled"); + this.log("["+ this.name +"]:GarageDoorOpener CurrentDoorState characteristic enabled"); this.bindCharacteristic(myService, Characteristic.CurrentDoorState, "Int", config.CurrentDoorState); } if (config.TargetDoorState) { - this.log("GarageDoorOpener TargetDoorState characteristic enabled"); + this.log("["+ this.name +"]:GarageDoorOpener TargetDoorState characteristic enabled"); //myService.getCharacteristic(Characteristic.TargetDoorState).minimumValue=0; // //myService.getCharacteristic(Characteristic.TargetDoorState).maximumValue=4; // this.bindCharacteristic(myService, Characteristic.TargetDoorState, "Int", config.TargetDoorState); } if (config.ObstructionDetected) { - this.log("GarageDoorOpener ObstructionDetected characteristic enabled"); + this.log("["+ this.name +"]:GarageDoorOpener ObstructionDetected characteristic enabled"); this.bindCharacteristic(myService, Characteristic.ObstructionDetected, "Bool", config.ObstructionDetected); } //optionals if (config.LockCurrentState) { - this.log("GarageDoorOpener LockCurrentState characteristic enabled"); + this.log("["+ this.name +"]:GarageDoorOpener LockCurrentState characteristic enabled"); myService.addCharacteristic(Characteristic.LockCurrentState); this.bindCharacteristic(myService, Characteristic.LockCurrentState, "Int", config.LockCurrentState); } if (config.LockTargetState) { - this.log("GarageDoorOpener LockTargetState characteristic enabled"); + this.log("["+ this.name +"]:GarageDoorOpener LockTargetState characteristic enabled"); myService.addCharacteristic(Characteristic.LockTargetState); this.bindCharacteristic(myService, Characteristic.LockTargetState, "Bool", config.LockTargetState); } @@ -596,12 +632,12 @@ KNXDevice.prototype = { var myService = new Service.Lightbulb(config.name,config.name); // On (and Off) if (config.On) { - this.log("Lightbulb on/off characteristic enabled"); + this.log("["+ this.name +"]:Lightbulb on/off characteristic enabled"); this.bindCharacteristic(myService, Characteristic.On, "Bool", config.On); } // On characteristic // Brightness if available if (config.Brightness) { - this.log("Lightbulb Brightness characteristic enabled"); + this.log("["+ this.name +"]:Lightbulb Brightness characteristic enabled"); myService.addCharacteristic(Characteristic.Brightness); // it's an optional this.bindCharacteristic(myService, Characteristic.Brightness, "Percent", config.Brightness); } @@ -623,7 +659,7 @@ KNXDevice.prototype = { var myService = new Service.LightSensor(config.name,config.name); // CurrentTemperature) if (config.CurrentAmbientLightLevel) { - this.log("LightSensor CurrentAmbientLightLevel characteristic enabled"); + this.log("["+ this.name +"]:LightSensor CurrentAmbientLightLevel characteristic enabled"); this.bindCharacteristic(myService, Characteristic.CurrentAmbientLightLevel, "Float", config.CurrentAmbientLightLevel); } return myService; @@ -648,20 +684,20 @@ KNXDevice.prototype = { // LockCurrentState if (config.LockCurrentState) { // for normal contacts: Secured = 1 - this.log("LockMechanism LockCurrentState characteristic enabled"); + this.log("["+ this.name +"]:LockMechanism LockCurrentState characteristic enabled"); this.bindCharacteristic(myService, Characteristic.LockCurrentState, "Bool", config.LockCurrentState); } else if (config.LockCurrentStateSecured0) { // for reverse contacts Secured = 0 - this.log("LockMechanism LockCurrentState characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.LockCurrentState, "BoolReverse", config.LockCurrentStateSecured0); + this.log(colorOn+ "[ERROR] outdated type passed: [LockCurrentStateSecured0]"+colorOff); + throw new Error("[ERROR] outdated type passed"); } // LockTargetState if (config.LockTargetState) { - this.log("LockMechanism LockTargetState characteristic enabled"); + this.log("["+ this.name +"]:LockMechanism LockTargetState characteristic enabled"); this.bindCharacteristic(myService, Characteristic.LockTargetState, "Bool", config.LockTargetState); } else if (config.LockTargetStateSecured0) { - this.log("LockMechanism LockTargetState characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.LockTargetState, "BoolReverse", config.LockTargetStateSecured0); + this.log(colorOn+ "[ERROR] outdated type passed: [LockTargetStateSecured0]"+colorOff); + throw new Error("[ERROR] outdated type passed"); } //iterate(myService); @@ -683,27 +719,27 @@ KNXDevice.prototype = { var myService = new Service.MotionSensor(config.name,config.name); if (config.MotionDetected) { - this.log("MotionSensor MotionDetected characteristic enabled"); + this.log("["+ this.name +"]:MotionSensor MotionDetected characteristic enabled"); this.bindCharacteristic(myService, Characteristic.MotionDetected, "Bool", config.MotionDetected); } //optionals if (config.StatusActive) { - this.log("MotionSensor StatusActive characteristic enabled"); + this.log("["+ this.name +"]:MotionSensor StatusActive characteristic enabled"); myService.addCharacteristic(Characteristic.StatusActive); this.bindCharacteristic(myService, Characteristic.StatusActive, "Bool", config.StatusActive); } if (config.StatusFault) { - this.log("MotionSensor StatusFault characteristic enabled"); + this.log("["+ this.name +"]:MotionSensor StatusFault characteristic enabled"); myService.addCharacteristic(Characteristic.StatusFault); this.bindCharacteristic(myService, Characteristic.StatusFault, "Bool", config.StatusFault); } if (config.StatusTampered) { - this.log("MotionSensor StatusTampered characteristic enabled"); + this.log("["+ this.name +"]:MotionSensor StatusTampered characteristic enabled"); myService.addCharacteristic(Characteristic.StatusTampered); this.bindCharacteristic(myService, Characteristic.StatusTampered, "Bool", config.StatusTampered); } if (config.StatusLowBattery) { - this.log("MotionSensor StatusLowBattery characteristic enabled"); + this.log("["+ this.name +"]:MotionSensor StatusLowBattery characteristic enabled"); myService.addCharacteristic(Characteristic.StatusLowBattery); this.bindCharacteristic(myService, Characteristic.StatusLowBattery, "Bool", config.StatusLowBattery); } @@ -726,11 +762,11 @@ KNXDevice.prototype = { var myService = new Service.Outlet(config.name,config.name); // On (and Off) if (config.On) { - this.log("Outlet on/off characteristic enabled"); + this.log("["+ this.name +"]:Outlet on/off characteristic enabled"); this.bindCharacteristic(myService, Characteristic.On, "Bool", config.On); } // OutletInUse characteristic if (config.OutletInUse) { - this.log("Outlet on/off characteristic enabled"); + this.log("["+ this.name +"]:Outlet on/off characteristic enabled"); this.bindCharacteristic(myService, Characteristic.OutletInUse, "Bool", config.OutletInUse); } return myService; @@ -748,7 +784,7 @@ KNXDevice.prototype = { var myService = new Service.Switch(config.name,config.name); // On (and Off) if (config.On) { - this.log("Switch on/off characteristic enabled"); + this.log("["+ this.name +"]:Switch on/off characteristic enabled"); this.bindCharacteristic(myService, Characteristic.On, "Bool", config.On); } // On characteristic @@ -775,26 +811,35 @@ KNXDevice.prototype = { var myService = new Service.Thermostat(config.name,config.name); // CurrentTemperature) + // props update for https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR108 if (config.CurrentTemperature) { - this.log("Thermostat CurrentTemperature characteristic enabled"); + this.log("["+ this.name +"]:Thermostat CurrentTemperature characteristic enabled"); + myService.getCharacteristic(Characteristic.CurrentTemperature).setProps({ + minValue: config.CurrentTemperature.minValue || -40, + maxValue: config.CurrentTemperature.maxValue || 60 + }); // °C by default this.bindCharacteristic(myService, Characteristic.CurrentTemperature, "Float", config.CurrentTemperature); } // TargetTemperature if available if (config.TargetTemperature) { - this.log("Thermostat TargetTemperature characteristic enabled"); + this.log("["+ this.name +"]:Thermostat TargetTemperature characteristic enabled"); // default boundary too narrow for thermostats - myService.getCharacteristic(Characteristic.TargetTemperature).minimumValue=0; // °C - myService.getCharacteristic(Characteristic.TargetTemperature).maximumValue=40; // °C + // props update for https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR108 + myService.getCharacteristic(Characteristic.TargetTemperature).setProps({ + minValue: config.TargetTemperature.minValue || 0, + maxValue: config.TargetTemperature.maxValue || 40 + }); + this.bindCharacteristic(myService, Characteristic.TargetTemperature, "Float", config.TargetTemperature); } // HVAC if (config.CurrentHeatingCoolingState) { - this.log("Thermostat CurrentHeatingCoolingState characteristic enabled"); + this.log("["+ this.name +"]:Thermostat CurrentHeatingCoolingState characteristic enabled"); this.bindCharacteristic(myService, Characteristic.CurrentHeatingCoolingState, "HVAC", config.CurrentHeatingCoolingState); } // HVAC if (config.TargetHeatingCoolingState) { - this.log("Thermostat TargetHeatingCoolingState characteristic enabled"); + this.log("["+ this.name +"]:Thermostat TargetHeatingCoolingState characteristic enabled"); this.bindCharacteristic(myService, Characteristic.TargetHeatingCoolingState, "HVAC", config.TargetHeatingCoolingState); } return myService; @@ -812,10 +857,16 @@ KNXDevice.prototype = { } var myService = new Service.TemperatureSensor(config.name,config.name); // CurrentTemperature) + // props update for https://github.com/KhaosT/HAP-NodeJS/commit/1d84d128d1513beedcafc24d2c07d98185563243#diff-cb84de3a1478a38b2cf8388d709f1c1cR108 if (config.CurrentTemperature) { - this.log("TemperatureSensor CurrentTemperature characteristic enabled"); + this.log("["+ this.name +"]:TemperatureSensor CurrentTemperature characteristic enabled"); + myService.getCharacteristic(Characteristic.CurrentTemperature).setProps({ + minValue: config.CurrentTemperature.minValue || -40, + maxValue: config.CurrentTemperature.maxValue || 60 + }); // °C by default this.bindCharacteristic(myService, Characteristic.CurrentTemperature, "Float", config.CurrentTemperature); } + return myService; }, getWindowService: function(config) { @@ -845,16 +896,16 @@ KNXDevice.prototype = { var myService = new Service.Window(config.name,config.name); if (config.CurrentPosition) { - this.log("Window CurrentPosition characteristic enabled"); + this.log("["+ this.name +"]:Window CurrentPosition characteristic enabled"); this.bindCharacteristic(myService, Characteristic.CurrentPosition, "Percent", config.CurrentPosition); } if (config.TargetPosition) { - this.log("Window TargetPosition characteristic enabled"); + this.log("["+ this.name +"]:Window TargetPosition characteristic enabled"); this.bindCharacteristic(myService, Characteristic.TargetPosition, "Percent", config.TargetPosition); } if (config.PositionState) { - this.log("Window PositionState characteristic enabled"); - this.bindCharacteristic(myService, Characteristic.PositionState, "Float", config.PositionState); + this.log("["+ this.name +"]:Window PositionState characteristic enabled"); + this.bindCharacteristic(myService, Characteristic.PositionState, "Int", config.PositionState); } return myService; }, @@ -880,15 +931,15 @@ KNXDevice.prototype = { var myService = new Service.WindowCovering(config.name,config.name); if (config.CurrentPosition) { - this.log("WindowCovering CurrentPosition characteristic enabled"); + this.log("["+ this.name +"]:WindowCovering CurrentPosition characteristic enabled"); this.bindCharacteristic(myService, Characteristic.CurrentPosition, "Percent", config.CurrentPosition); } if (config.TargetPosition) { - this.log("WindowCovering TargetPosition characteristic enabled"); + this.log("["+ this.name +"]:WindowCovering TargetPosition characteristic enabled"); this.bindCharacteristic(myService, Characteristic.TargetPosition, "Percent", config.TargetPosition); } if (config.PositionState) { - this.log("WindowCovering PositionState characteristic enabled"); + this.log("["+ this.name +"]:WindowCovering PositionState characteristic enabled"); this.bindCharacteristic(myService, Characteristic.PositionState, "Float", config.PositionState); } return myService; @@ -910,7 +961,7 @@ KNXDevice.prototype = { informationService .setCharacteristic(Characteristic.Manufacturer, "Opensource Community") .setCharacteristic(Characteristic.Model, "KNX Universal Device") - .setCharacteristic(Characteristic.SerialNumber, "Version 1.1.2"); + .setCharacteristic(Characteristic.SerialNumber, "Version 1.1.4"); accessoryServices.push(informationService); @@ -965,8 +1016,8 @@ KNXDevice.prototype = { accessoryServices.push(this.getWindowCoveringService(configService)); break; default: - this.log("[ERROR] unknown 'type' property of '"+configService.type+"' for service "+ configService.name + " in config.json. KNX platform section fault "); - //throw new Error("[ERROR] unknown 'type' property for service "+ configService.name + " in config.json. KNX platform section fault "); + this.log("[ERROR] unknown 'type' property of ["+configService.type+"] for service ["+ configService.name + "] in config.json. KNX platform section fault "); + throw new Error("[ERROR] unknown 'type' property of ["+configService.type+"] for service '"+ configService.name + "' in config.json. KNX platform section fault "); } } // start listening for events on the bus (if not started yet - will prevent itself) diff --git a/config-sample.json b/config-sample.json index 6f24554..7af74db 100644 --- a/config-sample.json +++ b/config-sample.json @@ -89,14 +89,19 @@ "delay": 30, "repeat": 3, "zones":["Kitchen Lamp","Bedroom Lamp","Living Room Lamp","Hallway Lamp"] - }, - { + }, + { "platform": "HomeAssistant", "name": "HomeAssistant", "host": "http://192.168.1.10:8123", "password": "XXXXX", "supported_types": ["light", "switch", "media_player", "scene"] - } + }, + { + "platform": "LIFx", + "name": "LIFx", + "access_token": "XXXXXXXX generate at https://cloud.lifx.com/settings" + } ], "accessories": [ diff --git a/platforms/HomeAssistant.js b/platforms/HomeAssistant.js index 5a23ab5..61d3bc2 100644 --- a/platforms/HomeAssistant.js +++ b/platforms/HomeAssistant.js @@ -152,15 +152,24 @@ HomeAssistantPlatform.prototype = { entity = data[i] entity_type = entity.entity_id.split('.')[0] + // ignore devices that are not in the list of supported types if (that.supportedTypes.indexOf(entity_type) == -1) { continue; } + // ignore hidden devices + if (entity.attributes && entity.attributes.hidden) { + continue; + } + var accessory = null if (entity_type == 'light') { accessory = new HomeAssistantLight(that.log, entity, that) }else if (entity_type == 'switch'){ + console.log(JSON.stringify(entity)) + console.log(""); + console.log(""); accessory = new HomeAssistantSwitch(that.log, entity, that) }else if (entity_type == 'scene'){ accessory = new HomeAssistantSwitch(that.log, entity, that, 'scene') diff --git a/platforms/KNX-sample-config.json b/platforms/KNX-sample-config.json index 2c29736..eda2fe3 100644 --- a/platforms/KNX-sample-config.json +++ b/platforms/KNX-sample-config.json @@ -1,154 +1,156 @@ { - "bridge": { - "name": "Homebridge", - "username": "CC:22:3D:E3:CE:30", - "port": 51826, - "pin": "031-45-154" - }, - "description": "This is an example configuration file for KNX platform shim", - "hint": "Always paste into jsonlint.com validation page before starting your homebridge, saves a lot of frustration", - "hint2": "Replace all group addresses by current addresses of your installation, these are arbitrary examples!", - "hint3": "For valid services and their characteristics have a look at the knxdevice.md file in folder accessories!", - "platforms": [ - { - "platform": "KNX", - "name": "KNX", - "knxd_ip": "192.168.178.205", - "knxd_port": 6720, - "accessories": [ - { - "accessory_type": "knxdevice", - "description": "Only generic type knxdevice is supported, all previous knx types have been merged into that.", - "name": "Living Room North Lamp", - "services": [ - { - "type": "Lightbulb", - "description": "iOS8 Lightbulb type, supports On (Switch) and Brightness", - "name": "Living Room North Lamp", - "On": { - "Set": "1/1/6", - "Listen": [ - "1/1/63" - ] - }, - "Brightness": { - "Set": "1/1/62", - "Listen": [ - "1/1/64" - ] - } - } - ], - "services-description": "Services is an array, you CAN have multiple service types in one accessory, though it is not fully supported in many iOS HK apps, such as EVE and myTouchHome" - }, - { - "accessory_type": "knxdevice", - "name": "Office Temperature", - "description": "iOS8.4.1 TemperatureSensor type, supports CurrentTemperature", - "services": [ - { - "type": "TemperatureSensor", - "name": "Raumtemperatur", - "CurrentTemperature": { - "Listen": "3/3/44" - } - } - ] - }, - { - "accessory_type": "knxdevice", - "name": "Office Window Lock", - "services": [ - { - "type": "LockMechanism", - "description": "iOS8 Lock mechanism, Supports LockCurrentStateSecured0 OR LockCurrentState, LockTargetStateSecured0 OR LockTargetState, use depending if LOCKED is 0 or 1", - "name": "Office Window Lock", - "LockCurrentStateSecured0": { - "Listen": "5/3/15" - }, - "LockTargetStateSecured0": { - "Listen": "5/3/15" - } - } - ] - }, - { - "accessory_type": "knxdevice", - "description": "sample device with multiple services. Multiple services of different types are widely supported", - "name": "Office", - "services": [ - { - "type": "Lightbulb", - "name": "Office Lamp", - "On": { - "Set": "1/3/5" - } - }, - { - "type": "Thermostat", - "description": "iOS8 Thermostat type, supports CurrentTemperature, TargetTemperature, CurrentHeatingCoolingState ", - "name": "Raumtemperatur", - "CurrentTemperature": { - "Listen": "3/3/44" - }, - "TargetTemperature": { - "Set": "3/3/94" - }, - "CurrentHeatingCoolingState": { - "Listen": "3/3/64" - } - }, - { - "type": "WindowCovering", - "description": "iOS9 Window covering (blinds etc) type, still WIP", - "name": "Blinds", - "TargetPosition": { - "Set": "1/2/3", - "Listen": "1/2/4" - }, - "CurrentPosition": { - "Set": "1/3/1", - "Listen": "1/3/2" - }, - "PositionState": { - "Listen": "2/7/1" - } - } - ] - }, - { - "accessory_type": "knxdevice", - "description": "sample contact sensor device", - "name": "Office Contact", - "services": [ - { - "type": "ContactSensor", - "name": "Office Door", - "ContactSensorState": { - "Listen": "5/3/5" - } - } - ] - }, - { - "accessory_type": "knxdevice", - "description": "sample garage door opener", - "name": "Office Garage", - "services": [ - { - "type": "GarageDoorOpener", - "name": "Office Garage Opener", - "CurrentDoorState": { - "Listen": "5/4/5" - }, - "TargetDoorState": { - "Listen": "5/4/6" - } - } - ] - } - ] - } - ], - "accessories": [] + "bridge": { + "name": "Homebridge", + "username": "CC:22:3D:E3:CE:30", + "port": 51826, + "pin": "031-45-154" + }, + "description": "This is an example configuration file for KNX platform shim", + "hint": "Always paste into jsonlint.com validation page before starting your homebridge, saves a lot of frustration", + "hint2": "Replace all group addresses by current addresses of your installation, these are arbitrary examples!", + "hint3": "For valid services and their characteristics have a look at the KNX.md file in folder platforms!", + "platforms": [ + { + "platform": "KNX", + "name": "KNX", + "knxd_ip": "192.168.178.205", + "knxd_port": 6720, + "accessories": [ + { + "accessory_type": "knxdevice", + "description": "Only generic type knxdevice is supported, all previous knx types have been merged into that.", + "name": "Living Room North Lamp", + "services": [ + { + "type": "Lightbulb", + "description": "iOS8 Lightbulb type, supports On (Switch) and Brightness", + "name": "Living Room North Lamp", + "On": { + "Set": "1/1/6", + "Listen": [ + "1/1/63" + ] + }, + "Brightness": { + "Set": "1/1/62", + "Listen": [ + "1/1/64" + ] + } + } + ], + "services-description": "Services is an array, you CAN have multiple service types in one accessory, though it is not fully supported in many iOS HK apps, such as EVE and myTouchHome" + }, + { + "accessory_type": "knxdevice", + "name": "Office Temperature", + "description": "iOS8.4.1 TemperatureSensor type, supports CurrentTemperature", + "services": [ + { + "type": "TemperatureSensor", + "name": "Raumtemperatur", + "CurrentTemperature": { + "Listen": "3/3/44" + } + } + ] + }, + { + "accessory_type": "knxdevice", + "name": "Office Window Lock", + "services": [ + { + "type": "LockMechanism", + "description": "iOS8 Lock mechanism, Supports LockCurrentState, LockTargetState, append R to the addresses if LOCKED is 1", + "name": "Office Window Lock", + "LockCurrentState": { + "Listen": "5/3/15R" + }, + "LockTargetState": { + "Listen": "5/3/16R" + } + } + ] + }, + { + "accessory_type": "knxdevice", + "description": "sample device with multiple services. Multiple services of different types are widely supported", + "name": "Office", + "services": [ + { + "type": "Lightbulb", + "name": "Office Lamp", + "On": { + "Set": "1/3/5" + } + }, + { + "type": "Thermostat", + "description": "iOS8 Thermostat type, supports CurrentTemperature, TargetTemperature, CurrentHeatingCoolingState ", + "name": "Raumtemperatur", + "CurrentTemperature": { + "Listen": "3/3/44" + }, + "TargetTemperature": { + "Set": "3/3/94" + }, + "CurrentHeatingCoolingState": { + "Listen": "3/3/64" + } + }, + { + "type": "WindowCovering", + "description": "iOS9 Window covering (blinds etc) type, still WIP", + "name": "Blinds", + "TargetPosition": { + "Set": "1/2/3", + "Listen": "1/2/4" + }, + "CurrentPosition": { + "Set": "1/3/1", + "Listen": "1/3/2" + }, + "PositionState": { + "Listen": "2/7/1" + } + } + ] + }, + { + "accessory_type": "knxdevice", + "description": "sample contact sensor device", + "name": "Office Contact", + "services": [ + { + "type": "ContactSensor", + "name": "Office Door", + "ContactSensorState": { + "Listen": "5/3/5" + } + } + ] + }, + { + "accessory_type": "knxdevice", + "description": "sample garage door opener", + "name": "Office Garage", + "services": [ + { + "type": "GarageDoorOpener", + "name": "Office Garage Opener", + "CurrentDoorState": { + "Listen": "5/4/5" + }, + "TargetDoorState": { + "Listen": "5/4/6" + } + } + ] + } + ] + } + ], + "accessories": [ + + ] } \ No newline at end of file diff --git a/platforms/KNX.js b/platforms/KNX.js index 573b3b9..65f7a13 100644 --- a/platforms/KNX.js +++ b/platforms/KNX.js @@ -116,8 +116,8 @@ function groupsocketlisten(opts, callback) { } -var registerSingleGA = function registerSingleGA (groupAddress, callback) { - subscriptions.push({address: groupAddress, callback: callback }); +var registerSingleGA = function registerSingleGA (groupAddress, callback, reverse) { + subscriptions.push({address: groupAddress, callback: callback, reverse:reverse }); } /* @@ -143,7 +143,7 @@ var startMonitor = function startMonitor(opts) { // using { host: name-ip, port if (subscriptions[i].address === dest) { // found one, notify console.log('HIT: Write from '+src+' to '+dest+': '+val+' ['+type+']'); - subscriptions[i].callback(val, src, dest, type); + subscriptions[i].callback(val, src, dest, type, subscriptions[i].reverse); } } }); @@ -156,7 +156,7 @@ var startMonitor = function startMonitor(opts) { // using { host: name-ip, port if (subscriptions[i].address === dest) { // found one, notify // console.log('HIT: Response from '+src+' to '+dest+': '+val+' ['+type+']'); - subscriptions[i].callback(val, src, dest, type); + subscriptions[i].callback(val, src, dest, type, subscriptions[i].reverse); } } @@ -185,13 +185,16 @@ var registerGA = function (groupAddresses, callback) { if (groupAddresses.constructor.toString().indexOf("Array") > -1) { // handle multiple addresses for (var i = 0; i < groupAddresses.length; i++) { - if (groupAddresses[i]) { // do not bind empty addresses - registerSingleGA (groupAddresses[i], callback); + if (groupAddresses[i] && groupAddresses[i].match(/(\d*\/\d*\/\d*)/)) { // do not bind empty addresses or invalid addresses + // clean the addresses + registerSingleGA (groupAddresses[i].match(/(\d*\/\d*\/\d*)/)[0], callback,groupAddresses[i].match(/\d*\/\d*\/\d*(R)/) ? true:false ); } } } else { // it's only one - registerSingleGA (groupAddresses, callback); + if (groupAddresses.match(/(\d*\/\d*\/\d*)/)) { + registerSingleGA (groupAddresses.match(/(\d*\/\d*\/\d*)/)[0], callback, groupAddresses[i].match(/\d*\/\d*\/\d*(R)/) ? true:false); + } } // console.log("listeners now: " + subscriptions.length); }; diff --git a/platforms/KNX.md b/platforms/KNX.md index c3b4fe3..ffb7301 100644 --- a/platforms/KNX.md +++ b/platforms/KNX.md @@ -47,7 +47,7 @@ You have to add services in the following syntax: { "type": "SERVICENAME", "description": "This is just for you to remember things", - "name": "We need a name for each service, though it usually shows only if multiple services are present in one accessory", + "name": "beer tap thermostat", "CHARACTERISTIC1": { "Set": "1/1/6", "Listen": [ @@ -68,11 +68,54 @@ Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group addres `"Listen":["1/2/3","1/2/4","1/2/5"]` is an array of addresses that are listened to additionally. To these addresses never values get written, but the on startup the service will issue *KNX read requests* to ALL addresses listed in `Set:` and in `Listen:` -# Supported Services and their characteristics +For two characteristics there are additional minValue and maxValue attributes. These are CurrentTemperature and TargetTemperature, and are used in TemperatureSensor and Thermostat. +So the charcteristic section may look like: + + ````json + { + "type": "Thermostat", + "description": "Sample thermostat", + "name": "We need a name for each service, though it usually shows only if multiple services are present in one accessory", + "CurrentTemperature": { + "Set": "1/1/6", + "Listen": [ + "1/1/63" + ], + "minValue": -18, + "maxValue": 30 + }, + "TargetTemperature": { + "Set": "1/1/62", + "Listen": [ + "1/1/64" + ], + "minValue": -4, + "maxValue": 12 + } + } +```` + + +## reversal of values for characteristics +In general, all DPT1 types can be reversed. If you need a 1 for "contact" of a contact senser, you can append an "R" to the group address. +Likewise, all percentages of DPT5 can be reversed, if you need a 100% (=255) for window closed, append an "R" to the group address. Do not forget the listening addresses! + ````json + { + "type": "ContactSensor", + "description": "Sample ContactSensor with 1 as contact (0 is Apple's default)", + "name": "WindowContact1", + "ContactSensorState": { + "Listen": [ + "1/1/100R" + ] + } + } +```` +# Supported Services and their characteristics ## ContactSensor -- ContactSensorState: DPT 1.002, 0 as contact **OR** -- ContactSensorStateContact1: DPT 1.002, 1 as contact +- ContactSensorState: DPT 1.002, 0 as contact +- ~~ContactSensorStateContact1: DPT 1.002, 1 as contact~~ - StatusActive: DPT 1.011, 1 as true - StatusFault: DPT 1.011, 1 as true @@ -113,10 +156,10 @@ Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group addres - CurrentAmbientLightLevel: DPT 9.004, 0 to 100000 Lux ## LockMechanism (This is poorly mapped!) -- LockCurrentState: DPT 1, 1 as secured **OR (but not both:)** -- LockCurrentStateSecured0: DPT 1, 0 as secured -- LockTargetState: DPT 1, 1 as secured **OR** -- LockTargetStateSecured0: DPT 1, 0 as secured +- LockCurrentState: DPT 1, 1 as secured +- ~~LockCurrentStateSecured0: DPT 1, 0 as secured~~ +- LockTargetState: DPT 1, 1 as secured +- ~~LockTargetStateSecured0: DPT 1, 0 as secured~~ *ToDo here: correction of mappings, HomeKit reqires lock states UNSECURED=0, SECURED=1, JAMMED = 2, UNKNOWN=3* @@ -136,11 +179,11 @@ Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group addres - On: DPT 1.001, 1 as on, 0 as off ## TemperatureSensor -- CurrentTemperature: DPT9.001 in °C [listen only] +- CurrentTemperature: DPT9.001 in °C [listen only] ## Thermostat -- CurrentTemperature: DPT9.001 in °C [listen only] -- TargetTemperature: DPT9.001, values 0..40°C only, all others are ignored +- CurrentTemperature: DPT9.001 in °C [listen only], -40 to 80°C if not overriden as shown above +- TargetTemperature: DPT9.001, values 0..40°C only, all others are ignored - CurrentHeatingCoolingState: DPT20.102 HVAC, because of the incompatible mapping only off and heating (=auto) are shown, [listen only] - TargetHeatingCoolingState: DPT20.102 HVAC, as above @@ -152,7 +195,7 @@ Two kinds of addresses are supported: `"Set":"1/2/3"` is a writable group addres ## WindowCovering - CurrentPosition: DPT5 percentage - TargetPosition: DPT5 percentage -- PositionState: DPT5 value [listen only] +- PositionState: DPT5 value [listen only: 0 Closing, 1 Opening, 2 Stopped] ### not yet supported - HoldPosition