smart-nodes 0.1.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 (62) hide show
  1. package/LICENSE +21 -0
  2. package/LICENSE.md +21 -0
  3. package/README.md +127 -0
  4. package/central/central.html +328 -0
  5. package/central/central.js +95 -0
  6. package/compare/compare.html +137 -0
  7. package/compare/compare.js +151 -0
  8. package/delay/delay.html +192 -0
  9. package/delay/delay.js +175 -0
  10. package/examples/central.json +804 -0
  11. package/examples/central.png +0 -0
  12. package/examples/compare.json +916 -0
  13. package/examples/compare.png +0 -0
  14. package/examples/delay.json +198 -0
  15. package/examples/delay.png +0 -0
  16. package/examples/forwarder.json +152 -0
  17. package/examples/forwarder.png +0 -0
  18. package/examples/hysteresis.json +358 -0
  19. package/examples/hysteresis.png +0 -0
  20. package/examples/light-control.json +499 -0
  21. package/examples/light-control.png +0 -0
  22. package/examples/logic.json +562 -0
  23. package/examples/logic.png +0 -0
  24. package/examples/long-press-control.json +113 -0
  25. package/examples/long-press-control.png +0 -0
  26. package/examples/multi-press-control.json +136 -0
  27. package/examples/multi-press-control.png +0 -0
  28. package/examples/scene-control.json +535 -0
  29. package/examples/scene-control.png +0 -0
  30. package/examples/scheduler.json +164 -0
  31. package/examples/scheduler.png +0 -0
  32. package/examples/shutter-complex-control.json +489 -0
  33. package/examples/shutter-complex-control.png +0 -0
  34. package/examples/shutter-control.json +457 -0
  35. package/examples/shutter-control.png +0 -0
  36. package/examples/statistic.json +1112 -0
  37. package/examples/statistic.png +0 -0
  38. package/forwarder/forwarder.html +100 -0
  39. package/forwarder/forwarder.js +95 -0
  40. package/hysteresis/hysteresis.html +152 -0
  41. package/hysteresis/hysteresis.js +146 -0
  42. package/light-control/light-control.html +358 -0
  43. package/light-control/light-control.js +231 -0
  44. package/logic/logic.html +168 -0
  45. package/logic/logic.js +171 -0
  46. package/long-press-control/long-press-control.html +74 -0
  47. package/long-press-control/long-press-control.js +75 -0
  48. package/multi-press-control/multi-press-control.html +135 -0
  49. package/multi-press-control/multi-press-control.js +68 -0
  50. package/package.json +59 -0
  51. package/persistence.js +74 -0
  52. package/scene-control/scene-control.html +575 -0
  53. package/scene-control/scene-control.js +265 -0
  54. package/scheduler/scheduler.html +338 -0
  55. package/scheduler/scheduler.js +209 -0
  56. package/shutter-complex-control/shutter-complex-control.html +330 -0
  57. package/shutter-complex-control/shutter-complex-control.js +399 -0
  58. package/shutter-control/shutter-control.html +283 -0
  59. package/shutter-control/shutter-control.js +208 -0
  60. package/smart_helper.js +156 -0
  61. package/statistic/statistic.html +107 -0
  62. package/statistic/statistic.js +196 -0
