Date.now makes Node-RED crash

Hi,
I'm trying to run through a sequence of steps and update a timer for the user to see what time is left on each step. I came up with the code below, which I thought would be similar to what is used on Arduino micro-controllers with the millis function. The while loop seems to make Node-RED crash, particularly the date.now use from what I was able to determine. I guess this is because the date.now returns a big number too often. I stumbled upon the setInterval and setTimeout functions, but as they're non-blocking, I can't get my head around how I should use them instead. I'm not a JS expert, so I don't know what my options would be.

var sequence = msg.payload
var timer //Remaining time to send every minute
var previousTime
flow.set("sequence.stop.write", false, "memoryOnly") //Sequence is started

//Loop through sequence
for (var i = 0; i < sequence.length; i++) {
    timer = sequence[i].time //Initialize timer
    node.send(sequence[i]) //Send sequence step to instruments
    previousTime = Date.now() //Get date at which the step begins
    // Stop the step when "stop" is pressed or timer is 0
    while (flow.get("sequence.stop.write", "memoryOnly") != true || timer > 0) {
        if (Date.now() - previousTime >= 60000) { //Compares current time with previous
            previousTime = Date.now() //Defines new time to compare to
            timer = timer-- //decrement timer by 1 minute
            node.send(
                msg = {
                    "topic": "timer",
                    "time": timer
                    }   
                )
            }
    }
    if (flow.get("sequence.stop.write", "memoryOnly") == true) {
        break
    }
}
msg = {
    "topic": "stop"
}
flow.set("sequence.stop.write", true, "memoryOnly") //Sequence is stopped

return msg;

I'd be willing to take any idea on how to improve this code. Thanks in advance

This is a bad idea in an async system (node-js). You are blocking the event queue preventing the flow of messages.

You seem to be writing things in a c fashion - a common mistake for those new to node-red.

What you have written here can be achieved in a no-code/low-code manner using the built in nodes (inject, change, switch etc) - you should give that a go as it would help you get up to speed with the "low-code" way of doing things.

Also, since you are new to node-red, I recommend watching this playlist: Node-RED Essentials. The videos are done by the developers of node-red. They're nice & short and to the point. You will understand a whole lot more in about 1 hour. A small investment for a lot of gain.

Thank you for your reply. I'm not new to Node-RED but I expected this sequence handler to be easier to make in one go using the function node and what I know, and also not to overcharge the UI with a lot of nodes. Nevertheless, it's true that not being familiar with the underlying javascript which runs Node-RED might make things harder. I guess split, trigger and delay nodes would be involved in the answer to this problem. I would still be interested to know if there's a way to tackle this with the function node only.

As you suspected, setTimeout and setInterval are one way to doing this in a function node (if you must)

But it is easier to reset a delay node than to store the setInterval (so that it can be later cancelled). Additionally, your function would need to cater for the possibility of function re-entry and numerous other conditions to look out for.

You could also improve what you have now my adding an async sleep in the while

const sleep = ms => new Promise(r => setTimeout(r, ms));
// ...
while (...) {
  await sleep(sequence[i].time) // async sleep for xxx milliseconds
  if (stop) {
    break;
  }
  // do stuff
}

If its the overloaded canvas of nodes that puts you off, you can always make a subflow to minimise the screen footprint.

Thanks a lot. I think I will try both approches, I might have a few ideas to handle this with nodes, but I'm also willing to learn a bit more JS.

Although I cannot decipher what your function node is actually doing, it sounds like you can use the trigger node.

Hello,
So I've been experimenting a lot to try and make this sequence thing work with nodes, and I think I succeeded. Not as hard as I thought, just some deep thinking...

Might look complicated but actually not that much. I use the delay node to retain and send the steps at the right time. To do this, I use a function node to create a delay based on the time I want to wait at each step. The first must be 0 to be sent immediatly, the others need be the sum of the previous ones.

var delay = 0, previousTimer = 0
var sequenceVar = RED.util.cloneMessage(msg.payload) //Need clone to remove reference
//Add a delay property to send the first row immediatly in the delay node
//and to wait the right amount of time for next steps
for (var i = 0; i < sequenceVar.length; i++) {
    delay = delay + previousTimer
    sequenceVar[i].delay = delay
    previousTimer = sequenceVar[i].time
}
//Add last row to wait for the last step and go back to idle state
sequenceVar.push({
    "reactor" : 0,
    "saturator" : 0,
    "time" : 0,
    "delay": delay + previousTimer
})
msg.payload = sequenceVar
return msg;

The timer part currently uses a trigger node resending the time I must wait every minute. I use a counter to count how many times the trigger node is triggered and substract this counter to the original time. The sequence is stopped at the end or by the user by resetting the trigger and delay nodes.

I'm thinking of improvements :

  • Know the state of the sequence : idle, started, stopped, finished successfully
  • More precision on the timer (every second)
  • It might be interesting in my case to wait for the stabilisation of an instrument to start the timer

This seems actually easier than learning JS, but it needs peculiar way of thinking. Hope this can help someone.

1 Like

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