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,265 @@
|
|
|
1
|
+
module.exports = function (RED)
|
|
2
|
+
{
|
|
3
|
+
function SceneControlNode(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_values: [], // light is on or off for a scene
|
|
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
|
+
|
|
19
|
+
// runtime values
|
|
20
|
+
let max_time_on_timeout = null;
|
|
21
|
+
let isPermanent = false;
|
|
22
|
+
let current_timeout_ms = 0;
|
|
23
|
+
|
|
24
|
+
// central handling
|
|
25
|
+
var event = "node:" + config.id;
|
|
26
|
+
var handler = function (msg)
|
|
27
|
+
{
|
|
28
|
+
node.receive(msg);
|
|
29
|
+
}
|
|
30
|
+
RED.events.on(event, handler);
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
if (nodeSettings.last_values.length != config.scenes.length)
|
|
34
|
+
{
|
|
35
|
+
// Per default expect that all outputs are off
|
|
36
|
+
nodeSettings.last_values = new Array(config.outputs).fill(false);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
node.status({});
|
|
40
|
+
|
|
41
|
+
node.on("input", function (msg)
|
|
42
|
+
{
|
|
43
|
+
handleTopic(msg);
|
|
44
|
+
|
|
45
|
+
// At least one light is on, now
|
|
46
|
+
if (getCurrentScene() != 0)
|
|
47
|
+
startAutoOffIfNeeded(helper.getTimeInMsFromString(msg.time_on ?? max_time_on));
|
|
48
|
+
|
|
49
|
+
status();
|
|
50
|
+
smartContext.set(node.id, nodeSettings);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
node.on("close", function ()
|
|
54
|
+
{
|
|
55
|
+
stopAutoOff();
|
|
56
|
+
RED.events.off(event, handler);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
let handleTopic = msg =>
|
|
60
|
+
{
|
|
61
|
+
let currentScene = getCurrentScene();
|
|
62
|
+
let [realTopic, scenes] = helper.getTopicName(msg.topic).split("_");
|
|
63
|
+
let number = helper.getTopicNumber(msg.topic) - 1; // number should be used 0-based
|
|
64
|
+
|
|
65
|
+
switch (realTopic)
|
|
66
|
+
{
|
|
67
|
+
case "status":
|
|
68
|
+
// Make sure it is bool
|
|
69
|
+
msg.payload = !!msg.payload;
|
|
70
|
+
nodeSettings.last_values[number] = msg.payload;
|
|
71
|
+
|
|
72
|
+
notifyCentral();
|
|
73
|
+
|
|
74
|
+
// All off? Stop permanent
|
|
75
|
+
if (getCurrentScene() == 0)
|
|
76
|
+
isPermanent = false;
|
|
77
|
+
|
|
78
|
+
// never forward status message to next node
|
|
79
|
+
return;
|
|
80
|
+
|
|
81
|
+
case "off":
|
|
82
|
+
nodeSettings.last_values = new Array(config.outputs).fill(false);
|
|
83
|
+
break;
|
|
84
|
+
|
|
85
|
+
case "on":
|
|
86
|
+
nodeSettings.last_values = new Array(config.outputs).fill(true);
|
|
87
|
+
break;
|
|
88
|
+
|
|
89
|
+
case "set":
|
|
90
|
+
// Make sure it is bool
|
|
91
|
+
msg.payload = !!msg.payload;
|
|
92
|
+
nodeSettings.last_values = new Array(config.outputs).fill(msg.payload);
|
|
93
|
+
|
|
94
|
+
// This happens because of splitting by _ for scenes
|
|
95
|
+
if (scenes == "permanent")
|
|
96
|
+
isPermanent = msg.payload;
|
|
97
|
+
break;
|
|
98
|
+
|
|
99
|
+
case "scene":
|
|
100
|
+
// Skip if button is released;
|
|
101
|
+
if (msg.payload === false)
|
|
102
|
+
return;
|
|
103
|
+
|
|
104
|
+
if (typeof scenes === "undefined")
|
|
105
|
+
{
|
|
106
|
+
node.error("called topic=scene without scene(s) set. Try topic=scene_0,1");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
scenes = scenes.split(",").map(s => parseInt(s, 10));
|
|
111
|
+
|
|
112
|
+
if (scenes.length == 0)
|
|
113
|
+
{
|
|
114
|
+
node.error("called topic=scene without scene(s) set. Try topic=scene_0,1");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let nextSceneIndex = scenes.indexOf(currentScene);
|
|
119
|
+
if (nextSceneIndex === -1 || nextSceneIndex == scenes.length - 1)
|
|
120
|
+
nextSceneIndex = scenes[0];
|
|
121
|
+
else
|
|
122
|
+
nextSceneIndex = scenes[nextSceneIndex + 1];
|
|
123
|
+
|
|
124
|
+
// To be able to toggle if only one scene is set
|
|
125
|
+
if (currentScene == nextSceneIndex)
|
|
126
|
+
nextSceneIndex = 0;
|
|
127
|
+
|
|
128
|
+
if (nextSceneIndex == 0)
|
|
129
|
+
{
|
|
130
|
+
nodeSettings.last_values = new Array(config.outputs).fill(false);
|
|
131
|
+
}
|
|
132
|
+
else
|
|
133
|
+
{
|
|
134
|
+
const scene = config.scenes[nextSceneIndex - 1]; // scene numbers are 1 based
|
|
135
|
+
const expectedOn = scene.outputs.split(",");
|
|
136
|
+
for (let o = 0; o < nodeSettings.last_values.length; o++)
|
|
137
|
+
{
|
|
138
|
+
const output = nodeSettings.last_values[o];
|
|
139
|
+
// Check if output has to be changed
|
|
140
|
+
if ((output && !expectedOn.includes("" + (o + 1))) || (!output && expectedOn.includes("" + (o + 1))))
|
|
141
|
+
{
|
|
142
|
+
nodeSettings.last_values[o] = !output;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
147
|
+
|
|
148
|
+
case "toggle":
|
|
149
|
+
// Skip if button is released;
|
|
150
|
+
if (msg.payload === false)
|
|
151
|
+
return;
|
|
152
|
+
|
|
153
|
+
nodeSettings.last_values = new Array(config.outputs).fill(currentScene == 0);
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
stopAutoOff();
|
|
158
|
+
|
|
159
|
+
node.send(nodeSettings.last_values.map(val => { return { payload: val }; }));
|
|
160
|
+
notifyCentral();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let getCurrentScene = () =>
|
|
164
|
+
{
|
|
165
|
+
// All off ist scene 0
|
|
166
|
+
if (!nodeSettings.last_values.includes(true))
|
|
167
|
+
return 0;
|
|
168
|
+
|
|
169
|
+
for (let s = 0; s < config.scenes.length; s++)
|
|
170
|
+
{
|
|
171
|
+
const scene = config.scenes[s];
|
|
172
|
+
const expectedOn = scene.outputs.split(",");
|
|
173
|
+
let skipScene = false;
|
|
174
|
+
|
|
175
|
+
for (let o = 0; o < nodeSettings.last_values.length; o++)
|
|
176
|
+
{
|
|
177
|
+
const output = nodeSettings.last_values[o];
|
|
178
|
+
// Check if one condition fails
|
|
179
|
+
if ((output && !expectedOn.includes("" + (o + 1))) || (!output && expectedOn.includes("" + (o + 1))))
|
|
180
|
+
{
|
|
181
|
+
skipScene = true;
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (skipScene)
|
|
187
|
+
continue;
|
|
188
|
+
|
|
189
|
+
return s + 1; // Scene number is 1 based
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Not a scene
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let startAutoOffIfNeeded = origTimeMs =>
|
|
197
|
+
{
|
|
198
|
+
let timeMs = parseInt(origTimeMs, 10);
|
|
199
|
+
|
|
200
|
+
if (isNaN(timeMs))
|
|
201
|
+
{
|
|
202
|
+
node.error("Invalid time_on value send: " + origTimeMs);
|
|
203
|
+
timeMs = max_time_on;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
current_timeout_ms = timeMs;
|
|
207
|
+
|
|
208
|
+
// Stop if any timeout is set
|
|
209
|
+
stopAutoOff();
|
|
210
|
+
|
|
211
|
+
// 0 = Always on or already off
|
|
212
|
+
if (timeMs <= 0 || isPermanent || getCurrentScene() == 0)
|
|
213
|
+
return;
|
|
214
|
+
|
|
215
|
+
max_time_on_timeout = setTimeout(() =>
|
|
216
|
+
{
|
|
217
|
+
nodeSettings.last_values = new Array(config.outputs).fill(false);
|
|
218
|
+
node.send(nodeSettings.last_values.map(val => { return { payload: val }; }));
|
|
219
|
+
notifyCentral();
|
|
220
|
+
|
|
221
|
+
status();
|
|
222
|
+
smartContext.set(node.id, nodeSettings);
|
|
223
|
+
}, timeMs);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let stopAutoOff = () =>
|
|
227
|
+
{
|
|
228
|
+
if (max_time_on_timeout != null)
|
|
229
|
+
{
|
|
230
|
+
clearTimeout(max_time_on_timeout);
|
|
231
|
+
max_time_on_timeout = null;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let status = () =>
|
|
236
|
+
{
|
|
237
|
+
let scene = getCurrentScene();
|
|
238
|
+
if (scene != 0)
|
|
239
|
+
{
|
|
240
|
+
if (isPermanent || current_timeout_ms <= 0)
|
|
241
|
+
node.status({ fill: "green", shape: "dot", text: "Scene " + scene + " active" });
|
|
242
|
+
else if (max_time_on_timeout)
|
|
243
|
+
node.status({ fill: "yellow", shape: "ring", text: "Scene " + scene + " active, wait " + helper.formatMsToStatus(current_timeout_ms, "until") + " for auto off" });
|
|
244
|
+
}
|
|
245
|
+
else
|
|
246
|
+
{
|
|
247
|
+
node.status({ fill: "red", shape: "dot", text: "Off" });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
let notifyCentral = () =>
|
|
252
|
+
{
|
|
253
|
+
if (!config.links)
|
|
254
|
+
return;
|
|
255
|
+
|
|
256
|
+
let state = getCurrentScene() !== 0;
|
|
257
|
+
|
|
258
|
+
config.links.forEach(link =>
|
|
259
|
+
{
|
|
260
|
+
RED.events.emit("node:" + link, { source: node.id, state: state });
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
RED.nodes.registerType("smart_scene-control", SceneControlNode);
|
|
265
|
+
};
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
<script type="text/html" data-template-name="smart_scheduler">
|
|
2
|
+
<div class="form-row">
|
|
3
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
|
|
4
|
+
<input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name" />
|
|
5
|
+
</div>
|
|
6
|
+
<div class="form-row">
|
|
7
|
+
<label for="node-input-enabled"></label>
|
|
8
|
+
<input type="checkbox" id="node-input-enabled" style="width: 20px;" />
|
|
9
|
+
<label for="node-input-enabled" style="width: 200px;">Scheduler aktiviert</label>
|
|
10
|
+
</div>
|
|
11
|
+
<div class="form-row" style="margin-bottom: 2px;">
|
|
12
|
+
<p class="text-center"><i class="fa fa-list"></i> <strong>Schedules</strong></p>
|
|
13
|
+
</div>
|
|
14
|
+
<div class="form-row node-scheduler-schedules-row">
|
|
15
|
+
<ol id="node-scheduler-schedules"></ol>
|
|
16
|
+
</div>
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<script type="text/html" data-template-scheduler-row="">
|
|
20
|
+
<div>
|
|
21
|
+
<div style="display: inline-block; width: 30px; margin-left: 10px;">
|
|
22
|
+
<i class="fa fa-calendar"></i>
|
|
23
|
+
</div>
|
|
24
|
+
<input class="days"/>
|
|
25
|
+
</div>
|
|
26
|
+
<div style="margin-top: 8px;">
|
|
27
|
+
<div style="display: inline-block; width: 30px; margin-left: 10px;">
|
|
28
|
+
<i class="fa fa-clock-o"></i>
|
|
29
|
+
</div>
|
|
30
|
+
<select class="node-input-scheduler-select-hours hour fill24" style="display: inline-block; width: 75px;">
|
|
31
|
+
<option value="-1">hour</option>
|
|
32
|
+
</select>
|
|
33
|
+
:
|
|
34
|
+
<select class="node-input-scheduler-select-minutes minute fill60" style="display: inline-block; width: 75px;">
|
|
35
|
+
<option value="-1">min</option>
|
|
36
|
+
</select>
|
|
37
|
+
:
|
|
38
|
+
<select class="node-input-scheduler-select-seconds second fill60" style="display: inline-block; width: 75px;">
|
|
39
|
+
<option value="-1">sec</option>
|
|
40
|
+
</select>
|
|
41
|
+
</div>
|
|
42
|
+
<div style="margin-top: 8px;">
|
|
43
|
+
<div style="display: inline-block; width: 30px; margin-left: 10px;">
|
|
44
|
+
<i class="fa fa-envelope"></i>
|
|
45
|
+
</div>
|
|
46
|
+
<input type="text" class="message"/>
|
|
47
|
+
</div>
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<script type="text/javascript">
|
|
51
|
+
RED.nodes.registerType("smart_scheduler", {
|
|
52
|
+
category: "Smart Nodes",
|
|
53
|
+
paletteLabel: "Scheduler",
|
|
54
|
+
color: "#6EE2D9",
|
|
55
|
+
defaults: {
|
|
56
|
+
name: { value: "" },
|
|
57
|
+
enabled: { value: false },
|
|
58
|
+
schedules: {
|
|
59
|
+
value: [],
|
|
60
|
+
validate: function (v)
|
|
61
|
+
{
|
|
62
|
+
let schedules = [];
|
|
63
|
+
|
|
64
|
+
if (v.length < 1)
|
|
65
|
+
{
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** make a first pass and create extra schedules if off is before on
|
|
70
|
+
* this so we can calculate overlaps later on */
|
|
71
|
+
for (let i = 0; i < v.length; i++)
|
|
72
|
+
{
|
|
73
|
+
v[i].valid = true;
|
|
74
|
+
|
|
75
|
+
days = v[i].days;
|
|
76
|
+
hour = parseInt(v[i].hour, 10);
|
|
77
|
+
minute = parseInt(v[i].minute, 10);
|
|
78
|
+
second = parseInt(v[i].second, 10);
|
|
79
|
+
message = v[i].message;
|
|
80
|
+
|
|
81
|
+
schedules.push({
|
|
82
|
+
days: v[i].days,
|
|
83
|
+
hour: hour,
|
|
84
|
+
minute: minute,
|
|
85
|
+
second: second,
|
|
86
|
+
message: message,
|
|
87
|
+
n: i,
|
|
88
|
+
valid: true,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** validate schedules */
|
|
93
|
+
for (let i = 0; i < schedules.length; i++)
|
|
94
|
+
{
|
|
95
|
+
let s = schedules[i];
|
|
96
|
+
|
|
97
|
+
/** hours should be 0..23 */
|
|
98
|
+
if (s.hour < 0 || s.hour > 23)
|
|
99
|
+
{
|
|
100
|
+
v[s.n].valid = false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** minutes and seconds should be 0..59 */
|
|
104
|
+
if (s.minute < 0 || s.second < 0 || s.minute > 59 || s.second > 59)
|
|
105
|
+
{
|
|
106
|
+
v[s.n].valid = false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** if any are not valid return false */
|
|
111
|
+
for (i = 0; i < v.length; i++)
|
|
112
|
+
{
|
|
113
|
+
if (v[i].valid === false)
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return true;
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
inputs: 1,
|
|
122
|
+
outputs: 1,
|
|
123
|
+
icon: "font-awesome/fa-clock-o",
|
|
124
|
+
label: function ()
|
|
125
|
+
{
|
|
126
|
+
return this.name || "Scheduler";
|
|
127
|
+
},
|
|
128
|
+
oneditprepare: function ()
|
|
129
|
+
{
|
|
130
|
+
let node = this;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* prepare schedule
|
|
134
|
+
*/
|
|
135
|
+
let scheduleList = $("#node-scheduler-schedules");
|
|
136
|
+
|
|
137
|
+
scheduleList
|
|
138
|
+
.css("min-height", "147px")
|
|
139
|
+
.css("min-width", "350px")
|
|
140
|
+
.editableList({
|
|
141
|
+
sortable: true,
|
|
142
|
+
removable: true,
|
|
143
|
+
addItem: function (row, index, data)
|
|
144
|
+
{
|
|
145
|
+
schedule = data.schedule || false;
|
|
146
|
+
|
|
147
|
+
let template = $("script[data-template-scheduler-row]").html() || "";
|
|
148
|
+
$(row).append(template);
|
|
149
|
+
|
|
150
|
+
fillOptions(row, schedule);
|
|
151
|
+
scheduleResize();
|
|
152
|
+
},
|
|
153
|
+
removeItem: function (data)
|
|
154
|
+
{
|
|
155
|
+
scheduleResize();
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* pass all existing schedules to editableList or pass an empty one
|
|
161
|
+
*/
|
|
162
|
+
if (node.schedules.length == 0)
|
|
163
|
+
{
|
|
164
|
+
scheduleList.editableList("addItem");
|
|
165
|
+
}
|
|
166
|
+
else
|
|
167
|
+
{
|
|
168
|
+
for (let i = 0; i < node.schedules.length; i++)
|
|
169
|
+
{
|
|
170
|
+
let schedule = node.schedules[i];
|
|
171
|
+
scheduleList.editableList("addItem", { schedule: schedule, i: i });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* resize the editableList
|
|
177
|
+
*/
|
|
178
|
+
function scheduleResize()
|
|
179
|
+
{
|
|
180
|
+
height = 40 + scheduleList.editableList("length") * 135;
|
|
181
|
+
scheduleList.editableList("height", height);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* fill the controls with days/hours/minutes/seconds/message
|
|
186
|
+
* @param row
|
|
187
|
+
*/
|
|
188
|
+
function fillOptions(row, schedule)
|
|
189
|
+
{
|
|
190
|
+
schedule = schedule ? schedule : {
|
|
191
|
+
days: "",
|
|
192
|
+
hour: -1,
|
|
193
|
+
minute: -1,
|
|
194
|
+
second: -1,
|
|
195
|
+
message: '{"topic": ""}'
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// Fill days list
|
|
199
|
+
row.find(".days")
|
|
200
|
+
.typedInput({
|
|
201
|
+
types: [
|
|
202
|
+
{
|
|
203
|
+
default: "",
|
|
204
|
+
multiple: true,
|
|
205
|
+
options: [
|
|
206
|
+
{ value: "1", label: "Montag" },
|
|
207
|
+
{ value: "2", label: "Dienstag" },
|
|
208
|
+
{ value: "3", label: "Mittwoch" },
|
|
209
|
+
{ value: "4", label: "Donnerstag" },
|
|
210
|
+
{ value: "5", label: "Freitag" },
|
|
211
|
+
{ value: "6", label: "Samstag" },
|
|
212
|
+
{ value: "0", label: "Sonntag" }
|
|
213
|
+
],
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Fill hours
|
|
219
|
+
let hours = row.find(".fill24");
|
|
220
|
+
for (let i = 0; i < 24; i++)
|
|
221
|
+
{
|
|
222
|
+
i = pad(i);
|
|
223
|
+
hours.append($("<option></option>").val(i).text(i));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Fill minutes and seconds
|
|
227
|
+
let minsec = row.find(".fill60");
|
|
228
|
+
for (let i = 0; i < 60; i++)
|
|
229
|
+
{
|
|
230
|
+
i = pad(i);
|
|
231
|
+
minsec.append($("<option></option>").val(i).text(i));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
row.find(".message").typedInput({
|
|
235
|
+
type: "json",
|
|
236
|
+
types: ["json"]
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* set the value of the select boxes
|
|
241
|
+
*/
|
|
242
|
+
row.find(".days").typedInput("value", schedule.days);
|
|
243
|
+
row.find(".hour").val(schedule.hour);
|
|
244
|
+
row.find(".minute").val(schedule.minute);
|
|
245
|
+
row.find(".second").val(schedule.second);
|
|
246
|
+
row.find(".message").typedInput("value", schedule.message);
|
|
247
|
+
|
|
248
|
+
/** set error on the li */
|
|
249
|
+
if (schedule.valid === false)
|
|
250
|
+
{
|
|
251
|
+
row.closest("li").css("background-color", "#f9b1bf");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** modify some css */
|
|
255
|
+
row.parent().find(".red-ui-editableList-item-handle").css("color", "black").css("left", "10px");
|
|
256
|
+
row.parent().find(".red-ui-editableList-item-remove").css("right", "10px");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* prepend string with 0
|
|
261
|
+
* @param s
|
|
262
|
+
* @returns {string|*}
|
|
263
|
+
*/
|
|
264
|
+
function pad(s)
|
|
265
|
+
{
|
|
266
|
+
s = s.toString();
|
|
267
|
+
if (s.length < 2)
|
|
268
|
+
{
|
|
269
|
+
s = "0".concat(s);
|
|
270
|
+
}
|
|
271
|
+
return s;
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
oneditsave: function ()
|
|
276
|
+
{
|
|
277
|
+
let node = this;
|
|
278
|
+
node.schedules = [];
|
|
279
|
+
|
|
280
|
+
let scheduleList = $("#node-scheduler-schedules");
|
|
281
|
+
let schedules = scheduleList.editableList("items");
|
|
282
|
+
|
|
283
|
+
schedules.each(function (i)
|
|
284
|
+
{
|
|
285
|
+
let days = this.find(".days").val();
|
|
286
|
+
let hour = this.find(".hour").val();
|
|
287
|
+
let minute = this.find(".minute").val();
|
|
288
|
+
let second = this.find(".second").val();
|
|
289
|
+
let message = this.find(".message").val();
|
|
290
|
+
|
|
291
|
+
let schedule = {
|
|
292
|
+
days: days,
|
|
293
|
+
hour: hour,
|
|
294
|
+
minute: minute,
|
|
295
|
+
second: second,
|
|
296
|
+
message: message,
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
/** if any of the inputs was modified, save it */
|
|
300
|
+
if (days.length > 0 || hour != -1 || minute != -1 || second != -1 || message != "{}")
|
|
301
|
+
{
|
|
302
|
+
node.schedules.push(schedule);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
</script>
|
|
308
|
+
|
|
309
|
+
<script type="text/html" data-help-name="smart_scheduler">
|
|
310
|
+
<p>
|
|
311
|
+
Diese Node sendet zu den angegebenen Zeitpunkten die entsprechenden Nachrichten an den Ausgang.
|
|
312
|
+
<p>
|
|
313
|
+
Folgende topics werden akzeptiert:
|
|
314
|
+
<table>
|
|
315
|
+
<thead>
|
|
316
|
+
<tr>
|
|
317
|
+
<th>Topic</th>
|
|
318
|
+
<th>Beschreibung</th>
|
|
319
|
+
</tr>
|
|
320
|
+
</thead>
|
|
321
|
+
<tbody>
|
|
322
|
+
<tr>
|
|
323
|
+
<td><code>enable</code></td>
|
|
324
|
+
<td>Aktiviert den Scheduler.</td>
|
|
325
|
+
</tr>
|
|
326
|
+
<tr>
|
|
327
|
+
<td><code>disable</code></td>
|
|
328
|
+
<td>Deaktiviert den Scheduler.</td>
|
|
329
|
+
</tr>
|
|
330
|
+
<tr>
|
|
331
|
+
<td><code>set_state</code></td>
|
|
332
|
+
<td>Aktiviert den Scheduler, wenn <code>msg.payload = true</code> oder deaktiviert den Scheduler, wenn <code>msg.payload = false</code>.</td>
|
|
333
|
+
</tr>
|
|
334
|
+
</tbody>
|
|
335
|
+
</table>
|
|
336
|
+
</p>
|
|
337
|
+
</p>
|
|
338
|
+
</script>
|