Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
FROM node:slim
LABEL maintainer="Holger Imbery <contact@connectedobjects.cloud>" \
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

RUN cd /node && \
npm install

EXPOSE 2126
EXPOSE 2127
ENTRYPOINT ["hm2mqtt"]

ENTRYPOINT [ "node", "/node/index.js" ]
14 changes: 7 additions & 7 deletions Dockerfile.armhf
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
FROM hypriot/rpi-node:slim
LABEL maintainer="Holger Imbery <contact@connectedobjects.cloud>" \
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

RUN cd /node && \
npm install

EXPOSE 2126
EXPOSE 2127
ENTRYPOINT ["hm2mqtt"]

ENTRYPOINT [ "node", "/node/index.js" ]
4 changes: 3 additions & 1 deletion config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -41,7 +42,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,
Expand Down
69 changes: 39 additions & 30 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,14 @@ mqtt.on('message', (topic, payload) => {
const parts = topic.split('/');
if (parts.length >= 4 && parts[1] === 'set') {
// Topic <name>/set/<channel>/<datapoint>
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 <name>/paramset/<channel>/<paramset>/<datapoint>
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);
Expand Down Expand Up @@ -295,27 +297,20 @@ 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;
}
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 <i4>22</i$> or <double>22</double>,
* 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 <string>22</string> messages and does a string to int/float conversion itself - currently in testing.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... I'm asking myself if this works with the binrpc protocol also...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good question, because I never checked.
Funny thing is - it does 😄

root@vserver:~# docker logs hm2mqtt
2017-12-21 13:05:05.161 <info>  hm2mqtt 2.3.0 starting
2017-12-21 13:05:05.164 <info>  mqtt trying to connect mqtt://10.1.1.50
2017-12-21 13:05:05.243 <info>  mqtt connected mqtt://10.1.1.50
2017-12-21 13:05:05.244 <info>  mqtt subscribe hm/set/#
2017-12-21 13:05:05.245 <info>  mqtt subscribe hm/paramset/#
2017-12-21 13:05:05.245 <info>  mqtt subscribe hm/rega/#
2017-12-21 13:05:05.245 <info>  mqtt subscribe hm/rpc/#
2017-12-21 13:05:05.245 <info>  mqtt subscribe hm/command/#
2017-12-21 13:05:05.257 <info>  rpc rfd > init [ 'xmlrpc_bin://10.1.1.50:2127', 'hm2mqtt_rfd' ]
2017-12-21 13:05:05.261 <info>  rpc hmip > init [ 'http://10.1.1.50:2126', 'hm2mqtt_hmip' ]
2017-12-21 13:05:05.750 <error> unknown device hm2mqtt_rfd LEQ1519968:0
2017-12-21 13:05:05.750 <error> unknown device hm2mqtt_rfd NEQ0629891:0
2017-12-21 13:05:05.751 <error> unknown device hm2mqtt_rfd NEQ0879789:0
2017-12-21 13:05:05.751 <error> unknown device hm2mqtt_rfd NEQ0880952:0
2017-12-21 13:05:05.751 <error> unknown device hm2mqtt_rfd NEQ0881133:0
2017-12-21 13:05:10.588 <error> createParamsetQueue called for unknown devices hmip
2017-12-21 13:05:10.962 <info>  rfd got 135 devices and channels
2017-12-21 13:05:34.504 <error> unknown paramsetDescription HM-TC-IT-WM-W-EU/22/THERMALCONTROL_TRANSMIT
2017-12-21 13:05:34.505 <error> unknown paramsetDescription HM-TC-IT-WM-W-EU/22/THERMALCONTROL_TRANSMIT
2017-12-21 13:05:34.505 <error> unknown paramsetDescription HM-TC-IT-WM-W-EU/22/THERMALCONTROL_TRANSMIT
2017-12-21 13:05:40.419 <info>  got 22 paramsetDescriptions
2017-12-21 13:15:05.294 <info>  rpc hmip > init [ 'http://10.1.1.50:2126', 'hm2mqtt_hmip' ]
2017-12-21 13:15:10.603 <error> createParamsetQueue called for unknown devices hmip
2017-12-21 13:25:05.320 <info>  rpc hmip > init [ 'http://10.1.1.50:2126', 'hm2mqtt_hmip' ]
2017-12-21 13:25:10.637 <error> createParamsetQueue called for unknown devices hmip
2017-12-21 13:35:05.328 <info>  rpc hmip > init [ 'http://10.1.1.50:2126', 'hm2mqtt_hmip' ]

I started the script a few weeks ago with my most recent branch. It used xmlrpc_bin as protocol and yeah.. works since then. For e.g. hm/set/NEQ0881133_4/SET_TEMPERATURE accepts either 19 and 19.5 doing the string conversion.

*/
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) {
val = paramset.MIN;
} else if (val > paramset.MAX) {
val = paramset.MAX;
}
break;
case 'STRING':
val = String(val);
Expand Down Expand Up @@ -357,12 +352,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) {
Expand Down Expand Up @@ -635,9 +641,9 @@ process.on('SIGTERM', stop);
function initIface(name, protocol) {
let url;
if (protocol === 'binrpc') {
url = 'xmlrpc_bin://' + (config.initAddress || config.listenAddress) + ':' + config.binrpcListenPort;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why remove this? listenAdress is needed if hm2mqtt runs in a vm with nat networking or in a container that exposes the listenPort to the hosts interface

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's actually the other way around. In a Docker environment you can and will have multiple network interfaces. It's therefore a good idea to bind the socket to 0.0.0.0 a.k.a. "listen on all interfaces". This happens here in your script.

The line from this comment creates the url-string that is sent to the CCU. If you don't specify the --init-address it will default to the information form your firstip.js script, otherwise (for e.g. NAT-ed Docker) the user has to provide the real external IP and take care of proper port forwarding.

You can leave the line in here but combined with the change from line 44, this makes no semantical sense anymore.

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);
Expand Down Expand Up @@ -831,7 +837,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 !== '""') {
Expand All @@ -842,7 +850,8 @@ const rpcMethods = {
}
}
if (ps.TYPE === 'ENUM') {
payload.hm.ENUM = ps.VALUE_LIST[params[3]];
payload.val = ps.VALUE_LIST[params[3]];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a change that would break a few things in my scripts and in my ui... hmmm... :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought so, how about:

--protocol-option-replace-colons for this
--protocol-option-direct-enums

payload.hm.VALUE_LIST = ps.VALUE_LIST;
}
payload = JSON.stringify(payload);

Expand Down