node-red-contrib-ccu 4.1.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/HANDOFF.md +58 -15
- package/README.md +13 -0
- package/ROADMAP.md +24 -21
- package/examples/home-assistant.json +127 -0
- package/nodes/ccu-connection.js +37 -2
- package/nodes/ccu-homeassistant.html +334 -0
- package/nodes/ccu-homeassistant.js +272 -0
- package/nodes/ccu-mqtt.js +62 -3
- package/nodes/lib/hadiscovery.js +846 -0
- package/nodes/lib/haroles.js +193 -0
- package/package.json +2 -1
- package/roadmap-archive/B-16.md +31 -0
package/nodes/ccu-mqtt.js
CHANGED
|
@@ -3,6 +3,18 @@ const mw = require('mqtt-wildcard');
|
|
|
3
3
|
|
|
4
4
|
const statusHelper = require(path.join(__dirname, '/lib/status.js'));
|
|
5
5
|
|
|
6
|
+
/** HM thermostats: CONTROL_MODE is read-only, the modes are set through actions */
|
|
7
|
+
const HM_MODES = {
|
|
8
|
+
'AUTO-MODE': () => ['AUTO_MODE', true],
|
|
9
|
+
'MANU-MODE': (setpoint) => ['MANU_MODE', setpoint],
|
|
10
|
+
'BOOST-MODE': () => ['BOOST_MODE', true],
|
|
11
|
+
'COMFORT-MODE': () => ['COMFORT_MODE', true],
|
|
12
|
+
'LOWERING-MODE': () => ['LOWERING_MODE', true],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** Words Home Assistant sends to LEVEL; 1.005 is the actuators' "old level" value */
|
|
16
|
+
const LEVEL_WORDS = {OPEN: 1, CLOSE: 0, ON: 1.005, OFF: 0};
|
|
17
|
+
|
|
6
18
|
module.exports = function (RED) {
|
|
7
19
|
class CcuMqttNode {
|
|
8
20
|
constructor(config) {
|
|
@@ -98,12 +110,15 @@ module.exports = function (RED) {
|
|
|
98
110
|
}
|
|
99
111
|
|
|
100
112
|
event(message) {
|
|
101
|
-
|
|
113
|
+
// channels without a ReGa name used to produce hm/status//STATE-style
|
|
114
|
+
// topics — fall back to the address (B-3)
|
|
115
|
+
const topicMessage = message.channelName ? message : {...message, channelName: message.channel};
|
|
116
|
+
const topic = this.ccu.topicReplace(this.topicOutputEvent, topicMessage);
|
|
102
117
|
const retain = !(message.datapoint && message.datapoint.startsWith('PRESS_'));
|
|
103
118
|
this.send({topic, payload: this.output(message), retain});
|
|
104
119
|
|
|
105
120
|
if (['LEVEL', 'STATE'].includes(message.datapoint) && message.working === false) {
|
|
106
|
-
const messageNotWorking = RED.util.cloneMessage(
|
|
121
|
+
const messageNotWorking = RED.util.cloneMessage(topicMessage);
|
|
107
122
|
messageNotWorking.datapoint += '_NOTWORKING';
|
|
108
123
|
messageNotWorking.datapointName += '_NOTWORKING';
|
|
109
124
|
this.send({
|
|
@@ -231,7 +246,51 @@ module.exports = function (RED) {
|
|
|
231
246
|
return;
|
|
232
247
|
}
|
|
233
248
|
|
|
234
|
-
this.
|
|
249
|
+
const command = this.translateCommand(iface, filter.channel, filter.datapoint, payload);
|
|
250
|
+
this.ccu.setValue(iface, filter.channel, command.datapoint, command.payload).catch(() => {});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Words Home Assistant's single-topic conventions send (see the
|
|
255
|
+
* ccu-homeassistant node): LEVEL accepts OPEN, CLOSE, STOP, ON (restore the
|
|
256
|
+
* last level) and OFF; HM thermostats accept the CONTROL_MODE names
|
|
257
|
+
* (AUTO-MODE, MANU-MODE, BOOST-MODE, COMFORT-MODE, LOWERING-MODE), which
|
|
258
|
+
* are translated to the *_MODE actions because CONTROL_MODE itself is
|
|
259
|
+
* read-only there. Everything else passes through unchanged.
|
|
260
|
+
* @returns {{datapoint: string, payload: *}}
|
|
261
|
+
*/
|
|
262
|
+
translateCommand(iface, channel, datapoint, payload) {
|
|
263
|
+
if (typeof payload !== 'string') {
|
|
264
|
+
return {datapoint, payload};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const word = payload.trim().toUpperCase();
|
|
268
|
+
const device = this.ccu.metadata.devices[iface] && this.ccu.metadata.devices[iface][channel];
|
|
269
|
+
const description = (device && this.ccu.getParamsetDescription(iface, device, 'VALUES')) || {};
|
|
270
|
+
|
|
271
|
+
if (datapoint === 'LEVEL') {
|
|
272
|
+
if (word === 'STOP' && description.STOP) {
|
|
273
|
+
return {datapoint: 'STOP', payload: true};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (word in LEVEL_WORDS) {
|
|
277
|
+
return {datapoint, payload: LEVEL_WORDS[word]};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (
|
|
282
|
+
datapoint === 'CONTROL_MODE' &&
|
|
283
|
+
HM_MODES[word] &&
|
|
284
|
+
description.CONTROL_MODE &&
|
|
285
|
+
!(description.CONTROL_MODE.OPERATIONS & 2)
|
|
286
|
+
) {
|
|
287
|
+
const current = this.ccu.values[iface + '.' + channel + '.SET_TEMPERATURE'];
|
|
288
|
+
const setpoint = current && typeof current.value === 'number' ? current.value : 20;
|
|
289
|
+
const [dp, value] = HM_MODES[word](setpoint);
|
|
290
|
+
return {datapoint: dp, payload: value};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return {datapoint, payload};
|
|
235
294
|
}
|
|
236
295
|
|
|
237
296
|
sysvar(filter, payload) {
|