node-red-contrib-ccu 3.4.2 → 4.0.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.
Files changed (49) hide show
  1. package/.prettierignore +10 -0
  2. package/.prettierrc +14 -0
  3. package/AGENTS.md +96 -0
  4. package/CHANGELOG.md +66 -0
  5. package/README.md +17 -21
  6. package/ROADMAP.md +402 -0
  7. package/eslint.config.js +45 -0
  8. package/examples/rpc-events.json +65 -0
  9. package/examples/switch-datapoint.json +86 -0
  10. package/examples/system-variable.json +60 -0
  11. package/nodes/ccu-alexa.html +12 -7
  12. package/nodes/ccu-alexa.js +33 -14
  13. package/nodes/ccu-connection.html +9 -1
  14. package/nodes/ccu-connection.js +588 -335
  15. package/nodes/ccu-display.html +11 -4
  16. package/nodes/ccu-display.js +26 -9
  17. package/nodes/ccu-get-value.html +13 -4
  18. package/nodes/ccu-get-value.js +2 -2
  19. package/nodes/ccu-mqtt.html +9 -10
  20. package/nodes/ccu-mqtt.js +55 -29
  21. package/nodes/ccu-program.html +1 -1
  22. package/nodes/ccu-program.js +9 -7
  23. package/nodes/ccu-rpc-event.html +12 -7
  24. package/nodes/ccu-rpc-event.js +4 -5
  25. package/nodes/ccu-rpc.html +12 -5
  26. package/nodes/ccu-rpc.js +8 -6
  27. package/nodes/ccu-script.html +1 -1
  28. package/nodes/ccu-script.js +4 -3
  29. package/nodes/ccu-set-value.html +11 -7
  30. package/nodes/ccu-set-value.js +40 -16
  31. package/nodes/ccu-signal.html +11 -5
  32. package/nodes/ccu-signal.js +34 -23
  33. package/nodes/ccu-switch.html +13 -5
  34. package/nodes/ccu-sysvar.html +1 -1
  35. package/nodes/ccu-sysvar.js +11 -7
  36. package/nodes/ccu-value.html +12 -7
  37. package/nodes/ccu-value.js +36 -13
  38. package/nodes/lib/base62.js +22 -0
  39. package/nodes/lib/discover.js +127 -0
  40. package/nodes/lib/nextport.js +30 -0
  41. package/nodes/lib/similarity.js +51 -0
  42. package/nodes/lib/status.js +1 -1
  43. package/package.json +105 -155
  44. package/roadmap-archive/README.md +20 -0
  45. package/roadmap-archive/phase-1.md +48 -0
  46. package/roadmap-archive/todo-history.md +39 -0
  47. package/TODO.md +0 -41
  48. package/ccu_rega_localhost.json +0 -1
  49. package/ccu_values_localhost.json +0 -1
@@ -4,15 +4,11 @@ const path = require('path');
4
4
  const fs = require('fs');
5
5
  const crypto = require('crypto');
6
6
 
7
- const promiseFinally = require('promise.prototype.finally');
8
-
9
- promiseFinally.shim();
10
-
11
- const base62 = require('buffer-base62').toBase62;
12
- const stringSimilarity = require('string-similarity');
13
- const nextport = require('nextport');
14
- const hmDiscover = require('hm-discover');
15
- const Rega = require('homematic-rega');
7
+ const base62 = require('./lib/base62.js').toBase62;
8
+ const {bestMatch} = require('./lib/similarity.js');
9
+ const nextport = require('./lib/nextport.js');
10
+ const hmDiscover = require('./lib/discover.js');
11
+ const {Rega} = require('homematic-rega'); // ES module - require(esm) needs Node >= 20.19 / >= 22.12
16
12
  const xmlrpc = require('homematic-xmlrpc');
17
13
  const binrpc = require('binrpc');
18
14
 
@@ -24,8 +20,24 @@ const pkg = require(path.join(__dirname, '..', 'package.json'));
24
20
  * @param obj
25
21
  * @returns {boolean}
26
22
  */
23
+ /* homematic-rega 2.x has a promise API; these adapters feed the existing
24
+ callback-style call sites. execToCallback maps exec()'s {output, objects}
25
+ to the old (err, output, objects) signature. */
26
+ function toCallback(promise, callback) {
27
+ promise.then(
28
+ (res) => callback(null, res),
29
+ (err) => callback(err),
30
+ );
31
+ }
32
+
33
+ function execToCallback(promise, callback) {
34
+ promise.then(
35
+ ({output, objects}) => callback(null, output, objects),
36
+ (err) => callback(err),
37
+ );
38
+ }
39
+
27
40
  function isIterable(object) {
28
- // eslint-disable-next-line eqeqeq, no-eq-null
29
41
  return object != null && typeof object[Symbol.iterator] === 'function' && typeof object.forEach === 'function';
30
42
  }
31
43
 
