From 983d357a61e207f2b03ab1ab4294cebc613b4a65 Mon Sep 17 00:00:00 2001 From: Simon Christmann Date: Fri, 1 Dec 2017 22:25:48 +0100 Subject: [PATCH 01/18] =?UTF-8?q?Bind=20to=20all=20interfaces,=20send=20?= =?UTF-8?q?=E2=80=98eth0=E2=80=99=20IP=20to=20CCU?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.js | 3 ++- index.js | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/config.js b/config.js index 30fd7b8..7310540 100644 --- a/config.js +++ b/config.js @@ -41,7 +41,8 @@ module.exports = require('yargs') 'mqtt-url': 'mqtt://127.0.0.1', name: 'hm', verbosity: 'info', - 'listen-address': require('./firstip.js'), + 'listen-address': '0.0.0.0', + 'init-address': require('./firstip.js'), 'listen-port': 2126, 'binrpc-listen-port': 2127, 'ping-interval': 30, diff --git a/index.js b/index.js index 9c1994a..6a3ba9b 100755 --- a/index.js +++ b/index.js @@ -635,9 +635,9 @@ process.on('SIGTERM', stop); function initIface(name, protocol) { let url; if (protocol === 'binrpc') { - url = 'xmlrpc_bin://' + (config.initAddress || config.listenAddress) + ':' + config.binrpcListenPort; + url = 'xmlrpc_bin://' + (config.initAddress) + ':' + config.binrpcListenPort; } else { - url = 'http://' + (config.initAddress || config.listenAddress) + ':' + config.listenPort; + url = 'http://' + (config.initAddress) + ':' + config.listenPort; } const params = [url, 'hm2mqtt_' + name]; log.info('rpc', name, '> init', params); From 26c197d9ade6352fb2bfaeeb2731b035c632b74f Mon Sep 17 00:00:00 2001 From: Simon Christmann Date: Fri, 1 Dec 2017 22:56:16 +0100 Subject: [PATCH 02/18] Disable explicitDouble for XML-RPC --- index.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 6a3ba9b..ed967ac 100755 --- a/index.js +++ b/index.js @@ -300,7 +300,17 @@ function rpcType(payload, paramset) { } else if (val > paramset.MAX) { val = paramset.MAX; } - val = {explicitDouble: val}; + /* JavaScript doesn't seperate integer and float/double types, so in JavaScript there's only "Number". + * However, the XML-RPC library needs to determine whether to wrap the value in 22 or 22, + * see [list of allowed datatypes](https://ws.apache.org/xmlrpc/types.html). + * + * node-xmlrpc does this with an `if ( value % 1 == 0)`, see [serializer.js:188](https://github.com/baalexander/node-xmlrpc/blob/d9c88c4185e16637ed5a22c1b91c80e958e8d69e/lib/serializer.js#L188) + * + * homematic-xmlrpc introduced an object like `{explicitDouble: val}`. + * + * The CCU2 seems to accept 22 messages and does a string to int/float conversion itself - currently in testing. + */ + val = String(val); break; case 'ENUM': if (typeof val === 'string') { From 00b79321aabc41632b74d99a2e27b2276cd611ec Mon Sep 17 00:00:00 2001 From: Simon Christmann Date: Fri, 1 Dec 2017 22:59:14 +0100 Subject: [PATCH 03/18] Conversion is not needed anymore CCU will accept strings containing the ENUM value --- index.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/index.js b/index.js index ed967ac..5498979 100755 --- a/index.js +++ b/index.js @@ -312,13 +312,6 @@ function rpcType(payload, paramset) { */ val = String(val); break; - case 'ENUM': - if (typeof val === 'string') { - if (paramset.ENUM && (paramset.ENUM.indexOf(val) !== -1)) { - val = paramset.ENUM.indexOf(val); - } - } - // eslint-disable-line no-fallthrough case 'INTEGER': val = parseInt(val, 10); if (val < paramset.MIN) { From b8170d34a7dc70f33a157904ab743744f09f8252 Mon Sep 17 00:00:00 2001 From: Simon Christmann Date: Fri, 1 Dec 2017 23:19:07 +0100 Subject: [PATCH 04/18] Fix hm/paramset/# topics --- index.js | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 5498979..56cd94a 100755 --- a/index.js +++ b/index.js @@ -360,12 +360,23 @@ function rpcSet(name, paramset, datapoint, payload) { const val = rpcType(payload, ps); - log.debug('rpc', iface, '> setValue', [address, datapoint, val]); - rpcClient[iface].methodCall('setValue', [address, datapoint, val], err => { - if (err) { - log.error(err); - } - }); + if ( paramset == "VALUES" ) { + log.debug('rpc', iface, '> setValue', [address, datapoint, val]); + rpcClient[iface].methodCall('setValue', [address, datapoint, val], err => { + if (err) { + log.error(err); + } + }); + } else { + const set = {}; + set[datapoint] = val; + log.debug('rpc', iface, '> putParamset', [address, paramset, set]); + rpcClient[iface].methodCall('putParamset', [address, paramset, set], err => { + if (err) { + log.error(err); + } + }); + } } function rega(script, callback) { From 2a1087c605feea57701ee543a84535910e546ac4 Mon Sep 17 00:00:00 2001 From: Simon Christmann Date: Sat, 2 Dec 2017 11:58:10 +0100 Subject: [PATCH 05/18] Option to replace colons in topic name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enables compatibility to OpenHAB MQTT binding, there the colon (:) is used as seperator in configuration and can’t be used in topic name: --- config.js | 1 + index.js | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/config.js b/config.js index 7310540..4c8c014 100644 --- a/config.js +++ b/config.js @@ -18,6 +18,7 @@ module.exports = require('yargs') .describe('help', 'show help') .describe('publish-metadata', '') .describe('mqtt-retain', 'enable/disable retain flag for mqtt messages') + .describe('replace-colons', 'Replace colons (:) in topic name with underscores (_). Useful for OpenHAB compatibility.').boolean('replace-colons') .alias({ a: 'ccu-address', b: 'binrpc-listen-port', diff --git a/index.js b/index.js index 56cd94a..ed509a6 100755 --- a/index.js +++ b/index.js @@ -129,12 +129,14 @@ mqtt.on('message', (topic, payload) => { const parts = topic.split('/'); if (parts.length >= 4 && parts[1] === 'set') { // Topic /set// - const channel = parts.slice(2, parts.length - 1).join('/'); + var channel = parts.slice(2, parts.length - 1).join('/'); + if ( config.replaceColons ) channel = channel.replace("_",":"); const datapoint = parts[parts.length - 1]; rpcSet(channel, 'VALUES', datapoint, payload); } else if (parts.length >= 5 && parts[1] === 'paramset') { // Topic /paramset/// - const channel = parts.slice(2, parts.length - 2).join('/'); + var channel = parts.slice(2, parts.length - 2).join('/'); + if ( config.replaceColons ) channel = channel.replace("_",":"); const paramset = parts[parts.length - 2]; const datapoint = parts[parts.length - 1]; rpcSet(channel, paramset, datapoint, payload); @@ -845,7 +847,9 @@ const rpcMethods = { ps = {}; } - const topic = config.name + '/status/' + (names[params[1]] || params[1]) + '/' + params[2]; + var channel = (names[params[1]] || params[1]); + if ( config.replaceColons ) channel = channel.replace(":","_"); + const topic = config.name + '/status/' + channel + '/' + params[2]; let payload = {val: params[3], ts, lc: changes[key], hm: {ADDRESS: params[1]}}; if (ps.UNIT && ps.UNIT !== '""') { From 0c9bcf28263f0bf18e13aeb3c1963f068d7beeaf Mon Sep 17 00:00:00 2001 From: Simon Christmann Date: Sat, 2 Dec 2017 12:00:40 +0100 Subject: [PATCH 06/18] Unneccessary, CCU already does checking --- index.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/index.js b/index.js index ed509a6..26e48cf 100755 --- a/index.js +++ b/index.js @@ -297,11 +297,6 @@ function rpcType(payload, paramset) { break; case 'FLOAT': val = parseFloat(val); - if (val < paramset.MIN) { - val = paramset.MIN; - } else if (val > paramset.MAX) { - val = paramset.MAX; - } /* JavaScript doesn't seperate integer and float/double types, so in JavaScript there's only "Number". * However, the XML-RPC library needs to determine whether to wrap the value in 22 or 22, * see [list of allowed datatypes](https://ws.apache.org/xmlrpc/types.html). @@ -316,11 +311,6 @@ function rpcType(payload, paramset) { break; case 'INTEGER': val = parseInt(val, 10); - if (val < paramset.MIN) { - val = paramset.MIN; - } else if (val > paramset.MAX) { - val = paramset.MAX; - } break; case 'STRING': val = String(val); From 43b3ac05db14f525971e49912b6b3e71f563cbf2 Mon Sep 17 00:00:00 2001 From: Simon Christmann Date: Mon, 11 Dec 2017 17:51:23 +0100 Subject: [PATCH 07/18] Replace Dockerfiles --- Dockerfile | 12 +++++------- Dockerfile.armhf | 15 ++++++--------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5e8352b..ed04229 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,8 @@ FROM node:slim -LABEL maintainer="Holger Imbery " \ - version="1.1a" \ - description="HM2MQTT (hm2mqtt.js) dockerized version of https://github.com/hobbyquaker/hm2mqtt.js" -RUN npm config set unsafe-perm true && npm install -g hm2mqtt +COPY . /node -EXPOSE 2126 -EXPOSE 2127 -ENTRYPOINT ["hm2mqtt"] +RUN cd /node && \ + npm install + +ENTRYPOINT [ "node", "/node/index.js" ] diff --git a/Dockerfile.armhf b/Dockerfile.armhf index 6fb0748..7a60b0e 100644 --- a/Dockerfile.armhf +++ b/Dockerfile.armhf @@ -1,11 +1,8 @@ -FROM hypriot/rpi-node:slim -LABEL maintainer="Holger Imbery " \ - version="1.1a" \ - description="HM2MQTT (hm2mqtt.js) dockerized version of https://github.com/hobbyquaker/hm2mqtt.js" +FROM arm32v7/node:slim -RUN npm config set unsafe-perm true -RUN npm install -g hm2mqtt +COPY . /node -EXPOSE 2126 -EXPOSE 2127 -ENTRYPOINT ["hm2mqtt"] +RUN cd /node && \ + npm install + +ENTRYPOINT [ "node", "/node/index.js" ] From 448f07ffbbea6576616fbaa049a9faefa49fe1c4 Mon Sep 17 00:00:00 2001 From: Simon Christmann Date: Mon, 11 Dec 2017 17:53:05 +0100 Subject: [PATCH 08/18] Add exposed ports --- Dockerfile | 3 +++ Dockerfile.armhf | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Dockerfile b/Dockerfile index ed04229..999ab7e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,4 +5,7 @@ COPY . /node RUN cd /node && \ npm install +EXPOSE 2126 +EXPOSE 2127 + ENTRYPOINT [ "node", "/node/index.js" ] diff --git a/Dockerfile.armhf b/Dockerfile.armhf index 7a60b0e..681e981 100644 --- a/Dockerfile.armhf +++ b/Dockerfile.armhf @@ -5,4 +5,7 @@ COPY . /node RUN cd /node && \ npm install +EXPOSE 2126 +EXPOSE 2127 + ENTRYPOINT [ "node", "/node/index.js" ] From 8c07bd8586afb34b040982aba68d56b3c8735e70 Mon Sep 17 00:00:00 2001 From: Simon Christmann Date: Fri, 15 Dec 2017 10:20:06 +0100 Subject: [PATCH 09/18] Publish ENUM as string-value, apped option list Before: { "val": 1, "ts": 1513329359300, "lc": 1512324435548, "hm": { "ADDRESS": "LEQ1519968:4", "ENUM": "MANU-MODE" } } After: { "val": "MANU-MODE", "ts": 1513329359300, "lc": 1512324435548, "hm": { "ADDRESS": "LEQ1519968:4", "VALUE_LIST": [ "AUTO-MODE", "MANU-MODE", "PARTY-MODE", "BOOST-MODE" ] } } --- index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 26e48cf..87619f0 100755 --- a/index.js +++ b/index.js @@ -850,7 +850,8 @@ const rpcMethods = { } } if (ps.TYPE === 'ENUM') { - payload.hm.ENUM = ps.VALUE_LIST[params[3]]; + payload.val = ps.VALUE_LIST[params[3]]; + payload.hm.VALUE_LIST = ps.VALUE_LIST; } payload = JSON.stringify(payload); From e0797743ccc3502f458dabed05f51f0782ce83e3 Mon Sep 17 00:00:00 2001 From: Simon Christmann Date: Sat, 24 Mar 2018 12:40:32 +0100 Subject: [PATCH 10/18] Update README --- README.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 81b5633..6949d75 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ hm2mqtt sends virtual datapoints named `LEVEL_NOTWORKING` respectively `STATE_NO is useful for e.g. sliders in a UI to prevent jumping sliders when a Blind or Keymatic is moving or a Dimmer is dimming. -## docker image for hm2mqtt.js +## Docker #### Usage (architecture: amd64) - pull the image to your machine, or if you are on a swarm to each node @@ -113,6 +113,27 @@ docker pull mqttsmarthome/hm2mqtt:armhf ``` - follow the description above (architecture: amd64), but leave out the pull sequence mentioned there. +### Developing with Docker + + git clone hm2mqtt + cd hm2mqtt + +#### Build for x86 + + docker build -t hm2mqtt-dev . + +#### Build for armhf (Raspberry Pi) + + docker build -t hm2mqtt-dev-armhf -f Dockerfile.armhf . + +You can also do a build for armhf with »Docker for Mac« or »Docker for Windows« even if you are on a x86 architecture. In this case you can copy over the built image via + + docker save hm2mqtt-dev-armhf | gzip > hm2mqtt-dev-armhf.tar.gz + gunzip -c hm2mqtt-dev-armhf.tar.gz | docker load + +or directly via `ssh`: + + docker save hm2mqtt-dev-armhf | gzip | pv | ssh root@hma-pi.lan 'gunzip | docker load' ## License From cf6a11591f78e91fe1b9491b36202946cb741e04 Mon Sep 17 00:00:00 2001 From: Simon Christmann Date: Sat, 24 Mar 2018 15:24:51 +0100 Subject: [PATCH 11/18] Rename option --- config.js | 2 +- index.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config.js b/config.js index 76cf228..2923803 100644 --- a/config.js +++ b/config.js @@ -17,7 +17,7 @@ module.exports = require('yargs') .describe('help', 'show help') .describe('publish-metadata', '') .describe('mqtt-retain', 'enable/disable retain flag for mqtt messages') - .describe('replace-colons', 'Replace colons (:) in topic name with underscores (_). Useful for OpenHAB compatibility.').boolean('replace-colons') + .describe('protocol-replace-colons', 'Replace colons (:) in topic name with underscores (_). Useful for OpenHAB compatibility.').boolean('protocol-replace-colons') .describe('insecure', 'allow tls connections with invalid certificates') .boolean('insecure') .alias({ diff --git a/index.js b/index.js index 23f07c6..61fb4cd 100755 --- a/index.js +++ b/index.js @@ -134,20 +134,20 @@ mqtt.on('message', (topic, payload) => { if (parts.length >= 4 && parts[1] === 'set') { // Topic /set// var channel = parts.slice(2, parts.length - 1).join('/'); - if ( config.replaceColons ) channel = channel.replace("_",":"); + if ( config.protocolReplaceColons ) channel = channel.replace("_",":"); const datapoint = parts[parts.length - 1]; rpcSet(channel, 'VALUES', datapoint, payload); } else if (parts.length >= 5 && parts[1] === 'param') { // Topic /param/// var channel = parts.slice(2, parts.length - 2).join('/'); - if ( config.replaceColons ) channel = channel.replace("_",":"); + if ( config.protocolReplaceColons ) channel = channel.replace("_",":"); const paramset = parts[parts.length - 2]; const datapoint = parts[parts.length - 1]; rpcPutParam(channel, paramset, datapoint, payload); } else if (parts.length >= 4 && parts[1] === 'paramset') { // Topic /paramset// var channel = parts.slice(2, parts.length - 1).join('/'); - if ( config.replaceColons ) channel = channel.replace("_",":"); + if ( config.protocolReplaceColons ) channel = channel.replace("_",":"); const paramset = parts[parts.length - 1]; rpcPutParamset(channel, paramset, payload); } else if (parts.length === 5 && parts[1] === 'rpc') { @@ -926,7 +926,7 @@ const rpcMethods = { ps = (ps && ps.VALUES && ps.VALUES[params[2]]) || {}; var channel = (names[params[1]] || params[1]); - if ( config.replaceColons ) channel = channel.replace(":","_"); + if ( config.protocolReplaceColons ) channel = channel.replace(":","_"); const topic = config.name + '/status/' + channel + '/' + params[2]; let payload = {val: params[3], ts, lc: changes[key], hm: {ADDRESS: params[1]}}; From e145d3f43bb7cb0dee982f67e1c9538dfd9fe840 Mon Sep 17 00:00:00 2001 From: Simon Christmann Date: Sat, 24 Mar 2018 15:30:04 +0100 Subject: [PATCH 12/18] Protocol option: Prefer Strings --- config.js | 1 + index.js | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/config.js b/config.js index 2923803..751ba4a 100644 --- a/config.js +++ b/config.js @@ -18,6 +18,7 @@ module.exports = require('yargs') .describe('publish-metadata', '') .describe('mqtt-retain', 'enable/disable retain flag for mqtt messages') .describe('protocol-replace-colons', 'Replace colons (:) in topic name with underscores (_). Useful for OpenHAB compatibility.').boolean('protocol-replace-colons') + .describe('protocol-prefer-strings', 'Disables the use of explicitDouble converstion and transmitts float values as string. Might put more load on the CCU.').boolean('protocol-prefer-strings') .describe('insecure', 'allow tls connections with invalid certificates') .boolean('insecure') .alias({ diff --git a/index.js b/index.js index 61fb4cd..1c98959 100755 --- a/index.js +++ b/index.js @@ -312,13 +312,29 @@ function rpcType(payload, paramset) { } else if (val > paramset.MAX) { val = paramset.MAX; } - val = {explicitDouble: val}; + if ( config.protocolPreferStrings ) { + /* JavaScript doesn't seperate integer and float/double types, so in JavaScript there's only "Number". + * However, the XML-RPC library needs to determine whether to wrap the value in 22 or 22, + * see [list of allowed datatypes](https://ws.apache.org/xmlrpc/types.html). + * + * node-xmlrpc does this with an `if ( value % 1 == 0)`, see [serializer.js:188](https://github.com/baalexander/node-xmlrpc/blob/d9c88c4185e16637ed5a22c1b91c80e958e8d69e/lib/serializer.js#L188) + * + * homematic-xmlrpc introduced an object like `{explicitDouble: val}`. + * + * The CCU2 seems to accept 22 messages and does a string to int/float conversion itself - currently in testing. + */ + val = String(val); + } else { + val = {explicitDouble: val}; + } break; case 'ENUM': - if (typeof val === 'string') { + if (typeof val === 'string' && !config.protocolPreferStrings ) { if (paramset.ENUM && (paramset.ENUM.indexOf(val) !== -1)) { val = paramset.ENUM.indexOf(val); } + } else { + // When message is already an integer, check if it's an allowed index } // eslint-disable-line no-fallthrough case 'INTEGER': From cd936212b7a502a82400d27bd8259a341aafdf45 Mon Sep 17 00:00:00 2001 From: Simon Christmann Date: Sat, 24 Mar 2018 15:50:07 +0100 Subject: [PATCH 13/18] Protocol option: Disable Value Checking --- config.js | 1 + index.js | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/config.js b/config.js index 751ba4a..ea7b8b2 100644 --- a/config.js +++ b/config.js @@ -19,6 +19,7 @@ module.exports = require('yargs') .describe('mqtt-retain', 'enable/disable retain flag for mqtt messages') .describe('protocol-replace-colons', 'Replace colons (:) in topic name with underscores (_). Useful for OpenHAB compatibility.').boolean('protocol-replace-colons') .describe('protocol-prefer-strings', 'Disables the use of explicitDouble converstion and transmitts float values as string. Might put more load on the CCU.').boolean('protocol-prefer-strings') + .describe('protocol-disable-value-checking', 'Disables the checking for unallowed values for e.g. in ENUM datapoints or the range of integer values. If set to true (disabled), all values will be sent to the CCU, the CCU then handles filtering/throwing error messages etc.').boolean('protocol-disable-value-checking') .describe('insecure', 'allow tls connections with invalid certificates') .boolean('insecure') .alias({ diff --git a/index.js b/index.js index 1c98959..8dfbaa9 100755 --- a/index.js +++ b/index.js @@ -307,10 +307,12 @@ function rpcType(payload, paramset) { break; case 'FLOAT': val = parseFloat(val); - if (val < paramset.MIN) { - val = paramset.MIN; - } else if (val > paramset.MAX) { - val = paramset.MAX; + if ( !config.protocolDisableValueChecking ) { + if (val < paramset.MIN) { + val = paramset.MIN; + } else if (val > paramset.MAX) { + val = paramset.MAX; + } } if ( config.protocolPreferStrings ) { /* JavaScript doesn't seperate integer and float/double types, so in JavaScript there's only "Number". @@ -339,10 +341,12 @@ function rpcType(payload, paramset) { // eslint-disable-line no-fallthrough case 'INTEGER': val = parseInt(val, 10); - if (val < paramset.MIN) { - val = paramset.MIN; - } else if (val > paramset.MAX) { - val = paramset.MAX; + if ( !config.protocolDisableValueChecking ) { + if (val < paramset.MIN) { + val = paramset.MIN; + } else if (val > paramset.MAX) { + val = paramset.MAX; + } } break; case 'STRING': From 3f5d7772c5d9ffeefae42180fb6734162a85be05 Mon Sep 17 00:00:00 2001 From: Simon Christmann Date: Sat, 24 Mar 2018 15:57:05 +0100 Subject: [PATCH 14/18] Protocol option: Publish ENUM as String --- config.js | 1 + index.js | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/config.js b/config.js index ea7b8b2..4b0ce09 100644 --- a/config.js +++ b/config.js @@ -20,6 +20,7 @@ module.exports = require('yargs') .describe('protocol-replace-colons', 'Replace colons (:) in topic name with underscores (_). Useful for OpenHAB compatibility.').boolean('protocol-replace-colons') .describe('protocol-prefer-strings', 'Disables the use of explicitDouble converstion and transmitts float values as string. Might put more load on the CCU.').boolean('protocol-prefer-strings') .describe('protocol-disable-value-checking', 'Disables the checking for unallowed values for e.g. in ENUM datapoints or the range of integer values. If set to true (disabled), all values will be sent to the CCU, the CCU then handles filtering/throwing error messages etc.').boolean('protocol-disable-value-checking') + .describe('protocol-publish-enum-as-string', 'Publishes ENUM datapoints with verbal string value instead of index, for e.g. thermostats "val":"MANU-MODE" instead of "val":1 (1 is the index of MANU-MODE in ENUM).').boolean('protocol-publish-enum-as-string') .describe('insecure', 'allow tls connections with invalid certificates') .boolean('insecure') .alias({ diff --git a/index.js b/index.js index 8dfbaa9..d6f7481 100755 --- a/index.js +++ b/index.js @@ -958,7 +958,11 @@ const rpcMethods = { } } if (ps.TYPE === 'ENUM') { - payload.hm.ENUM = ps.VALUE_LIST[params[3]]; + if ( config.protocolPublishEnumAsString ) { + payload.val = ps.VALUE_LIST[params[3]]; + } else { + payload.hm.ENUM = ps.VALUE_LIST[params[3]]; + } } payload = JSON.stringify(payload); From 492317eb428606fab14f51a71b41af9529da2902 Mon Sep 17 00:00:00 2001 From: Simon Christmann Date: Sat, 24 Mar 2018 15:59:57 +0100 Subject: [PATCH 15/18] Protocol option: Publish ENUM list --- config.js | 1 + index.js | 3 +++ 2 files changed, 4 insertions(+) diff --git a/config.js b/config.js index 4b0ce09..971ef84 100644 --- a/config.js +++ b/config.js @@ -21,6 +21,7 @@ module.exports = require('yargs') .describe('protocol-prefer-strings', 'Disables the use of explicitDouble converstion and transmitts float values as string. Might put more load on the CCU.').boolean('protocol-prefer-strings') .describe('protocol-disable-value-checking', 'Disables the checking for unallowed values for e.g. in ENUM datapoints or the range of integer values. If set to true (disabled), all values will be sent to the CCU, the CCU then handles filtering/throwing error messages etc.').boolean('protocol-disable-value-checking') .describe('protocol-publish-enum-as-string', 'Publishes ENUM datapoints with verbal string value instead of index, for e.g. thermostats "val":"MANU-MODE" instead of "val":1 (1 is the index of MANU-MODE in ENUM).').boolean('protocol-publish-enum-as-string') + .describe('protocol-publish-enum-list', 'Publishes complete VALUE_LIST for ENUM datapoints with every message. Might cause more load on MQTT broker.').boolean('protocol-publish-enum-list') .describe('insecure', 'allow tls connections with invalid certificates') .boolean('insecure') .alias({ diff --git a/index.js b/index.js index d6f7481..add2036 100755 --- a/index.js +++ b/index.js @@ -963,6 +963,9 @@ const rpcMethods = { } else { payload.hm.ENUM = ps.VALUE_LIST[params[3]]; } + if ( config.protocolPublishEnumList ) { + payload.hm.VALUE_LIST = ps.VALUE_LIST; + } } payload = JSON.stringify(payload); From 4bd1205cf438e85212fc71acda1da4c4317cfd8d Mon Sep 17 00:00:00 2001 From: Simon Christmann Date: Sat, 24 Mar 2018 16:09:42 +0100 Subject: [PATCH 16/18] Protocol option: Prefer xmlrpc for rfd --- config.js | 1 + index.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/config.js b/config.js index 971ef84..2ed45c9 100644 --- a/config.js +++ b/config.js @@ -22,6 +22,7 @@ module.exports = require('yargs') .describe('protocol-disable-value-checking', 'Disables the checking for unallowed values for e.g. in ENUM datapoints or the range of integer values. If set to true (disabled), all values will be sent to the CCU, the CCU then handles filtering/throwing error messages etc.').boolean('protocol-disable-value-checking') .describe('protocol-publish-enum-as-string', 'Publishes ENUM datapoints with verbal string value instead of index, for e.g. thermostats "val":"MANU-MODE" instead of "val":1 (1 is the index of MANU-MODE in ENUM).').boolean('protocol-publish-enum-as-string') .describe('protocol-publish-enum-list', 'Publishes complete VALUE_LIST for ENUM datapoints with every message. Might cause more load on MQTT broker.').boolean('protocol-publish-enum-list') + .describe('protocol-prefer-xmlrpc-for-rfd', 'Prefer xmlrpc instead of binrpc for rfd communications').boolean('protocol-prefer-xmlrpc-for-rfd') .describe('insecure', 'allow tls connections with invalid certificates') .boolean('insecure') .alias({ diff --git a/index.js b/index.js index add2036..6b35021 100755 --- a/index.js +++ b/index.js @@ -647,7 +647,7 @@ function getPrograms(cb) { log.debug('discover interfaces'); discover(config.ccuAddress, { // Todo... cuxd: {port: 8701, protocol: 'binrpc'}, - rfd: {port: 2001, protocol: 'binrpc'}, + rfd: {port: 2001, protocol: (config.protocolPreferXmlrpcForRfd ? 'xmlrpc' : 'binrpc') }, hs485d: {port: 2000, protocol: 'binrpc'}, hmip: {port: 2010, protocol: 'xmlrpc'} }, interfaces => { From 7a7fb59a196618f0fb7259c431b93fc58dd48e70 Mon Sep 17 00:00:00 2001 From: Simon Christmann Date: Sat, 31 Mar 2018 12:01:12 +0200 Subject: [PATCH 17/18] Protocol option: Publish val distinct --- config.js | 1 + index.js | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/config.js b/config.js index 2ed45c9..2454d35 100644 --- a/config.js +++ b/config.js @@ -23,6 +23,7 @@ module.exports = require('yargs') .describe('protocol-publish-enum-as-string', 'Publishes ENUM datapoints with verbal string value instead of index, for e.g. thermostats "val":"MANU-MODE" instead of "val":1 (1 is the index of MANU-MODE in ENUM).').boolean('protocol-publish-enum-as-string') .describe('protocol-publish-enum-list', 'Publishes complete VALUE_LIST for ENUM datapoints with every message. Might cause more load on MQTT broker.').boolean('protocol-publish-enum-list') .describe('protocol-prefer-xmlrpc-for-rfd', 'Prefer xmlrpc instead of binrpc for rfd communications').boolean('protocol-prefer-xmlrpc-for-rfd') + .describe('protocol-publish-val-distinct', 'Publish just the value, no JSON object').boolean('protocol-publish-val-distinct') .describe('insecure', 'allow tls connections with invalid certificates') .boolean('insecure') .alias({ diff --git a/index.js b/index.js index 6b35021..b78a7ed 100755 --- a/index.js +++ b/index.js @@ -967,7 +967,11 @@ const rpcMethods = { payload.hm.VALUE_LIST = ps.VALUE_LIST; } } - payload = JSON.stringify(payload); + if (config.protocolPublishValDistinct) { + payload = String(payload.val); + } else { + payload = JSON.stringify(payload); + } const retain = (config.mqttRetain) && (ps.TYPE !== 'ACTION'); From 1b9340511def3b09e7ca084f2d5b5ebb87fa9f7c Mon Sep 17 00:00:00 2001 From: Simon Christmann Date: Wed, 3 Oct 2018 16:25:56 +0200 Subject: [PATCH 18/18] README --- README.md | 156 +++++++----------------------------------------------- 1 file changed, 18 insertions(+), 138 deletions(-) diff --git a/README.md b/README.md index fb1e220..f1bccd9 100644 --- a/README.md +++ b/README.md @@ -1,153 +1,33 @@ # hm2mqtt.js -[![mqtt-smarthome](https://img.shields.io/badge/mqtt-smarthome-blue.svg)](https://github.com/mqtt-smarthome/mqtt-smarthome) -[![NPM version](https://badge.fury.io/js/hm2mqtt.svg)](http://badge.fury.io/js/hm2mqtt) -[![dependencies Status](https://david-dm.org/hobbyquaker/hm2mqtt.js/status.svg)](https://david-dm.org/hobbyquaker/hm2mqtt.js) -[![Build Status](https://travis-ci.org/hobbyquaker/hm2mqtt.js.svg?branch=master)](https://travis-ci.org/hobbyquaker/hm2mqtt.js) -[![Coverage Status](https://coveralls.io/repos/github/hobbyquaker/hm2mqtt.js/badge.svg?branch=master)](https://coveralls.io/github/hobbyquaker/hm2mqtt.js?branch=master) -[![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo) -[![License][mit-badge]][mit-url] +#### History of this project -> Node.js based Interface between Homematic and MQTT +It started off with owagner's Java-based [hm2mqtt](https://github.com/owagner/hm2mqtt). Hobbyquaker then created [hm2mqtt.js](https://github.com/hobbyquaker/hm2mqtt.js) (JavaScript) providing the same functionality as hm2mqtt. He gave up the development in favor his new projects [node-red-contrib-ccu](https://github.com/hobbyquaker/node-red-contrib-ccu) and [RedMatic](https://github.com/hobbyquaker/RedMatic). -# UNMAINTAINED - I gave up working on this project in favor of [node-red-contrib-ccu](https://github.com/hobbyquaker/node-red-contrib-ccu) respectively the CCU3/RaspberryMatic Addon [RedMatic](https://github.com/hobbyquaker/RedMatic) that includes node-red-contrib-ccu. The functionality of hm2mqtt.js will be implemented in node-red-contrib-ccu so this can act as a 1:1 drop-in-replacement. +I forked the project some time ago and added a few features to it. This project however got a bit too inflated for my taste, thus I'm also working on a very simple approach, implementing only rfd. No rega, no paramset database, just sending and receiving messages: [simple homematic rfd](https://github.com/dersimn/simplehomematicrfd2mqtt). -Because [hm2mqtt](https://github.com/owagner/hm2mqtt) isn't developed anymore and I don't really like Java I decided to -re-implement this with Node.js -It's kind of the same like the original hm2mqtt, but it supports BINRPC and XMLRPC (hm2mqtt only supports BINRPC), so it -can be used with Homematic IP also. Furthermore it supports Rega variables and programs. +## Usage with Docker +Run -### Installation + docker run dersimn/hm2mqtt.js --help -Prerequisites: [Node.js](https://nodejs.org) 6.0 or higher. +for a full list of options. A full command could look like this: -`npm install -g hm2mqtt` + docker run -d --restart=always --name=hm \ + -p 2126:2126 -p 2127:2127 \ + dersimn/hm2mqtt.js \ + --mqtt-url mqtt://10.1.1.50 \ + --ccu-address 10.1.1.112 \ + --disable-rega \ + --init-address 10.1.1.50 \ + --protocol-prefer-strings \ + --protocol-disable-value-checking \ + --protocol-publish-enum-as-string \ + --protocol-prefer-xmlrpc-for-rfd -I suggest to use [pm2](http://pm2.keymetrics.io/) to manage the hm2mqtt process (start on system boot, manage log files, -...) - - -### Command Line Options - -Use `hm2mqtt --help` to get a list of available options. All options can also be set per environment variable (e.g. -setting `HM2MQTT_VERBOSITY=debug` has the same effect as using `--verbosity debug` as commandline parameter). - -### MQTT URL - -You can add Username/Password for the connection to the MQTT broker to the MQTT URL param like e.g. -`mqtt://user:pass@broker`. For a secure connection via TLS use `mqtts://` as URL scheme. - - -### Topics - -* Events are published on `/status//` (JSON payload, follows -[mqtt-smarthome payload format](https://github.com/mqtt-smarthome/mqtt-smarthome/blob/master/Architecture.md)) -* Values can be set via `/set//` (can be plain or JSON payload). Example: -`hmip/set/Light_Garage/STATE`, -* Single values from arbitrary Paramsets can be set via -`/param///`. Example topic for setting the Mode of an 1st gen -Thermostat HM-CC-TC: `hm/param/Temperatur Hobbyraum Soll/MASTER/MODE_TEMPERATUR_REGULATOR` -* Multiple values at once in arbitrary Paramsets can be set via ` -``/param//`. The payload has to be a JSON object like e.g. -`{"MODE_TEMPERATURE_REGULATOR":2,"TEMPERATUR_COMFORT_VALUE":24}`. -* Arbitrary RPC methods can be called via `/rpc///` and respond to `/response/` -(JSON encoded Array as payload). The callId can be an arbitrary string, its purpose is just to collate the response -to the command. iface can be one of `hmip`, `rfd` or `hs485d`. - - -### Device and Channel Names - -Device and Channel names are queried from ReGa, this can be disabled by setting the `--disable-rega` option. To trigger -a re-read after changes on the ReGa you can publish a message to `/command/regasync` or just restart hm2mqtt. -As an alternative to using the names from ReGa you can also supply a json file with the `--json-name-table` option -containing address to name mappings, created by e.g. -[homematic-manager](https://github.com/hobbyquaker/homematic-manager). This file should look like: -```javascript -{ - "EEQ1234567": "Device Name", - "EEQ1234567:1": "Channel Name", - ... -} -``` - - -### ReGa (Homematic variables and programs) - -To receive changes from ReGa you have to set `--rega-poll-interval` and/or `--rega-poll-trigger`. -`--rega-poll-trigger` can be set to e.g. `BidCoS-RF:50.PRESS_SHORT`, then a polling is done whenever this virtual button -is pressed. This is meant to create a "pseudo push mechanism" where a program on the ccu reacts on variable changes and -presses this virtual button. - -Variables and Programs are published to `/status/` and can be set by sending a message to -`/rega/`. Publishing `true` or `false` to a program activates/deactivates the program. To -start a program publish the string `start`. - - -### _NOTWORKING datapoints - -hm2mqtt sends virtual datapoints named `LEVEL_NOTWORKING` respectively `STATE_NOTWORKING` for actuators that have a -`WORKING` and/or `DIRECTION` datapoint. The `*_NOTWORKING` datapoints are only updated when `WORKING` is `false` - this -is useful for e.g. sliders in a UI to prevent jumping sliders when a Blind or Keymatic is moving or a Dimmer is dimming. - - -## Docker - -#### Usage (architecture: amd64) -- pull the image to your machine, or if you are on a swarm to each node -``` -docker pull mqttsmarthome/hm2mqtt:latest -``` -- start the container with (e.g) -``` -docker run -d -p 2126:2126 -p 2127:2127 --name hm2mqtt -e HM2MQTT_MQTT-URL="mqtt://xxx.xxx.xxx.xxx" -e HM2MQTT_MQTT-USERNAME="mqtt-user-name" -e HM2MQTT_MQTT-PASSWORD="mqtt-user-password" -e HM2MQTT_CCU-ADDRESS="xxx.xxx.xxx.xxx" -e HM2MQTT_INIT-ADDRESS="xxx.xxx.xxx.xxx" -e HM2MQTT_VERBOSITY="debug" mqttsmarthome/hm2mqtt -``` -- or the service in your swarm with (e.g) -``` -docker service create --name hm2mqtt \ ---network ingress \ ---publish 2126:2126 \ ---publish 2127:2127 \ ---env HM2MQTT_MQTT-URL="mqtt://xxx.xxx.xxx.xxx" \ ---env HM2MQTT_CCU-ADDRESS="xxx.xxx.xxx.xxx" \ ---env HM2MQTT_INIT-ADDRESS="xxx.xxx.xxx.xxx" \ ---env HM2MQTT_VERBOSITY="debug" \ -mqttsmarthome/hm2mqtt -``` - -#### Usage (architecture: armhf) -- pull the image to your machine, or if you are on a swarm to each node -``` -docker pull mqttsmarthome/hm2mqtt:armhf -``` -- follow the description above (architecture: amd64), but leave out the pull sequence mentioned there. - -### Developing with Docker - - git clone hm2mqtt - cd hm2mqtt - -#### Build for x86 - - docker build -t hm2mqtt-dev . - -#### Build for armhf (Raspberry Pi) - - docker build -t hm2mqtt-dev-armhf -f Dockerfile.armhf . - -You can also do a build for armhf with »Docker for Mac« or »Docker for Windows« even if you are on a x86 architecture. In this case you can copy over the built image via - - docker save hm2mqtt-dev-armhf | gzip > hm2mqtt-dev-armhf.tar.gz - gunzip -c hm2mqtt-dev-armhf.tar.gz | docker load - -or directly via `ssh`: - - docker save hm2mqtt-dev-armhf | gzip | pv | ssh root@hma-pi.lan 'gunzip | docker load' ## License MIT (c) 2017 [Sebastian Raff](https://github.com/hobbyquaker) - -[mit-badge]: https://img.shields.io/badge/License-MIT-blue.svg?style=flat -[mit-url]: LICENSE