node-red-contrib-ccu 3.4.1 → 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 +43 -15
- package/nodes/ccu-connection.html +9 -1
- package/nodes/ccu-connection.js +602 -337
- package/nodes/ccu-display.html +11 -4
- package/nodes/ccu-display.js +26 -9
- package/nodes/ccu-get-value.html +23 -14
- package/nodes/ccu-get-value.js +10 -5
- 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 +47 -16
- package/nodes/ccu-signal.html +11 -5
- package/nodes/ccu-signal.js +34 -23
- package/nodes/ccu-switch.html +23 -13
- 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 -144
- 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,27 +557,36 @@ 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
|
+
);
|
|
572
|
+
|
|
513
573
|
this.loadMetadata();
|
|
514
574
|
this.loadRegadata();
|
|
515
|
-
|
|
575
|
+
this.loadValues();
|
|
516
576
|
|
|
517
577
|
this.setContext();
|
|
518
578
|
|
|
519
579
|
this.rega = new Rega({
|
|
520
580
|
host: this.host,
|
|
521
|
-
port: this.isLocal ? 8183 :
|
|
581
|
+
port: this.isLocal ? 8183 : config.tls ? 48181 : 8181,
|
|
522
582
|
tls: config.tls,
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
pass: config.password
|
|
583
|
+
insecure: config.inSecure,
|
|
584
|
+
username: config.authentication ? config.username : undefined,
|
|
585
|
+
password: config.authentication ? config.password : undefined,
|
|
527
586
|
});
|
|
528
587
|
|
|
529
588
|
this.enabledIfaces = [];
|
|
530
|
-
Object.keys(this.ifaceTypes).forEach(iface => {
|
|
589
|
+
Object.keys(this.ifaceTypes).forEach((iface) => {
|
|
531
590
|
const enabled = config[this.ifaceTypes[iface].conf + 'Enabled'];
|
|
532
591
|
if (enabled) {
|
|
533
592
|
this.enabledIfaces.push(iface);
|
|
@@ -536,11 +595,10 @@ module.exports = function (RED) {
|
|
|
536
595
|
});
|
|
537
596
|
|
|
538
597
|
if (config.regaEnabled) {
|
|
539
|
-
this.getRegaData()
|
|
540
|
-
.
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
});
|
|
598
|
+
this.getRegaData().then(() => {
|
|
599
|
+
this.regaPoll();
|
|
600
|
+
this.initIfaces(config);
|
|
601
|
+
});
|
|
544
602
|
} else {
|
|
545
603
|
this.initIfaces(config);
|
|
546
604
|
}
|
|
@@ -566,7 +624,7 @@ module.exports = function (RED) {
|
|
|
566
624
|
},
|
|
567
625
|
error: (...args) => {
|
|
568
626
|
this.error(args.join(' ').slice(0, 300));
|
|
569
|
-
}
|
|
627
|
+
},
|
|
570
628
|
};
|
|
571
629
|
}
|
|
572
630
|
|
|
@@ -575,12 +633,18 @@ module.exports = function (RED) {
|
|
|
575
633
|
* @param config
|
|
576
634
|
*/
|
|
577
635
|
checkDuplicateConfig(config) {
|
|
578
|
-
resolveHost(config.host).then(myAddr => {
|
|
579
|
-
RED.nodes.eachNode(n => {
|
|
636
|
+
resolveHost(config.host).then((myAddr) => {
|
|
637
|
+
RED.nodes.eachNode((n) => {
|
|
580
638
|
if (n.type === this.type && n.id !== this.id) {
|
|
581
|
-
resolveHost(n.host).then(addr => {
|
|
639
|
+
resolveHost(n.host).then((addr) => {
|
|
582
640
|
if (myAddr === addr) {
|
|
583
|
-
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
|
+
);
|
|
584
648
|
}
|
|
585
649
|
});
|
|
586
650
|
}
|
|
@@ -593,11 +657,15 @@ module.exports = function (RED) {
|
|
|
593
657
|
*/
|
|
594
658
|
setContext() {
|
|
595
659
|
if (this.contextStore) {
|
|
596
|
-
this.globalContext.set(
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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
|
+
);
|
|
601
669
|
}
|
|
602
670
|
}
|
|
603
671
|
|
|
@@ -632,15 +700,24 @@ module.exports = function (RED) {
|
|
|
632
700
|
|
|
633
701
|
if (RED.settings.logging) {
|
|
634
702
|
const [firstLogger] = Object.keys(RED.settings.logging);
|
|
635
|
-
if (
|
|
703
|
+
if (
|
|
704
|
+
RED.settings.logging[firstLogger] &&
|
|
705
|
+
RED.settings.logging[firstLogger].level !== 'debug' &&
|
|
706
|
+
RED.settings.logging[firstLogger].level !== 'trace'
|
|
707
|
+
) {
|
|
636
708
|
return;
|
|
637
709
|
}
|
|
638
710
|
}
|
|
639
711
|
|
|
640
712
|
this.statsInterval = setInterval(() => {
|
|
641
|
-
this.logger.debug(
|
|
713
|
+
this.logger.debug(
|
|
714
|
+
'stats rpc rx: ' + JSON.stringify(this.rxCounters) + ' tx: ' + JSON.stringify(this.txCounters),
|
|
715
|
+
);
|
|
642
716
|
this.logger.debug('stats rpc subscribers ' + Object.keys(this.callbacks).length);
|
|
643
|
-
this.logger.debug(
|
|
717
|
+
this.logger.debug(
|
|
718
|
+
'stats rega subscribers ' +
|
|
719
|
+
(Object.keys(this.programCallbacks).length + Object.keys(this.sysvarCallbacks).length),
|
|
720
|
+
);
|
|
644
721
|
}, 60000);
|
|
645
722
|
}
|
|
646
723
|
|
|
@@ -654,11 +731,17 @@ module.exports = function (RED) {
|
|
|
654
731
|
if (iface === 'ReGaHSS') {
|
|
655
732
|
this.logger.info('Interface', iface, connected ? 'connected' : 'disconnected');
|
|
656
733
|
} else {
|
|
657
|
-
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
|
+
);
|
|
658
741
|
}
|
|
659
742
|
|
|
660
743
|
this.ifaceStatus[iface] = !this.serverError[iface] && connected;
|
|
661
|
-
Object.keys(this.users).forEach(id => {
|
|
744
|
+
Object.keys(this.users).forEach((id) => {
|
|
662
745
|
if (typeof this.users[id].setStatus === 'function') {
|
|
663
746
|
this.users[id].setStatus({ifaceStatus: this.ifaceStatus});
|
|
664
747
|
}
|
|
@@ -671,7 +754,7 @@ module.exports = function (RED) {
|
|
|
671
754
|
* @returns {Promise<any>}
|
|
672
755
|
*/
|
|
673
756
|
saveMetadata() {
|
|
674
|
-
return new Promise(resolve => {
|
|
757
|
+
return new Promise((resolve) => {
|
|
675
758
|
fs.writeFileSync(this.metadataFile, JSON.stringify(this.metadata));
|
|
676
759
|
this.logger.info('metadata saved to', this.metadataFile);
|
|
677
760
|
resolve();
|
|
@@ -683,17 +766,20 @@ module.exports = function (RED) {
|
|
|
683
766
|
* @returns {Promise<any>}
|
|
684
767
|
*/
|
|
685
768
|
saveRegadata() {
|
|
686
|
-
return new Promise(resolve => {
|
|
687
|
-
fs.writeFileSync(
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
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
|
+
);
|
|
697
783
|
this.logger.info('regadata saved to', this.regadataFile);
|
|
698
784
|
resolve();
|
|
699
785
|
});
|
|
@@ -704,10 +790,13 @@ module.exports = function (RED) {
|
|
|
704
790
|
* @returns {Promise<any>}
|
|
705
791
|
*/
|
|
706
792
|
saveValues() {
|
|
707
|
-
return new Promise(resolve => {
|
|
708
|
-
fs.writeFileSync(
|
|
709
|
-
|
|
710
|
-
|
|
793
|
+
return new Promise((resolve) => {
|
|
794
|
+
fs.writeFileSync(
|
|
795
|
+
this.valuesFile,
|
|
796
|
+
JSON.stringify({
|
|
797
|
+
values: this.values,
|
|
798
|
+
}),
|
|
799
|
+
);
|
|
711
800
|
this.logger.info('values saved to', this.valuesFile);
|
|
712
801
|
resolve();
|
|
713
802
|
});
|
|
@@ -718,16 +807,16 @@ module.exports = function (RED) {
|
|
|
718
807
|
* @returns {Promise<any>}
|
|
719
808
|
*/
|
|
720
809
|
loadMetadata() {
|
|
721
|
-
return new Promise(resolve => {
|
|
810
|
+
return new Promise((resolve) => {
|
|
722
811
|
try {
|
|
723
812
|
this.metadata = JSON.parse(fs.readFileSync(this.metadataFile));
|
|
724
813
|
this.logger.info('metadata loaded from', this.metadataFile);
|
|
725
814
|
resolve();
|
|
726
815
|
} catch {
|
|
727
|
-
this.logger.
|
|
816
|
+
this.logger.info('no cached metadata yet, starting empty');
|
|
728
817
|
this.metadata = {
|
|
729
818
|
devices: {},
|
|
730
|
-
types: {}
|
|
819
|
+
types: {},
|
|
731
820
|
};
|
|
732
821
|
resolve();
|
|
733
822
|
}
|
|
@@ -739,7 +828,7 @@ module.exports = function (RED) {
|
|
|
739
828
|
* @returns {Promise<any>}
|
|
740
829
|
*/
|
|
741
830
|
loadRegadata() {
|
|
742
|
-
return new Promise(resolve => {
|
|
831
|
+
return new Promise((resolve) => {
|
|
743
832
|
try {
|
|
744
833
|
const regadata = JSON.parse(fs.readFileSync(this.regadataFile));
|
|
745
834
|
this.logger.info('regadata loaded from', this.regadataFile);
|
|
@@ -761,7 +850,12 @@ module.exports = function (RED) {
|
|
|
761
850
|
*/
|
|
762
851
|
resolve();
|
|
763
852
|
} catch (error) {
|
|
764
|
-
|
|
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
|
+
|
|
765
859
|
resolve();
|
|
766
860
|
}
|
|
767
861
|
});
|
|
@@ -772,14 +866,28 @@ module.exports = function (RED) {
|
|
|
772
866
|
* @returns {Promise<any>}
|
|
773
867
|
*/
|
|
774
868
|
loadValues() {
|
|
775
|
-
return new Promise(resolve => {
|
|
869
|
+
return new Promise((resolve) => {
|
|
776
870
|
try {
|
|
777
|
-
const
|
|
871
|
+
const {values} = JSON.parse(fs.readFileSync(this.valuesFile));
|
|
778
872
|
this.logger.info('values loaded from', this.valuesFile);
|
|
779
|
-
|
|
873
|
+
|
|
874
|
+
for (const datapointName in values) {
|
|
875
|
+
this.values[datapointName] = {
|
|
876
|
+
...values[datapointName],
|
|
877
|
+
cache: true,
|
|
878
|
+
change: false,
|
|
879
|
+
uncertain: true,
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
|
|
780
883
|
resolve();
|
|
781
884
|
} catch (error) {
|
|
782
|
-
|
|
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
|
+
|
|
783
891
|
resolve();
|
|
784
892
|
}
|
|
785
893
|
});
|
|
@@ -790,9 +898,13 @@ module.exports = function (RED) {
|
|
|
790
898
|
* @returns {Promise<any>}
|
|
791
899
|
*/
|
|
792
900
|
saveParamsets() {
|
|
793
|
-
return new Promise(resolve => {
|
|
901
|
+
return new Promise((resolve) => {
|
|
794
902
|
fs.writeFileSync(this.paramsetFile, JSON.stringify(this.paramsetDescriptions, null, ' '));
|
|
795
|
-
this.logger.info(
|
|
903
|
+
this.logger.info(
|
|
904
|
+
'paramsets saved to',
|
|
905
|
+
this.paramsetFile,
|
|
906
|
+
this.paramsetDescriptions ? Object.keys(this.paramsetDescriptions).length : 0,
|
|
907
|
+
);
|
|
796
908
|
resolve();
|
|
797
909
|
});
|
|
798
910
|
}
|
|
@@ -802,8 +914,8 @@ module.exports = function (RED) {
|
|
|
802
914
|
* @returns {Promise<any>}
|
|
803
915
|
*/
|
|
804
916
|
loadParamsets() {
|
|
805
|
-
return new Promise(resolve => {
|
|
806
|
-
const load = file => {
|
|
917
|
+
return new Promise((resolve) => {
|
|
918
|
+
const load = (file) => {
|
|
807
919
|
try {
|
|
808
920
|
this.paramsetDescriptions = JSON.parse(fs.readFileSync(file));
|
|
809
921
|
this.logger.info('paramsets loaded from', file);
|
|
@@ -835,7 +947,7 @@ module.exports = function (RED) {
|
|
|
835
947
|
this.cancelRegaPoll = true;
|
|
836
948
|
clearTimeout(this.regaPollTimeout);
|
|
837
949
|
|
|
838
|
-
Object.keys(this.rpcPingTimer).forEach(iface => {
|
|
950
|
+
Object.keys(this.rpcPingTimer).forEach((iface) => {
|
|
839
951
|
this.logger.debug('clear rpcPingTimer', iface);
|
|
840
952
|
clearTimeout(this.rpcPingTimer[iface]);
|
|
841
953
|
});
|
|
@@ -847,7 +959,8 @@ module.exports = function (RED) {
|
|
|
847
959
|
.then(() => {
|
|
848
960
|
this.logger.info('rpc close done');
|
|
849
961
|
done();
|
|
850
|
-
})
|
|
962
|
+
})
|
|
963
|
+
.catch((error) => {
|
|
851
964
|
this.logger.warn(error);
|
|
852
965
|
done();
|
|
853
966
|
});
|
|
@@ -879,24 +992,27 @@ module.exports = function (RED) {
|
|
|
879
992
|
* @returns {Promise<any>}
|
|
880
993
|
*/
|
|
881
994
|
getGroupsData() {
|
|
882
|
-
return new Promise(resolve => {
|
|
995
|
+
return new Promise((resolve) => {
|
|
883
996
|
this.logger.debug('virtualdevices get groups');
|
|
884
|
-
|
|
997
|
+
execToCallback(
|
|
998
|
+
this.rega.exec(`
|
|
885
999
|
var stdoutGroups;
|
|
886
1000
|
var stderrGroups;
|
|
887
1001
|
system.Exec("cat /etc/config/groups.gson", &stdoutGroups, &stderrGroups);
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
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
|
+
}
|
|
897
1012
|
|
|
898
|
-
|
|
899
|
-
|
|
1013
|
+
resolve();
|
|
1014
|
+
},
|
|
1015
|
+
);
|
|
900
1016
|
});
|
|
901
1017
|
}
|
|
902
1018
|
|
|
@@ -919,17 +1035,28 @@ module.exports = function (RED) {
|
|
|
919
1035
|
*/
|
|
920
1036
|
getRegaValues() {
|
|
921
1037
|
return new Promise((resolve, reject) => {
|
|
922
|
-
this.logger.
|
|
923
|
-
this.rega.getValues((err, res) => {
|
|
1038
|
+
this.logger.info('rega getValues');
|
|
1039
|
+
toCallback(this.rega.getValues(), (err, res) => {
|
|
924
1040
|
if (err) {
|
|
925
1041
|
reject(new Error('rega getValues ' + err.message));
|
|
926
1042
|
} else {
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
const ts =
|
|
1043
|
+
res.forEach((dp) => {
|
|
1044
|
+
// dp.ts is epoch ms since homematic-rega 2.x (0 = never)
|
|
1045
|
+
const ts = dp.ts;
|
|
930
1046
|
const [iface, channel, datapoint] = dp.name.split('.');
|
|
931
1047
|
if (this.enabledIfaces.includes(iface) && datapoint) {
|
|
932
|
-
|
|
1048
|
+
if (['RSSI_DEVICE', 'RSSI_PEER'].includes(datapoint)) {
|
|
1049
|
+
dp.value -= 256;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
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
|
+
});
|
|
933
1060
|
this.values[message.datapointName] = message;
|
|
934
1061
|
if (!datapoint.startsWith('PRESS_')) {
|
|
935
1062
|
this.callCallbacks(message);
|
|
@@ -951,7 +1078,7 @@ module.exports = function (RED) {
|
|
|
951
1078
|
getRegaChannels() {
|
|
952
1079
|
return new Promise((resolve, reject) => {
|
|
953
1080
|
this.logger.debug('rega getChannels');
|
|
954
|
-
this.rega.getChannels((err, res) => {
|
|
1081
|
+
toCallback(this.rega.getChannels(), (err, res) => {
|
|
955
1082
|
if (err) {
|
|
956
1083
|
reject(new Error('rega getChannels ' + err.message));
|
|
957
1084
|
} else {
|
|
@@ -961,7 +1088,7 @@ module.exports = function (RED) {
|
|
|
961
1088
|
this.channelNames = {};
|
|
962
1089
|
}
|
|
963
1090
|
|
|
964
|
-
res.forEach(ch => {
|
|
1091
|
+
res.forEach((ch) => {
|
|
965
1092
|
this.regaChannels.push(ch);
|
|
966
1093
|
this.regaIdChannel[ch.id] = ch.address;
|
|
967
1094
|
this.channelNames[ch.address] = ch.name;
|
|
@@ -979,7 +1106,7 @@ module.exports = function (RED) {
|
|
|
979
1106
|
getRegaRooms() {
|
|
980
1107
|
return new Promise((resolve, reject) => {
|
|
981
1108
|
this.logger.debug('rega getRooms');
|
|
982
|
-
this.rega.getRooms((err, res) => {
|
|
1109
|
+
toCallback(this.rega.getRooms(), (err, res) => {
|
|
983
1110
|
if (err) {
|
|
984
1111
|
reject(new Error('rega getRooms ' + err.message));
|
|
985
1112
|
} else {
|
|
@@ -988,9 +1115,9 @@ module.exports = function (RED) {
|
|
|
988
1115
|
this.channelRooms = {};
|
|
989
1116
|
}
|
|
990
1117
|
|
|
991
|
-
res.forEach(room => {
|
|
1118
|
+
res.forEach((room) => {
|
|
992
1119
|
this.rooms.push(room.name);
|
|
993
|
-
room.channels.forEach(chId => {
|
|
1120
|
+
room.channels.forEach((chId) => {
|
|
994
1121
|
const regaChannel = this.getEntry(this.regaChannels, 'id', chId);
|
|
995
1122
|
const address = regaChannel && regaChannel.address;
|
|
996
1123
|
if (address) {
|
|
@@ -1015,7 +1142,7 @@ module.exports = function (RED) {
|
|
|
1015
1142
|
getRegaFunctions() {
|
|
1016
1143
|
return new Promise((resolve, reject) => {
|
|
1017
1144
|
this.logger.debug('rega getFunctions');
|
|
1018
|
-
this.rega.getFunctions((err, res) => {
|
|
1145
|
+
toCallback(this.rega.getFunctions(), (err, res) => {
|
|
1019
1146
|
if (err) {
|
|
1020
1147
|
reject(new Error('rega getFunctions ' + err.message));
|
|
1021
1148
|
} else {
|
|
@@ -1024,9 +1151,9 @@ module.exports = function (RED) {
|
|
|
1024
1151
|
this.channelFunctions = {};
|
|
1025
1152
|
}
|
|
1026
1153
|
|
|
1027
|
-
res.forEach(func => {
|
|
1154
|
+
res.forEach((func) => {
|
|
1028
1155
|
this.functions.push(func.name);
|
|
1029
|
-
func.channels.forEach(chId => {
|
|
1156
|
+
func.channels.forEach((chId) => {
|
|
1030
1157
|
const regaChannel = this.getEntry(this.regaChannels, 'id', chId);
|
|
1031
1158
|
const address = regaChannel && regaChannel.address;
|
|
1032
1159
|
if (address) {
|
|
@@ -1056,12 +1183,12 @@ module.exports = function (RED) {
|
|
|
1056
1183
|
if (program) {
|
|
1057
1184
|
const script = `dom.GetObject(${program.id}).Active(${active});`;
|
|
1058
1185
|
this.logger.debug('rega programActive', name, script);
|
|
1059
|
-
this.rega.exec(script + '\n', err => {
|
|
1186
|
+
execToCallback(this.rega.exec(script + '\n'), (err) => {
|
|
1060
1187
|
if (err) {
|
|
1061
1188
|
reject(err);
|
|
1062
1189
|
} else {
|
|
1063
1190
|
Object.assign(program, {
|
|
1064
|
-
active
|
|
1191
|
+
active,
|
|
1065
1192
|
});
|
|
1066
1193
|
resolve(program);
|
|
1067
1194
|
}
|
|
@@ -1084,14 +1211,21 @@ module.exports = function (RED) {
|
|
|
1084
1211
|
const d = new Date();
|
|
1085
1212
|
const script = `dom.GetObject(${program.id}).ProgramExecute();`;
|
|
1086
1213
|
this.logger.debug('rega programExecute', name, script);
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
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
|
+
);
|
|
1095
1229
|
} else {
|
|
1096
1230
|
reject(new Error('programExecute ' + name + ' not found'));
|
|
1097
1231
|
}
|
|
@@ -1146,7 +1280,7 @@ module.exports = function (RED) {
|
|
|
1146
1280
|
|
|
1147
1281
|
const script = `dom.GetObject(${sysvar.id}).State(${value});`;
|
|
1148
1282
|
this.logger.debug('setVariable', name, script);
|
|
1149
|
-
this.rega.exec(script + '\n', err => {
|
|
1283
|
+
execToCallback(this.rega.exec(script + '\n'), (err) => {
|
|
1150
1284
|
if (err) {
|
|
1151
1285
|
reject(err);
|
|
1152
1286
|
} else {
|
|
@@ -1174,9 +1308,9 @@ module.exports = function (RED) {
|
|
|
1174
1308
|
this.regaPollPending = true;
|
|
1175
1309
|
clearTimeout(this.regaPollTimeout);
|
|
1176
1310
|
this.getRegaVariables()
|
|
1177
|
-
.catch(error => this.logger.error('getRegaVariables', error))
|
|
1311
|
+
.catch((error) => this.logger.error('getRegaVariables', error))
|
|
1178
1312
|
.then(() => this.getRegaPrograms())
|
|
1179
|
-
.catch(error => this.logger.error('getRegaPrograms', error))
|
|
1313
|
+
.catch((error) => this.logger.error('getRegaPrograms', error))
|
|
1180
1314
|
.finally(() => {
|
|
1181
1315
|
if (this.regaInterval && this.regaPollEnabled && !this.cancelRegaPoll) {
|
|
1182
1316
|
//this.logger.trace('rega next poll in', this.regaInterval, 'seconds');
|
|
@@ -1248,24 +1382,39 @@ module.exports = function (RED) {
|
|
|
1248
1382
|
unit: sysvar.unit,
|
|
1249
1383
|
enum: sysvar.enum,
|
|
1250
1384
|
id: sysvar.id,
|
|
1251
|
-
cache: isNew
|
|
1385
|
+
cache: isNew,
|
|
1252
1386
|
};
|
|
1253
1387
|
if (sysvar.channel) {
|
|
1254
1388
|
const channel = this.regaIdChannel[sysvar.channel];
|
|
1255
1389
|
const iface = this.findIface(channel);
|
|
1256
|
-
const device =
|
|
1390
|
+
const device =
|
|
1391
|
+
this.metadata.devices[iface] &&
|
|
1392
|
+
this.metadata.devices[iface][channel] &&
|
|
1393
|
+
this.metadata.devices[iface][channel].PARENT;
|
|
1257
1394
|
Object.assign(this.sysvar[sysvar.name], {
|
|
1258
1395
|
device,
|
|
1259
1396
|
deviceName: this.channelNames[device],
|
|
1260
|
-
deviceType:
|
|
1397
|
+
deviceType:
|
|
1398
|
+
this.metadata.devices[iface] &&
|
|
1399
|
+
this.metadata.devices[iface][device] &&
|
|
1400
|
+
this.metadata.devices[iface][device].TYPE,
|
|
1261
1401
|
channel,
|
|
1262
1402
|
channelName: this.channelNames[channel],
|
|
1263
|
-
channelType:
|
|
1403
|
+
channelType:
|
|
1404
|
+
this.metadata.devices[iface] &&
|
|
1405
|
+
this.metadata.devices[iface][channel] &&
|
|
1406
|
+
this.metadata.devices[iface][channel].TYPE,
|
|
1264
1407
|
channelIndex: channel && Number.parseInt(channel.split(':')[1], 10),
|
|
1265
1408
|
rooms: this.channelRooms[channel],
|
|
1266
|
-
room:
|
|
1409
|
+
room:
|
|
1410
|
+
this.channelRooms[channel] && this.channelRooms[channel].length === 1
|
|
1411
|
+
? this.channelRooms[channel][0]
|
|
1412
|
+
: undefined,
|
|
1267
1413
|
functions: this.channelFunctions[channel],
|
|
1268
|
-
function:
|
|
1414
|
+
function:
|
|
1415
|
+
this.channelFunctions[channel] && this.channelFunctions[channel].length === 1
|
|
1416
|
+
? this.channelFunctions[channel][0]
|
|
1417
|
+
: undefined,
|
|
1269
1418
|
});
|
|
1270
1419
|
}
|
|
1271
1420
|
} else if (this.sysvar[sysvar.name].fromFile) {
|
|
@@ -1283,13 +1432,16 @@ module.exports = function (RED) {
|
|
|
1283
1432
|
valueEnumPrevious: this.sysvar[sysvar.name].valueEnum,
|
|
1284
1433
|
ts: sysvar.ts,
|
|
1285
1434
|
tsPrevious: this.sysvar[sysvar.name].ts,
|
|
1286
|
-
lc:
|
|
1435
|
+
lc:
|
|
1436
|
+
this.sysvar[sysvar.name].value !== sysvar.val && !isNew
|
|
1437
|
+
? sysvar.ts
|
|
1438
|
+
: this.sysvar[sysvar.name].lc,
|
|
1287
1439
|
lcPrevious: this.sysvar[sysvar.name].lc,
|
|
1288
1440
|
change: isNew ? false : this.sysvar[sysvar.name].value !== sysvar.val,
|
|
1289
|
-
cache: isNew
|
|
1441
|
+
cache: isNew,
|
|
1290
1442
|
});
|
|
1291
1443
|
|
|
1292
|
-
Object.keys(this.sysvarCallbacks).forEach(key => {
|
|
1444
|
+
Object.keys(this.sysvarCallbacks).forEach((key) => {
|
|
1293
1445
|
const {filter, callback} = this.sysvarCallbacks[key];
|
|
1294
1446
|
let match = !filter.name || filter.name === sysvar.name;
|
|
1295
1447
|
if (this.sysvar[sysvar.name].cache && !filter.cache) {
|
|
@@ -1315,24 +1467,26 @@ module.exports = function (RED) {
|
|
|
1315
1467
|
getRegaVariables() {
|
|
1316
1468
|
return new Promise((resolve, reject) => {
|
|
1317
1469
|
this.logger.debug('getRegaVariables');
|
|
1318
|
-
this.rega.getVariables((err, res) => {
|
|
1470
|
+
toCallback(this.rega.getVariables(), (err, res) => {
|
|
1319
1471
|
if (err) {
|
|
1320
1472
|
reject(err);
|
|
1321
1473
|
this.hadTimeout.add('ReGaHSS');
|
|
1322
1474
|
this.setIfaceStatus('ReGaHSS', false);
|
|
1323
1475
|
} else {
|
|
1324
|
-
|
|
1325
|
-
res.forEach(sysvar => {
|
|
1476
|
+
res.forEach((sysvar) => {
|
|
1326
1477
|
//this.logger.trace(JSON.stringify(sysvar));
|
|
1327
|
-
|
|
1478
|
+
// sysvar.ts is epoch ms since homematic-rega 2.x (0 = never)
|
|
1479
|
+
sysvar.ts = sysvar.ts || Date.now();
|
|
1328
1480
|
this.updateRegaVariable(sysvar);
|
|
1329
1481
|
});
|
|
1330
1482
|
if (!this.hasRegaVariables) {
|
|
1331
1483
|
this.hasRegaVariables = true;
|
|
1332
|
-
Object.keys(this.setVariableQueueTimeout).forEach(name =>
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
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(),
|
|
1336
1490
|
);
|
|
1337
1491
|
}
|
|
1338
1492
|
|
|
@@ -1350,16 +1504,15 @@ module.exports = function (RED) {
|
|
|
1350
1504
|
getRegaPrograms() {
|
|
1351
1505
|
return new Promise((resolve, reject) => {
|
|
1352
1506
|
this.logger.debug('getRegaPrograms');
|
|
1353
|
-
this.rega.getPrograms((err, res) => {
|
|
1507
|
+
toCallback(this.rega.getPrograms(), (err, res) => {
|
|
1354
1508
|
if (err) {
|
|
1355
1509
|
reject(err);
|
|
1356
1510
|
this.hadTimeout.add('ReGaHSS');
|
|
1357
1511
|
this.setIfaceStatus('ReGaHSS', false);
|
|
1358
1512
|
} else if (res && Array.isArray(res)) {
|
|
1359
|
-
|
|
1360
|
-
res.forEach(prg => {
|
|
1513
|
+
res.forEach((prg) => {
|
|
1361
1514
|
prg.type = 'PROGRAM';
|
|
1362
|
-
prg.ts
|
|
1515
|
+
// prg.ts is epoch ms since homematic-rega 2.x (0 = never)
|
|
1363
1516
|
if (!this.program[prg.name]) {
|
|
1364
1517
|
this.program[prg.name] = {};
|
|
1365
1518
|
}
|
|
@@ -1381,9 +1534,9 @@ module.exports = function (RED) {
|
|
|
1381
1534
|
active: prg.active,
|
|
1382
1535
|
activePrevious: this.program[prg.name].active,
|
|
1383
1536
|
ts: prg.ts,
|
|
1384
|
-
tsPrevious: this.program[prg.name].ts
|
|
1537
|
+
tsPrevious: this.program[prg.name].ts,
|
|
1385
1538
|
};
|
|
1386
|
-
Object.keys(this.programCallbacks).forEach(key => {
|
|
1539
|
+
Object.keys(this.programCallbacks).forEach((key) => {
|
|
1387
1540
|
const {filter, callback} = this.programCallbacks[key];
|
|
1388
1541
|
if (!filter.name || filter.name === prg.name) {
|
|
1389
1542
|
callback(this.program[prg.name]);
|
|
@@ -1402,20 +1555,22 @@ module.exports = function (RED) {
|
|
|
1402
1555
|
* @param config
|
|
1403
1556
|
*/
|
|
1404
1557
|
initIfaces(config) {
|
|
1405
|
-
Object.keys(this.ifaceTypes).forEach(iface => {
|
|
1558
|
+
Object.keys(this.ifaceTypes).forEach((iface) => {
|
|
1406
1559
|
const enabled = config[this.ifaceTypes[iface].conf + 'Enabled'];
|
|
1407
1560
|
this.ifaceTypes[iface].enabled = enabled;
|
|
1408
1561
|
if (enabled) {
|
|
1409
1562
|
this.createClient(iface)
|
|
1410
1563
|
.then(() => {
|
|
1411
1564
|
if (this.ifaceTypes[iface].init) {
|
|
1412
|
-
return this.rpcInit(iface)
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
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
|
+
});
|
|
1419
1574
|
}
|
|
1420
1575
|
|
|
1421
1576
|
this.setIfaceStatus(iface, true);
|
|
@@ -1432,7 +1587,7 @@ module.exports = function (RED) {
|
|
|
1432
1587
|
* @returns {Promise<any>}
|
|
1433
1588
|
*/
|
|
1434
1589
|
createClient(iface) {
|
|
1435
|
-
return new Promise(resolve => {
|
|
1590
|
+
return new Promise((resolve) => {
|
|
1436
1591
|
const {rpc, port, path, protocol, auth, user, pass, tls, inSecure} = this.ifaceTypes[iface];
|
|
1437
1592
|
const clientOptions = {};
|
|
1438
1593
|
if (path) {
|
|
@@ -1454,12 +1609,20 @@ module.exports = function (RED) {
|
|
|
1454
1609
|
this.clients[iface] = rpc.createClient(clientOptions);
|
|
1455
1610
|
}
|
|
1456
1611
|
|
|
1457
|
-
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
|
+
);
|
|
1458
1623
|
if (this.methodCallQueue[iface]) {
|
|
1459
|
-
this.methodCallQueue[iface].forEach(c => {
|
|
1460
|
-
this.methodCall(iface, c[0], c[1])
|
|
1461
|
-
.then(c[2])
|
|
1462
|
-
.catch(c[3]);
|
|
1624
|
+
this.methodCallQueue[iface].forEach((c) => {
|
|
1625
|
+
this.methodCall(iface, c[0], c[1]).then(c[2]).catch(c[3]);
|
|
1463
1626
|
});
|
|
1464
1627
|
delete this.methodCallQueue[iface];
|
|
1465
1628
|
}
|
|
@@ -1480,20 +1643,40 @@ module.exports = function (RED) {
|
|
|
1480
1643
|
const initId = 'nr_' + hash + '_' + iface;
|
|
1481
1644
|
this.lastEvent[iface] = now();
|
|
1482
1645
|
|
|
1483
|
-
|
|
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
|
+
);
|
|
1484
1661
|
this.methodCall(iface, 'init', [initUrl, initId])
|
|
1485
1662
|
.then(() => {
|
|
1486
1663
|
if (iface === 'CUxD') {
|
|
1487
|
-
this.getDevices(iface)
|
|
1664
|
+
this.getDevices(iface)
|
|
1665
|
+
.then(() => resolve(iface))
|
|
1666
|
+
.catch(() => resolve(iface));
|
|
1488
1667
|
} else if (['BidCos-RF', 'BidCos-Wired', 'HmIP-RF'].includes(iface)) {
|
|
1489
|
-
this.methodCall(iface, 'getLinks', [])
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1668
|
+
this.methodCall(iface, 'getLinks', [])
|
|
1669
|
+
.then((res) => {
|
|
1670
|
+
this.links[iface] = res;
|
|
1671
|
+
resolve(iface);
|
|
1672
|
+
})
|
|
1673
|
+
.catch(() => resolve(iface));
|
|
1493
1674
|
} else {
|
|
1494
1675
|
resolve(iface);
|
|
1495
1676
|
}
|
|
1496
|
-
})
|
|
1677
|
+
})
|
|
1678
|
+
.catch((error) => reject(error))
|
|
1679
|
+
.finally(() => {
|
|
1497
1680
|
if (this.ifaceTypes[iface].ping) {
|
|
1498
1681
|
this.rpcCheckInit(iface);
|
|
1499
1682
|
}
|
|
@@ -1511,7 +1694,7 @@ module.exports = function (RED) {
|
|
|
1511
1694
|
getLinks(iface, address, receiver) {
|
|
1512
1695
|
const links = [];
|
|
1513
1696
|
if (this.links[iface]) {
|
|
1514
|
-
this.links[iface].forEach(link => {
|
|
1697
|
+
this.links[iface].forEach((link) => {
|
|
1515
1698
|
if (link[receiver ? 'RECEIVER' : 'SENDER'] === address) {
|
|
1516
1699
|
links.push(link[receiver ? 'SENDER' : 'RECEIVER']);
|
|
1517
1700
|
}
|
|
@@ -1528,34 +1711,36 @@ module.exports = function (RED) {
|
|
|
1528
1711
|
*/
|
|
1529
1712
|
getDevices(iface) {
|
|
1530
1713
|
return new Promise((resolve, reject) => {
|
|
1531
|
-
this.methodCall(iface, 'listDevices', [])
|
|
1532
|
-
|
|
1533
|
-
this.metadata.devices[iface]
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
const knownDevices = [];
|
|
1537
|
-
let change = false;
|
|
1538
|
-
devices.forEach(device => {
|
|
1539
|
-
knownDevices.push(device.ADDRESS);
|
|
1540
|
-
if (!this.metadata.devices[iface][device.ADDRESS]) {
|
|
1541
|
-
this.newDevice(iface, device);
|
|
1542
|
-
change = true;
|
|
1714
|
+
this.methodCall(iface, 'listDevices', [])
|
|
1715
|
+
.then((devices) => {
|
|
1716
|
+
if (!this.metadata.devices[iface]) {
|
|
1717
|
+
this.metadata.devices[iface] = {};
|
|
1543
1718
|
}
|
|
1544
|
-
});
|
|
1545
1719
|
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
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
|
+
});
|
|
1552
1729
|
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1730
|
+
Object.keys(this.metadata.devices[iface]).forEach((addr) => {
|
|
1731
|
+
if (!knownDevices.includes(addr)) {
|
|
1732
|
+
this.deleteDevice(iface, addr);
|
|
1733
|
+
change = true;
|
|
1734
|
+
}
|
|
1735
|
+
});
|
|
1556
1736
|
|
|
1557
|
-
|
|
1558
|
-
|
|
1737
|
+
if (change) {
|
|
1738
|
+
this.saveMetadata();
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
resolve();
|
|
1742
|
+
})
|
|
1743
|
+
.catch(reject);
|
|
1559
1744
|
});
|
|
1560
1745
|
}
|
|
1561
1746
|
|
|
@@ -1576,11 +1761,11 @@ module.exports = function (RED) {
|
|
|
1576
1761
|
this.hadTimeout.add(iface);
|
|
1577
1762
|
this.setIfaceStatus(iface, false);
|
|
1578
1763
|
this.logger.warn('ping timeout', iface, elapsed);
|
|
1579
|
-
this.rpcInit(iface).catch(error => this.logger.error(error.message));
|
|
1764
|
+
this.rpcInit(iface).catch((error) => this.logger.error(error.message));
|
|
1580
1765
|
return;
|
|
1581
1766
|
}
|
|
1582
1767
|
|
|
1583
|
-
if (elapsed >=
|
|
1768
|
+
if (elapsed >= pingTimeout / 2) {
|
|
1584
1769
|
//this.logger.trace('ping', iface, elapsed);
|
|
1585
1770
|
this.methodCall(iface, 'ping', ['nr']).catch(() => {
|
|
1586
1771
|
this.setIfaceStatus(iface, false);
|
|
@@ -1599,19 +1784,21 @@ module.exports = function (RED) {
|
|
|
1599
1784
|
rpcClose() {
|
|
1600
1785
|
this.logger.debug('rpcClose');
|
|
1601
1786
|
const calls = [];
|
|
1602
|
-
Object.keys(this.clients).forEach(iface => {
|
|
1787
|
+
Object.keys(this.clients).forEach((iface) => {
|
|
1603
1788
|
if (this.ifaceTypes[iface].init) {
|
|
1604
1789
|
this.logger.debug('queue de-init ' + iface + ' ' + this.initUrl(iface));
|
|
1605
1790
|
calls.push(() => {
|
|
1606
|
-
return new Promise(resolve => {
|
|
1791
|
+
return new Promise((resolve) => {
|
|
1607
1792
|
this.logger.debug('de-init ' + iface + ' ' + this.initUrl(iface));
|
|
1608
1793
|
this.methodCall(iface, 'init', [this.initUrl(iface), ''])
|
|
1609
1794
|
.then(() => {
|
|
1610
1795
|
this.logger.info('de-init ' + iface + ' ' + this.initUrl(iface) + ' done');
|
|
1611
1796
|
resolve();
|
|
1612
1797
|
})
|
|
1613
|
-
.catch(error => {
|
|
1614
|
-
this.logger.error(
|
|
1798
|
+
.catch((error) => {
|
|
1799
|
+
this.logger.error(
|
|
1800
|
+
'de-init ' + iface + ' ' + this.initUrl(iface) + ' failed ' + error,
|
|
1801
|
+
);
|
|
1615
1802
|
resolve();
|
|
1616
1803
|
});
|
|
1617
1804
|
});
|
|
@@ -1621,7 +1808,7 @@ module.exports = function (RED) {
|
|
|
1621
1808
|
|
|
1622
1809
|
this.logger.debug('queue binrpc server closing');
|
|
1623
1810
|
calls.push(() => {
|
|
1624
|
-
return new Promise(resolve => {
|
|
1811
|
+
return new Promise((resolve) => {
|
|
1625
1812
|
this.logger.debug('binrpc server closing');
|
|
1626
1813
|
let timeout;
|
|
1627
1814
|
if (this.servers.binrpc && this.servers.binrpc.server) {
|
|
@@ -1643,7 +1830,7 @@ module.exports = function (RED) {
|
|
|
1643
1830
|
|
|
1644
1831
|
this.logger.debug('xmlrpc binrpc server closing');
|
|
1645
1832
|
calls.push(() => {
|
|
1646
|
-
return new Promise(resolve => {
|
|
1833
|
+
return new Promise((resolve) => {
|
|
1647
1834
|
this.logger.debug('xmlrpc server closing');
|
|
1648
1835
|
let timeout;
|
|
1649
1836
|
if (this.servers.http && this.servers.http.close) {
|
|
@@ -1676,7 +1863,7 @@ module.exports = function (RED) {
|
|
|
1676
1863
|
*/
|
|
1677
1864
|
initUrl(iface) {
|
|
1678
1865
|
const {protocol} = this.ifaceTypes[iface];
|
|
1679
|
-
const port =
|
|
1866
|
+
const port = protocol === 'binrpc' ? this.rpcBinPort : this.rpcXmlPort;
|
|
1680
1867
|
return protocol + '://' + (this.rpcInitAddress || this.rpcServerHost) + ':' + port;
|
|
1681
1868
|
}
|
|
1682
1869
|
|
|
@@ -1688,7 +1875,7 @@ module.exports = function (RED) {
|
|
|
1688
1875
|
rpcServer(iface) {
|
|
1689
1876
|
const url = this.initUrl(iface);
|
|
1690
1877
|
const {rpc, protocol} = this.ifaceTypes[iface];
|
|
1691
|
-
const port =
|
|
1878
|
+
const port = protocol === 'binrpc' ? this.rpcBinPort : this.rpcXmlPort;
|
|
1692
1879
|
if (!this.servers[protocol]) {
|
|
1693
1880
|
this.servers[protocol] = rpc.createServer({host: this.rpcServerHost, port}, () => {
|
|
1694
1881
|
// Todo homematic-xmlrpc and binrpc module: clarify onListening callback params
|
|
@@ -1696,13 +1883,14 @@ module.exports = function (RED) {
|
|
|
1696
1883
|
this.serverError[iface] = null;
|
|
1697
1884
|
});
|
|
1698
1885
|
|
|
1699
|
-
// Todo
|
|
1700
|
-
|
|
1701
|
-
|
|
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);
|
|
1702
1890
|
this.serverError[iface] = err.message;
|
|
1703
1891
|
});
|
|
1704
1892
|
|
|
1705
|
-
Object.keys(this.rpcMethods).forEach(method => {
|
|
1893
|
+
Object.keys(this.rpcMethods).forEach((method) => {
|
|
1706
1894
|
this.servers[protocol].on(method, (err, parameters, callback) => {
|
|
1707
1895
|
if (err) {
|
|
1708
1896
|
this.logger.error('rpc <', protocol, method, err);
|
|
@@ -1716,7 +1904,14 @@ module.exports = function (RED) {
|
|
|
1716
1904
|
if (isIterable(parameters)) {
|
|
1717
1905
|
this.rpcMethods[method](err, parameters, callback);
|
|
1718
1906
|
} else {
|
|
1719
|
-
this.logger.error(
|
|
1907
|
+
this.logger.error(
|
|
1908
|
+
'rpc <',
|
|
1909
|
+
protocol,
|
|
1910
|
+
'method',
|
|
1911
|
+
method,
|
|
1912
|
+
'params not iterable',
|
|
1913
|
+
JSON.stringify(parameters),
|
|
1914
|
+
);
|
|
1720
1915
|
callback(null, '');
|
|
1721
1916
|
}
|
|
1722
1917
|
});
|
|
@@ -1763,7 +1958,7 @@ module.exports = function (RED) {
|
|
|
1763
1958
|
this.paramsQueue.push({
|
|
1764
1959
|
iface,
|
|
1765
1960
|
address: device.ADDRESS,
|
|
1766
|
-
paramset
|
|
1961
|
+
paramset,
|
|
1767
1962
|
});
|
|
1768
1963
|
}
|
|
1769
1964
|
|
|
@@ -1772,16 +1967,17 @@ module.exports = function (RED) {
|
|
|
1772
1967
|
if (item) {
|
|
1773
1968
|
const {iface, address, paramset} = item;
|
|
1774
1969
|
this.methodCall(iface, 'getParamset', [address, paramset])
|
|
1775
|
-
.then(res => {
|
|
1970
|
+
.then((res) => {
|
|
1776
1971
|
this.params[paramset][address] = res;
|
|
1777
1972
|
})
|
|
1778
|
-
.catch(error => this.logger.error(error))
|
|
1973
|
+
.catch((error) => this.logger.error(error))
|
|
1779
1974
|
.then(() => {
|
|
1780
1975
|
clearTimeout(this.getParamsTimeout);
|
|
1781
1976
|
this.getParamsTimeout = setTimeout(() => {
|
|
1782
1977
|
this.paramsQueueShift();
|
|
1783
1978
|
}, 200);
|
|
1784
|
-
})
|
|
1979
|
+
})
|
|
1980
|
+
.catch(() => {});
|
|
1785
1981
|
} else {
|
|
1786
1982
|
this.logger.debug(JSON.stringify(this.params));
|
|
1787
1983
|
}
|
|
@@ -1794,14 +1990,14 @@ module.exports = function (RED) {
|
|
|
1794
1990
|
*/
|
|
1795
1991
|
paramsetQueuePush(iface, device) {
|
|
1796
1992
|
if (device && device.PARAMSETS) {
|
|
1797
|
-
device.PARAMSETS.forEach(paramset => {
|
|
1993
|
+
device.PARAMSETS.forEach((paramset) => {
|
|
1798
1994
|
const name = this.paramsetName(iface, device, paramset);
|
|
1799
1995
|
if (!this.paramsetDescriptions[name]) {
|
|
1800
1996
|
this.paramsetQueue.push({
|
|
1801
1997
|
iface,
|
|
1802
1998
|
name,
|
|
1803
1999
|
address: device.ADDRESS,
|
|
1804
|
-
paramset
|
|
2000
|
+
paramset,
|
|
1805
2001
|
});
|
|
1806
2002
|
}
|
|
1807
2003
|
});
|
|
@@ -1833,7 +2029,7 @@ module.exports = function (RED) {
|
|
|
1833
2029
|
setImmediate(() => this.paramsetQueueShift());
|
|
1834
2030
|
} else {
|
|
1835
2031
|
this.methodCall(iface, 'getParamsetDescription', [address, paramset])
|
|
1836
|
-
.then(res => {
|
|
2032
|
+
.then((res) => {
|
|
1837
2033
|
//this.logger.trace('paramsetDescription', name);
|
|
1838
2034
|
this.newParamsetDescriptionCount += 1;
|
|
1839
2035
|
this.newParamsetDescription = true;
|
|
@@ -1844,14 +2040,15 @@ module.exports = function (RED) {
|
|
|
1844
2040
|
this.saveParamsets();
|
|
1845
2041
|
}
|
|
1846
2042
|
})
|
|
1847
|
-
.catch(error => this.logger.error(error))
|
|
2043
|
+
.catch((error) => this.logger.error(error))
|
|
1848
2044
|
.then(() => {
|
|
1849
2045
|
this.paramsetPending = false;
|
|
1850
2046
|
clearTimeout(this.getParamsetTimeout);
|
|
1851
2047
|
this.getParamsetTimeout = setTimeout(() => {
|
|
1852
2048
|
this.paramsetQueueShift();
|
|
1853
2049
|
}, 200);
|
|
1854
|
-
})
|
|
2050
|
+
})
|
|
2051
|
+
.catch(() => {});
|
|
1855
2052
|
}
|
|
1856
2053
|
} else {
|
|
1857
2054
|
this.paramsetPending = false;
|
|
@@ -1913,7 +2110,10 @@ module.exports = function (RED) {
|
|
|
1913
2110
|
throw new Error('device type undefined: ' + JSON.stringify(device));
|
|
1914
2111
|
}
|
|
1915
2112
|
|
|
1916
|
-
if (
|
|
2113
|
+
if (
|
|
2114
|
+
this.metadata.types[iface][device.TYPE] &&
|
|
2115
|
+
!this.metadata.types[iface][device.TYPE].includes(device.ADDRESS)
|
|
2116
|
+
) {
|
|
1917
2117
|
this.metadata.types[iface][device.TYPE].push(device.ADDRESS);
|
|
1918
2118
|
} else {
|
|
1919
2119
|
this.metadata.types[iface][device.TYPE] = [device.ADDRESS];
|
|
@@ -1944,7 +2144,7 @@ module.exports = function (RED) {
|
|
|
1944
2144
|
listDevices(iface) {
|
|
1945
2145
|
const result = [];
|
|
1946
2146
|
if (this.metadata.devices[iface]) {
|
|
1947
|
-
Object.keys(this.metadata.devices[iface]).forEach(addr => {
|
|
2147
|
+
Object.keys(this.metadata.devices[iface]).forEach((addr) => {
|
|
1948
2148
|
const dev = this.metadata.devices[iface][addr];
|
|
1949
2149
|
if (dev.TYPE === 'HmIP-RCV-50' || dev.PARENT_TYPE === 'HmIP-RCV-50') {
|
|
1950
2150
|
// Würgaround for Firmware 3.43.15
|
|
@@ -1973,8 +2173,8 @@ module.exports = function (RED) {
|
|
|
1973
2173
|
switch (iface) {
|
|
1974
2174
|
case 'HmIP-RF':
|
|
1975
2175
|
// fallthrough by intention
|
|
1976
|
-
case 'VirtualDevices':
|
|
1977
|
-
const d = {
|
|
2176
|
+
case 'VirtualDevices': {
|
|
2177
|
+
const d = {
|
|
1978
2178
|
ADDRESS: device.ADDRESS,
|
|
1979
2179
|
VERSION: device.VERSION,
|
|
1980
2180
|
AES_ACTIVE: device.AES_ACTIVE,
|
|
@@ -1996,9 +2196,9 @@ module.exports = function (RED) {
|
|
|
1996
2196
|
TEAM: device.TEAM,
|
|
1997
2197
|
TEAM_CHANNELS: device.TEAM_CHANNELS,
|
|
1998
2198
|
TEAM_TAG: device.TEAM_TAG,
|
|
1999
|
-
TYPE: device.TYPE
|
|
2199
|
+
TYPE: device.TYPE,
|
|
2000
2200
|
};
|
|
2001
|
-
Object.keys(d).forEach(k => {
|
|
2201
|
+
Object.keys(d).forEach((k) => {
|
|
2002
2202
|
if (typeof d[k] === 'undefined') {
|
|
2003
2203
|
delete d[k];
|
|
2004
2204
|
}
|
|
@@ -2010,6 +2210,8 @@ module.exports = function (RED) {
|
|
|
2010
2210
|
});
|
|
2011
2211
|
|
|
2012
2212
|
return d;
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2013
2215
|
default:
|
|
2014
2216
|
return {ADDRESS: device.ADDRESS, VERSION: device.VERSION};
|
|
2015
2217
|
}
|
|
@@ -2068,7 +2270,7 @@ module.exports = function (RED) {
|
|
|
2068
2270
|
const [idInit, devices] = parameters;
|
|
2069
2271
|
const iface = this.getIfaceFromIdInit(idInit);
|
|
2070
2272
|
|
|
2071
|
-
devices.forEach(device => {
|
|
2273
|
+
devices.forEach((device) => {
|
|
2072
2274
|
this.newDevice(iface, device);
|
|
2073
2275
|
});
|
|
2074
2276
|
|
|
@@ -2081,7 +2283,7 @@ module.exports = function (RED) {
|
|
|
2081
2283
|
const [idInit, devices] = parameters;
|
|
2082
2284
|
const iface = this.getIfaceFromIdInit(idInit);
|
|
2083
2285
|
|
|
2084
|
-
devices.forEach(device => {
|
|
2286
|
+
devices.forEach((device) => {
|
|
2085
2287
|
this.deleteDevice(iface, device);
|
|
2086
2288
|
});
|
|
2087
2289
|
|
|
@@ -2131,7 +2333,7 @@ module.exports = function (RED) {
|
|
|
2131
2333
|
let direction;
|
|
2132
2334
|
let pong = true;
|
|
2133
2335
|
if (isIterable(parameters[0])) {
|
|
2134
|
-
parameters[0].forEach(call => {
|
|
2336
|
+
parameters[0].forEach((call) => {
|
|
2135
2337
|
if (call && call.methodName === 'event') {
|
|
2136
2338
|
queue.push(call);
|
|
2137
2339
|
if (isIterable(call.params)) {
|
|
@@ -2163,13 +2365,20 @@ module.exports = function (RED) {
|
|
|
2163
2365
|
} else if (call && this.rpcMethods[call.methodName]) {
|
|
2164
2366
|
pong = false;
|
|
2165
2367
|
if (isIterable(call.params)) {
|
|
2166
|
-
this.rpcMethods[call.methodName](call.methodName, call.params, (_, res) =>
|
|
2368
|
+
this.rpcMethods[call.methodName](call.methodName, call.params, (_, res) =>
|
|
2369
|
+
result.push(res),
|
|
2370
|
+
);
|
|
2167
2371
|
} else {
|
|
2168
|
-
this.logger.error(
|
|
2372
|
+
this.logger.error(
|
|
2373
|
+
'rpc <',
|
|
2374
|
+
call.methodName,
|
|
2375
|
+
'params not iterable',
|
|
2376
|
+
JSON.stringify(call.params),
|
|
2377
|
+
);
|
|
2169
2378
|
}
|
|
2170
2379
|
}
|
|
2171
2380
|
});
|
|
2172
|
-
queue.forEach(call => {
|
|
2381
|
+
queue.forEach((call) => {
|
|
2173
2382
|
this.publishEvent(call.params, working, direction);
|
|
2174
2383
|
});
|
|
2175
2384
|
}
|
|
@@ -2185,7 +2394,7 @@ module.exports = function (RED) {
|
|
|
2185
2394
|
}
|
|
2186
2395
|
|
|
2187
2396
|
callback(null, result);
|
|
2188
|
-
}
|
|
2397
|
+
},
|
|
2189
2398
|
};
|
|
2190
2399
|
}
|
|
2191
2400
|
|
|
@@ -2296,7 +2505,7 @@ module.exports = function (RED) {
|
|
|
2296
2505
|
'room',
|
|
2297
2506
|
'function',
|
|
2298
2507
|
'rooms',
|
|
2299
|
-
'functions'
|
|
2508
|
+
'functions',
|
|
2300
2509
|
]);
|
|
2301
2510
|
|
|
2302
2511
|
const propertiesArray = Object.keys(filter);
|
|
@@ -2315,7 +2524,7 @@ module.exports = function (RED) {
|
|
|
2315
2524
|
this.callbacks[id] = {filter, callback};
|
|
2316
2525
|
|
|
2317
2526
|
if (filter.cache && this.cachedValuesReceived) {
|
|
2318
|
-
Object.keys(this.values).forEach(dp => {
|
|
2527
|
+
Object.keys(this.values).forEach((dp) => {
|
|
2319
2528
|
const message = {...this.values[dp]};
|
|
2320
2529
|
message.cache = true;
|
|
2321
2530
|
message.change = false;
|
|
@@ -2344,11 +2553,11 @@ module.exports = function (RED) {
|
|
|
2344
2553
|
this.logger.trace('unsubscribe', id);
|
|
2345
2554
|
delete this.callbacks[id];
|
|
2346
2555
|
|
|
2347
|
-
Object.keys(this.callbackBlacklists).forEach(dp => {
|
|
2556
|
+
Object.keys(this.callbackBlacklists).forEach((dp) => {
|
|
2348
2557
|
this.callbackBlacklists[dp].delete(id);
|
|
2349
2558
|
});
|
|
2350
2559
|
|
|
2351
|
-
Object.keys(this.callbackWhitelists).forEach(dp => {
|
|
2560
|
+
Object.keys(this.callbackWhitelists).forEach((dp) => {
|
|
2352
2561
|
this.callbackWhitelists[dp].delete(id);
|
|
2353
2562
|
});
|
|
2354
2563
|
|
|
@@ -2371,13 +2580,13 @@ module.exports = function (RED) {
|
|
|
2371
2580
|
}
|
|
2372
2581
|
|
|
2373
2582
|
const messageLower = {};
|
|
2374
|
-
Object.keys(message).forEach(k => {
|
|
2583
|
+
Object.keys(message).forEach((k) => {
|
|
2375
2584
|
messageLower[k.toLowerCase()] = message[k];
|
|
2376
2585
|
});
|
|
2377
2586
|
|
|
2378
2587
|
const match = topic.match(/\${[^}]+}/g);
|
|
2379
2588
|
if (match) {
|
|
2380
|
-
match.forEach(v => {
|
|
2589
|
+
match.forEach((v) => {
|
|
2381
2590
|
const key = v.substr(2, v.length - 3);
|
|
2382
2591
|
const rx = new RegExp('\\${' + key + '}', 'g');
|
|
2383
2592
|
let rkey = key.toLowerCase();
|
|
@@ -2407,19 +2616,25 @@ module.exports = function (RED) {
|
|
|
2407
2616
|
this.values[datapointName] = {};
|
|
2408
2617
|
}
|
|
2409
2618
|
|
|
2410
|
-
const device =
|
|
2619
|
+
const device =
|
|
2620
|
+
this.metadata.devices[iface] &&
|
|
2621
|
+
this.metadata.devices[iface][channel] &&
|
|
2622
|
+
this.metadata.devices[iface][channel].PARENT;
|
|
2411
2623
|
const ts = now();
|
|
2412
2624
|
let change = false;
|
|
2413
2625
|
|
|
2414
|
-
const valueStable =
|
|
2626
|
+
const valueStable = additions && additions.working ? this.values[datapointName].valueStable : payload;
|
|
2415
2627
|
|
|
2416
2628
|
let description = {};
|
|
2417
2629
|
if (this.metadata.devices[iface] && this.metadata.devices[iface][channel]) {
|
|
2418
|
-
description =
|
|
2630
|
+
description =
|
|
2631
|
+
this.getParamsetDescription(iface, this.metadata.devices[iface][channel], 'VALUES', datapoint) ||
|
|
2632
|
+
{};
|
|
2419
2633
|
}
|
|
2420
2634
|
|
|
2421
2635
|
if (
|
|
2422
2636
|
description.TYPE === 'ACTION' ||
|
|
2637
|
+
this.values[datapointName].cache ||
|
|
2423
2638
|
this.values[datapointName].payload !== payload ||
|
|
2424
2639
|
this.values[datapointName].valueStable !== valueStable
|
|
2425
2640
|
) {
|
|
@@ -2428,16 +2643,23 @@ module.exports = function (RED) {
|
|
|
2428
2643
|
|
|
2429
2644
|
this.logger.trace('createMessage', channel, datapoint, payload, 'change=' + change);
|
|
2430
2645
|
|
|
2431
|
-
const message = {
|
|
2646
|
+
const message = {
|
|
2647
|
+
topic: '',
|
|
2432
2648
|
payload,
|
|
2433
2649
|
ccu: this.host,
|
|
2434
2650
|
iface,
|
|
2435
2651
|
device,
|
|
2436
2652
|
deviceName: this.channelNames[device],
|
|
2437
|
-
deviceType:
|
|
2653
|
+
deviceType:
|
|
2654
|
+
this.metadata.devices[iface] &&
|
|
2655
|
+
this.metadata.devices[iface][device] &&
|
|
2656
|
+
this.metadata.devices[iface][device].TYPE,
|
|
2438
2657
|
channel,
|
|
2439
2658
|
channelName: this.channelNames[channel],
|
|
2440
|
-
channelType:
|
|
2659
|
+
channelType:
|
|
2660
|
+
this.metadata.devices[iface] &&
|
|
2661
|
+
this.metadata.devices[iface][channel] &&
|
|
2662
|
+
this.metadata.devices[iface][channel].TYPE,
|
|
2441
2663
|
channelIndex: channel && Number.parseInt(channel.split(':')[1], 10),
|
|
2442
2664
|
datapoint,
|
|
2443
2665
|
datapointName,
|
|
@@ -2452,13 +2674,21 @@ module.exports = function (RED) {
|
|
|
2452
2674
|
valueEnum: description.ENUM ? description.ENUM[Number(payload)] : undefined,
|
|
2453
2675
|
valueStable,
|
|
2454
2676
|
rooms: this.channelRooms[channel] || [],
|
|
2455
|
-
room:
|
|
2677
|
+
room:
|
|
2678
|
+
this.channelRooms[channel] && this.channelRooms[channel].length > 0
|
|
2679
|
+
? this.channelRooms[channel][0]
|
|
2680
|
+
: undefined,
|
|
2456
2681
|
functions: this.channelFunctions[channel] || [],
|
|
2457
|
-
function:
|
|
2682
|
+
function:
|
|
2683
|
+
this.channelFunctions[channel] && this.channelFunctions[channel].length > 0
|
|
2684
|
+
? this.channelFunctions[channel][0]
|
|
2685
|
+
: undefined,
|
|
2458
2686
|
ts,
|
|
2459
2687
|
tsPrevious: this.values[datapointName].ts,
|
|
2460
2688
|
lc: change ? ts : this.values[datapointName].lc,
|
|
2461
|
-
change,
|
|
2689
|
+
change,
|
|
2690
|
+
...additions,
|
|
2691
|
+
};
|
|
2462
2692
|
|
|
2463
2693
|
message.stable = !message.working;
|
|
2464
2694
|
|
|
@@ -2487,16 +2717,27 @@ module.exports = function (RED) {
|
|
|
2487
2717
|
|
|
2488
2718
|
//this.logger.trace('publishEvent', JSON.stringify(params));
|
|
2489
2719
|
|
|
2490
|
-
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
|
+
});
|
|
2491
2726
|
|
|
2492
2727
|
let waitForWorking = false;
|
|
2493
2728
|
|
|
2494
2729
|
if (message.channelType && !working) {
|
|
2495
|
-
if (
|
|
2730
|
+
if (
|
|
2731
|
+
message.datapoint === 'STATE' &&
|
|
2732
|
+
message.channelType.match(/SIGNAL|SWITCH|RAINDETECTOR_HEAT|ALARMACTUATOR/)
|
|
2733
|
+
) {
|
|
2496
2734
|
waitForWorking = true;
|
|
2497
2735
|
} else if (message.datapoint === 'ARMSTATE' && message.channelType === 'ARMING') {
|
|
2498
2736
|
waitForWorking = true;
|
|
2499
|
-
} else if (
|
|
2737
|
+
} else if (
|
|
2738
|
+
message.datapoint.startsWith('LEVEL') &&
|
|
2739
|
+
message.channelType.match(/DIMMER|DUAL_WHITE|BLIND|SHUTTER|JALOUSIE|WINMATIC|KEYMATIC/)
|
|
2740
|
+
) {
|
|
2500
2741
|
waitForWorking = true;
|
|
2501
2742
|
}
|
|
2502
2743
|
}
|
|
@@ -2506,9 +2747,17 @@ module.exports = function (RED) {
|
|
|
2506
2747
|
this.workingTimeout[message.datapointName] = setTimeout(() => {
|
|
2507
2748
|
const datapointNamePrefix = iface + '.' + channel + '.';
|
|
2508
2749
|
|
|
2509
|
-
if (
|
|
2510
|
-
|
|
2511
|
-
|
|
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);
|
|
2512
2761
|
message.working = Boolean(message.working);
|
|
2513
2762
|
} else if (this.values[datapointNamePrefix + 'PROCESS']) {
|
|
2514
2763
|
message.working = Boolean(this.values[datapointNamePrefix + 'PROCESS'].value);
|
|
@@ -2555,7 +2804,7 @@ module.exports = function (RED) {
|
|
|
2555
2804
|
if (filter) {
|
|
2556
2805
|
const arrayAttr = Object.keys(filter);
|
|
2557
2806
|
|
|
2558
|
-
for (let i = 0, {length} = arrayAttr; match &&
|
|
2807
|
+
for (let i = 0, {length} = arrayAttr; match && i < length; i++) {
|
|
2559
2808
|
const attr = arrayAttr[i];
|
|
2560
2809
|
|
|
2561
2810
|
if (attr === 'cache') {
|
|
@@ -2571,7 +2820,7 @@ module.exports = function (RED) {
|
|
|
2571
2820
|
|
|
2572
2821
|
if (attr === 'change') {
|
|
2573
2822
|
// if filter.change==true - Drop messages with msg.change==false - except msg.cache==true && filter.cache==true
|
|
2574
|
-
if (
|
|
2823
|
+
if (filter.change && !message.change && !(filter.cache && message.cache)) {
|
|
2575
2824
|
//this.logger.trace('cb mismatch change ' + id + ' ' + filter.change + ' ' + msg.change + ' ' + msg.cache);
|
|
2576
2825
|
return false;
|
|
2577
2826
|
}
|
|
@@ -2599,7 +2848,8 @@ module.exports = function (RED) {
|
|
|
2599
2848
|
continue;
|
|
2600
2849
|
}
|
|
2601
2850
|
|
|
2602
|
-
if (filter[attr] === '') {
|
|
2851
|
+
if (filter[attr] === '') {
|
|
2852
|
+
// TODO rethink
|
|
2603
2853
|
continue;
|
|
2604
2854
|
}
|
|
2605
2855
|
|
|
@@ -2611,7 +2861,7 @@ module.exports = function (RED) {
|
|
|
2611
2861
|
if (filter[attr] instanceof RegExp) {
|
|
2612
2862
|
match = false;
|
|
2613
2863
|
this.logger.trace('cb test regex array', id, attr, filter[attr], message[attr]);
|
|
2614
|
-
message[attr].forEach(item => {
|
|
2864
|
+
message[attr].forEach((item) => {
|
|
2615
2865
|
if (filter[attr].test(item)) {
|
|
2616
2866
|
match = true;
|
|
2617
2867
|
}
|
|
@@ -2626,7 +2876,9 @@ module.exports = function (RED) {
|
|
|
2626
2876
|
match = false;
|
|
2627
2877
|
}
|
|
2628
2878
|
} else if (filter[attr] !== message[attr]) {
|
|
2629
|
-
this.logger.trace(
|
|
2879
|
+
this.logger.trace(
|
|
2880
|
+
'cb mismatch misc ' + id + ' ' + attr + ' ' + filter[attr] + ' ' + message[attr],
|
|
2881
|
+
);
|
|
2630
2882
|
match = false;
|
|
2631
2883
|
}
|
|
2632
2884
|
}
|
|
@@ -2658,7 +2910,7 @@ module.exports = function (RED) {
|
|
|
2658
2910
|
this.callbackWhitelists[message.datapointName] = new Set();
|
|
2659
2911
|
}
|
|
2660
2912
|
|
|
2661
|
-
Object.keys(this.callbacks).forEach(key => {
|
|
2913
|
+
Object.keys(this.callbacks).forEach((key) => {
|
|
2662
2914
|
if (this.callbackBlacklists[message.datapointName].has(key)) {
|
|
2663
2915
|
//this.logger.trace('blacklistet ' + key + ' ' + msg.datapointName);
|
|
2664
2916
|
return;
|
|
@@ -2708,7 +2960,16 @@ module.exports = function (RED) {
|
|
|
2708
2960
|
this.methodCallQueue[iface] = [[method, parameters, resolve, reject]];
|
|
2709
2961
|
}
|
|
2710
2962
|
} else {
|
|
2711
|
-
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
|
+
);
|
|
2712
2973
|
}
|
|
2713
2974
|
});
|
|
2714
2975
|
}
|
|
@@ -2725,12 +2986,13 @@ module.exports = function (RED) {
|
|
|
2725
2986
|
*/
|
|
2726
2987
|
setValueQueued(iface, address, datapoint, value, burst, force) {
|
|
2727
2988
|
return new Promise((resolve, reject) => {
|
|
2728
|
-
this.setValueQueue = this.setValueQueue.filter(element => {
|
|
2989
|
+
this.setValueQueue = this.setValueQueue.filter((element) => {
|
|
2729
2990
|
return element.iface !== iface || element.address !== address || element.datapoint !== datapoint;
|
|
2730
2991
|
});
|
|
2731
2992
|
const datapointName = iface + '.' + address + '.' + datapoint;
|
|
2732
2993
|
const currentValue = this.values[datapointName] && this.values[datapointName].value;
|
|
2733
|
-
|
|
2994
|
+
const cache = this.values[datapointName] && this.values[datapointName].cache;
|
|
2995
|
+
if (force || value !== currentValue || cache || datapoint.startsWith('PRESS_')) {
|
|
2734
2996
|
this.setValueQueue.push({iface, address, datapoint, value, burst, resolve, reject});
|
|
2735
2997
|
this.setValueShiftQueue();
|
|
2736
2998
|
} else {
|
|
@@ -2766,7 +3028,7 @@ module.exports = function (RED) {
|
|
|
2766
3028
|
resolve();
|
|
2767
3029
|
}
|
|
2768
3030
|
})
|
|
2769
|
-
.catch(error => {
|
|
3031
|
+
.catch((error) => {
|
|
2770
3032
|
if (!timeout) {
|
|
2771
3033
|
reject(error);
|
|
2772
3034
|
}
|
|
@@ -2815,10 +3077,12 @@ module.exports = function (RED) {
|
|
|
2815
3077
|
}, this.setValueThrottle);
|
|
2816
3078
|
}
|
|
2817
3079
|
|
|
2818
|
-
this.methodCall(iface, 'setValue', parameters)
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
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
|
+
});
|
|
2822
3086
|
}
|
|
2823
3087
|
});
|
|
2824
3088
|
}
|
|
@@ -2834,10 +3098,12 @@ module.exports = function (RED) {
|
|
|
2834
3098
|
const [iface] = id.split('.');
|
|
2835
3099
|
const {params, resolve, reject} = this.setValueCache[id];
|
|
2836
3100
|
delete this.setValueCache[id];
|
|
2837
|
-
return this.methodCall(iface, 'setValue', params)
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
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
|
+
});
|
|
2841
3107
|
}
|
|
2842
3108
|
}
|
|
2843
3109
|
|
|
@@ -2861,7 +3127,8 @@ module.exports = function (RED) {
|
|
|
2861
3127
|
case 'BOOL':
|
|
2862
3128
|
if (value === 'false') {
|
|
2863
3129
|
value = false;
|
|
2864
|
-
} else if (!isNaN(value)) {
|
|
3130
|
+
} else if (!isNaN(value)) {
|
|
3131
|
+
// Make sure that the string "0" gets casted to boolean false
|
|
2865
3132
|
value = Number(value);
|
|
2866
3133
|
}
|
|
2867
3134
|
|
|
@@ -2880,7 +3147,7 @@ module.exports = function (RED) {
|
|
|
2880
3147
|
break;
|
|
2881
3148
|
case 'ENUM':
|
|
2882
3149
|
if (typeof value === 'string') {
|
|
2883
|
-
if (paramset.ENUM &&
|
|
3150
|
+
if (paramset.ENUM && paramset.ENUM.includes(value)) {
|
|
2884
3151
|
value = paramset.ENUM.indexOf(value);
|
|
2885
3152
|
}
|
|
2886
3153
|
}
|
|
@@ -2924,7 +3191,7 @@ module.exports = function (RED) {
|
|
|
2924
3191
|
*/
|
|
2925
3192
|
script(script) {
|
|
2926
3193
|
return new Promise((resolve, reject) => {
|
|
2927
|
-
this.rega.exec(script, (err, payload, objects) => {
|
|
3194
|
+
execToCallback(this.rega.exec(script), (err, payload, objects) => {
|
|
2928
3195
|
if (err) {
|
|
2929
3196
|
reject(err);
|
|
2930
3197
|
} else {
|
|
@@ -2935,7 +3202,5 @@ module.exports = function (RED) {
|
|
|
2935
3202
|
}
|
|
2936
3203
|
}
|
|
2937
3204
|
|
|
2938
|
-
RED.nodes.registerType('ccu-connection', CcuConnectionNode, {
|
|
2939
|
-
|
|
2940
|
-
});
|
|
3205
|
+
RED.nodes.registerType('ccu-connection', CcuConnectionNode, {});
|
|
2941
3206
|
};
|