@@ -41,7 +53,7 @@ module.exports = function (RED) {
41
53
  */
42
54
  function findport(start) {
43
55
  return new Promise((resolve, reject) => {
44
- nextport(start, port => {
56
+ nextport(start, (port) => {
45
57
  if (port) {
46
58
  ccu.network.ports.push(port);
47
59
  resolve();
@@ -52,13 +64,13 @@ module.exports = function (RED) {
52
64
  });
53
65
  }
54
66
 
55
- hmDiscover(res => {
67
+ hmDiscover((res) => {
56
68
  ccu.network.discover = res;
57
69
  });
58
70
 
59
71
  const networkInterfaces = os.networkInterfaces();
60
- Object.keys(networkInterfaces).forEach(name => {
61
- networkInterfaces[name].forEach(addr => {
72
+ Object.keys(networkInterfaces).forEach((name) => {
73
+ networkInterfaces[name].forEach((addr) => {
62
74
  if (addr.family === 'IPv4') {
63
75
  ccu.network.listen.push(addr.address);
64
76
  }
@@ -78,10 +90,10 @@ module.exports = function (RED) {
78
90
 
79
91
  switch (request.query.type) {
80
92
  case 'ifaces': {
81
- Object.keys(config.ifaceTypes).forEach(iface => {
93
+ Object.keys(config.ifaceTypes).forEach((iface) => {
82
94
  object[iface] = {
83
95
  enabled: Boolean(config.ifaceTypes[iface].enabled),
84
- connected: Boolean(config.ifaceStatus[iface])
96
+ connected: Boolean(config.ifaceStatus[iface]),
85
97
  };
86
98
  });
87
99
  res.status(200).send(JSON.stringify(object));
@@ -91,14 +103,14 @@ module.exports = function (RED) {
91
103
  case 'channels': {
92
104
  const devices = config.metadata.devices[request.query.iface];
93
105
  if (devices) {
94
- Object.keys(devices).forEach(addr => {
106
+ Object.keys(devices).forEach((addr) => {
95
107
  if (addr.match(/:\d+$/)) {
96
108
  const psKey = config.paramsetName(request.query.iface, devices[addr], 'VALUES');
97
109
  if (config.paramsetDescriptions[psKey]) {
98
110
  object[addr] = {
99
111
  name: config.channelNames[addr],
100
112
  datapoints: Object.keys(config.paramsetDescriptions[psKey]),
101
- rxMode: devices[devices[addr].PARENT] && devices[devices[addr].PARENT].RX_MODE
113
+ rxMode: devices[devices[addr].PARENT] && devices[devices[addr].PARENT].RX_MODE,
102
114
  };
103
115
  }
104
116
  }
@@ -115,7 +127,7 @@ module.exports = function (RED) {
115
127
  return;
116
128
  }
117
129
 
118
- Object.keys(devices).forEach(addr => {
130
+ Object.keys(devices).forEach((addr) => {
119
131
  if (addr.match(/:\d+$/)) {
120
132
  const psKey = config.paramsetName(iface, devices[addr], 'VALUES');
121
133
  if (config.paramsetDescriptions[psKey]) {
@@ -123,26 +135,26 @@ module.exports = function (RED) {
123
135
  const dps = [];
124
136
  const chName = config.channelNames[addr];
125
137
 
126
- Object.keys(config.paramsetDescriptions[psKey]).forEach(dp => {
138
+ Object.keys(config.paramsetDescriptions[psKey]).forEach((dp) => {
127
139
  dps.push({
128
140
  id: iface + '.' + addr + '.' + dp,
129
141
  iface,
130
- channel: (chName) ? addr + ' ' + chName : addr,
142
+ channel: chName ? addr + ' ' + chName : addr,
131
143
  label: dp,
132
144
  icon: 'fa fa-tag fa-fw',
133
- class: request.query.classDp
145
+ class: request.query.classDp,
134
146
  });
135
147
  });
136
148
  dps.sort((a, b) => a.label.localeCompare(b.label));
137
149
  const channel = {
138
150
  id: iface + '.' + addr,
139
151
  iface,
140
- label: (chName) ? chName + ' (' + addr + ')' : addr,
152
+ label: chName ? chName + ' (' + addr + ')' : addr,
141
153
  children: dps,
142
154
  rooms: config.channelRooms[addr],
143
155
  functions: config.channelFunctions[addr],
144
156
  icon: 'fa fa-tags fa-fw',
145
- class: request.query.classCh
157
+ class: request.query.classCh,
146
158
  };
147
159
  callback(iface, devID, channel, addr);
148
160
  }
@@ -161,12 +173,14 @@ module.exports = function (RED) {
161
173
  object[devID] = {
162
174
  id: iface + '.' + devID,
163
175
  name: config.channelNames[devID],
164
- label: (config.channelNames[devID]) ? config.channelNames[devID] + ' (' + devID + ')' : devID,
176
+ label: config.channelNames[devID]
177
+ ? config.channelNames[devID] + ' (' + devID + ')'
178
+ : devID,
165
179
  type: devices[devID].TYPE,
166
180
  iface,
167
181
  icon: 'fa fa-archive fa-fw',
168
182
  channels: {},
169
- children: []
183
+ children: [],
170
184
  };
171
185
  }
172
186
 
@@ -174,7 +188,7 @@ module.exports = function (RED) {
174
188
  object[devID].children.push(channel);
175
189
  });
176
190
  } else {
177
- Object.keys(config.metadata.devices).forEach(iface => {
191
+ Object.keys(config.metadata.devices).forEach((iface) => {
178
192
  const devices = config.metadata.devices[iface];
179
193
  processChannels(iface, devices, (iface, devID, channel, chID) => {
180
194
  if (!channel.children || channel.children.length === 0) {
@@ -191,15 +205,19 @@ module.exports = function (RED) {
191
205
  }
192
206
 
193
207
  case 'rooms':
194
- res.status(200).send(JSON.stringify({
195
- rooms: config.rooms
196
- }));
208
+ res.status(200).send(
209
+ JSON.stringify({
210
+ rooms: config.rooms,
211
+ }),
212
+ );
197
213
  break;
198
214
 
199
215
  case 'functions':
200
- res.status(200).send(JSON.stringify({
201
- functions: config.functions
202
- }));
216
+ res.status(200).send(
217
+ JSON.stringify({
218
+ functions: config.functions,
219
+ }),
220
+ );
203
221
  break;
204
222
 
205
223
  case 'sysvar':
@@ -213,30 +231,29 @@ module.exports = function (RED) {
213
231
  case 'signal': {
214
232
  const devices = config.metadata.devices[request.query.iface];
215
233
  if (devices) {
216
- Object.keys(devices).forEach(addr => {
217
- if ([
218
- 'SIGNAL_CHIME',
219
- 'SIGNAL_LED',
220
- 'ALARM_SWITCH_VIRTUAL_RECEIVER'
221
- ].includes(devices[addr].TYPE)) {
234
+ Object.keys(devices).forEach((addr) => {
235
+ if (
236
+ ['SIGNAL_CHIME', 'SIGNAL_LED', 'ALARM_SWITCH_VIRTUAL_RECEIVER'].includes(
237
+ devices[addr].TYPE,
238
+ )
239
+ ) {
222
240
  object[addr] = {
223
241
  name: config.channelNames[addr],
224
242
  type: devices[addr].TYPE,
225
- deviceType: devices[addr].PARENT_TYPE
243
+ deviceType: devices[addr].PARENT_TYPE,
226
244
  };
227
245
  }
228
246
 
229
- if ([
230
- 'HmIP-MP3P',
231
- 'HmIP-BSL'
232
- ].includes(devices[addr].PARENT_TYPE) && [
233
- 'ACOUSTIC_SIGNAL_VIRTUAL_RECEIVER',
234
- 'DIMMER_VIRTUAL_RECEIVER'
235
- ].includes(devices[addr].TYPE)) {
247
+ if (
248
+ ['HmIP-MP3P', 'HmIP-BSL'].includes(devices[addr].PARENT_TYPE) &&
249
+ ['ACOUSTIC_SIGNAL_VIRTUAL_RECEIVER', 'DIMMER_VIRTUAL_RECEIVER'].includes(
250
+ devices[addr].TYPE,
251
+ )
252
+ ) {
236
253
  object[addr] = {
237
254
  name: config.channelNames[addr],
238
255
  type: devices[addr].TYPE,
239
- deviceType: devices[addr].PARENT_TYPE
256
+ deviceType: devices[addr].PARENT_TYPE,
240
257
  };
241
258
  }
242
259
  });
@@ -249,14 +266,15 @@ module.exports = function (RED) {
249
266
  case 'display': {
250
267
  const devices = config.metadata.devices[request.query.iface];
251
268
  if (devices) {
252
- Object.keys(devices).forEach(addr => {
269
+ Object.keys(devices).forEach((addr) => {
253
270
  if (
254
271
  (addr.endsWith(':3') && devices[addr].PARENT_TYPE.match(/HM-Dis-EP-WM55/)) ||
255
- ((addr.endsWith(':1') || addr.endsWith(':2')) && devices[addr].PARENT_TYPE.match(/HM-Dis-WM55/))
272
+ ((addr.endsWith(':1') || addr.endsWith(':2')) &&
273
+ devices[addr].PARENT_TYPE.match(/HM-Dis-WM55/))
256
274
  ) {
257
275
  object[addr] = {
258
276
  name: config.channelNames[addr],
259
- type: devices[addr].PARENT_TYPE
277
+ type: devices[addr].PARENT_TYPE,
260
278
  };
261
279
  }
262
280
  });
@@ -267,25 +285,29 @@ module.exports = function (RED) {
267
285
  }
268
286
 
269
287
  default:
270
- res.status(200).send(JSON.stringify({
271
- channelNames: config.channelNames,
272
- metadata: config.metadata,
273
- paramsetDescriptions: config.paramsetDescriptions,
274
- rooms: config.rooms,
275
- functions: config.functions,
276
- sysvar: config.sysvar,
277
- program: config.program,
278
- channelRooms: config.channelRooms,
279
- channelFunctions: config.channelFunctions,
280
- enabledIfaces: config.enabledIfaces
281
- }));
288
+ res.status(200).send(
289
+ JSON.stringify({
290
+ channelNames: config.channelNames,
291
+ metadata: config.metadata,
292
+ paramsetDescriptions: config.paramsetDescriptions,
293
+ rooms: config.rooms,
294
+ functions: config.functions,
295
+ sysvar: config.sysvar,
296
+ program: config.program,
297
+ channelRooms: config.channelRooms,
298
+ channelFunctions: config.channelFunctions,
299
+ enabledIfaces: config.enabledIfaces,
300
+ }),
301
+ );
282
302
  }
283
303
  } else {
284
304
  ccu.network.ports = [];
285
305
  const start = 2040 + Math.floor(Math.random() * 50);
286
- findport(start).then(() => findport(ccu.network.ports[0] + 1)).then(() => {
287
- res.status(200).send(JSON.stringify(ccu.network));
288
- });
306
+ findport(start)
307
+ .then(() => findport(ccu.network.ports[0] + 1))
308
+ .then(() => {
309
+ res.status(200).send(JSON.stringify(ccu.network));
310
+ });
289
311
  }
290
312
  });
291
313
 
@@ -294,7 +316,7 @@ module.exports = function (RED) {
294
316
  * @returns {number}
295
317
  */
296
318
  function now() {
297
- return (new Date()).getTime();
319
+ return new Date().getTime();
298
320
  }
299
321
 
300
322
  /**
@@ -311,10 +333,18 @@ module.exports = function (RED) {
311
333
  return addr;
312
334
  }
313
335
 
314
- return new Promise(resolve => {
315
- if (/^([01]?\d?\d|2[0-4]\d|25[0-5])\\.([01]?\d?\d|2[0-4]\d|25[0-5])\\.([01]?\d?\d|2[0-4]\d|25[0-5])\\.([01]?\d?\d|2[0-4]\d|25[0-5])$/g.test(host)) {
336
+ return new Promise((resolve) => {
337
+ if (
338
+ /^([01]?\d?\d|2[0-4]\d|25[0-5])\\.([01]?\d?\d|2[0-4]\d|25[0-5])\\.([01]?\d?\d|2[0-4]\d|25[0-5])\\.([01]?\d?\d|2[0-4]\d|25[0-5])$/g.test(
339
+ host,
340
+ )
341
+ ) {
316
342
  resolve(unifyLoopback(host));
317
- } else if (/^([\dA-Fa-f]{1,4})((?::[\dA-Fa-f]{1,4}))*::([\dA-Fa-f]{1,4})((?::[\dA-Fa-f]{1,4}))*|([\dA-Fa-f]{1,4})((?::[\dA-Fa-f]{1,4})){7}$/g.test(host)) {
343
+ } else if (
344
+ /^([\dA-Fa-f]{1,4})((?::[\dA-Fa-f]{1,4}))*::([\dA-Fa-f]{1,4})((?::[\dA-Fa-f]{1,4}))*|([\dA-Fa-f]{1,4})((?::[\dA-Fa-f]{1,4})){7}$/g.test(
345
+ host,
346
+ )
347
+ ) {
318
348
  resolve(host);
319
349
  } else {
320
350
  dns.lookup(host, (err, addr) => {
@@ -358,25 +388,25 @@ module.exports = function (RED) {
358
388
  port: this.isLocal ? 31999 : 1999,
359
389
  protocol: this.isLocal ? 'binrpc' : 'http',
360
390
  init: false,
361
- ping: false
391
+ ping: false,
362
392
  },
363
393
  'BidCos-RF': {
364
394
  conf: 'bcrf',
365
- rpc: (this.isLocal || config.bcrfBinRpc) ? binrpc : xmlrpc,
366
- port: this.isLocal ? 32001 : (config.tls ? 42001 : 2001),
367
- protocol: (this.isLocal || config.bcrfBinRpc) ? 'binrpc' : 'http',
395
+ rpc: this.isLocal || config.bcrfBinRpc ? binrpc : xmlrpc,
396
+ port: this.isLocal ? 32001 : config.tls ? 42001 : 2001,
397
+ protocol: this.isLocal || config.bcrfBinRpc ? 'binrpc' : 'http',
368
398
  auth: config.authentication,
369
399
  user: config.username,
370
400
  pass: config.password,
371
401
  tls: config.tls,
372
402
  inSecure: config.inSecure,
373
403
  init: true,
374
- ping: true
404
+ ping: true,
375
405
  },
376
406
  'BidCos-Wired': {
377
407
  conf: 'bcwi',
378
408
  rpc: this.isLocal ? binrpc : xmlrpc,
379
- port: this.isLocal ? 32000 : (config.tls ? 42000 : 2000),
409
+ port: this.isLocal ? 32000 : config.tls ? 42000 : 2000,
380
410
  protocol: this.isLocal ? 'binrpc' : 'http',
381
411
  auth: config.authentication,
382
412
  user: config.username,
@@ -384,12 +414,12 @@ module.exports = function (RED) {
384
414
  tls: config.tls,
385
415
  inSecure: config.inSecure,
386
416
  init: true,
387
- ping: true
417
+ ping: true,
388
418
  },
389
419
  'HmIP-RF': {
390
420
  conf: 'iprf',
391
421
  rpc: xmlrpc,
392
- port: this.isLocal ? 32010 : (config.tls ? 42010 : 2010),
422
+ port: this.isLocal ? 32010 : config.tls ? 42010 : 2010,
393
423
  protocol: 'http',
394
424
  auth: config.authentication,
395
425
  user: config.username,
@@ -398,12 +428,12 @@ module.exports = function (RED) {
398
428
  inSecure: config.inSecure,
399
429
  init: true,
400
430
  ping: true, // Todo https://github.com/eq-3/occu/issues/42 - should be fixed, but isn't
401
- pingTimeout: 600 // Overwrites ccu-connection config
431
+ pingTimeout: 600, // Overwrites ccu-connection config
402
432
  },
403
433
  VirtualDevices: {
404
434
  conf: 'virt',
405
435
  rpc: xmlrpc,
406
- port: this.isLocal ? 39292 : (config.tls ? 49292 : 9292),
436
+ port: this.isLocal ? 39292 : config.tls ? 49292 : 9292,
407
437
  path: 'groups',
408
438
  protocol: 'http',
409
439
  auth: config.authentication,
@@ -412,7 +442,7 @@ module.exports = function (RED) {
412
442
  tls: config.tls,
413
443
  inSecure: config.inSecure,
414
444
  init: true,
415
- ping: false // Todo ?
445
+ ping: false, // Todo ?
416
446
  },
417
447
  CUxD: {
418
448
  conf: 'cuxd',
@@ -420,8 +450,22 @@ module.exports = function (RED) {
420
450
  port: 8701,
421
451
  protocol: 'binrpc',
422
452
  init: true,
423
- ping: true
424
- }
453
+ ping: true,
454
+ },
455
+ 'CCU-Jack': {
456
+ conf: 'jack',
457
+ rpc: xmlrpc,
458
+ port: Number(config.jackPort) || (config.tls ? 2122 : 2121),
459
+ path: 'RPC3',
460
+ protocol: 'http',
461
+ auth: config.authentication,
462
+ user: config.username,
463
+ pass: config.password,
464
+ tls: config.tls,
465
+ inSecure: config.inSecure,
466
+ init: true,
467
+ ping: false,
468
+ },
425
469
  };
426
470
 
427
471
  this.name = config.name;
@@ -434,8 +478,14 @@ module.exports = function (RED) {
434
478
  if (ccu.network.listen.includes(config.rpcServerHost)) {
435
479
  this.rpcServerHost = config.rpcServerHost;
436
480
  } else {
437
- this.rpcServerHost = stringSimilarity.findBestMatch(config.rpcServerHost, ccu.network.listen).bestMatch.target;
438
- this.logger.error('Local address ' + config.rpcServerHost + ' not available. Using ' + this.rpcServerHost + ' instead.');
481
+ this.rpcServerHost = bestMatch(config.rpcServerHost, ccu.network.listen);
482
+ this.logger.error(
483
+ 'Local address ' +
484
+ config.rpcServerHost +
485
+ ' not available. Using ' +
486
+ this.rpcServerHost +
487
+ ' instead.',
488
+ );
439
489
  }
440
490
 
441
491
  this.rpcInitAddress = config.rpcInitAddress || this.rpcServerHost;
@@ -507,9 +557,18 @@ module.exports = function (RED) {
507
557
  this.rxCounters = {};
508
558
  this.txCounters = {};
509
559
 
510
- this.metadataFile = path.join(RED.settings.userDir || path.join(__dirname, '..'), 'ccu_' + this.host + '.json');
511
- this.regadataFile = path.join(RED.settings.userDir || path.join(__dirname, '..'), 'ccu_rega_' + this.host + '.json');
512
- this.valuesFile = path.join(RED.settings.userDir || path.join(__dirname, '..'), 'ccu_values_' + this.host + '.json');
560
+ this.metadataFile = path.join(
561
+ RED.settings.userDir || path.join(__dirname, '..'),
562
+ 'ccu_' + this.host + '.json',
563
+ );
564
+ this.regadataFile = path.join(
565
+ RED.settings.userDir || path.join(__dirname, '..'),
566
+ 'ccu_rega_' + this.host + '.json',
567
+ );
568
+ this.valuesFile = path.join(
569
+ RED.settings.userDir || path.join(__dirname, '..'),
570
+ 'ccu_values_' + this.host + '.json',
571
+ );
513
572
 
514
573
  this.loadMetadata();
515
574
  this.loadRegadata();
@@ -519,16 +578,15 @@ module.exports = function (RED) {
519
578
 
520
579
  this.rega = new Rega({
521
580
  host: this.host,
522
- port: this.isLocal ? 8183 : (config.tls ? 48181 : 8181),
581
+ port: this.isLocal ? 8183 : config.tls ? 48181 : 8181,
523
582
  tls: config.tls,
524
- inSecure: config.inSecure,
525
- auth: config.authentication,
526
- user: config.username,
527
- pass: config.password
583
+ insecure: config.inSecure,
584
+ username: config.authentication ? config.username : undefined,
585
+ password: config.authentication ? config.password : undefined,
528
586
  });
529
587
 
530
588
  this.enabledIfaces = [];
531
- Object.keys(this.ifaceTypes).forEach(iface => {
589
+ Object.keys(this.ifaceTypes).forEach((iface) => {
532
590
  const enabled = config[this.ifaceTypes[iface].conf + 'Enabled'];
533
591
  if (enabled) {
534
592
  this.enabledIfaces.push(iface);
@@ -537,11 +595,10 @@ module.exports = function (RED) {
537
595
  });
538
596
 
539
597
  if (config.regaEnabled) {
540
- this.getRegaData()
541
- .then(() => {
542
- this.regaPoll();
543
- this.initIfaces(config);
544
- });
598
+ this.getRegaData().then(() => {
599
+ this.regaPoll();
600
+ this.initIfaces(config);
601
+ });
545
602
  } else {
546
603
  this.initIfaces(config);
547
604
  }
@@ -567,7 +624,7 @@ module.exports = function (RED) {
567
624
  },
568
625
  error: (...args) => {
569
626
  this.error(args.join(' ').slice(0, 300));
570
- }
627
+ },
571
628
  };
572
629
  }
573
630
 
@@ -576,12 +633,18 @@ module.exports = function (RED) {
576
633
  * @param config
577
634
  */
578
635
  checkDuplicateConfig(config) {
579
- resolveHost(config.host).then(myAddr => {
580
- RED.nodes.eachNode(n => {
636
+ resolveHost(config.host).then((myAddr) => {
637
+ RED.nodes.eachNode((n) => {
581
638
  if (n.type === this.type && n.id !== this.id) {
582
- resolveHost(n.host).then(addr => {
639
+ resolveHost(n.host).then((addr) => {
583
640
  if (myAddr === addr) {
584
- this.logger.error('ccu-connection node ' + n.name + ' (' + n.id + ') is configured to connect to the same ccu. this leads to problems - only one ccu-connection node per ccu should exist!');
641
+ this.logger.error(
642
+ 'ccu-connection node ' +
643
+ n.name +
644
+ ' (' +
645
+ n.id +
646
+ ') is configured to connect to the same ccu. this leads to problems - only one ccu-connection node per ccu should exist!',
647
+ );
585
648
  }
586
649
  });
587
650
  }
@@ -594,11 +657,15 @@ module.exports = function (RED) {
594
657
  */
595
658
  setContext() {
596
659
  if (this.contextStore) {
597
- this.globalContext.set('ccu-' + this.host.replace(/\./g, '_'), {
598
- values: this.values,
599
- sysvar: this.sysvar,
600
- program: this.program
601
- }, this.contextStore);
660
+ this.globalContext.set(
661
+ 'ccu-' + this.host.replace(/\./g, '_'),
662
+ {
663
+ values: this.values,
664
+ sysvar: this.sysvar,
665
+ program: this.program,
666
+ },
667
+ this.contextStore,
668
+ );
602
669
  }
603
670
  }
604
671
 
@@ -633,15 +700,24 @@ module.exports = function (RED) {
633
700
 
634
701
  if (RED.settings.logging) {
635
702
  const [firstLogger] = Object.keys(RED.settings.logging);
636
- if (RED.settings.logging[firstLogger] && RED.settings.logging[firstLogger].level !== 'debug' && RED.settings.logging[firstLogger].level !== 'trace') {
703
+ if (
704
+ RED.settings.logging[firstLogger] &&
705
+ RED.settings.logging[firstLogger].level !== 'debug' &&
706
+ RED.settings.logging[firstLogger].level !== 'trace'
707
+ ) {
637
708
  return;
638
709
  }
639
710
  }
640
711
 
641
712
  this.statsInterval = setInterval(() => {
642
- this.logger.debug('stats rpc rx: ' + JSON.stringify(this.rxCounters) + ' tx: ' + JSON.stringify(this.txCounters));
713
+ this.logger.debug(
714
+ 'stats rpc rx: ' + JSON.stringify(this.rxCounters) + ' tx: ' + JSON.stringify(this.txCounters),
715
+ );
643
716
  this.logger.debug('stats rpc subscribers ' + Object.keys(this.callbacks).length);
644
- this.logger.debug('stats rega subscribers ' + (Object.keys(this.programCallbacks).length + Object.keys(this.sysvarCallbacks).length));
717
+ this.logger.debug(
718
+ 'stats rega subscribers ' +
719
+ (Object.keys(this.programCallbacks).length + Object.keys(this.sysvarCallbacks).length),
720
+ );
645
721
  }, 60000);
646
722
  }
647
723
 
@@ -655,11 +731,17 @@ module.exports = function (RED) {
655
731
  if (iface === 'ReGaHSS') {
656
732
  this.logger.info('Interface', iface, connected ? 'connected' : 'disconnected');
657
733
  } else {
658
- this.logger.info('Interface', iface, connected ? (this.ifaceTypes[iface].protocol + ' port ' + this.ifaceTypes[iface].port + ' connected') : 'disconnected');
734
+ this.logger.info(
735
+ 'Interface',
736
+ iface,
737
+ connected
738
+ ? this.ifaceTypes[iface].protocol + ' port ' + this.ifaceTypes[iface].port + ' connected'
739
+ : 'disconnected',
740
+ );
659
741
  }
660
742
 
661
743
  this.ifaceStatus[iface] = !this.serverError[iface] && connected;
662
- Object.keys(this.users).forEach(id => {
744
+ Object.keys(this.users).forEach((id) => {
663
745
  if (typeof this.users[id].setStatus === 'function') {
664
746
  this.users[id].setStatus({ifaceStatus: this.ifaceStatus});
665
747
  }
@@ -672,7 +754,7 @@ module.exports = function (RED) {
672
754
  * @returns {Promise<any>}
673
755
  */
674
756
  saveMetadata() {
675
- return new Promise(resolve => {
757
+ return new Promise((resolve) => {
676
758
  fs.writeFileSync(this.metadataFile, JSON.stringify(this.metadata));
677
759
  this.logger.info('metadata saved to', this.metadataFile);
678
760
  resolve();
@@ -684,17 +766,20 @@ module.exports = function (RED) {
684
766
  * @returns {Promise<any>}
685
767
  */
686
768
  saveRegadata() {
687
- return new Promise(resolve => {
688
- fs.writeFileSync(this.regadataFile, JSON.stringify({
689
- channelNames: this.channelNames,
690
- regaIdChannel: this.regaIdChannel,
691
- regaChannels: this.regaChannels,
692
- channelRooms: this.channelRooms,
693
- channelFunctions: this.channelFunctions,
694
- groups: this.groups,
695
- sysvar: this.sysvar,
696
- program: this.program
697
- }));
769
+ return new Promise((resolve) => {
770
+ fs.writeFileSync(
771
+ this.regadataFile,
772
+ JSON.stringify({
773
+ channelNames: this.channelNames,
774
+ regaIdChannel: this.regaIdChannel,
775
+ regaChannels: this.regaChannels,
776
+ channelRooms: this.channelRooms,
777
+ channelFunctions: this.channelFunctions,
778
+ groups: this.groups,
779
+ sysvar: this.sysvar,
780
+ program: this.program,
781
+ }),
782
+ );
698
783
  this.logger.info('regadata saved to', this.regadataFile);
699
784
  resolve();
700
785
  });
@@ -705,10 +790,13 @@ module.exports = function (RED) {
705
790
  * @returns {Promise<any>}
706
791
  */
707
792
  saveValues() {
708
- return new Promise(resolve => {
709
- fs.writeFileSync(this.valuesFile, JSON.stringify({
710
- values: this.values
711
- }));
793
+ return new Promise((resolve) => {
794
+ fs.writeFileSync(
795
+ this.valuesFile,
796
+ JSON.stringify({
797
+ values: this.values,
798
+ }),
799
+ );
712
800
  this.logger.info('values saved to', this.valuesFile);
713
801
  resolve();
714
802
  });
@@ -719,16 +807,16 @@ module.exports = function (RED) {
719
807
  * @returns {Promise<any>}
720
808
  */
721
809
  loadMetadata() {
722
- return new Promise(resolve => {
810
+ return new Promise((resolve) => {
723
811
  try {
724
812
  this.metadata = JSON.parse(fs.readFileSync(this.metadataFile));
725
813
  this.logger.info('metadata loaded from', this.metadataFile);
726
814
  resolve();
727
815
  } catch {
728
- this.logger.warn('metadata new empty');
816
+ this.logger.info('no cached metadata yet, starting empty');
729
817
  this.metadata = {
730
818
  devices: {},
731
- types: {}
819
+ types: {},
732
820
  };
733
821
  resolve();
734
822
  }
@@ -740,7 +828,7 @@ module.exports = function (RED) {
740
828
  * @returns {Promise<any>}
741
829
  */
742
830
  loadRegadata() {
743
- return new Promise(resolve => {
831
+ return new Promise((resolve) => {
744
832
  try {
745
833
  const regadata = JSON.parse(fs.readFileSync(this.regadataFile));
746
834
  this.logger.info('regadata loaded from', this.regadataFile);
@@ -762,7 +850,12 @@ module.exports = function (RED) {
762
850
  */
763
851
  resolve();
764
852
  } catch (error) {
765
- this.logger.error('error loading regadata ' + error.message);
853
+ if (error.code === 'ENOENT') {
854
+ this.logger.info('no cached regadata yet (first start)');
855
+ } else {
856
+ this.logger.error('error loading regadata ' + error.message);
857
+ }
858
+
766
859
  resolve();
767
860
  }
768
861
  });
@@ -773,19 +866,28 @@ module.exports = function (RED) {
773
866
  * @returns {Promise<any>}
774
867
  */
775
868
  loadValues() {
776
- return new Promise(resolve => {
869
+ return new Promise((resolve) => {
777
870
  try {
778
871
  const {values} = JSON.parse(fs.readFileSync(this.valuesFile));
779
872
  this.logger.info('values loaded from', this.valuesFile);
780
873
 
781
- /* eslint-disable-next-line guard-for-in */
782
874
  for (const datapointName in values) {
783
- this.values[datapointName] = {...values[datapointName], cache: true, change: false, uncertain: true};
875
+ this.values[datapointName] = {
876
+ ...values[datapointName],
877
+ cache: true,
878
+ change: false,
879
+ uncertain: true,
880
+ };
784
881
  }
785
882
 
786
883
  resolve();
787
884
  } catch (error) {
788
- this.logger.error('error loading values ' + error.message);
885
+ if (error.code === 'ENOENT') {
886
+ this.logger.info('no cached values yet (first start)');
887
+ } else {
888
+ this.logger.error('error loading values ' + error.message);
889
+ }
890
+
789
891
  resolve();
790
892
  }
791
893
  });
@@ -796,9 +898,13 @@ module.exports = function (RED) {
796
898
  * @returns {Promise<any>}
797
899
  */
798
900
  saveParamsets() {
799
- return new Promise(resolve => {
901
+ return new Promise((resolve) => {
800
902
  fs.writeFileSync(this.paramsetFile, JSON.stringify(this.paramsetDescriptions, null, ' '));
801
- this.logger.info('paramsets saved to', this.paramsetFile, (this.paramsetDescriptions ? Object.keys(this.paramsetDescriptions).length : 0));
903
+ this.logger.info(
904
+ 'paramsets saved to',
905
+ this.paramsetFile,
906
+ this.paramsetDescriptions ? Object.keys(this.paramsetDescriptions).length : 0,
907
+ );
802
908
  resolve();
803
909
  });
804
910
  }
@@ -808,8 +914,8 @@ module.exports = function (RED) {
808
914
  * @returns {Promise<any>}
809
915
  */
810
916
  loadParamsets() {
811
- return new Promise(resolve => {
812
- const load = file => {
917
+ return new Promise((resolve) => {
918
+ const load = (file) => {
813
919
  try {
814
920
  this.paramsetDescriptions = JSON.parse(fs.readFileSync(file));
815
921
  this.logger.info('paramsets loaded from', file);
@@ -841,7 +947,7 @@ module.exports = function (RED) {
841
947
  this.cancelRegaPoll = true;
842
948
  clearTimeout(this.regaPollTimeout);
843
949
 
844
- Object.keys(this.rpcPingTimer).forEach(iface => {
950
+ Object.keys(this.rpcPingTimer).forEach((iface) => {
845
951
  this.logger.debug('clear rpcPingTimer', iface);
846
952
  clearTimeout(this.rpcPingTimer[iface]);
847
953
  });
@@ -853,7 +959,8 @@ module.exports = function (RED) {
853
959
  .then(() => {
854
960
  this.logger.info('rpc close done');
855
961
  done();
856
- }).catch(error => {
962
+ })
963
+ .catch((error) => {
857
964
  this.logger.warn(error);
858
965
  done();
859
966
  });
@@ -885,24 +992,27 @@ module.exports = function (RED) {
885
992
  * @returns {Promise<any>}
886
993
  */
887
994
  getGroupsData() {
888
- return new Promise(resolve => {
995
+ return new Promise((resolve) => {
889
996
  this.logger.debug('virtualdevices get groups');
890
- this.rega.exec(`
997
+ execToCallback(
998
+ this.rega.exec(`
891
999
  var stdoutGroups;
892
1000
  var stderrGroups;
893
1001
  system.Exec("cat /etc/config/groups.gson", &stdoutGroups, &stderrGroups);
894
- `, (err, stdout, objects) => {
895
- if (!err && objects && objects.stderrGroups === 'null') {
896
- try {
897
- const {groups} = JSON.parse(objects.stdoutGroups);
898
- groups.forEach(group => {
899
- this.groups[group.id] = group;
900
- });
901
- } catch {}
902
- }
1002
+ `),
1003
+ (err, stdout, objects) => {
1004
+ if (!err && objects && objects.stderrGroups === 'null') {
1005
+ try {
1006
+ const {groups} = JSON.parse(objects.stdoutGroups);
1007
+ groups.forEach((group) => {
1008
+ this.groups[group.id] = group;
1009
+ });
1010
+ } catch {}
1011
+ }
903
1012
 
904
- resolve();
905
- });
1013
+ resolve();
1014
+ },
1015
+ );
906
1016
  });
907
1017
  }
908
1018
 
@@ -926,20 +1036,27 @@ module.exports = function (RED) {
926
1036
  getRegaValues() {
927
1037
  return new Promise((resolve, reject) => {
928
1038
  this.logger.info('rega getValues');
929
- this.rega.getValues((err, res) => {
1039
+ toCallback(this.rega.getValues(), (err, res) => {
930
1040
  if (err) {
931
1041
  reject(new Error('rega getValues ' + err.message));
932
1042
  } else {
933
- const d = new Date();
934
- res.forEach(dp => {
935
- const ts = (new Date(dp.ts + ' UTC+' + (d.getTimezoneOffset() / -60))).getTime();
1043
+ res.forEach((dp) => {
1044
+ // dp.ts is epoch ms since homematic-rega 2.x (0 = never)
1045
+ const ts = dp.ts;
936
1046
  const [iface, channel, datapoint] = dp.name.split('.');
937
1047
  if (this.enabledIfaces.includes(iface) && datapoint) {
938
1048
  if (['RSSI_DEVICE', 'RSSI_PEER'].includes(datapoint)) {
939
1049
  dp.value -= 256;
940
1050
  }
941
1051
 
942
- const message = this.createMessage(iface, channel, datapoint, dp.value, {cache: true, change: false, working: false, uncertain: dp.ts === '1970-01-01 01:00:00', ts, lc: ts});
1052
+ const message = this.createMessage(iface, channel, datapoint, dp.value, {
1053
+ cache: true,
1054
+ change: false,
1055
+ working: false,
1056
+ uncertain: dp.ts === 0,
1057
+ ts,
1058
+ lc: ts,
1059
+ });
943
1060
  this.values[message.datapointName] = message;
944
1061
  if (!datapoint.startsWith('PRESS_')) {
945
1062
  this.callCallbacks(message);
@@ -961,7 +1078,7 @@ module.exports = function (RED) {
961
1078
  getRegaChannels() {
962
1079
  return new Promise((resolve, reject) => {
963
1080
  this.logger.debug('rega getChannels');
964
- this.rega.getChannels((err, res) => {
1081
+ toCallback(this.rega.getChannels(), (err, res) => {
965
1082
  if (err) {
966
1083
  reject(new Error('rega getChannels ' + err.message));
967
1084
  } else {
@@ -971,7 +1088,7 @@ module.exports = function (RED) {
971
1088
  this.channelNames = {};
972
1089
  }
973
1090
 
974
- res.forEach(ch => {
1091
+ res.forEach((ch) => {
975
1092
  this.regaChannels.push(ch);
976
1093
  this.regaIdChannel[ch.id] = ch.address;
977
1094
  this.channelNames[ch.address] = ch.name;
@@ -989,7 +1106,7 @@ module.exports = function (RED) {
989
1106
  getRegaRooms() {
990
1107
  return new Promise((resolve, reject) => {
991
1108
  this.logger.debug('rega getRooms');
992
- this.rega.getRooms((err, res) => {
1109
+ toCallback(this.rega.getRooms(), (err, res) => {
993
1110
  if (err) {
994
1111
  reject(new Error('rega getRooms ' + err.message));
995
1112
  } else {
@@ -998,9 +1115,9 @@ module.exports = function (RED) {
998
1115
  this.channelRooms = {};
999
1116
  }
1000
1117
 
1001
- res.forEach(room => {
1118
+ res.forEach((room) => {
1002
1119
  this.rooms.push(room.name);
1003
- room.channels.forEach(chId => {
1120
+ room.channels.forEach((chId) => {
1004
1121
  const regaChannel = this.getEntry(this.regaChannels, 'id', chId);
1005
1122
  const address = regaChannel && regaChannel.address;
1006
1123
  if (address) {
@@ -1025,7 +1142,7 @@ module.exports = function (RED) {
1025
1142
  getRegaFunctions() {
1026
1143
  return new Promise((resolve, reject) => {
1027
1144
  this.logger.debug('rega getFunctions');
1028
- this.rega.getFunctions((err, res) => {
1145
+ toCallback(this.rega.getFunctions(), (err, res) => {
1029
1146
  if (err) {
1030
1147
  reject(new Error('rega getFunctions ' + err.message));
1031
1148
  } else {
@@ -1034,9 +1151,9 @@ module.exports = function (RED) {
1034
1151
  this.channelFunctions = {};
1035
1152
  }
1036
1153
 
1037
- res.forEach(func => {
1154
+ res.forEach((func) => {
1038
1155
  this.functions.push(func.name);
1039
- func.channels.forEach(chId => {
1156
+ func.channels.forEach((chId) => {
1040
1157
  const regaChannel = this.getEntry(this.regaChannels, 'id', chId);
1041
1158
  const address = regaChannel && regaChannel.address;
1042
1159
  if (address) {
@@ -1066,12 +1183,12 @@ module.exports = function (RED) {
1066
1183
  if (program) {
1067
1184
  const script = `dom.GetObject(${program.id}).Active(${active});`;
1068
1185
  this.logger.debug('rega programActive', name, script);
1069
- this.rega.exec(script + '\n', err => {
1186
+ execToCallback(this.rega.exec(script + '\n'), (err) => {
1070
1187
  if (err) {
1071
1188
  reject(err);
1072
1189
  } else {
1073
1190
  Object.assign(program, {
1074
- active
1191
+ active,
1075
1192
  });
1076
1193
  resolve(program);
1077
1194
  }
@@ -1094,14 +1211,21 @@ module.exports = function (RED) {
1094
1211
  const d = new Date();
1095
1212
  const script = `dom.GetObject(${program.id}).ProgramExecute();`;
1096
1213
  this.logger.debug('rega programExecute', name, script);
1097
- this.rega.exec(script + `\nvar lastExecTime = dom.GetObject(${program.id}).ProgramLastExecuteTime();\n`, (err, res, objects) => {
1098
- if (err) {
1099
- reject(err);
1100
- } else {
1101
- program.ts = (new Date(objects.lastExecTime + ' UTC+' + (d.getTimezoneOffset() / -60))).getTime();
1102
- resolve(program);
1103
- }
1104
- });
1214
+ execToCallback(
1215
+ this.rega.exec(
1216
+ script + `\nvar lastExecTime = dom.GetObject(${program.id}).ProgramLastExecuteTime();\n`,
1217
+ ),
1218
+ (err, res, objects) => {
1219
+ if (err) {
1220
+ reject(err);
1221
+ } else {
1222
+ program.ts = new Date(
1223
+ objects.lastExecTime + ' UTC+' + d.getTimezoneOffset() / -60,
1224
+ ).getTime();
1225
+ resolve(program);
1226
+ }
1227
+ },
1228
+ );
1105
1229
  } else {
1106
1230
  reject(new Error('programExecute ' + name + ' not found'));
1107
1231
  }
@@ -1156,7 +1280,7 @@ module.exports = function (RED) {
1156
1280
 
1157
1281
  const script = `dom.GetObject(${sysvar.id}).State(${value});`;
1158
1282
  this.logger.debug('setVariable', name, script);
1159
- this.rega.exec(script + '\n', err => {
1283
+ execToCallback(this.rega.exec(script + '\n'), (err) => {
1160
1284
  if (err) {
1161
1285
  reject(err);
1162
1286
  } else {
@@ -1184,9 +1308,9 @@ module.exports = function (RED) {
1184
1308
  this.regaPollPending = true;
1185
1309
  clearTimeout(this.regaPollTimeout);
1186
1310
  this.getRegaVariables()
1187
- .catch(error => this.logger.error('getRegaVariables', error))
1311
+ .catch((error) => this.logger.error('getRegaVariables', error))
1188
1312
  .then(() => this.getRegaPrograms())
1189
- .catch(error => this.logger.error('getRegaPrograms', error))
1313
+ .catch((error) => this.logger.error('getRegaPrograms', error))
1190
1314
  .finally(() => {
1191
1315
  if (this.regaInterval && this.regaPollEnabled && !this.cancelRegaPoll) {
1192
1316
  //this.logger.trace('rega next poll in', this.regaInterval, 'seconds');
@@ -1258,24 +1382,39 @@ module.exports = function (RED) {
1258
1382
  unit: sysvar.unit,
1259
1383
  enum: sysvar.enum,
1260
1384
  id: sysvar.id,
1261
- cache: isNew
1385
+ cache: isNew,
1262
1386
  };
1263
1387
  if (sysvar.channel) {
1264
1388
  const channel = this.regaIdChannel[sysvar.channel];
1265
1389
  const iface = this.findIface(channel);
1266
- const device = this.metadata.devices[iface] && this.metadata.devices[iface][channel] && this.metadata.devices[iface][channel].PARENT;
1390
+ const device =
1391
+ this.metadata.devices[iface] &&
1392
+ this.metadata.devices[iface][channel] &&
1393
+ this.metadata.devices[iface][channel].PARENT;
1267
1394
  Object.assign(this.sysvar[sysvar.name], {
1268
1395
  device,
1269
1396
  deviceName: this.channelNames[device],
1270
- deviceType: this.metadata.devices[iface] && this.metadata.devices[iface][device] && this.metadata.devices[iface][device].TYPE,
1397
+ deviceType:
1398
+ this.metadata.devices[iface] &&
1399
+ this.metadata.devices[iface][device] &&
1400
+ this.metadata.devices[iface][device].TYPE,
1271
1401
  channel,
1272
1402
  channelName: this.channelNames[channel],
1273
- channelType: this.metadata.devices[iface] && this.metadata.devices[iface][channel] && this.metadata.devices[iface][channel].TYPE,
1403
+ channelType:
1404
+ this.metadata.devices[iface] &&
1405
+ this.metadata.devices[iface][channel] &&
1406
+ this.metadata.devices[iface][channel].TYPE,
1274
1407
  channelIndex: channel && Number.parseInt(channel.split(':')[1], 10),
1275
1408
  rooms: this.channelRooms[channel],
1276
- room: this.channelRooms[channel] && this.channelRooms[channel].length === 1 ? this.channelRooms[channel][0] : undefined,
1409
+ room:
1410
+ this.channelRooms[channel] && this.channelRooms[channel].length === 1
1411
+ ? this.channelRooms[channel][0]
1412
+ : undefined,
1277
1413
  functions: this.channelFunctions[channel],
1278
- function: this.channelFunctions[channel] && this.channelFunctions[channel].length === 1 ? this.channelFunctions[channel][0] : undefined
1414
+ function:
1415
+ this.channelFunctions[channel] && this.channelFunctions[channel].length === 1
1416
+ ? this.channelFunctions[channel][0]
1417
+ : undefined,
1279
1418
  });
1280
1419
  }
1281
1420
  } else if (this.sysvar[sysvar.name].fromFile) {
@@ -1293,13 +1432,16 @@ module.exports = function (RED) {
1293
1432
  valueEnumPrevious: this.sysvar[sysvar.name].valueEnum,
1294
1433
  ts: sysvar.ts,
1295
1434
  tsPrevious: this.sysvar[sysvar.name].ts,
1296
- lc: this.sysvar[sysvar.name].value !== sysvar.val && !isNew ? sysvar.ts : this.sysvar[sysvar.name].lc,
1435
+ lc:
1436
+ this.sysvar[sysvar.name].value !== sysvar.val && !isNew
1437
+ ? sysvar.ts
1438
+ : this.sysvar[sysvar.name].lc,
1297
1439
  lcPrevious: this.sysvar[sysvar.name].lc,
1298
1440
  change: isNew ? false : this.sysvar[sysvar.name].value !== sysvar.val,
1299
- cache: isNew
1441
+ cache: isNew,
1300
1442
  });
1301
1443
 
1302
- Object.keys(this.sysvarCallbacks).forEach(key => {
1444
+ Object.keys(this.sysvarCallbacks).forEach((key) => {
1303
1445
  const {filter, callback} = this.sysvarCallbacks[key];
1304
1446
  let match = !filter.name || filter.name === sysvar.name;
1305
1447
  if (this.sysvar[sysvar.name].cache && !filter.cache) {
@@ -1325,24 +1467,26 @@ module.exports = function (RED) {
1325
1467
  getRegaVariables() {
1326
1468
  return new Promise((resolve, reject) => {
1327
1469
  this.logger.debug('getRegaVariables');
1328
- this.rega.getVariables((err, res) => {
1470
+ toCallback(this.rega.getVariables(), (err, res) => {
1329
1471
  if (err) {
1330
1472
  reject(err);
1331
1473
  this.hadTimeout.add('ReGaHSS');
1332
1474
  this.setIfaceStatus('ReGaHSS', false);
1333
1475
  } else {
1334
- const d = new Date();
1335
- res.forEach(sysvar => {
1476
+ res.forEach((sysvar) => {
1336
1477
  //this.logger.trace(JSON.stringify(sysvar));
1337
- sysvar.ts = sysvar.ts ? (new Date(sysvar.ts + ' UTC+' + (d.getTimezoneOffset() / -60))).getTime() : d.getTime();
1478
+ // sysvar.ts is epoch ms since homematic-rega 2.x (0 = never)
1479
+ sysvar.ts = sysvar.ts || Date.now();
1338
1480
  this.updateRegaVariable(sysvar);
1339
1481
  });
1340
1482
  if (!this.hasRegaVariables) {
1341
1483
  this.hasRegaVariables = true;
1342
- Object.keys(this.setVariableQueueTimeout).forEach(name => clearTimeout(this.setVariableQueueTimeout[name]));
1343
- Object.keys(this.setVariableQueue).reduce((p, name) =>
1344
- p.then(_ => this.setVariable(name, this.setVariableQueue[name])),
1345
- Promise.resolve()
1484
+ Object.keys(this.setVariableQueueTimeout).forEach((name) =>
1485
+ clearTimeout(this.setVariableQueueTimeout[name]),
1486
+ );
1487
+ Object.keys(this.setVariableQueue).reduce(
1488
+ (p, name) => p.then((_) => this.setVariable(name, this.setVariableQueue[name])),
1489
+ Promise.resolve(),
1346
1490
  );
1347
1491
  }
1348
1492
 
@@ -1360,16 +1504,15 @@ module.exports = function (RED) {
1360
1504
  getRegaPrograms() {
1361
1505
  return new Promise((resolve, reject) => {
1362
1506
  this.logger.debug('getRegaPrograms');
1363
- this.rega.getPrograms((err, res) => {
1507
+ toCallback(this.rega.getPrograms(), (err, res) => {
1364
1508
  if (err) {
1365
1509
  reject(err);
1366
1510
  this.hadTimeout.add('ReGaHSS');
1367
1511
  this.setIfaceStatus('ReGaHSS', false);
1368
1512
  } else if (res && Array.isArray(res)) {
1369
- const d = new Date();
1370
- res.forEach(prg => {
1513
+ res.forEach((prg) => {
1371
1514
  prg.type = 'PROGRAM';
1372
- prg.ts = (new Date(prg.ts + ' UTC+' + (d.getTimezoneOffset() / -60))).getTime();
1515
+ // prg.ts is epoch ms since homematic-rega 2.x (0 = never)
1373
1516
  if (!this.program[prg.name]) {
1374
1517
  this.program[prg.name] = {};
1375
1518
  }
@@ -1391,9 +1534,9 @@ module.exports = function (RED) {
1391
1534
  active: prg.active,
1392
1535
  activePrevious: this.program[prg.name].active,
1393
1536
  ts: prg.ts,
1394
- tsPrevious: this.program[prg.name].ts
1537
+ tsPrevious: this.program[prg.name].ts,
1395
1538
  };
1396
- Object.keys(this.programCallbacks).forEach(key => {
1539
+ Object.keys(this.programCallbacks).forEach((key) => {
1397
1540
  const {filter, callback} = this.programCallbacks[key];
1398
1541
  if (!filter.name || filter.name === prg.name) {
1399
1542
  callback(this.program[prg.name]);
@@ -1412,20 +1555,22 @@ module.exports = function (RED) {
1412
1555
  * @param config
1413
1556
  */
1414
1557
  initIfaces(config) {
1415
- Object.keys(this.ifaceTypes).forEach(iface => {
1558
+ Object.keys(this.ifaceTypes).forEach((iface) => {
1416
1559
  const enabled = config[this.ifaceTypes[iface].conf + 'Enabled'];
1417
1560
  this.ifaceTypes[iface].enabled = enabled;
1418
1561
  if (enabled) {
1419
1562
  this.createClient(iface)
1420
1563
  .then(() => {
1421
1564
  if (this.ifaceTypes[iface].init) {
1422
- return this.rpcInit(iface).then(() => {
1423
- this.setIfaceStatus(iface, true);
1424
- }).catch(error => {
1425
- this.logger.error('init', iface, error);
1426
- this.hadTimeout.add(iface);
1427
- this.setIfaceStatus(iface, false);
1428
- });
1565
+ return this.rpcInit(iface)
1566
+ .then(() => {
1567
+ this.setIfaceStatus(iface, true);
1568
+ })
1569
+ .catch((error) => {
1570
+ this.logger.error('init', iface, error);
1571
+ this.hadTimeout.add(iface);
1572
+ this.setIfaceStatus(iface, false);
1573
+ });
1429
1574
  }
1430
1575
 
1431
1576
  this.setIfaceStatus(iface, true);
@@ -1442,7 +1587,7 @@ module.exports = function (RED) {
1442
1587
  * @returns {Promise<any>}
1443
1588
  */
1444
1589
  createClient(iface) {
1445
- return new Promise(resolve => {
1590
+ return new Promise((resolve) => {
1446
1591
  const {rpc, port, path, protocol, auth, user, pass, tls, inSecure} = this.ifaceTypes[iface];
1447
1592
  const clientOptions = {};
1448
1593
  if (path) {
@@ -1464,12 +1609,20 @@ module.exports = function (RED) {
1464
1609
  this.clients[iface] = rpc.createClient(clientOptions);
1465
1610
  }
1466
1611
 
1467
- this.logger.debug('rpc client created', iface, JSON.stringify(clientOptions));
1612
+ this.logger.info(
1613
+ 'rpc client ' +
1614
+ iface +
1615
+ ' ' +
1616
+ (protocol === 'binrpc' ? 'binrpc' : tls ? 'xmlrpc/tls' : 'xmlrpc') +
1617
+ ' ' +
1618
+ this.host +
1619
+ ':' +
1620
+ port +
1621
+ (path ? '/' + path : ''),
1622
+ );
1468
1623
  if (this.methodCallQueue[iface]) {
1469
- this.methodCallQueue[iface].forEach(c => {
1470
- this.methodCall(iface, c[0], c[1])
1471
- .then(c[2])
1472
- .catch(c[3]);
1624
+ this.methodCallQueue[iface].forEach((c) => {
1625
+ this.methodCall(iface, c[0], c[1]).then(c[2]).catch(c[3]);
1473
1626
  });
1474
1627
  delete this.methodCallQueue[iface];
1475
1628
  }
@@ -1490,20 +1643,40 @@ module.exports = function (RED) {
1490
1643
  const initId = 'nr_' + hash + '_' + iface;
1491
1644
  this.lastEvent[iface] = now();
1492
1645
 
1493
- this.logger.info('init ' + iface + ' ' + initUrl + ' ' + initId);
1646
+ const {protocol, port} = this.ifaceTypes[iface];
1647
+ this.logger.info(
1648
+ 'init ' +
1649
+ iface +
1650
+ ' (' +
1651
+ (protocol === 'binrpc' ? 'binrpc' : 'xmlrpc') +
1652
+ ' ' +
1653
+ this.host +
1654
+ ':' +
1655
+ port +
1656
+ ') callback ' +
1657
+ initUrl +
1658
+ ' ' +
1659
+ initId,
1660
+ );
1494
1661
  this.methodCall(iface, 'init', [initUrl, initId])
1495
1662
  .then(() => {
1496
1663
  if (iface === 'CUxD') {
1497
- this.getDevices(iface).then(() => resolve(iface)).catch(() => resolve(iface));
1664
+ this.getDevices(iface)
1665
+ .then(() => resolve(iface))
1666
+ .catch(() => resolve(iface));
1498
1667
  } else if (['BidCos-RF', 'BidCos-Wired', 'HmIP-RF'].includes(iface)) {
1499
- this.methodCall(iface, 'getLinks', []).then(res => {
1500
- this.links[iface] = res;
1501
- resolve(iface);
1502
- }).catch(() => resolve(iface));
1668
+ this.methodCall(iface, 'getLinks', [])
1669
+ .then((res) => {
1670
+ this.links[iface] = res;
1671
+ resolve(iface);
1672
+ })
1673
+ .catch(() => resolve(iface));
1503
1674
  } else {
1504
1675
  resolve(iface);
1505
1676
  }
1506
- }).catch(error => reject(error)).finally(() => {
1677
+ })
1678
+ .catch((error) => reject(error))
1679
+ .finally(() => {
1507
1680
  if (this.ifaceTypes[iface].ping) {
1508
1681
  this.rpcCheckInit(iface);
1509
1682
  }
@@ -1521,7 +1694,7 @@ module.exports = function (RED) {
1521
1694
  getLinks(iface, address, receiver) {
1522
1695
  const links = [];
1523
1696
  if (this.links[iface]) {
1524
- this.links[iface].forEach(link => {
1697
+ this.links[iface].forEach((link) => {
1525
1698
  if (link[receiver ? 'RECEIVER' : 'SENDER'] === address) {
1526
1699
  links.push(link[receiver ? 'SENDER' : 'RECEIVER']);
1527
1700
  }
@@ -1538,34 +1711,36 @@ module.exports = function (RED) {
1538
1711
  */
1539
1712
  getDevices(iface) {
1540
1713
  return new Promise((resolve, reject) => {
1541
- this.methodCall(iface, 'listDevices', []).then(devices => {
1542
- if (!this.metadata.devices[iface]) {
1543
- this.metadata.devices[iface] = {};
1544
- }
1545
-
1546
- const knownDevices = [];
1547
- let change = false;
1548
- devices.forEach(device => {
1549
- knownDevices.push(device.ADDRESS);
1550
- if (!this.metadata.devices[iface][device.ADDRESS]) {
1551
- this.newDevice(iface, device);
1552
- change = true;
1714
+ this.methodCall(iface, 'listDevices', [])
1715
+ .then((devices) => {
1716
+ if (!this.metadata.devices[iface]) {
1717
+ this.metadata.devices[iface] = {};
1553
1718
  }
1554
- });
1555
1719
 
1556
- Object.keys(this.metadata.devices[iface]).forEach(addr => {
1557
- if (!knownDevices.includes(addr)) {
1558
- this.deleteDevice(iface, addr);
1559
- change = true;
1560
- }
1561
- });
1720
+ const knownDevices = [];
1721
+ let change = false;
1722
+ devices.forEach((device) => {
1723
+ knownDevices.push(device.ADDRESS);
1724
+ if (!this.metadata.devices[iface][device.ADDRESS]) {
1725
+ this.newDevice(iface, device);
1726
+ change = true;
1727
+ }
1728
+ });
1562
1729
 
1563
- if (change) {
1564
- this.saveMetadata();
1565
- }
1730
+ Object.keys(this.metadata.devices[iface]).forEach((addr) => {
1731
+ if (!knownDevices.includes(addr)) {
1732
+ this.deleteDevice(iface, addr);
1733
+ change = true;
1734
+ }
1735
+ });
1566
1736
 
1567
- resolve();
1568
- }).catch(reject);
1737
+ if (change) {
1738
+ this.saveMetadata();
1739
+ }
1740
+
1741
+ resolve();
1742
+ })
1743
+ .catch(reject);
1569
1744
  });
1570
1745
  }
1571
1746
 
@@ -1586,11 +1761,11 @@ module.exports = function (RED) {
1586
1761
  this.hadTimeout.add(iface);
1587
1762
  this.setIfaceStatus(iface, false);
1588
1763
  this.logger.warn('ping timeout', iface, elapsed);
1589
- this.rpcInit(iface).catch(error => this.logger.error(error.message));
1764
+ this.rpcInit(iface).catch((error) => this.logger.error(error.message));
1590
1765
  return;
1591
1766
  }
1592
1767
 
1593
- if (elapsed >= (pingTimeout / 2)) {
1768
+ if (elapsed >= pingTimeout / 2) {
1594
1769
  //this.logger.trace('ping', iface, elapsed);
1595
1770
  this.methodCall(iface, 'ping', ['nr']).catch(() => {
1596
1771
  this.setIfaceStatus(iface, false);
@@ -1609,19 +1784,21 @@ module.exports = function (RED) {
1609
1784
  rpcClose() {
1610
1785
  this.logger.debug('rpcClose');
1611
1786
  const calls = [];
1612
- Object.keys(this.clients).forEach(iface => {
1787
+ Object.keys(this.clients).forEach((iface) => {
1613
1788
  if (this.ifaceTypes[iface].init) {
1614
1789
  this.logger.debug('queue de-init ' + iface + ' ' + this.initUrl(iface));
1615
1790
  calls.push(() => {
1616
- return new Promise(resolve => {
1791
+ return new Promise((resolve) => {
1617
1792
  this.logger.debug('de-init ' + iface + ' ' + this.initUrl(iface));
1618
1793
  this.methodCall(iface, 'init', [this.initUrl(iface), ''])
1619
1794
  .then(() => {
1620
1795
  this.logger.info('de-init ' + iface + ' ' + this.initUrl(iface) + ' done');
1621
1796
  resolve();
1622
1797
  })
1623
- .catch(error => {
1624
- this.logger.error('de-init ' + iface + ' ' + this.initUrl(iface) + ' failed ' + error);
1798
+ .catch((error) => {
1799
+ this.logger.error(
1800
+ 'de-init ' + iface + ' ' + this.initUrl(iface) + ' failed ' + error,
1801
+ );
1625
1802
  resolve();
1626
1803
  });
1627
1804
  });
@@ -1631,7 +1808,7 @@ module.exports = function (RED) {
1631
1808
 
1632
1809
  this.logger.debug('queue binrpc server closing');
1633
1810
  calls.push(() => {
1634
- return new Promise(resolve => {
1811
+ return new Promise((resolve) => {
1635
1812
  this.logger.debug('binrpc server closing');
1636
1813
  let timeout;
1637
1814
  if (this.servers.binrpc && this.servers.binrpc.server) {
@@ -1653,7 +1830,7 @@ module.exports = function (RED) {
1653
1830
 
1654
1831
  this.logger.debug('xmlrpc binrpc server closing');
1655
1832
  calls.push(() => {
1656
- return new Promise(resolve => {
1833
+ return new Promise((resolve) => {
1657
1834
  this.logger.debug('xmlrpc server closing');
1658
1835
  let timeout;
1659
1836
  if (this.servers.http && this.servers.http.close) {
@@ -1686,7 +1863,7 @@ module.exports = function (RED) {
1686
1863
  */
1687
1864
  initUrl(iface) {
1688
1865
  const {protocol} = this.ifaceTypes[iface];
1689
- const port = (protocol === 'binrpc' ? this.rpcBinPort : this.rpcXmlPort);
1866
+ const port = protocol === 'binrpc' ? this.rpcBinPort : this.rpcXmlPort;
1690
1867
  return protocol + '://' + (this.rpcInitAddress || this.rpcServerHost) + ':' + port;
1691
1868
  }
1692
1869
 
@@ -1698,7 +1875,7 @@ module.exports = function (RED) {
1698
1875
  rpcServer(iface) {
1699
1876
  const url = this.initUrl(iface);
1700
1877
  const {rpc, protocol} = this.ifaceTypes[iface];
1701
- const port = (protocol === 'binrpc' ? this.rpcBinPort : this.rpcXmlPort);
1878
+ const port = protocol === 'binrpc' ? this.rpcBinPort : this.rpcXmlPort;
1702
1879
  if (!this.servers[protocol]) {
1703
1880
  this.servers[protocol] = rpc.createServer({host: this.rpcServerHost, port}, () => {
1704
1881
  // Todo homematic-xmlrpc and binrpc module: clarify onListening callback params
@@ -1706,13 +1883,14 @@ module.exports = function (RED) {
1706
1883
  this.serverError[iface] = null;
1707
1884
  });
1708
1885
 
1709
- // Todo homematic-xmlrpc and binrpc module: emit error event on server object to eliminate access to httpServer/server
1710
- this.servers[protocol][protocol === 'binrpc' ? 'server' : 'httpServer'].on('error', err => {
1711
- this.logger.error('binrpc ' + err.message);
1886
+ // Todo binrpc module: emit error event on server object to eliminate the reach-in
1887
+ const errorEmitter = protocol === 'binrpc' ? this.servers[protocol].server : this.servers[protocol];
1888
+ errorEmitter.on('error', (err) => {
1889
+ this.logger.error(protocol + ' ' + err.message);
1712
1890
  this.serverError[iface] = err.message;
1713
1891
  });
1714
1892
 
1715
- Object.keys(this.rpcMethods).forEach(method => {
1893
+ Object.keys(this.rpcMethods).forEach((method) => {
1716
1894
  this.servers[protocol].on(method, (err, parameters, callback) => {
1717
1895
  if (err) {
1718
1896
  this.logger.error('rpc <', protocol, method, err);
@@ -1726,7 +1904,14 @@ module.exports = function (RED) {
1726
1904
  if (isIterable(parameters)) {
1727
1905
  this.rpcMethods[method](err, parameters, callback);
1728
1906
  } else {
1729
- this.logger.error('rpc <', protocol, 'method', method, 'params not iterable', JSON.stringify(parameters));
1907
+ this.logger.error(
1908
+ 'rpc <',
1909
+ protocol,
1910
+ 'method',
1911
+ method,
1912
+ 'params not iterable',
1913
+ JSON.stringify(parameters),
1914
+ );
1730
1915
  callback(null, '');
1731
1916
  }
1732
1917
  });
@@ -1773,7 +1958,7 @@ module.exports = function (RED) {
1773
1958
  this.paramsQueue.push({
1774
1959
  iface,
1775
1960
  address: device.ADDRESS,
1776
- paramset
1961
+ paramset,
1777
1962
  });
1778
1963
  }
1779
1964
 
@@ -1782,16 +1967,17 @@ module.exports = function (RED) {
1782
1967
  if (item) {
1783
1968
  const {iface, address, paramset} = item;
1784
1969
  this.methodCall(iface, 'getParamset', [address, paramset])
1785
- .then(res => {
1970
+ .then((res) => {
1786
1971
  this.params[paramset][address] = res;
1787
1972
  })
1788
- .catch(error => this.logger.error(error))
1973
+ .catch((error) => this.logger.error(error))
1789
1974
  .then(() => {
1790
1975
  clearTimeout(this.getParamsTimeout);
1791
1976
  this.getParamsTimeout = setTimeout(() => {
1792
1977
  this.paramsQueueShift();
1793
1978
  }, 200);
1794
- }).catch(() => {});
1979
+ })
1980
+ .catch(() => {});
1795
1981
  } else {
1796
1982
  this.logger.debug(JSON.stringify(this.params));
1797
1983
  }
@@ -1804,14 +1990,14 @@ module.exports = function (RED) {
1804
1990
  */
1805
1991
  paramsetQueuePush(iface, device) {
1806
1992
  if (device && device.PARAMSETS) {
1807
- device.PARAMSETS.forEach(paramset => {
1993
+ device.PARAMSETS.forEach((paramset) => {
1808
1994
  const name = this.paramsetName(iface, device, paramset);
1809
1995
  if (!this.paramsetDescriptions[name]) {
1810
1996
  this.paramsetQueue.push({
1811
1997
  iface,
1812
1998
  name,
1813
1999
  address: device.ADDRESS,
1814
- paramset
2000
+ paramset,
1815
2001
  });
1816
2002
  }
1817
2003
  });
@@ -1843,7 +2029,7 @@ module.exports = function (RED) {
1843
2029
  setImmediate(() => this.paramsetQueueShift());
1844
2030
  } else {
1845
2031
  this.methodCall(iface, 'getParamsetDescription', [address, paramset])
1846
- .then(res => {
2032
+ .then((res) => {
1847
2033
  //this.logger.trace('paramsetDescription', name);
1848
2034
  this.newParamsetDescriptionCount += 1;
1849
2035
  this.newParamsetDescription = true;
@@ -1854,14 +2040,15 @@ module.exports = function (RED) {
1854
2040
  this.saveParamsets();
1855
2041
  }
1856
2042
  })
1857
- .catch(error => this.logger.error(error))
2043
+ .catch((error) => this.logger.error(error))
1858
2044
  .then(() => {
1859
2045
  this.paramsetPending = false;
1860
2046
  clearTimeout(this.getParamsetTimeout);
1861
2047
  this.getParamsetTimeout = setTimeout(() => {
1862
2048
  this.paramsetQueueShift();
1863
2049
  }, 200);
1864
- }).catch(() => {});
2050
+ })
2051
+ .catch(() => {});
1865
2052
  }
1866
2053
  } else {
1867
2054
  this.paramsetPending = false;
@@ -1923,7 +2110,10 @@ module.exports = function (RED) {
1923
2110
  throw new Error('device type undefined: ' + JSON.stringify(device));
1924
2111
  }
1925
2112
 
1926
- if (this.metadata.types[iface][device.TYPE] && !this.metadata.types[iface][device.TYPE].includes(device.ADDRESS)) {
2113
+ if (
2114
+ this.metadata.types[iface][device.TYPE] &&
2115
+ !this.metadata.types[iface][device.TYPE].includes(device.ADDRESS)
2116
+ ) {
1927
2117
  this.metadata.types[iface][device.TYPE].push(device.ADDRESS);
1928
2118
  } else {
1929
2119
  this.metadata.types[iface][device.TYPE] = [device.ADDRESS];
@@ -1954,7 +2144,7 @@ module.exports = function (RED) {
1954
2144
  listDevices(iface) {
1955
2145
  const result = [];
1956
2146
  if (this.metadata.devices[iface]) {
1957
- Object.keys(this.metadata.devices[iface]).forEach(addr => {
2147
+ Object.keys(this.metadata.devices[iface]).forEach((addr) => {
1958
2148
  const dev = this.metadata.devices[iface][addr];
1959
2149
  if (dev.TYPE === 'HmIP-RCV-50' || dev.PARENT_TYPE === 'HmIP-RCV-50') {
1960
2150
  // Würgaround for Firmware 3.43.15
@@ -1983,8 +2173,8 @@ module.exports = function (RED) {
1983
2173
  switch (iface) {
1984
2174
  case 'HmIP-RF':
1985
2175
  // fallthrough by intention
1986
- case 'VirtualDevices':
1987
- const d = { // eslint-disable-line no-case-declarations
2176
+ case 'VirtualDevices': {
2177
+ const d = {
1988
2178
  ADDRESS: device.ADDRESS,
1989
2179
  VERSION: device.VERSION,
1990
2180
  AES_ACTIVE: device.AES_ACTIVE,
@@ -2006,9 +2196,9 @@ module.exports = function (RED) {
2006
2196
  TEAM: device.TEAM,
2007
2197
  TEAM_CHANNELS: device.TEAM_CHANNELS,
2008
2198
  TEAM_TAG: device.TEAM_TAG,
2009
- TYPE: device.TYPE
2199
+ TYPE: device.TYPE,
2010
2200
  };
2011
- Object.keys(d).forEach(k => {
2201
+ Object.keys(d).forEach((k) => {
2012
2202
  if (typeof d[k] === 'undefined') {
2013
2203
  delete d[k];
2014
2204
  }
@@ -2020,6 +2210,8 @@ module.exports = function (RED) {
2020
2210
  });
2021
2211
 
2022
2212
  return d;
2213
+ }
2214
+
2023
2215
  default:
2024
2216
  return {ADDRESS: device.ADDRESS, VERSION: device.VERSION};
2025
2217
  }
@@ -2078,7 +2270,7 @@ module.exports = function (RED) {
2078
2270
  const [idInit, devices] = parameters;
2079
2271
  const iface = this.getIfaceFromIdInit(idInit);
2080
2272
 
2081
- devices.forEach(device => {
2273
+ devices.forEach((device) => {
2082
2274
  this.newDevice(iface, device);
2083
2275
  });
2084
2276
 
@@ -2091,7 +2283,7 @@ module.exports = function (RED) {
2091
2283
  const [idInit, devices] = parameters;
2092
2284
  const iface = this.getIfaceFromIdInit(idInit);
2093
2285
 
2094
- devices.forEach(device => {
2286
+ devices.forEach((device) => {
2095
2287
  this.deleteDevice(iface, device);
2096
2288
  });
2097
2289
 
@@ -2141,7 +2333,7 @@ module.exports = function (RED) {
2141
2333
  let direction;
2142
2334
  let pong = true;
2143
2335
  if (isIterable(parameters[0])) {
2144
- parameters[0].forEach(call => {
2336
+ parameters[0].forEach((call) => {
2145
2337
  if (call && call.methodName === 'event') {
2146
2338
  queue.push(call);
2147
2339
  if (isIterable(call.params)) {
@@ -2173,13 +2365,20 @@ module.exports = function (RED) {
2173
2365
  } else if (call && this.rpcMethods[call.methodName]) {
2174
2366
  pong = false;
2175
2367
  if (isIterable(call.params)) {
2176
- this.rpcMethods[call.methodName](call.methodName, call.params, (_, res) => result.push(res));
2368
+ this.rpcMethods[call.methodName](call.methodName, call.params, (_, res) =>
2369
+ result.push(res),
2370
+ );
2177
2371
  } else {
2178
- this.logger.error('rpc <', call.methodName, 'params not iterable', JSON.stringify(call.params));
2372
+ this.logger.error(
2373
+ 'rpc <',
2374
+ call.methodName,
2375
+ 'params not iterable',
2376
+ JSON.stringify(call.params),
2377
+ );
2179
2378
  }
2180
2379
  }
2181
2380
  });
2182
- queue.forEach(call => {
2381
+ queue.forEach((call) => {
2183
2382
  this.publishEvent(call.params, working, direction);
2184
2383
  });
2185
2384
  }
@@ -2195,7 +2394,7 @@ module.exports = function (RED) {
2195
2394
  }
2196
2395
 
2197
2396
  callback(null, result);
2198
- }
2397
+ },
2199
2398
  };
2200
2399
  }
2201
2400
 
@@ -2306,7 +2505,7 @@ module.exports = function (RED) {
2306
2505
  'room',
2307
2506
  'function',
2308
2507
  'rooms',
2309
- 'functions'
2508
+ 'functions',
2310
2509
  ]);
2311
2510
 
2312
2511
  const propertiesArray = Object.keys(filter);
@@ -2325,7 +2524,7 @@ module.exports = function (RED) {
2325
2524
  this.callbacks[id] = {filter, callback};
2326
2525
 
2327
2526
  if (filter.cache && this.cachedValuesReceived) {
2328
- Object.keys(this.values).forEach(dp => {
2527
+ Object.keys(this.values).forEach((dp) => {
2329
2528
  const message = {...this.values[dp]};
2330
2529
  message.cache = true;
2331
2530
  message.change = false;
@@ -2354,11 +2553,11 @@ module.exports = function (RED) {
2354
2553
  this.logger.trace('unsubscribe', id);
2355
2554
  delete this.callbacks[id];
2356
2555
 
2357
- Object.keys(this.callbackBlacklists).forEach(dp => {
2556
+ Object.keys(this.callbackBlacklists).forEach((dp) => {
2358
2557
  this.callbackBlacklists[dp].delete(id);
2359
2558
  });
2360
2559
 
2361
- Object.keys(this.callbackWhitelists).forEach(dp => {
2560
+ Object.keys(this.callbackWhitelists).forEach((dp) => {
2362
2561
  this.callbackWhitelists[dp].delete(id);
2363
2562
  });
2364
2563
 
@@ -2381,13 +2580,13 @@ module.exports = function (RED) {
2381
2580
  }
2382
2581
 
2383
2582
  const messageLower = {};
2384
- Object.keys(message).forEach(k => {
2583
+ Object.keys(message).forEach((k) => {
2385
2584
  messageLower[k.toLowerCase()] = message[k];
2386
2585
  });
2387
2586
 
2388
2587
  const match = topic.match(/\${[^}]+}/g);
2389
2588
  if (match) {
2390
- match.forEach(v => {
2589
+ match.forEach((v) => {
2391
2590
  const key = v.substr(2, v.length - 3);
2392
2591
  const rx = new RegExp('\\${' + key + '}', 'g');
2393
2592
  let rkey = key.toLowerCase();
@@ -2417,15 +2616,20 @@ module.exports = function (RED) {
2417
2616
  this.values[datapointName] = {};
2418
2617
  }
2419
2618
 
2420
- const device = this.metadata.devices[iface] && this.metadata.devices[iface][channel] && this.metadata.devices[iface][channel].PARENT;
2619
+ const device =
2620
+ this.metadata.devices[iface] &&
2621
+ this.metadata.devices[iface][channel] &&
2622
+ this.metadata.devices[iface][channel].PARENT;
2421
2623
  const ts = now();
2422
2624
  let change = false;
2423
2625
 
2424
- const valueStable = (additions && additions.working) ? this.values[datapointName].valueStable : payload;
2626
+ const valueStable = additions && additions.working ? this.values[datapointName].valueStable : payload;
2425
2627
 
2426
2628
  let description = {};
2427
2629
  if (this.metadata.devices[iface] && this.metadata.devices[iface][channel]) {
2428
- description = this.getParamsetDescription(iface, this.metadata.devices[iface][channel], 'VALUES', datapoint) || {};
2630
+ description =
2631
+ this.getParamsetDescription(iface, this.metadata.devices[iface][channel], 'VALUES', datapoint) ||
2632
+ {};
2429
2633
  }
2430
2634
 
2431
2635
  if (
@@ -2439,16 +2643,23 @@ module.exports = function (RED) {
2439
2643
 
2440
2644
  this.logger.trace('createMessage', channel, datapoint, payload, 'change=' + change);
2441
2645
 
2442
- const message = {topic: '',
2646
+ const message = {
2647
+ topic: '',
2443
2648
  payload,
2444
2649
  ccu: this.host,
2445
2650
  iface,
2446
2651
  device,
2447
2652
  deviceName: this.channelNames[device],
2448
- deviceType: this.metadata.devices[iface] && this.metadata.devices[iface][device] && this.metadata.devices[iface][device].TYPE,
2653
+ deviceType:
2654
+ this.metadata.devices[iface] &&
2655
+ this.metadata.devices[iface][device] &&
2656
+ this.metadata.devices[iface][device].TYPE,
2449
2657
  channel,
2450
2658
  channelName: this.channelNames[channel],
2451
- channelType: this.metadata.devices[iface] && this.metadata.devices[iface][channel] && this.metadata.devices[iface][channel].TYPE,
2659
+ channelType:
2660
+ this.metadata.devices[iface] &&
2661
+ this.metadata.devices[iface][channel] &&
2662
+ this.metadata.devices[iface][channel].TYPE,
2452
2663
  channelIndex: channel && Number.parseInt(channel.split(':')[1], 10),
2453
2664
  datapoint,
2454
2665
  datapointName,
@@ -2463,13 +2674,21 @@ module.exports = function (RED) {
2463
2674
  valueEnum: description.ENUM ? description.ENUM[Number(payload)] : undefined,
2464
2675
  valueStable,
2465
2676
  rooms: this.channelRooms[channel] || [],
2466
- room: this.channelRooms[channel] && this.channelRooms[channel].length > 0 ? this.channelRooms[channel][0] : undefined,
2677
+ room:
2678
+ this.channelRooms[channel] && this.channelRooms[channel].length > 0
2679
+ ? this.channelRooms[channel][0]
2680
+ : undefined,
2467
2681
  functions: this.channelFunctions[channel] || [],
2468
- function: this.channelFunctions[channel] && this.channelFunctions[channel].length > 0 ? this.channelFunctions[channel][0] : undefined,
2682
+ function:
2683
+ this.channelFunctions[channel] && this.channelFunctions[channel].length > 0
2684
+ ? this.channelFunctions[channel][0]
2685
+ : undefined,
2469
2686
  ts,
2470
2687
  tsPrevious: this.values[datapointName].ts,
2471
2688
  lc: change ? ts : this.values[datapointName].lc,
2472
- change, ...additions};
2689
+ change,
2690
+ ...additions,
2691
+ };
2473
2692
 
2474
2693
  message.stable = !message.working;
2475
2694
 
@@ -2498,16 +2717,27 @@ module.exports = function (RED) {
2498
2717
 
2499
2718
  //this.logger.trace('publishEvent', JSON.stringify(params));
2500
2719
 
2501
- const message = this.createMessage(iface, channel, datapoint, payload, {cache: false, uncertain: false, working, direction});
2720
+ const message = this.createMessage(iface, channel, datapoint, payload, {
2721
+ cache: false,
2722
+ uncertain: false,
2723
+ working,
2724
+ direction,
2725
+ });
2502
2726
 
2503
2727
  let waitForWorking = false;
2504
2728
 
2505
2729
  if (message.channelType && !working) {
2506
- if (message.datapoint === 'STATE' && message.channelType.match(/SIGNAL|SWITCH|RAINDETECTOR_HEAT|ALARMACTUATOR/)) {
2730
+ if (
2731
+ message.datapoint === 'STATE' &&
2732
+ message.channelType.match(/SIGNAL|SWITCH|RAINDETECTOR_HEAT|ALARMACTUATOR/)
2733
+ ) {
2507
2734
  waitForWorking = true;
2508
2735
  } else if (message.datapoint === 'ARMSTATE' && message.channelType === 'ARMING') {
2509
2736
  waitForWorking = true;
2510
- } else if (message.datapoint.startsWith('LEVEL') && message.channelType.match(/DIMMER|DUAL_WHITE|BLIND|SHUTTER|JALOUSIE|WINMATIC|KEYMATIC/)) {
2737
+ } else if (
2738
+ message.datapoint.startsWith('LEVEL') &&
2739
+ message.channelType.match(/DIMMER|DUAL_WHITE|BLIND|SHUTTER|JALOUSIE|WINMATIC|KEYMATIC/)
2740
+ ) {
2511
2741
  waitForWorking = true;
2512
2742
  }
2513
2743
  }
@@ -2517,9 +2747,17 @@ module.exports = function (RED) {
2517
2747
  this.workingTimeout[message.datapointName] = setTimeout(() => {
2518
2748
  const datapointNamePrefix = iface + '.' + channel + '.';
2519
2749
 
2520
- if (this.values[datapointNamePrefix + 'WORKING'] || this.values[datapointNamePrefix + 'WORKING_SLATS']) {
2521
- message.working = this.values[datapointNamePrefix + 'WORKING'] && this.values[datapointNamePrefix + 'WORKING'].value;
2522
- message.working = message.working || (this.values[datapointNamePrefix + 'WORKING_SLATS'] && this.values[datapointNamePrefix + 'WORKING_SLATS'].value);
2750
+ if (
2751
+ this.values[datapointNamePrefix + 'WORKING'] ||
2752
+ this.values[datapointNamePrefix + 'WORKING_SLATS']
2753
+ ) {
2754
+ message.working =
2755
+ this.values[datapointNamePrefix + 'WORKING'] &&
2756
+ this.values[datapointNamePrefix + 'WORKING'].value;
2757
+ message.working =
2758
+ message.working ||
2759
+ (this.values[datapointNamePrefix + 'WORKING_SLATS'] &&
2760
+ this.values[datapointNamePrefix + 'WORKING_SLATS'].value);
2523
2761
  message.working = Boolean(message.working);
2524
2762
  } else if (this.values[datapointNamePrefix + 'PROCESS']) {
2525
2763
  message.working = Boolean(this.values[datapointNamePrefix + 'PROCESS'].value);
@@ -2566,7 +2804,7 @@ module.exports = function (RED) {
2566
2804
  if (filter) {
2567
2805
  const arrayAttr = Object.keys(filter);
2568
2806
 
2569
- for (let i = 0, {length} = arrayAttr; match && (i < length); i++) {
2807
+ for (let i = 0, {length} = arrayAttr; match && i < length; i++) {
2570
2808
  const attr = arrayAttr[i];
2571
2809
 
2572
2810
  if (attr === 'cache') {
@@ -2582,7 +2820,7 @@ module.exports = function (RED) {
2582
2820
 
2583
2821
  if (attr === 'change') {
2584
2822
  // if filter.change==true - Drop messages with msg.change==false - except msg.cache==true && filter.cache==true
2585
- if ((filter.change && !message.change) && !(filter.cache && message.cache)) {
2823
+ if (filter.change && !message.change && !(filter.cache && message.cache)) {
2586
2824
  //this.logger.trace('cb mismatch change ' + id + ' ' + filter.change + ' ' + msg.change + ' ' + msg.cache);
2587
2825
  return false;
2588
2826
  }
@@ -2610,7 +2848,8 @@ module.exports = function (RED) {
2610
2848
  continue;
2611
2849
  }
2612
2850
 
2613
- if (filter[attr] === '') { // TODO rethink
2851
+ if (filter[attr] === '') {
2852
+ // TODO rethink
2614
2853
  continue;
2615
2854
  }
2616
2855
 
@@ -2622,7 +2861,7 @@ module.exports = function (RED) {
2622
2861
  if (filter[attr] instanceof RegExp) {
2623
2862
  match = false;
2624
2863
  this.logger.trace('cb test regex array', id, attr, filter[attr], message[attr]);
2625
- message[attr].forEach(item => {
2864
+ message[attr].forEach((item) => {
2626
2865
  if (filter[attr].test(item)) {
2627
2866
  match = true;
2628
2867
  }
@@ -2637,7 +2876,9 @@ module.exports = function (RED) {
2637
2876
  match = false;
2638
2877
  }
2639
2878
  } else if (filter[attr] !== message[attr]) {
2640
- this.logger.trace('cb mismatch misc ' + id + ' ' + attr + ' ' + filter[attr] + ' ' + message[attr]);
2879
+ this.logger.trace(
2880
+ 'cb mismatch misc ' + id + ' ' + attr + ' ' + filter[attr] + ' ' + message[attr],
2881
+ );
2641
2882
  match = false;
2642
2883
  }
2643
2884
  }
@@ -2669,7 +2910,7 @@ module.exports = function (RED) {
2669
2910
  this.callbackWhitelists[message.datapointName] = new Set();
2670
2911
  }
2671
2912
 
2672
- Object.keys(this.callbacks).forEach(key => {
2913
+ Object.keys(this.callbacks).forEach((key) => {
2673
2914
  if (this.callbackBlacklists[message.datapointName].has(key)) {
2674
2915
  //this.logger.trace('blacklistet ' + key + ' ' + msg.datapointName);
2675
2916
  return;
@@ -2719,7 +2960,16 @@ module.exports = function (RED) {
2719
2960
  this.methodCallQueue[iface] = [[method, parameters, resolve, reject]];
2720
2961
  }
2721
2962
  } else {
2722
- reject(new Error('unknown interface ' + iface + ' ' + Object.keys(this.clients) + ' ' + Object.keys(this.ifaceTypes)));
2963
+ reject(
2964
+ new Error(
2965
+ 'unknown interface ' +
2966
+ iface +
2967
+ ' ' +
2968
+ Object.keys(this.clients) +
2969
+ ' ' +
2970
+ Object.keys(this.ifaceTypes),
2971
+ ),
2972
+ );
2723
2973
  }
2724
2974
  });
2725
2975
  }
@@ -2736,13 +2986,13 @@ module.exports = function (RED) {
2736
2986
  */
2737
2987
  setValueQueued(iface, address, datapoint, value, burst, force) {
2738
2988
  return new Promise((resolve, reject) => {
2739
- this.setValueQueue = this.setValueQueue.filter(element => {
2989
+ this.setValueQueue = this.setValueQueue.filter((element) => {
2740
2990
  return element.iface !== iface || element.address !== address || element.datapoint !== datapoint;
2741
2991
  });
2742
2992
  const datapointName = iface + '.' + address + '.' + datapoint;
2743
2993
  const currentValue = this.values[datapointName] && this.values[datapointName].value;
2744
2994
  const cache = this.values[datapointName] && this.values[datapointName].cache;
2745
- if (force || (value !== currentValue) || cache || datapoint.startsWith('PRESS_')) {
2995
+ if (force || value !== currentValue || cache || datapoint.startsWith('PRESS_')) {
2746
2996
  this.setValueQueue.push({iface, address, datapoint, value, burst, resolve, reject});
2747
2997
  this.setValueShiftQueue();
2748
2998
  } else {
@@ -2778,7 +3028,7 @@ module.exports = function (RED) {
2778
3028
  resolve();
2779
3029
  }
2780
3030
  })
2781
- .catch(error => {
3031
+ .catch((error) => {
2782
3032
  if (!timeout) {
2783
3033
  reject(error);
2784
3034
  }
@@ -2827,10 +3077,12 @@ module.exports = function (RED) {
2827
3077
  }, this.setValueThrottle);
2828
3078
  }
2829
3079
 
2830
- this.methodCall(iface, 'setValue', parameters).then(resolve).catch(error => {
2831
- this.logger.error('rpc >', iface, 'setValue', JSON.stringify(parameters), '<', error);
2832
- reject(error);
2833
- });
3080
+ this.methodCall(iface, 'setValue', parameters)
3081
+ .then(resolve)
3082
+ .catch((error) => {
3083
+ this.logger.error('rpc >', iface, 'setValue', JSON.stringify(parameters), '<', error);
3084
+ reject(error);
3085
+ });
2834
3086
  }
2835
3087
  });
2836
3088
  }
@@ -2846,10 +3098,12 @@ module.exports = function (RED) {
2846
3098
  const [iface] = id.split('.');
2847
3099
  const {params, resolve, reject} = this.setValueCache[id];
2848
3100
  delete this.setValueCache[id];
2849
- return this.methodCall(iface, 'setValue', params).then(resolve).catch(error => {
2850
- this.logger.error('rpc >', iface, 'setValue', JSON.stringify(params), '<', error);
2851
- reject(error);
2852
- });
3101
+ return this.methodCall(iface, 'setValue', params)
3102
+ .then(resolve)
3103
+ .catch((error) => {
3104
+ this.logger.error('rpc >', iface, 'setValue', JSON.stringify(params), '<', error);
3105
+ reject(error);
3106
+ });
2853
3107
  }
2854
3108
  }
2855
3109
 
@@ -2873,7 +3127,8 @@ module.exports = function (RED) {
2873
3127
  case 'BOOL':
2874
3128
  if (value === 'false') {
2875
3129
  value = false;
2876
- } else if (!isNaN(value)) { // Make sure that the string "0" gets casted to boolean false
3130
+ } else if (!isNaN(value)) {
3131
+ // Make sure that the string "0" gets casted to boolean false
2877
3132
  value = Number(value);
2878
3133
  }
2879
3134
 
@@ -2892,7 +3147,7 @@ module.exports = function (RED) {
2892
3147
  break;
2893
3148
  case 'ENUM':
2894
3149
  if (typeof value === 'string') {
2895
- if (paramset.ENUM && (paramset.ENUM.includes(value))) {
3150
+ if (paramset.ENUM && paramset.ENUM.includes(value)) {
2896
3151
  value = paramset.ENUM.indexOf(value);
2897
3152
  }
2898
3153
  }
@@ -2936,7 +3191,7 @@ module.exports = function (RED) {
2936
3191
  */
2937
3192
  script(script) {
2938
3193
  return new Promise((resolve, reject) => {
2939
- this.rega.exec(script, (err, payload, objects) => {
3194
+ execToCallback(this.rega.exec(script), (err, payload, objects) => {
2940
3195
  if (err) {
2941
3196
  reject(err);
2942
3197
  } else {
@@ -2947,7 +3202,5 @@ module.exports = function (RED) {
2947
3202
  }
2948
3203
  }
2949
3204
 
2950
- RED.nodes.registerType('ccu-connection', CcuConnectionNode, {
2951
-
2952
- });
3205
+ RED.nodes.registerType('ccu-connection', CcuConnectionNode, {});
2953
3206
  };