Accurate countdown method?

hi.. does anyone know an existing node, or an effective way of making an accurate countdown timer ? i have some values in milliseconds (usually between 15000 and 40000) i need a way to start counting down from the payload input to 0 and have pretty good accuracy in milliseconds..

i have a method which someone on here helped me with about a 18 months ago.. it works OK for seconds but when i give it milliseconds its slow.. its a heartbeat node, followed by a change node with payload -1 expression then loops back on itself..

i know the debug window is slow and i dont expect that to keep up but i need the output of the node to be accurate. if anyone has an idea ? appreciate any help...

I wouldn't generally expect any Node.js based application to have ms accurate timing. That's because JavaScript is based primarily on a single loop thread. That loop has some really complex attachments as to what gets run when. That is compounded by it running on a general OS which also does not really have real-time capabilities.

The general guidance is that if you need realtime capabilities, you should use a suitable realtime OS on a dedicated microprocessor. ESP32's for example.

Maybe if you explained why you want such accurate timing, someone might be able to offer alterative ideas.

Can you show us this? You might be causing back pressure (filling the event loop).

Node and JavaScript is mostly event driven - continuous loops should definitely be avoided.

i might have overstated what i need.. i dont really need perfect accuracy but i need it to be somewhere close. the current method i have drifts out by 2 or 3 seconds over 20 second countdown which is too much.. if the timing was within 100 ms that would be ok..

what i am doing is trying to display a countdown timer in an OBS scene.. the reason i thought it might be possible to do it with accuracy is because i also have another flow with an OSC node talking to some audio software and it keeps the timeline counter from that very accurate..

here is the heartbeat loop thing i mentioned..

[
    {
        "id": "e1ef5728e9206b4a",
        "type": "tab",
        "label": "Flow 1",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "bfe690abb7f991fd",
        "type": "trigger",
        "z": "e1ef5728e9206b4a",
        "name": "heartbeat",
        "op1": "",
        "op2": "",
        "op1type": "nul",
        "op2type": "payl",
        "duration": "1",
        "extend": false,
        "overrideDelay": false,
        "units": "ms",
        "reset": "0",
        "bytopic": "all",
        "topic": "topic",
        "outputs": 1,
        "x": 600,
        "y": 280,
        "wires": [
            [
                "dcc79dbed557c3d2"
            ]
        ],
        "outputLabels": [
            "send \"fail\" if not feeded"
        ]
    },
    {
        "id": "dcc79dbed557c3d2",
        "type": "change",
        "z": "e1ef5728e9206b4a",
        "name": "minus 1",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "payload - 1",
                "tot": "jsonata"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 740,
        "y": 280,
        "wires": [
            [
                "bfe690abb7f991fd",
                "f8badec6ca1b948d"
            ]
        ]
    },
    {
        "id": "ea3b9988e49f747c",
        "type": "inject",
        "z": "e1ef5728e9206b4a",
        "name": "",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "3000",
        "payloadType": "num",
        "x": 430,
        "y": 280,
        "wires": [
            [
                "bfe690abb7f991fd"
            ]
        ]
    },
    {
        "id": "f8badec6ca1b948d",
        "type": "debug",
        "z": "e1ef5728e9206b4a",
        "name": "debug 119",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 910,
        "y": 280,
        "wires": []
    }
]

OK, not loaded the flow yet but I can see that you are using JSONata and that is a red flag - JSONata adds quite an overhead.

Personally, I would try to do this in a function node using timer function. That should give you something a lot more accurate.


Something like this:

let counter = 100

const i = setInterval(function() {
    node.send({payload: counter--})
    if (counter < 1) clearInterval(i)
}, 100)

You only have to trigger it once. It will send a message every 100ms until the counter reaches 0 and will then stop.

1 Like

You are never going to get 1 miliisecond heart beat countdown to work using JS or JSONata,.
The js function will take a few milliseconds to loop as other stuff needs to be done in the single thread.
You would be better handing it of to other hardware designed for millisecond processing.

just for info
Javascript payload-- (takes 4- 7 millisecond)
JSONata payload - 1 (takes 3 - 5 milliseconds)
on my device

