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..6aa4ffd 100644 --- a/accessories/knxdevice.js +++ b/accessories/knxdevice.js @@ -14,6 +14,11 @@ 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 +New 2015-10-07: +- Accept uuid_base parameter from config.json to use as unique identifier in UUIDs instead of name (optional) * */ var Service = require("HAP-NodeJS").Service; @@ -24,6 +29,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; @@ -33,6 +40,9 @@ function KNXDevice(log, config) { if (config.name) { this.name = config.name; } + if (config.uuid_base) { + this.uuid_base = config.uuid_base; + } if (config.knxd_ip){ this.knxd_ip = config.knxd_ip; } else { @@ -115,6 +125,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 +141,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 +154,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 +169,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 +282,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 +297,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 +373,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 +402,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 +411,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 +474,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 +486,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 +502,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 +535,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 +596,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 +637,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 +664,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 +689,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 +724,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 +767,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 +789,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 +816,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 +862,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 +901,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 +936,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 +966,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 +1021,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/app.js b/app.js index a8c2006..126b7e3 100644 --- a/app.js +++ b/app.js @@ -86,7 +86,7 @@ function loadAccessories() { log("Initializing %s accessory...", accessoryType); var accessoryInstance = new accessoryConstructor(log, accessoryConfig); - var accessory = createAccessory(accessoryInstance, accessoryName); + var accessory = createAccessory(accessoryInstance, accessoryName, accessoryType, accessoryConfig.uuid_base); //pass accessoryType for UUID generation, and optional parameter uuid_base which can be used instead of displayName for UUID generation // add it to the bridge bridge.addBridgedAccessory(accessory); @@ -113,11 +113,11 @@ function loadPlatforms() { log("Initializing %s platform...", platformType); var platformInstance = new platformConstructor(log, platformConfig); - loadPlatformAccessories(platformInstance, log); + loadPlatformAccessories(platformInstance, log, platformType); } } -function loadPlatformAccessories(platformInstance, log) { +function loadPlatformAccessories(platformInstance, log, platformType) { asyncCalls++; platformInstance.accessories(once(function(foundAccessories){ asyncCalls--; @@ -129,7 +129,7 @@ function loadPlatformAccessories(platformInstance, log) { log("Initializing platform accessory '%s'...", accessoryName); - var accessory = createAccessory(accessoryInstance, accessoryName); + var accessory = createAccessory(accessoryInstance, accessoryName, platformType, accessoryInstance.uuid_base); // add it to the bridge bridge.addBridgedAccessory(accessory); @@ -141,7 +141,7 @@ function loadPlatformAccessories(platformInstance, log) { })); } -function createAccessory(accessoryInstance, displayName) { +function createAccessory(accessoryInstance, displayName, accessoryType, uuid_base) { var services = accessoryInstance.getServices(); @@ -159,7 +159,7 @@ function createAccessory(accessoryInstance, displayName) { // The returned "services" for this accessory are simply an array of new-API-style // Service instances which we can add to a created HAP-NodeJS Accessory directly. - var accessoryUUID = uuid.generate(accessoryInstance.constructor.name + ":" + displayName); + var accessoryUUID = uuid.generate(accessoryType + ":" + (uuid_base || displayName)); var accessory = new Accessory(displayName, accessoryUUID); 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/HomeSeer.js b/platforms/HomeSeer.js new file mode 100644 index 0000000..4618efe --- /dev/null +++ b/platforms/HomeSeer.js @@ -0,0 +1,370 @@ +'use strict'; + +// +// HomeSeer Platform Shim for HomeBridge +// V0.1 - Jean-Michel Joudrier (stipus at stipus dot com) - 2015/10/07 - Initial version +// +// +// Remember to add platform to config.json. +// +// You can get HomeSeer Device References by clicking a HomeSeer device name, then +// choosing the Advanced Tab. +// +// Example: +// "platforms": [ +// { +// "platform": "HomeSeer", // required +// "name": "HomeSeer", // required +// "url": "http://192.168.3.4:81", // required +// "accessories":[ +// { +// "ref":8, // required - HomeSeer Device Reference (To get it, select the HS Device - then Advanced Tab) +// "type":"Lightbulb", // Optional - Lightbulb is the default +// "name":"My Light", // Optional - HomeSeer device name is the default +// "offValue":"0", // Optional - 0 is the default +// "onValue":"100", // Optional - 100 is the default +// "can_dim":true // Optional - true is the default - false for a non dimmable lightbulb +// }, +// { +// "ref":9 // This is a dimmable Lightbulb by default +// }, +// { +// "ref":58, // This is an controllable outlet +// "type":"Outlet" +// } +// ] +// } +// ], +// +// +// SUPORTED TYPES: +// - Lightbulb (can_dim, onValue, offValue options) +// - Fan (onValue, offValue options) +// - Switch (onValue, offValue options) +// - Outlet (onValue, offValue options) +// - TemperatureSensor +// - ContactSensor +// - MotionSensor +// - LeakSensor +// - LightSensor +// - OccupancySensor +// - SmokeSensor +// - Door + + +var Service = require("HAP-NodeJS").Service; +var Characteristic = require("HAP-NodeJS").Characteristic; +var request = require("request"); + + +function httpRequest(url, method, callback) { + request({ + url: url, + method: method + }, + function (error, response, body) { + callback(error, response, body) + }) +} + + + +function HomeSeerPlatform(log, config){ + this.log = log; + this.config = config; +} + +HomeSeerPlatform.prototype = { + accessories: function(callback) { + this.log("Fetching HomeSeer devices."); + + var refList = ""; + for( var i=0; i -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 diff --git a/platforms/Nest.js b/platforms/Nest.js index 414fcef..c1ef3bd 100644 --- a/platforms/Nest.js +++ b/platforms/Nest.js @@ -44,7 +44,11 @@ NestPlatform.prototype = { function NestThermostatAccessory(log, name, device, deviceId) { // device info - this.name = name; + if (name) { + this.name = name; + } else { + this.name = "Nest"; + } this.model = device.model_version; this.serial = device.serial_number; this.deviceId = deviceId; @@ -390,4 +394,4 @@ NestThermostatAccessory.prototype = { } module.exports.accessory = NestThermostatAccessory; -module.exports.platform = NestPlatform; \ No newline at end of file +module.exports.platform = NestPlatform; diff --git a/platforms/YamahaAVR.js b/platforms/YamahaAVR.js index 1659c0f..f08fa96 100644 --- a/platforms/YamahaAVR.js +++ b/platforms/YamahaAVR.js @@ -23,6 +23,7 @@ function YamahaAVRPlatform(log, config){ this.setMainInputTo = config["setMainInputTo"]; this.expectedDevices = config["expected_devices"] || 100; this.discoveryTimeout = config["discovery_timeout"] || 30; + this.manualAddresses = config["manual_addresses"] || {}; this.browser = mdns.createBrowser(mdns.tcp('http'), {resolverSequence: sequence}); } @@ -75,25 +76,44 @@ YamahaAVRPlatform.prototype = { var accessories = []; var timer, timeElapsed = 0, checkCyclePeriod = 5000; - browser.on('serviceUp', function(service){ + // Hmm... seems we need to prevent double-listing via manual and Bonjour... + var sysIds = {}; + + var setupFromService = function(service){ var name = service.name; //console.log('Found HTTP service "' + name + '"'); // We can't tell just from mdns if this is an AVR... if (service.port != 80) return; // yamaha-nodejs assumes this, so finding one on another port wouldn't do any good anyway. var yamaha = new Yamaha(service.host); - yamaha.getSystemConfig().then(function(sysConfig){ - var sysModel = sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0]; - var sysId = sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0]; - that.log("Found Yamaha " + sysModel + " - " + sysId + ", \"" + name + "\""); - var accessory = new YamahaAVRAccessory(that.log, that.config, service, yamaha, sysConfig); - accessories.push(accessory); - if(accessories.length >= this.expectedDevices) - timeoutFunction(); // We're done, call the timeout function now. - //callback([accessory]); - }, function(err){ - return; + yamaha.getSystemConfig().then( + function(sysConfig){ + var sysModel = sysConfig.YAMAHA_AV.System[0].Config[0].Model_Name[0]; + var sysId = sysConfig.YAMAHA_AV.System[0].Config[0].System_ID[0]; + if(sysIds[sysId]){ + this.log("WARN: Got multiple systems with ID " + sysId + "! Omitting duplicate!"); + return; + } + sysIds[sysId] = true; + this.log("Found Yamaha " + sysModel + " - " + sysId + ", \"" + name + "\""); + var accessory = new YamahaAVRAccessory(this.log, this.config, name, yamaha, sysConfig); + accessories.push(accessory); + if(accessories.length >= this.expectedDevices) + timeoutFunction(); // We're done, call the timeout function now. + }.bind(this) + ); + }.bind(this); + + // process manually specified devices... + for(var key in this.manualAddresses){ + if(!this.manualAddresses.hasOwnProperty(key)) continue; + setupFromService({ + name: key, + host: this.manualAddresses[key], + port: 80 }); - }); + } + + browser.on('serviceUp', setupFromService); browser.start(); // The callback can only be called once...so we'll have to find as many as we can @@ -119,15 +139,15 @@ YamahaAVRPlatform.prototype = { } }; -function YamahaAVRAccessory(log, config, mdnsService, yamaha, sysConfig) { +function YamahaAVRAccessory(log, config, name, yamaha, sysConfig) { this.log = log; this.config = config; - this.mdnsService = mdnsService; this.yamaha = yamaha; this.sysConfig = sysConfig; - this.name = mdnsService.name; - this.serviceName = mdnsService.name + " Speakers"; + this.nameSuffix = config["name_suffix"] || " Speakers"; + this.name = name; + this.serviceName = name + this.nameSuffix; this.setMainInputTo = config["setMainInputTo"]; this.playVolume = this.config["play_volume"]; this.minVolume = config["min_volume"] || -50.0;