smart-nodes 0.3.36 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +35 -17
  3. package/central/central.html +27 -24
  4. package/central/central.js +84 -26
  5. package/central/locales/de-DE/central.html +10 -0
  6. package/central/locales/de-DE/central.json +14 -0
  7. package/central/locales/en-US/central.html +10 -0
  8. package/central/locales/en-US/central.json +14 -0
  9. package/compare/compare.html +64 -87
  10. package/compare/compare.js +69 -29
  11. package/compare/locales/de-DE/compare.html +36 -0
  12. package/compare/locales/de-DE/compare.json +35 -0
  13. package/compare/locales/en-US/compare.html +36 -0
  14. package/compare/locales/en-US/compare.json +35 -0
  15. package/counter/counter.html +70 -72
  16. package/counter/counter.js +43 -20
  17. package/counter/locales/de-DE/counter.html +48 -0
  18. package/counter/locales/de-DE/counter.json +21 -0
  19. package/counter/locales/en-US/counter.html +48 -0
  20. package/counter/locales/en-US/counter.json +21 -0
  21. package/delay/delay.html +30 -102
  22. package/delay/delay.js +63 -20
  23. package/delay/locales/de-DE/delay.html +71 -0
  24. package/delay/locales/de-DE/delay.json +19 -0
  25. package/delay/locales/en-US/delay.html +76 -0
  26. package/delay/locales/en-US/delay.json +19 -0
  27. package/forwarder/forwarder.html +11 -42
  28. package/forwarder/forwarder.js +59 -18
  29. package/forwarder/locales/de-DE/forwarder.html +32 -0
  30. package/forwarder/locales/de-DE/forwarder.json +15 -0
  31. package/forwarder/locales/en-US/forwarder.html +32 -0
  32. package/forwarder/locales/en-US/forwarder.json +15 -0
  33. package/heating-curve/heating-curve.html +10 -51
  34. package/heating-curve/heating-curve.js +38 -13
  35. package/heating-curve/locales/de-DE/heating-curve.html +38 -0
  36. package/heating-curve/locales/de-DE/heating-curve.json +12 -0
  37. package/heating-curve/locales/en-US/heating-curve.html +38 -0
  38. package/heating-curve/locales/en-US/heating-curve.json +12 -0
  39. package/hysteresis/hysteresis.html +49 -69
  40. package/hysteresis/hysteresis.js +94 -68
  41. package/hysteresis/locales/de-DE/hysteresis.html +36 -0
  42. package/hysteresis/locales/de-DE/hysteresis.json +27 -0
  43. package/hysteresis/locales/en-US/hysteresis.html +36 -0
  44. package/hysteresis/locales/en-US/hysteresis.json +27 -0
  45. package/light/light.html +250 -0
  46. package/{light-control/light-control.js → light/light.js} +151 -32
  47. package/light/locales/de-DE/light.html +149 -0
  48. package/light/locales/de-DE/light.json +24 -0
  49. package/light/locales/en-US/light.html +148 -0
  50. package/light/locales/en-US/light.json +24 -0
  51. package/logic/locales/de-DE/logic.html +12 -0
  52. package/logic/locales/de-DE/logic.json +26 -0
  53. package/logic/locales/en-US/logic.html +12 -0
  54. package/logic/locales/en-US/logic.json +26 -0
  55. package/logic/logic.html +48 -64
  56. package/logic/logic.js +63 -29
  57. package/long-press/locales/de-DE/long-press.html +5 -0
  58. package/long-press/locales/de-DE/long-press.json +13 -0
  59. package/long-press/locales/en-US/long-press.html +5 -0
  60. package/long-press/locales/en-US/long-press.json +13 -0
  61. package/{long-press-control/long-press-control.html → long-press/long-press.html} +10 -14
  62. package/long-press/long-press.js +163 -0
  63. package/mixing-valve/locales/de-DE/mixing-valve.html +65 -0
  64. package/mixing-valve/locales/de-DE/mixing-valve.json +19 -0
  65. package/mixing-valve/locales/en-US/mixing-valve.html +66 -0
  66. package/mixing-valve/locales/en-US/mixing-valve.json +19 -0
  67. package/mixing-valve/mixing-valve.html +27 -93
  68. package/mixing-valve/mixing-valve.js +87 -61
  69. package/multi-press/locales/de-DE/multi-press.html +5 -0
  70. package/multi-press/locales/de-DE/multi-press.json +12 -0
  71. package/multi-press/locales/en-US/multi-press.html +5 -0
  72. package/multi-press/locales/en-US/multi-press.json +12 -0
  73. package/{multi-press-control/multi-press-control.html → multi-press/multi-press.html} +9 -13
  74. package/{multi-press-control/multi-press-control.js → multi-press/multi-press.js} +53 -5
  75. package/package.json +7 -7
  76. package/persistence.js +1 -0
  77. package/scene/locales/de-DE/scene.html +105 -0
  78. package/scene/locales/de-DE/scene.json +21 -0
  79. package/scene/locales/en-US/scene.html +107 -0
  80. package/scene/locales/en-US/scene.json +20 -0
  81. package/{scene-control/scene-control.html → scene/scene.html} +30 -132
  82. package/{scene-control/scene-control.js → scene/scene.js} +76 -26
  83. package/scheduler/locales/de-DE/scheduler.html +30 -0
  84. package/scheduler/locales/de-DE/scheduler.json +21 -0
  85. package/scheduler/locales/en-US/scheduler.html +30 -0
  86. package/scheduler/locales/en-US/scheduler.json +21 -0
  87. package/scheduler/scheduler.html +34 -64
  88. package/scheduler/scheduler.js +85 -53
  89. package/shutter/locales/de-DE/shutter.html +127 -0
  90. package/shutter/locales/de-DE/shutter.json +11 -0
  91. package/shutter/locales/en-US/shutter.html +133 -0
  92. package/shutter/locales/en-US/shutter.json +11 -0
  93. package/{shutter-control/shutter-control.html → shutter/shutter.html} +7 -133
  94. package/{shutter-control/shutter-control.js → shutter/shutter.js} +116 -56
  95. package/shutter-complex/locales/de-DE/shutter-complex.html +120 -0
  96. package/shutter-complex/locales/de-DE/shutter-complex.json +20 -0
  97. package/shutter-complex/locales/en-US/shutter-complex.html +120 -0
  98. package/shutter-complex/locales/en-US/shutter-complex.json +20 -0
  99. package/{shutter-complex-control/shutter-complex-control.html → shutter-complex/shutter-complex.html} +36 -140
  100. package/shutter-complex/shutter-complex.js +578 -0
  101. package/smart_helper.js +52 -9
  102. package/statistic/locales/de-DE/statistic.html +10 -0
  103. package/statistic/locales/de-DE/statistic.json +29 -0
  104. package/statistic/locales/en-US/statistic.html +10 -0
  105. package/statistic/locales/en-US/statistic.json +29 -0
  106. package/statistic/statistic.html +38 -43
  107. package/statistic/statistic.js +57 -28
  108. package/text-exec/locales/de-DE/text-exec.html +18 -0
  109. package/text-exec/locales/de-DE/text-exec.json +7 -0
  110. package/text-exec/locales/en-US/text-exec.html +18 -0
  111. package/text-exec/locales/en-US/text-exec.json +7 -0
  112. package/text-exec/text-exec.html +9 -25
  113. package/text-exec/text-exec.js +43 -2
  114. package/light-control/light-control.html +0 -363
  115. package/long-press-control/long-press-control.js +0 -76
  116. package/shutter-complex-control/shutter-complex-control.js +0 -442