@@ -0,0 +1,137 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType("smart_compare", {
3
+ category: "Smart Nodes",
4
+ paletteLabel: "Compare",
5
+ color: "#E2D96E",
6
+ defaults: {
7
+ name: { value: "" },
8
+ comparator: { value: "EQUAL" }, // EQUAL, GREATER, GREATER_EQUAL, SMALLER, SMALLER_EQUAL, UNEQUAL
9
+ value1: { value: "" },
10
+ value1_type: { value: "null" },
11
+ value2: { value: "" },
12
+ value2_type: { value: "null" },
13
+ save_state: { value: true },
14
+ resend_on_start: { value: true }
15
+ },
16
+ inputs: 1,
17
+ outputs: 1,
18
+ icon: "font-awesome/fa-sort",
19
+ label: function ()
20
+ {
21
+ return this.name || this.comparator || "Compare";
22
+ },
23
+ oneditprepare: function ()
24
+ {
25
+ $("#node-input-comparator").typedInput({
26
+ types: [
27
+ {
28
+ default: "EQUAL",
29
+ options: [
30
+ { value: "SMALLER", label: "< Kleiner" },
31
+ { value: "SMALLER_EQUAL", label: "<= Kleiner oder gleich" },
32
+ { value: "EQUAL", label: "== Gleich" },
33
+ { value: "UNEQUAL", label: "!= Ungleich" },
34
+ { value: "GREATER_EQUAL", label: ">= Größer oder gleich" },
35
+ { value: "GREATER", label: "> Größer" }
36
+ ],
37
+ },
38
+ ],
39
+ });
40
+
41
+ $("#node-input-value1").typedInput({
42
+ type: "num",
43
+ types: ["str", "num", "bool", {
44
+ value: "null",
45
+ label: "Kein Wert",
46
+ icon: "fa fa-times",
47
+ hasValue: false,
48
+ }],
49
+ typeField: "#node-input-value1_type"
50
+ });
51
+
52
+ $("#node-input-value2").typedInput({
53
+ type: "num",
54
+ types: ["str", "num", "bool", {
55
+ value: "null",
56
+ label: "Kein Wert",
57
+ icon: "fa fa-times",
58
+ hasValue: false,
59
+ }],
60
+ typeField: "#node-input-value2_type"
61
+ });
62
+
63
+ $("#node-input-save_state").on("change", ev =>
64
+ {
65
+ if (ev.target.checked)
66
+ $("#resend_on_start_row").show();
67
+ else
68
+ $("#resend_on_start_row").hide();
69
+ });
70
+ $("#node-input-save_state").trigger("change");
71
+ },
72
+ });
73
+ </script>
74
+
75
+ <script type="text/html" data-template-name="smart_compare">
76
+ <div class="form-row">
77
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
78
+ <input type="text" id="node-input-name" placeholder="Name" />
79
+ </div>
80
+ <div class="form-row">
81
+ <label for="node-input-comparator"><i class="fa fa-sort"></i> Vergleich</label>
82
+ <input id="node-input-comparator" />
83
+ </div>
84
+ <div class="form-row">
85
+ <div>Defaultwerte werden hier gesetzt und können durch die einkommenden Nachrichten überschrieben werden.</div>
86
+ </div>
87
+ <div class="form-row">
88
+ <label for="node-input-value1"><i class="fa fa-hashtag"></i> Wert 1</label>
89
+ <input type="text" id="node-input-value1">
90
+ <input type="hidden" id="node-input-value1_type">
91
+ </div>
92
+ <div class="form-row">
93
+ <label for="node-input-value2"><i class="fa fa-hashtag"></i> Wert 2</label>
94
+ <input type="text" id="node-input-value2"/>
95
+ <input type="hidden" id="node-input-value2_type">
96
+ </div>
97
+ <hr/>
98
+ <h4 style="margin: 0.5rem 0;">Systemstart</h4>
99
+ <div class="form-row">
100
+ <input type="checkbox" id="node-input-save_state" style="width: 20px;" />
101
+ <label for="node-input-save_state" style="width: calc(100% - 30px);">Zustand speichern</label>
102
+ </div>
103
+ <div class="form-row" id="resend_on_start_row">
104
+ <input type="checkbox" id="node-input-resend_on_start" style="width: 20px;" />
105
+ <label for="node-input-resend_on_start" style="width: calc(100% - 30px);">Letze Nachricht 10 Sekunden nach dem Start senden</label>
106
+ </div>
107
+ </script>
108
+
109
+ <script type="text/html" data-help-name="smart_compare">
110
+ <p>Diese Node führt verschiedene Vergleiche aus. Dabei können nicht nur Zahlen, sondern auch Texte verglichen werden.</p>
111
+ <p>
112
+ <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/>
113
+ Diese Node verwendet nur den Teil <code>nummer</code>. <code>name</code> und <code>#</code> sind dabei optional.
114
+ </p>
115
+ <p>
116
+ Folgende topics werden akzeptiert:
117
+ <table>
118
+ <thead>
119
+ <tr>
120
+ <th>Topic</th>
121
+ <th>Beschreibung</th>
122
+ </tr>
123
+ </thead>
124
+ <tbody>
125
+ <tr>
126
+ <td><code>1</code></td>
127
+ <td>Setzt den linken zu vergleichenden Wert</td>
128
+ </tr>
129
+ <tr>
130
+ <td><code>2</code></td>
131
+ <td>Setzt den rechten zu vergleichenden Wert</td>
132
+ </tr>
133
+ </tbody>
134
+ </table>
135
+ </p>
136
+ <p>Texte werden anhand ihres Ascii-Codes verglichen.<code>"c" &lt; "b"</code> ergibt <code>false</code><code>"C" &lt; "b"</code> ergibt <code>true</code>.</p>
137
+ </script>
@@ -0,0 +1,151 @@
1
+ module.exports = function (RED)
2
+ {
3
+ function CompareNode(config)
4
+ {
5
+ const node = this;
6
+ RED.nodes.createNode(node, config);
7
+
8
+ const smartContext = require("../persistence.js")(RED);
9
+ const helper = require("../smart_helper.js");
10
+
11
+ var nodeSettings = {
12
+ values: [
13
+ helper.evaluateNodeProperty(RED, config.value1, config.value1_type),
14
+ helper.evaluateNodeProperty(RED, config.value2, config.value2_type)
15
+ ],
16
+ lastResult: null,
17
+ };
18
+
19
+ if (config.save_state)
20
+ {
21
+ // load old saved values
22
+ nodeSettings = Object.assign(nodeSettings, smartContext.get(node.id));
23
+ }
24
+ else
25
+ {
26
+ // delete old saved values
27
+ smartContext.del(node.id);
28
+ }
29
+
30
+ // dynamic config
31
+ let comparator = config.comparator;
32
+
33
+ // runtime values
34
+
35
+ node.on("input", function (msg)
36
+ {
37
+ // Check if topic has a valid value
38
+ let input = helper.getTopicNumber(msg.topic);
39
+ if (input == null || input < 1 || input > 2)
40
+ {
41
+ node.error("Topic has to be 1 or 2, send: " + msg.topic);
42
+ return;
43
+ }
44
+
45
+ // check if payload has a valid value
46
+ let num = parseFloat(msg.payload);
47
+ if (Number.isNaN(num))
48
+ {
49
+ node.error("Payload has to be numeric");
50
+ return;
51
+ }
52
+
53
+ // Save new value
54
+ nodeSettings.values[input - 1] = num;
55
+
56
+ let result = getResult();
57
+ setStatus(result);
58
+
59
+ if (result != null)
60
+ {
61
+ nodeSettings.lastResult = result;
62
+
63
+ if (config.save_state)
64
+ smartContext.set(node.id, nodeSettings);
65
+
66
+ node.send({ payload: result, comparator: comparator });
67
+ }
68
+ });
69
+
70
+ let getResult = () =>
71
+ {
72
+ // Wait until both values are set
73
+ if (nodeSettings.values[0] == null || nodeSettings.values[1] == null)
74
+ return null;
75
+
76
+ switch (comparator)
77
+ {
78
+ case "SMALLER":
79
+ return nodeSettings.values[0] < nodeSettings.values[1];
80
+
81
+ case "SMALLER_EQUAL":
82
+ return nodeSettings.values[0] <= nodeSettings.values[1];
83
+
84
+ case "EQUAL":
85
+ return nodeSettings.values[0] === nodeSettings.values[1];
86
+
87
+ case "UNEQUAL":
88
+ return nodeSettings.values[0] !== nodeSettings.values[1];
89
+
90
+ case "GREATER_EQUAL":
91
+ return nodeSettings.values[0] >= nodeSettings.values[1];
92
+
93
+ case "GREATER":
94
+ return nodeSettings.values[0] > nodeSettings.values[1];
95
+ }
96
+
97
+ return null;
98
+ }
99
+
100
+ // This is only used for status message
101
+ let getComparatorSign = () =>
102
+ {
103
+ switch (comparator)
104
+ {
105
+ case "SMALLER":
106
+ return "<";
107
+
108
+ case "SMALLER_EQUAL":
109
+ return "<=";
110
+
111
+ case "EQUAL":
112
+ return "==";
113
+
114
+ case "UNEQUAL":
115
+ return "!=";
116
+
117
+ case "GREATER_EQUAL":
118
+ return ">=";
119
+
120
+ case "GREATER":
121
+ return ">";
122
+ }
123
+ return "???";
124
+ }
125
+
126
+ // updates the status
127
+ let setStatus = (result) =>
128
+ {
129
+ if (result == null)
130
+ node.status({});
131
+ else
132
+ node.status({ fill: "yellow", shape: "ring", text: nodeSettings.values.join(" " + getComparatorSign() + " ") + " => " + result });
133
+ }
134
+
135
+ node.on("close", function ()
136
+ {
137
+ });
138
+
139
+ if (config.save_state && config.resend_on_start && nodeSettings.lastResult != null)
140
+ {
141
+ setTimeout(() =>
142
+ {
143
+ node.send({ payload: nodeSettings.lastResult, comparator: comparator });
144
+ }, 10000);
145
+ }
146
+
147
+ setStatus(nodeSettings.lastResult);
148
+ }
149
+
150
+ RED.nodes.registerType("smart_compare", CompareNode);
151
+ }
@@ -0,0 +1,192 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType("smart_delay", {
3
+ category: "Smart Nodes",
4
+ paletteLabel: "delay",
5
+ color: "#6EE2D9",
6
+ defaults: {
7
+ name: { value: "" },
8
+ on_delay: { value: 10 },
9
+ on_delay_unit: { value: "s" },
10
+ off_delay: { value: 10 },
11
+ off_delay_unit: { value: "s" },
12
+ delay_only_on_change: { value: true },
13
+ save_state: { value: true },
14
+ resend_on_start: { value: true }
15
+ },
16
+ inputs: 1,
17
+ outputs: 1,
18
+ icon: "timer.png",
19
+ label: function ()
20
+ {
21
+ return this.name || "Delay";
22
+ },
23
+ oneditprepare: function ()
24
+ {
25
+ $("#node-input-on_delay").spinner({
26
+ min: 0,
27
+ change: function (event, ui)
28
+ {
29
+ var value = parseInt(this.value);
30
+ value = isNaN(value) ? 0 : value;
31
+ value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
32
+ // value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
33
+ if (value !== this.value)
34
+ $(this).spinner("value", value);
35
+ }
36
+ });
37
+
38
+ $("#node-input-on_delay_unit").css("max-width", "10rem").typedInput({
39
+ types: [
40
+ {
41
+ default: "s",
42
+ options: [
43
+ { value: "ms", label: "Millisekunden" },
44
+ { value: "s", label: "Sekunden" },
45
+ { value: "min", label: "Minuten" },
46
+ { value: "h", label: "Stunden" },
47
+ ]
48
+ }
49
+ ]
50
+ });
51
+
52
+ $("#node-input-off_delay").spinner({
53
+ min: 0,
54
+ change: function (event, ui)
55
+ {
56
+ var value = parseInt(this.value);
57
+ value = isNaN(value) ? 0 : value;
58
+ value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
59
+ // value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
60
+ if (value !== this.value)
61
+ $(this).spinner("value", value);
62
+ }
63
+ });
64
+
65
+ $("#node-input-off_delay_unit").css("max-width", "10rem").typedInput({
66
+ types: [
67
+ {
68
+ default: "s",
69
+ options: [
70
+ { value: "ms", label: "Millisekunden" },
71
+ { value: "s", label: "Sekunden" },
72
+ { value: "min", label: "Minuten" },
73
+ { value: "h", label: "Stunden" },
74
+ ]
75
+ }
76
+ ]
77
+ });
78
+
79
+ $("#node-input-save_state").on("change", ev =>
80
+ {
81
+ if (ev.target.checked)
82
+ $("#resend_on_start_row").show();
83
+ else
84
+ $("#resend_on_start_row").hide();
85
+ });
86
+ $("#node-input-save_state").trigger("change");
87
+ },
88
+ });
89
+ </script>
90
+
91
+ <script type="text/html" data-template-name="smart_delay">
92
+ <div class="form-row">
93
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
94
+ <input type="text" id="node-input-name" placeholder="Name" />
95
+ </div>
96
+ <div class="form-row">
97
+ <label for="node-input-on_delay"><i class="fa fa-clock-o"></i> Einschalt- verzögerung</label>
98
+ <input id="node-input-on_delay" value="0" />
99
+ <input id="node-input-on_delay_unit" />
100
+ </div>
101
+ <div class="form-row">
102
+ <label for="node-input-off_delay"><i class="fa fa-clock-o"></i> Ausschalt- verzögerung</label>
103
+ <input id="node-input-off_delay" value="0" />
104
+ <input id="node-input-off_delay_unit" />
105
+ </div>
106
+ <div class="form-row">
107
+ <input id="node-input-delay_only_on_change" type="checkbox" style="display: inline-block; width: auto; vertical-align: top;"/>
108
+ <span>Nur verzögern, wenn sich <code>msg.payload</code> verändert hat.</span>
109
+ </div>
110
+ <hr/>
111
+ <h4 style="margin: 0.5rem 0;">Systemstart</h4>
112
+ <div class="form-row">
113
+ <input type="checkbox" id="node-input-save_state" style="width: 20px;" />
114
+ <label for="node-input-save_state" style="width: calc(100% - 30px);">Zustand speichern</label>
115
+ </div>
116
+ <div class="form-row" id="resend_on_start_row">
117
+ <input type="checkbox" id="node-input-resend_on_start" style="width: 20px;" />
118
+ <label for="node-input-resend_on_start" style="width: calc(100% - 30px);">Letze Nachricht 10 Sekunden nach dem Start senden</label>
119
+ </div>
120
+ </script>
121
+
122
+ <script type="text/html" data-help-name="smart_delay">
123
+ <p>Diese Node kann dazu verwendet werden ein Ein- oder Ausschaltsignal zu verzögern.</p>
124
+ <p>Die Zeit 0 bedeutet keine Verögerung.</p>
125
+ <p>
126
+ <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/>
127
+ Diese Node verwendet nur den Teil <code>name</code>. <code>#</code> und <code>nummer</code> sind dabei optional.
128
+ </p>
129
+ <p>
130
+ Folgende topics werden akzeptiert:
131
+ <table>
132
+ <thead>
133
+ <tr>
134
+ <th>Topic</th>
135
+ <th>Beschreibung</th>
136
+ </tr>
137
+ </thead>
138
+ <tbody>
139
+ <tr>
140
+ <td><code>set_delay_on</code></td>
141
+ <td>Setzt die Einschaltverzögerung auf die in <code>msg.payload</code> angegebene Zeit.</td>
142
+ </tr>
143
+ <tr>
144
+ <td><code>set_delay_off</code></td>
145
+ <td>Setzt die Ausschaltverzögerung auf die in <code>msg.payload</code> angegebene Zeit.</td>
146
+ </tr>
147
+ <tr>
148
+ <td><code>set_delays</code></td>
149
+ <td>Setzt die Ein- und Ausschaltverzögerung auf die in <code>msg.payload</code> angegebene Zeit.</td>
150
+ </tr>
151
+ </tbody>
152
+ </table>
153
+ </p>
154
+ <p>
155
+ Beispiel: <code>msg = { "topic": "set_delay_on", "payload": 5000 }</code> oder <code>msg = { "topic": "set_delay_on", "payload": "5s" }</code><br/>
156
+ Diese Nachricht setzt die Einschaltverzögerung auf 5000 Millisekunden / 5 Sekunden.
157
+ Ist die Zeit auf 0 eingestellt, wird die Nachricht sofort weitergeleitet.<br/>
158
+ Als Einheit für die Zeit können folgende Werte verwendet werden:
159
+ <table>
160
+ <thead>
161
+ <tr>
162
+ <th>Einheit</th>
163
+ <th>Beschreibung</th>
164
+ </tr>
165
+ </thead>
166
+ <tbody>
167
+ <tr>
168
+ <td><code>ms</code> (default)</td>
169
+ <td>Millisekunden</td>
170
+ </tr>
171
+ <tr>
172
+ <td><code>s</code> oder <code>sec</code></td>
173
+ <td>Sekunden</td>
174
+ </tr>
175
+ <tr>
176
+ <td><code>m</code> oder <code>min</code></td>
177
+ <td>Mintun.</td>
178
+ </tr>
179
+ <tr>
180
+ <td><code>h</code></td>
181
+ <td>Stunden</td>
182
+ </tr>
183
+ </tbody>
184
+ </table>
185
+ </p>
186
+ <p>
187
+ Wenn die Checkbox <b>Nur verzögern, wenn sich <code>msg.payload</code> verändert hat.</b> aktiviert ist
188
+ und während dem Warten um ein Einschaltsignal weiterzuleiten ein Ausschaltsignal empfangen wird,
189
+ so wird das Senden des Einschaltsignals abgebrochen. Dies gilt auch für den umgekehrten Fall.<br/>
190
+ Ist die Checkbox nicht aktiviert, wird jede Nachricht verzögert, sofern nicht bereits eine Verzögerung für den gleichen <code>payload</code> aktiv ist.
191
+ </p>
192
+ </script>
package/delay/delay.js ADDED
@@ -0,0 +1,175 @@
1
+ module.exports = function (RED)
2
+ {
3
+ function DelayNode(config)
4
+ {
5
+ const node = this;
6
+ RED.nodes.createNode(node, config);
7
+
8
+ const smartContext = require("../persistence.js")(RED);
9
+ const helper = require("../smart_helper.js");
10
+
11
+ var nodeSettings = {
12
+ on_delay_ms: helper.getTimeInMs(config.on_delay, config.on_delay_unit),
13
+ off_delay_ms: helper.getTimeInMs(config.off_delay, config.off_delay_unit),
14
+ lastMessage: null,
15
+ };
16
+
17
+ if (config.save_state)
18
+ {
19
+ // load old saved values
20
+ nodeSettings = Object.assign(nodeSettings, smartContext.get(node.id));
21
+
22
+ switch (nodeSettings.lastMessage?.payload)
23
+ {
24
+ case true:
25
+ node.status({ fill: "yellow", shape: "ring", text: "Load last state: On" });
26
+ break;
27
+
28
+ case false:
29
+ node.status({ fill: "yellow", shape: "ring", text: "Load last state: Off" });
30
+ break;
31
+
32
+ default:
33
+ node.status({ fill: "yellow", shape: "ring", text: "No last state available" });
34
+ break;
35
+ }
36
+ }
37
+ else
38
+ {
39
+ // delete old saved values
40
+ node.status({});
41
+ smartContext.del(node.id);
42
+ }
43
+
44
+ // dynamic config
45
+ let delay_only_on_change = config.delay_only_on_change;
46
+
47
+ // runtime values
48
+ let timeout = null;
49
+ let next_payload = null;
50
+
51
+ node.on("input", function (msg)
52
+ {
53
+ switch (msg.topic)
54
+ {
55
+ case "set_delay_on":
56
+ nodeSettings.on_delay_ms = helper.getTimeInMsFromString(msg.payload);
57
+ node.status({ fill: "yellow", shape: "ring", text: "New on delay: " + helper.formatMsToStatus(nodeSettings.on_delay_ms) });
58
+
59
+ if (config.save_state)
60
+ smartContext.set(node.id, nodeSettings);
61
+ break;
62
+
63
+ case "set_delay_off":
64
+ nodeSettings.off_delay_ms = helper.getTimeInMsFromString(msg.payload);
65
+ node.status({ fill: "yellow", shape: "ring", text: "New off delay: " + helper.formatMsToStatus(nodeSettings.on_delay_ms) });
66
+
67
+ if (config.save_state)
68
+ smartContext.set(node.id, nodeSettings);
69
+ break;
70
+
71
+ case "set_delays":
72
+ nodeSettings.on_delay_ms = helper.getTimeInMsFromString(msg.payload);
73
+ nodeSettings.off_delay_ms = nodeSettings.on_delay_ms;
74
+ node.status({ fill: "yellow", shape: "ring", text: "New delays: " + helper.formatMsToStatus(nodeSettings.on_delay_ms) });
75
+
76
+ if (config.save_state)
77
+ smartContext.set(node.id, nodeSettings);
78
+ break;
79
+
80
+ default:
81
+ send(msg);
82
+ break;
83
+ }
84
+ });
85
+
86
+ node.on("close", function ()
87
+ {
88
+ if (timeout != null)
89
+ {
90
+ clearTimeout(timeout);
91
+ timeout = null;
92
+ }
93
+ });
94
+
95
+ let send = (msg) =>
96
+ {
97
+ let delayMs = 0;
98
+
99
+ if (msg.payload)
100
+ delayMs = nodeSettings.on_delay_ms;
101
+ else
102
+ delayMs = nodeSettings.off_delay_ms;
103
+
104
+ if (delay_only_on_change)
105
+ {
106
+ if (timeout)
107
+ {
108
+ // current delay runs already for the same payload, so don't start a new one.
109
+ if (next_payload == msg.payload)
110
+ return;
111
+
112
+ // payload changed back to last value => stop current delay
113
+ if (nodeSettings.lastMessage?.payload == msg.payload)
114
+ {
115
+ node.status({});
116
+ clearTimeout(timeout);
117
+ timeout = null;
118
+
119
+ return;
120
+ }
121
+ }
122
+ // payload hasn't changed
123
+ else if (nodeSettings.lastMessage?.payload == msg.payload)
124
+ {
125
+ return;
126
+ }
127
+ }
128
+
129
+ // stop timeout if any
130
+ if (timeout != null)
131
+ {
132
+ node.status({});
133
+ clearTimeout(timeout);
134
+ timeout = null;
135
+ }
136
+
137
+ // No delay if 0 or smaller
138
+ if (delayMs <= 0)
139
+ {
140
+ node.status({});
141
+ nodeSettings.lastMessage = msg
142
+
143
+ if (config.save_state)
144
+ smartContext.set(node.id, nodeSettings);
145
+
146
+ node.send(msg);
147
+ return;
148
+ }
149
+
150
+ // start new timeout
151
+ node.status({ fill: "yellow", shape: "ring", text: "Forward msg.topic = '" + (msg.topic ?? "null") + "' msg.payload = '" + (msg.payload ?? "null") + "' in " + helper.formatMsToStatus(delayMs, "at") });
152
+ timeout = setTimeout(() =>
153
+ {
154
+ node.status({});
155
+ nodeSettings.lastMessage = msg
156
+
157
+ if (config.save_state)
158
+ smartContext.set(node.id, nodeSettings);
159
+
160
+ node.send(msg);
161
+ }, delayMs);
162
+ }
163
+
164
+ if (config.save_state && config.resend_on_start && nodeSettings.lastMessage != null)
165
+ {
166
+ setTimeout(() =>
167
+ {
168
+ node.status({});
169
+ node.send(nodeSettings.lastMessage);
170
+ }, 10000);
171
+ }
172
+ }
173
+
174
+ RED.nodes.registerType("smart_delay", DelayNode);
175
+ }