node-red-contrib-ccu 3.4.2 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/.prettierignore +10 -0
  2. package/.prettierrc +14 -0
  3. package/AGENTS.md +96 -0
  4. package/CHANGELOG.md +66 -0
  5. package/README.md +17 -21
  6. package/ROADMAP.md +402 -0
  7. package/eslint.config.js +45 -0
  8. package/examples/rpc-events.json +65 -0
  9. package/examples/switch-datapoint.json +86 -0
  10. package/examples/system-variable.json +60 -0
  11. package/nodes/ccu-alexa.html +12 -7
  12. package/nodes/ccu-alexa.js +33 -14
  13. package/nodes/ccu-connection.html +9 -1
  14. package/nodes/ccu-connection.js +588 -335
  15. package/nodes/ccu-display.html +11 -4
  16. package/nodes/ccu-display.js +26 -9
  17. package/nodes/ccu-get-value.html +13 -4
  18. package/nodes/ccu-get-value.js +2 -2
  19. package/nodes/ccu-mqtt.html +9 -10
  20. package/nodes/ccu-mqtt.js +55 -29
  21. package/nodes/ccu-program.html +1 -1
  22. package/nodes/ccu-program.js +9 -7
  23. package/nodes/ccu-rpc-event.html +12 -7
  24. package/nodes/ccu-rpc-event.js +4 -5
  25. package/nodes/ccu-rpc.html +12 -5
  26. package/nodes/ccu-rpc.js +8 -6
  27. package/nodes/ccu-script.html +1 -1
  28. package/nodes/ccu-script.js +4 -3
  29. package/nodes/ccu-set-value.html +11 -7
  30. package/nodes/ccu-set-value.js +40 -16
  31. package/nodes/ccu-signal.html +11 -5
  32. package/nodes/ccu-signal.js +34 -23
  33. package/nodes/ccu-switch.html +13 -5
  34. package/nodes/ccu-sysvar.html +1 -1
  35. package/nodes/ccu-sysvar.js +11 -7
  36. package/nodes/ccu-value.html +12 -7
  37. package/nodes/ccu-value.js +36 -13
  38. package/nodes/lib/base62.js +22 -0
  39. package/nodes/lib/discover.js +127 -0
  40. package/nodes/lib/nextport.js +30 -0
  41. package/nodes/lib/similarity.js +51 -0
  42. package/nodes/lib/status.js +1 -1
  43. package/package.json +105 -155
  44. package/roadmap-archive/README.md +20 -0
  45. package/roadmap-archive/phase-1.md +48 -0
  46. package/roadmap-archive/todo-history.md +39 -0
  47. package/TODO.md +0 -41
  48. package/ccu_rega_localhost.json +0 -1
  49. package/ccu_values_localhost.json +0 -1
@@ -14,7 +14,7 @@
14
14
  return false;
15
15
  }
16
16
  }},
17
- topic: {value: '${CCU}/${Interface}/${Method}'} // eslint-disable-line no-template-curly-in-string
17
+ topic: {value: '${CCU}/${Interface}/${Method}'}
18
18
  },
19
19
  inputs: 1,
20
20
  outputs: 1,
@@ -34,6 +34,7 @@
34
34
 
35
35
  let ifacesLoaded = false;
36
36
  let ifacesPending = false;
37
+ let ifaceLoadRetries = 5;
37
38
 
38
39
  function loadIfaces(iface, cb) {
39
40
  if (ifacesPending) {
@@ -41,7 +42,6 @@
41
42
  }
42
43
 
43
44
  ifacesPending = true;
44
- console.log('loadIfaces()');
45
45
  $nodeInputIface.html('<option></option>');
46
46
  const nodeId = $nodeInputCcuConfig.val();
47
47
  if (nodeId === '_ADD_') {
@@ -59,12 +59,17 @@
59
59
  cb();
60
60
  ifacesPending = false;
61
61
  }
62
+ }).fail(() => {
63
+ // config node not (yet) available in the runtime - allow retrying
64
+ ifacesPending = false;
65
+ if (ifaceLoadRetries-- > 0) {
66
+ setTimeout(() => loadIfaces(iface, cb), 1000);
67
+ }
62
68
  });
63
69
  }
64
70
  }
65
71
 
66
72
  $nodeInputCcuConfig.change(() => {
67
- console.log('$nodeInputCcuConfig change');
68
73
  loadIfaces(this.iface, () => {
69
74
  ifacesLoaded = true;
70
75
  $nodeInputIface.removeAttr('disabled');
@@ -72,6 +77,10 @@
72
77
  });
73
78
  });
