node-red-contrib-ccu 4.2.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 +96 -0
- package/HANDOFF.md +11 -3
- package/README.md +29 -0
- package/ROADMAP.md +112 -33
- package/nodes/ccu-alexa.html +3 -1
- package/nodes/ccu-connection.html +26 -0
- package/nodes/ccu-connection.js +87 -11
- 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-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/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-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
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>
|
package/nodes/ccu-set-value.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
|
|
3
3
|
const statusHelper = require(path.join(__dirname, '/lib/status.js'));
|
|
4
|
+
const {effectiveConfig, configCacheKey} = require(path.join(__dirname, '/lib/dynconfig.js'));
|
|
5
|
+
const {channelMatches, datapointsOf, CONFIG_KEYS} = require(path.join(__dirname, '/lib/channelfilter.js'));
|
|
6
|
+
|
|
7
|
+
/** Configuration keys an incoming message may supply (B-2). */
|
|
8
|
+
const DYNAMIC_KEYS = [...CONFIG_KEYS, 'force'];
|
|
4
9
|
|
|
5
10
|
module.exports = function (RED) {
|
|
6
11
|
class CcuSetValue {
|
|
@@ -15,36 +20,31 @@ module.exports = function (RED) {
|
|
|
15
20
|
|
|
16
21
|
this.ccu.register(this);
|
|
17
22
|
|
|
18
|
-
this.config = {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
device: config.device,
|
|
23
|
-
deviceType: config.deviceType,
|
|
24
|
-
deviceName: config.deviceName,
|
|
25
|
-
channel: config.channel,
|
|
26
|
-
channelType: config.channelType,
|
|
27
|
-
channelIndex: config.channelIndex,
|
|
28
|
-
channelName: config.channelName,
|
|
29
|
-
datapoint: config.datapoint,
|
|
30
|
-
roomsRx: config.roomsRx,
|
|
31
|
-
functionsRx: config.functionsRx,
|
|
32
|
-
deviceRx: config.deviceRx,
|
|
33
|
-
deviceTypeRx: config.deviceTypeRx,
|
|
34
|
-
deviceNameRx: config.deviceNameRx,
|
|
35
|
-
channelRx: config.channelRx,
|
|
36
|
-
channelTypeRx: config.channelTypeRx,
|
|
37
|
-
channelIndexRx: config.channelIndexRx,
|
|
38
|
-
channelNameRx: config.channelNameRx,
|
|
39
|
-
datapointRx: config.datapointRx,
|
|
40
|
-
force: config.force,
|
|
41
|
-
};
|
|
23
|
+
this.config = {};
|
|
24
|
+
DYNAMIC_KEYS.forEach((key) => {
|
|
25
|
+
this.config[key] = config[key];
|
|
26
|
+
});
|
|
42
27
|
|
|
28
|
+
// Channels already known to (not) match the filter currently in
|
|
29
|
+
// effect. Keyed by that filter, so a message that changes it gets
|
|
30
|
+
// a fresh evaluation instead of inheriting the previous one (#133).
|
|
31
|
+
this.filterKey = null;
|
|
43
32
|
this.blacklist = new Set();
|
|
44
33
|
this.whitelist = new Set();
|
|
45
34
|
|
|
46
|
-
this.on('input', (message) => {
|
|
47
|
-
|
|
35
|
+
this.on('input', (message, send, done) => {
|
|
36
|
+
try {
|
|
37
|
+
this.setValues(message);
|
|
38
|
+
if (done) {
|
|
39
|
+
done();
|
|
40
|
+
}
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (done) {
|
|
43
|
+
done(error);
|
|
44
|
+
} else {
|
|
45
|
+
this.error(error.message, message);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
this.on('close', this._destructor);
|
|
@@ -63,22 +63,23 @@ module.exports = function (RED) {
|
|
|
63
63
|
statusHelper(this, data);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (dynamicConfig) {
|
|
79
|
-
this.whitelist.clear();
|
|
80
|
-
this.blacklist.clear();
|
|
66
|
+
/**
|
|
67
|
+
* The match caches are only valid for the filter they were built
|
|
68
|
+
* from — reset them whenever the effective filter changes.
|
|
69
|
+
* @param {object} config effective configuration for this message
|
|
70
|
+
*/
|
|
71
|
+
useFilter(config) {
|
|
72
|
+
const key = configCacheKey(config, CONFIG_KEYS);
|
|
73
|
+
if (key !== this.filterKey) {
|
|
74
|
+
this.filterKey = key;
|
|
75
|
+
this.blacklist = new Set();
|
|
76
|
+
this.whitelist = new Set();
|
|
81
77
|
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
setValues(message) {
|
|
81
|
+
const {config} = effectiveConfig(this.config, message, DYNAMIC_KEYS, {flat: true});
|
|
82
|
+
this.useFilter(config);
|
|
82
83
|
|
|
83
84
|
let count = 0;
|
|
84
85
|
Object.keys(this.ccu.metadata.devices).forEach((iface) => {
|
|
@@ -91,206 +92,29 @@ module.exports = function (RED) {
|
|
|
91
92
|
return;
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
const channel = this.ccu.metadata.devices[iface][address];
|
|
95
|
-
|
|
96
|
-
if (!channel.PARENT) {
|
|
97
|
-
this.blacklist.add(address);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
95
|
if (!this.whitelist.has(address)) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
this.blacklist.add(address);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (config.deviceRx === 're' && !channel.PARENT.match(new RegExp(config.device))) {
|
|
110
|
-
this.blacklist.add(address);
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (config.deviceType) {
|
|
116
|
-
if (config.deviceTypeRx === 'str' && config.deviceType !== device.TYPE) {
|
|
117
|
-
this.blacklist.add(address);
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (config.deviceTypeRx === 're' && !device.TYPE.match(new RegExp(config.deviceType))) {
|
|
122
|
-
this.blacklist.add(address);
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (config.deviceName) {
|
|
128
|
-
if (!this.ccu.channelNames[address]) {
|
|
129
|
-
this.blacklist.add(address);
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (
|
|
134
|
-
config.deviceNameRx === 'str' &&
|
|
135
|
-
this.ccu.channelNames[channel.PARENT] !== config.deviceName
|
|
136
|
-
) {
|
|
137
|
-
this.blacklist.add(address);
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (
|
|
142
|
-
config.deviceNameRx === 're' &&
|
|
143
|
-
!this.ccu.channelNames[channel.PARENT].match(new RegExp(config.deviceName))
|
|
144
|
-
) {
|
|
145
|
-
this.blacklist.add(address);
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (config.channel) {
|
|
151
|
-
if (config.channelRx === 'str' && config.channel !== address) {
|
|
152
|
-
this.blacklist.add(address);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (config.channelRx === 're' && !address.match(new RegExp(config.channel))) {
|
|
157
|
-
this.blacklist.add(address);
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (config.channelType) {
|
|
163
|
-
if (config.channelTypeRx === 'str' && config.channelType !== channel.TYPE) {
|
|
164
|
-
this.blacklist.add(address);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (config.channelTypeRx === 're' && !channel.TYPE.match(new RegExp(config.channelType))) {
|
|
169
|
-
this.blacklist.add(address);
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (config.channelIndex) {
|
|
175
|
-
if (config.channelIndexRx === 'str' && !address.endsWith(':' + config.channelIndex)) {
|
|
176
|
-
this.blacklist.add(address);
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (
|
|
181
|
-
config.channelIndexRx === 're' &&
|
|
182
|
-
!address.split(':')[1].match(new RegExp(String(config.channelIndex)))
|
|
183
|
-
) {
|
|
184
|
-
this.blacklist.add(address);
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (config.channelName) {
|
|
190
|
-
if (!this.ccu.channelNames[address]) {
|
|
191
|
-
this.blacklist.add(address);
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (
|
|
196
|
-
config.channelNameRx === 'str' &&
|
|
197
|
-
this.ccu.channelNames[address] !== config.channelName
|
|
198
|
-
) {
|
|
199
|
-
this.blacklist.add(address);
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (
|
|
204
|
-
config.channelNameRx === 're' &&
|
|
205
|
-
!this.ccu.channelNames[address].match(new RegExp(config.channelName))
|
|
206
|
-
) {
|
|
207
|
-
this.blacklist.add(address);
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (config.rooms) {
|
|
213
|
-
if (!this.ccu.channelRooms[address]) {
|
|
214
|
-
this.blacklist.add(address);
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (config.roomsRx === 'str' && !this.ccu.channelRooms[address].includes(config.rooms)) {
|
|
219
|
-
this.blacklist.add(address);
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (config.roomsRx === 're') {
|
|
224
|
-
let match = false;
|
|
225
|
-
this.ccu.channelRooms[address].forEach((room) => {
|
|
226
|
-
if (room.match(new RegExp(config.rooms))) {
|
|
227
|
-
match = true;
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
if (!match) {
|
|
231
|
-
this.blacklist.add(address);
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (config.functions) {
|
|
238
|
-
if (!this.ccu.channelFunctions[address]) {
|
|
239
|
-
this.blacklist.add(address);
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (
|
|
244
|
-
config.functionsRx === 'str' &&
|
|
245
|
-
!this.ccu.channelFunctions[address].includes(config.functions)
|
|
246
|
-
) {
|
|
247
|
-
this.blacklist.add(address);
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (config.functionsRx === 're') {
|
|
252
|
-
let match = false;
|
|
253
|
-
this.ccu.channelFunctions[address].forEach((func) => {
|
|
254
|
-
if (func.match(new RegExp(config.functions))) {
|
|
255
|
-
match = true;
|
|
256
|
-
}
|
|
257
|
-
});
|
|
258
|
-
if (!match) {
|
|
259
|
-
this.blacklist.add(address);
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
96
|
+
if (!channelMatches(this.ccu, iface, address, config)) {
|
|
97
|
+
this.blacklist.add(address);
|
|
98
|
+
return;
|
|
263
99
|
}
|
|
264
100
|
|
|
265
101
|
this.whitelist.add(address);
|
|
266
102
|
}
|
|
267
103
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
count += 1;
|
|
283
|
-
if (
|
|
284
|
-
dp.startsWith('PRESS_') ||
|
|
285
|
-
typeof currentValue === 'undefined' ||
|
|
286
|
-
currentValue !== message.payload
|
|
287
|
-
) {
|
|
288
|
-
this.ccu
|
|
289
|
-
.setValueQueued(iface, address, dp, message.payload, false, config.force)
|
|
290
|
-
.catch(() => {});
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
}
|
|
104
|
+
datapointsOf(this.ccu, iface, address, config).forEach((dp) => {
|
|
105
|
+
const datapointName = iface + '.' + address + '.' + dp;
|
|
106
|
+
const currentValue = this.ccu.values[datapointName] && this.ccu.values[datapointName].value;
|
|
107
|
+
count += 1;
|
|
108
|
+
if (
|
|
109
|
+
dp.startsWith('PRESS_') ||
|
|
110
|
+
typeof currentValue === 'undefined' ||
|
|
111
|
+
currentValue !== message.payload
|
|
112
|
+
) {
|
|
113
|
+
this.ccu
|
|
114
|
+
.setValueQueued(iface, address, dp, message.payload, false, config.force)
|
|
115
|
+
.catch(() => {});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
294
118
|
});
|
|
295
119
|
});
|
|
296
120
|
this.status({fill: 'green', shape: 'ring', text: String(count) + ' datapoints'});
|
package/nodes/ccu-signal.html
CHANGED
|
@@ -268,7 +268,9 @@
|
|
|
268
268
|
|
|
269
269
|
$nodeInputChannel.autocomplete('option', 'source', arr);
|
|
270
270
|
|
|
271
|
-
|
|
271
|
+
// ${ENV} placeholders are substituted by Node-RED before the node is
|
|
272
|
+
// constructed (subflow parameters, #158) - do not clear them
|
|
273
|
+
if (!/^\$\{[^}]+\}$/.test($nodeInputChannel.val()) && !data[$nodeInputChannel.val().split(' ')[0]]) {
|
|
272
274
|
$nodeInputChannel.val('');
|
|
273
275
|
}
|
|
274
276
|
}
|
|
@@ -645,8 +647,60 @@
|
|
|
645
647
|
|
|
646
648
|
<script type="text/x-red" data-help-name="ccu-signal" lang="de-DE">
|
|
647
649
|
<p>Senden von LED- und Tonsignalen an Displaytaster und Funk-Gongs.</p>
|
|
650
|
+
|
|
651
|
+
<h3>Dynamische Konfiguration</h3>
|
|
652
|
+
<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>
|
|
653
|
+
<ul>
|
|
654
|
+
<li><code>iface</code></li>
|
|
655
|
+
<li><code>channel</code></li>
|
|
656
|
+
<li><code>channelType</code></li>
|
|
657
|
+
<li><code>chime</code></li>
|
|
658
|
+
<li><code>led</code></li>
|
|
659
|
+
<li><code>signal</code></li>
|
|
660
|
+
<li><code>volume</code></li>
|
|
661
|
+
<li><code>repeat</code></li>
|
|
662
|
+
<li><code>repetitions</code></li>
|
|
663
|
+
<li><code>durationUnit</code></li>
|
|
664
|
+
<li><code>durationValue</code></li>
|
|
665
|
+
<li><code>rampTimeUnit</code></li>
|
|
666
|
+
<li><code>rampTimeValue</code></li>
|
|
667
|
+
<li><code>acousticAlarmSelection</code></li>
|
|
668
|
+
<li><code>opticalAlarmSelection</code></li>
|
|
669
|
+
<li><code>dimmerLevel</code></li>
|
|
670
|
+
<li><code>dimmerColor</code></li>
|
|
671
|
+
<li><code>dimmerList</code></li>
|
|
672
|
+
<li><code>soundLevel</code></li>
|
|
673
|
+
<li><code>soundList</code></li>
|
|
674
|
+
</ul>
|
|
675
|
+
<p><code>soundList</code> und <code>dimmerList</code> akzeptieren auch ein einfaches Array von Werten, z.B. <code>msg.config.soundList = [1, 2]</code>.</p>
|
|
648
676
|
</script>
|
|
649
677
|
|
|
650
678
|
<script type="text/x-red" data-help-name="ccu-signal" lang="en-US">
|
|
651
679
|
<p>Send LED- and Sound-Signals.</p>
|
|
680
|
+
|
|
681
|
+
<h3>Dynamic configuration</h3>
|
|
682
|
+
<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>
|
|
683
|
+
<ul>
|
|
684
|
+
<li><code>iface</code></li>
|
|
685
|
+
<li><code>channel</code></li>
|
|
686
|
+
<li><code>channelType</code></li>
|
|
687
|
+
<li><code>chime</code></li>
|
|
688
|
+
<li><code>led</code></li>
|
|
689
|
+
<li><code>signal</code></li>
|
|
690
|
+
<li><code>volume</code></li>
|
|
691
|
+
<li><code>repeat</code></li>
|
|
692
|
+
<li><code>repetitions</code></li>
|
|
693
|
+
<li><code>durationUnit</code></li>
|
|
694
|
+
<li><code>durationValue</code></li>
|
|
695
|
+
<li><code>rampTimeUnit</code></li>
|
|
696
|
+
<li><code>rampTimeValue</code></li>
|
|
697
|
+
<li><code>acousticAlarmSelection</code></li>
|
|
698
|
+
<li><code>opticalAlarmSelection</code></li>
|
|
699
|
+
<li><code>dimmerLevel</code></li>
|
|
700
|
+
<li><code>dimmerColor</code></li>
|
|
701
|
+
<li><code>dimmerList</code></li>
|
|
702
|
+
<li><code>soundLevel</code></li>
|
|
703
|
+
<li><code>soundList</code></li>
|
|
704
|
+
</ul>
|
|
705
|
+
<p><code>soundList</code> and <code>dimmerList</code> also accept a plain array of values, e.g. <code>msg.config.soundList = [1, 2]</code>.</p>
|
|
652
706
|
</script>
|
package/nodes/ccu-signal.js
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
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
|
+
/** Fields that already carry a typed input (`<name>Type`) in the editor. */
|
|
7
|
+
const TYPED_VALUES = ['rampTimeValue', 'durationValue', 'repeat', 'volume', 'soundLevel'];
|
|
8
|
+
|
|
9
|
+
/** Everything a message may override through `msg.config` (B-2, #80 #148 #185). */
|
|
10
|
+
const DYNAMIC_KEYS = [
|
|
11
|
+
'iface',
|
|
12
|
+
'channel',
|
|
13
|
+
'channelType',
|
|
14
|
+
'chime',
|
|
15
|
+
'led',
|
|
16
|
+
'signal',
|
|
17
|
+
'acousticAlarmSelection',
|
|
18
|
+
'opticalAlarmSelection',
|
|
19
|
+
'durationUnit',
|
|
20
|
+
'rampTimeUnit',
|
|
21
|
+
'repetitions',
|
|
22
|
+
'dimmerLevel',
|
|
23
|
+
'dimmerColor',
|
|
24
|
+
'dimmerList',
|
|
25
|
+
'soundList',
|
|
26
|
+
...TYPED_VALUES,
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Accept both the editor's list shape and a plain array of values, so a
|
|
31
|
+
* flow can send `msg.config.soundList = [1, 2]` instead of building
|
|
32
|
+
* `[{sound: 1}, {sound: 2}]` by hand (#148).
|
|
33
|
+
* @param {*} list
|
|
34
|
+
* @param {string} key 'sound' or 'color'
|
|
35
|
+
* @returns {object[]}
|
|
36
|
+
*/
|
|
37
|
+
function normalizeList(list, key) {
|
|
38
|
+
if (!Array.isArray(list)) {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return list.map((item) => (item !== null && typeof item === 'object' ? item : {[key]: item}));
|
|
43
|
+
}
|
|
4
44
|
|
|
5
45
|
module.exports = function (RED) {
|
|
6
46
|
class CcuSignal {
|
|
@@ -33,7 +73,10 @@ module.exports = function (RED) {
|
|
|
33
73
|
this.error(error.message);
|
|
34
74
|
})
|
|
35
75
|
.then(() => {
|
|
36
|
-
|
|
76
|
+
// msg.config wins over both the stored config and the
|
|
77
|
+
// typed inputs above
|
|
78
|
+
const {config: effective} = effectiveConfig({...this.config, ...values}, message, DYNAMIC_KEYS);
|
|
79
|
+
this.sendCommand(effective)
|
|
37
80
|
.then(() => {
|
|
38
81
|
done();
|
|
39
82
|
})
|
|
@@ -51,12 +94,10 @@ module.exports = function (RED) {
|
|
|
51
94
|
|
|
52
95
|
switch (config.channelType) {
|
|
53
96
|
case 'SIGNAL_CHIME':
|
|
54
|
-
|
|
55
|
-
payload = [config.volume / 100, config.repeat, 108000, ...config.chime.split(',')];
|
|
56
|
-
console.log('payload', payload);
|
|
97
|
+
payload = [config.volume / 100, config.repeat, 108000, ...String(config.chime).split(',')];
|
|
57
98
|
return this.ccu.setValue(config.iface, config.channel, 'SUBMIT', payload);
|
|
58
99
|
case 'SIGNAL_LED':
|
|
59
|
-
payload = ['1', config.repeat, 108000, ...config.led.split(',')];
|
|
100
|
+
payload = ['1', config.repeat, 108000, ...String(config.led).split(',')];
|
|
60
101
|
return this.ccu.setValue(config.iface, config.channel, 'SUBMIT', payload);
|
|
61
102
|
case 'ALARM_SWITCH_VIRTUAL_RECEIVER':
|
|
62
103
|
return this.ccu.methodCall(config.iface, 'putParamset', [
|
|
@@ -70,6 +111,7 @@ module.exports = function (RED) {
|
|
|
70
111
|
},
|
|
71
112
|
]);
|
|
72
113
|
case 'DIMMER_VIRTUAL_RECEIVER': {
|
|
114
|
+
const dimmerList = normalizeList(config.dimmerList, 'color');
|
|
73
115
|
const parameters = {
|
|
74
116
|
LEVEL: config.dimmerLevel / 100,
|
|
75
117
|
RAMP_TIME_UNIT: config.rampTimeUnit,
|
|
@@ -77,9 +119,9 @@ module.exports = function (RED) {
|
|
|
77
119
|
DURATION_UNIT: config.durationUnit,
|
|
78
120
|
DURATION_VALUE: Number.parseInt(config.durationValue, 10) || 0,
|
|
79
121
|
REPETITIONS: Number(config.repetitions),
|
|
80
|
-
OUTPUT_SELECT_SIZE:
|
|
122
|
+
OUTPUT_SELECT_SIZE: dimmerList.length,
|
|
81
123
|
};
|
|
82
|
-
|
|
124
|
+
dimmerList.forEach((item, i) => {
|
|
83
125
|
const index = i + 1;
|
|
84
126
|
parameters['COLOR_LIST_' + index] = Number(item.color);
|
|
85
127
|
parameters['ON_TIME_LIST_' + index] = Number(item.ontime);
|
|
@@ -103,6 +145,7 @@ module.exports = function (RED) {
|
|
|
103
145
|
}
|
|
104
146
|
|
|
105
147
|
case 'ACOUSTIC_SIGNAL_VIRTUAL_RECEIVER': {
|
|
148
|
+
const soundList = normalizeList(config.soundList, 'sound');
|
|
106
149
|
const parameters = {
|
|
107
150
|
LEVEL: config.soundLevel / 100,
|
|
108
151
|
RAMP_TIME_UNIT: config.rampTimeUnit,
|
|
@@ -110,9 +153,9 @@ module.exports = function (RED) {
|
|
|
110
153
|
DURATION_UNIT: config.durationUnit,
|
|
111
154
|
DURATION_VALUE: Number.parseInt(config.durationValue, 10) || 0,
|
|
112
155
|
REPETITIONS: Number(config.repetitions),
|
|
113
|
-
OUTPUT_SELECT_SIZE:
|
|
156
|
+
OUTPUT_SELECT_SIZE: soundList.length,
|
|
114
157
|
};
|
|
115
|
-
|
|
158
|
+
soundList.forEach((item, i) => {
|
|
116
159
|
const index = i + 1;
|
|
117
160
|
parameters['SOUNDFILE_LIST_' + index] = Number(item.sound);
|
|
118
161
|
});
|