that works well for what i need thanks.. much simpler than the loop, plus i needed to get the countdown times out of flow context so i already needed a function node for that anyway.. i tried it using with 1ms and it seems to be close enough for what i need even at that faster resolution.. is there any drawbacks in lowering the interval to 1, or should i leave it higher ? much appreciated...

no worries.. im still learning my way with this stuff so wasnt sure.. i overstated my expectations also.. i just need it to be close enough for a display to give the an indication of time remaining.. the problem was the heartbeat loop was off by too much..

I am wondering, do you need 3000 outputs from 3000 down to 0, or do you just need an output 3000ms after you click the inject node?

just an output 3000ms after the inject.. most of the times i have are actually between 15000 and 40000 so there is more countdown to be seen in those but yeah that's the idea... as long as it ends at roughly the right moment and has a reasonably convincing countdown that's the main thing..

Then just use the trigger node

[{"id":"bfe690abb7f991fd","type":"trigger","z":"e1ef5728e9206b4a","name":"heartbeat","op1":"","op2":"","op1type":"nul","op2type":"num","duration":"3000","extend":false,"overrideDelay":true,"units":"ms","reset":"0","bytopic":"all","topic":"topic","outputs":1,"x":440,"y":220,"wires":[["f8badec6ca1b948d"]],"outputLabels":["send \"fail\" if not feeded"]},{"id":"ea3b9988e49f747c","type":"inject","z":"e1ef5728e9206b4a","name":"","props":[{"p":"delay","v":"3000","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":250,"y":220,"wires":[["bfe690abb7f991fd"]]},{"id":"f8badec6ca1b948d","type":"debug","z":"e1ef5728e9206b4a","name":"debug 119","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"$millis()","targetType":"jsonata","statusVal":"","statusType":"auto","x":630,"y":220,"wires":[]}]

Will output after 3000 ms or what ever time you feed in.

[edit] corrected inject to have msg.delay

Only the pressure on the loop. If you were to kick multiples of these off or had other time-sensitive tasks, the faster you go, the larger the impact. However, because you aren't doing a heavy task in the handler, it shouldn't be too bad. Just don't be tempted to make the output message bigger.

Also important that you shouldn't attach multiple wires to the output as this forces Node-RED to do a deep clone of the message which is quite a slow process.

However, I would really question WHY? It isn't as though a person can handle outputs below 100-200 ms or so.

E1cid.. i need a countdown being output also and for it to stop at roughly the right time but i dont need it to be accurate to the millisecond with what is being displayed.. for example update every 100ms is OK..

TotallyInformation... cool thanks.. you're right for this purpose 100 refresh rate is fine, i'll leave it at that.. the main thing is it stops at the right time which is what i was missing previously..

1 Like

I was confused by your reply to my question.

Is this count down being displayed in a browser, as you could make the browser do the countdown, removing the workload from node red. This way your timing in node-red is more likely to be close to accurate. You send one message at start, to browser and one after 3000ms to do what is required.

E1cid... its being displayed in OBS scene which is just text being updated.. there might be other times where i need to do similar thing in a browser.. what is your suggestion for a browser countdown ?

TotallyInformation.. if i set the function to update at 100 and i have 20,000 milliseconds it takes 20,000 x 100 to do the countdown which is 100 times slower.. i can divide the 20,000 by 100 and then the speed is right but my number display is now 200 not 20,000..

first i thought i'll just use 1 in the function to count at the right speed and that shows the value in the display properly but.... id rather keep the display updating every 100 not 1 like you suggested.. do you know any way in the function to countdown from the full 20,000 but just show it in 100 intervals ? that would mean 200 outputs from the function rather than 20,000 (much better) but still give me the full 20,000 countdown in the display ?

EDIT: I tried the range node to keep the function at 100 and then map values back up to 20,000... that works OK.. if there is any way to map the range in the function also that would be cool to know, but otherwise this is working..

You would use similar setInterval() and Document: getElementById() method - Web APIs | MDN
to update the css element id per interval. there are many examples of js countdown timers on the web. How To Create a Countdown Timer

as to your other issue

let counter = msg.payload //30000

const i = setInterval(function() {
    node.send({payload: counter-=100})
    if (counter < 1) clearInterval(i)
}, 100)
1 Like

thanks, i'll check out the browser stuff... the function node is perfect now with the counter at 100 also.. appreciate the help..

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.