Interval in IF-loop is 'deaf' for injecting input

Can someone tell me how to get this to work, or if it is an error in Node-Red?

I am programming a countdown interval in a function node.

But inside the IF loop, the clearInterval does not want to be triggered by injecting msg.topic == '0'.

if (msg.topic == '1') {
    var counter = 10;
    var countdown = setInterval(function(){
        global.get('gInside');
        node.send({"payload":(counter)});
        counter--;

    if (msg.topic == '0') {
        clearInterval(countdown);
    }
    
    else if (counter <5) {
        clearInterval(countdown);
    }
    }, 1000)
}

Strangely it works if i remove the first IF line, but i need this functionality to extend its use.

I tried many other (global)variables, statements, etc.

Just inside the IF loop the Interval does not want to listen anymore to injected information.

One thing I can see:

if (msg.topic == '1') {
    var counter = 10;
    var countdown = setInterval(function(){
        global.get('gInside');
        node.send({"payload":(counter)});
        counter--;

    if (msg.topic == '0') {
        clearInterval(countdown);
    }
    
    else if (counter <5) {
        clearInterval(countdown);
    }
    }, 1000)
}

Line 1: if (msg.topic == '1')
That lives all the way through.
So if msg.topic == '1' then when it gets to line.... 8 (blanks included) it won't work.

Normal variables, such as your countdown variable, are recreated for each message received. So the countdown you clear when you get the clear message will not be the one containing the timer id.
Therefore your countdown variable needs to be stored in the node context and picked up again when the next message comes in. When you clear it, set it to zero so you won't try and clear it again.

[Edit] Also, looking at your code again, the section

should not be inside the setInterval function, it should be at the outer level. As @Trying_to_learn said, the topic will never change in there as that instance of the function only has access to the original message.

Thank you Trying_to_learn & Colin.

I get closer in understanding, but after many hours of searching it is still a mystery to me why it is not working.

The Function Node is now listening to the topic 1 & 0 input.
But once started with 1 it keeps counting to 5 and than stops.

If i first inject a 1 the countdown starts.

If 0 is injected while running it just shows 10 in between. But the original counting keeps going down.

I don't understand why:

  • The clearInterval from "if (counter <5) " stops the Interval.
  • The one from "if (msg.topic == '0')" does only restart an separate IF process putting out the number 10.
    But does not stop the countdown of the still running Interval.
var counter = 10;
var countdown = setInterval(function(){
    node.send({payload:(counter)});
   
    if (msg.topic == '1') {
        counter--;
    }
    
    if (counter <5) {
        clearInterval(countdown);
    }

    if (msg.topic == '0') {
        clearInterval(countdown);
    }
}, 1000)

Here the nodes exported:

[{"id":"9a551ca4.5b30b","type":"function","z":"d2d9018a.7d512","name":"Countdown","func":"var counter = 10;\n\nvar countdown = setInterval(function(){\n\n    node.send({payload:(counter)});\n    node.status({fill:\"green\",shape:\"ring\",text:(counter)});\n\n    if (msg.topic == '1') {\n        counter--;\n    }\n    \n    if (counter <5) {\n        clearInterval(countdown);\n        node.status({fill:\"red\",shape:\"dot\",text:'finished'});\n    }\n\n    if (msg.topic == '0') {\n        clearInterval(countdown);\n        node.status({fill:\"red\",shape:\"dot\",text:'stopped'});\n        \n    }\n}, 1000)","outputs":1,"noerr":0,"initialize":"","finalize":"","x":350,"y":120,"wires":[["4400ea00.ee4d34"]],"icon":"font-awesome/fa-hourglass-1"},{"id":"913ccad1.53a7e8","type":"inject","z":"d2d9018a.7d512","name":"1","props":[{"p":"topic","v":"1","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","x":180,"y":120,"wires":[["9a551ca4.5b30b"]],"icon":"node-red/arrow-in.svg"},{"id":"4400ea00.ee4d34","type":"debug","z":"d2d9018a.7d512","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":530,"y":120,"wires":[]},{"id":"b9f004b9.08bc68","type":"inject","z":"d2d9018a.7d512","name":"0","props":[{"p":"topic","v":"0","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","x":170,"y":180,"wires":[["9a551ca4.5b30b"]],"icon":"node-red/arrow-in.svg"}]

You are comparing numbers to strings.
msg.topic is send as a number, if (msg.topic == '0') { should be if (msg.topic === 0) {
same applies to 1

But could you explain what you are trying to achieve ? What is the end goal of this function ? If you receive more messages on this node the interval will become confusing.

1 Like

As @Colin said above you need to save the countdown to a context variable and check for the topics outside of the set intervall. Everytime you send a message to a function node it creates an instance of the function that doesnt know about the other instances of the same function. So your only way to have a persistent value across multiple invoked instances of the function is said context var. Something like this:

var countdown = context.get("countdown") || false;
if (!countdown && msg.topic === 1) {
    var counter = 10;
    node.send({payload:(counter)});
    node.status({fill:"green",shape:"ring",text:(counter)});
    countdown = setInterval(function(){
        counter -= 1;
        if (counter < 5) {
            clearInterval(countdown);
            context.set("countdown", false);
            node.status({fill:"red",shape:"dot",text:'finished'});
            return;
        }
        node.send({payload:(counter)});
        node.status({fill:"green",shape:"ring",text:(counter)});
    }, 1000); 
    context.set("countdown", countdown);
} else if (msg.topic === 0 && countdown) {
    clearInterval(countdown);
    node.status({fill:"red",shape:"dot",text:'stopped'});
    context.set("countdown", false);
}
return null;

This is not really tested and of the top of my head. Be aware that this is only the recommended way if your default context store is memory as nodered cant serialise the interval function and you will get funny errors when the default is filesystem. In this case use something like context.countdown = countdown; which is the old deprecated way to set context variables that is still recommended for this sitiuation.

Johannes

2 Likes

It's for our advanced well pump, filter, drinking and irrigation system.
As our up hill 'water tower container' sensors give only full or empty. I want to simulate on the dashboard: slow emptying over the day and when filling up.
I had already programmed a count up & down function. But got stuck and trimmed the setInterval function down to find out how to stop and control it.

Thank you Johannes,
That is a really great and comprehensive answer.
Your function script works flawlessly!
I will now build it out to simulate on the dashboard: slow emptying over the day and when filling up. As our up hill 'water tower container' sensors give only full or empty.

I am just a novice and learning to program Node-Red since a month now.
So your explanation teaches me a lot and i can continue to work on the 'far to big' task of our comprehensive well pump, filter, drinking and irrigation water system. It advances slowly as i am learning. The piping system is complete after a month of work, the electronics 2/3 ready. And i already have 1/3 of the whole program done. But i have time and patience.
I will share the whole project on the slack forum (and maybe also here) when finished.

2 Likes

You should mark that post as the solution.

That's an option near the like button.

2 Likes

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