smart-nodes 0.3.36 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/README.md +35 -17
- package/central/central.html +27 -24
- package/central/central.js +84 -26
- package/central/locales/de-DE/central.html +10 -0
- package/central/locales/de-DE/central.json +14 -0
- package/central/locales/en-US/central.html +10 -0
- package/central/locales/en-US/central.json +14 -0
- package/compare/compare.html +64 -87
- package/compare/compare.js +69 -29
- package/compare/locales/de-DE/compare.html +36 -0
- package/compare/locales/de-DE/compare.json +35 -0
- package/compare/locales/en-US/compare.html +36 -0
- package/compare/locales/en-US/compare.json +35 -0
- package/counter/counter.html +70 -72
- package/counter/counter.js +43 -20
- package/counter/locales/de-DE/counter.html +48 -0
- package/counter/locales/de-DE/counter.json +21 -0
- package/counter/locales/en-US/counter.html +48 -0
- package/counter/locales/en-US/counter.json +21 -0
- package/delay/delay.html +30 -102
- package/delay/delay.js +63 -20
- package/delay/locales/de-DE/delay.html +71 -0
- package/delay/locales/de-DE/delay.json +19 -0
- package/delay/locales/en-US/delay.html +76 -0
- package/delay/locales/en-US/delay.json +19 -0
- package/forwarder/forwarder.html +11 -42
- package/forwarder/forwarder.js +59 -18
- package/forwarder/locales/de-DE/forwarder.html +32 -0
- package/forwarder/locales/de-DE/forwarder.json +15 -0
- package/forwarder/locales/en-US/forwarder.html +32 -0
- package/forwarder/locales/en-US/forwarder.json +15 -0
- package/heating-curve/heating-curve.html +10 -51
- package/heating-curve/heating-curve.js +38 -13
- package/heating-curve/locales/de-DE/heating-curve.html +38 -0
- package/heating-curve/locales/de-DE/heating-curve.json +12 -0
- package/heating-curve/locales/en-US/heating-curve.html +38 -0
- package/heating-curve/locales/en-US/heating-curve.json +12 -0
- package/hysteresis/hysteresis.html +49 -69
- package/hysteresis/hysteresis.js +94 -68
- package/hysteresis/locales/de-DE/hysteresis.html +36 -0
- package/hysteresis/locales/de-DE/hysteresis.json +27 -0
- package/hysteresis/locales/en-US/hysteresis.html +36 -0
- package/hysteresis/locales/en-US/hysteresis.json +27 -0
- package/light/light.html +250 -0
- package/{light-control/light-control.js → light/light.js} +151 -32
- package/light/locales/de-DE/light.html +149 -0
- package/light/locales/de-DE/light.json +24 -0
- package/light/locales/en-US/light.html +148 -0
- package/light/locales/en-US/light.json +24 -0
- package/logic/locales/de-DE/logic.html +12 -0
- package/logic/locales/de-DE/logic.json +26 -0
- package/logic/locales/en-US/logic.html +12 -0
- package/logic/locales/en-US/logic.json +26 -0
- package/logic/logic.html +48 -64
- package/logic/logic.js +63 -29
- package/long-press/locales/de-DE/long-press.html +5 -0
- package/long-press/locales/de-DE/long-press.json +13 -0
- package/long-press/locales/en-US/long-press.html +5 -0
- package/long-press/locales/en-US/long-press.json +13 -0
- package/{long-press-control/long-press-control.html → long-press/long-press.html} +10 -14
- package/long-press/long-press.js +163 -0
- package/mixing-valve/locales/de-DE/mixing-valve.html +65 -0
- package/mixing-valve/locales/de-DE/mixing-valve.json +19 -0
- package/mixing-valve/locales/en-US/mixing-valve.html +66 -0
- package/mixing-valve/locales/en-US/mixing-valve.json +19 -0
- package/mixing-valve/mixing-valve.html +27 -93
- package/mixing-valve/mixing-valve.js +87 -61
- package/multi-press/locales/de-DE/multi-press.html +5 -0
- package/multi-press/locales/de-DE/multi-press.json +12 -0
- package/multi-press/locales/en-US/multi-press.html +5 -0
- package/multi-press/locales/en-US/multi-press.json +12 -0
- package/{multi-press-control/multi-press-control.html → multi-press/multi-press.html} +9 -13
- package/{multi-press-control/multi-press-control.js → multi-press/multi-press.js} +53 -5
- package/package.json +7 -7
- package/persistence.js +1 -0
- package/scene/locales/de-DE/scene.html +105 -0
- package/scene/locales/de-DE/scene.json +21 -0
- package/scene/locales/en-US/scene.html +107 -0
- package/scene/locales/en-US/scene.json +20 -0
- package/{scene-control/scene-control.html → scene/scene.html} +30 -132
- package/{scene-control/scene-control.js → scene/scene.js} +76 -26
- package/scheduler/locales/de-DE/scheduler.html +30 -0
- package/scheduler/locales/de-DE/scheduler.json +21 -0
- package/scheduler/locales/en-US/scheduler.html +30 -0
- package/scheduler/locales/en-US/scheduler.json +21 -0
- package/scheduler/scheduler.html +34 -64
- package/scheduler/scheduler.js +85 -53
- package/shutter/locales/de-DE/shutter.html +127 -0
- package/shutter/locales/de-DE/shutter.json +11 -0
- package/shutter/locales/en-US/shutter.html +133 -0
- package/shutter/locales/en-US/shutter.json +11 -0
- package/{shutter-control/shutter-control.html → shutter/shutter.html} +7 -133
- package/{shutter-control/shutter-control.js → shutter/shutter.js} +116 -56
- package/shutter-complex/locales/de-DE/shutter-complex.html +120 -0
- package/shutter-complex/locales/de-DE/shutter-complex.json +20 -0
- package/shutter-complex/locales/en-US/shutter-complex.html +120 -0
- package/shutter-complex/locales/en-US/shutter-complex.json +20 -0
- package/{shutter-complex-control/shutter-complex-control.html → shutter-complex/shutter-complex.html} +36 -140
- package/shutter-complex/shutter-complex.js +578 -0
- package/smart_helper.js +52 -9
- package/statistic/locales/de-DE/statistic.html +10 -0
- package/statistic/locales/de-DE/statistic.json +29 -0
- package/statistic/locales/en-US/statistic.html +10 -0
- package/statistic/locales/en-US/statistic.json +29 -0
- package/statistic/statistic.html +38 -43
- package/statistic/statistic.js +57 -28
- package/text-exec/locales/de-DE/text-exec.html +18 -0
- package/text-exec/locales/de-DE/text-exec.json +7 -0
- package/text-exec/locales/en-US/text-exec.html +18 -0
- package/text-exec/locales/en-US/text-exec.json +7 -0
- package/text-exec/text-exec.html +9 -25
- package/text-exec/text-exec.js +43 -2
- package/light-control/light-control.html +0 -363
- package/long-press-control/long-press-control.js +0 -76
- package/shutter-complex-control/shutter-complex-control.js +0 -442
|
@@ -7,11 +7,25 @@ module.exports = function (RED)
|
|
|
7
7
|
const node = this;
|
|
8
8
|
RED.nodes.createNode(node, config);
|
|
9
9
|
|
|
10
|
+
|
|
11
|
+
// ###################
|
|
12
|
+
// # Class constants #
|
|
13
|
+
// ###################
|
|
14
|
+
const ADJUST_OPEN = 0;
|
|
15
|
+
const ADJUST_CLOSE = 1;
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
// #######################
|
|
19
|
+
// # Global help objects #
|
|
20
|
+
// #######################
|
|
10
21
|
const smart_context = require("../persistence.js")(RED);
|
|
11
22
|
const helper = require("../smart_helper.js");
|
|
12
23
|
|
|
13
|
-
|
|
14
|
-
|
|
24
|
+
|
|
25
|
+
// #####################
|
|
26
|
+
// # persistent values #
|
|
27
|
+
// #####################
|
|
28
|
+
var node_settings = helper.cloneObject({
|
|
15
29
|
enabled: config.enabled,
|
|
16
30
|
setpoint: config.setpoint,
|
|
17
31
|
off_mode: config.off_mode,
|
|
@@ -19,21 +33,46 @@ module.exports = function (RED)
|
|
|
19
33
|
last_position: null
|
|
20
34
|
}, smart_context.get(node.id));
|
|
21
35
|
|
|
22
|
-
|
|
36
|
+
|
|
37
|
+
// ##################
|
|
38
|
+
// # Dynamic config #
|
|
39
|
+
// ##################
|
|
23
40
|
let time_total = config.time_total;
|
|
24
41
|
let time_sampling = config.time_sampling;
|
|
25
42
|
|
|
26
|
-
|
|
43
|
+
|
|
44
|
+
// ##################
|
|
45
|
+
// # Runtime values #
|
|
46
|
+
// ##################
|
|
47
|
+
|
|
48
|
+
// Here the setInterval return value is stored which is used to recheck the valve position
|
|
27
49
|
let sampling_interval = null;
|
|
50
|
+
|
|
51
|
+
// Here the setTimeout return value is stored to stop the calibration time.
|
|
28
52
|
let calibration_timeout = null;
|
|
53
|
+
|
|
54
|
+
// Here the setTimeout return value is stored to stop the valve adjustment.
|
|
29
55
|
let changing_timeout = null;
|
|
30
|
-
|
|
56
|
+
|
|
57
|
+
// Store the direction of the adjustment
|
|
58
|
+
let adjusting = null;
|
|
59
|
+
|
|
60
|
+
// Stores the current temperature which is used to determine the adjusting direction
|
|
31
61
|
let current_temperature = null;
|
|
32
|
-
let changing_start_time = null;
|
|
33
62
|
|
|
63
|
+
// The start time of the adjustment to count the change
|
|
64
|
+
let adjusting_start_time = null;
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
// ###############
|
|
68
|
+
// # Node events #
|
|
69
|
+
// ###############
|
|
34
70
|
node.on("input", function (msg)
|
|
35
71
|
{
|
|
36
72
|
handleTopic(msg);
|
|
73
|
+
|
|
74
|
+
setStatus();
|
|
75
|
+
smart_context.set(node.id, node_settings);
|
|
37
76
|
});
|
|
38
77
|
|
|
39
78
|
node.on("close", function ()
|
|
@@ -53,14 +92,22 @@ module.exports = function (RED)
|
|
|
53
92
|
});
|
|
54
93
|
|
|
55
94
|
|
|
95
|
+
// #####################
|
|
96
|
+
// # Private functions #
|
|
97
|
+
// #####################
|
|
98
|
+
|
|
99
|
+
// This is the main function which handles all topics that was received.
|
|
56
100
|
let handleTopic = msg =>
|
|
57
101
|
{
|
|
58
102
|
let real_topic = helper.getTopicName(msg.topic);
|
|
103
|
+
|
|
104
|
+
if (real_topic == "set_state")
|
|
105
|
+
real_topic = (!!msg.payload) ? "enable" : "disable";
|
|
106
|
+
|
|
59
107
|
switch (real_topic)
|
|
60
108
|
{
|
|
61
109
|
case "enable":
|
|
62
110
|
node_settings.enabled = true;
|
|
63
|
-
smart_context.set(node.id, node_settings);
|
|
64
111
|
|
|
65
112
|
stopChanging();
|
|
66
113
|
startSampling();
|
|
@@ -68,37 +115,20 @@ module.exports = function (RED)
|
|
|
68
115
|
|
|
69
116
|
case "disable":
|
|
70
117
|
node_settings.enabled = false;
|
|
71
|
-
smart_context.set(node.id, node_settings);
|
|
72
118
|
|
|
73
119
|
stopSampling();
|
|
74
120
|
doOffMode();
|
|
75
121
|
break;
|
|
76
122
|
|
|
77
|
-
case "set_state":
|
|
78
|
-
node_settings.enabled = !!msg.payload; // force boolean
|
|
79
|
-
smart_context.set(node.id, node_settings);
|
|
80
|
-
|
|
81
|
-
if (node_settings.enabled)
|
|
82
|
-
{
|
|
83
|
-
startSampling();
|
|
84
|
-
}
|
|
85
|
-
else
|
|
86
|
-
{
|
|
87
|
-
stopSampling();
|
|
88
|
-
doOffMode();
|
|
89
|
-
}
|
|
90
|
-
break;
|
|
91
|
-
|
|
92
123
|
case "setpoint":
|
|
93
124
|
let new_setpoint = parseFloat(msg.payload);
|
|
94
125
|
if (isNaN(new_setpoint) && !isFinite(new_setpoint))
|
|
95
126
|
{
|
|
96
|
-
|
|
127
|
+
console.warn("Invalid payload: " + msg.payload);
|
|
97
128
|
return;
|
|
98
129
|
}
|
|
130
|
+
|
|
99
131
|
node_settings.setpoint = new_setpoint;
|
|
100
|
-
smart_context.set(node.id, node_settings);
|
|
101
|
-
node.status({ fill: "yellow", shape: "ring", text: helper.getCurrentTimeForStatus() + ": New setpoint " + new_setpoint });
|
|
102
132
|
break;
|
|
103
133
|
|
|
104
134
|
case "off_mode":
|
|
@@ -108,11 +138,14 @@ module.exports = function (RED)
|
|
|
108
138
|
case "OPEN":
|
|
109
139
|
case "CLOSE":
|
|
110
140
|
node_settings.off_mode = msg.payload;
|
|
111
|
-
smart_context.set(node.id, node_settings);
|
|
112
141
|
|
|
113
142
|
if (!node_settings.enabled)
|
|
114
143
|
doOffMode();
|
|
115
144
|
break;
|
|
145
|
+
|
|
146
|
+
default:
|
|
147
|
+
console.warn("Invalid off_mode: " + msg.payload);
|
|
148
|
+
return;
|
|
116
149
|
}
|
|
117
150
|
break;
|
|
118
151
|
|
|
@@ -122,12 +155,13 @@ module.exports = function (RED)
|
|
|
122
155
|
case "HEATING":
|
|
123
156
|
case "COOLING":
|
|
124
157
|
node_settings.valve_mode = msg.payload;
|
|
125
|
-
smart_context.set(node.id, node_settings);
|
|
126
158
|
setStatus();
|
|
127
159
|
break;
|
|
160
|
+
|
|
161
|
+
default:
|
|
162
|
+
console.warn("Invalid valve_mode: " + msg.payload);
|
|
163
|
+
return;
|
|
128
164
|
}
|
|
129
|
-
node_settings.enabled = true;
|
|
130
|
-
smart_context.set(node.id, node_settings);
|
|
131
165
|
|
|
132
166
|
startSampling();
|
|
133
167
|
break;
|
|
@@ -136,11 +170,10 @@ module.exports = function (RED)
|
|
|
136
170
|
let new_temp = parseFloat(msg.payload);
|
|
137
171
|
if (isNaN(new_temp) && !isFinite(new_temp))
|
|
138
172
|
{
|
|
139
|
-
|
|
173
|
+
console.warn("Invalid payload for current_temperature: " + msg.payload);
|
|
140
174
|
return;
|
|
141
175
|
}
|
|
142
176
|
current_temperature = new_temp;
|
|
143
|
-
setStatus();
|
|
144
177
|
break;
|
|
145
178
|
|
|
146
179
|
case "calibrate":
|
|
@@ -148,7 +181,7 @@ module.exports = function (RED)
|
|
|
148
181
|
break;
|
|
149
182
|
|
|
150
183
|
default:
|
|
151
|
-
|
|
184
|
+
console.warn("Invalid topic: " + real_topic);
|
|
152
185
|
return;
|
|
153
186
|
}
|
|
154
187
|
}
|
|
@@ -208,55 +241,50 @@ module.exports = function (RED)
|
|
|
208
241
|
}
|
|
209
242
|
|
|
210
243
|
// calculate direction
|
|
211
|
-
let
|
|
244
|
+
let adjustAction = ADJUST_CLOSE;
|
|
212
245
|
if (current_temperature < node_settings.setpoint)
|
|
213
246
|
{
|
|
214
247
|
if (node_settings.valve_mode == "HEATING")
|
|
215
|
-
|
|
248
|
+
adjustAction = ADJUST_OPEN;
|
|
216
249
|
}
|
|
217
250
|
else
|
|
218
251
|
{
|
|
219
252
|
if (node_settings.valve_mode == "COOLING")
|
|
220
|
-
|
|
253
|
+
adjustAction = ADJUST_OPEN;
|
|
221
254
|
}
|
|
222
255
|
|
|
223
256
|
// start moving
|
|
224
|
-
startChanging(
|
|
225
|
-
|
|
226
|
-
setStatus();
|
|
257
|
+
startChanging(adjustAction, moving_time);
|
|
227
258
|
}
|
|
228
259
|
|
|
229
|
-
let startChanging = (
|
|
260
|
+
let startChanging = (adjustAction, time_ms) =>
|
|
230
261
|
{
|
|
231
262
|
stopChanging();
|
|
232
263
|
|
|
233
264
|
// Already oppened/closed
|
|
234
|
-
if (
|
|
265
|
+
if (adjustAction == ADJUST_OPEN && node_settings.last_position == 100)
|
|
235
266
|
return;
|
|
236
|
-
if (
|
|
267
|
+
if (adjustAction == ADJUST_CLOSE && node_settings.last_position == 0)
|
|
237
268
|
return;
|
|
238
269
|
|
|
239
|
-
|
|
240
|
-
if (
|
|
270
|
+
adjusting_start_time = Date.now();
|
|
271
|
+
if (adjustAction == ADJUST_OPEN)
|
|
241
272
|
node.send([{ payload: true }, { payload: false }, null]);
|
|
242
273
|
else
|
|
243
274
|
node.send([{ payload: false }, { payload: true }, null]);
|
|
244
275
|
|
|
245
|
-
|
|
246
|
-
fill: "yellow", shape: "ring", text: helper.getCurrentTimeForStatus() + ": Start " + (do_open ? "opening" : "closing") + " for " + helper.formatMsToStatus(time_ms)
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
changing_open = do_open;
|
|
276
|
+
adjusting = adjustAction;
|
|
250
277
|
changing_timeout = setTimeout(() =>
|
|
251
278
|
{
|
|
252
279
|
changing_timeout = null;
|
|
253
280
|
stopChanging();
|
|
281
|
+
setStatus();
|
|
254
282
|
}, time_ms);
|
|
255
|
-
|
|
256
283
|
}
|
|
257
284
|
|
|
258
285
|
let stopChanging = () =>
|
|
259
286
|
{
|
|
287
|
+
// Stop any runnting timeout
|
|
260
288
|
if (changing_timeout !== null)
|
|
261
289
|
{
|
|
262
290
|
clearTimeout(changing_timeout);
|
|
@@ -264,15 +292,15 @@ module.exports = function (RED)
|
|
|
264
292
|
}
|
|
265
293
|
|
|
266
294
|
// No changing in progress
|
|
267
|
-
if (
|
|
295
|
+
if (adjusting_start_time == null)
|
|
268
296
|
return;
|
|
269
297
|
|
|
270
298
|
// Calculate moved percentage
|
|
271
|
-
let time_passed = (Date.now() -
|
|
272
|
-
|
|
299
|
+
let time_passed = (Date.now() - adjusting_start_time) / 1000;
|
|
300
|
+
adjusting_start_time = null;
|
|
273
301
|
|
|
274
302
|
let changed_value = (time_passed / time_total) * 100; // calculate in % value (0-100)
|
|
275
|
-
if (
|
|
303
|
+
if (adjusting == ADJUST_OPEN)
|
|
276
304
|
node_settings.last_position += changed_value;
|
|
277
305
|
else
|
|
278
306
|
node_settings.last_position -= changed_value;
|
|
@@ -280,9 +308,6 @@ module.exports = function (RED)
|
|
|
280
308
|
// Only values from 0 to 100 are allowed
|
|
281
309
|
node_settings.last_position = Math.min(Math.max(node_settings.last_position, 0), 100);
|
|
282
310
|
|
|
283
|
-
// Save state
|
|
284
|
-
smart_context.set(node.id, node_settings);
|
|
285
|
-
|
|
286
311
|
node.send([{ payload: false }, { payload: false }, { payload: node_settings.last_position.toFixed(1) }]);
|
|
287
312
|
|
|
288
313
|
setStatus();
|
|
@@ -302,6 +327,7 @@ module.exports = function (RED)
|
|
|
302
327
|
|
|
303
328
|
// Calibration finished, start sampling if enabled
|
|
304
329
|
calibration_timeout = null;
|
|
330
|
+
|
|
305
331
|
if (node_settings.enabled)
|
|
306
332
|
startSampling();
|
|
307
333
|
else
|
|
@@ -309,8 +335,6 @@ module.exports = function (RED)
|
|
|
309
335
|
|
|
310
336
|
setStatus();
|
|
311
337
|
}, time_total * 1000);
|
|
312
|
-
|
|
313
|
-
setStatus();
|
|
314
338
|
}
|
|
315
339
|
|
|
316
340
|
let doOffMode = () =>
|
|
@@ -318,11 +342,11 @@ module.exports = function (RED)
|
|
|
318
342
|
switch (node_settings.off_mode)
|
|
319
343
|
{
|
|
320
344
|
case "OPEN":
|
|
321
|
-
startChanging(
|
|
345
|
+
startChanging(ADJUST_OPEN, time_total * 1000);
|
|
322
346
|
break;
|
|
323
347
|
|
|
324
348
|
case "CLOSE":
|
|
325
|
-
startChanging(
|
|
349
|
+
startChanging(ADJUST_CLOSE, time_total * 1000);
|
|
326
350
|
break;
|
|
327
351
|
|
|
328
352
|
case "NOTHING":
|
|
@@ -335,6 +359,8 @@ module.exports = function (RED)
|
|
|
335
359
|
{
|
|
336
360
|
if (calibration_timeout !== null)
|
|
337
361
|
node.status({ fill: "yellow", shape: "ring", text: helper.getCurrentTimeForStatus() + ": In calibration" });
|
|
362
|
+
else if (changing_timeout != null)
|
|
363
|
+
node.status({ fill: node_settings.enabled ? "green" : "red", shape: "ring", text: helper.getCurrentTimeForStatus() + ": " + (node_settings.valve_mode == "HEATING" ? "🔥" : "❄️") + " " + (adjusting == ADJUST_OPEN ? "Opening" : "Closing") + ", Set: " + node_settings.setpoint?.toFixed(1) + "°C, Cur: " + current_temperature?.toFixed(1) + "°C, Pos: " + node_settings.last_position?.toFixed(1) + "%" });
|
|
338
364
|
else
|
|
339
365
|
node.status({ fill: node_settings.enabled ? "green" : "red", shape: "dot", text: helper.getCurrentTimeForStatus() + ": " + (node_settings.valve_mode == "HEATING" ? "🔥" : "❄️") + " Set: " + node_settings.setpoint?.toFixed(1) + "°C, Cur: " + current_temperature?.toFixed(1) + "°C, Pos: " + node_settings.last_position?.toFixed(1) + "%" });
|
|
340
366
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<script type="text/html" data-help-name="smart_multi-press-control">
|
|
2
|
+
<p>Diese Node zählt mit, wie oft eine bestimmte Taste gedrückt wurde. Ein weiterer Tastendruck muss innerhalb der eingestellten Zeit erfolgen.</p>
|
|
3
|
+
<p>Wenn nach der eingestellten Zeit kein weiterer Tastendruck vorgenommen wird, so wird eine Nachricht mit dem eingestellen Inhalt an den entsprechenden Ausgang geschickt.</p>
|
|
4
|
+
<p>Ist die maximale Anzahl an Tastendrücken erreicht, wird sofort eine Nachricht rausgeschickt. Weitere Tastendrücke werden wieder von vorne gezählt und ausgewertet.</p>
|
|
5
|
+
</script>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"multi-press": {
|
|
3
|
+
"ui": {
|
|
4
|
+
"name": "Name",
|
|
5
|
+
"count_presses": "Anzahl Tastendrücke",
|
|
6
|
+
"count_presses_placeholder": "Max Anzahl an Tastendrücke",
|
|
7
|
+
"time_interval": "Zeitabstand",
|
|
8
|
+
"time_interval_placeholder": "Max Zeit in ms zwischen Tastendruck",
|
|
9
|
+
"output_messages": "Ausgangsnachrichten"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<script type="text/html" data-help-name="smart_multi-press-control">
|
|
2
|
+
<p>This node counts how often a certain key has been pressed. A further key press must be made within the set time.</p>
|
|
3
|
+
<p>If no further key press is made after the set time, a message with the set content is sent to the corresponding output.</p>
|
|
4
|
+
<p>If the maximum number of key presses is reached, a message is sent out immediately. Further key presses are counted and evaluated from the beginning again.</p>
|
|
5
|
+
</script>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"multi-press": {
|
|
3
|
+
"ui": {
|
|
4
|
+
"name": "Name",
|
|
5
|
+
"count_presses": "Number of keypresses",
|
|
6
|
+
"count_presses_placeholder": "Max number of keypresses",
|
|
7
|
+
"time_interval": "Time interval",
|
|
8
|
+
"time_interval_placeholder": "Max time in ms between keypresses",
|
|
9
|
+
"output_messages": "Output messages"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
},
|
|
26
26
|
oneditprepare: function ()
|
|
27
27
|
{
|
|
28
|
+
let node = this;
|
|
29
|
+
|
|
28
30
|
$("#node-input-outputs").spinner({
|
|
29
31
|
min: 2,
|
|
30
32
|
max: 4,
|
|
@@ -96,20 +98,20 @@
|
|
|
96
98
|
|
|
97
99
|
<script type="text/html" data-template-name="smart_multi-press-control">
|
|
98
100
|
<div class="form-row">
|
|
99
|
-
<label for="node-input-name"><i class="fa fa-tag"></i>
|
|
100
|
-
<input type="text" id="node-input-name"
|
|
101
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="multi-press.ui.name"></span></label>
|
|
102
|
+
<input type="text" id="node-input-name" data-i18n="[placeholder]multi-press.ui.name" />
|
|
101
103
|
</div>
|
|
102
104
|
<div class="form-row">
|
|
103
|
-
<label for="node-input-outputs" style="vertical-align: middle"><i class="fa fa-hand-o-up"></i>
|
|
104
|
-
<input id="node-input-outputs"
|
|
105
|
+
<label for="node-input-outputs" style="vertical-align: middle"><i class="fa fa-hand-o-up"></i> <span data-i18n="multi-press.ui.count_presses"></span></label>
|
|
106
|
+
<input id="node-input-outputs" data-i18n="[placeholder]multi-press.ui.count_presses_placeholder" placeholder="" />
|
|
105
107
|
</div>
|
|
106
108
|
<div class="form-row">
|
|
107
|
-
<label for="node-input-press_delay_ms"><i class="fa fa-clock-o"></i>
|
|
108
|
-
<input id="node-input-press_delay_ms"
|
|
109
|
+
<label for="node-input-press_delay_ms"><i class="fa fa-clock-o"></i> <span data-i18n="multi-press.ui.time_interval"></span></label>
|
|
110
|
+
<input id="node-input-press_delay_ms" data-i18n="[placeholder]multi-press.ui.time_interval_placeholder" placeholder="" />
|
|
109
111
|
<span> ms</span>
|
|
110
112
|
</div>
|
|
111
113
|
<hr/>
|
|
112
|
-
<h4 style="margin: 0.5rem 0;"
|
|
114
|
+
<h4 style="margin: 0.5rem 0;" data-i18n="multi-press.ui.output_messages"></h4>
|
|
113
115
|
<div class="form-row">
|
|
114
116
|
<label for="node-input-out1"><i class="fa fa-hand-o-up"></i> 1x</label>
|
|
115
117
|
<input type="text" id="node-input-out1" />
|
|
@@ -126,10 +128,4 @@
|
|
|
126
128
|
<label for="node-input-out4"><i class="fa fa-hand-o-up"></i> 4x</label>
|
|
127
129
|
<input type="text" id="node-input-out4" />
|
|
128
130
|
</div>
|
|
129
|
-
</script>
|
|
130
|
-
|
|
131
|
-
<script type="text/html" data-help-name="smart_multi-press-control">
|
|
132
|
-
<p>Diese Node zählt mit, wie oft eine bestimmte Taste gedrückt wurde. Ein weiterer Tastendruck muss innerhalb der eingestellten Zeit erfolgen.</p>
|
|
133
|
-
<p>Wenn nach der eingestellten Zeit kein weiterer Tastendruck vorgenommen wird, so wird eine Nachricht mit dem eingestellen Inhalt an den entsprechenden Ausgang geschickt.</p>
|
|
134
|
-
<p>Ist die maximale Anzahl an Tastendrücken erreicht, wird sofort eine Nachricht rausgeschickt. Weitere Tastendrücke werden wieder von vorne gezählt und ausgewertet.</p>
|
|
135
131
|
</script>
|
|
@@ -7,9 +7,27 @@ module.exports = function (RED)
|
|
|
7
7
|
const node = this;
|
|
8
8
|
RED.nodes.createNode(node, config);
|
|
9
9
|
|
|
10
|
+
|
|
11
|
+
// ###################
|
|
12
|
+
// # Class constants #
|
|
13
|
+
// ###################
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
// #######################
|
|
17
|
+
// # Global help objects #
|
|
18
|
+
// #######################
|
|
19
|
+
const smart_context = require("../persistence.js")(RED);
|
|
10
20
|
const helper = require("../smart_helper.js");
|
|
11
21
|
|
|
12
|
-
|
|
22
|
+
|
|
23
|
+
// #####################
|
|
24
|
+
// # persistent values #
|
|
25
|
+
// #####################
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
// ##################
|
|
29
|
+
// # Dynamic config #
|
|
30
|
+
// ##################
|
|
13
31
|
let press_delay_ms = parseInt(config.press_delay_ms || 200, 10);
|
|
14
32
|
let outs = [
|
|
15
33
|
helper.evaluateNodeProperty(RED, config.out1, "json"),
|
|
@@ -18,12 +36,21 @@ module.exports = function (RED)
|
|
|
18
36
|
helper.evaluateNodeProperty(RED, config.out4, "json")
|
|
19
37
|
];
|
|
20
38
|
|
|
21
|
-
|
|
39
|
+
|
|
40
|
+
// ##################
|
|
41
|
+
// # Runtime values #
|
|
42
|
+
// ##################
|
|
43
|
+
|
|
44
|
+
// Count the number of current presses
|
|
22
45
|
let count = 0;
|
|
46
|
+
|
|
47
|
+
// Here the setTimeout return value is stored to detect end of multipress.
|
|
23
48
|
let timeout = null;
|
|
24
49
|
|
|
25
|
-
node.status({});
|
|
26
50
|
|
|
51
|
+
// ###############
|
|
52
|
+
// # Node events #
|
|
53
|
+
// ###############
|
|
27
54
|
node.on("input", function (msg)
|
|
28
55
|
{
|
|
29
56
|
if (msg.payload)
|
|
@@ -39,6 +66,13 @@ module.exports = function (RED)
|
|
|
39
66
|
}
|
|
40
67
|
});
|
|
41
68
|
|
|
69
|
+
|
|
70
|
+
// #####################
|
|
71
|
+
// # Private functions #
|
|
72
|
+
// #####################
|
|
73
|
+
/**
|
|
74
|
+
* This function start/restarts the timer and increase the counter by one.
|
|
75
|
+
*/
|
|
42
76
|
let startTimeout = () =>
|
|
43
77
|
{
|
|
44
78
|
count++;
|
|
@@ -49,18 +83,32 @@ module.exports = function (RED)
|
|
|
49
83
|
timeout = null;
|
|
50
84
|
}
|
|
51
85
|
|
|
86
|
+
// If maximum presses are detected, send result immediately
|
|
87
|
+
// Otherwise start timeout
|
|
52
88
|
if (count == config.outputs)
|
|
53
89
|
sendResult();
|
|
54
90
|
else
|
|
55
|
-
timeout = setTimeout(
|
|
91
|
+
timeout = setTimeout(() =>
|
|
92
|
+
{
|
|
93
|
+
timeout = null;
|
|
94
|
+
sendResult();
|
|
95
|
+
}, press_delay_ms);
|
|
56
96
|
}
|
|
57
97
|
|
|
98
|
+
/**
|
|
99
|
+
* This function is called if the number of presses should be send to the output.
|
|
100
|
+
*/
|
|
58
101
|
let sendResult = () =>
|
|
59
102
|
{
|
|
60
103
|
node.status({ fill: "green", shape: "dot", text: helper.getCurrentTimeForStatus() + ": Last was press " + count + " time" + (count == 1 ? "" : "s") });
|
|
104
|
+
|
|
105
|
+
// Prepare data
|
|
61
106
|
let data = Array.from({ length: config.outputs }).fill(null);
|
|
62
|
-
|
|
107
|
+
// Send a copy of the defined json
|
|
108
|
+
data[count - 1] = helper.cloneObject(outs[count - 1]);
|
|
63
109
|
node.send(data);
|
|
110
|
+
|
|
111
|
+
// Reset values
|
|
64
112
|
count = 0;
|
|
65
113
|
timeout = null;
|
|
66
114
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smart-nodes",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Smart Nodes",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node-red",
|
|
@@ -34,12 +34,12 @@
|
|
|
34
34
|
"node-red": {
|
|
35
35
|
"version": ">=1.0.0",
|
|
36
36
|
"nodes": {
|
|
37
|
-
"light
|
|
38
|
-
"scene
|
|
39
|
-
"shutter
|
|
40
|
-
"shutter-complex
|
|
41
|
-
"long-press
|
|
42
|
-
"multi-press
|
|
37
|
+
"light": "light/light.js",
|
|
38
|
+
"scene": "scene/scene.js",
|
|
39
|
+
"shutter": "shutter/shutter.js",
|
|
40
|
+
"shutter-complex": "shutter-complex/shutter-complex.js",
|
|
41
|
+
"long-press": "long-press/long-press.js",
|
|
42
|
+
"multi-press": "multi-press/multi-press.js",
|
|
43
43
|
"logic": "logic/logic.js",
|
|
44
44
|
"forwarder": "forwarder/forwarder.js",
|
|
45
45
|
"compare": "compare/compare.js",
|
package/persistence.js
CHANGED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<script type="text/html" data-help-name="smart_scene-control">
|
|
2
|
+
<p>
|
|
3
|
+
Diese Node steuert mehrere Ausgänge die anhand einer definierten Szene ein- bzw. ausgeschaltet werden.
|
|
4
|
+
An jeden Ausgang wird immer <code>msg.payload = true</code> oder <code>msg.payload = false</code> gesendet um ihn ein-, bzw. auszuschalten.
|
|
5
|
+
</p>
|
|
6
|
+
<p>
|
|
7
|
+
<b>Hinweis:</b> Smart Nodes verwenden Topics im Format <code>name#nummer</code>, damit können verschiedene Smart Nodes mit dem gleichen Topic angesteuert werden.<br/>
|
|
8
|
+
Diese Node verwendet sowohl den Teil <code>name</code> als auch <code>nummer</code>. Je nach Nachricht müssen evtl. beide Werte gesetzt sein.
|
|
9
|
+
</p>
|
|
10
|
+
<p>
|
|
11
|
+
Folgende topics werden akzeptiert:
|
|
12
|
+
<table>
|
|
13
|
+
<thead>
|
|
14
|
+
<tr>
|
|
15
|
+
<th>Topic</th>
|
|
16
|
+
<th>Beschreibung</th>
|
|
17
|
+
</tr>
|
|
18
|
+
</thead>
|
|
19
|
+
<tbody>
|
|
20
|
+
<tr>
|
|
21
|
+
<td><code>status#2</code></td>
|
|
22
|
+
<td>
|
|
23
|
+
Gibt über <code>msg.payload = true</code> oder <code>msg.payload = false</code> den aktuellen Status des Ausgangs <code>2</code> an.<br/>
|
|
24
|
+
Bei einem Wechsel eines Ausgangs wird die Zeitmessung für die hinterlegte, bzw. mitgesendete Zeit gestartet, sofern vorhanden.
|
|
25
|
+
</td>
|
|
26
|
+
</tr>
|
|
27
|
+
<tr>
|
|
28
|
+
<td><code>on</code></td>
|
|
29
|
+
<td>Schaltet alle Ausgänge ein und startet die Zeitmessung für die hinterlegte, bzw. mitgesendete Zeit gestartet, sofern vorhanden.</td>
|
|
30
|
+
</tr>
|
|
31
|
+
<tr>
|
|
32
|
+
<td><code>off</code></td>
|
|
33
|
+
<td>Schaltet alle Ausgänge aus.</td>
|
|
34
|
+
</tr>
|
|
35
|
+
<tr>
|
|
36
|
+
<td><code>set</code></td>
|
|
37
|
+
<td>
|
|
38
|
+
Schaltet alle Ausgänge bei <code>msg.payload = true</code> ein und bei <code>msg.payload = false</code> aus.<br/>
|
|
39
|
+
Bei einem Wechsel von ausgeschaltet nach eingeschaltet wird die Zeitmessung für die hinterlegte, bzw. mitgesendete Zeit gestartet, sofern vorhanden.
|
|
40
|
+
</td>
|
|
41
|
+
</tr>
|
|
42
|
+
<tr>
|
|
43
|
+
<td><code>set_permanent</code></td>
|
|
44
|
+
<td>
|
|
45
|
+
Schaltet alle Ausgänge bei <code>msg.payload = true</code>dauerhaft ein und bei <code>msg.payload = false</code> aus.<br/>
|
|
46
|
+
Es wird dabei keine Zeitmessung gestartet.
|
|
47
|
+
</td>
|
|
48
|
+
</tr>
|
|
49
|
+
<tr>
|
|
50
|
+
<td><code>scene_x,y,z</code></td>
|
|
51
|
+
<td>
|
|
52
|
+
Schaltet die Ausgänge entsprechend der mitgegebenen Szenennummern (x, y und z) an, bzw. aus.<br/>
|
|
53
|
+
Beim Eintreffen der Nachricht wird geschaut welche Szene aktuell aktiv ist. Taucht die Nummer in der Liste auf, wird die nächste angegebene Szene aktiviert.<br/>
|
|
54
|
+
Taucht die Szene nicht in der Liste auf, wird die erste aufgelistete Szene aktiviert.<br/>
|
|
55
|
+
Wenn nur eine Szene angegeben ist, wechselt der Baustein zwischen der angegebenen Szene und aus.<br/>
|
|
56
|
+
Sollte mindestens ein Ausgang eingeschaltet sein, startet die Zeitmessung erneut.
|
|
57
|
+
</td>
|
|
58
|
+
</tr>
|
|
59
|
+
<tr>
|
|
60
|
+
<td><code>toggle</code> (default)</td>
|
|
61
|
+
<td>
|
|
62
|
+
Schaltet alle Ausgänge aus, falls mindestens einer bereits eingeschaltet wahr.<br/>
|
|
63
|
+
Sollten bereits alle Ausgänge ausgeschaltet sein, werden alle eingeschalten.
|
|
64
|
+
</td>
|
|
65
|
+
</tr>
|
|
66
|
+
</tbody>
|
|
67
|
+
</table>
|
|
68
|
+
</p>
|
|
69
|
+
<p>
|
|
70
|
+
Diese Node hat eine einstellbare Maximallaufzeit, bevor alle Ausgänge automatisch wieder ausgeschalten werden.
|
|
71
|
+
Diese Zeitmessung wird wie in der Tabelle oben verwendet.
|
|
72
|
+
Die eingestellte Zeit kann gezielt überschrieben werden.
|
|
73
|
+
Beispiel: <code>msg = { "topic": "on", "time_on": 5000 }</code> oder <code>msg = { "topic": "on", "time_on": "5s" }</code><br/>
|
|
74
|
+
Diese Nachricht schaltet das Licht für 5000 Millisekunden / 5 Sekunden an und anschließend wieder aus.
|
|
75
|
+
Die nächste Nachricht ohne <code>time_on</code> Angabe verwendet wieder die voreingestellte Zeit.
|
|
76
|
+
Ist die Zeit auf 0 eingestellt, wird das Licht <b>nicht</b> automatisch ausgeschalten.<br/>
|
|
77
|
+
Als Einheit für die Zeit können folgende Werte verwendet werden:
|
|
78
|
+
<table>
|
|
79
|
+
<thead>
|
|
80
|
+
<tr>
|
|
81
|
+
<th>Einheit</th>
|
|
82
|
+
<th>Beschreibung</th>
|
|
83
|
+
</tr>
|
|
84
|
+
</thead>
|
|
85
|
+
<tbody>
|
|
86
|
+
<tr>
|
|
87
|
+
<td><code>ms</code> (default)</td>
|
|
88
|
+
<td>Millisekunden</td>
|
|
89
|
+
</tr>
|
|
90
|
+
<tr>
|
|
91
|
+
<td><code>s</code> oder <code>sec</code></td>
|
|
92
|
+
<td>Sekunden</td>
|
|
93
|
+
</tr>
|
|
94
|
+
<tr>
|
|
95
|
+
<td><code>m</code> oder <code>min</code></td>
|
|
96
|
+
<td>Mintun.</td>
|
|
97
|
+
</tr>
|
|
98
|
+
<tr>
|
|
99
|
+
<td><code>h</code></td>
|
|
100
|
+
<td>Stunden</td>
|
|
101
|
+
</tr>
|
|
102
|
+
</tbody>
|
|
103
|
+
</table>
|
|
104
|
+
</p>
|
|
105
|
+
</script>
|