74
79
 
80
+ // Node-RED 4.x does not reliably fire the change event on dialog
81
+ // open - trigger the initial load explicitly (see ROADMAP.md 8.1)
82
+ $nodeInputCcuConfig.trigger('change');
83
+
75
84
  $('#node-input-method').autocomplete({
76
85
  source: [],
77
86
  close: () => {
@@ -83,7 +92,6 @@
83
92
 
84
93
  $('#node-input-iface').change(() => {
85
94
  if (ifacesLoaded) {
86
- console.log('#node-input-iface change');
87
95
  autocompleteMethods();
88
96
  }
89
97
  });
@@ -116,7 +124,6 @@
116
124
  methods = [];
117
125
  }
118
126
 
119
- console.log('autocompleteMethods', iface, methods.length);
120
127
  $('#node-input-method').autocomplete('option', 'source', methods);
121
128
  }
122
129
  }
package/nodes/ccu-rpc.js CHANGED
@@ -37,27 +37,29 @@ module.exports = function (RED) {
37
37
  } else if (method === 'putParamset') {
38
38
  let [address, paramset, values] = parameters;
39
39
  values = values || {};
40
- Object.keys(values).forEach(parameter => {
40
+ Object.keys(values).forEach((parameter) => {
41
41
  values[parameter] = this.ccu.paramCast(iface, address, paramset, parameter, values[parameter]);
42
42
  });
43
43
  parameters[2] = values;
44
44
  }
45
45
 
46
- this.ccu.methodCall(iface, method, parameters)
47
- .then(res => {
46
+ this.ccu
47
+ .methodCall(iface, method, parameters)
48
+ .then((res) => {
48
49
  const message = {
49
50
  ccu: this.ccu.host,
50
51
  iface,
51
52
  topic: config.topic,
52
53
  payload: res,
53
- ts: (new Date()).getTime(),
54
- method
54
+ ts: new Date().getTime(),
55
+ method,
55
56
  };
56
57
  message.topic = this.ccu.topicReplace(config.topic, message);
57
58
  send(message);
58
59
 
59
60
  done();
60
- }).catch(error => {
61
+ })
62
+ .catch((error) => {
61
63
  done(error);
62
64
  });
63
65
  });
@@ -6,7 +6,7 @@
6
6
  name: {value: ''},
7
7
  script: {value: ''},
8
8
  ccuConfig: {value: 'localhost', type: 'ccu-connection', required: true},
9
- topic: {value: '${CCU}/${Interface}'} // eslint-disable-line no-template-curly-in-string
9
+ topic: {value: '${CCU}/${Interface}'}
10
10
  },
11
11
  inputs: 1,
12
12
  outputs: 1,
@@ -21,8 +21,9 @@ module.exports = function (RED) {
21
21
  _input(message, send, done) {
22
22
  let script = this.script || message.payload;
23
23
  script += '\n\nvar nr_script_call_success = true;\n';
24
- this.ccu.script(script)
25
- .then(message => {
24
+ this.ccu
25
+ .script(script)
26
+ .then((message) => {
26
27
  message.iface = this.iface;
27
28
  message.ccu = this.ccu.host;
28
29
  message.topic = this.ccu.topicReplace(this.topic, message);
@@ -36,7 +37,7 @@ module.exports = function (RED) {
36
37
  done(new Error('Script call failed'));
37
38
  }
38
39
  })
39
- .catch(error => {
40
+ .catch((error) => {
40
41
  this.status({fill: 'red', shape: 'dot', text: 'error'});
41
42
  done(error);
42
43
  });
@@ -49,6 +49,7 @@
49
49
 
50
50
  let ifacesLoaded = false;
51
51
  let ifacesPending = false;
52
+ let ifaceLoadRetries = 5;
52
53
 
53
54
  $('#node-input-force-dropdown').val(String(Boolean(this.force)));
54
55
 
@@ -96,7 +97,6 @@
96
97
  }
97
98
 
98
99
  ifacesPending = true;
99
- console.log('loadIfaces()');
100
100
  $nodeInputIface.html('<option value="">*</option>');
101
101
  const nodeId = $nodeInputCcuConfig.val();
102
102
  if (nodeId === '_ADD_') {
@@ -116,6 +116,12 @@
116
116
  cb();
117
117
  ifacesPending = false;
118
118
  }
119
+ }).fail(() => {
120
+ // config node not (yet) available in the runtime - allow retrying
121
+ ifacesPending = false;
122
+ if (ifaceLoadRetries-- > 0) {
123
+ setTimeout(() => loadIfaces(iface, cb), 1000);
124
+ }
119
125
  });
120
126
  }
121
127
  }
@@ -128,15 +134,17 @@
128
134
  });
