forked from linuxha/node-red-contrib-mytimeout
-
Notifications
You must be signed in to change notification settings - Fork 0
/
mytimeout.js
547 lines (481 loc) · 21.6 KB
/
mytimeout.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
// Author: Neil Cherry <[email protected]>
// I'd be much better off with a state machine
// https://www.smashingmagazine.com/2018/01/rise-state-machines/
// the on/off trigger string below whould be user configurable
//
// Inputs:
// msg.payload = {
// "payload": "on",
// "timeout": 3600,
// "warning": 300
// }
//
// The above can be used to change the timeout and warning
//
// or
//
// {
// "payload": "on"
// }
//
// {
// "payload": <Don't care>
// }
//
// The above gets treated as an on condition
//
// {
// "payload": "off"
// }
//
// {
// "payload": "off"
// }
//
// {
// "payload": "stop"
// }
//
// {
// "payload": "cancel"
// }
//
// {
// "payload": "tick"
// }
// Initial is stop
// STOP => RUN => STOP (via timeout)
module.exports = function(RED) {
"use strict";
/*
node-input-name
node-input-outtopic
node-input-outsafe
node-input-outwarning
node-input-warning
node-input-timer
node-input-limit
node-input-repeat
node-input-again
node-input-atStart
*/
var countdownNode = function(n) {
// Local variables
var node = this;
var state = 'stop';
var wflag = false;
var ticks = -1; //
var lastPayload = Date.now();; //
var timeout = parseInt(n.timer||30); //
var warn = parseInt(n.warning||10); //
var ignoreCase = '';
var line = {};
var version = '3.0.2'; // deploy incoming exception as on payload
RED.nodes.createNode(this, n);
// GUI variables
// @FIXME: Remove unused or unnecessary vars
node.timer = parseInt(n.timer||30); // Need to build checking into the html file
node.state = 'stop'; // For now it's a string, later an object?
/*
This is the Edit dialog for this node
node properties
Name Countdown timer
Output Topic topic <- if blank no output topic
Timer On payload on
Warning state payload Warning
Timer Off payload off
Countdown (secs) 30
Warning (secs) 5
Rate Limit (msg/secs) 30
[ ] Repeat message every second <- This doesn't seem like a good idea (???)
[ ] Auto-restart when timed out
[ ] Run at start
*/
node.name = n.name; // node-input-name - Name
node.warnT = parseInt(n.warning)||5;// node-input-warning - time in seconds (?)
node.topic = n.outtopic; // node-input-outtopic - Output topic
node.outsafe = n.outsafe || "on"; // node-input-outsafe - Timer on payload
node.outwarn = n.outwarning; // node-input-outwarning - Warning state payload
node.outunsafe = n.outunsafe || "off"; // node-input-outunsafe - Timer off payload
// // node-input-warning - warning seconds
// // node-input-timer - countdown seconds
// // node-input-repeat - Rate limit seconds
node.repeat = n.repeat; // node-input-repeat - Repeat message every second
node.again = n.again; // node-input-again - Auto restart when timed out
node.atStart = n.atStart; // node-input-atStart - Run at start
//
function ndebug(s){
// debug is either true or false
if(n.ndebug) { // only if true do we debug
node.log(s); // Can't use the obj this here, used obj node
}
}
if(n.ignoreCase) { // Only if true do we ignore case
ignoreCase = 'i';
}
// this.log("Debug = (" + n.ndebug + ")"); // Can't use the obj node here, use obj this
this.status({
fill : "red",
shape : "dot",
text : "Stopped: init"
});
// =====================================================================
// There can be:
// payload = "on" or 1 or "1" or anthing else except off/stop/cancel/debug
function on(msg) {
// My intention is to move all the calculations for
// these variables to here. At the moment they're all over
// the place and confusing
if(n.ndebug) {
ndebug(" msg: " + JSON.stringify(msg));
ndebug(" msg: " + typeof(msg));
ndebug(" node.timer: " + node.timer);
ndebug(" node.warnT: " + node.warnT);
try {
ndebug(" msg.timeout:" + msg.timeout);
ndebug(" msg.warning:" + msg.warning);
} catch(e) {
ndebug(" msg.timeout:undefined");
ndebug(" msg.warning:undefined");
}
ndebug(" timeout: " + timeout);
ndebug(" warn: " + warn);
ndebug(" ticks: " + ticks);
}
//
// There are 3 sets of variables
// default values (node.timer, node.warnT)
// passed values (timeout, warn - if any)
// running values (ticks)
//
timeout = msg.timeout||timeout||node.timer;
timeout = parseInt(timeout); // [email protected] parseInt()
warn = msg.warning||warn||node.warnT;
warn = parseInt(warn); // [email protected] parseInt()
ticks = timeout;
if(n.ndebug) {
ndebug("");
ndebug(" node.timer: " + node.timer);
ndebug(" node.warnT: " + node.warnT);
ndebug(" timeout: " + timeout);
ndebug(" warn: " + warn);
ndebug(" ticks: " + ticks);
ndebug("Count timer on");
}
node.status({
fill : "green",
shape : "dot",
text : "Running: " + ticks // provide a visual countdown
});
msg.payload = node.outsafe;
lastPayload = msg.payload;
ndebug("Send green: " + lastPayload);
node.send([msg, null]);
state = 'run';
wflag = false; // rest the warning flag
} // on(msg)
function off() {
ndebug("off!");
ticks = -1;
stop('off');
} // off()
// I'm about to make this a bit complicated
// In: off out: off
// In: stop out: stop
// In: cancel out: (nothing)
function stop(s) {
// The states are only called from the node.on("input", ...)
// That's where I define the line obj after I massage the incoming
// message
var msg = line; // This is a biy of a risk, I'll leave it for now
// if the main input routine calls this function, it will pass an
// object (the input msg) which we don't care about really
if(typeof(s) !== "string") {
ndebug("Empty stop");
s = 'stop';
}
ndebug(s + "! A");
ticks = 0;
node.status({
fill : "red",
shape : "dot",
text : "Stopped: " + s // provide a visual countdown
});
// Stop or off can send, not cancel
switch(s) {
case 'stop':
msg.payload = "stop";
lastPayload = msg.payload;
ndebug("Send red: " + lastPayload);
var tremain = {"payload": -1, "state": 0, "flag": "stop"};
node.send([msg, tremain]);
break;
case 'off':
msg.payload = node.outunsafe;
lastPayload = msg.payload;
ndebug("Send red: " + lastPayload);
var tremain = { "payload": 0, "state": 0, "flag": "off"};
node.send([msg, tremain]);
break;
case 'cancel':
ndebug("Send red: null");
var tremain = { "payload": -1, "state": 0, "flag": "cancel"};
lastPayload = Date.now();
node.send([null, tremain]);
break;
default:
ndebug("Send red: ???");
var tremain = { "payload": -1, "state": 0, "flag": "unknown"};
lastPayload = Date.now();
node.send([null, tremain]);
break;
}
state = 'stop';
ticks = -1;
timeout = parseInt(node.timer);
warn = parseInt(node.warnT);
ndebug('=[ fini ]=======================================================================');
}
function cancel() {
ndebug("cancel!");
stop('cancel');
ticks = -1;
}
function doNothing() {
ndebug("doNothing!");
state = 'stop';
ticks = -1;
}
// @FIXME: This should return the original msg with as few changes as possible
function newMsg(msg) {
var nMsg = Object.assign(msg, {});
// x console.log("msg = " + JSON.stringify(msg));
// x console.log("nMsg = " + JSON.stringify(nMsg));
switch(typeof(msg.payload)) {
case "string":
ndebug("Str msg = " + JSON.stringify(msg));
if(/.*"payload".*/.test(msg.payload)) {
ndebug(" msg.payload = " + JSON.stringify(msg));
// string contain payload, convery to JSON
//
// in = {"payload":"{ \"payload\":\"on\",\"timeout\":6,\"warning\":0}","qos":0}
// msg = {"payload":"{ \"payload\":\"on\",\"timeout\":6,\"warning\":0}","qos":0}
// nMsg = {"payload":"{ \"payload\":\"on\",\"timeout\":6,\"warning\":0}","qos":0}
// msg = {"payload":"on","timeout":6,"warning":0}
// nMsg = {"payload":"on","timeout":6,"warning":0}
// str msg = {"payload":"on","timeout":6,"warning":0}
// str msg = {"payload":"on","qos":0}
// out = {"payload":"on","qos":0}
//
// > obj1 = { "payload": 1, "qos": 0 };
// > obj2 = { "payload": "on", "timeout": 60 };
// > var result = Object.assign({},obj1, obj2);
// { payload: 'on', qos: 0, timeout: 60 }
//
// x console.log("str msg = " + JSON.stringify(msg));
var t = newMsg(JSON.parse(msg.payload));
//nMsg.payload = t.payload;
nMsg = Object.assign({}, msg, t);
} else {
// x console.log("Not a /.*\"payload\".*/")
//nMsgpayload = msg.payload;
}
// x console.log("str msg = " + JSON.stringify(nMsg));
// x console.log("typeof = " + typeof(nMsg));
// x console.log("str msg = " + JSON.stringify(nMsg.payload));
// x console.log("typeof = " + typeof(nMsg.payload));
// @FIXME: Need to also deal with timeout and warning
try{
nMsg.payload = nMsg.payload.toLowerCase();
} catch(e) {
nMsg.payload = nMsg.payload.toString().toLowerCase();
}
break;
case "number":
ndebug("Num msg = " + JSON.stringify(msg));
nMsg.payload = msg.payload.toString();
// x console.log("num msg = " + JSON.stringify(nMsg));
break;
case "object":
ndebug("Obj msg = " + JSON.stringify(msg));
/*
o1 = { "payload": "old", "_msgId": 1, "something": "else"};
o2 = { "payload": "on", "timeout": 60, "warning": 10};
Object.assign(o1, o2);
{ payload: 'on',
_msgId: 1,
something: 'else',
timeout: 60,
warning: 10 }
msg = { payload: [ 50, 20 ] };
{ payload: [ 50, 20 ] }
typeof(msg)
'object'
typeof(msg.payload)
'object' <-- This is an issue
*/
// x console.log("obj msg = " + JSON.stringify(msg));
//msg.payload = msg.payload.payload;
msg = Object.assign(msg, msg.payload);
t = newMsg(msg);
nMsg.payload = t.payload;
// x console.log("obj nmsg = " + JSON.stringify(nMsg));
break;
default:
ndebug("??? msg = " + JSON.stringify(msg));
ndebug("??? msg = " + typeof(msg.payload));
nMsg.payload = "";
// x console.log("??? msg = " + JSON.stringify(nMsg));
break;
}
ndebug("RTN msg = " + JSON.stringify(nMsg));
return(nMsg);
}
// Leave this here, need the functions ref from above
var states = {
// Not sure if this is what I want in the long run but this is good for now
stop: { 0: off, on: on, off: off, stop: doNothing, cancel: doNothing },
run: { 0: off, on: on, off: off, stop: stop, cancel: cancel }
};
// -------------------------------------------------------------------------------
// Commands
// TIX
node.on("TIX", function(inMsg) {
lastPayload = Date.now();
var msg = {};
if(ticks > 0) {
// A blank outwarning means don't send
if((warn >= ticks) && (node.outwarn !== "")) {
// a warn of 0 seconds also means don't send
if (warn) {
// Timer at warning
node.status({
fill : "yellow",
shape : "dot",
text : "Warning: " + ticks // provide a visual countdown
});
if(!wflag) {
msg.payload = node.outwarn;
lastPayload = msg.payload;
ndebug("Send yellow: " + lastPayload);
node.send([msg, null]);
wflag = true;
}
} // warn if there's a warn message
var tremain = { "payload": ticks, "state": 2, "flag": "warn >= ticks"};
node.send([null, tremain]);
} else {
// HOW does this get sent out?
node.status({
fill : "green",
shape : "dot",
text : "Running: " + ticks // provide a visual countdown
});
var tremain = { "payload": ticks, "state": 1, "flag": "ticks > 0"};
node.send([null, tremain]);
}
ticks--;
} else if(ticks == 0){
ndebug("ticks == 0");
stop("off");
//var tremain = { "payload": 0, "state": 0, "flag": "ticks == 0"}};
//node.send([null, tremain]);
ticks = -1;
} else {
// Do nothing
}
}); // node.on("TIX", function {});
// Stop (initial state at start up and when not running)
// On (timer reset to default value and running, on sent)
// Off (timer off, off sent, return to Stop)
// Cancel (timer off, nothing sent, return to Stop)
// Warning (timer still on, warning sent)
// Timeout (timer off, off sent, return to Stop
node.on( "input", function(inMsg) {
// inMsg = {"topic":"home/test/countdown-in-b","payload":"{ \"payload\":\"on\",\"timeout\":6,\"warning\":3}","qos":0,"retain":false,"_msgid":"10ea6e2f.68fb32"}
// inMsg = {"topic":"home/test/countdown-in-b","payload":"on","qos":0,"retain":false,"_msgid":"fd875a01.526a68"}
if(n.ndebug) {
ndebug('=[ input ]======================================================================');
ndebug('1 node.input("input");');
ndebug("1 inMsg = " + JSON.stringify(inMsg));
ndebug("1 State = " + state);
ndebug("1 timeout = " + node.timer + " - node.timer");
ndebug("1 timeout = " + timeout + " - timeout");
ndebug("1 warning = " + node.warnT);
}
// =================================================================
// First we need to drop any message than matches the last message
// sent. This will keep us from getting into an infinite loop.
// =================================================================
// From here to the try { } catch {} we should only need to get the
// inMsg's payload into line.payload (could be a string or object)
// This is taken from myTimeout.js (node-red-contrib-mytimeout)
// It's purposed is to stop from repeating the same message from
// being resent (an endless loop). For some reason this is on the
// isString() check ... hmmm
// =================================================================
// ================================================================================
// =========================================================
// Okay here's what I expect, user sends something (anything)
// node send node.outsafe,
// if node sees the exact string node.outstring just sent
// then drop it
// If the timer goes off then the lastPayload should be cleared
// We only send a simple message ('on', 'off', 'stop' or 'cancel')
var regex = new RegExp('^' + lastPayload + '$', ignoreCase);
//if(lastPayload === inMsg.payload) {
if(regex.test(inMsg.payload)) {
// So it's the same message as what was previously sent
ndebug("4 TO: In == Out match, skip (" + lastPayload + "/" + inMsg.payload + ")");
ndebug('=[ Skip ]=======================================================================');
lastPayload = Date.now();
return ; //
}
try {
line = newMsg(inMsg);
} catch (err) {
// Basically treat the unknown payload as an on (i.e. anything can
// tickle the timer as long as it's not off, 0, stop or cancel)
node.warn("L495: newMsg(inMsg): " + err + " <" + JSON.stringify(inMsg) + ">");
}
ndebug("line = " + JSON.stringify(line));
// ================================================================================
var s = "nada";
try {
if(typeof(line.payload) == 'string') {
s = ("1 states catch: line \"" + line.payload);
states[state][line.payload](line);
} else {
// Hopefully I've converted all the input to a string and
// to lower case.
s = ("2 states catch: line \"" + line.payload.payload);
states[state][line.payload.payload](line);
}
} catch(err) {
// =============================================================
// Anything that is not an existing state/function is treated as
// an on request.
// =============================================================
ndebug(s + " \" (this is not an error)");
ndebug("states catch: " + err + "(" + ticks + "/" + warn + " - this is not an error)");
// If it's not an existing state then treat it as an on
// that way anthing can be used as a kicker to keep the timer
// running
on(line);
}
// ================================================================================
}); // node.on("input", ... )
// Once the node is instantiated this keeps running
// I'd like to change this to run when only needed
var tick = setInterval(function() {
var msg = { payload:'TIX', topic:""};
node.emit("TIX", msg);
}, 1000); // trigger every 1 sec
node.on("close", function() {
if (tick) {
clearInterval(tick);
}
});
} // function myTimeoutNode(n);
RED.nodes.registerType("mytimeout", countdownNode);
} // module.exports