@@ -0,0 +1,578 @@
1
+ module.exports = function (RED)
2
+ {
3
+ "use strict";
4
+
5
+ function ShutterComplexControlNode(config)
6
+ {
7
+ const node = this;
8
+ RED.nodes.createNode(node, config);
9
+
10
+ // ###################
11
+ // # Class constants #
12
+ // ###################
13
+ const ACTION_UP = 0;
14
+ const ACTION_DOWN = 1;
15
+ const ACTION_STOP = 2;
16
+ const ACTION_POSITION = 3;
17
+
18
+ // #######################
19
+ // # Global help objects #
20
+ // #######################
21
+ const smart_context = require("../persistence.js")(RED);
22
+ const helper = require("../smart_helper.js");
23
+
24
+
25
+ // ############################
26
+ // # Used from text-exec node #
27
+ // ############################
28
+ if (typeof config.exec_text_names == "string")
29
+ node.exec_text_names = config.exec_text_names.split(",").map(n => n.trim().toLowerCase());
30
+ else
31
+ node.exec_text_names = [];
32
+
33
+
34
+ // #####################
35
+ // # persistent values #
36
+ // #####################
37
+ var node_settings = helper.cloneObject({
38
+ last_position: 0, // 0 = opened, 100 = closed
39
+ last_direction_up: true, // remember last direction for toggle action
40
+ last_position_before_alarm: 0, // remember position to restore on alarm off event
41
+ alarm_active: false, // remember if alarm is on or off
42
+ }, smart_context.get(node.id));
43
+
44
+
45
+ // ##########################
46
+ // # Backward compatibility #
47
+ // ##########################
48
+ if (typeof config.max_time != "undefined")
49
+ {
50
+ config.max_time_up = config.max_time;
51
+ config.max_time_down = config.max_time;
52
+ delete config.max_time;
53
+ }
54
+
55
+
56
+ // ##################
57
+ // # Dynamic config #
58
+ // ##################
59
+ let max_time_up = parseInt(config.max_time_up || 60, 10);
60
+ let max_time_down = parseInt(config.max_time_down || 60, 10);
61
+ let revert_time_ms = parseInt(config.revert_time_ms || 100, 10);
62
+ let alarm_action = config.alarm_action || "NOTHING";
63
+ let alarm_off_action = config.alarm_off_action || "NOTHING";
64
+
65
+
66
+ // ##################
67
+ // # Runtime values #
68
+ // ##################
69
+
70
+ // Here the setTimeout return value is stored to stop the shutter.
71
+ // That means if it is null, the shutter is stopped.
72
+ let timeout = null;
73
+
74
+ // The local time when the shutter starts moving.
75
+ // This is needed to calc the new position of the shutter.
76
+ let on_time = null;
77
+
78
+ // The local time when the shutter was stopped last time.
79
+ // This is used to measure the revert time.
80
+ let off_time = null;
81
+
82
+ // This is the return value of setTimeout when the shutter has to wait until the reverse time is finished.
83
+ let wait_timeout = null;
84
+
85
+
86
+ // #########################
87
+ // # Central node handling #
88
+ // #########################
89
+ var event = "node:" + config.id;
90
+ var handler = function (msg)
91
+ {
92
+ node.receive(msg);
93
+ }
94
+ RED.events.on(event, handler);
95
+
96
+
97
+ // ###############
98
+ // # Node events #
99
+ // ###############
100
+ node.on("input", function (msg)
101
+ {
102
+ handleTopic(msg);
103
+
104
+ setStatus();
105
+ smart_context.set(node.id, node_settings);
106
+ });
107
+
108
+ node.on("close", function ()
109
+ {
110
+ startAction(ACTION_STOP);
111
+ RED.events.off(event, handler);
112
+ });
113
+
114
+
115
+ // #####################
116
+ // # Private functions #
117
+ // #####################
118
+
119
+ // This is the main function which handles all topics that was received.
120
+ let handleTopic = msg =>
121
+ {
122
+ helper.log("handle topic:");
123
+ helper.log(msg);
124
+
125
+ let real_topic = helper.getRealTopic(msg.topic, "toggle", ["up", "up_stop", "down", "down_stop", "stop", "toggle", "up_down", "position", "alarm"]);
126
+
127
+ // skip if button is released
128
+ if (msg.payload === false && ["up", "up_stop", "down", "down_stop", "stop", "toggle"].includes(real_topic))
129
+ return;
130
+
131
+ // Convert up_down from HA UI to next command
132
+ if (real_topic == "up_down")
133
+ {
134
+ if (msg.payload)
135
+ real_topic = "down";
136
+ else
137
+ real_topic = "up";
138
+
139
+ delete msg.payload;
140
+ }
141
+
142
+ // Correct next topic to avoid handling up_stop, down_stop or toggle separately.
143
+ if (timeout != null && ["up_stop", "down_stop", "toggle"].includes(real_topic))
144
+ {
145
+ real_topic = "stop";
146
+ }
147
+ else if (timeout == null)
148
+ {
149
+ // shutter is not running, set next command depending on topic
150
+ switch (real_topic)
151
+ {
152
+ case "up_stop":
153
+ real_topic = "up";
154
+ break;
155
+
156
+ case "down_stop":
157
+ real_topic = "down";
158
+ break;
159
+
160
+ case "toggle":
161
+ real_topic = node_settings.last_direction_up ? "down" : "up";
162
+ break;
163
+ }
164
+ }
165
+
166
+ helper.log("handle real topic: " + real_topic);
167
+ switch (real_topic)
168
+ {
169
+ case "up":
170
+ startAction(ACTION_UP, msg.time_on ?? null, msg.exact ?? null);
171
+ break;
172
+
173
+ case "stop":
174
+ startAction(ACTION_STOP);
175
+ break;
176
+
177
+ case "down":
178
+ startAction(ACTION_DOWN, msg.time_on ?? null, msg.exact ?? null);
179
+ break;
180
+
181
+ case "position":
182
+ startAction(ACTION_POSITION, msg.payload ?? null);
183
+ break;
184
+
185
+ case "alarm":
186
+ // Make sure it is bool
187
+ msg.payload = !!msg.payload;
188
+
189
+ // No alarm change, do nothing
190
+ if (node_settings.alarm_active == msg.payload)
191
+ return;
192
+
193
+ node_settings.alarm_active = msg.payload;
194
+
195
+ if (node_settings.alarm_active)
196
+ handleAlarmOn();
197
+ else
198
+ handleAlarmOff();
199
+
200
+ break;
201
+ }
202
+ };
203
+
204
+ /**
205
+ * This function is called when the alarm changes from off to on
206
+ */
207
+ let handleAlarmOn = () =>
208
+ {
209
+ switch (alarm_action)
210
+ {
211
+ case "UP":
212
+ startAction(ACTION_UP, null, null, true);
213
+ break;
214
+
215
+ case "DOWN":
216
+ startAction(ACTION_DOWN, null, null, true);
217
+ break;
218
+ }
219
+ }
220
+
221
+ /**
222
+ * This function is called when the alarm changes from on to off
223
+ */
224
+ let handleAlarmOff = () =>
225
+ {
226
+ switch (alarm_off_action)
227
+ {
228
+ case "NOTHING":
229
+ break;
230
+
231
+ case "UP":
232
+ startAction(ACTION_UP);
233
+ break;
234
+
235
+ case "DOWN":
236
+ startAction(ACTION_DOWN);
237
+ break;
238
+
239
+ case "LAST":
240
+ startAction(ACTION_POSITION, node_settings.last_position_before_alarm);
241
+ break;
242
+ }
243
+ }
244
+
245
+ /**
246
+ * This functions stops the shutter if needed and starts the requested action.
247
+ *
248
+ * @param {int} action One of ACTION_UP, ACTION_STOP, ACTION_DOWN or ACTION_POSITION.
249
+ * @param {string|float|null} data For ACTION_POSITION this is the position, for ACTION_UP and ACTION_DOWN it is the max run time.
250
+ * @param {boolean?} exact If true, the exact given time is used, otherwise some offset at 0% and 100% is used to ensure the shutter reach the end.
251
+ * @param {boolean?} ignoreAlarm If true, the action is performed even the alarm is activated. The default is false.
252
+ */
253
+ let startAction = (action, data = null, exact = false, ignoreAlarm = false) =>
254
+ {
255
+ helper.log("startAction");
256
+ helper.log({ action, data, exact, ignoreAlarm });
257
+
258
+ // Nothing allowed if alarm is on
259
+ if (ignoreAlarm === false && node_settings.alarm_active)
260
+ return;
261
+
262
+ // Variable declarations
263
+ let run_time_ms;
264
+ let needStop = false;
265
+ let now = Date.now();
266
+ let currentPosition = calcNewPosition();
267
+
268
+ // handle 0% and 100% as UP and DOWN command
269
+ if (action == ACTION_POSITION && data === 0)
270
+ {
271
+ action = ACTION_UP;
272
+ data = null;
273
+ }
274
+ else if (action == ACTION_POSITION && data === 100)
275
+ {
276
+ action = ACTION_DOWN;
277
+ data = null;
278
+ }
279
+
280
+ // Stop waiting for revert time, it will be restarted later with the correct following action
281
+ if (wait_timeout != null)
282
+ {
283
+ clearTimeout(wait_timeout);
284
+ wait_timeout = null;
285
+ }
286
+
287
+ // Shutter is running, check if it has to be stopped first
288
+ if (timeout != null)
289
+ {
290
+ switch (action)
291
+ {
292
+ case ACTION_UP:
293
+ if (node_settings.last_direction_up === false)
294
+ needStop = true;
295
+ break;
296
+
297
+ case ACTION_DOWN:
298
+ if (node_settings.last_direction_up === true)
299
+ needStop = true;
300
+ break;
301
+
302
+ case ACTION_STOP:
303
+ needStop = true;
304
+ break;
305
+
306
+ case ACTION_POSITION:
307
+ // Wrong direction, do stop
308
+ if (node_settings.last_direction_up === true && currentPosition < data)
309
+ needStop = true;
310
+ if (node_settings.last_direction_up === false && currentPosition > data)
311
+ needStop = true;
312
+ break;
313
+ }
314
+ }
315
+
316
+ // Stop shutter if needed and save new positions
317
+ if (needStop)
318
+ {
319
+ clearTimeout(timeout);
320
+ off_time = Date.now();
321
+ timeout = null;
322
+
323
+ if (!node_settings.alarm_active)
324
+ node_settings.last_position_before_alarm = currentPosition;
325
+
326
+ node_settings.last_position = currentPosition;
327
+ sendToOutput(false, false);
328
+ }
329
+
330
+ // Determine needed runtime
331
+ switch (action)
332
+ {
333
+ case ACTION_STOP:
334
+ // Already stopped, nothing more to do
335
+ return;
336
+
337
+ case ACTION_UP:
338
+ // data is the run time
339
+ if (data == null)
340
+ run_time_ms = node_settings.last_position * max_time_up / 100 * 1000;
341
+ else
342
+ run_time_ms = helper.getTimeInMsFromString(data);
343
+ break;
344
+
345
+ case ACTION_DOWN:
346
+ // data is the run time
347
+ if (data == null)
348
+ run_time_ms = (100 - node_settings.last_position) * max_time_down / 100 * 1000;
349
+ else
350
+ run_time_ms = helper.getTimeInMsFromString(data);
351
+ break;
352
+
353
+ case ACTION_POSITION:
354
+ // data is the position in percent
355
+ if (data == null)
356
+ {
357
+ console.warn("WARN: Try to set position without giving a new position");
358
+ return;
359
+ }
360
+
361
+ // Make sure it is in range 0-100
362
+ data = Math.min(100, Math.max(0, data));
363
+
364
+ // Convert to UP/DOWN with a specific time
365
+ if (data < currentPosition)
366
+ {
367
+ run_time_ms = (currentPosition - data) / 100 * max_time_up * 1000;
368
+ action = ACTION_UP;
369
+ }
370
+ else // if (data > currentPosition)
371
+ {
372
+ action = ACTION_DOWN;
373
+ run_time_ms = (data - currentPosition) / 100 * max_time_down * 1000;
374
+ }
375
+ break;
376
+ }
377
+
378
+ if (exact !== true && data == null)
379
+ {
380
+ // Run at least for 5 seconds
381
+ if (run_time_ms < 5000)
382
+ run_time_ms = 5000;
383
+ }
384
+
385
+ let dirChange = ((action == ACTION_UP && !node_settings.last_direction_up) || (action == ACTION_DOWN && node_settings.last_direction_up));
386
+
387
+ // Just to make sure there is no mistake
388
+ if (run_time_ms < 0)
389
+ run_time_ms = 0;
390
+
391
+ if (timeout != null)
392
+ {
393
+ // This happens if the time needs to be changed, but the direction is the same
394
+ clearTimeout(timeout);
395
+ helper.log("stop after " + run_time_ms + "ms");
396
+
397
+ off_time = Date.now();
398
+ on_time = off_time;
399
+ node_settings.last_position = currentPosition;
400
+
401
+ timeout = setTimeout(() =>
402
+ {
403
+ // Just stop after the new time
404
+ startAction(ACTION_STOP, null, null, ignoreAlarm);
405
+ timeout = null;
406
+
407
+ smart_context.set(node.id, node_settings);
408
+
409
+ setStatus();
410
+ }, run_time_ms);
411
+ }
412
+ else if (off_time + revert_time_ms - now > 0 && dirChange)
413
+ {
414
+ // revert time is not fully passed
415
+ helper.log("revert time is not fully passed, wait for " + (off_time + revert_time_ms - now) + "ms");
416
+ wait_timeout = setTimeout(() =>
417
+ {
418
+ wait_timeout = null;
419
+ startAction(action, data, exact, ignoreAlarm);
420
+
421
+ smart_context.set(node.id, node_settings);
422
+
423
+ setStatus();
424
+ }, off_time + revert_time_ms - now);
425
+ }
426
+ else if (run_time_ms == 0)
427
+ {
428
+ // Do nothing.
429
+ // This can happen if exact = true but the shutter is already at the target position
430
+ }
431
+ else
432
+ {
433
+ // The shutter can finally be started.
434
+ switch (action)
435
+ {
436
+ case ACTION_UP:
437
+ helper.log("start ACTION_UP");
438
+ sendToOutput(true, false);
439
+ break;
440
+
441
+ case ACTION_DOWN:
442
+ helper.log("start ACTION_DOWN");
443
+ sendToOutput(false, true);
444
+ break;
445
+ }
446
+
447
+ if (!node_settings.alarm_active)
448
+ node_settings.last_position_before_alarm = currentPosition;
449
+
450
+ on_time = Date.now();
451
+
452
+ helper.log("stop after " + run_time_ms + "ms");
453
+ timeout = setTimeout(() =>
454
+ {
455
+ startAction(ACTION_STOP, null, null, ignoreAlarm);
456
+ timeout = null;
457
+
458
+ smart_context.set(node.id, node_settings);
459
+
460
+ setStatus();
461
+ }, run_time_ms);
462
+ }
463
+ }
464
+
465
+ /**
466
+ *
467
+ * @param {*} up
468
+ * @param {*} down
469
+ * @returns
470
+ */
471
+ let sendToOutput = (up, down) =>
472
+ {
473
+ helper.log("sendToOutput");
474
+ helper.log({ up, down });
475
+
476
+ if (up && down)
477
+ {
478
+ console.error("Fatal exception, Cannot send up and down at the same time.");
479
+ return;
480
+ }
481
+
482
+ if (up)
483
+ node_settings.last_direction_up = true;
484
+ else if (down)
485
+ node_settings.last_direction_up = false;
486
+
487
+ node.send([{ payload: up }, { payload: down }, { payload: node_settings.last_position }]);
488
+
489
+ // Inform central nodes that shutter is running/stopped
490
+ notifyCentral(up || down);
491
+ }
492
+
493
+ /**
494
+ *
495
+ * @returns
496
+ */
497
+ let calcNewPosition = () =>
498
+ {
499
+ let now = Date.now();
500
+
501
+ if (timeout == null)
502
+ return node_settings.last_position;
503
+
504
+ // Change position while running,
505
+ // Calculate current position first
506
+ if (node_settings.last_direction_up)
507
+ {
508
+ let change_percentage = (now - on_time) / 1000 / max_time_up * 100;
509
+ return Math.max(0, node_settings.last_position - change_percentage);
510
+ }
511
+ else
512
+ {
513
+ let change_percentage = (now - on_time) / 1000 / max_time_down * 100;
514
+ return Math.min(100, node_settings.last_position + change_percentage);
515
+ }
516
+ }
517
+
518
+ /**
519
+ * Set the current node status
520
+ */
521
+ let setStatus = () =>
522
+ {
523
+ let fill = node_settings.alarm_active ? "red" : "green";
524
+ let shape = timeout != null ? "ring" : "dot";
525
+
526
+ // collect all texts and join later with a comma
527
+ let texts = [];
528
+
529
+ if (node_settings.alarm_active)
530
+ texts.push("ALARM");
531
+
532
+ if (timeout == null)
533
+ texts.push("Stopped");
534
+ else if (node_settings.last_direction_up)
535
+ texts.push("Up");
536
+ else
537
+ texts.push("Down");
538
+
539
+ texts.push("Position: " + node_settings.last_position?.toFixed(0) + "%");
540
+
541
+ node.status({ fill, shape, text: helper.getCurrentTimeForStatus() + ": " + texts.join(", ") });
542
+ }
543
+
544
+ /**
545
+ * Notify all connected central nodes
546
+ * @param {boolean} state The state if the shutter is running
547
+ * @returns
548
+ */
549
+ let notifyCentral = state =>
550
+ {
551
+ if (!config.links)
552
+ return;
553
+
554
+ config.links.forEach(link =>
555
+ {
556
+ helper.log(node.id + " -> " + link);
557
+ helper.log({ source: node.id, state: state });
558
+ RED.events.emit("node:" + link, { source: node.id, state: state });
559
+ });
560
+ };
561
+
562
+ // For security reason, stop shutter at node start
563
+ wait_timeout = setTimeout(() =>
564
+ {
565
+ wait_timeout = null;
566
+
567
+ sendToOutput(false, false);
568
+ notifyCentral(false);
569
+
570
+ if (node_settings.alarm_active)
571
+ handleAlarmOn();
572
+
573
+ setStatus();
574
+ }, 10000);
575
+ }
576
+
577
+ RED.nodes.registerType("smart_shutter-complex-control", ShutterComplexControlNode);
578
+ };
package/smart_helper.js CHANGED
@@ -5,16 +5,16 @@ module.exports = {
5
5
  * If a type is unknown the NodeRed function is used for conversation.
6
6
  *
7
7
  * Known types are:
8
- * - "null", which will return always null
8
+ * - "NOTHING", which will return always null
9
9
  */
10
10
  evaluateNodeProperty(RED, value, type)
11
11
  {
12
12
  try
13
13
  {
14
- switch (type)
14
+ switch (type?.toUpperCase())
15
15
  {
16
16
  case null:
17
- case "null":
17
+ case "NOTHING":
18
18
  return null;
19
19
 
20
20
  default:
@@ -23,10 +23,7 @@ module.exports = {
23
23
  }
24
24
  catch (error)
25
25
  {
26
- console.error(value);
27
- console.error(type);
28
- console.error(error);
29
- console.error(msg);
26
+ console.error({ value, type, error });
30
27
  return null;
31
28
  }
32
29
  },
@@ -72,6 +69,23 @@ module.exports = {
72
69
  return result;
73
70
  },
74
71
 
72
+ /**
73
+ * This functions extracts the topic name out of the given topic and checks if it is a valid topic.
74
+ * If not, the default topic is returned.
75
+ * @param {string} topic
76
+ * @param {string} defaultTopic
77
+ * @param {string[]} possibleTopics
78
+ * @returns The real topic that needs to be processes
79
+ */
80
+ getRealTopic(topic, defaultTopic, possibleTopics)
81
+ {
82
+ let realTopic = this.getTopicName(topic);
83
+ if (possibleTopics.includes(realTopic))
84
+ return realTopic;
85
+
86
+ return defaultTopic;
87
+ },
88
+
75
89
  /**
76
90
  * This function converts the given value with the given unit into milli seconds.
77
91
  *
@@ -166,6 +180,26 @@ module.exports = {
166
180
  return this.getTimeInMs(values[1].replace(",", "."), values[2] || "ms");
167
181
  },
168
182
 
183
+ /**
184
+ * Formats a string by replacing {n} with the n-th argument.
185
+ * Example: format("Hello {0}. {1} {0}.", "world", "Bye") => "Hello world. Bye world."
186
+ * @param {string} str The string to format
187
+ * @param {...string} args The arguments used for the replacement
188
+ * @returns The formatted string
189
+ */
190
+ format(str, ...args)
191
+ {
192
+ if (!args.length)
193
+ return str;
194
+
195
+ for (var a in args)
196
+ {
197
+ str = str.replace(new RegExp("\\{" + a + "\\}", "gi"), args[a]);
198
+ }
199
+
200
+ return str
201
+ },
202
+
169
203
  /**
170
204
  * This function returns a string that represents the remaining time given in time_ms.
171
205
  * If a timeConcatWord it set, it returns also this word including the target time.
@@ -302,8 +336,17 @@ module.exports = {
302
336
  * @param {*} obj The Object to copy
303
337
  * @returns A new Object
304
338
  */
305
- cloneObject(obj)
339
+ cloneObject(...obj)
340
+ {
341
+ return Object.assign({}, ...obj);
342
+ },
343
+
344
+ /**
345
+ * Forward all arguments to the console if it is enabled
346
+ */
347
+ log()
306
348
  {
307
- return Object.assign({}, obj);
349
+ // uncomment to see all log values
350
+ // console.log(...arguments);
308
351
  }
309
352
  };
@@ -0,0 +1,10 @@
1
+ <script type="text/html" data-help-name="smart_statistic">
2
+ <p>Dieser Knoten berechnet das Minimum, Maximum, die Summe, die Differenz, den absoluten Wert, die absolute Differenz, den Durchschnitt sowie den gleitenden Mittelwert verschiedener Werte.</p>
3
+ <p>Jeder Wert muss mit einem eigenen Topic gesendet werden. Kommt ein zweiter Wert mit dem gleichen Topic, wird der entsprechende Wert überschrieben.</p>
4
+ <p>Im Falle von MIN, MAX und ABS wird das entsprechende Topic, welches mit den Werten kam, mit ausgegeben. Bei SUM, DIFF, AVG und MOV_AVG handelt es sich um kombinierte Ergebnisse, weshalb Topic hier immer nicht gesetzt ist.</p>
5
+ <p>Für den absoluten Wert sowie den gleitenden Mittelwert spielt das Topic keine Rolle, jeder Wert der empfangen wird, wird für die Berechnung verwendet.</p>
6
+ <p>
7
+ <b>Hinweis:</b> Smart Nodes verwenden Topics im Format <code>name#nummer</code>, damit können verschiedene Smart Nodes mit dem gleichen Topic angesteuert werden.<br/>
8
+ Diese Node verwendet nur den Teil <code>nummer</code>. <code>name</code> und <code>#</code> sind dabei optional.
9
+ </p>
10
+ </script>