node-red-contrib-stoptimer-varidelay-plus 0.5.4
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 +201 -0
- package/README.md +197 -0
- package/examples/StopTimerVariDelay Examples.json +406 -0
- package/package.json +28 -0
- package/stoptimer-varidelay/cycle.js +182 -0
- package/stoptimer-varidelay/icons/stoptimer.png +0 -0
- package/stoptimer-varidelay/stoptimer-varidelay.html +230 -0
- package/stoptimer-varidelay/stoptimer-varidelay.js +627 -0
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Modifications copyright (C) 2020 hamsando
|
|
3
|
+
* Copyright jbardi
|
|
4
|
+
*
|
|
5
|
+
* Modifications copyright (C) 2025 mchristegh
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
* you may not use this file except in compliance with the License.
|
|
9
|
+
* You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
* See the License for the specific language governing permissions and
|
|
17
|
+
* limitations under the License.
|
|
18
|
+
*
|
|
19
|
+
**/
|
|
20
|
+
|
|
21
|
+
module.exports = function(RED) {
|
|
22
|
+
"use strict";
|
|
23
|
+
function StopTimerVariDelay(n) {
|
|
24
|
+
RED.nodes.createNode(this, n);
|
|
25
|
+
let fs = require('fs');
|
|
26
|
+
let path = require('path');
|
|
27
|
+
let nodefile = n.id.toString();
|
|
28
|
+
let nodepath = "";
|
|
29
|
+
require('./cycle.js');
|
|
30
|
+
|
|
31
|
+
if (n._alias != null) {
|
|
32
|
+
nodepath = n._flow.path.replace(/\//g, "-") + "-";
|
|
33
|
+
nodefile = n._alias;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const stvdtimersFile = path.join(RED.settings.userDir, "stvd-timers", nodepath + nodefile);
|
|
37
|
+
|
|
38
|
+
this.units = n.units || "Second";
|
|
39
|
+
this.durationType = n.durationType;
|
|
40
|
+
this.duration = isNaN(Number(RED.util.evaluateNodeProperty(n.duration, this.durationType, this, null))) ? 5 : Number(RED.util.evaluateNodeProperty(n.duration, this.durationType, this, null));
|
|
41
|
+
this.payloadval = n.payloadval || "0";
|
|
42
|
+
this.payloadtype = n.payloadtype || "num";
|
|
43
|
+
this.reporting = n.reporting || "none";
|
|
44
|
+
this.reportingformat = n.reportingformat || "human";
|
|
45
|
+
this.persist = n.persist || false;
|
|
46
|
+
this.ignoretimerpass = n.ignoretimerpass || false;
|
|
47
|
+
this.donotresettimer = n.donotresettimer || false;
|
|
48
|
+
|
|
49
|
+
if (this.duration <= 0) {
|
|
50
|
+
this.duration = 0;
|
|
51
|
+
} else {
|
|
52
|
+
if (this.units == "Second") {
|
|
53
|
+
this.duration = this.duration * 1000;
|
|
54
|
+
}
|
|
55
|
+
if (this.units == "Minute") {
|
|
56
|
+
this.duration = this.duration * 1000 * 60;
|
|
57
|
+
}
|
|
58
|
+
if (this.units == "Hour") {
|
|
59
|
+
this.duration = this.duration * 1000 * 60 * 60;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if ((this.payloadtype === "num") && (!isNaN(this.payloadval))) {
|
|
64
|
+
this.payloadval = Number(this.payloadval);
|
|
65
|
+
} else if (this.payloadval === 'true' || this.payloadval === 'false') {
|
|
66
|
+
let bValue = false;
|
|
67
|
+
if (this.payloadval === 'true') {
|
|
68
|
+
bValue = true;
|
|
69
|
+
}
|
|
70
|
+
this.payloadval = bValue;
|
|
71
|
+
} else if (this.payloadval == "null") {
|
|
72
|
+
this.payloadtype = 'null';
|
|
73
|
+
this.payloadval = null;
|
|
74
|
+
} else {
|
|
75
|
+
this.payloadval = String(this.payloadval);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let node = this;
|
|
79
|
+
|
|
80
|
+
let timeout = null;
|
|
81
|
+
let miniTimeout = null;
|
|
82
|
+
let countdown = null;
|
|
83
|
+
let stopped = false;
|
|
84
|
+
let paused = false;
|
|
85
|
+
let delayRemainingDisplay = 0;
|
|
86
|
+
let delayFactor = 1000;
|
|
87
|
+
let reporting = this.reporting;
|
|
88
|
+
let reportingformat = this.reportingformat;
|
|
89
|
+
|
|
90
|
+
const maxTimeout = 2147483647;
|
|
91
|
+
let actualDelayInUse = 0;
|
|
92
|
+
let actualDelayRemaining = 0;
|
|
93
|
+
|
|
94
|
+
let ignoredCount = 0;
|
|
95
|
+
let lastIgnoredTime = null;
|
|
96
|
+
let timerRunning = false;
|
|
97
|
+
let timerState = "stopped";
|
|
98
|
+
let timerStartTime = null;
|
|
99
|
+
let timerDuration = 0;
|
|
100
|
+
|
|
101
|
+
// Read the state from a persistent file
|
|
102
|
+
if (this.persist == true) {
|
|
103
|
+
try {
|
|
104
|
+
if (fs.existsSync(stvdtimersFile)) {
|
|
105
|
+
let savedState = JSON.retrocycle(JSON.parse(readState()));
|
|
106
|
+
let targetMS = (new Date(savedState.time.toString())).getTime();
|
|
107
|
+
let nowMS = (new Date).getTime();
|
|
108
|
+
this.reporting = savedState.reporting.toString();
|
|
109
|
+
if (typeof savedState.reportingformat !== 'undefined') {
|
|
110
|
+
this.reportingformat = savedState.reportingformat.toString();
|
|
111
|
+
} else {
|
|
112
|
+
this.reportingformat = "human";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (typeof savedState.ignoredCount !== 'undefined') {
|
|
116
|
+
ignoredCount = savedState.ignoredCount;
|
|
117
|
+
}
|
|
118
|
+
if (typeof savedState.lastIgnoredTime !== 'undefined' && savedState.lastIgnoredTime !== null) {
|
|
119
|
+
lastIgnoredTime = new Date(savedState.lastIgnoredTime);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (savedState.paused === true) {
|
|
123
|
+
// Restore as paused at the saved remaining time
|
|
124
|
+
let remainingMS = targetMS - nowMS;
|
|
125
|
+
if (remainingMS <= 0) {
|
|
126
|
+
remainingMS = (Math.floor((Math.random() * 5) + 3) * 1000);
|
|
127
|
+
}
|
|
128
|
+
delayRemainingDisplay = remainingMS;
|
|
129
|
+
timerDuration = typeof savedState.timerDuration !== 'undefined' ? savedState.timerDuration : remainingMS;
|
|
130
|
+
timerStartTime = new Date(nowMS - (timerDuration - remainingMS));
|
|
131
|
+
paused = true;
|
|
132
|
+
timerRunning = false;
|
|
133
|
+
timerState = "paused";
|
|
134
|
+
let statusObj = buildStatus(displayTime(delayRemainingDisplay, node.reportingformat), "paused");
|
|
135
|
+
node.status(statusObj);
|
|
136
|
+
} else {
|
|
137
|
+
if ((targetMS - nowMS) <= 3000) {
|
|
138
|
+
targetMS = (Math.floor((Math.random() * 5) + 3) * 1000);
|
|
139
|
+
} else {
|
|
140
|
+
targetMS = (Math.round((targetMS - nowMS) / 1000)) * 1000;
|
|
141
|
+
}
|
|
142
|
+
savedState.origmsg.units = "millisecond";
|
|
143
|
+
savedState.origmsg.delay = targetMS;
|
|
144
|
+
if (typeof savedState.timerDuration !== 'undefined') {
|
|
145
|
+
timerDuration = savedState.timerDuration;
|
|
146
|
+
}
|
|
147
|
+
handleInputEvent(savedState.origmsg);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} catch (error) {
|
|
151
|
+
this.error("Error processing persistent file data for stoptimer-varidelay node " + n.id.toString() + "\n\n" + error.toString());
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
deleteState();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
this.on("input", function(msg) {
|
|
158
|
+
handleInputEvent(msg);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
this.on("close", function(removed, done) {
|
|
162
|
+
if (timeout) {
|
|
163
|
+
clearTimeout(timeout);
|
|
164
|
+
}
|
|
165
|
+
if (countdown) {
|
|
166
|
+
clearInterval(countdown);
|
|
167
|
+
}
|
|
168
|
+
if (miniTimeout) {
|
|
169
|
+
clearTimeout(miniTimeout);
|
|
170
|
+
}
|
|
171
|
+
node.status({});
|
|
172
|
+
|
|
173
|
+
if (removed) {
|
|
174
|
+
deleteState();
|
|
175
|
+
}
|
|
176
|
+
done();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
function buildStatus(timeDisplay, state) {
|
|
180
|
+
if (state === "stopped" || state === "expired") {
|
|
181
|
+
if (node.donotresettimer) {
|
|
182
|
+
let lastStr = lastIgnoredTime ? formatIgnoredTime(lastIgnoredTime) : "--";
|
|
183
|
+
let stateLabel = state === "stopped" ? "Stopped" : "Expired";
|
|
184
|
+
return { fill: state === "stopped" ? "red" : "blue", shape: "ring", text: stateLabel + " | Ignored: " + ignoredCount + ", Last: " + lastStr };
|
|
185
|
+
} else {
|
|
186
|
+
if (state === "stopped") {
|
|
187
|
+
return { fill: "red", shape: "ring", text: "stopped" };
|
|
188
|
+
} else {
|
|
189
|
+
return { fill: "blue", shape: "square", text: "expired" };
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} else if (state === "paused") {
|
|
193
|
+
if (node.donotresettimer) {
|
|
194
|
+
let lastStr = lastIgnoredTime ? formatIgnoredTime(lastIgnoredTime) : "--";
|
|
195
|
+
return { fill: "yellow", shape: "ring", text: "Paused: " + timeDisplay + " | Ignored: " + ignoredCount + ", Last: " + lastStr };
|
|
196
|
+
} else {
|
|
197
|
+
return { fill: "yellow", shape: "ring", text: "Paused: " + timeDisplay };
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
if (node.donotresettimer) {
|
|
201
|
+
let lastStr = lastIgnoredTime ? formatIgnoredTime(lastIgnoredTime) : "--";
|
|
202
|
+
return { fill: "green", shape: "dot", text: "Remaining: " + timeDisplay + " | Ignored: " + ignoredCount + ", Last: " + lastStr };
|
|
203
|
+
} else {
|
|
204
|
+
return { fill: "green", shape: "dot", text: timeDisplay };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function formatIgnoredTime(date) {
|
|
210
|
+
let months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
|
|
211
|
+
let mon = months[date.getMonth()];
|
|
212
|
+
let day = String(date.getDate()).padStart(2,"0");
|
|
213
|
+
let hh = String(date.getHours()).padStart(2,"0");
|
|
214
|
+
let mm = String(date.getMinutes()).padStart(2,"0");
|
|
215
|
+
let ss = String(date.getSeconds()).padStart(2,"0");
|
|
216
|
+
return mon + " " + day + " " + hh + ":" + mm + ":" + ss;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function getElapsedTime() {
|
|
220
|
+
if (timerStartTime === null) return 0;
|
|
221
|
+
return (new Date()).getTime() - timerStartTime.getTime();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function handleInputEvent(msg) {
|
|
225
|
+
node.status({});
|
|
226
|
+
let delayUnits = node.units;
|
|
227
|
+
reporting = node.reporting;
|
|
228
|
+
|
|
229
|
+
// Handle pause
|
|
230
|
+
if (msg.payload == "pause" || msg.payload == "PAUSE") {
|
|
231
|
+
if (paused) {
|
|
232
|
+
// Already paused, send to output 4
|
|
233
|
+
let msg4 = RED.util.cloneMessage(msg);
|
|
234
|
+
msg4.remainingTime = delayRemainingDisplay;
|
|
235
|
+
msg4.timerState = timerState;
|
|
236
|
+
node.send([null, null, null, msg4]);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (timerRunning) {
|
|
240
|
+
clearTimeout(timeout);
|
|
241
|
+
clearTimeout(miniTimeout);
|
|
242
|
+
clearInterval(countdown);
|
|
243
|
+
timeout = null;
|
|
244
|
+
countdown = null;
|
|
245
|
+
miniTimeout = null;
|
|
246
|
+
paused = true;
|
|
247
|
+
timerRunning = false;
|
|
248
|
+
timerState = "paused";
|
|
249
|
+
writeState(msg);
|
|
250
|
+
let statusObj = buildStatus(displayTime(delayRemainingDisplay, reportingformat), "paused");
|
|
251
|
+
node.status(statusObj);
|
|
252
|
+
let msg2 = RED.util.cloneMessage(msg);
|
|
253
|
+
msg2.payload = "paused";
|
|
254
|
+
msg2.timerState = timerState;
|
|
255
|
+
msg2.timerDuration = timerDuration;
|
|
256
|
+
msg2.elapsedTime = getElapsedTime();
|
|
257
|
+
let msg3 = { payload: displayTime(delayRemainingDisplay, reportingformat), timerState: timerState, remainingTime: delayRemainingDisplay, timerDuration: timerDuration, elapsedTime: getElapsedTime() };
|
|
258
|
+
node.send([null, msg2, msg3, null]);
|
|
259
|
+
}
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Handle resume
|
|
264
|
+
if (msg.payload == "resume" || msg.payload == "RESUME") {
|
|
265
|
+
if (paused) {
|
|
266
|
+
paused = false;
|
|
267
|
+
timerRunning = true;
|
|
268
|
+
timerState = "running";
|
|
269
|
+
// Recalculate timerStartTime to account for the paused period
|
|
270
|
+
timerStartTime = new Date((new Date()).getTime() - (timerDuration - delayRemainingDisplay));
|
|
271
|
+
writeState(msg);
|
|
272
|
+
actualDelayRemaining = delayRemainingDisplay;
|
|
273
|
+
if (actualDelayRemaining > maxTimeout) {
|
|
274
|
+
actualDelayInUse = maxTimeout;
|
|
275
|
+
actualDelayRemaining = actualDelayRemaining - maxTimeout;
|
|
276
|
+
} else {
|
|
277
|
+
actualDelayInUse = actualDelayRemaining;
|
|
278
|
+
actualDelayRemaining = 0;
|
|
279
|
+
}
|
|
280
|
+
timeout = setTimeout(timerElapsed, actualDelayInUse, msg);
|
|
281
|
+
let statusObj = buildStatus(displayTime(delayRemainingDisplay, reportingformat), "running");
|
|
282
|
+
node.status(statusObj);
|
|
283
|
+
let msg2 = RED.util.cloneMessage(msg);
|
|
284
|
+
msg2.payload = "resumed";
|
|
285
|
+
msg2.timerState = timerState;
|
|
286
|
+
msg2.timerDuration = timerDuration;
|
|
287
|
+
msg2.elapsedTime = getElapsedTime();
|
|
288
|
+
let msg3 = { payload: displayTime(delayRemainingDisplay, reportingformat), timerState: timerState, remainingTime: delayRemainingDisplay, timerDuration: timerDuration, elapsedTime: getElapsedTime() };
|
|
289
|
+
node.send([null, msg2, msg3, null]);
|
|
290
|
+
|
|
291
|
+
// Restart reporting intervals if needed
|
|
292
|
+
if (reporting !== "none") {
|
|
293
|
+
if ((delayRemainingDisplay > 60000) && (reporting == "last_minute_seconds")) {
|
|
294
|
+
miniTimeout = setTimeout(function() {
|
|
295
|
+
if ((delayRemainingDisplay % 60000) != 0) {
|
|
296
|
+
delayRemainingDisplay = delayRemainingDisplay - (delayRemainingDisplay % 60000);
|
|
297
|
+
let msg3 = { payload: displayTime(delayRemainingDisplay, reportingformat), timerState: timerState, remainingTime: delayRemainingDisplay, timerDuration: timerDuration, elapsedTime: getElapsedTime() };
|
|
298
|
+
let statusObj = buildStatus(displayTime(delayRemainingDisplay, reportingformat), "running");
|
|
299
|
+
node.status(statusObj);
|
|
300
|
+
node.send([null, null, msg3, null]);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (delayRemainingDisplay <= 60000) {
|
|
304
|
+
countdown = setInterval(function() {
|
|
305
|
+
delayRemainingDisplay = delayRemainingDisplay - 1000;
|
|
306
|
+
let msg3 = { payload: displayTime(delayRemainingDisplay, reportingformat), timerState: timerState, remainingTime: delayRemainingDisplay, timerDuration: timerDuration, elapsedTime: getElapsedTime() };
|
|
307
|
+
let statusObj = buildStatus(displayTime(delayRemainingDisplay, reportingformat), "running");
|
|
308
|
+
node.status(statusObj);
|
|
309
|
+
node.send([null, null, msg3, null]);
|
|
310
|
+
}, 1000);
|
|
311
|
+
} else {
|
|
312
|
+
countdown = setInterval(function() {
|
|
313
|
+
if (delayRemainingDisplay > 60000) {
|
|
314
|
+
delayRemainingDisplay = delayRemainingDisplay - 60000;
|
|
315
|
+
let msg3 = { payload: displayTime(delayRemainingDisplay, reportingformat), timerState: timerState, remainingTime: delayRemainingDisplay, timerDuration: timerDuration, elapsedTime: getElapsedTime() };
|
|
316
|
+
let statusObj = buildStatus(displayTime(delayRemainingDisplay, reportingformat), "running");
|
|
317
|
+
node.status(statusObj);
|
|
318
|
+
node.send([null, null, msg3, null]);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (delayRemainingDisplay <= 60000) {
|
|
322
|
+
clearInterval(countdown);
|
|
323
|
+
countdown = null;
|
|
324
|
+
countdown = setInterval(function() {
|
|
325
|
+
delayRemainingDisplay = delayRemainingDisplay - 1000;
|
|
326
|
+
let msg3 = { payload: displayTime(delayRemainingDisplay, reportingformat), timerState: timerState, remainingTime: delayRemainingDisplay, timerDuration: timerDuration, elapsedTime: getElapsedTime() };
|
|
327
|
+
let statusObj = buildStatus(displayTime(delayRemainingDisplay, reportingformat), "running");
|
|
328
|
+
node.status(statusObj);
|
|
329
|
+
node.send([null, null, msg3, null]);
|
|
330
|
+
}, 1000);
|
|
331
|
+
}
|
|
332
|
+
}, 60000);
|
|
333
|
+
}
|
|
334
|
+
miniTimeout = null;
|
|
335
|
+
}, delayRemainingDisplay % 60000);
|
|
336
|
+
} else {
|
|
337
|
+
countdown = setInterval(function() {
|
|
338
|
+
delayRemainingDisplay = delayRemainingDisplay - 1000;
|
|
339
|
+
let msg3 = { payload: displayTime(delayRemainingDisplay, reportingformat), timerState: timerState, remainingTime: delayRemainingDisplay, timerDuration: timerDuration, elapsedTime: getElapsedTime() };
|
|
340
|
+
let statusObj = buildStatus(displayTime(delayRemainingDisplay, reportingformat), "running");
|
|
341
|
+
node.status(statusObj);
|
|
342
|
+
node.send([null, null, msg3, null]);
|
|
343
|
+
}, 1000);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// While paused, any non-stop/resume message goes to output 4
|
|
351
|
+
if (paused && msg.payload !== "stop" && msg.payload !== "STOP") {
|
|
352
|
+
ignoredCount++;
|
|
353
|
+
lastIgnoredTime = new Date();
|
|
354
|
+
let statusObj = buildStatus(displayTime(delayRemainingDisplay, reportingformat), "paused");
|
|
355
|
+
node.status(statusObj);
|
|
356
|
+
let msg4 = RED.util.cloneMessage(msg);
|
|
357
|
+
msg4.remainingTime = delayRemainingDisplay;
|
|
358
|
+
msg4.timerState = timerState;
|
|
359
|
+
node.send([null, null, null, msg4]);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if(stopped === false || msg._timerpass !== true || node.ignoretimerpass === true) {
|
|
364
|
+
|
|
365
|
+
// If donotresettimer is on and the timer is already running, ignore the message
|
|
366
|
+
if (node.donotresettimer && timerRunning && msg.payload !== "stop" && msg.payload !== "STOP" && msg._timerpass !== true) {
|
|
367
|
+
ignoredCount++;
|
|
368
|
+
lastIgnoredTime = new Date();
|
|
369
|
+
let statusObj = buildStatus(displayTime(delayRemainingDisplay, reportingformat), "running");
|
|
370
|
+
node.status(statusObj);
|
|
371
|
+
let msg4 = RED.util.cloneMessage(msg);
|
|
372
|
+
msg4.remainingTime = delayRemainingDisplay;
|
|
373
|
+
msg4.timerState = timerState;
|
|
374
|
+
node.send([null, null, null, msg4]);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
stopped = false;
|
|
379
|
+
paused = false;
|
|
380
|
+
clearTimeout(timeout);
|
|
381
|
+
clearTimeout(miniTimeout);
|
|
382
|
+
clearInterval(countdown);
|
|
383
|
+
timeout = null;
|
|
384
|
+
countdown = null;
|
|
385
|
+
|
|
386
|
+
if (msg.payload == "stop" || msg.payload == "STOP") {
|
|
387
|
+
timerRunning = false;
|
|
388
|
+
timerState = "stopped";
|
|
389
|
+
let statusObj = buildStatus(null, "stopped");
|
|
390
|
+
node.status(statusObj);
|
|
391
|
+
stopped = true;
|
|
392
|
+
let msg2 = RED.util.cloneMessage(msg);
|
|
393
|
+
msg2.payload = "stopped";
|
|
394
|
+
msg2.timerState = timerState;
|
|
395
|
+
msg2.timerDuration = timerDuration;
|
|
396
|
+
msg2.elapsedTime = getElapsedTime();
|
|
397
|
+
deleteState();
|
|
398
|
+
ignoredCount = 0;
|
|
399
|
+
lastIgnoredTime = null;
|
|
400
|
+
node.send([null, msg2, msg2, null]);
|
|
401
|
+
} else {
|
|
402
|
+
msg._timerpass = true;
|
|
403
|
+
if (msg.units != null) {
|
|
404
|
+
if (msg.units.toLowerCase().includes("millisecond")) {
|
|
405
|
+
delayUnits = "Millisecond";
|
|
406
|
+
} else if (msg.units.toLowerCase().includes("second")) {
|
|
407
|
+
delayUnits = "Second";
|
|
408
|
+
} else if (msg.units.toLowerCase().includes("minute")) {
|
|
409
|
+
delayUnits = "Minute";
|
|
410
|
+
} else if (msg.units.toLowerCase().includes("hour")) {
|
|
411
|
+
delayUnits = "Hour";
|
|
412
|
+
} else {
|
|
413
|
+
node.warn("Unknown units in message, using node default: " + delayUnits);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (delayUnits == "Second") {
|
|
418
|
+
delayFactor = 1000;
|
|
419
|
+
} else if (delayUnits == "Minute") {
|
|
420
|
+
delayFactor = 1000 * 60;
|
|
421
|
+
} else if (delayUnits == "Hour") {
|
|
422
|
+
delayFactor = 1000 * 60 * 60;
|
|
423
|
+
} else {
|
|
424
|
+
delayFactor = 1;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if ((msg.delay != null) && (!isNaN(parseInt(msg.delay, 10)))) {
|
|
428
|
+
delayRemainingDisplay = msg.delay * delayFactor;
|
|
429
|
+
} else {
|
|
430
|
+
delayRemainingDisplay = node.duration;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
ignoredCount = 0;
|
|
434
|
+
lastIgnoredTime = null;
|
|
435
|
+
timerRunning = true;
|
|
436
|
+
timerState = "running";
|
|
437
|
+
timerStartTime = new Date();
|
|
438
|
+
timerDuration = delayRemainingDisplay;
|
|
439
|
+
|
|
440
|
+
writeState(msg);
|
|
441
|
+
actualDelayRemaining = delayRemainingDisplay;
|
|
442
|
+
if (actualDelayRemaining > maxTimeout) {
|
|
443
|
+
actualDelayInUse = maxTimeout;
|
|
444
|
+
actualDelayRemaining = actualDelayRemaining - maxTimeout;
|
|
445
|
+
} else {
|
|
446
|
+
actualDelayInUse = actualDelayRemaining;
|
|
447
|
+
actualDelayRemaining = 0;
|
|
448
|
+
}
|
|
449
|
+
timeout = setTimeout(timerElapsed, actualDelayInUse, msg);
|
|
450
|
+
|
|
451
|
+
if (reporting == "none") {
|
|
452
|
+
let statusObj = buildStatus(displayTime(delayRemainingDisplay, reportingformat), "running");
|
|
453
|
+
node.status(statusObj);
|
|
454
|
+
} else {
|
|
455
|
+
let statusObj = buildStatus(displayTime(delayRemainingDisplay, reportingformat), "running");
|
|
456
|
+
node.status(statusObj);
|
|
457
|
+
let msg3 = { payload: displayTime(delayRemainingDisplay, reportingformat), timerState: timerState, remainingTime: delayRemainingDisplay, timerDuration: timerDuration, elapsedTime: getElapsedTime() };
|
|
458
|
+
node.send([null, null, msg3, null]);
|
|
459
|
+
|
|
460
|
+
if ((delayRemainingDisplay > 60000) && (reporting == "last_minute_seconds")) {
|
|
461
|
+
miniTimeout = setTimeout(function() {
|
|
462
|
+
if ((delayRemainingDisplay % 60000) != 0) {
|
|
463
|
+
delayRemainingDisplay = delayRemainingDisplay - (delayRemainingDisplay % 60000);
|
|
464
|
+
let msg3 = { payload: displayTime(delayRemainingDisplay, reportingformat), timerState: timerState, remainingTime: delayRemainingDisplay, timerDuration: timerDuration, elapsedTime: getElapsedTime() };
|
|
465
|
+
let statusObj = buildStatus(displayTime(delayRemainingDisplay, reportingformat), "running");
|
|
466
|
+
node.status(statusObj);
|
|
467
|
+
node.send([null, null, msg3, null]);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (delayRemainingDisplay <= 60000) {
|
|
471
|
+
countdown = setInterval(function() {
|
|
472
|
+
delayRemainingDisplay = delayRemainingDisplay - 1000;
|
|
473
|
+
let msg3 = { payload: displayTime(delayRemainingDisplay, reportingformat), timerState: timerState, remainingTime: delayRemainingDisplay, timerDuration: timerDuration, elapsedTime: getElapsedTime() };
|
|
474
|
+
let statusObj = buildStatus(displayTime(delayRemainingDisplay, reportingformat), "running");
|
|
475
|
+
node.status(statusObj);
|
|
476
|
+
node.send([null, null, msg3, null]);
|
|
477
|
+
}, 1000);
|
|
478
|
+
} else {
|
|
479
|
+
countdown = setInterval(function() {
|
|
480
|
+
if (delayRemainingDisplay > 60000) {
|
|
481
|
+
delayRemainingDisplay = delayRemainingDisplay - 60000;
|
|
482
|
+
let msg3 = { payload: displayTime(delayRemainingDisplay, reportingformat), timerState: timerState, remainingTime: delayRemainingDisplay, timerDuration: timerDuration, elapsedTime: getElapsedTime() };
|
|
483
|
+
let statusObj = buildStatus(displayTime(delayRemainingDisplay, reportingformat), "running");
|
|
484
|
+
node.status(statusObj);
|
|
485
|
+
node.send([null, null, msg3, null]);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (delayRemainingDisplay <= 60000) {
|
|
489
|
+
clearInterval(countdown);
|
|
490
|
+
countdown = null;
|
|
491
|
+
countdown = setInterval(function() {
|
|
492
|
+
delayRemainingDisplay = delayRemainingDisplay - 1000;
|
|
493
|
+
let msg3 = { payload: displayTime(delayRemainingDisplay, reportingformat), timerState: timerState, remainingTime: delayRemainingDisplay, timerDuration: timerDuration, elapsedTime: getElapsedTime() };
|
|
494
|
+
let statusObj = buildStatus(displayTime(delayRemainingDisplay, reportingformat), "running");
|
|
495
|
+
node.status(statusObj);
|
|
496
|
+
node.send([null, null, msg3, null]);
|
|
497
|
+
}, 1000);
|
|
498
|
+
}
|
|
499
|
+
}, 60000);
|
|
500
|
+
}
|
|
501
|
+
miniTimeout = null;
|
|
502
|
+
}, delayRemainingDisplay % 60000);
|
|
503
|
+
} else {
|
|
504
|
+
countdown = setInterval(function() {
|
|
505
|
+
delayRemainingDisplay = delayRemainingDisplay - 1000;
|
|
506
|
+
let msg3 = { payload: displayTime(delayRemainingDisplay, reportingformat), timerState: timerState, remainingTime: delayRemainingDisplay, timerDuration: timerDuration, elapsedTime: getElapsedTime() };
|
|
507
|
+
let statusObj = buildStatus(displayTime(delayRemainingDisplay, reportingformat), "running");
|
|
508
|
+
node.status(statusObj);
|
|
509
|
+
node.send([null, null, msg3, null]);
|
|
510
|
+
}, 1000);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
} else {
|
|
515
|
+
node.status({ fill: "red", shape: "ring", text: "stopped" });
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function timerElapsed(msg) {
|
|
520
|
+
if (actualDelayRemaining == 0) {
|
|
521
|
+
clearInterval(countdown);
|
|
522
|
+
timerRunning = false;
|
|
523
|
+
timerState = "expired";
|
|
524
|
+
let statusObj = buildStatus(null, "expired");
|
|
525
|
+
node.status(statusObj);
|
|
526
|
+
|
|
527
|
+
if(stopped === false) {
|
|
528
|
+
let msg2 = RED.util.cloneMessage(msg);
|
|
529
|
+
let msg3 = { payload: displayTime(0, reportingformat), timerState: timerState, remainingTime: 0, timerDuration: timerDuration, elapsedTime: getElapsedTime() };
|
|
530
|
+
msg2.payload = node.payloadval;
|
|
531
|
+
msg2.timerState = timerState;
|
|
532
|
+
msg2.timerDuration = timerDuration;
|
|
533
|
+
msg2.elapsedTime = getElapsedTime();
|
|
534
|
+
msg.timerState = timerState;
|
|
535
|
+
msg.timerDuration = timerDuration;
|
|
536
|
+
msg.elapsedTime = getElapsedTime();
|
|
537
|
+
if (reporting == "none") {
|
|
538
|
+
msg3 = null;
|
|
539
|
+
}
|
|
540
|
+
deleteState();
|
|
541
|
+
ignoredCount = 0;
|
|
542
|
+
lastIgnoredTime = null;
|
|
543
|
+
node.send([msg, msg2, msg3, null]);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
timeout = null;
|
|
547
|
+
countdown = null;
|
|
548
|
+
miniTimeout = null;
|
|
549
|
+
} else if (actualDelayRemaining > maxTimeout) {
|
|
550
|
+
actualDelayInUse = maxTimeout;
|
|
551
|
+
actualDelayRemaining = actualDelayRemaining - maxTimeout;
|
|
552
|
+
} else {
|
|
553
|
+
actualDelayInUse = actualDelayRemaining;
|
|
554
|
+
actualDelayRemaining = 0;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
timeout = setTimeout(timerElapsed, actualDelayInUse, msg);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function displayTime(delayToDisplay, reportingformat) {
|
|
561
|
+
let timeToDisplay = "";
|
|
562
|
+
let hours, minutes, seconds;
|
|
563
|
+
|
|
564
|
+
delayToDisplay = delayToDisplay / 1000;
|
|
565
|
+
|
|
566
|
+
if (reportingformat == "seconds") {
|
|
567
|
+
timeToDisplay = delayToDisplay;
|
|
568
|
+
} else if (reportingformat == "minutes") {
|
|
569
|
+
timeToDisplay = delayToDisplay / 60;
|
|
570
|
+
} else if (reportingformat == "hours") {
|
|
571
|
+
timeToDisplay = delayToDisplay / 3600;
|
|
572
|
+
} else {
|
|
573
|
+
hours = String(Math.floor(delayToDisplay / 3600)).padStart(2, "0");
|
|
574
|
+
delayToDisplay %= 3600;
|
|
575
|
+
minutes = String(Math.floor(delayToDisplay / 60)).padStart(2, "0");
|
|
576
|
+
seconds = String(delayToDisplay % 60).padStart(2, "0");
|
|
577
|
+
timeToDisplay = hours + ":" + minutes + ":" + seconds;
|
|
578
|
+
}
|
|
579
|
+
return timeToDisplay;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function writeState(msg) {
|
|
583
|
+
if (node.persist == true) {
|
|
584
|
+
try {
|
|
585
|
+
if (!fs.existsSync(path.dirname(stvdtimersFile))) fs.mkdirSync(path.dirname(stvdtimersFile), { recursive: true });
|
|
586
|
+
let target = (new Date((new Date().getTime() + delayRemainingDisplay))).toISOString();
|
|
587
|
+
fs.writeFileSync(stvdtimersFile, JSON.stringify(JSON.decycle({
|
|
588
|
+
reporting: node.reporting,
|
|
589
|
+
reportingformat: node.reportingformat,
|
|
590
|
+
time: target,
|
|
591
|
+
origmsg: msg,
|
|
592
|
+
paused: paused,
|
|
593
|
+
timerDuration: timerDuration,
|
|
594
|
+
ignoredCount: ignoredCount,
|
|
595
|
+
lastIgnoredTime: lastIgnoredTime ? lastIgnoredTime.toISOString() : null
|
|
596
|
+
})));
|
|
597
|
+
} catch (error) {
|
|
598
|
+
node.error("Error writing persistent file for stoptimer-varidelay node " + node.id.toString() + "\n\n" + error.toString());
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function readState() {
|
|
604
|
+
let retVal = -1;
|
|
605
|
+
try {
|
|
606
|
+
let contents = fs.readFileSync(stvdtimersFile).toString();
|
|
607
|
+
if (typeof contents !== 'undefined') {
|
|
608
|
+
retVal = contents;
|
|
609
|
+
}
|
|
610
|
+
} catch (error) {
|
|
611
|
+
node.error("Error reading persistent file for stoptimer-varidelay node " + node.id.toString() + "\n\n" + error.toString());
|
|
612
|
+
}
|
|
613
|
+
return retVal;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function deleteState() {
|
|
617
|
+
try {
|
|
618
|
+
if (fs.existsSync(stvdtimersFile)) {
|
|
619
|
+
fs.unlinkSync(stvdtimersFile);
|
|
620
|
+
}
|
|
621
|
+
} catch (error) {
|
|
622
|
+
node.error("Error deleting persistent file for stoptimer-varidelay node " + node.id.toString() + "\n\n" + error.toString());
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
RED.nodes.registerType("stoptimer-varidelay", StopTimerVariDelay);
|
|
627
|
+
}
|