-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Haxiboy
committed
Oct 23, 2020
0 parents
commit 9c9bbb3
Showing
5 changed files
with
344 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
|
||
|
||
|
||
Advanced Looptimer for node-red | ||
|
||
|
||
|
||
---------------------------- | ||
|
||
|
||
|
||
This project is based on **node-red-contrib-looptimer** and **node-red-contrib-looptimer2** with the need of overwrite properties via messages. | ||
|
||
**Installation:** | ||
> npm install node-red-contrib-looptimer-advanced | ||
The main functionality is not changed (for now), however fix from looptimer2 has been implemented, because it did not work with other timers for example **node-red-contrib-stoptimer** and I've did some code rework for better functionality and cleaner code. | ||
|
||
|
||
|
||
|
||
|
||
Sends the `msg` through the first output, then continues to do so in a loop, once per the timer duration specified, until a payload of `stop` or `STOP` is received, at which time the second output will automatically send a payload of `stopped`. | ||
|
||
|
||
|
||
|
||
|
||
You can also stop the loop by specifying a maximum number of loops, which when reached, will stop the loop and timer, as well as sending a payload of `max loops reached` through the second output. Keep in mind, the first `msg` simply passes through, and is therefore not part of the loop. So if you set the max loops to `5`, you will get `6 messages`, which is 1 original message, and 5 messages from the loop. | ||
|
||
|
||
|
||
|
||
|
||
Finally, to ensure you do not end up with an infinite loop, you can set a maximum timeout in seconds, minutes or hours, and when that time is reached, the loop and timer will also be stopped. | ||
|
||
|
||
|
||
|
||
|
||
The properties can be overridden via `msg.loop` messages supplemented with property name. Accepted messages for loop are `msg.loop.duration` and `msg.loop.units`, for timeout are `msg.loop.maxtimeout` and `msg.loop.maxtimeoutunits`. | ||
|
||
|
||
|
||
You can define maximum number of loops with `msg.loop.maxloops` | ||
|
||
|
||
|
||
|
||
|
||
Setting the Max Loops and Max Timeout settings to high values can, for all intents, ensure that the loop can only be stopped by an incoming `stop` payload, however, the stability of the loop has not been tested over an extended number of hours. | ||
|
||
|
||
|
||
|
||
|
||
**0.0.1** - Initial Release | ||
|
||
|
||
|
||
|
||
|
||
**To Do:** | ||
|
||
- [ ] Mustache Template Support | ||
|
||
- [ ] Example Flows | ||
|
||
|
||
|
||
|
||
[](https://www.buymeacoffee.com/haxiboy) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
<script type="text/javascript"> | ||
RED.nodes.registerType('looptimer-advanced',{ | ||
category: 'function', | ||
color:"#33B2FF", | ||
defaults: { | ||
duration: {value:"5",required:true,validate:RED.validators.number()}, | ||
units: {value:"Second"}, | ||
maxloops: {value:"100",required:false,validate:RED.validators.number()}, | ||
maxtimeout: {value:"1",required:false,validate:RED.validators.number()}, | ||
maxtimeoutunits: {value:"Hour"}, | ||
name: {value:""} | ||
}, | ||
inputs:1, | ||
outputs:2, | ||
icon: "looptimer_advanced.png", | ||
label: function() { | ||
return this.name || this.duration + " " + this.units + " Loop"; | ||
}, | ||
labelStyle: function() { | ||
return this.name?"node_label_italic":""; | ||
}, | ||
outputLabels: ["original msg","stopped msg"] | ||
}); | ||
</script> | ||
|
||
<script type="text/x-red" data-template-name="looptimer-advanced"> | ||
<div class="form-row"> | ||
<label for="node-input-duration"><i class="fa fa-clock-o"></i> Loop Every</label> | ||
<input type="text" id="node-input-duration" style="text-align:end; width:70px !important"> | ||
<select id="node-input-units" style="width:140px !important"> | ||
<option value="Milisecond">Miliseconds</option> | ||
<option value="Second">Seconds</option> | ||
<option value="Minute">Minutes</option> | ||
<option value="Hour">Hours</option> | ||
<option value="Day">Days</option> | ||
</select> | ||
</div> | ||
<div class="form-row"> | ||
<label for="node-input-maxloops"><i class="fa fa-repeat"></i> Max Loops</label> | ||
<input type="text" id="node-input-maxloops" style="text-align:end; width:70px !important"> | ||
</div> | ||
<div class="form-row"> | ||
<label for="node-input-maxtimeout"><i class="fa fa-clock-o"></i> Max Timeout</label> | ||
<input type="text" id="node-input-maxtimeout" style="text-align:end; width:70px !important"> | ||
<select id="node-input-maxtimeoutunits" style="width:140px !important"> | ||
<option value="Milisecond">Miliseconds</option> | ||
<option value="Second">Seconds</option> | ||
<option value="Minute">Minutes</option> | ||
<option value="Hour">Hours</option> | ||
<option value="Day">Days</option> | ||
</select> | ||
</div> | ||
<div class="form-row"> | ||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> | ||
<input type="text" id="node-input-name" placeholder="Name"></input> | ||
</div> | ||
</script> | ||
|
||
<script type="text/x-red" data-help-name="looptimer-advanced"> | ||
<p>Sends the <code>msg</code> through the first output, then continues to do so in a loop, once per the timer duration specified, until a payload of <code>stop</code> or <code>STOP</code> is received, at which time the second output will automatically send a payload of <code>stopped</code>. | ||
<p>You can also stop the loop by specifying a maximum number of loops, which when reached, will stop the loop and timer, as well as sending a payload of <code>max loops reached</code> through the second output. Keep in mind, the first <code>msg</code> simply passes through, and is therefore not part of the loop. So if you set the max loops to <code>5</code>, you will get <code>6 messages</code>, which is 1 original message, and 5 messages from the loop.</p> | ||
<p>Finally, to ensure you do not end up with an infinite loop, you can set a maximum timeout in seconds, minutes or hours, and when that time is reached, the loop and timer will also be stopped.</p> | ||
<p>Properties can be overridden via <code>msg.loop</code> messages supplemented with property name.</p> | ||
<p>Accepted messages for loop are <code>msg.loop.duration</code> and <code>msg.loop.units</code></p> | ||
<p>For timeout you can use <code>msg.loop.maxtimeout</code> and <code>msg.loop.maxtimeoutunits</code></p> | ||
<p><code>NOTE:</code> Setting the Max Loops and Max Timeout settings to high values can, for all intents, ensure that the loop can only be stopped by an incoming <code>stop</code> payload, however, the stability of the loop has not been tested over an extended number of hours.</p> | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
module.exports = function (RED) { | ||
"use strict"; | ||
var Looper = require('loop'); | ||
|
||
var unitMap = [ | ||
{ unit: "Millisecond", multiplier: 1, duration: 1 }, | ||
{ unit: "Second", multiplier: 1000, get duration() { return unitMap[0].duration * unitMap[1].multiplier } }, | ||
{ unit: "Minute", multiplier: 60, get duration() { return unitMap[1].duration * unitMap[2].multiplier } }, | ||
{ unit: "Hour", multiplier: 60, get duration() { return unitMap[2].duration * unitMap[3].multiplier } }, | ||
{ unit: "Day", multiplier: 24, get duration() { return unitMap[3].duration * unitMap[4].multiplier } } | ||
] | ||
|
||
var CalcDuration = function (unit, duration) { return (unitMap.find(o => o.unit === unit).duration * duration); } | ||
function LoopTimer(n) { | ||
RED.nodes.createNode(this, n); | ||
|
||
this.units = n.units || "Second"; | ||
this.duration = n.duration || 5; | ||
this.maxloops = n.maxloops || 100; | ||
this.maxtimeoutunits = n.maxtimeoutunits || "Hour"; | ||
this.maxtimeout = n.maxtimeout || 1; | ||
this.nodeduration = n.nodeduration || 1; | ||
this.nodemaxtimeout = n.nodemaxtimeout || 1; | ||
|
||
if (this.duration <= 0) { | ||
this.duration = 0; | ||
} | ||
|
||
if (this.maxtimeout <= 0) { | ||
this.maxtimeout = 0; | ||
} | ||
|
||
var node = this; | ||
var loop = null; | ||
var timeout = null; | ||
var stopped = false; | ||
var cpmsg = null; | ||
var stopmsg = null; | ||
var mlmsg = null; | ||
|
||
this.on("input", function (msg) { | ||
loop = Looper(); | ||
if (msg.loop != undefined) { | ||
|
||
if (msg.loop.units != undefined) { | ||
if ((msg.loop.units === "Millisecond") || (msg.loop.units === "Second") || (msg.loop.units === "Hour") || (msg.loop.units === "Day")) { | ||
node.units = msg.loop.units; | ||
} else { | ||
node.status({ fill: "red", shape: "ring", text: "Invalid attribbute msg.loop.units" }); | ||
node.error('Invalid Attribbute: msg.loop.units, allowed values are Millisecond, Second, Minute, Hour, and Day'); | ||
return; | ||
} | ||
} | ||
|
||
|
||
if (msg.loop.duration != undefined) { | ||
if (typeof msg.loop.duration === 'number') { | ||
node.duration = msg.loop.duration; | ||
} else { | ||
node.status({ fill: "red", shape: "ring", text: "msg.loop.units must be an Integer" }); | ||
node.error('Invalid Attribbute: msg.loop.duration, value must be an Integer'); | ||
return; | ||
} | ||
} | ||
|
||
if (msg.loop.maxtimeoutunits != undefined) { | ||
if ((msg.loop.maxtimeoutunits === "Millisecond") || (msg.loop.maxtimeoutunits === "Second") || (msg.loop.maxtimeoutunits === "Hour") || (msg.loop.maxtimeoutunits === "Day")) { | ||
node.maxtimeoutunits = msg.loop.maxtimeoutunits; | ||
} else { | ||
node.status({ fill: "red", shape: "ring", text: "Invalid attribbute msg.loop.maxtimeoutunits" }); | ||
node.error('Invalid Attribbute: msg.loop.maxtimeoutunits, allowed values are Millisecond, Second, Minute, Hour, and Day'); | ||
return; | ||
} | ||
} | ||
|
||
|
||
if (msg.loop.maxtimeout != undefined) { | ||
if (typeof msg.loop.maxtimeout === 'number') { | ||
node.maxtimeout = msg.loop.maxtimeout; | ||
} else { | ||
node.status({ fill: "red", shape: "ring", text: "msg.loop.maxtimeout must be an Integer" }); | ||
node.error('Invalid Attribbute: msg.loop.maxtimeout, value must be an Integer'); | ||
return; | ||
} | ||
} | ||
if (msg.loop.maxloops != undefined) { | ||
if (typeof msg.loop.maxloops === 'number') { | ||
node.maxloops = msg.loop.maxloops; | ||
} else { | ||
node.status({ fill: "red", shape: "ring", text: "msg.loop.maxloops must be an Integer" }); | ||
node.error('Invalid Attribbute: msg.loop.maxloops, value must be an Integer'); | ||
return; | ||
} | ||
} | ||
|
||
|
||
|
||
} | ||
node.nodeduration = CalcDuration(node.units, node.duration); | ||
node.nodemaxtimeout = CalcDuration(node.maxtimeoutunits, node.maxtimeout); | ||
|
||
loop.setTimeout(node.nodemaxtimeout); | ||
loop.setMaxLoop(node.maxloops); | ||
node.status({}); | ||
|
||
if (stopped === false || msg._timerpass !== true) { | ||
stopped = false; | ||
clearTimeout(timeout); | ||
timeout = null; | ||
if (msg.payload == "stop" || msg.payload == "STOP") { | ||
node.status({ | ||
fill: "red", | ||
shape: "ring", | ||
text: "stopped" | ||
}); | ||
stopped = true; | ||
stopmsg = RED.util.cloneMessage(msg); | ||
stopmsg.payload = "stopped"; | ||
node.send([null, stopmsg]); | ||
stopped = false; | ||
} else { | ||
cpmsg = RED.util.cloneMessage(msg); | ||
node.send([cpmsg, null]); | ||
msg._timerpass = true; | ||
loop.run(function (next, err, data) { | ||
node.status({ | ||
fill: "green", | ||
shape: "dot", | ||
text: "running" | ||
}); | ||
data++; | ||
timeout = setTimeout(function () { | ||
if (stopped === false) { | ||
cpmsg = RED.util.cloneMessage(msg); | ||
if (data == node.maxloops) { | ||
mlmsg = RED.util.cloneMessage(msg); | ||
mlmsg.payload = "max loops reached"; | ||
node.send([cpmsg, mlmsg]); | ||
node.status({ | ||
fill: "red", | ||
shape: "ring", | ||
text: "max loops reached" | ||
}); | ||
timeout = null; | ||
next("break"); | ||
} else { | ||
node.status({}); | ||
if ((data * node.nodeduration) <= node.nodemaxtimeout) { | ||
node.send([cpmsg, null]); | ||
timeout = null; | ||
if (((data + 1) * node.nodeduration) > node.nodemaxtimeout) { | ||
next("break"); | ||
} else { | ||
next(undefined, data); | ||
} | ||
} else { | ||
timeout = null; | ||
next("break"); | ||
} | ||
} | ||
} else { | ||
timeout = null; | ||
next("break"); | ||
} | ||
}, node.nodeduration); | ||
}, 0); | ||
} | ||
} | ||
}); | ||
this.on("close", function () { | ||
stopped = false; | ||
if (timeout) { | ||
clearTimeout(timeout); | ||
} | ||
node.status({}); | ||
}); | ||
} | ||
RED.nodes.registerType("looptimer-advanced", LoopTimer); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
{ | ||
"name": "node-red-contrib-looptimer-advanced", | ||
"version": "0.0.1", | ||
"description": "Sends the msg through the first output, then continues to do so in a loop, once per the duration specified, until a payload of 'stop' or 'STOP' is received. Supports override with messages!", | ||
"main": "looptimer_advanced.js", | ||
"scripts": {}, | ||
"dependencies": { | ||
"loop": "2.1.2" | ||
}, | ||
"directories": {}, | ||
"keywords": [ | ||
"node-red", | ||
"loop", | ||
"timer", | ||
"advanced" | ||
], | ||
"author": { | ||
"name": "Haxiboy" | ||
}, | ||
"license": "Apache-2.0", | ||
"node-red": { | ||
"nodes": { | ||
"looptimer-advanced": "looptimer_advanced.js" | ||
} | ||
} | ||
} |