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/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
- const topic = this.ccu.topicReplace(this.topicOutputEvent, message);
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(message);
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.ccu.setValue(iface, filter.channel, filter.datapoint, payload).catch(() => {});
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) {