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.
- package/.prettierignore +10 -0
- package/.prettierrc +14 -0
- package/AGENTS.md +96 -0
- package/CHANGELOG.md +66 -0
- package/README.md +17 -21
- package/ROADMAP.md +402 -0
- package/eslint.config.js +45 -0
- package/examples/rpc-events.json +65 -0
- package/examples/switch-datapoint.json +86 -0
- package/examples/system-variable.json +60 -0
- package/nodes/ccu-alexa.html +12 -7
- package/nodes/ccu-alexa.js +33 -14
- package/nodes/ccu-connection.html +9 -1
- package/nodes/ccu-connection.js +588 -335
- package/nodes/ccu-display.html +11 -4
- package/nodes/ccu-display.js +26 -9
- package/nodes/ccu-get-value.html +13 -4
- package/nodes/ccu-get-value.js +2 -2
- package/nodes/ccu-mqtt.html +9 -10
- package/nodes/ccu-mqtt.js +55 -29
- package/nodes/ccu-program.html +1 -1
- package/nodes/ccu-program.js +9 -7
- package/nodes/ccu-rpc-event.html +12 -7
- package/nodes/ccu-rpc-event.js +4 -5
- package/nodes/ccu-rpc.html +12 -5
- package/nodes/ccu-rpc.js +8 -6
- package/nodes/ccu-script.html +1 -1
- package/nodes/ccu-script.js +4 -3
- package/nodes/ccu-set-value.html +11 -7
- package/nodes/ccu-set-value.js +40 -16
- package/nodes/ccu-signal.html +11 -5
- package/nodes/ccu-signal.js +34 -23
- package/nodes/ccu-switch.html +13 -5
- package/nodes/ccu-sysvar.html +1 -1
- package/nodes/ccu-sysvar.js +11 -7
- package/nodes/ccu-value.html +12 -7
- package/nodes/ccu-value.js +36 -13
- package/nodes/lib/base62.js +22 -0
- package/nodes/lib/discover.js +127 -0
- package/nodes/lib/nextport.js +30 -0
- package/nodes/lib/similarity.js +51 -0
- package/nodes/lib/status.js +1 -1
- package/package.json +105 -155
- package/roadmap-archive/README.md +20 -0
- package/roadmap-archive/phase-1.md +48 -0
- package/roadmap-archive/todo-history.md +39 -0
- package/TODO.md +0 -41
- package/ccu_rega_localhost.json +0 -1
- package/ccu_values_localhost.json +0 -1
package/nodes/ccu-connection.js
CHANGED
|
@@ -4,15 +4,11 @@ const path = require('path');
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const crypto = require('crypto');
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
|
195
|
-
|
|
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(
|
|
201
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
'
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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')) &&
|
|
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(
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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)
|
|
287
|
-
|
|
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
|
|
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 (
|
|
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 (
|
|
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:
|
|
366
|
-
port: this.isLocal ? 32001 :
|
|
367
|
-
protocol:
|
|
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 :
|
|
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 :
|
|
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 :
|
|
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 =
|
|
438
|
-
this.logger.error(
|
|
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(
|
|
511
|
-
|
|
512
|
-
|
|
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 :
|
|
581
|
+
port: this.isLocal ? 8183 : config.tls ? 48181 : 8181,
|
|
523
582
|
tls: config.tls,
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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
|
-
.
|
|
542
|
-
|
|
543
|
-
|
|
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(
|
|
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(
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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 (
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
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(
|
|
710
|
-
|
|
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.
|
|
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
|
-
|
|
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] = {
|
|
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
|
-
|
|
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(
|
|
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
|
-
})
|
|
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
|
-
|
|
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
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
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
|
-
|
|
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
|
-
|
|
934
|
-
|
|
935
|
-
const ts =
|
|
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, {
|
|
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
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
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 =
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
1335
|
-
res.forEach(sysvar => {
|
|
1476
|
+
res.forEach((sysvar) => {
|
|
1336
1477
|
//this.logger.trace(JSON.stringify(sysvar));
|
|
1337
|
-
|
|
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 =>
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
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
|
-
|
|
1370
|
-
res.forEach(prg => {
|
|
1513
|
+
res.forEach((prg) => {
|
|
1371
1514
|
prg.type = 'PROGRAM';
|
|
1372
|
-
prg.ts
|
|
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)
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
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.
|
|
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
|
-
|
|
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)
|
|
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', [])
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
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
|
-
})
|
|
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', [])
|
|
1542
|
-
|
|
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
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
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
|
-
|
|
1564
|
-
|
|
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
|
-
|
|
1568
|
-
|
|
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 >=
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
1710
|
-
|
|
1711
|
-
|
|
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(
|
|
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
|
-
})
|
|
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
|
-
})
|
|
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 (
|
|
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 = {
|
|
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) =>
|
|
2368
|
+
this.rpcMethods[call.methodName](call.methodName, call.params, (_, res) =>
|
|
2369
|
+
result.push(res),
|
|
2370
|
+
);
|
|
2177
2371
|
} else {
|
|
2178
|
-
this.logger.error(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 = {
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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,
|
|
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, {
|
|
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 (
|
|
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 (
|
|
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 (
|
|
2521
|
-
|
|
2522
|
-
|
|
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 &&
|
|
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 (
|
|
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] === '') {
|
|
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(
|
|
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(
|
|
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 ||
|
|
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)
|
|
2831
|
-
|
|
2832
|
-
|
|
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)
|
|
2850
|
-
|
|
2851
|
-
|
|
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)) {
|
|
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 &&
|
|
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
|
};
|