diff --git a/CHANGELOG.md b/CHANGELOG.md index ae136fd..958807a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.5.0 +- **BREAKING CHANGES** + The configuration of the KNX gateway (knxd or multicast) has been moved to homebridge's config.json again, to make it configurable using the homebridge UI +- homebridge UI support for creating child bridges +- knx configuration files can now be YAML files (extension must be `.yaml`) +- knx configuration files can now be splitted into multiple files - only one file per child bridge should contain any other contents than `Devices` + ## 0.4.3 - merged PR #198 (Update WindowCoveringTilt.js) by @EyeOfTheStorm - merged PR #204 (Update GarageDoorOpenerAdvanced.js) by @christof-fersch diff --git a/README.md b/README.md index 7256409..2ecc8b0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# homebridge-knx Version 0.4 +# homebridge-knx Version 0.5 [![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url][![Dependency status][david-dm-image]][david-dm-url] KNX platform shim for homebridge. @@ -21,10 +21,11 @@ I cannot support the knxd. Please address issues directly at the [knxd issue pag - Install homebridge first, from [https://homebridge.io/](https://homebridge.io/); - Once you have your instance running (without any devices yet), go to the `Plugins` tab and type `knx` in the search box - `homebridge-knx` should be within the top five hits (yes, there are alternatives), please check the name before installing -- Then put the configuration file *knx_config.json* into `~/.homebridge` (or another folder to your liking, but it should be reaadable and writable by user `homebridge` or group `homebridge` which is created by the homebridge installer), and adapt them to your needs (knxd address and some test devices in `knx_config.json`) -- Eliminate everything (especially all group addresses) that might harm your KNX installation. Sending bus telegrams to your alarm device might wake the neighbourhood unpleasantly! -- If you used the dfeault paths (~/.homebridge/knx_config.json) you can just restart homebridge using the GUI -- If you didn't, or want to use child bridges for more accessories (than 149) you need to configure homebridge (using the GUI e.g.). The following sample is from my test installation +- Then put the configuration file *knx_config.json* into `~/.homebridge` (or another folder to your liking, but it should be **readable** and **writable** by user `homebridge` or group `homebridge` which is created by the homebridge installer), and adapt them to your needs (knxd address and some test devices in `knx_config.json`) +- Eliminate everything (especially all group addresses) that might harm your KNX installation. Sending bus telegrams to your alarm device might wake the neighbourhood unpleasantly! +- Use the homebridge UI to create platform instances. You need to specify a name (your choice), a file name or path to th eknx configuration files, and the means of communication with the KNX bus (knxd or KNX multicast). +- You can use the UI to move the platform instances you have created into _child bridges_, which is **heavily encouraged**. +- You can look at (or even modify) the config using the UI. The following sample is from my test installation ```json { @@ -49,7 +50,11 @@ I cannot support the knxd. Please address issues directly at the [knxd issue pag { "name": "KNX", "platform": "KNX", - "config_path": "/home/pi/homebridge/dg-knx_config.json" + "config_path": "/home/pi/homebridge/dg-knx_config.json", + "_bridge": { + "username": "1E:0B:9B:24:17:01", + "port": 51490 + } }, { "name": "KNX", @@ -57,7 +62,7 @@ I cannot support the knxd. Please address issues directly at the [knxd issue pag "config_path": "/home/pi/homebridge/og-knx_config.json", "_bridge": { "username": "0E:0B:9B:24:17:00", - "port": 51490 + "port": 51492 } } ] diff --git a/index.js b/index.js index 05fdca9..914d39b 100644 --- a/index.js +++ b/index.js @@ -185,10 +185,10 @@ KNXPlatform.prototype.configure = function () { /* *************** read the config the first time * - */ - if (!this.config.GroupAddresses) { - this.config.GroupAddresses = []; - } + // */ + // if (!this.config.GroupAddresses) { + // this.config.GroupAddresses = []; + // } // iterate through all devices the platform my offer // for each device, create an accessory diff --git a/lib/groupaddress.js b/lib/groupaddress.js index 3deaf68..c41db84 100644 --- a/lib/groupaddress.js +++ b/lib/groupaddress.js @@ -14,17 +14,17 @@ * @param {boolean} reversed - Indicates if Boolean or Percentage values should be counted down instead of up */ function GroupAddress(gaNumber, name, dptype, readable, readOnStartup, writable, comment, reversed) { - this.address = gaNumber; - this.name = name; + this.address = gaNumber; + this.name = name; - //todo: This check requires a string, not an object from the list - this.dptype = (dptype in DPTTypes) ? DPTTypes[dptype] : DPTTypes.DPT1; + //todo: This check requires a string, not an object from the list + this.dptype = (dptype in DPTTypes) ? DPTTypes[dptype] : DPTTypes.DPT1; - this.readable = (readable !== false); - this.readOnStartup = (readOnStartup !== false); - this.writable = (writable !== false); - this.comment = comment; - this.reversed = (reversed == true); // the evil twins are right here. + this.readable = (readable !== false); + this.readOnStartup = (readOnStartup !== false); + this.writable = (writable !== false); + this.comment = comment; + this.reversed = (reversed == true); // the evil twins are right here. } // change from 31 to 15: fixes issue #69 https://github.com/snowdd1/homebridge-knx/issues/69 @@ -38,30 +38,30 @@ function GroupAddress(gaNumber, name, dptype, readable, readOnStartup, writable, * @param {String} groupAddress - Address as String, such as "15/7/255" * @returns {String} */ -var validateAddressText = function(groupAddress) { - if (typeof groupAddress !== 'string') { - return 'ERR Invalid parameter'; - } - // assume triple notation (0..31) (0..7) (0..255) - // extended to 5 bit for first triple - // https://github.com/snowdd1/homebridge-knx/issues/72 - // https://github.com/andreek/node-eibd/pull/41 - var addressArray = groupAddress.match(/^(([0-9]|[1-9][0-9]{1,2})\/([0-9]|[1-9][0-9]{1,2})\/([0-9]|[1-9][0-9]{1,2}))/); - if (!addressArray) { - // no valid structure - return 'ERR no valid group address structure (31/7/255)'; - } - // change from 31 to 15: fixes issue #69 https://github.com/snowdd1/homebridge-knx/issues/69 - if (parseInt(addressArray[2]) > 31) { - return 'ERR no valid group address structure (31/7/255): first triple exceeds 31'; - } - if (parseInt(addressArray[3]) > 7) { - return 'ERR no valid group address structure (31/7/255): second triple exceeds 7'; - } - if (parseInt(addressArray[4]) > 255) { - return 'ERR no valid group address structure (31/7/255): third triple exceeds 255'; - } - return 'OK'; +var validateAddressText = function (groupAddress) { + if (typeof groupAddress !== 'string') { + return 'ERR Invalid parameter'; + } + // assume triple notation (0..31) (0..7) (0..255) + // extended to 5 bit for first triple + // https://github.com/snowdd1/homebridge-knx/issues/72 + // https://github.com/andreek/node-eibd/pull/41 + var addressArray = groupAddress.match(/^(([0-9]|[1-9][0-9]{1,2})\/([0-9]|[1-9][0-9]{1,2})\/([0-9]|[1-9][0-9]{1,2}))/); + if (!addressArray) { + // no valid structure + return 'ERR no valid group address structure (31/7/255)'; + } + // change from 31 to 15: fixes issue #69 https://github.com/snowdd1/homebridge-knx/issues/69 + if (parseInt(addressArray[2]) > 31) { + return 'ERR no valid group address structure (31/7/255): first triple exceeds 31'; + } + if (parseInt(addressArray[3]) > 7) { + return 'ERR no valid group address structure (31/7/255): second triple exceeds 7'; + } + if (parseInt(addressArray[4]) > 255) { + return 'ERR no valid group address structure (31/7/255): third triple exceeds 255'; + } + return 'OK'; }; /** @@ -70,11 +70,11 @@ var validateAddressText = function(groupAddress) { * @param {String} groupAddress * @returns {Boolean} */ - var validateAddress = function(groupAddress) { - if (validateAddressText(groupAddress) === 'OK') { - return true; - } - return false; +var validateAddress = function (groupAddress) { + if (validateAddressText(groupAddress) === 'OK') { + return true; + } + return false; }; @@ -84,80 +84,80 @@ var validateAddressText = function(groupAddress) { * @param {characteristic} characteristic - the characteristic to be bound, for automatic type derivation. * @returns {GroupAddress} */ -var gaComplete = function(address, globs, Characteristic){ - // look for the address in the global group address list - if (!globs.config.GroupAddresses[address]) { - globs.log("The Group Address " + address + " is not yet defined. Type testing is not supported. Assuming match to HomeKit type"); - if (Characteristic.props.format === globs.Characteristic.Formats.BOOL) { - // Boolean - return new GroupAddress(address, 'automatically generated', DPTTypes.DPT1, true, true, true, 'INITIAL') - } else if (Characteristic.props.format === globs.Characteristic.Formats.INT || Characteristic.props.format === globs.Characteristic.Formats.UINT8) { - if (Characteristic.props.unit === globs.Characteristic.Units.PERCENTAGE) { - // percentage - return new GroupAddress(address, 'automatically generated', DPTTypes['DPT5.001'], true, true, true, 'INITIAL'); - } else { - // integer (assuming 1 byte) - return new GroupAddress(address, 'automatically generated', DPTTypes.DPT5, true, true, true, 'INITIAL'); - } +var gaComplete = function (address, globs, Characteristic) { + // look for the address in the global group address list + //if (!globs.config.GroupAddresses[address]) { + //globs.log("The Group Address " + address + " is not yet defined. Type testing is not supported. Assuming match to HomeKit type"); + if (Characteristic.props.format === globs.Characteristic.Formats.BOOL) { + // Boolean + return new GroupAddress(address, 'automatically generated', DPTTypes.DPT1, true, true, true, 'INITIAL') + } else if (Characteristic.props.format === globs.Characteristic.Formats.INT || Characteristic.props.format === globs.Characteristic.Formats.UINT8) { + if (Characteristic.props.unit === globs.Characteristic.Units.PERCENTAGE) { + // percentage + return new GroupAddress(address, 'automatically generated', DPTTypes['DPT5.001'], true, true, true, 'INITIAL'); + } else { + // integer (assuming 1 byte) + return new GroupAddress(address, 'automatically generated', DPTTypes.DPT5, true, true, true, 'INITIAL'); + } - } else if (Characteristic.props.format === globs.Characteristic.Formats.FLOAT) { - return new GroupAddress(address, 'automatically generated', DPTTypes.DPT9, true, true, true, 'INITIAL') - } - } else { - return new GroupAddress(address, globs.config.GroupAddresses[address].name, globs.config.GroupAddresses[address].dptype, - globs.config.GroupAddresses[address].readable, globs.config.GroupAddresses[address].readOnStartup, - globs.config.GroupAddresses[address].writable, globs.config.GroupAddresses[address].comment); + } else if (Characteristic.props.format === globs.Characteristic.Formats.FLOAT) { + return new GroupAddress(address, 'automatically generated', DPTTypes.DPT9, true, true, true, 'INITIAL') + } + // } else { + // return new GroupAddress(address, globs.config.GroupAddresses[address].name, globs.config.GroupAddresses[address].dptype, + // globs.config.GroupAddresses[address].readable, globs.config.GroupAddresses[address].readOnStartup, + // globs.config.GroupAddresses[address].writable, globs.config.GroupAddresses[address].comment); - } + // } } var validDPTTypes = { - DPT1 : "DPT1", - DPT5 : "DPT5", - DPT9 : "DPT9", - DPT1_002 : "DPT1.002", - DPT1_011 : "DPT1.011", - DPT5_001 : "DPT5.001" + DPT1: "DPT1", + DPT5: "DPT5", + DPT9: "DPT9", + DPT1_002: "DPT1.002", + DPT1_011: "DPT1.011", + DPT5_001: "DPT5.001" }; var DPTTypes = { - DPT1 : { - type : "DPT1", - char : "boolean", - name : "generic 1 bit" - }, - DPT5 : { - type : "DPT5", - char : "int", - name : "generic 1 byte unsigned integer" - }, - DPT9 : { - type : "DPT9", - char : "float", - name : "generic 2 bytes float" - }, - "DPT1.002" : { - type : "DPT1.002", - char : "boolean", - name : "Boolean" - }, - "DPT1.011" : { - type : "DPT1.011", - char : "boolean", - name : "Status" - }, - "DPT5.001" : { - type : "DPT5.001", - char : "percentage", - name : "Percentage" - } + DPT1: { + type: "DPT1", + char: "boolean", + name: "generic 1 bit" + }, + DPT5: { + type: "DPT5", + char: "int", + name: "generic 1 byte unsigned integer" + }, + DPT9: { + type: "DPT9", + char: "float", + name: "generic 2 bytes float" + }, + "DPT1.002": { + type: "DPT1.002", + char: "boolean", + name: "Boolean" + }, + "DPT1.011": { + type: "DPT1.011", + char: "boolean", + name: "Status" + }, + "DPT5.001": { + type: "DPT5.001", + char: "percentage", + name: "Percentage" + } }; module.exports = { - GroupAddress : GroupAddress, - DPTTypes : DPTTypes, - validDPTTypes : validDPTTypes, - gaComplete: gaComplete, - validateAddressText: validateAddressText, - validateAddress: validateAddress + GroupAddress: GroupAddress, + DPTTypes: DPTTypes, + validDPTTypes: validDPTTypes, + gaComplete: gaComplete, + validateAddressText: validateAddressText, + validateAddress: validateAddress }; diff --git a/lib/knxaccess.js b/lib/knxaccess.js index 8bec998..5cbc71f 100644 --- a/lib/knxaccess.js +++ b/lib/knxaccess.js @@ -7,10 +7,10 @@ */ var globs; -var knx = require('knx'); +var knx = require('knx'); var knxd = require('eibd'); var iterate = require('./iterate'); -var allFunctions = {}; +var allFunctions = {}; // all purpose / all types write function @@ -21,224 +21,224 @@ var allFunctions = {}; * @param {string} dpt - Data Point Type string as "DPT1" * @param {string} value - Value to send */ -allFunctions.knxwrite = function(callback, groupAddress, dpt, value) { - //globs.info("DEBUG in knxwrite"); - /** @type {eibd~Connection} */ - - if(!(!globs.knxconnection) || globs.knxconnection==='knxjs') { - // use KNX JS for KNX IP - var connection = knx.Connection({ - handlers: { - connected: function() { - connection.write(groupAddress, parseFloat(value), dpt, function(err) { - if (callback) { - try { - callback(); - } catch (e) { - globs.log('Caught error '+ e + ' when calling homebridge callback.'); - } - } - }); - } - } - }); - } else { - // use EIBD package for KNXD - var knxdConnection = new knxd.Connection(); - //globs.info("DEBUG in knxwrite: created empty connection, trying to connect socket to " + globs.knxd_ip + ":" + globs.knxd_port); - knxdConnection.socketRemote({ - host : globs.knxd_ip, - port : globs.knxd_port - }, function(err) { - if (err) { - // a fatal error occurred - console.error("FATAL: knxd or eibd not reachable: " + err); - throw new Error("Cannot reach knxd or eibd service, please check installation and configuration .json"); - } - var dest = knxd.str2addr(groupAddress); - globs.debug("DEBUG knxwrite Address conversion, converted "+ groupAddress+ " to " + dest); - knxdConnection.openTGroup(dest, 1, function(err) { - if (err) { - globs.errorlog("[ERROR] knxwrite:openTGroup: " + err); - if (callback) { - try { - callback(err); - } catch (e) { - globs.log('Caught error '+ e + ' when calling homebridge callback.'); - } - } - } else { - //globs.debug("DEBUG opened TGroup "); - var msg = knxd.createMessage('write', dpt, parseFloat(value)); - knxdConnection.sendAPDU(msg, function(err) { - if (err) { - globs.errorlog("[ERROR] knxwrite:sendAPDU: " + err); - if (callback) { - try { - callback(err); - } catch (e) { - globs.log('Caught error '+ e + ' when calling homebridge callback.'); - } - } - } else { - globs.debug("knxAccess.knxwrite: knx data sent: Value " + value + " for GA " + groupAddress); - if (callback) { - try { - callback(); - } catch (e) { - globs.log('Caught error '+ e + ' when calling homebridge callback.'); - } - } - } - }); - } - }); - }); - } +allFunctions.knxwrite = function (callback, groupAddress, dpt, value) { + //globs.info("DEBUG in knxwrite"); + /** @type {eibd~Connection} */ + + if (!(!globs.knxconnection) || globs.knxconnection === 'knxjs') { + // use KNX JS for KNX IP + var connection = knx.Connection({ + handlers: { + connected: function () { + connection.write(groupAddress, parseFloat(value), dpt, function (err) { + if (callback) { + try { + callback(); + } catch (e) { + globs.log('Caught error ' + e + ' when calling homebridge callback.'); + } + } + }); + } + } + }); + } else { + // use EIBD package for KNXD + var knxdConnection = new knxd.Connection(); + //globs.info("DEBUG in knxwrite: created empty connection, trying to connect socket to " + globs.knxd_ip + ":" + globs.knxd_port); + knxdConnection.socketRemote({ + host: globs.knxd_ip, + port: globs.knxd_port + }, function (err) { + if (err) { + // a fatal error occurred + console.error("FATAL: knxd or eibd not reachable: " + err); + throw new Error("Cannot reach knxd or eibd service, please check installation and configuration .json"); + } + var dest = knxd.str2addr(groupAddress); + globs.debug("DEBUG knxwrite Address conversion, converted " + groupAddress + " to " + dest); + knxdConnection.openTGroup(dest, 1, function (err) { + if (err) { + globs.errorlog("[ERROR] knxwrite:openTGroup: " + err); + if (callback) { + try { + callback(err); + } catch (e) { + globs.log('Caught error ' + e + ' when calling homebridge callback.'); + } + } + } else { + //globs.debug("DEBUG opened TGroup "); + var msg = knxd.createMessage('write', dpt, parseFloat(value)); + knxdConnection.sendAPDU(msg, function (err) { + if (err) { + globs.errorlog("[ERROR] knxwrite:sendAPDU: " + err); + if (callback) { + try { + callback(err); + } catch (e) { + globs.log('Caught error ' + e + ' when calling homebridge callback.'); + } + } + } else { + globs.debug("knxAccess.knxwrite: knx data sent: Value " + value + " for GA " + groupAddress); + if (callback) { + try { + callback(); + } catch (e) { + globs.log('Caught error ' + e + ' when calling homebridge callback.'); + } + } + } + }); + } + }); + }); + } }; -allFunctions.setBooleanState = function(value, callback, gaddress, reverseflag) { - var numericValue = reverseflag ? 1 : 0; - if (value) { - numericValue = reverseflag ? 0 : 1; // need 0 or 1, not true or something - } - globs.debug("setBooleanState: Setting " + gaddress + " " + reverseflag ? " (reverse)" : "" + " Boolean to " + numericValue); - allFunctions.knxwrite(callback, gaddress, 'DPT1', numericValue); +allFunctions.setBooleanState = function (value, callback, gaddress, reverseflag) { + var numericValue = reverseflag ? 1 : 0; + if (value) { + numericValue = reverseflag ? 0 : 1; // need 0 or 1, not true or something + } + globs.debug("setBooleanState: Setting " + gaddress + " " + reverseflag ? " (reverse)" : "" + " Boolean to " + numericValue); + allFunctions.knxwrite(callback, gaddress, 'DPT1', numericValue); }; -allFunctions.setPercentage = function(value, callback, gaddress, reverseflag) { - - var numericValue = 0; - value = (value >= 0 ? (value <= 100 ? value : 100) : 0); //ensure range 0..100 - if (!(!globs.knxconnection) || globs.knxconnection === 'knxjs') { - numericValue = reverseflag ? 100 - value : value; - } else { - 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 - } - } - globs.debug("setPercentage: Setting " + gaddress + " percentage " + reverseflag ? " (reverse)" : "" + " to " + value + " (" + numericValue + ")"); - allFunctions.knxwrite(callback, gaddress, 'DPT5.001', numericValue); +allFunctions.setPercentage = function (value, callback, gaddress, reverseflag) { + + var numericValue = 0; + value = (value >= 0 ? (value <= 100 ? value : 100) : 0); //ensure range 0..100 + if (!(!globs.knxconnection) || globs.knxconnection === 'knxjs') { + numericValue = reverseflag ? 100 - value : value; + } else { + 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 + } + } + globs.debug("setPercentage: Setting " + gaddress + " percentage " + reverseflag ? " (reverse)" : "" + " to " + value + " (" + numericValue + ")"); + allFunctions.knxwrite(callback, gaddress, 'DPT5.001', numericValue); }; -allFunctions.setInt = function(value, callback, gaddress) { +allFunctions.setInt = function (value, callback, gaddress) { - var numericValue = 0; - if (value && value >= 0 && value <= 255) { - numericValue = value; // assure 0..255 for KNX bus - } - globs.debug("setInt: Setting " + gaddress + " int to " + value + " (" + numericValue + ")"); - allFunctions.knxwrite(callback, gaddress, 'DPT5', numericValue); + var numericValue = 0; + if (value && value >= 0 && value <= 255) { + numericValue = value; // assure 0..255 for KNX bus + } + globs.debug("setInt: Setting " + gaddress + " int to " + value + " (" + numericValue + ")"); + allFunctions.knxwrite(callback, gaddress, 'DPT5', numericValue); }; -allFunctions.setUInt16 = function(value, callback, gaddress) { +allFunctions.setUInt16 = function (value, callback, gaddress) { - var numericValue = 0; - if (value && value >= 0 && value <= 65535) { - numericValue = value; // assure 0..65535 for KNX bus - } - globs.debug("setUInt16: Setting " + gaddress + " int to " + value + " (" + numericValue + ")"); - allFunctions.knxwrite(callback, gaddress, 'DPT7', numericValue); + var numericValue = 0; + if (value && value >= 0 && value <= 65535) { + numericValue = value; // assure 0..65535 for KNX bus + } + globs.debug("setUInt16: Setting " + gaddress + " int to " + value + " (" + numericValue + ")"); + allFunctions.knxwrite(callback, gaddress, 'DPT7', numericValue); }; -allFunctions.setFloat = function(value, callback, gaddress) { +allFunctions.setFloat = function (value, callback, gaddress) { - var numericValue = 0; - if (value) { - numericValue = value; // homekit expects precision of 1 decimal - } - globs.debug("setFloat: Setting " + gaddress + " Float to " + numericValue); - allFunctions.knxwrite(callback, gaddress, 'DPT9', numericValue); + var numericValue = 0; + if (value) { + numericValue = value; // homekit expects precision of 1 decimal + } + globs.debug("setFloat: Setting " + gaddress + " Float to " + numericValue); + allFunctions.knxwrite(callback, gaddress, 'DPT9', numericValue); }; // issues an all purpose read request on the knx bus // DOES NOT WAIT for an answer. Please register the address with a callback using registerGA() function -allFunctions.knxread = function(groupAddress) { - globs.debug("DEBUG in knxread"); - if (!groupAddress) { - return null; - } - globs.debug("[knxdevice:knxread] preparing knx request for " + groupAddress); - - // new knxconnection syntax, might fix #164 - if(!(!globs.knxconnection) || globs.knxconnection==='knxjs') { - // use KNX JS for KNX IP - var connection = knx.Connection({ - handlers: { - connected: function() { - connection.read(groupAddress); - } - } - }); - } else { - // use eibd package for KNXD bus access - var knxdConnection = new knxd.Connection(); - //globs.info("DEBUG in knxread: created empty connection, trying to connect socket to " + globs.knxd_ip + ":" + globs.knxd_port); - knxdConnection.socketRemote({ - host : globs.knxd_ip, - port : globs.knxd_port - }, function(err) { - if (err) { - throw { - name : "KNXD connection failed", - message : "The connection to the knx daemon failed. Check IP and Port." - }; - } - var dest = knxd.str2addr(groupAddress); - // globs.info("DEBUG got dest="+dest); - knxdConnection.openTGroup(dest, 1, function(err) { - if (err) { - globs.errorlog("[ERROR] knxread:openTGroup: " + err); - } else { - // globs.info("DEBUG knxread: opened TGroup "); - var msg = knxd.createMessage('read', 'DPT1', 0); - knxdConnection.sendAPDU(msg, function(err) { - if (err) { - globs.errorlog("[ERROR] knxread:sendAPDU: " + err); - } else { - globs.debug("[knxdevice:knxread] knx request sent for " + groupAddress); - } - }); - } - }); - }); - } +allFunctions.knxread = function (groupAddress) { + globs.debug("DEBUG in knxread"); + if (!groupAddress) { + return null; + } + globs.debug("[knxdevice:knxread] preparing knx request for " + groupAddress); + + // new knxconnection syntax, might fix #164 + if (!(!globs.knxconnection) || globs.knxconnection === 'knxjs') { + // use KNX JS for KNX IP + var connection = knx.Connection({ + handlers: { + connected: function () { + connection.read(groupAddress); + } + } + }); + } else { + // use eibd package for KNXD bus access + var knxdConnection = new knxd.Connection(); + //globs.info("DEBUG in knxread: created empty connection, trying to connect socket to " + globs.knxd_ip + ":" + globs.knxd_port); + knxdConnection.socketRemote({ + host: globs.knxd_ip, + port: globs.knxd_port + }, function (err) { + if (err) { + throw { + name: "KNXD connection failed", + message: "The connection to the knx daemon failed. Check IP and Port." + }; + } + var dest = knxd.str2addr(groupAddress); + // globs.info("DEBUG got dest="+dest); + knxdConnection.openTGroup(dest, 1, function (err) { + if (err) { + globs.errorlog("[ERROR] knxread:openTGroup: " + err); + } else { + // globs.info("DEBUG knxread: opened TGroup "); + var msg = knxd.createMessage('read', 'DPT1', 0); + knxdConnection.sendAPDU(msg, function (err) { + if (err) { + globs.errorlog("[ERROR] knxread:sendAPDU: " + err); + } else { + globs.debug("[knxdevice:knxread] knx request sent for " + groupAddress); + } + }); + } + }); + }); + } }; // issuing multiple read requests at once -allFunctions.knxreadarray = function(groupAddresses) { - 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 - allFunctions.knxread(groupAddresses[i].match(/(\d*\/\d*\/\d*)/)[0]); // clean address - } - } - } else { - // it's only one - allFunctions.knxread(groupAddresses.match(/(\d*\/\d*\/\d*)/)[0]); // regex for cleaning address - } +allFunctions.knxreadarray = function (groupAddresses) { + 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 + allFunctions.knxread(groupAddresses[i].match(/(\d*\/\d*\/\d*)/)[0]); // clean address + } + } + } else { + // it's only one + allFunctions.knxread(groupAddresses.match(/(\d*\/\d*\/\d*)/)[0]); // regex for cleaning address + } }; /** * reads all addresses that are stored in an named list (Object) in notation {"1/2/3":1, "1/2/4":1, ...} * @param {object[]} groupAddresses - group addresses in an object notation */ -allFunctions.knxreadhash = function(groupAddresses) { - for ( var address in groupAddresses) { - if (groupAddresses.hasOwnProperty(address)) { // do not use inherited properties - allFunctions.knxread(address.match(/(\d*\/\d*\/\d*)/)[0]); // clean address - } - - } +allFunctions.knxreadhash = function (groupAddresses) { + for (var address in groupAddresses) { + if (groupAddresses.hasOwnProperty(address)) { // do not use inherited properties + allFunctions.knxread(address.match(/(\d*\/\d*\/\d*)/)[0]); // clean address + } + + } }; -allFunctions.setGlobs = function(globsObject) { - globs = globsObject; +allFunctions.setGlobs = function (globsObject) { + globs = globsObject; }; /** @@ -248,41 +248,41 @@ allFunctions.setGlobs = function(globsObject) { * @param {GroupAddress} groupAddress Object * @param {function()} callback - the callback to be called upon completion */ -allFunctions.writeValueKNX = function(value, groupAddress, callback) { - /* depending on the DPT of the target address we have to convert the value. - * As with the possible mappings the dpt could be different for mapping results, - * we do it at runtime, and do not do the decision at init time. - */ - - var setGA = groupAddress.address; - var setReverse = groupAddress.reversed; - -/* - iterate(groupAddress); - globs.debug("Value: " + value); - globs.debug("Reversed: "+ setReverse); -*/ - switch (groupAddress.dptype) { - case "DPT1": - allFunctions.setBooleanState(value, callback, setGA, setReverse); - break; - case "DPT5.001": - allFunctions.setPercentage(value, callback, setGA, setReverse); - break; - case "DPT9": - allFunctions.setFloat(value, callback, setGA); - break; - case "DPT5": - allFunctions.setInt(value, callback, setGA); - break; - case "DPT7": - allFunctions.setUInt16(value, callback, setGA); - break; - default: - globs.errorlog( "[ERROR] unknown type passed: [" + groupAddress.dptype + "]" ); - throw new Error("[ERROR] unknown type passed"); - - } +allFunctions.writeValueKNX = function (value, groupAddress, callback) { + /* depending on the DPT of the target address we have to convert the value. + * As with the possible mappings the dpt could be different for mapping results, + * we do it at runtime, and do not do the decision at init time. + */ + + var setGA = groupAddress.address; + var setReverse = groupAddress.reversed; + + /* + iterate(groupAddress); + globs.debug("Value: " + value); + globs.debug("Reversed: "+ setReverse); + */ + switch (groupAddress.dptype) { + case "DPT1": + allFunctions.setBooleanState(value, callback, setGA, setReverse); + break; + case "DPT5.001": + allFunctions.setPercentage(value, callback, setGA, setReverse); + break; + case "DPT9": + allFunctions.setFloat(value, callback, setGA); + break; + case "DPT5": + allFunctions.setInt(value, callback, setGA); + break; + case "DPT7": + allFunctions.setUInt16(value, callback, setGA); + break; + default: + globs.errorlog("[ERROR] unknown type passed: [" + groupAddress.dptype + "]"); + throw new Error("[ERROR] unknown type passed"); + + } }; @@ -294,146 +294,148 @@ allFunctions.writeValueKNX = function(value, groupAddress, callback) { * @param {String} type - the DPT of the value received, if received directly from the bus. null or undefined if * mapped using values. */ -allFunctions.writeValueHK = function(val, chrKNX, type, reverse) { - // switch depending on the value type to be written - // @type {hap-nodeJS/lib/characteristic.js~characteristic} - var characteristic = chrKNX.getHomekitCharacteristic(); - globs.debug("knxAccess.writeValueHK("+ val +","+chrKNX.name+","+type+","+reverse + ")"); - globs.debug("knxAccess.writeValueHK: Format "+ characteristic.props.format +""); - - var returnValue=null; - - switch (characteristic.props.format) { - /* - * // Known HomeKit formats - Characteristic.Formats = { - BOOL: 'bool', - INT: 'int', - FLOAT: 'float', - STRING: 'string', - ARRAY: 'array', // unconfirmed - DICTIONARY: 'dictionary', // unconfirmed - UINT8: 'uint8', - UINT16: 'uint16', - UINT32: 'uint32', - UINT64: 'uint64', - DATA: 'data', // unconfirmed - TLV8: 'tlv8' - } - */ - case globs.Characteristic.Formats.BOOL: - // boolean is good for any trueish expression from value - globs.debug("BOOL:[" + characteristic.displayName + "]: Received value from KNX handler:" + val + " of type " + type ); - //characteristic.setValue(val ? (reverse ? 0 : 1) : (reverse ? 1 : 0), undefined, 'fromKNXBus'); - returnValue = val ? (reverse ? 0 : 1) : (reverse ? 1 : 0); - break; - case globs.Characteristic.Formats.INT: - // let fall through - case globs.Characteristic.Formats.UINT8: - // let fall through - case globs.Characteristic.Formats.UINT16: - // let fall through - //fixes #123 - case globs.Characteristic.Formats.UINT32: - // fixes #123 - // to an INT we can assume that the min and max values are set, or a list of allowed values is supplied - globs.debug("INT:[" + characteristic.displayName + "]: Received value from KNX handler:" + val + " of type " + type ); - - //iterate(characteristic.props); - - /* using evil twins "==" to address bug #66 https://github.com/snowdd1/homebridge-knx/issues/66 */ - if (characteristic.props.minValue == undefined && characteristic.props.maxValue == undefined) { - // no min or max defined, we use the safeSet() function to check for a list of allowed values - //characteristic.setValue(safeSet(characteristic, val), undefined, 'fromKNXBus'); - returnValue = val; // safeSet(characteristic, val); - // does it behave boolean and has a Reverse key word? - - } else { - - // we have a defined range. If it's a percentage, we want to convert the bus value to the spectrum - if (characteristic.props.unit === globs.Characteristic.Units.PERCENTAGE) { - if (!(!globs.knxconnection) || globs.knxconnection === 'knxjs') { - if (type === 'DPT5') { - val = reverse ? (255 - val) : val; - } else if (type === 'DPT5.001') { - val = reverse ? (100 - val) : val; - } - } else if (type === 'DPT5' || type === 'DPT5.001') { - val = (reverse ? (255 - val) : val) / 255 * 100; - } - // if the sending types are different, assume decimal percentage value (100=100%) - } - // round it for integer use: - val = Math.round(val); - if (val >= (characteristic.props.minValue || 0) && val <= (characteristic.props.maxValue || 255)) { - // it's in range - returnValue = val; - //characteristic.setValue(val, undefined, 'fromKNXBus'); - } else { - globs.errorlog('error', "[" + "]:[" + characteristic.displayName + "]: Value " + val + " out of bounds "+(characteristic.props.minValue || 0)+"..."+(characteristic.props.maxValue || 255)); - } - } - - break; - case globs.Characteristic.Formats.FLOAT: - globs.debug("FLOAT:[" + characteristic.displayName + "]: Received value from KNX handler:" + val + " of type " + type + " for " + - characteristic.displayName); - - // If it's a percentage, we want to convert the bus value to the spectrum - if (characteristic.props.unit === globs.Characteristic.Units.PERCENTAGE) { - if (!(!globs.knxconnection) || globs.knxconnection === 'knxjs') { - if (type === 'DPT5') { - val = reverse ? (255 - val) : val; - } else if (type === 'DPT5.001') { - val = reverse ? (100 - val) : val; - } - } else if (type === 'DPT5' || type === 'DPT5.001') { - val = Math.round((reverse ? (255 - val) : val) / 255 * 100); - } - // if the sending types are different, assume decimal percentage value (100=100%) - } - - // make hk_value compliant to properties - var hk_value; - if (characteristic.props.minStep) { - // quantize - hk_value = Math.round(val / characteristic.props.minStep) / (1 / characteristic.props.minStep); - } else { - hk_value = val; - } - // range check - var 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) { - returnValue = hk_value; - //characteristic.setValue(hk_value, undefined, 'fromKNXBus'); - } else { - globs.errorlog(":[" + characteristic.displayName + "]: Value " + hk_value +" out of bounds "+ characteristic.props.minValue +" ... " + characteristic.props.maxValue); - } - break; - default: - globs.log.warn("knxAccess.writeValueHK() - NO KNOWN TYPE"); - break; - } - if (returnValue!==null) { - // the value has changed - // is the value the same as in homebridge? - if (characteristic.value!==returnValue) { - // value has changed - globs.debug('Value changed, updating homebridge'); - characteristic.updateValue(returnValue, undefined, 'fromKNXBus'); - } else { - globs.debug('INFO HomeKit: No value change'); - } - } else { - globs.debug('INFO HomeKit: No valid value.'); - } - globs.debug('exiting writeValueHK()'); +allFunctions.writeValueHK = function (val, chrKNX, type, reverse) { + // switch depending on the value type to be written + // @type {hap-nodeJS/lib/characteristic.js~characteristic} + var characteristic = chrKNX.getHomekitCharacteristic(); + globs.debug("knxAccess.writeValueHK(" + val + "," + chrKNX.name + "," + type + "," + reverse + ")"); + globs.debug("knxAccess.writeValueHK: Format " + characteristic.props.format + ""); + + var returnValue = null; + + switch (characteristic.props.format) { + /* + * // Known HomeKit formats + Characteristic.Formats = { + BOOL: 'bool', + INT: 'int', + FLOAT: 'float', + STRING: 'string', + ARRAY: 'array', // unconfirmed + DICTIONARY: 'dictionary', // unconfirmed + UINT8: 'uint8', + UINT16: 'uint16', + UINT32: 'uint32', + UINT64: 'uint64', + DATA: 'data', // unconfirmed + TLV8: 'tlv8' + } + */ + case globs.Characteristic.Formats.BOOL: + // boolean is good for any trueish expression from value + globs.debug("BOOL:[" + characteristic.displayName + "]: Received value from KNX handler:" + val + " of type " + type); + //characteristic.setValue(val ? (reverse ? 0 : 1) : (reverse ? 1 : 0), undefined, 'fromKNXBus'); + returnValue = val ? (reverse ? 0 : 1) : (reverse ? 1 : 0); + break; + case globs.Characteristic.Formats.INT: + // let fall through + case globs.Characteristic.Formats.UINT8: + // let fall through + case globs.Characteristic.Formats.UINT16: + // let fall through + //fixes #123 + case globs.Characteristic.Formats.UINT32: + // fixes #123 + // to an INT we can assume that the min and max values are set, or a list of allowed values is supplied + globs.debug("INT:[" + characteristic.displayName + "]: Received value from KNX handler:" + val + " of type " + type); + + //iterate(characteristic.props); + + /* using evil twins "==" to address bug #66 https://github.com/snowdd1/homebridge-knx/issues/66 */ + if (characteristic.props.minValue == undefined && characteristic.props.maxValue == undefined) { + // no min or max defined, we use the safeSet() function to check for a list of allowed values + //characteristic.setValue(safeSet(characteristic, val), undefined, 'fromKNXBus'); + returnValue = val; // safeSet(characteristic, val); + // does it behave boolean and has a Reverse key word? + + } else { + + // we have a defined range. If it's a percentage, we want to convert the bus value to the spectrum + if (characteristic.props.unit === globs.Characteristic.Units.PERCENTAGE) { + if (!(!globs.knxconnection) || globs.knxconnection === 'knxjs') { + if (type === 'DPT5') { + val = reverse ? (255 - val) : val; + } else if (type === 'DPT5.001') { + val = reverse ? (100 - val) : val; + } + } else if (type === 'DPT5' || type === 'DPT5.001') { + val = (reverse ? (255 - val) : val) / 255 * 100; + } + // if the sending types are different, assume decimal percentage value (100=100%) + } + // round it for integer use: + val = Math.round(val); + if (val >= (characteristic.props.minValue || 0) && val <= (characteristic.props.maxValue || 255)) { + // it's in range + returnValue = val; + //characteristic.setValue(val, undefined, 'fromKNXBus'); + } else { + globs.errorlog('error UINT32:', "[" + "]:[" + characteristic.displayName + "]: Value " + val + " out of bounds " + (characteristic.props.minValue || 0) + "..." + (characteristic.props.maxValue || 255)); + } + } + + break; + case globs.Characteristic.Formats.FLOAT: + globs.debug("FLOAT:[" + characteristic.displayName + "]: Received value from KNX handler:" + val + " of type " + type + " for " + + characteristic.displayName); + + // If it's a percentage, we want to convert the bus value to the spectrum + if (characteristic.props.unit === globs.Characteristic.Units.PERCENTAGE) { + if (!(!globs.knxconnection) || globs.knxconnection === 'knxjs') { + if (type === 'DPT5') { + val = reverse ? (255 - val) : val; + } else if (type === 'DPT5.001') { + val = reverse ? (100 - val) : val; + } + } else if (type === 'DPT5' || type === 'DPT5.001') { + val = Math.round((reverse ? (255 - val) : val) / 255 * 100); + } + // if the sending types are different, assume decimal percentage value (100=100%) + } + + // make hk_value compliant to properties + var hk_value; + if (characteristic.props.minStep) { + // quantize + hk_value = Math.round(val / characteristic.props.minStep) / (1 / characteristic.props.minStep); + } else { + hk_value = val; + } + // range check + var validValue = true; // assume validity at beginning + if (characteristic.props.minValue) { + validValue = validValue && (hk_value >= characteristic.props.minValue); + globs.debug("min value violation: " + !(hk_value >= characteristic.props.minValue)) + } + if (characteristic.props.maxValue) { + validValue = validValue && (hk_value <= characteristic.props.maxValue); + globs.debug("max value violation: " + !(hk_value <= characteristic.props.maxValue)) + } + if (validValue) { + returnValue = hk_value; + //characteristic.setValue(hk_value, undefined, 'fromKNXBus'); + } else { + globs.errorlog("error FLOAT:[" + characteristic.displayName + "]: Value " + hk_value + " out of bounds " + characteristic.props.minValue + " ... " + characteristic.props.maxValue); + } + break; + default: + globs.log.warn("knxAccess.writeValueHK() - NO KNOWN TYPE"); + break; + } + if (returnValue !== null) { + // the value has changed + // is the value the same as in homebridge? + if (characteristic.value !== returnValue) { + // value has changed + globs.debug('Value changed, updating homebridge'); + characteristic.updateValue(returnValue, undefined, 'fromKNXBus'); + } else { + globs.debug('INFO HomeKit: No value change'); + } + } else { + globs.debug('INFO HomeKit: No valid value.'); + } + globs.debug('exiting writeValueHK()'); }; /** @@ -443,29 +445,29 @@ allFunctions.writeValueHK = function(val, chrKNX, type, reverse) { * @param {String} groupAddress - Address as String, such as "31/7/255" * @returns {String} */ -allFunctions.validateAddressText = function(groupAddress) { - if (typeof groupAddress !== 'string') { - return 'ERR Invalid parameter'; - } - // assume triple notation (0..31) (0..7) (0..255) - // extended to 5 bit for first triple - // https://github.com/snowdd1/homebridge-knx/issues/72 - // https://github.com/andreek/node-eibd/pull/41 - var addressArray = groupAddress.match(/^(([0-9]|[1-9][0-9]{1,2})\/([0-9]|[1-9][0-9]{1,2})\/([0-9]|[1-9][0-9]{1,2}))/); - if (!addressArray) { - // no valid structure - return 'ERR no valid group address structure (31/7/255)'; - } - if (parseInt(addressArray[2]) > 31) { - return 'ERR no valid group address structure (31/7/255): first triple exceeds 31'; - } - if (parseInt(addressArray[3]) > 7) { - return 'ERR no valid group address structure (31/7/255): second triple exceeds 7'; - } - if (parseInt(addressArray[4]) > 255) { - return 'ERR no valid group address structure (31/7/255): third triple exceeds 255'; - } - return 'OK'; +allFunctions.validateAddressText = function (groupAddress) { + if (typeof groupAddress !== 'string') { + return 'ERR Invalid parameter'; + } + // assume triple notation (0..31) (0..7) (0..255) + // extended to 5 bit for first triple + // https://github.com/snowdd1/homebridge-knx/issues/72 + // https://github.com/andreek/node-eibd/pull/41 + var addressArray = groupAddress.match(/^(([0-9]|[1-9][0-9]{1,2})\/([0-9]|[1-9][0-9]{1,2})\/([0-9]|[1-9][0-9]{1,2}))/); + if (!addressArray) { + // no valid structure + return 'ERR no valid group address structure (31/7/255)'; + } + if (parseInt(addressArray[2]) > 31) { + return 'ERR no valid group address structure (31/7/255): first triple exceeds 31'; + } + if (parseInt(addressArray[3]) > 7) { + return 'ERR no valid group address structure (31/7/255): second triple exceeds 7'; + } + if (parseInt(addressArray[4]) > 255) { + return 'ERR no valid group address structure (31/7/255): third triple exceeds 255'; + } + return 'OK'; }; module.exports = allFunctions; diff --git a/lib/user.js b/lib/user.js index 70d22c6..d6405f2 100644 --- a/lib/user.js +++ b/lib/user.js @@ -126,29 +126,43 @@ User.loadConfig = function (platformconfig = undefined) { for (var int = 0; int < fileslist.length; int++) { // @type String let currfile = fileslist[int]; - console.debug("Reading from config: " + int + " of " + fileslist.length + ", file " + currfile); + console.debug("Reading from config: " + (int + 1) + " of " + fileslist.length + ", file " + currfile); if (currfile.endsWith(".json") || looksLikeYAML(currfile)) { - console.debug("extension fits"); + //console.debug("extension fits"); let readContent = yaml.load(fs.readFileSync(currfile)) + //console.debug(JSON.stringify(readContent, null, 4)) if ("Devices" in readContent) { for (let idev = 0; idev < readContent.Devices.length; idev++) { // add the current path name to each of the devices - readContent.Devices[i].sourcefilepath = currfile + readContent.Devices[idev].sourcefilename = currfile + devices.push(readContent.Devices[idev]) + console.debug("Pushing: " + devices[devices.length - 1].sourcefilename + " ?== " + currfile) } - devices.concat(readContent.Devices) + //console.debug("Devices " + readContent.Devices.length) + //devices.concat(readContent.Devices) delete readContent.Devices + //console.log("Devices: " + devices.length) } if ("GroupAddresses" in readContent) { delete readContent.GroupAddresses // legacy never used, delete it. } if (Object.getOwnPropertyNames(readContent).length > 0) { // still properties there - console.debug("settings are in " + currfile) - readContent.sourcefilepath = currfile + //console.debug("settings are in " + currfile) + readContent.sourcefilename = currfile + } + if (config == undefined) { + config = readContent + //console.debug("config is undefined") + } + if (readContent != undefined) { + //console.log("file content is defined") + //console.log("Devices: " + devices.length) + Object.assign(config, readContent) } - Object.assign(config, readContent) } } + config.Devices = devices } @@ -196,22 +210,30 @@ User.storeConfig = function (platformconfig = undefined) { // export the devices //collect the file names from devices - let filenames_in_config = {} - config.Devices.forEach(element => { - if (element.sourcefilename) { - if (filenames_in_config["element.sourcefilename"]) { - // exists, append - filenames_in_config["element.sourcefilename"].push(element) - } else { - // new - filenames_in_config["element.sourcefilename"] = [element] + let filenames_in_config = {}; + //console.debug(config.Devices.length); + if (config.Devices) { + config.Devices.forEach(element => { + console.debug("element ", element.DeviceName) + if (element.sourcefilename) { + if (filenames_in_config[element.sourcefilename]) { + // exists, append + filenames_in_config[element.sourcefilename].push(element) + //console.debug("add " + element.sourcefilename + ": " + element.DeviceName) + } else { + // new + //console.debug("Create: " + element.sourcefilename + ": " + element.DeviceName) + filenames_in_config[element.sourcefilename] = [element] + } } - } - }); - // export for each filename - Object.getOwnPropertyNames(filenames_in_config).forEach(filename => { - export_config(filenames_in_config[filename], filename) - }) + }); + // export for each filename + //console.debug("Filenames: " + Object.getOwnPropertyNames(filenames_in_config)) + Object.getOwnPropertyNames(filenames_in_config).forEach(filename => { + let export_object = { "Devices": filenames_in_config[filename] } + export_config(export_object, filename) + }) + } } console.log("---"); diff --git a/package.json b/package.json index 3decc84..1cac83e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homebridge-knx", - "version": "0.5.0-beta.10", + "version": "0.5.0-beta.12", "description": "homebridge shim for KNX home automation.", "main": "index.js", "scripts": {