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.
- package/LICENSE +21 -0
- package/LICENSE.md +21 -0
- package/README.md +127 -0
- package/central/central.html +328 -0
- package/central/central.js +95 -0
- package/compare/compare.html +137 -0
- package/compare/compare.js +151 -0
- package/delay/delay.html +192 -0
- package/delay/delay.js +175 -0
- package/examples/central.json +804 -0
- package/examples/central.png +0 -0
- package/examples/compare.json +916 -0
- package/examples/compare.png +0 -0
- package/examples/delay.json +198 -0
- package/examples/delay.png +0 -0
- package/examples/forwarder.json +152 -0
- package/examples/forwarder.png +0 -0
- package/examples/hysteresis.json +358 -0
- package/examples/hysteresis.png +0 -0
- package/examples/light-control.json +499 -0
- package/examples/light-control.png +0 -0
- package/examples/logic.json +562 -0
- package/examples/logic.png +0 -0
- package/examples/long-press-control.json +113 -0
- package/examples/long-press-control.png +0 -0
- package/examples/multi-press-control.json +136 -0
- package/examples/multi-press-control.png +0 -0
- package/examples/scene-control.json +535 -0
- package/examples/scene-control.png +0 -0
- package/examples/scheduler.json +164 -0
- package/examples/scheduler.png +0 -0
- package/examples/shutter-complex-control.json +489 -0
- package/examples/shutter-complex-control.png +0 -0
- package/examples/shutter-control.json +457 -0
- package/examples/shutter-control.png +0 -0
- package/examples/statistic.json +1112 -0
- package/examples/statistic.png +0 -0
- package/forwarder/forwarder.html +100 -0
- package/forwarder/forwarder.js +95 -0
- package/hysteresis/hysteresis.html +152 -0
- package/hysteresis/hysteresis.js +146 -0
- package/light-control/light-control.html +358 -0
- package/light-control/light-control.js +231 -0
- package/logic/logic.html +168 -0
- package/logic/logic.js +171 -0
- package/long-press-control/long-press-control.html +74 -0
- package/long-press-control/long-press-control.js +75 -0
- package/multi-press-control/multi-press-control.html +135 -0
- package/multi-press-control/multi-press-control.js +68 -0
- package/package.json +59 -0
- package/persistence.js +74 -0
- package/scene-control/scene-control.html +575 -0
- package/scene-control/scene-control.js +265 -0
- package/scheduler/scheduler.html +338 -0
- package/scheduler/scheduler.js +209 -0
- package/shutter-complex-control/shutter-complex-control.html +330 -0
- package/shutter-complex-control/shutter-complex-control.js +399 -0
- package/shutter-control/shutter-control.html +283 -0
- package/shutter-control/shutter-control.js +208 -0
- package/smart_helper.js +156 -0
- package/statistic/statistic.html +107 -0
- package/statistic/statistic.js +196 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
(function ()
|
|
3
|
+
{
|
|
4
|
+
let treeList;
|
|
5
|
+
let candidateNodesCount = 0;
|
|
6
|
+
let flows = [];
|
|
7
|
+
let flowMap = {};
|
|
8
|
+
|
|
9
|
+
function onEditPrepare(node, targetTypes)
|
|
10
|
+
{
|
|
11
|
+
if (!node.links)
|
|
12
|
+
node.links = [];
|
|
13
|
+
|
|
14
|
+
const activeSubflow = RED.nodes.subflow(node.z);
|
|
15
|
+
|
|
16
|
+
treeList = $("<div>")
|
|
17
|
+
.css({ width: "100%", height: "100%" })
|
|
18
|
+
.appendTo(".node-input-link-row")
|
|
19
|
+
.treeList({ autoSelect: false })
|
|
20
|
+
.on("treelistitemmouseover", function (e, item)
|
|
21
|
+
{
|
|
22
|
+
if (item.node)
|
|
23
|
+
{
|
|
24
|
+
item.node.highlighted = true;
|
|
25
|
+
item.node.dirty = true;
|
|
26
|
+
RED.view.redraw();
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
.on("treelistitemmouseout", function (e, item)
|
|
30
|
+
{
|
|
31
|
+
if (item.node)
|
|
32
|
+
{
|
|
33
|
+
item.node.highlighted = false;
|
|
34
|
+
item.node.dirty = true;
|
|
35
|
+
RED.view.redraw();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
flows = [];
|
|
40
|
+
flowMap = {};
|
|
41
|
+
|
|
42
|
+
if (activeSubflow)
|
|
43
|
+
{
|
|
44
|
+
flowMap[activeSubflow.id] = {
|
|
45
|
+
id: activeSubflow.id,
|
|
46
|
+
class: "red-ui-palette-header",
|
|
47
|
+
label: "Subflow : " + (activeSubflow.name || activeSubflow.id),
|
|
48
|
+
expanded: true,
|
|
49
|
+
children: []
|
|
50
|
+
};
|
|
51
|
+
flows.push(flowMap[activeSubflow.id]);
|
|
52
|
+
}
|
|
53
|
+
else
|
|
54
|
+
{
|
|
55
|
+
RED.nodes.eachWorkspace(function (ws)
|
|
56
|
+
{
|
|
57
|
+
if (!ws.disabled)
|
|
58
|
+
{
|
|
59
|
+
flowMap[ws.id] = {
|
|
60
|
+
id: ws.id,
|
|
61
|
+
class: "red-ui-palette-header",
|
|
62
|
+
label: (ws.label || ws.id) + (node.z === ws.id ? " *" : ""),
|
|
63
|
+
expanded: true,
|
|
64
|
+
children: []
|
|
65
|
+
};
|
|
66
|
+
flows.push(flowMap[ws.id]);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
setTimeout(function ()
|
|
72
|
+
{
|
|
73
|
+
treeList.treeList("show", node.z);
|
|
74
|
+
}, 100);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function initTreeList(node, targetTypes)
|
|
78
|
+
{
|
|
79
|
+
candidateNodesCount = 0;
|
|
80
|
+
for (const key in flowMap)
|
|
81
|
+
{
|
|
82
|
+
flowMap[key].children = [];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let candidateNodes = [];
|
|
86
|
+
|
|
87
|
+
targetTypes.forEach(function (targetType)
|
|
88
|
+
{
|
|
89
|
+
candidateNodes = candidateNodes.concat(RED.nodes.filterNodes({ type: targetType }));
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
candidateNodes.forEach(function (n)
|
|
93
|
+
{
|
|
94
|
+
if (flowMap[n.z])
|
|
95
|
+
{
|
|
96
|
+
const isChecked = (node.links.indexOf(n.id) !== -1) || (n.links || []).indexOf(node.id) !== -1;
|
|
97
|
+
if (isChecked)
|
|
98
|
+
{
|
|
99
|
+
flowMap[n.z].children.push({
|
|
100
|
+
id: n.id,
|
|
101
|
+
node: n,
|
|
102
|
+
label: n.name || n.id,
|
|
103
|
+
selected: false,
|
|
104
|
+
checkbox: false,
|
|
105
|
+
radio: false
|
|
106
|
+
});
|
|
107
|
+
candidateNodesCount++;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
for (const key in flowMap)
|
|
113
|
+
{
|
|
114
|
+
flowMap[key].children.sort((a, b) => a.label.localeCompare(b.label));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const flowsFiltered = flows.filter(function (f) { return f.children.length > 0 });
|
|
118
|
+
treeList.treeList("empty");
|
|
119
|
+
treeList.treeList("data", flowsFiltered);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function resizeNodeList()
|
|
123
|
+
{
|
|
124
|
+
var rows = $("#dialog-form>div:not(.node-input-link-row)");
|
|
125
|
+
var height = $("#dialog-form").height();
|
|
126
|
+
for (var i = 0; i < rows.length; i++)
|
|
127
|
+
{
|
|
128
|
+
height -= $(rows[i]).outerHeight(true);
|
|
129
|
+
}
|
|
130
|
+
var editorRow = $("#dialog-form>div.node-input-link-row");
|
|
131
|
+
height -= (parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom")));
|
|
132
|
+
$(".node-input-link-row").css("height", height + "px");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
RED.nodes.registerType("smart_light-control", {
|
|
136
|
+
category: "Smart Nodes",
|
|
137
|
+
paletteLabel: "Light control",
|
|
138
|
+
color: "#C882FF",
|
|
139
|
+
defaults: {
|
|
140
|
+
name: { value: "" },
|
|
141
|
+
icon: { value: "light" }, // light | outlet
|
|
142
|
+
max_time_on: { value: "0" },
|
|
143
|
+
max_time_on_unit: { value: "s" },
|
|
144
|
+
alarm_action: { value: 'NOTHING' }, // NOTHING | ON | OFF
|
|
145
|
+
links: { value: [], type: "smart_central-control[]" }
|
|
146
|
+
},
|
|
147
|
+
inputs: 1,
|
|
148
|
+
outputs: 1,
|
|
149
|
+
icon: function ()
|
|
150
|
+
{
|
|
151
|
+
if (this.icon == "outlet")
|
|
152
|
+
return "font-awesome/fa-plug";
|
|
153
|
+
return "font-awesome/fa-lightbulb-o";
|
|
154
|
+
},
|
|
155
|
+
label: function ()
|
|
156
|
+
{
|
|
157
|
+
return this.name || (this.icon == "outlet" ? "Outlet control" : "Light control");
|
|
158
|
+
},
|
|
159
|
+
oneditprepare: function ()
|
|
160
|
+
{
|
|
161
|
+
let node = this;
|
|
162
|
+
onEditPrepare(this, ["smart_central-control"]);
|
|
163
|
+
initTreeList(node, ["smart_central-control"]);
|
|
164
|
+
|
|
165
|
+
$("#node-input-icon")
|
|
166
|
+
.css("max-width", "10rem")
|
|
167
|
+
.typedInput({
|
|
168
|
+
types: [
|
|
169
|
+
{
|
|
170
|
+
default: "light",
|
|
171
|
+
options: [
|
|
172
|
+
{ value: "light", label: "Licht" },
|
|
173
|
+
{ value: "outlet", label: "Steckdose" }
|
|
174
|
+
],
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
$("#node-input-max_time_on")
|
|
180
|
+
.spinner({
|
|
181
|
+
min: 0,
|
|
182
|
+
change: function (event, ui)
|
|
183
|
+
{
|
|
184
|
+
var value = parseInt(this.value);
|
|
185
|
+
value = isNaN(value) ? 0 : value;
|
|
186
|
+
value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
|
|
187
|
+
// value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
|
|
188
|
+
if (value !== this.value) $(this).spinner("value", value);
|
|
189
|
+
},
|
|
190
|
+
}).css("max-width", "4rem");
|
|
191
|
+
|
|
192
|
+
$("#node-input-max_time_on_unit")
|
|
193
|
+
.css("max-width", "10rem")
|
|
194
|
+
.typedInput({
|
|
195
|
+
types: [
|
|
196
|
+
{
|
|
197
|
+
default: "s",
|
|
198
|
+
options: [
|
|
199
|
+
{ value: "ms", label: "Millisekunden" },
|
|
200
|
+
{ value: "s", label: "Sekunden" },
|
|
201
|
+
{ value: "min", label: "Minuten" },
|
|
202
|
+
{ value: "h", label: "Stunden" },
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
$("#node-input-alarm_action").typedInput({
|
|
209
|
+
types: [
|
|
210
|
+
{
|
|
211
|
+
default: "NOTHING",
|
|
212
|
+
options: [
|
|
213
|
+
{ value: "NOTHING", label: "Keine Aktion" },
|
|
214
|
+
{ value: "ON", label: "Einschalten" },
|
|
215
|
+
{ value: "OFF", label: "Ausschalten" }
|
|
216
|
+
],
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
onadd: function ()
|
|
222
|
+
{
|
|
223
|
+
this.links = [];
|
|
224
|
+
},
|
|
225
|
+
oneditresize: resizeNodeList
|
|
226
|
+
});
|
|
227
|
+
})();
|
|
228
|
+
</script>
|
|
229
|
+
|
|
230
|
+
<script type="text/html" data-template-name="smart_light-control">
|
|
231
|
+
<div class="form-row">
|
|
232
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
233
|
+
<input type="text" id="node-input-name" placeholder="Name" />
|
|
234
|
+
</div>
|
|
235
|
+
<div class="form-row">
|
|
236
|
+
<label for="node-input-icon"><i class="fa fa-picture-o"></i> Icon</label>
|
|
237
|
+
<input id="node-input-icon" />
|
|
238
|
+
</div>
|
|
239
|
+
<div class="form-row">
|
|
240
|
+
<label for="node-input-max_time_on"><i class="fa fa-clock-o"></i> Zeit Ein</label>
|
|
241
|
+
<input id="node-input-max_time_on" value="0" />
|
|
242
|
+
<input id="node-input-max_time_on_unit" />
|
|
243
|
+
</div>
|
|
244
|
+
<div class="form-row">
|
|
245
|
+
<label for="node-input-alarm_action"><i class="fa fa-exclamation-triangle"></i> Alarm Aktion</label>
|
|
246
|
+
<input id="node-input-alarm_action"/>
|
|
247
|
+
</div>
|
|
248
|
+
<span><i class="fa fa-link"></i> Dieser Baustein wird von folgenden Zentralbausteinen gesteuert:</span>
|
|
249
|
+
<div class="form-row node-input-link-row node-input-link-rows"></div>
|
|
250
|
+
</script>
|
|
251
|
+
|
|
252
|
+
<script type="text/html" data-help-name="smart_light-control">
|
|
253
|
+
<p>
|
|
254
|
+
Diese Node steuert einen Ausgang. Dies kann ein Licht, eine Steckdose oder ähnliches sein.
|
|
255
|
+
Als Ausgang wird immer <code>msg.payload = true</code> oder <code>msg.payload = false</code> gesendet um den Ausgang ein-, bzw. auszuschalten.
|
|
256
|
+
</p>
|
|
257
|
+
<p>
|
|
258
|
+
<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/>
|
|
259
|
+
Diese Node verwendet nur den Teil <code>name</code>. <code>#</code> und <code>nummer</code> sind dabei optional.
|
|
260
|
+
</p>
|
|
261
|
+
<p>
|
|
262
|
+
Folgende topics werden akzeptiert:
|
|
263
|
+
<table>
|
|
264
|
+
<thead>
|
|
265
|
+
<tr>
|
|
266
|
+
<th>Topic</th>
|
|
267
|
+
<th>Beschreibung</th>
|
|
268
|
+
</tr>
|
|
269
|
+
</thead>
|
|
270
|
+
<tbody>
|
|
271
|
+
<tr>
|
|
272
|
+
<td><code>status</code></td>
|
|
273
|
+
<td>
|
|
274
|
+
Gibt über <code>msg.payload = true</code> oder <code>msg.payload = false</code> den aktuellen Status des Ausgangs an.<br/>
|
|
275
|
+
Bei einem Wechsel von ausgeschaltet nach eingeschaltet wird die Zeitmessung für die hinterlegte, bzw. mitgesendete Zeit gestartet, sofern vorhanden.
|
|
276
|
+
</td>
|
|
277
|
+
</tr>
|
|
278
|
+
<tr>
|
|
279
|
+
<td><code>on</code></td>
|
|
280
|
+
<td>Schaltet den Ausgang ein und startet die Zeitmessung für die hinterlegte, bzw. mitgesendete Zeit gestartet, sofern vorhanden.</td>
|
|
281
|
+
</tr>
|
|
282
|
+
<tr>
|
|
283
|
+
<td><code>off</code></td>
|
|
284
|
+
<td>Schaltet den Ausgang aus.</td>
|
|
285
|
+
</tr>
|
|
286
|
+
<tr>
|
|
287
|
+
<td><code>set</code></td>
|
|
288
|
+
<td>
|
|
289
|
+
Schaltet den Ausgang bei <code>msg.payload = true</code> ein und bei <code>msg.payload = false</code> aus.<br/>
|
|
290
|
+
Bei einem Wechsel von ausgeschaltet nach eingeschaltet wird die Zeitmessung für die hinterlegte, bzw. mitgesendete Zeit gestartet, sofern vorhanden.
|
|
291
|
+
</td>
|
|
292
|
+
</tr>
|
|
293
|
+
<tr>
|
|
294
|
+
<td><code>set_permanent</code></td>
|
|
295
|
+
<td>
|
|
296
|
+
Schaltet den Ausgang bei <code>msg.payload = true</code> dauerhaft ein und bei <code>msg.payload = false</code> aus.<br/>
|
|
297
|
+
Es wird dabei keine Zeitmessung gestartet.
|
|
298
|
+
</td>
|
|
299
|
+
</tr>
|
|
300
|
+
<tr>
|
|
301
|
+
<td><code>motion</code></td>
|
|
302
|
+
<td>
|
|
303
|
+
Schaltet den Ausgang bei <code>msg.payload = true</code> ein ohne eine Zeitmessung.<br/>
|
|
304
|
+
Bei <code>msg.payload = false</code> wird die Zeitmessung für die hinterlegte, bzw. mitgesendete Zeit gestartet, sofern vorhanden.<br/>
|
|
305
|
+
Ist keine Zeit angegeben oder hinterlegt, schaltet sich der Ausgang sofort aus.
|
|
306
|
+
</td>
|
|
307
|
+
</tr>
|
|
308
|
+
<tr>
|
|
309
|
+
<td><code>alarm</code></td>
|
|
310
|
+
<td>Setzt den aktuellen Alarmzustand auf den Wert von <code>msg.payload</code> und löst die entsprechende Aktion aus.</td>
|
|
311
|
+
</tr>
|
|
312
|
+
<tr>
|
|
313
|
+
<td><code>toggle</code> (default)</td>
|
|
314
|
+
<td>
|
|
315
|
+
Schaltet den Ausgang abwechselnd ein und aus.<br/>
|
|
316
|
+
Bei einem Wechsel von ausgeschaltet nach eingeschaltet wird die Zeitmessung für die hinterlegte, bzw. mitgesendete Zeit gestartet, sofern vorhanden.
|
|
317
|
+
</td>
|
|
318
|
+
</tr>
|
|
319
|
+
</tbody>
|
|
320
|
+
</table>
|
|
321
|
+
</p>
|
|
322
|
+
<p>
|
|
323
|
+
Diese Node hat eine einstellbare Maximallaufzeit, bevor der Ausgang automatisch wieder ausgeschalten wird.
|
|
324
|
+
Diese Zeitmessung wird wie in der Tabelle oben verwendet.
|
|
325
|
+
Die eingestellte Zeit kann gezielt überschrieben werden.
|
|
326
|
+
Beispiel: <code>msg = { "topic": "on", "time_on": 5000 }</code> oder <code>msg = { "topic": "on", "time_on": "5s" }</code><br/>
|
|
327
|
+
Diese Nachricht schaltet das Licht für 5000 Millisekunden / 5 Sekunden an und anschließend wieder aus.
|
|
328
|
+
Die nächste Nachricht ohne <code>time_on</code> Angabe verwendet wieder die voreingestellte Zeit.
|
|
329
|
+
Ist die Zeit auf 0 eingestellt, wird das Licht <b>nicht</b> automatisch ausgeschalten.<br/>
|
|
330
|
+
Als Einheit für die Zeit können folgende Werte verwendet werden:
|
|
331
|
+
<table>
|
|
332
|
+
<thead>
|
|
333
|
+
<tr>
|
|
334
|
+
<th>Einheit</th>
|
|
335
|
+
<th>Beschreibung</th>
|
|
336
|
+
</tr>
|
|
337
|
+
</thead>
|
|
338
|
+
<tbody>
|
|
339
|
+
<tr>
|
|
340
|
+
<td><code>ms</code> (default)</td>
|
|
341
|
+
<td>Millisekunden</td>
|
|
342
|
+
</tr>
|
|
343
|
+
<tr>
|
|
344
|
+
<td><code>s</code> oder <code>sec</code></td>
|
|
345
|
+
<td>Sekunden</td>
|
|
346
|
+
</tr>
|
|
347
|
+
<tr>
|
|
348
|
+
<td><code>m</code> oder <code>min</code></td>
|
|
349
|
+
<td>Mintun.</td>
|
|
350
|
+
</tr>
|
|
351
|
+
<tr>
|
|
352
|
+
<td><code>h</code></td>
|
|
353
|
+
<td>Stunden</td>
|
|
354
|
+
</tr>
|
|
355
|
+
</tbody>
|
|
356
|
+
</table>
|
|
357
|
+
</p>
|
|
358
|
+
</script>
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
module.exports = function (RED)
|
|
2
|
+
{
|
|
3
|
+
function LightControlNode(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
|
+
// persistent values
|
|
12
|
+
var nodeSettings = Object.assign({}, {
|
|
13
|
+
last_value: false,
|
|
14
|
+
}, smartContext.get(node.id));
|
|
15
|
+
|
|
16
|
+
// dynamic config
|
|
17
|
+
let max_time_on = helper.getTimeInMs(config.max_time_on, config.max_time_on_unit);
|
|
18
|
+
let alarm_action = config.alarm_action || "NOTHING";
|
|
19
|
+
|
|
20
|
+
// runtime values
|
|
21
|
+
let max_time_on_timeout = null;
|
|
22
|
+
let isPermanent = false;
|
|
23
|
+
let isMotion = false;
|
|
24
|
+
let current_timeout_ms = 0;
|
|
25
|
+
let alarm_active = false;
|
|
26
|
+
|
|
27
|
+
// central handling
|
|
28
|
+
var event = "node:" + node.id;
|
|
29
|
+
var handler = function (msg)
|
|
30
|
+
{
|
|
31
|
+
node.receive(msg);
|
|
32
|
+
}
|
|
33
|
+
RED.events.on(event, handler);
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
node.on("input", function (msg)
|
|
37
|
+
{
|
|
38
|
+
handleTopic(msg);
|
|
39
|
+
|
|
40
|
+
setStatus();
|
|
41
|
+
smartContext.set(node.id, nodeSettings);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
node.on("close", function ()
|
|
45
|
+
{
|
|
46
|
+
stopAutoOff();
|
|
47
|
+
RED.events.off(event, handler);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
let handleTopic = msg =>
|
|
51
|
+
{
|
|
52
|
+
let doRestartTimer = true;
|
|
53
|
+
|
|
54
|
+
switch (helper.getTopicName(msg.topic))
|
|
55
|
+
{
|
|
56
|
+
case "status":
|
|
57
|
+
// Make sure it is bool
|
|
58
|
+
msg.payload = !!msg.payload;
|
|
59
|
+
doRestartTimer = nodeSettings.last_value != msg.payload;
|
|
60
|
+
|
|
61
|
+
nodeSettings.last_value = msg.payload;
|
|
62
|
+
break;
|
|
63
|
+
|
|
64
|
+
case "off":
|
|
65
|
+
nodeSettings.last_value = false;
|
|
66
|
+
break;
|
|
67
|
+
|
|
68
|
+
case "on":
|
|
69
|
+
nodeSettings.last_value = true;
|
|
70
|
+
break;
|
|
71
|
+
|
|
72
|
+
case "set":
|
|
73
|
+
// Make sure it is bool
|
|
74
|
+
msg.payload = !!msg.payload;
|
|
75
|
+
nodeSettings.last_value = msg.payload;
|
|
76
|
+
break;
|
|
77
|
+
|
|
78
|
+
case "set_permanent":
|
|
79
|
+
// Make sure it is bool
|
|
80
|
+
msg.payload = !!msg.payload;
|
|
81
|
+
nodeSettings.last_value = msg.payload;
|
|
82
|
+
isPermanent = msg.payload;
|
|
83
|
+
break;
|
|
84
|
+
|
|
85
|
+
case "motion":
|
|
86
|
+
// Make sure it is bool
|
|
87
|
+
msg.payload = !!msg.payload;
|
|
88
|
+
isMotion = msg.payload;
|
|
89
|
+
|
|
90
|
+
if (msg.payload == false)
|
|
91
|
+
{
|
|
92
|
+
// It already was off, so don't turn on
|
|
93
|
+
if (nodeSettings.last_value == false)
|
|
94
|
+
return;
|
|
95
|
+
|
|
96
|
+
// If time is set to 0, then turn off immediately
|
|
97
|
+
if (helper.getTimeInMsFromString(msg.time_on ?? max_time_on) == 0)
|
|
98
|
+
nodeSettings.last_value = false;
|
|
99
|
+
}
|
|
100
|
+
else
|
|
101
|
+
{
|
|
102
|
+
nodeSettings.last_value = true;
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
|
|
106
|
+
case "alarm":
|
|
107
|
+
// Make sure it is bool
|
|
108
|
+
msg.payload = !!msg.payload;
|
|
109
|
+
alarm_active = msg.payload;
|
|
110
|
+
break;
|
|
111
|
+
|
|
112
|
+
case "toggle":
|
|
113
|
+
default:
|
|
114
|
+
// If button is released, don't handle this message
|
|
115
|
+
if (msg.payload === false)
|
|
116
|
+
return;
|
|
117
|
+
|
|
118
|
+
nodeSettings.last_value = !nodeSettings.last_value;
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (doRestartTimer)
|
|
123
|
+
stopAutoOff();
|
|
124
|
+
|
|
125
|
+
// Check alarm values
|
|
126
|
+
if (alarm_active)
|
|
127
|
+
{
|
|
128
|
+
isPermanent = false;
|
|
129
|
+
|
|
130
|
+
switch (alarm_action)
|
|
131
|
+
{
|
|
132
|
+
case "ON":
|
|
133
|
+
nodeSettings.last_value = true;
|
|
134
|
+
break;
|
|
135
|
+
|
|
136
|
+
default:
|
|
137
|
+
case "OFF":
|
|
138
|
+
nodeSettings.last_value = false;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (alarm_active || helper.getTopicName(msg.topic) != "status")
|
|
144
|
+
node.send({ payload: nodeSettings.last_value });
|
|
145
|
+
|
|
146
|
+
// Output is on, now
|
|
147
|
+
if (nodeSettings.last_value && doRestartTimer)
|
|
148
|
+
startAutoOffIfNeeded(helper.getTimeInMsFromString(msg.time_on ?? max_time_on));
|
|
149
|
+
|
|
150
|
+
notifyCentral(nodeSettings.last_value);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let startAutoOffIfNeeded = origTimeMs =>
|
|
154
|
+
{
|
|
155
|
+
// No timer when alarm is active
|
|
156
|
+
if (alarm_active)
|
|
157
|
+
return;
|
|
158
|
+
|
|
159
|
+
let timeMs = parseInt(origTimeMs);
|
|
160
|
+
|
|
161
|
+
if (isNaN(timeMs))
|
|
162
|
+
{
|
|
163
|
+
node.error("Invalid time_on value send: " + origTimeMs);
|
|
164
|
+
timeMs = max_time_on;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
current_timeout_ms = timeMs;
|
|
168
|
+
|
|
169
|
+
// Stop if any timeout is set
|
|
170
|
+
stopAutoOff();
|
|
171
|
+
|
|
172
|
+
// 0 = Always on
|
|
173
|
+
if (timeMs <= 0 || isPermanent || isMotion || !nodeSettings.last_value)
|
|
174
|
+
return;
|
|
175
|
+
|
|
176
|
+
max_time_on_timeout = setTimeout(() =>
|
|
177
|
+
{
|
|
178
|
+
max_time_on_timeout = null;
|
|
179
|
+
nodeSettings.last_value = false;
|
|
180
|
+
node.send({ payload: false });
|
|
181
|
+
notifyCentral(false);
|
|
182
|
+
|
|
183
|
+
setStatus();
|
|
184
|
+
smartContext.set(node.id, nodeSettings);
|
|
185
|
+
}, timeMs);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
let stopAutoOff = () =>
|
|
189
|
+
{
|
|
190
|
+
if (max_time_on_timeout != null)
|
|
191
|
+
{
|
|
192
|
+
clearTimeout(max_time_on_timeout);
|
|
193
|
+
max_time_on_timeout = null;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
let setStatus = () =>
|
|
198
|
+
{
|
|
199
|
+
if (alarm_active)
|
|
200
|
+
{
|
|
201
|
+
node.status({ fill: "red", shape: "dot", text: "ALARM is active" });
|
|
202
|
+
}
|
|
203
|
+
else if (nodeSettings.last_value)
|
|
204
|
+
{
|
|
205
|
+
if (isPermanent || isMotion || current_timeout_ms <= 0)
|
|
206
|
+
node.status({ fill: "green", shape: "dot", text: "On" });
|
|
207
|
+
else if (max_time_on_timeout)
|
|
208
|
+
node.status({ fill: "yellow", shape: "ring", text: "Wait " + helper.formatMsToStatus(current_timeout_ms, "until") + " for auto off" });
|
|
209
|
+
}
|
|
210
|
+
else
|
|
211
|
+
{
|
|
212
|
+
node.status({ fill: "red", shape: "dot", text: "Off" });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
let notifyCentral = state =>
|
|
217
|
+
{
|
|
218
|
+
if (!config.links)
|
|
219
|
+
return;
|
|
220
|
+
|
|
221
|
+
config.links.forEach(link =>
|
|
222
|
+
{
|
|
223
|
+
RED.events.emit("node:" + link, { source: node.id, state: state });
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
setStatus();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
RED.nodes.registerType("smart_light-control", LightControlNode);
|
|
231
|
+
};
|