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
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
|
});
|
package/nodes/ccu-switch.html
CHANGED
|
@@ -239,7 +239,9 @@
|
|
|
239
239
|
arr.sort((a, b) => a.localeCompare(b));
|
|
240
240
|
$nodeInputChannel.autocomplete('option', 'source', arr);
|
|
241
241
|
|
|
242
|
-
|
|
242
|
+
// ${ENV} placeholders are substituted by Node-RED before the node is
|
|
243
|
+
// constructed (subflow parameters, #158) - do not clear them
|
|
244
|
+
if (!/^\$\{[^}]+\}$/.test($nodeInputChannel.val()) && !data[$nodeInputChannel.val().split(' ')[0]]) {
|
|
243
245
|
$nodeInputChannel.val('');
|
|
244
246
|
$nodeInputDatapoint.val('');
|
|
245
247
|
}
|
|
@@ -345,12 +347,12 @@
|
|
|
345
347
|
selectField.append($('<option></option>').val(operators[d].v).text(node._(operators[d].t)));
|
|
346
348
|
}
|
|
347
349
|
}
|
|
348
|
-
const valueField = $('<input/>', {class: 'node-input-rule-value', type: 'text', style: 'margin-left: 5px;'}).appendTo(row).typedInput({default: 'str', types: ['msg', 'flow', 'global', 'str', 'num', 'jsonata', 'env', previousValueType]});
|
|
350
|
+
const valueField = $('<input/>', {class: 'node-input-rule-value', type: 'text', style: 'margin-left: 5px;'}).appendTo(row).typedInput({default: 'str', types: ['msg', 'flow', 'global', 'str', 'num', 'bool', 'jsonata', 'env', previousValueType]});
|
|
349
351
|
const numValueField = $('<input/>', {class: 'node-input-rule-num-value', type: 'text', style: 'margin-left: 5px;'}).appendTo(row).typedInput({default: 'num', types: ['flow', 'global', 'num', 'jsonata', 'env']});
|
|
350
352
|
const expValueField = $('<input/>', {class: 'node-input-rule-exp-value', type: 'text', style: 'margin-left: 5px;'}).appendTo(row).typedInput({default: 'jsonata', types: ['jsonata']});
|
|
351
|
-
const btwnValueField = $('<input/>', {class: 'node-input-rule-btwn-value', type: 'text', style: 'margin-left: 5px;'}).appendTo(row).typedInput({default: 'num', types: ['msg', 'flow', 'global', 'str', 'num', 'jsonata', 'env', previousValueType]});
|
|
353
|
+
const btwnValueField = $('<input/>', {class: 'node-input-rule-btwn-value', type: 'text', style: 'margin-left: 5px;'}).appendTo(row).typedInput({default: 'num', types: ['msg', 'flow', 'global', 'str', 'num', 'bool', 'jsonata', 'env', previousValueType]});
|
|
352
354
|
const btwnAndLabel = $('<span/>', {class: 'node-input-rule-btwn-label'}).text(' ' + andLabel + ' ').appendTo(row3);
|
|
353
|
-
const btwnValue2Field = $('<input/>', {class: 'node-input-rule-btwn-value2', type: 'text', style: 'margin-left:2px;'}).appendTo(row3).typedInput({default: 'num', types: ['msg', 'flow', 'global', 'str', 'num', 'jsonata', 'env', previousValueType]});
|
|
355
|
+
const btwnValue2Field = $('<input/>', {class: 'node-input-rule-btwn-value2', type: 'text', style: 'margin-left:2px;'}).appendTo(row3).typedInput({default: 'num', types: ['msg', 'flow', 'global', 'str', 'num', 'bool', 'jsonata', 'env', previousValueType]});
|
|
354
356
|
const typeValueField = $('<input/>', {class: 'node-input-rule-type-value', type: 'text', style: 'margin-left: 5px;'}).appendTo(row)
|
|
355
357
|
.typedInput({default: 'string', types: [
|
|
356
358
|
{value: 'string', label: 'string', hasValue: false},
|
|
@@ -801,6 +803,17 @@
|
|
|
801
803
|
<p>Der ccu switch Node verhält sich ähnlich wie der switch Node von Node-RED. Er leitet eine Nachricht abhängig von einem Zustand eines Aktors oder einer Systemvariablen weiter, wahlweise auch je nach Zustand an unterschiedliche Ausgänge.</p>
|
|
802
804
|
<h4>Dokumentation</h4>
|
|
803
805
|
<a target="_blank" href="https://github.com/rdmtc/RedMatic/wiki/CCU-Nodes#ccu-switch-node">ccu switch node im RedMatic Wiki</a>
|
|
806
|
+
|
|
807
|
+
<h3>Dynamische Konfiguration</h3>
|
|
808
|
+
<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>
|
|
809
|
+
<ul>
|
|
810
|
+
<li><code>iface</code></li>
|
|
811
|
+
<li><code>channel</code></li>
|
|
812
|
+
<li><code>datapoint</code></li>
|
|
813
|
+
<li><code>datapointProperty</code></li>
|
|
814
|
+
<li><code>sysvar</code></li>
|
|
815
|
+
<li><code>sysvarProperty</code></li>
|
|
816
|
+
</ul>
|
|
804
817
|
</script>
|
|
805
818
|
|
|
806
819
|
|
package/nodes/ccu-switch.js
CHANGED
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
**/
|
|
16
16
|
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
const {effectiveConfig} = require(path.join(__dirname, '/lib/dynconfig.js'));
|
|
20
|
+
|
|
21
|
+
/** Source selection a message may override through msg.config (B-2, #103). */
|
|
22
|
+
const DYNAMIC_KEYS = ['iface', 'channel', 'datapoint', 'datapointProperty', 'sysvar', 'sysvarProperty'];
|
|
23
|
+
|
|
17
24
|
module.exports = function (RED) {
|
|
18
25
|
'use strict';
|
|
19
26
|
|
|
@@ -132,11 +139,28 @@ module.exports = function (RED) {
|
|
|
132
139
|
|
|
133
140
|
function getProperty(node, msg) {
|
|
134
141
|
return new Promise((resolve, reject) => {
|
|
135
|
-
|
|
136
|
-
|
|
142
|
+
// which datapoint or variable to look at can come from the
|
|
143
|
+
// message, so one switch node can serve a whole subflow (#103)
|
|
144
|
+
const {config} = effectiveConfig(
|
|
145
|
+
{
|
|
146
|
+
iface: node.iface,
|
|
147
|
+
channel: node.channel,
|
|
148
|
+
datapoint: node.datapoint,
|
|
149
|
+
datapointProperty: node.datapointProperty,
|
|
150
|
+
sysvar: node.sysvar,
|
|
151
|
+
sysvarProperty: node.sysvarProperty
|
|
152
|
+
},
|
|
153
|
+
msg,
|
|
154
|
+
DYNAMIC_KEYS
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
if (config.iface === 'ReGaHSS') {
|
|
158
|
+
const sysvar = node.ccu.sysvar[config.sysvar];
|
|
159
|
+
resolve(sysvar && sysvar[config.sysvarProperty || 'value']);
|
|
137
160
|
} else {
|
|
138
|
-
const address =
|
|
139
|
-
|
|
161
|
+
const address = config.iface + '.' + String(config.channel).split(' ')[0] + '.' + config.datapoint;
|
|
162
|
+
const value = node.ccu.values[address];
|
|
163
|
+
resolve(value && value[config.datapointProperty || 'value']);
|
|
140
164
|
}
|
|
141
165
|
});
|
|
142
166
|
}
|