129
135
  });
130
136
 
137
+ // Node-RED 4.x does not reliably fire the change event on dialog
138
+ // open - trigger the initial load explicitly (see ROADMAP.md 8.1)
139
+ $nodeInputCcuConfig.trigger('change');
140
+
131
141
  function loadConfig() {
132
142
  if (ifacesLoaded) {
133
- console.log('loadConfig');
134
143
  const nodeId = $nodeInputCcuConfig.val();
135
144
  const url = 'ccu?config=' + nodeId;
136
145
  $.getJSON(url, d => {
137
146
  data = d;
138
147
  processChannelAssignments();
139
- console.log('data', data);
140
148
  autocomplete();
141
149
  });
142
150
  }
@@ -150,7 +158,6 @@
150
158
  data.nameChannels = {};
151
159
  data.nameDevices = {};
152
160
 
153
- console.log(data);
154
161
 
155
162
  Object.keys(data.channelRooms).forEach(channel => {
156
163
  const room = data.channelRooms[channel];
@@ -236,7 +243,6 @@
236
243
  const channelNames = $('#node-input-channelName').val() ? [$('#node-input-channelName').val()] : [];
237
244
  const channelTypes = $('#node-input-channelType').val() ? [$('#node-input-channelType').val()] : [];
238
245
  //const channelIndexes = $('#node-input-channelIndex').val() ? [$('#node-input-channelIndex').val()] : [];
239
- const datapoints = $('#node-input-datapoint').val() ? [$('#node-input-datapoint').val()] : [];
240
246
 
241
247
  const lists = {
242
248
  device: [],
@@ -292,7 +298,6 @@
292
298
  }
293
299
  }
294
300
 
295
- console.log('autocomplete', {iface, rooms, funcs, devices, deviceNames, deviceTypes, channels, channelNames, channelTypes, datapoints});
296
301
 
297
302
  function checkDatapoint(datapoint) {
298
303
  let deviceCheck = false;
@@ -476,7 +481,6 @@
476
481
  });
477
482
  }
478
483
 
479
- console.log('lists', lists);
480
484
 
