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,399 @@
1
+ module.exports = function (RED)
2
+ {
3
+ function ShutterComplexControlNode(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_position: 0, // 0 = opened, 100 = closed
14
+ last_direction_up: true, // remember last direction for toggle action
15
+ }, smartContext.get(node.id));
16
+
17
+ // dynamic config
18
+ let max_time = parseInt(config.max_time || 60);
19
+ let revert_time_ms = parseInt(config.revert_time_ms || 100);
20
+ let alarm_action = config.alarm_action || "NOTHING";
21
+
22
+ // runtime values
23
+ let max_time_timeout = null;
24
+ let on_time = null;
25
+ let off_time = null;
26
+ let wait_for_up = false;
27
+ let wait_for_down = false;
28
+ let wait_timeout = null;
29
+ let alarm_active = false;
30
+
31
+ // central handling
32
+ var event = "node:" + config.id;
33
+ var handler = function (msg)
34
+ {
35
+ node.receive(msg);
36
+ }
37
+ RED.events.on(event, handler);
38
+
39
+
40
+ node.on("input", function (msg)
41
+ {
42
+ handleTopic(msg);
43
+
44
+ smartContext.set(node.id, nodeSettings);
45
+ });
46
+
47
+ node.on("close", function ()
48
+ {
49
+ stopAutoOff();
50
+ RED.events.off(event, handler);
51
+ });
52
+
53
+ let handleTopic = msg =>
54
+ {
55
+ var resultUp = false;
56
+ var resultDown = false;
57
+ var resultStop = false;
58
+
59
+ let realTopic = helper.getTopicName(msg.topic);
60
+
61
+ // set default topic if invalid value is send
62
+ if (!["up", "up_stop", "down", "down_stop", "stop", "toggle", "up_down", "position", "alarm"].includes(realTopic))
63
+ realTopic = "toggle";
64
+
65
+ // skip if button is released
66
+ if (msg.payload === false && ["up", "up_stop", "down", "down_stop", "stop", "toggle"].includes(realTopic))
67
+ return;
68
+
69
+ // Convert up_down from HA UI to next command
70
+ if (realTopic == "up_down")
71
+ {
72
+ if (msg.payload)
73
+ realTopic = "down";
74
+ else
75
+ realTopic = "up";
76
+ }
77
+
78
+ // Correct next topic to avoid handling up_stop, down_stop or toggle separately.
79
+ if (max_time_timeout != null && (realTopic == "up_stop" || realTopic == "down_stop" || realTopic == "toggle"))
80
+ {
81
+ realTopic = "stop";
82
+ }
83
+ else if (max_time_timeout == null)
84
+ {
85
+ // shutter is not running, set next command depending on topic
86
+ if (realTopic == "up_stop")
87
+ realTopic = "up";
88
+ else if (realTopic == "down_stop")
89
+ realTopic = "down";
90
+ else if (nodeSettings.last_direction_up && realTopic == "toggle")
91
+ realTopic = "down";
92
+ else if (!nodeSettings.last_direction_up && realTopic == "toggle")
93
+ realTopic = "up";
94
+ }
95
+
96
+ // console.log("Convert topic to: " + realTopic);
97
+
98
+ switch (realTopic)
99
+ {
100
+ case "up":
101
+ if (max_time_timeout != null)
102
+ {
103
+ // Nothing todo
104
+ if (nodeSettings.last_direction_up)
105
+ return;
106
+
107
+ // Runs down at the moment, so stop first
108
+ if (nodeSettings.last_direction_up == false)
109
+ {
110
+ // console.log("--- Sub topic ---");
111
+ handleTopic({ topic: "stop" });
112
+ // console.log("-----------------");
113
+ }
114
+ }
115
+
116
+ on_time = Date.now();
117
+ resultUp = true;
118
+ startAutoOff(false, helper.getTimeInMsFromString(msg.time_on) || null);
119
+ if (!alarm_active)
120
+ node.status({ fill: "green", shape: "dot", text: "Up" });
121
+ break;
122
+
123
+ case "stop":
124
+ if (alarm_active)
125
+ break;
126
+
127
+ resultStop = true;
128
+ if (max_time_timeout != null)
129
+ {
130
+ const change_percentage = (Date.now() - on_time) / 1000 / max_time * 100;
131
+ if (nodeSettings.last_direction_up)
132
+ nodeSettings.last_position = Math.max(0, nodeSettings.last_position - change_percentage);
133
+ else
134
+ nodeSettings.last_position = Math.min(100, nodeSettings.last_position + change_percentage);
135
+ }
136
+ off_time = Date.now();
137
+ stopAutoOff();
138
+ node.status({ fill: "red", shape: "dot", text: "Stopped at " + Math.round(nodeSettings.last_position) + "%" });
139
+ break;
140
+
141
+ case "down":
142
+ if (max_time_timeout != null)
143
+ {
144
+ // Nothing todo
145
+ if (nodeSettings.last_direction_up == false)
146
+ return;
147
+
148
+ // Runs up at the moment, so stop first
149
+ if (nodeSettings.last_direction_up)
150
+ {
151
+ // console.log("--- Sub topic ---");
152
+ handleTopic({ topic: "stop" });
153
+ // console.log("-----------------");
154
+ }
155
+ }
156
+
157
+ on_time = Date.now();
158
+ resultDown = true;
159
+ startAutoOff(true, helper.getTimeInMsFromString(msg.time_on) || null);
160
+ if (!alarm_active)
161
+ node.status({ fill: "green", shape: "dot", text: "Down" });
162
+ break;
163
+
164
+ case "position":
165
+ if (alarm_active)
166
+ break;
167
+
168
+ let value = parseFloat(msg.payload);
169
+
170
+ if (value < 0) value = 0;
171
+ if (value > 100) value = 100;
172
+
173
+ const now = Date.now();
174
+
175
+ if (max_time_timeout != null)
176
+ {
177
+ // Change position while running,
178
+ // Calculate current position first
179
+ const change_percentage = (now - on_time) / 1000 / max_time * 100;
180
+ if (nodeSettings.last_direction_up)
181
+ nodeSettings.last_position = Math.max(0, nodeSettings.last_position - change_percentage);
182
+ else
183
+ nodeSettings.last_position = Math.min(100, nodeSettings.last_position + change_percentage);
184
+
185
+ node.status({ fill: "gray", shape: "dot", text: "Update current position to " + nodeSettings.last_position + "%" });
186
+
187
+ // Runs in the wrong direction at the moment, so stop first
188
+ if (nodeSettings.last_direction_up && value > nodeSettings.last_position)
189
+ {
190
+ // console.log("--- Sub topic ---");
191
+ handleTopic({ topic: "stop" });
192
+ // console.log("-----------------");
193
+ }
194
+ else if (nodeSettings.last_direction_up == false && value < nodeSettings.last_position)
195
+ {
196
+ // console.log("--- Sub topic ---");
197
+ handleTopic({ topic: "stop" });
198
+ // console.log("-----------------");
199
+ }
200
+ }
201
+
202
+ on_time = now;
203
+
204
+ if (value < nodeSettings.last_position)
205
+ {
206
+ // Go up
207
+ resultUp = true;
208
+ startAutoOff(false, (nodeSettings.last_position - value) / 100 * max_time * 1000, value);
209
+ }
210
+ else if (value > nodeSettings.last_position)
211
+ {
212
+ // Go down
213
+ resultDown = true;
214
+ startAutoOff(true, (value - nodeSettings.last_position) / 100 * max_time * 1000, value);
215
+ }
216
+ else
217
+ {
218
+ // Really an exact match
219
+ handleTopic({ topic: "stop" });
220
+ return;
221
+ }
222
+
223
+ node.status({ fill: "green", shape: "dot", text: "Set position from " + nodeSettings.last_position + "% to " + value + "%" });
224
+ break;
225
+
226
+ case "alarm":
227
+ alarm_active = msg.payload;
228
+
229
+ if (alarm_active)
230
+ {
231
+ node.status({ fill: "red", shape: "dot", text: "ALARM is active" });
232
+
233
+ switch (alarm_action)
234
+ {
235
+ case "UP":
236
+ handleTopic({ topic: "up" });
237
+ break;
238
+
239
+ case "DOWN":
240
+ handleTopic({ topic: "down" });
241
+ break;
242
+ }
243
+ }
244
+ break;
245
+ }
246
+
247
+ sendResult(resultUp, resultDown, resultStop);
248
+ };
249
+
250
+ let startAutoOff = (down, wait_time_ms = null, new_position = null) =>
251
+ {
252
+ // console.log("startAutoOff (" + down + ", " + wait_time_ms + ", " + new_position + ");");
253
+
254
+ // Stop if any timeout is set
255
+ stopAutoOff();
256
+
257
+ if (new_position != null)
258
+ {
259
+ // use normal times for top or bottom position
260
+ if (new_position == 0)
261
+ {
262
+ new_position = null;
263
+ down = false;
264
+ }
265
+ else if (new_position == 100)
266
+ {
267
+ new_position = null;
268
+ down = true;
269
+ }
270
+ }
271
+ if (wait_time_ms == null)
272
+ {
273
+ if (down)
274
+ wait_time_ms = (100 - nodeSettings.last_position) * max_time / 100 * 1000;
275
+ else
276
+ wait_time_ms = nodeSettings.last_position * max_time / 100 * 1000;
277
+
278
+ // Run at least for 5 seconds
279
+ if (wait_time_ms < 5000)
280
+ wait_time_ms = 5000;
281
+ }
282
+
283
+ max_time_on = parseInt(wait_time_ms / 1000);
284
+
285
+ if (wait_time_ms < 0)
286
+ {
287
+ node.status({ fill: "red", shape: "dot", text: "time_on value has to be greater than 0" });
288
+ return;
289
+ }
290
+
291
+ if (!alarm_active)
292
+ node.status({ fill: "yellow", shape: "ring", text: (down ? "Down" : "Up") + ", wait " + helper.formatMsToStatus(max_time_on, "until") + " for auto off" });
293
+
294
+ max_time_timeout = setTimeout(() =>
295
+ {
296
+ handleTopic({ topic: "stop" });
297
+ if (new_position != null)
298
+ nodeSettings.last_position = new_position;
299
+ smartContext.set(node.id, nodeSettings);
300
+ }, wait_time_ms);
301
+ };
302
+
303
+ let stopAutoOff = () =>
304
+ {
305
+ if (max_time_timeout != null)
306
+ {
307
+ // console.log("stopAutoOff");
308
+ if (!alarm_active)
309
+ node.status({ fill: "green", shape: "dot", text: "Stopped at " + Math.round(nodeSettings.last_position) + "%" });
310
+ clearTimeout(max_time_timeout);
311
+ off_time = Date.now();
312
+ max_time_timeout = null;
313
+ }
314
+ };
315
+
316
+ let sendResult = (up, down, stop) =>
317
+ {
318
+ // console.log("sendResult(" + up + ", " + down + ", " + stop + ");");
319
+ if ((up && wait_for_down) || (down && wait_for_up))
320
+ {
321
+ // console.log("Stop wait timeout");
322
+ clearTimeout(wait_timeout);
323
+ wait_timeout = null;
324
+ }
325
+
326
+ let now = Date.now();
327
+ if (off_time + revert_time_ms > now)
328
+ {
329
+ // Output has to possibly wait until the revert time has passed
330
+ if ((nodeSettings.last_direction_up && down) || (nodeSettings.last_direction_up == false && up))
331
+ {
332
+ // console.log("Start wait timeout for " + (off_time + revert_time_ms - now) + "ms");
333
+
334
+ if (off_time + revert_time_ms - now > revert_time_ms)
335
+ return;
336
+
337
+ wait_timeout = setTimeout(() =>
338
+ {
339
+ wait_timeout = null;
340
+ sendResult(up, down, stop);
341
+ }, off_time + revert_time_ms - now);
342
+ return;
343
+ }
344
+ }
345
+
346
+ if (stop && wait_timeout != null)
347
+ {
348
+ clearTimeout(wait_timeout);
349
+ wait_timeout = null;
350
+ }
351
+
352
+ // console.log("really sendResult(" + up + ", " + down + ", " + stop + ");");
353
+ if (up)
354
+ {
355
+ if (!alarm_active || alarm_action === "UP")
356
+ {
357
+ nodeSettings.last_direction_up = true;
358
+ node.send([{ payload: true }, { payload: false }, { payload: nodeSettings.last_position }]);
359
+ notifyCentral(true);
360
+ }
361
+ }
362
+ else if (down)
363
+ {
364
+ if (!alarm_active || alarm_action === "DOWN")
365
+ {
366
+ nodeSettings.last_direction_up = false;
367
+ node.send([{ payload: false }, { payload: true }, { payload: nodeSettings.last_position }]);
368
+ notifyCentral(true);
369
+ }
370
+ }
371
+ else if (stop && !alarm_active)
372
+ {
373
+ node.send([{ payload: false }, { payload: false }, { payload: nodeSettings.last_position }]);
374
+ notifyCentral(false);
375
+ }
376
+ };
377
+
378
+ let notifyCentral = state =>
379
+ {
380
+ if (!config.links)
381
+ return;
382
+
383
+ config.links.forEach(link =>
384
+ {
385
+ RED.events.emit("node:" + link, { source: node.id, state: state });
386
+ });
387
+ };
388
+
389
+ // For security reason, stop shutter at node start
390
+ setTimeout(() =>
391
+ {
392
+ node.send([{ payload: false }, { payload: false }, { payload: nodeSettings.last_position }]);
393
+ node.status({ fill: "red", shape: "dot", text: "Stopped at " + Math.round(nodeSettings.last_position) + "%" });
394
+ notifyCentral(false);
395
+ }, 10000);
396
+ }
397
+
398
+ RED.nodes.registerType("smart_shutter-complex-control", ShutterComplexControlNode);
399
+ };
@@ -0,0 +1,283 @@
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_shutter-control", {
136
+ category: "Smart Nodes",
137
+ paletteLabel: "Shutter control",
138
+ color: "#C882FF",
139
+ defaults: {
140
+ name: { value: "" },
141
+ links: { value: [], type: "smart_central-control[]" }
142
+ },
143
+ inputs: 1,
144
+ outputs: 3,
145
+ outputLabels: ["Up/Down", "Stop", "Position"],
146
+ icon: "font-awesome/fa-align-justify",
147
+ label: function ()
148
+ {
149
+ return this.name || "Shutter control";
150
+ },
151
+ oneditprepare: function ()
152
+ {
153
+ let node = this;
154
+ onEditPrepare(this, ["smart_central-control"]);
155
+ initTreeList(node, ["smart_central-control"]);
156
+ },
157
+ onadd: function ()
158
+ {
159
+ this.links = [];
160
+ },
161
+ oneditresize: resizeNodeList
162
+ });
163
+ })();
164
+ </script>
165
+
166
+ <script type="text/html" data-template-name="smart_shutter-control">
167
+ <div class="form-row">
168
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
169
+ <input type="text" id="node-input-name" placeholder="Name" />
170
+ </div>
171
+ <span><i class="fa fa-link"></i>Dieser Baustein wird von folgenden Zentralbausteinen gesteuert:</span>
172
+ <div class="form-row node-input-link-row node-input-link-rows"></div>
173
+ </script>
174
+
175
+ <script type="text/html" data-help-name="smart_shutter-control">
176
+ <p>
177
+ <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/>
178
+ Diese Node verwendet nur den Teil <code>name</code>. <code>#</code> und <code>nummer</code> sind dabei optional.
179
+ </p>
180
+ <p>
181
+ Diese Node steuert Rollladen oder Jalousien.
182
+ Es gibt 3 Ausgänge die angesteuert werden können:
183
+ <ol>
184
+ <li><b>Auf/Ab:</b> <code>msg.payload = false</code> lässt den Rollladen nach oben fahren und <code>msg.payload = true</code> lässt den Rollladen nach unten fahren.</li>
185
+ <li><b>Stop:</b> <code>msg.payload = true</code> lässt den Rollladen stoppen.</li>
186
+ <li><b>Position:</b> <code>msg.payload = 42</code> Lässt den Rollladen auf 42% fahren.</li>
187
+ </ol>
188
+ Die Ausgänge sind den jeweiligen KNX Gruppenadressen zuzuordnen.
189
+ </p>
190
+ <p>
191
+ Diese Node erwartet folgende Topics als Eingang:<br/>
192
+ <table>
193
+ <thead>
194
+ <tr>
195
+ <th>Topic</th>
196
+ <th>Beschreibung</th>
197
+ </tr>
198
+ </thead>
199
+ <tbody>
200
+ <tr>
201
+ <td><code>status</code> oder <code>status_position</code></td>
202
+ <td>
203
+ Meldet der Node die aktuelle Position.
204
+ Dadurch kann die Node feststellen in welche Richtung ein Rollladen als letztes gefahren ist.
205
+ Dies wird für das topic <b>toggle</b> benötigt um die nächste Laufrichtung bei einer 1-Tasten Bedienung zu ermitteln.
206
+ </td>
207
+ </tr>
208
+ <tr>
209
+ <td><code>up_down</code></td>
210
+ <td>
211
+ Nimmt einen Befehl von Home Assistant entgegen um informiert zu werden ob der Rollladen läuft und in welche Richtung.<br/>
212
+ <strong>Wichtig:</strong> Dieses Topic startet den Rollladen <u>nicht</u>, da die Gruppenadresse direkt mit dem Jalousiemodul verbunden sein muss.
213
+ </td>
214
+ </tr>
215
+ <tr>
216
+ <td><code>up</code></td>
217
+ <td>Sendet einen Hochfahrbefehl, falls der Rollladen nicht bereits nach oben fährt. Ggf. wird vorher noch ein Stop-Befehl gesendet.</td>
218
+ </tr>
219
+ <tr>
220
+ <td><code>up_stop</code></td>
221
+ <td>Sendet abwechselnd einen Stop- und einen Hochfahrbefehl.</td>
222
+ </tr>
223
+ <tr>
224
+ <td><code>down</code></td>
225
+ <td>Sendet einen Runterfahrbefehl, falls der Rollladen nicht bereits nach unten fährt. Ggf. wird vorher noch ein Stop-Befehl gesendet.</td>
226
+ </tr>
227
+ <tr>
228
+ <td><code>down_stop</code></td>
229
+ <td>Sendet abwechselnd einen Stop- und einen Runterfahrbefehl.</td>
230
+ </tr>
231
+ <tr>
232
+ <td><code>stop</code></td>
233
+ <td>Sendet einen Stopbefehl.</td>
234
+ </tr>
235
+ <tr>
236
+ <td><code>position</code></td>
237
+ <td>Sendet einen Positionsbefehl. <code>msg.payload</code> sollte ein Wert zwischen 0 (offen) und 100 (geschlossen) haben.</td>
238
+ </tr>
239
+ <tr>
240
+ <td><code>toggle</code> (default)</td>
241
+ <td>Schaltet den Rollladen abwechselnd auf hoch, stop, runter, stop.</td>
242
+ </tr>
243
+ </tbody>
244
+ </table>
245
+ </p>
246
+ <p>
247
+ Diese Node verwaltet keine Laufzeit für den Rollladen selbst. Diese muss über ETS für den Ausgang konfiguriert werden.
248
+ Es ist jedoch möglich, den Rollladen nach einer definierten Zeit automatisch abzuschalten.
249
+ Es ist jedoch möglich, den Rollladen nach einer definierten Zeit automatisch abzuschalten.
250
+ Beispiel: <code>msg = { "topic": "up", "time_on": 5000 }</code> oder <code>msg = { "topic": "up", "time_on": "5s" }</code><br/>
251
+ Diese Nachricht lässt den Rollladen für 5000 Millisekunden / 5 Sekunden nach oben fahren. Sollte es sich um eine Jalousie halten, werden die Lamellen entsprechend gedreht.
252
+ Als Einheit für die Zeit können folgende Werte verwendet werden:
253
+ <table>
254
+ <thead>
255
+ <tr>
256
+ <th>Einheit</th>
257
+ <th>Beschreibung</th>
258
+ </tr>
259
+ </thead>
260
+ <tbody>
261
+ <tr>
262
+ <td><code>ms</code> (default)</td>
263
+ <td>Millisekunden</td>
264
+ </tr>
265
+ <tr>
266
+ <td><code>s</code> oder <code>sec</code></td>
267
+ <td>Sekunden</td>
268
+ </tr>
269
+ <tr>
270
+ <td><code>m</code> oder <code>min</code></td>
271
+ <td>Mintun.</td>
272
+ </tr>
273
+ <tr>
274
+ <td><code>h</code></td>
275
+ <td>Stunden</td>
276
+ </tr>
277
+ </tbody>
278
+ </table>
279
+ </p>
280
+ <p>
281
+ Die Angabe einer Zeit funktioniert nicht mit dem topic <b>position</b>.
282
+ </p>
283
+ </script>