node-red-contrib-looptimer-advanced 1.1.0
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!
โฑ๏ธ Advanced Looptimer for Node-RED
A message loop with brains: repeat a msg on an interval, then stop, pause or resume it, and reconfigure everything per message.
Highlights
- ๐ Repeat a message once per interval (milliseconds to days) with an optional maximum loop count and maximum run time.
- ๐ Stop with a payload, โธ pause and โถ resume without losing the loop counter or the timeout budget.
- ๐งฉ Reconfigure per message with
msg.loop: interval, limits and commands, no redeploy needed. - ๐ฏ Exact counts: optionally make Max Loops mean the total number of messages, including a single call, and optionally skip the immediate first message.
- ๐ Loop counters:
msg.loopstatewith the count, the remaining loops and the elapsed time, and astatuscommand to ask for them. - ๐งต One loop per topic: run a separate loop for every
msg.topicin one node, or ignore new messages while a loop runs. - โฒ๏ธ Pause timeout: resume or stop automatically when a pause lasts too long.
- ๐ Typed settings: read the interval and the limits from
msg.,flow.,global.orenv. - ๐ฏ No drift: every tick is due at a fixed time after the start, also after a pause.
- ๐งช Tested and dependency free: plain timers, an automated test suite and CI on Node.js 18 โ 24.
- ๐ Backwards compatible: flows built with 0.0.x keep working unchanged, every new feature is opt-in.
Installation
Use Menu โ Manage palette โ Install and search for node-red-contrib-looptimer-advanced, or:
cd ~/.node-red
npm install node-red-contrib-looptimer-advanced
Requires Node-RED 3.0+ and Node.js 18+. Older runtimes can stay on 0.0.x.
Quick start
An example flow is included (Import โ Examples in the editor, or paste the file). In short:
- Wire an inject node into looptimer-advanced, set Loop Every to
1 Secondand Max Loops to10. - Output 1 emits your message immediately and then once per second; output 2 reports
max loops reached. - Send a message with payload
stopto the same node to end the loop early.
stateDiagram-v2
[*] --> Running: any message
Running --> Running: tick (output 1)
Running --> Paused: pause
Paused --> Running: resume
Running --> [*]: stop / max loops / timeout (output 2)
Paused --> [*]: stop
Running --> Running: new message restarts the loop
Outputs
| Output | Sends |
|---|---|
| 1 | The original message immediately (unless switched off), then one copy per loop. The first copy after a resume carries msg.pauseduration (ms). With Add the loop counters every copy carries msg.loopstate. |
| 2 | Status messages with these payloads: stopped, max loops reached, timeout, paused, resumed, status. After an automatic resume or stop msg.reason is pause timeout. |
Controlling the loop
| How | What it does |
|---|---|
msg.payload = "stop" (or "STOP") |
Stops the loop |
msg.payload = "pause" / "resume" / "status" |
Pauses / resumes / reports the state (enable Pause / resume / status with payload in the node) |
msg.loop.command = "stop", "pause", "resume" or "status" |
Same, but always available and independent of the payload |
msg.loop.all = true |
With a command: applies to all loops of the node (see one loop per topic) |
| Any other message | Starts the loop, or restarts it (see New messages while a loop runs) |
Pausing freezes the remaining time of the current interval, the loop counter and the timeout budget. Resuming continues from exactly there. Stopping while paused works too.
Pause timeout
Set Pause Timeout (and After Timeout: resume or stop the loop) to get the loop going again, or end it, when a pause lasts too long. Output 2 then sends resumed / stopped with msg.reason = "pause timeout". Resuming or stopping by hand cancels the timeout.
Status command
A status command sends a message with payload status and msg.loopstate on output 2, for example to show the progress on a dashboard. When no loop is running msg.loopstate.state is idle.
Overriding settings with msg.loop
| Property | Type | Description |
|---|---|---|
msg.loop.duration |
number | Loop interval |
msg.loop.units |
string | Millisecond, Second, Minute, Hour, Day |
msg.loop.maxloops |
number | Maximum number of loops |
msg.loop.totalcount |
boolean | Max Loops counts the first message too |
msg.loop.maxtimeout |
number | Maximum run time of the loop |
msg.loop.maxtimeoutunits |
string | Same units as above |
msg.loop.sendfirst |
boolean | Send the first message immediately |
msg.loop.loopstate |
boolean | Add msg.loopstate to the messages |
msg.loop.pausetimeout |
number | Pause timeout (with msg.loop.pausetimeoutunits) |
msg.loop.pauseaction |
string | resume or stop, what happens after the pause timeout |
msg.loop.command |
string | stop, pause, resume or status |
msg.loop.all |
boolean | Apply a command to all loops |
// Function node: loop every 30 seconds, at most 10 times
msg.loop = { duration: 30, units: "Second", maxloops: 10 };
return msg;
Overrides stay in effect for the following messages (as they always did). In a function node create the object first: msg.loop = { maxloops: 5 };, never msg.loop.maxloops = 5 on its own.
How loops are counted
By default the first message is not part of the loop, so Max Loops 5 gives 6 messages: 1 original + 5 loops. This keeps compatibility with node-red-contrib-looptimer.
Tick Max Loops counts the first message too (or send msg.loop.totalcount = true) for exact counts:
| Max Loops | Default (legacy) | Exact count |
|---|---|---|
-1 |
loops until timeout or stop (the legacy way is 0) |
unlimited |
0 |
unlimited | no message at all |
1 |
2 messages | 1 message (a single call) |
5 |
6 messages | 5 messages |
Without Send the first message immediately (or msg.loop.sendfirst = false) nothing goes out at the start, the first message follows after the first interval. Max Loops 5 then gives 5 messages, and with counts the first message too it is the same number.
Loop counters (msg.loopstate)
Tick Add the loop counters as msg.loopstate (or send msg.loop.loopstate = true) and every message carries:
| Property | Meaning |
|---|---|
state |
running, paused, stopped, finished or idle |
count |
Loop messages sent, the first message not included |
sent |
All messages sent |
max, remaining |
Limit and loops left, null when unlimited |
elapsed |
Milliseconds the loop has been running, the time paused not included |
interval |
Loop interval in milliseconds |
New messages while a loop runs
The setting New message decides what a message does that arrives while a loop is running:
| Setting | Behavior |
|---|---|
| restarts the loop (default) | The loop starts again from the beginning, as it always did |
| is ignored while a loop runs | The message and its msg.loop settings are dropped until the loop is over. stop, pause, resume and status still work |
| starts a separate loop per msg.topic | Every topic has its own loop, a new message restarts only the loop of its topic. Commands apply to the loop of the topic of the command message, or to all loops with msg.loop.all = true. The node status shows the number of loops |
Typed settings
Loop Every, Max Loops and Max Timeout are typed inputs: next to a fixed number they can be read from msg., flow., global. or an environment variable (env). The value is read for every message that starts a loop, and has to be a number, otherwise the node reports an error and does not start. Overrides in msg.loop are applied on top.
Versioning
Since 1.0.0 the package follows Semantic Versioning and keeps a changelog in the Keep a Changelog format.
| Bump | Meaning |
|---|---|
patch 1.0.x |
Bug fixes only |
minor 1.x.0 |
New, backwards compatible features |
major x.0.0 |
Breaking changes (including raising the minimum Node-RED / Node.js version), always called out in the changelog |
Releases are tagged vX.Y.Z on GitHub. The old 0.0.x numbering was one increment per change and carried no compatibility promise. 1.0.0 marks the first release with a stable, tested public interface: the node's properties, msg.loop and the output payloads.
Upgrading
From 1.0.x to 1.1: nothing to change, every new setting is off by default and existing nodes keep their behavior. The only visible difference is that the ticks no longer drift: a long loop now sends its messages at fixed times after the start instead of a little later every time.
Upgrading from 0.0.x
Nothing to change in your flows. What is different:
- The
loopdependency is gone and the node runs on plain timers. Astopthat arrived between two iterations is no longer lost, and no timer is left running after stop or redeploy. - A loop that hits its maximum timeout now always reports
timeouton output 2 (before it sometimes ended silently). The old single-call workaround (maxloops: 0with a tinymaxtimeout) still works. - Invalid
msg.loopvalues are reported through normal error handling, so a Catch node sees them. - Minimum versions: Node-RED 3.0 and Node.js 18.
- A payload of
pause,resumeorstatusis only treated as a command when the option is ticked. Existing nodes have it off, so such payloads loop like any other value.
Contributing
Issues and pull requests are welcome.
git clone https://github.com/Haxiboy/node-red-contrib-looptimer-advanced.git
cd node-red-contrib-looptimer-advanced
npm install
npm test
Credits and license
Based on node-red-contrib-looptimer and node-red-contrib-looptimer2, with the addition of per-message overrides, pause/resume, loop counters, one loop per topic, typed settings and exact counting. Released under the Apache-2.0 license.