node-red-contrib-ccu 4.2.0 → 4.4.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 +3 -0
- package/CHANGELOG.md +140 -0
- package/HANDOFF.md +46 -10
- package/README.md +71 -0
- package/ROADMAP.md +130 -47
- package/nodes/ccu-alexa.html +3 -1
- package/nodes/ccu-connection.html +51 -0
- package/nodes/ccu-connection.js +323 -16
- package/nodes/ccu-display.js +29 -13
- package/nodes/ccu-get-value.html +3 -1
- package/nodes/ccu-mqtt.html +21 -4
- package/nodes/ccu-mqtt.js +126 -102
- package/nodes/ccu-poll.js +6 -0
- package/nodes/ccu-rpc-event.html +6 -0
- package/nodes/ccu-rpc-event.js +31 -12
- package/nodes/ccu-set-value.html +38 -0
- package/nodes/ccu-set-value.js +59 -235
- package/nodes/ccu-signal.html +55 -1
- package/nodes/ccu-signal.js +52 -9
- package/nodes/ccu-switch.html +17 -4
- package/nodes/ccu-switch.js +28 -4
- package/nodes/ccu-sysvar.html +40 -1
- package/nodes/ccu-sysvar.js +78 -8
- package/nodes/ccu-value.html +27 -1
- package/nodes/ccu-value.js +69 -19
- package/nodes/lib/cast.js +59 -14
- package/nodes/lib/channelfilter.js +136 -0
- package/nodes/lib/dynconfig.js +81 -0
- package/nodes/lib/localccu.js +81 -0
- package/nodes/lib/message.js +6 -2
- package/nodes/lib/metaprovider.js +716 -0
- package/nodes/lib/valuestatus.js +145 -0
- package/nodes/locales/de/ccu-display.html +17 -0
- package/nodes/locales/de/ccu-rpc-event.json +1 -0
- package/nodes/locales/de/ccu-sysvar.json +3 -2
- package/nodes/locales/de/ccu-value.json +2 -1
- package/nodes/locales/en-US/ccu-display.html +17 -0
- package/nodes/locales/en-US/ccu-rpc-event.json +3 -2
- package/nodes/locales/en-US/ccu-switch.html +12 -0
- package/nodes/locales/en-US/ccu-sysvar.json +3 -2
- package/nodes/locales/en-US/ccu-value.json +2 -1
- package/package.json +1 -1
- package/patch-certainonly.py +51 -0
- package/roadmap-archive/B-17.md +106 -0
- package/roadmap-archive/B-2.md +69 -0
- package/roadmap-archive/B-3.md +57 -0
- package/roadmap-archive/B-4.md +44 -0
- package/roadmap-archive/B-5.md +57 -0
- package/roadmap-archive/README.md +14 -0
package/nodes/ccu-mqtt.js
CHANGED
|
@@ -2,6 +2,7 @@ const path = require('path');
|
|
|
2
2
|
const mw = require('mqtt-wildcard');
|
|
3
3
|
|
|
4
4
|
const statusHelper = require(path.join(__dirname, '/lib/status.js'));
|
|
5
|
+
const {castValue} = require(path.join(__dirname, '/lib/cast.js'));
|
|
5
6
|
|
|
6
7
|
/** HM thermostats: CONTROL_MODE is read-only, the modes are set through actions */
|
|
7
8
|
const HM_MODES = {
|
|
@@ -35,7 +36,10 @@ module.exports = function (RED) {
|
|
|
35
36
|
this.topicInputPutParam = config.topicInputPutParam;
|
|
36
37
|
this.topicInputPutParamset = config.topicInputPutParamset;
|
|
37
38
|
|
|
39
|
+
this.topicInputGet = config.topicInputGet;
|
|
40
|
+
|
|
38
41
|
this.topicInputRpc = config.topicInputRpc;
|
|
42
|
+
this.topicOutputRpc = config.topicOutputRpc;
|
|
39
43
|
|
|
40
44
|
this.topicCounters = config.topicCounters;
|
|
41
45
|
this.rxCounters = {};
|
|
@@ -179,6 +183,7 @@ module.exports = function (RED) {
|
|
|
179
183
|
this.debug('input ' + topic + ' ' + JSON.stringify(payload).slice(0, 40));
|
|
180
184
|
|
|
181
185
|
const topicList = {
|
|
186
|
+
get: this.topicInputGet,
|
|
182
187
|
setValue: this.topicInputSetValue,
|
|
183
188
|
sysvar: this.topicInputSysvar,
|
|
184
189
|
putParam: this.topicInputPutParam,
|
|
@@ -189,7 +194,8 @@ module.exports = function (RED) {
|
|
|
189
194
|
let command;
|
|
190
195
|
let filter;
|
|
191
196
|
Object.keys(topicList).forEach((key) => {
|
|
192
|
-
|
|
197
|
+
// a topic left empty in the editor disables that command
|
|
198
|
+
if (!command && topicList[key]) {
|
|
193
199
|
const parts = topicList[key].split('/');
|
|
194
200
|
const patternArray = [];
|
|
195
201
|
const placeholders = [];
|
|
@@ -220,29 +226,129 @@ module.exports = function (RED) {
|
|
|
220
226
|
}
|
|
221
227
|
}
|
|
222
228
|
|
|
223
|
-
|
|
229
|
+
/**
|
|
230
|
+
* Resolve `channelNameOrAddress` to a channel address and its
|
|
231
|
+
* interface, reporting the same errors the commands used to report
|
|
232
|
+
* inline.
|
|
233
|
+
* @param {object} filter parsed from the topic; `channel` is filled in
|
|
234
|
+
* @param {boolean} [exact] pass through to findChannel
|
|
235
|
+
* @returns {string|null} the interface, or null when unresolvable
|
|
236
|
+
*/
|
|
237
|
+
resolveChannel(filter, exact) {
|
|
224
238
|
if (filter.channelNameOrAddress) {
|
|
225
239
|
if (this.ccu.channelNames[filter.channelNameOrAddress]) {
|
|
226
240
|
filter.channel = filter.channelNameOrAddress;
|
|
227
241
|
} else {
|
|
228
|
-
filter.channel = this.ccu.findChannel(filter.channelNameOrAddress,
|
|
242
|
+
filter.channel = this.ccu.findChannel(filter.channelNameOrAddress, exact);
|
|
229
243
|
}
|
|
230
244
|
|
|
231
245
|
if (!filter.channel) {
|
|
232
246
|
this.error('channel ' + filter.channelNameOrAddress + ' not found');
|
|
233
|
-
return;
|
|
247
|
+
return null;
|
|
234
248
|
}
|
|
235
249
|
}
|
|
236
250
|
|
|
237
251
|
if (!filter.channel) {
|
|
238
252
|
this.error('channel undefined');
|
|
239
|
-
return;
|
|
253
|
+
return null;
|
|
240
254
|
}
|
|
241
255
|
|
|
242
256
|
const iface = this.ccu.findIface(filter.channel);
|
|
243
257
|
|
|
244
258
|
if (!iface) {
|
|
245
259
|
this.error('no interface found for channel ' + filter.channel);
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return iface;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Republish the last known value of a datapoint on its status topic,
|
|
268
|
+
* so a flow can ask for a refresh over MQTT instead of waiting for the
|
|
269
|
+
* next event (#115). An empty datapoint republishes the whole channel.
|
|
270
|
+
* @param {object} filter
|
|
271
|
+
*/
|
|
272
|
+
get(filter) {
|
|
273
|
+
const iface = this.resolveChannel(filter, true);
|
|
274
|
+
if (!iface) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const prefix = iface + '.' + filter.channel + '.';
|
|
279
|
+
const names = filter.datapoint
|
|
280
|
+
? [prefix + filter.datapoint]
|
|
281
|
+
: Object.keys(this.ccu.values).filter((name) => name.startsWith(prefix));
|
|
282
|
+
|
|
283
|
+
if (names.length === 0) {
|
|
284
|
+
this.error('unknown datapoint ' + prefix + (filter.datapoint || '#'));
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
names.forEach((name) => {
|
|
289
|
+
const value = this.ccu.values[name];
|
|
290
|
+
if (value) {
|
|
291
|
+
this.event({...value});
|
|
292
|
+
} else {
|
|
293
|
+
this.error('unknown datapoint ' + name);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Call an arbitrary RPC method on an interface and publish the result
|
|
300
|
+
* (#22). The topic supplies interface, method and a caller-chosen id
|
|
301
|
+
* that the response topic echoes; the payload is the parameter array
|
|
302
|
+
* (a single value is accepted and wrapped).
|
|
303
|
+
* @param {object} filter
|
|
304
|
+
* @param {*} payload
|
|
305
|
+
*/
|
|
306
|
+
rpc(filter, payload) {
|
|
307
|
+
if (!filter.iface) {
|
|
308
|
+
this.error('interface undefined');
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (!filter.method) {
|
|
313
|
+
this.error('method undefined');
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
let parameters = payload;
|
|
318
|
+
if (parameters === undefined || parameters === null || parameters === '') {
|
|
319
|
+
parameters = [];
|
|
320
|
+
} else if (!Array.isArray(parameters)) {
|
|
321
|
+
parameters = [parameters];
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const respond = (result, error) => {
|
|
325
|
+
if (!this.topicOutputRpc) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
this.send({
|
|
330
|
+
topic: this.ccu.topicReplace(this.topicOutputRpc, {
|
|
331
|
+
iface: filter.iface,
|
|
332
|
+
method: filter.method,
|
|
333
|
+
callid: filter.callid,
|
|
334
|
+
}),
|
|
335
|
+
payload: error ? {error: error.message} : {result},
|
|
336
|
+
retain: false,
|
|
337
|
+
});
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
this.ccu
|
|
341
|
+
.methodCall(filter.iface, filter.method, parameters)
|
|
342
|
+
.then((result) => respond(result))
|
|
343
|
+
.catch((error) => {
|
|
344
|
+
this.error(error.message);
|
|
345
|
+
respond(undefined, error);
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
setValue(filter, payload) {
|
|
350
|
+
const iface = this.resolveChannel(filter, true);
|
|
351
|
+
if (!iface) {
|
|
246
352
|
return;
|
|
247
353
|
}
|
|
248
354
|
|
|
@@ -313,28 +419,8 @@ module.exports = function (RED) {
|
|
|
313
419
|
}
|
|
314
420
|
|
|
315
421
|
putParam(filter, payload) {
|
|
316
|
-
|
|
317
|
-
if (this.ccu.channelNames[filter.channelNameOrAddress]) {
|
|
318
|
-
filter.channel = filter.channelNameOrAddress;
|
|
319
|
-
} else {
|
|
320
|
-
filter.channel = this.ccu.findChannel(filter.channelNameOrAddress);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (!filter.channel) {
|
|
324
|
-
this.error('channel ' + filter.channelNameOrAddress + ' not found');
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (!filter.channel) {
|
|
330
|
-
this.error('channel undefined');
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const iface = this.ccu.findIface(filter.channel);
|
|
335
|
-
|
|
422
|
+
const iface = this.resolveChannel(filter);
|
|
336
423
|
if (!iface) {
|
|
337
|
-
this.error('no interface found for channel ' + filter.channel);
|
|
338
424
|
return;
|
|
339
425
|
}
|
|
340
426
|
|
|
@@ -345,11 +431,14 @@ module.exports = function (RED) {
|
|
|
345
431
|
);
|
|
346
432
|
const paramsetDescription = this.ccu.paramsetDescriptions[psName];
|
|
347
433
|
if (paramsetDescription && paramsetDescription[filter.param]) {
|
|
348
|
-
|
|
434
|
+
// was `!(OPERATIONS) && 2`, which is always false - the check
|
|
435
|
+
// never fired and the write went out regardless (B-3)
|
|
436
|
+
if (!(paramsetDescription[filter.param].OPERATIONS & 2)) {
|
|
349
437
|
this.error('param ' + filter.param + ' not writeable');
|
|
438
|
+
return;
|
|
350
439
|
}
|
|
351
440
|
|
|
352
|
-
payload =
|
|
441
|
+
payload = castValue(payload, paramsetDescription[filter.param], {clamp: true});
|
|
353
442
|
} else {
|
|
354
443
|
this.warn('unknown paramset/param ' + filter.paramset + ' ' + filter.param);
|
|
355
444
|
}
|
|
@@ -368,28 +457,8 @@ module.exports = function (RED) {
|
|
|
368
457
|
return;
|
|
369
458
|
}
|
|
370
459
|
|
|
371
|
-
|
|
372
|
-
if (this.ccu.channelNames[filter.channelNameOrAddress]) {
|
|
373
|
-
filter.channel = filter.channelNameOrAddress;
|
|
374
|
-
} else {
|
|
375
|
-
filter.channel = this.ccu.findChannel(filter.channelNameOrAddress);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
if (!filter.channel) {
|
|
379
|
-
this.error('channel ' + filter.channelNameOrAddress + ' not found');
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
if (!filter.channel) {
|
|
385
|
-
this.error('channel undefined');
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
const iface = this.ccu.findIface(filter.channel);
|
|
390
|
-
|
|
460
|
+
const iface = this.resolveChannel(filter);
|
|
391
461
|
if (!iface) {
|
|
392
|
-
this.error('no interface found for channel ' + filter.channel);
|
|
393
462
|
return;
|
|
394
463
|
}
|
|
395
464
|
|
|
@@ -404,11 +473,17 @@ module.exports = function (RED) {
|
|
|
404
473
|
|
|
405
474
|
Object.keys(payload).forEach((parameter) => {
|
|
406
475
|
if (paramsetDescription && paramsetDescription[parameter]) {
|
|
407
|
-
|
|
476
|
+
// was `!(OPERATIONS) && 2` (always false), and the cast
|
|
477
|
+
// used paramsetDescription[filter.param], which is
|
|
478
|
+
// undefined here - so every value went out uncast (B-3)
|
|
479
|
+
if (!(paramsetDescription[parameter].OPERATIONS & 2)) {
|
|
408
480
|
this.error('param ' + parameter + ' not writeable');
|
|
481
|
+
return;
|
|
409
482
|
}
|
|
410
483
|
|
|
411
|
-
paramset[parameter] =
|
|
484
|
+
paramset[parameter] = castValue(payload[parameter], paramsetDescription[parameter], {
|
|
485
|
+
clamp: true,
|
|
486
|
+
});
|
|
412
487
|
} else {
|
|
413
488
|
this.warn('unknown paramset/param ' + filter.paramset + ' ' + parameter);
|
|
414
489
|
paramset[parameter] = payload[parameter];
|
|
@@ -419,57 +494,6 @@ module.exports = function (RED) {
|
|
|
419
494
|
.methodCall(iface, 'putParamset', [filter.channel, filter.paramset, paramset])
|
|
420
495
|
.catch((error) => this.error(error.message));
|
|
421
496
|
}
|
|
422
|
-
|
|
423
|
-
paramCast(value, paramset) {
|
|
424
|
-
switch (paramset && paramset.TYPE) {
|
|
425
|
-
case 'BOOL':
|
|
426
|
-
// Fallthrough by intention
|
|
427
|
-
case 'ACTION':
|
|
428
|
-
// OMG this is so ugly...
|
|
429
|
-
if (value === 'false') {
|
|
430
|
-
value = false;
|
|
431
|
-
} else if (!isNaN(value)) {
|
|
432
|
-
// Make sure that the string "0" gets casted to boolean false
|
|
433
|
-
value = Number(value);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
value = Boolean(value);
|
|
437
|
-
break;
|
|
438
|
-
case 'FLOAT':
|
|
439
|
-
value = Number.parseFloat(value);
|
|
440
|
-
if (value < paramset.MIN) {
|
|
441
|
-
value = paramset.MIN;
|
|
442
|
-
} else if (value > paramset.MAX) {
|
|
443
|
-
value = paramset.MAX;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
value = {explicitDouble: value};
|
|
447
|
-
break;
|
|
448
|
-
case 'ENUM':
|
|
449
|
-
if (typeof value === 'string') {
|
|
450
|
-
if (paramset.ENUM && paramset.ENUM.includes(value)) {
|
|
451
|
-
value = paramset.ENUM.indexOf(value);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
// Fallthrough by intention
|
|
456
|
-
case 'INTEGER':
|
|
457
|
-
value = Number.parseInt(value, 10);
|
|
458
|
-
if (value < paramset.MIN) {
|
|
459
|
-
value = paramset.MIN;
|
|
460
|
-
} else if (value > paramset.MAX) {
|
|
461
|
-
value = paramset.MAX;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
break;
|
|
465
|
-
case 'STRING':
|
|
466
|
-
value = String(value);
|
|
467
|
-
break;
|
|
468
|
-
default:
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
return value;
|
|
472
|
-
}
|
|
473
497
|
}
|
|
474
498
|
|
|
475
499
|
RED.nodes.registerType('ccu-mqtt', CcuMqttNode);
|
package/nodes/ccu-poll.js
CHANGED
|
@@ -17,6 +17,12 @@ module.exports = function (RED) {
|
|
|
17
17
|
this.ccu.register(this);
|
|
18
18
|
|
|
19
19
|
this.on('input', (message, send, done) => {
|
|
20
|
+
if (this.ccu.metaMode) {
|
|
21
|
+
// openccu-lite has no ReGaHSS - nothing to poll (B-17)
|
|
22
|
+
done(this.ccu.regaMissingError('system variables and programs'));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
20
26
|
this.ccu.regaPoll(); // TODO catch errors
|
|
21
27
|
done();
|
|
22
28
|
});
|
package/nodes/ccu-rpc-event.html
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
change: {value: false},
|
|
30
30
|
working: {value: false},
|
|
31
31
|
cache: {value: false},
|
|
32
|
+
certainOnly: {value: false},
|
|
32
33
|
topic: {value: '${CCU}/${Interface}/${channelName}/${datapoint}'}
|
|
33
34
|
},
|
|
34
35
|
inputs: 0,
|
|
@@ -596,6 +597,11 @@
|
|
|
596
597
|
<input type="checkbox" id="node-input-cache">
|
|
597
598
|
<span data-i18n="ccu-rpc-event.cache"></span>
|
|
598
599
|
</label>
|
|
600
|
+
|
|
601
|
+
<label class="ccu-checkbox">
|
|
602
|
+
<input type="checkbox" id="node-input-certainOnly">
|
|
603
|
+
<span data-i18n="ccu-rpc-event.certainOnly"></span>
|
|
604
|
+
</label>
|
|
599
605
|
</div>
|
|
600
606
|
</div>
|
|
601
607
|
<div class="form-row">
|
package/nodes/ccu-rpc-event.js
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
|
|
3
3
|
const statusHelper = require(path.join(__dirname, '/lib/status.js'));
|
|
4
|
+
const {valueStatus} = require(path.join(__dirname, '/lib/valuestatus.js'));
|
|
5
|
+
|
|
6
|
+
const FILTER_ATTRIBUTES = [
|
|
7
|
+
'rooms',
|
|
8
|
+
'functions',
|
|
9
|
+
'device',
|
|
10
|
+
'deviceName',
|
|
11
|
+
'deviceType',
|
|
12
|
+
'channel',
|
|
13
|
+
'channelName',
|
|
14
|
+
'channelType',
|
|
15
|
+
'channelIndex',
|
|
16
|
+
'datapoint',
|
|
17
|
+
];
|
|
4
18
|
|
|
5
19
|
module.exports = function (RED) {
|
|
6
20
|
class CcuRpcEventNode {
|
|
@@ -21,20 +35,15 @@ module.exports = function (RED) {
|
|
|
21
35
|
iface: config.iface,
|
|
22
36
|
};
|
|
23
37
|
|
|
38
|
+
// opt-in: only present when the option is enabled, so existing
|
|
39
|
+
// subscriptions keep receiving uncertain values (#96)
|
|
40
|
+
if (config.certainOnly) {
|
|
41
|
+
filter.uncertain = false;
|
|
42
|
+
}
|
|
43
|
+
|
|
24
44
|
this.ccu.register(this);
|
|
25
45
|
|
|
26
|
-
|
|
27
|
-
'rooms',
|
|
28
|
-
'functions',
|
|
29
|
-
'device',
|
|
30
|
-
'deviceName',
|
|
31
|
-
'deviceType',
|
|
32
|
-
'channel',
|
|
33
|
-
'channelName',
|
|
34
|
-
'channelType',
|
|
35
|
-
'channelIndex',
|
|
36
|
-
'datapoint',
|
|
37
|
-
].forEach((attr) => {
|
|
46
|
+
FILTER_ATTRIBUTES.forEach((attr) => {
|
|
38
47
|
if (!config[attr]) {
|
|
39
48
|
return;
|
|
40
49
|
}
|
|
@@ -45,8 +54,14 @@ module.exports = function (RED) {
|
|
|
45
54
|
filter[attr] = config[attr];
|
|
46
55
|
}
|
|
47
56
|
});
|
|
57
|
+
|
|
58
|
+
// The node used to show nothing at all; show what came through
|
|
59
|
+
// last and how long ago (#52)
|
|
60
|
+
this.valueStatus = valueStatus(this);
|
|
61
|
+
|
|
48
62
|
this.idSubscription = this.ccu.subscribe(filter, (message) => {
|
|
49
63
|
message.topic = this.ccu.topicReplace(config.topic, message);
|
|
64
|
+
this.valueStatus.set(message.topic + ' = ' + message.payload, message.ts);
|
|
50
65
|
this.send(message);
|
|
51
66
|
});
|
|
52
67
|
this.on('close', this._destructor);
|
|
@@ -54,6 +69,10 @@ module.exports = function (RED) {
|
|
|
54
69
|
|
|
55
70
|
_destructor(done) {
|
|
56
71
|
this.debug('ccu-rpc-event close');
|
|
72
|
+
if (this.valueStatus) {
|
|
73
|
+
this.valueStatus.stop();
|
|
74
|
+
}
|
|
75
|
+
|
|
57
76
|
this.ccu.unsubscribe(this.idSubscription);
|
|
58
77
|
this.ccu.deregister(this);
|
|
59
78
|
done();
|
package/nodes/ccu-set-value.html
CHANGED
|
@@ -624,8 +624,46 @@
|
|
|
624
624
|
|
|
625
625
|
<h3>Input</h3>
|
|
626
626
|
|
|
627
|
+
|
|
628
|
+
<h3>Dynamische Konfiguration</h3>
|
|
629
|
+
<p><code>msg.config</code> ist ein Objekt, dessen Eigenschaften die Konfiguration dieses Nodes für genau diese Nachricht überschreiben. Die gespeicherte Konfiguration bleibt unverändert.</p>
|
|
630
|
+
<ul>
|
|
631
|
+
<li><code>iface</code></li>
|
|
632
|
+
<li><code>rooms</code></li>
|
|
633
|
+
<li><code>functions</code></li>
|
|
634
|
+
<li><code>device</code></li>
|
|
635
|
+
<li><code>deviceType</code></li>
|
|
636
|
+
<li><code>deviceName</code></li>
|
|
637
|
+
<li><code>channel</code></li>
|
|
638
|
+
<li><code>channelType</code></li>
|
|
639
|
+
<li><code>channelIndex</code></li>
|
|
640
|
+
<li><code>channelName</code></li>
|
|
641
|
+
<li><code>datapoint</code></li>
|
|
642
|
+
<li><code>force</code></li>
|
|
643
|
+
<li><code>sowie die zugehörigen <code>...Rx</code>-Selektoren</code></li>
|
|
644
|
+
</ul>
|
|
645
|
+
<p>Zusätzlich füllen gleichnamige Eigenschaften der Nachricht selbst (<code>msg.rooms</code> usw.) leer gelassene Felder — so war es schon immer.</p>
|
|
627
646
|
</script>
|
|
628
647
|
|
|
629
648
|
<script type="text/x-red" data-help-name="ccu-set-value" lang="en-US">
|
|
630
649
|
<p>Control multiple devices at once.</p>
|
|
650
|
+
|
|
651
|
+
<h3>Dynamic configuration</h3>
|
|
652
|
+
<p><code>msg.config</code> is an object whose properties override this node's configuration for that one message. The stored configuration is never modified.</p>
|
|
653
|
+
<ul>
|
|
654
|
+
<li><code>iface</code></li>
|
|
655
|
+
<li><code>rooms</code></li>
|
|
656
|
+
<li><code>functions</code></li>
|
|
657
|
+
<li><code>device</code></li>
|
|
658
|
+
<li><code>deviceType</code></li>
|
|
659
|
+
<li><code>deviceName</code></li>
|
|
660
|
+
<li><code>channel</code></li>
|
|
661
|
+
<li><code>channelType</code></li>
|
|
662
|
+
<li><code>channelIndex</code></li>
|
|
663
|
+
<li><code>channelName</code></li>
|
|
664
|
+
<li><code>datapoint</code></li>
|
|
665
|
+
<li><code>force</code></li>
|
|
666
|
+
<li><code>and the matching <code>...Rx</code> selectors</code></li>
|
|
667
|
+
</ul>
|
|
668
|
+
<p>Properties of the message itself (<code>msg.rooms</code> and so on) also fill in fields left empty in the dialog, as they always did.</p>
|
|
631
669
|
</script>
|