481
485
  Object.keys(lists).forEach(key => {
482
486
  if (key === 'channelIndex') {
@@ -37,13 +37,13 @@ module.exports = function (RED) {
37
37
  channelIndexRx: config.channelIndexRx,
38
38
  channelNameRx: config.channelNameRx,
39
39
  datapointRx: config.datapointRx,
40
- force: config.force
40
+ force: config.force,
41
41
  };
42
42
 
43
43
  this.blacklist = new Set();
44
44
  this.whitelist = new Set();
45
45
 
46
- this.on('input', message => {
46
+ this.on('input', (message) => {
47
47
  this.setValues(message);
48
48
  });
49
49
 
@@ -66,7 +66,7 @@ module.exports = function (RED) {
66
66
  setValues(message) {
67
67
  const {config} = this;
68
68
  let dynamicConfig = false;
69
- Object.keys(config).forEach(key => {
69
+ Object.keys(config).forEach((key) => {
70
70
  if (!config[key]) {
71
71
  if (key in message) {
72
72
  dynamicConfig = true;
@@ -81,12 +81,12 @@ module.exports = function (RED) {
81
81
  }
82
82
 
83
83
  let count = 0;
84
- Object.keys(this.ccu.metadata.devices).forEach(iface => {
84
+ Object.keys(this.ccu.metadata.devices).forEach((iface) => {
85
85
  if (config.iface && iface !== config.iface) {
86
86
  return;
87
87
  }
88
88
 
89
- Object.keys(this.ccu.metadata.devices[iface]).forEach(address => {
89
+ Object.keys(this.ccu.metadata.devices[iface]).forEach((address) => {
90
90
  if (this.blacklist.has(address)) {
91
91
  return;
92
92
  }
@@ -130,12 +130,18 @@ module.exports = function (RED) {
130
130
  return;
131
131
  }
132
132
 
133
- if (config.deviceNameRx === 'str' && this.ccu.channelNames[channel.PARENT] !== config.deviceName) {
133
+ if (
134
+ config.deviceNameRx === 'str' &&
135
+ this.ccu.channelNames[channel.PARENT] !== config.deviceName
136
+ ) {
134
137
  this.blacklist.add(address);
135
138
  return;
136
139
  }
137
140
 
138
- if (config.deviceNameRx === 're' && !this.ccu.channelNames[channel.PARENT].match(new RegExp(config.deviceName))) {
141
+ if (
142
+ config.deviceNameRx === 're' &&
143
+ !this.ccu.channelNames[channel.PARENT].match(new RegExp(config.deviceName))
144
+ ) {
139
145
  this.blacklist.add(address);
140
146
  return;
141
147
  }
@@ -171,7 +177,10 @@ module.exports = function (RED) {
171
177
  return;
172
178
  }
173
179
 
174
- if (config.channelIndexRx === 're' && !address.split(':')[1].match(new RegExp(String(config.channelIndex)))) {
180
+ if (
181
+ config.channelIndexRx === 're' &&
182
+ !address.split(':')[1].match(new RegExp(String(config.channelIndex)))
183
+ ) {
175
184
  this.blacklist.add(address);
176
185
  return;
177
186
  }
@@ -183,12 +192,18 @@ module.exports = function (RED) {
183
192
  return;
184
193
  }
185
194
 
186
- if (config.channelNameRx === 'str' && this.ccu.channelNames[address] !== config.channelName) {
195
+ if (
196
+ config.channelNameRx === 'str' &&
197
+ this.ccu.channelNames[address] !== config.channelName
198
+ ) {
187
199
  this.blacklist.add(address);
188
200
  return;
189
201
  }
190
202
 
191
- if (config.channelNameRx === 're' && !this.ccu.channelNames[address].match(new RegExp(config.channelName))) {
203
+ if (
204
+ config.channelNameRx === 're' &&
205
+ !this.ccu.channelNames[address].match(new RegExp(config.channelName))
206
+ ) {
192
207
  this.blacklist.add(address);
193
208
  return;
194
209
  }
@@ -207,7 +222,7 @@ module.exports = function (RED) {
207
222
 
208
223
  if (config.roomsRx === 're') {
209
224
  let match = false;
210
- this.ccu.channelRooms[address].forEach(room => {
225
+ this.ccu.channelRooms[address].forEach((room) => {
211
226
  if (room.match(new RegExp(config.rooms))) {
212
227
  match = true;
213
228
  }
@@ -225,14 +240,17 @@ module.exports = function (RED) {
225
240
  return;
226
241
  }
227
242
 
228
- if (config.functionsRx === 'str' && !this.ccu.channelFunctions[address].includes(config.functions)) {
243
+ if (
244
+ config.functionsRx === 'str' &&
245
+ !this.ccu.channelFunctions[address].includes(config.functions)
246
+ ) {
229
247
  this.blacklist.add(address);
230
248
  return;
231
249
  }
232
250
 
233
251
  if (config.functionsRx === 're') {
234
252
  let match = false;
235
- this.ccu.channelFunctions[address].forEach(func => {
253
+ this.ccu.channelFunctions[address].forEach((func) => {
236
254
  if (func.match(new RegExp(config.functions))) {
237
255
  match = true;
238
256
  }
@@ -250,7 +268,7 @@ module.exports = function (RED) {
250
268
  const psKey = this.ccu.paramsetName(iface, channel, 'VALUES');
251
269
  if (this.ccu.paramsetDescriptions[psKey]) {
252
270
  const rx = new RegExp(config.datapoint);
253
- Object.keys(this.ccu.paramsetDescriptions[psKey]).forEach(dp => {
271
+ Object.keys(this.ccu.paramsetDescriptions[psKey]).forEach((dp) => {
254
272
  if (config.datapointRx === 'str' && dp !== config.datapoint) {
255
273
  return;
256
274
  }
@@ -262,8 +280,14 @@ module.exports = function (RED) {
262
280
  const datapointName = iface + '.' + address + '.' + dp;
263
281
  const currentValue = this.ccu.values[datapointName] && this.ccu.values[datapointName].value;
264
282
  count += 1;
265
- if (dp.startsWith('PRESS_') || typeof currentValue === 'undefined' || currentValue !== message.payload) {
266
- this.ccu.setValueQueued(iface, address, dp, message.payload, false, config.force).catch(() => {});
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(() => {});
267
291
  }
268
292
  });
269
293
  }
@@ -151,6 +151,7 @@
151
151
 
152
152
  let ifacesLoaded = false;
153
153
  let ifacesPending = false;
154
+ let ifaceLoadRetries = 5;
154
155
 
155
156
  function loadIfaces(iface, cb) {
156
157
  if (ifacesPending) {
@@ -158,7 +159,6 @@
158
159
  }
159
160
 
160
161
  ifacesPending = true;
161
- console.log('loadIfaces()');
162
162
  $nodeInputIface.html('');
163
163
  const nodeId = $nodeInputCcuConfig.val();
164
164
  if (nodeId === '_ADD_') {
@@ -178,6 +178,12 @@
178
178
  cb();
179
179
  ifacesPending = false;
180
180
  }
181
+ }).fail(() => {
182
+ // config node not (yet) available in the runtime - allow retrying
183
+ ifacesPending = false;
184
+ if (ifaceLoadRetries-- > 0) {
185
+ setTimeout(() => loadIfaces(iface, cb), 1000);
186
+ }
181
187
  });
182
188
  }
183
189
  }
@@ -186,7 +192,6 @@
186
192
  if (ifacesLoaded) {
187
193
  const nodeId = $nodeInputCcuConfig.val();
188
194
  if (nodeId && nodeId !== '__ADD__') {
189
- console.log('loadConfig()');
190
195
  $.getJSON('ccu?type=signal&config=' + nodeId + '&iface=' + $nodeInputIface.val(), d => {
191
196
  data = d;
192
197
  autoCompleteChannel();
@@ -198,7 +203,6 @@
198
203
  }
199
204
 
200
205
  $nodeInputCcuConfig.change(() => {
201
- console.log('$nodeInputCcuConfig change');
202
206
  loadIfaces(this.iface, () => {
203
207
  ifacesLoaded = true;
204
208
  $nodeInputIface.removeAttr('disabled');
@@ -206,6 +210,10 @@
206
210
  });
207
211
  });
208
212
 
213
+ // Node-RED 4.x does not reliably fire the change event on dialog
214
+ // open - trigger the initial load explicitly (see ROADMAP.md 8.1)
215
+ $nodeInputCcuConfig.trigger('change');
216
+
209
217
  $nodeInputIface.change(() => {
210
218
  if (ifacesLoaded) {
211
219
  loadConfig();
@@ -258,7 +266,6 @@
258
266
  });
259
267
  }
260
268
 
261
- console.log('autoCompleteChannel()', arr.length);
262
269
  $nodeInputChannel.autocomplete('option', 'source', arr);
263
270
 
264
271
  if (!data[$nodeInputChannel.val().split(' ')[0]]) {
@@ -417,7 +424,6 @@
417
424
  $('#node-input-dimmer-container').editableList('items').each(function () {
418
425
  dimmerList.push({color: $(this).find('select.color').val(), ontime: $(this).find('select.ontime').val()});
419
426
  });
420
- console.log(dimmerList);
421
427
  this.dimmerList = dimmerList;
422
428
  break;
423
429
  }
@@ -29,16 +29,19 @@ module.exports = function (RED) {
29
29
  .then(() => this.getValue(values, 'repeat', message))
30
30
  .then(() => this.getValue(values, 'volume', message))
31
31
  .then(() => this.getValue(values, 'soundLevel', message))
32
- .catch(error => {
32
+ .catch((error) => {
33
33
  this.error(error.message);
34
34
  })
35
35
  .then(() => {
36
- this.sendCommand({...this.config, ...values}).then(() => {
37
- done();
38
- }).catch(error => {
39
- done(error);
40
- });
41
- }).catch(() => {});
36
+ this.sendCommand({...this.config, ...values})
37
+ .then(() => {
38
+ done();
39
+ })
40
+ .catch((error) => {
41
+ done(error);
42
+ });
43
+ })
44
+ .catch(() => {});
42
45
  });
43
46
  }
44
47
 
@@ -56,12 +59,16 @@ module.exports = function (RED) {
56
59
  payload = ['1', config.repeat, 108000, ...config.led.split(',')];
57
60
  return this.ccu.setValue(config.iface, config.channel, 'SUBMIT', payload);
58
61
  case 'ALARM_SWITCH_VIRTUAL_RECEIVER':
59
- return this.ccu.methodCall(config.iface, 'putParamset', [config.channel, 'VALUES', {
60
- ACOUSTIC_ALARM_SELECTION: config.acousticAlarmSelection,
61
- DURATION_UNIT: config.durationUnit,
62
- DURATION_VALUE: Number.parseInt(config.durationValue, 10) || 0,
63
- OPTICAL_ALARM_SELECTION: config.opticalAlarmSelection
64
- }]);
62
+ return this.ccu.methodCall(config.iface, 'putParamset', [
63
+ config.channel,
64
+ 'VALUES',
65
+ {
66
+ ACOUSTIC_ALARM_SELECTION: config.acousticAlarmSelection,
67
+ DURATION_UNIT: config.durationUnit,
68
+ DURATION_VALUE: Number.parseInt(config.durationValue, 10) || 0,
69
+ OPTICAL_ALARM_SELECTION: config.opticalAlarmSelection,
70
+ },
71
+ ]);
65
72
  case 'DIMMER_VIRTUAL_RECEIVER': {
66
73
  const parameters = {
67
74
  LEVEL: config.dimmerLevel / 100,
@@ -70,7 +77,7 @@ module.exports = function (RED) {
70
77
  DURATION_UNIT: config.durationUnit,
71
78
  DURATION_VALUE: Number.parseInt(config.durationValue, 10) || 0,
72
79
  REPETITIONS: Number(config.repetitions),
73
- OUTPUT_SELECT_SIZE: config.dimmerList.length
80
+ OUTPUT_SELECT_SIZE: config.dimmerList.length,
74
81
  };
75
82
  config.dimmerList.forEach((item, i) => {
76
83
  const index = i + 1;
@@ -81,14 +88,18 @@ module.exports = function (RED) {
81
88
  }
82
89
 
83
90
  case 'BSL_DIMMER_VIRTUAL_RECEIVER': {
84
- return this.ccu.methodCall(config.iface, 'putParamset', [config.channel, 'VALUES', {
85
- LEVEL: config.dimmerLevel / 100,
86
- RAMP_TIME_UNIT: config.rampTimeUnit,
87
- RAMP_TIME_VALUE: Number(config.rampTimeValue),
88
- DURATION_UNIT: config.durationUnit,
89
- DURATION_VALUE: Number.parseInt(config.durationValue, 10) || 0,
90
- COLOR: Number(config.dimmerColor)
91
- }]);
91
+ return this.ccu.methodCall(config.iface, 'putParamset', [
92
+ config.channel,
93
+ 'VALUES',
94
+ {
95
+ LEVEL: config.dimmerLevel / 100,
96
+ RAMP_TIME_UNIT: config.rampTimeUnit,
97
+ RAMP_TIME_VALUE: Number(config.rampTimeValue),
98
+ DURATION_UNIT: config.durationUnit,
99
+ DURATION_VALUE: Number.parseInt(config.durationValue, 10) || 0,
100
+ COLOR: Number(config.dimmerColor),
101
+ },
102
+ ]);
92
103
  }
93
104
 
94
105
  case 'ACOUSTIC_SIGNAL_VIRTUAL_RECEIVER': {
@@ -99,7 +110,7 @@ module.exports = function (RED) {
99
110
  DURATION_UNIT: config.durationUnit,
100
111
  DURATION_VALUE: Number.parseInt(config.durationValue, 10) || 0,
101
112
  REPETITIONS: Number(config.repetitions),
102
- OUTPUT_SELECT_SIZE: config.soundList.length
113
+ OUTPUT_SELECT_SIZE: config.soundList.length,
103
114
  };
104
115
  config.soundList.forEach((item, i) => {
105
116
  const index = i + 1;
@@ -113,6 +113,7 @@
113
113
 
114
114
  let ifacesLoaded = false;
115
115
  let ifacesPending = false;
116
+ let ifaceLoadRetries = 5;
116
117
 
117
118
  $('.form-row.datapoint').hide();
118
119
  $('.form-row.sysvar').hide();
@@ -134,7 +135,6 @@
134
135
  return;
135
136
  }
136
137
  ifacesPending = true;
137
- console.log('loadIfaces()');
138
138
 
139
139
  $nodeInputIface.html('');
140
140
 
@@ -154,6 +154,12 @@
154
154
  cb();
155
155
  ifacesPending = false;
156
156
  }
157
+ }).fail(() => {
158
+ // config node not (yet) available in the runtime - allow retrying
159
+ ifacesPending = false;
160
+ if (ifaceLoadRetries-- > 0) {
161
+ setTimeout(() => loadIfaces(iface, cb), 1000);
162
+ }
157
163
  });
158
164
  $.getJSON('ccu?type=sysvar&config=' + nodeId, data => {
159
165
  $nodeInputSysvar.html('');
@@ -163,6 +169,8 @@
163
169
  });
164
170
  }
165
171
  });
172
+ } else {
173
+ ifacesPending = false;
166
174
  }
167
175
  }
168
176
 
@@ -170,7 +178,6 @@
170
178
  if (!ifacesLoaded) {
171
179
  return;
172
180
  }
173
- console.log('loadConfig()');
174
181
  const nodeId = $nodeInputCcuConfig.val();
175
182
  const url = 'ccu?config=' + nodeId + '&type=channels&iface=' + $nodeInputIface.val();
176
183
  $.getJSON(url, d => {
@@ -181,13 +188,16 @@
181
188
  }
182
189
 
183
190
  $nodeInputCcuConfig.change(() => {
184
- console.log('$nodeInputCcuConfig change');
185
191
  loadIfaces(this.iface, () => {
186
192
  ifacesLoaded = true;
187
193
  loadConfig();
188
194
  });
189
195
  });
190
196
 
197
+ // Node-RED 4.x does not reliably fire the change event on dialog
198
+ // open - trigger the initial load explicitly (see ROADMAP.md 8.1)
199
+ $nodeInputCcuConfig.trigger('change');
200
+
191
201
  $nodeInputChannel.autocomplete({
192
202
  source: [],
193
203
  close: () => {
@@ -247,7 +257,6 @@
247
257
  }
248
258
 
249
259
  $nodeInputIface.change(() => {
250
- console.log('$nodeInputIface change');
251
260
  if ($nodeInputIface.val() === 'ReGaHSS') {
252
261
  $('.form-row.datapoint').hide();
253
262
  $('.form-row.sysvar').show();
@@ -531,7 +540,6 @@
531
540
  }
532
541
 
533
542
  $configLookupTree.on('treelistselect', (event, item) => {
534
- console.log(item);
535
543
  if (item && item.iface && item.channel && item.datapoint && item.property) {
536
544
  $('.form-row.sysvar').hide();
537
545
  $('.form-row.datapoint').show();
@@ -5,7 +5,7 @@
5
5
  defaults: {
6
6
  name: {value: ''},
7
7
  ccuConfig: {value: 'localhost', type: 'ccu-connection', required: true},
8
- topic: {value: 'ReGaHSS/${Name}'}, // eslint-disable-line no-template-curly-in-string
8
+ topic: {value: 'ReGaHSS/${Name}'},
9
9
  change: {value: true},
10
10
  cache: {value: true}
11
11
  },
@@ -28,11 +28,14 @@ module.exports = function (RED) {
28
28
  this.name = config.name;
29
29
  this.topic = config.topic;
30
30
 
31
- this.idSubscription = this.ccu.subscribeSysvar({name: this.name, cache: config.cache, change: config.change}, message => {
32
- this.status({fill: 'green', shape: 'ring', text: String(message.payload)});
33
- message.topic = this.ccu.topicReplace(config.topic, message);
34
- this.send(message);
35
- });
31
+ this.idSubscription = this.ccu.subscribeSysvar(
32
+ {name: this.name, cache: config.cache, change: config.change},
33
+ (message) => {
34
+ this.status({fill: 'green', shape: 'ring', text: String(message.payload)});
35
+ message.topic = this.ccu.topicReplace(config.topic, message);
36
+ this.send(message);
37
+ },
38
+ );
36
39
 
37
40
  this.on('input', this._input);
38
41
  this.on('close', this._destructor);
@@ -41,12 +44,13 @@ module.exports = function (RED) {
41
44
  _input(message, send, done) {
42
45
  const name = this.name || message.topic;
43
46
  const value = message.payload;
44
- this.ccu.setVariable(name, value)
47
+ this.ccu
48
+ .setVariable(name, value)
45
49
  .then(() => {
46
50
  this.status({fill: 'green', shape: 'ring', text: String(value)});
47
51
  done();
48
52
  })
49
- .catch(error => {
53
+ .catch((error) => {
50
54
  this.currentStatus = 'red';
51
55
  this.status({fill: 'red', shape: 'dot', text: 'error'});
52
56
  done(error);