node-red-contrib-ccu 4.1.0 → 4.3.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/CHANGELOG.md +125 -0
- package/HANDOFF.md +67 -16
- package/README.md +42 -0
- package/ROADMAP.md +129 -47
- package/examples/home-assistant.json +127 -0
- package/nodes/ccu-alexa.html +3 -1
- package/nodes/ccu-connection.html +26 -0
- package/nodes/ccu-connection.js +124 -13
- package/nodes/ccu-display.js +29 -13
- package/nodes/ccu-get-value.html +3 -1
- package/nodes/ccu-homeassistant.html +334 -0
- package/nodes/ccu-homeassistant.js +272 -0
- package/nodes/ccu-mqtt.html +21 -4
- package/nodes/ccu-mqtt.js +188 -105
- 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/hadiscovery.js +846 -0
- package/nodes/lib/haroles.js +193 -0
- package/nodes/lib/localccu.js +81 -0
- package/nodes/lib/message.js +6 -2
- 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 +2 -1
- package/patch-certainonly.py +51 -0
- package/roadmap-archive/B-16.md +31 -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 +11 -0
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
|
|
17
17
|
regaPoll: {value: true},
|
|
18
18
|
regaInterval: {value: 30},
|
|
19
|
+
regaMetaInterval: {value: 15},
|
|
19
20
|
|
|
21
|
+
rpcPing: {value: true},
|
|
20
22
|
rpcPingTimeout: {value: 60},
|
|
21
23
|
rpcInitAddress: {value: ''},
|
|
22
24
|
rpcServerHost: {value: '', required: true},
|
|
@@ -39,6 +41,18 @@
|
|
|
39
41
|
},
|
|
40
42
|
|
|
41
43
|
oneditprepare() {
|
|
44
|
+
// pre-4.3.0 config nodes have neither property - show the defaults
|
|
45
|
+
// the runtime uses rather than an unchecked box / empty field
|
|
46
|
+
if (typeof this.rpcPing === 'undefined') {
|
|
47
|
+
this.rpcPing = true;
|
|
48
|
+
$('#node-config-input-rpcPing').prop('checked', true);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (typeof this.regaMetaInterval === 'undefined') {
|
|
52
|
+
this.regaMetaInterval = 15;
|
|
53
|
+
$('#node-config-input-regaMetaInterval').val(15);
|
|
54
|
+
}
|
|
55
|
+
|
|
42
56
|
const $nodeConfigInputHost = $('#node-config-input-host');
|
|
43
57
|
const $nodeConfigInputName = $('#node-config-input-name');
|
|
44
58
|
const $nodeConfigInputRpcServerHost = $('#node-config-input-rpcServerHost');
|
|
@@ -178,6 +192,13 @@
|
|
|
178
192
|
<input type="text" id="node-config-input-jackPort" style="width: 70px;" placeholder="2121">
|
|
179
193
|
</div>
|
|
180
194
|
</div>
|
|
195
|
+
<div class="form-row">
|
|
196
|
+
<label for="node-config-input-rpcPing"><i class="icon-tag"></i> RPC ping</label>
|
|
197
|
+
<div style="width: 70%; display: inline-block; vertical-align: text-top;">
|
|
198
|
+
<label class="ccu-checkbox"><input type="checkbox" id="node-config-input-rpcPing"> Enabled</label><br>
|
|
199
|
+
<small>Off disables the periodic ping and the timeout-triggered re-init for this connection.</small>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
181
202
|
<div class="form-row">
|
|
182
203
|
<label for="node-config-input-rpcPingTimeout"><i class="icon-tag"></i> RPC ping timeout (s)</label>
|
|
183
204
|
<input type="text" id="node-config-input-rpcPingTimeout">
|
|
@@ -192,6 +213,11 @@
|
|
|
192
213
|
<label for="node-config-input-regaInterval"><i class="icon-tag"></i> Rega poll interval (s)</label>
|
|
193
214
|
<input type="text" id="node-config-input-regaInterval">
|
|
194
215
|
</div>
|
|
216
|
+
<div class="form-row">
|
|
217
|
+
<label for="node-config-input-regaMetaInterval"><i class="icon-tag"></i> Names/rooms refresh (min)</label>
|
|
218
|
+
<input type="text" id="node-config-input-regaMetaInterval">
|
|
219
|
+
<small>How often channel names, rooms and functions are re-read. 0 = never (the behaviour before 4.3.0).</small>
|
|
220
|
+
</div>
|
|
195
221
|
<div class="form-row">
|
|
196
222
|
<label for="node-config-input-queueTimeout"><i class="icon-tag"></i> Queue timeout (ms)</label>
|
|
197
223
|
<input type="text" id="node-config-input-queueTimeout">
|
package/nodes/ccu-connection.js
CHANGED
|
@@ -9,6 +9,7 @@ const {castValue, castSysvar} = require('./lib/cast.js');
|
|
|
9
9
|
const {combinedParameterValue} = require('./lib/combined.js');
|
|
10
10
|
const {createMessage} = require('./lib/message.js');
|
|
11
11
|
const {topicReplace} = require('./lib/topic.js');
|
|
12
|
+
const {isLocalCcu} = require('./lib/localccu.js');
|
|
12
13
|
const {bestMatch} = require('./lib/similarity.js');
|
|
13
14
|
const nextport = require('./lib/nextport.js');
|
|
14
15
|
const hmDiscover = require('./lib/discover.js');
|
|
@@ -208,6 +209,39 @@ module.exports = function (RED) {
|
|
|
208
209
|
break;
|
|
209
210
|
}
|
|
210
211
|
|
|
212
|
+
case 'devices': {
|
|
213
|
+
// ccu-homeassistant editor: every device (not channel) of every, or the
|
|
214
|
+
// given, interface with its channels
|
|
215
|
+
const all = (config.metadata && config.metadata.devices) || {};
|
|
216
|
+
const ifaces = request.query.iface ? [request.query.iface] : Object.keys(all);
|
|
217
|
+
ifaces.forEach((iface) => {
|
|
218
|
+
const devices = all[iface] || {};
|
|
219
|
+
Object.keys(devices).forEach((addr) => {
|
|
220
|
+
const device = devices[addr];
|
|
221
|
+
if (!device || device.PARENT) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
object[addr] = {
|
|
226
|
+
name: config.channelNames[addr],
|
|
227
|
+
type: device.TYPE,
|
|
228
|
+
iface,
|
|
229
|
+
firmware: device.FIRMWARE,
|
|
230
|
+
channels: (device.CHILDREN || [])
|
|
231
|
+
.filter((ch) => devices[ch])
|
|
232
|
+
.map((ch) => ({
|
|
233
|
+
address: ch,
|
|
234
|
+
name: config.channelNames[ch],
|
|
235
|
+
type: devices[ch].TYPE,
|
|
236
|
+
})),
|
|
237
|
+
};
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
res.status(200).send(JSON.stringify(object));
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
|
|
211
245
|
case 'rooms':
|
|
212
246
|
res.status(200).send(
|
|
213
247
|
JSON.stringify({
|
|
@@ -374,15 +408,13 @@ module.exports = function (RED) {
|
|
|
374
408
|
|
|
375
409
|
this.checkDuplicateConfig(config);
|
|
376
410
|
|
|
377
|
-
this.
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
}
|
|
385
|
-
} catch {}
|
|
411
|
+
// B-4: this used to grep /etc/lighttpd/conf.d/proxy.conf for the
|
|
412
|
+
// direct port. That file is a bare include on current firmware, so
|
|
413
|
+
// the check never matched and every local install went through the
|
|
414
|
+
// proxy. Ask the kernel for a listener instead.
|
|
415
|
+
this.isLocal = isLocalCcu(config.host);
|
|
416
|
+
if (this.isLocal) {
|
|
417
|
+
this.logger.info('local connection on ccu >= v3.41 detected');
|
|
386
418
|
}
|
|
387
419
|
|
|
388
420
|
this.ifaceTypes = {
|
|
@@ -496,6 +528,10 @@ module.exports = function (RED) {
|
|
|
496
528
|
this.rpcBinPort = Number.parseInt(config.rpcBinPort, 10);
|
|
497
529
|
this.rpcXmlPort = Number.parseInt(config.rpcXmlPort, 10);
|
|
498
530
|
this.rpcPingTimeout = Number.parseInt(config.rpcPingTimeout, 10) || 60;
|
|
531
|
+
// #44: opt out of the periodic ping/timeout supervision. Off means
|
|
532
|
+
// no pings and no timeout-triggered re-init for this connection -
|
|
533
|
+
// reconnecting after a CCU restart is then up to the CCU.
|
|
534
|
+
this.rpcPingEnabled = config.rpcPing === undefined ? true : Boolean(config.rpcPing);
|
|
499
535
|
this.rpcPingTimer = {};
|
|
500
536
|
this.ifaceStatus = {};
|
|
501
537
|
this.serverError = {};
|
|
@@ -506,6 +542,10 @@ module.exports = function (RED) {
|
|
|
506
542
|
|
|
507
543
|
this.regaEnabled = config.regaEnabled;
|
|
508
544
|
this.regaPollEnabled = config.regaPoll;
|
|
545
|
+
// #167: minutes between re-reads of channel names, rooms and
|
|
546
|
+
// functions. 0 switches it off.
|
|
547
|
+
this.regaMetaInterval =
|
|
548
|
+
config.regaMetaInterval === undefined ? 15 : Number.parseInt(config.regaMetaInterval, 10) || 0;
|
|
509
549
|
this.regaInterval = Number.parseInt(config.regaInterval, 10);
|
|
510
550
|
this.hadTimeout = new Set();
|
|
511
551
|
|
|
@@ -599,6 +639,7 @@ module.exports = function (RED) {
|
|
|
599
639
|
});
|
|
600
640
|
|
|
601
641
|
if (config.regaEnabled) {
|
|
642
|
+
this.lastRegaDataRefresh = now();
|
|
602
643
|
this.getRegaData().then(() => {
|
|
603
644
|
this.regaPoll();
|
|
604
645
|
this.initIfaces(config);
|
|
@@ -688,8 +729,10 @@ module.exports = function (RED) {
|
|
|
688
729
|
* @returns {*}
|
|
689
730
|
*/
|
|
690
731
|
deregister(node, done) {
|
|
691
|
-
delete
|
|
692
|
-
|
|
732
|
+
delete this.users[node.id];
|
|
733
|
+
if (typeof done === 'function') {
|
|
734
|
+
done();
|
|
735
|
+
}
|
|
693
736
|
}
|
|
694
737
|
|
|
695
738
|
/**
|
|
@@ -1049,7 +1092,14 @@ module.exports = function (RED) {
|
|
|
1049
1092
|
const ts = dp.ts;
|
|
1050
1093
|
const [iface, channel, datapoint] = dp.name.split('.');
|
|
1051
1094
|
if (this.enabledIfaces.includes(iface) && datapoint) {
|
|
1052
|
-
if (
|
|
1095
|
+
if (
|
|
1096
|
+
['RSSI_DEVICE', 'RSSI_PEER'].includes(datapoint) &&
|
|
1097
|
+
typeof dp.value === 'number' &&
|
|
1098
|
+
dp.value > 127
|
|
1099
|
+
) {
|
|
1100
|
+
// the ReGa reports the unsigned byte; without
|
|
1101
|
+
// the guard an already-signed value or the 0
|
|
1102
|
+
// for "never received" became -318 / -256 (#183)
|
|
1053
1103
|
dp.value -= 256;
|
|
1054
1104
|
}
|
|
1055
1105
|
|
|
@@ -1285,7 +1335,12 @@ module.exports = function (RED) {
|
|
|
1285
1335
|
regaPoll() {
|
|
1286
1336
|
//this.logger.trace('regaPoll');
|
|
1287
1337
|
if (this.regaPollPending) {
|
|
1288
|
-
|
|
1338
|
+
// #166: writing several variables at once used to lose all but
|
|
1339
|
+
// the first immediate re-poll, so their new values only showed
|
|
1340
|
+
// up at the next scheduled one (30 s by default). Remember the
|
|
1341
|
+
// request and run exactly one more poll afterwards.
|
|
1342
|
+
this.logger.debug('rega poll already pending, will repeat');
|
|
1343
|
+
this.regaPollAgain = true;
|
|
1289
1344
|
} else {
|
|
1290
1345
|
this.regaPollPending = true;
|
|
1291
1346
|
clearTimeout(this.regaPollTimeout);
|
|
@@ -1307,6 +1362,14 @@ module.exports = function (RED) {
|
|
|
1307
1362
|
|
|
1308
1363
|
this.firstRegaPollDone = true;
|
|
1309
1364
|
this.regaPollPending = false;
|
|
1365
|
+
|
|
1366
|
+
if (this.regaPollAgain && !this.cancelRegaPoll) {
|
|
1367
|
+
this.regaPollAgain = false;
|
|
1368
|
+
this.regaPoll();
|
|
1369
|
+
} else {
|
|
1370
|
+
this.regaPollAgain = false;
|
|
1371
|
+
this.refreshRegaDataIfDue();
|
|
1372
|
+
}
|
|
1310
1373
|
});
|
|
1311
1374
|
}
|
|
1312
1375
|
}
|
|
@@ -1446,6 +1509,35 @@ module.exports = function (RED) {
|
|
|
1446
1509
|
* Poll ReGaHSS variables and call subscription callbacks
|
|
1447
1510
|
* @returns {Promise}
|
|
1448
1511
|
*/
|
|
1512
|
+
/**
|
|
1513
|
+
* Re-read the ReGa metadata that only ever got fetched once, in the
|
|
1514
|
+
* constructor: channel names, rooms and functions. A room or function
|
|
1515
|
+
* added in the CCU WebUI was invisible until the flow was redeployed
|
|
1516
|
+
* (#167). Cheap enough for a slow schedule - three small scripts.
|
|
1517
|
+
* @returns {Promise<any>}
|
|
1518
|
+
*/
|
|
1519
|
+
refreshRegaData() {
|
|
1520
|
+
this.lastRegaDataRefresh = now();
|
|
1521
|
+
this.logger.debug('refreshRegaData');
|
|
1522
|
+
return this.getRegaChannels()
|
|
1523
|
+
.then(() => this.getRegaRooms())
|
|
1524
|
+
.then(() => this.getRegaFunctions())
|
|
1525
|
+
.then(() => this.saveRegadata())
|
|
1526
|
+
.catch((error) => this.logger.error('refreshRegaData', error));
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
/** Runs refreshRegaData() when the configured interval has elapsed. */
|
|
1530
|
+
refreshRegaDataIfDue() {
|
|
1531
|
+
if (!this.regaMetaInterval || this.cancelRegaPoll) {
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
const elapsed = now() - (this.lastRegaDataRefresh || 0);
|
|
1536
|
+
if (elapsed >= this.regaMetaInterval * 60000) {
|
|
1537
|
+
this.refreshRegaData();
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1449
1541
|
getRegaVariables() {
|
|
1450
1542
|
return new Promise((resolve, reject) => {
|
|
1451
1543
|
this.logger.debug('getRegaVariables');
|
|
@@ -1731,6 +1823,10 @@ module.exports = function (RED) {
|
|
|
1731
1823
|
* @param iface
|
|
1732
1824
|
*/
|
|
1733
1825
|
rpcCheckInit(iface) {
|
|
1826
|
+
if (!this.rpcPingEnabled) {
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1734
1830
|
if (!this.metadata.devices[iface] || !Object.keys(this.metadata.devices[iface]).length > 0) {
|
|
1735
1831
|
return;
|
|
1736
1832
|
}
|
|
@@ -2474,6 +2570,7 @@ module.exports = function (RED) {
|
|
|
2474
2570
|
'change',
|
|
2475
2571
|
'cache',
|
|
2476
2572
|
'stable',
|
|
2573
|
+
'uncertain',
|
|
2477
2574
|
'iface',
|
|
2478
2575
|
'device',
|
|
2479
2576
|
'deviceType',
|
|
@@ -2679,6 +2776,15 @@ module.exports = function (RED) {
|
|
|
2679
2776
|
let matchChange;
|
|
2680
2777
|
let matchStable;
|
|
2681
2778
|
|
|
2779
|
+
// Checked before the attribute loop below: that loop may break
|
|
2780
|
+
// early for a whitelisted datapoint, and this decision has to be
|
|
2781
|
+
// made for every message regardless of key order. Only present in
|
|
2782
|
+
// the filter when a node opted in, so existing subscriptions keep
|
|
2783
|
+
// receiving values the ReGa could not timestamp (#96).
|
|
2784
|
+
if (filter && filter.uncertain === false && message.uncertain) {
|
|
2785
|
+
return false;
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2682
2788
|
if (filter) {
|
|
2683
2789
|
const arrayAttr = Object.keys(filter);
|
|
2684
2790
|
|
|
@@ -2718,6 +2824,11 @@ module.exports = function (RED) {
|
|
|
2718
2824
|
continue;
|
|
2719
2825
|
}
|
|
2720
2826
|
|
|
2827
|
+
if (attr === 'uncertain') {
|
|
2828
|
+
// already decided above, and not a value to compare
|
|
2829
|
+
continue;
|
|
2830
|
+
}
|
|
2831
|
+
|
|
2721
2832
|
if (this.callbackWhitelists[message.datapointName].has(id)) {
|
|
2722
2833
|
if (matchCache && matchChange && matchStable) {
|
|
2723
2834
|
break;
|
package/nodes/ccu-display.js
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
|
|
3
3
|
const statusHelper = require(path.join(__dirname, '/lib/status.js'));
|
|
4
|
+
const {effectiveConfig} = require(path.join(__dirname, '/lib/dynconfig.js'));
|
|
5
|
+
|
|
6
|
+
/** Everything a message may override through msg.config (B-2, #80 #185). */
|
|
7
|
+
const DYNAMIC_KEYS = [
|
|
8
|
+
'iface',
|
|
9
|
+
'channel',
|
|
10
|
+
'channelType',
|
|
11
|
+
'sound',
|
|
12
|
+
'repeat',
|
|
13
|
+
'pause',
|
|
14
|
+
'signal',
|
|
15
|
+
...[1, 2, 3, 4, 5, 6].flatMap((i) => ['line' + i, 'color' + i, 'icon' + i, 'disable' + i]),
|
|
16
|
+
];
|
|
4
17
|
|
|
5
18
|
module.exports = function (RED) {
|
|
6
19
|
class CcuDisplay {
|
|
@@ -143,43 +156,46 @@ module.exports = function (RED) {
|
|
|
143
156
|
}
|
|
144
157
|
|
|
145
158
|
this.on('input', (message, send, done) => {
|
|
159
|
+
// msg.line1 & co. keep working as before; msg.config
|
|
160
|
+
// additionally reaches the channel and the EP-WM55 settings
|
|
161
|
+
const {config: effective} = effectiveConfig(config, message, DYNAMIC_KEYS);
|
|
146
162
|
let payload = '0x02';
|
|
147
163
|
|
|
148
164
|
let countLines = 6; // HM-Dis-WM55
|
|
149
|
-
if (
|
|
165
|
+
if (effective.channelType === 'HM-Dis-EP-WM55') {
|
|
150
166
|
payload += ',0x0A';
|
|
151
167
|
countLines = 3;
|
|
152
168
|
}
|
|
153
169
|
|
|
154
170
|
for (let i = 1; i <= countLines; i++) {
|
|
155
|
-
if (!message['disable' + i] && !
|
|
156
|
-
payload += convertString(message['line' + i] ||
|
|
157
|
-
if (
|
|
158
|
-
payload += convertColor(message['color' + i] ||
|
|
171
|
+
if (!message['disable' + i] && !effective['disable' + i]) {
|
|
172
|
+
payload += convertString(message['line' + i] || effective['line' + i]);
|
|
173
|
+
if (effective.channelType === 'HM-Dis-WM55') {
|
|
174
|
+
payload += convertColor(message['color' + i] || effective['color' + i]);
|
|
159
175
|
}
|
|
160
176
|
|
|
161
|
-
payload += convertIcon(message['icon' + i] ||
|
|
177
|
+
payload += convertIcon(message['icon' + i] || effective['icon' + i]);
|
|
162
178
|
}
|
|
163
179
|
|
|
164
180
|
payload += ',0x0A';
|
|
165
181
|
}
|
|
166
182
|
|
|
167
|
-
if (
|
|
168
|
-
if (
|
|
169
|
-
payload += ',0x14,' +
|
|
183
|
+
if (effective.channelType === 'HM-Dis-EP-WM55') {
|
|
184
|
+
if (effective.sound) {
|
|
185
|
+
payload += ',0x14,' + effective.sound + ',0x1C';
|
|
170
186
|
}
|
|
171
187
|
|
|
172
|
-
payload += ',' +
|
|
188
|
+
payload += ',' + effective.repeat + ',0x1D,' + effective.pause + ',0x16';
|
|
173
189
|
|
|
174
|
-
if (
|
|
175
|
-
payload += ',' +
|
|
190
|
+
if (effective.signal) {
|
|
191
|
+
payload += ',' + effective.signal;
|
|
176
192
|
}
|
|
177
193
|
}
|
|
178
194
|
|
|
179
195
|
payload += ',0x03';
|
|
180
196
|
|
|
181
197
|
this.ccu
|
|
182
|
-
.setValue(
|
|
198
|
+
.setValue(effective.iface, effective.channel, 'SUBMIT', payload)
|
|
183
199
|
.then(() => {
|
|
184
200
|
done();
|
|
185
201
|
})
|
package/nodes/ccu-get-value.html
CHANGED
|
@@ -195,7 +195,9 @@
|
|
|
195
195
|
arr.sort((a, b) => a.localeCompare(b));
|
|
196
196
|
$nodeInputChannel.autocomplete('option', 'source', arr);
|
|
197
197
|
|
|
198
|
-
|
|
198
|
+
// ${ENV} placeholders are substituted by Node-RED before the node is
|
|
199
|
+
// constructed (subflow parameters, #158) - do not clear them
|
|
200
|
+
if (!/^\$\{[^}]+\}$/.test($nodeInputChannel.val()) && !data[$nodeInputChannel.val().split(' ')[0]]) {
|
|
199
201
|
$nodeInputChannel.val('');
|
|
200
202
|
$nodeInputDatapoint.val('');
|
|
201
203
|
}
|