Fundamental misunderstanding of the loop node

I want to start off by saying I am new to node-Red, but I have a programming background and some PLC background, so loops and logic flow is not new to me, only node-Red itself.
I have a flow (see below) that I am using to turn on a light for a random amount of time, then turn off for another random amount of time.
I'm wanting to control the loop using a flow variable called "loop_running", so that as long as that variable is true then the loop will run, but once the variable goes to false then stop the loo pand run the loop end output branch. This doesn't seem to work.
Here's a screenshow of how I am trying to check that variable and stop the loop:
Screenshot 2023-10-25 194408

So not sure what I'm doing wrong here. Maybe I'm mixing up my language syntax (java vs javascrypt vs python vs C vs C++ vs C#) or maybe I'm not fully understanding how to check the variable and return the bool state to the node itself.
Here's the promised screenshot of the flow overall.

I know nothing about the loop node but shouldn't that condition be flow.loop_running, which is also equivalent to flow.loop_running == true (with a double equal sign)?

I did try that first, then remembered in some languages that booleans use a single equals sign, but either way doesn't work. I also thought about the condition being for not when to continue the loop but rather when to exit, so tried both variations of the false (single and double equals) but no joy either.

May have just had a "Ah ha!" moment. I set the condition to be flow.loop_running == false and on the next iteration the loop exited because the flow.loop_running value was actually true. I think the node only reads the variable once when the loop starts, but not again. Not sure if that's true or not, I should be able to test that pretty easily with a trigger node.

False alarm. Seems I can't get the loop to break on the change of the variable.

OK so a couple of things here - most people will not have the Home Assistant nodes installed (i think i am one of the few on here that do) so you are better off asking questions in the Node Red subforum of the HA forums. (Not because we are rude but because most people do not want to pollute their systems with all the HA nodes and the baggage that they bring.)

How long is the proposed check time for turning on and off (whatever it is you are controlling)

Usually to do this i would use a trigger node with a timeout - so lets say you want to (on motion) have a light turn on for 5 minutes - you would feed the change state node from HA into a trigger node - with a 5 minute timeout - when receiving the first motion change the trigger node would send an ON packet, - and every time new motion was detected it would send another on and reset its 5 minute counter

At the end of 5 minutes if it did not receive any mesages it would then send and OFF message to the light controller.

You need to be careful of loop nodes and race style conditions in a single threaded visual programming environment.

Here is an example of my setup use NR nodes only

[{"id":"9ef52fcc57f7f45d","type":"group","z":"68add8f2f0cd539b","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["6876b96d361ff3fd","d8dc5dafdb8bc7d1","b3d05afe545e2de1","df24536b5e187398","3e15ccd85843551f","a68207217b63f2bf","072fdf6cc92a1140","6893d27a3579280c","b81b415744cee2ce","48a93d66beb16d8e","087ebe70f676d609","2f6f4e46f45e337f","21982fa0c3c827aa","c15ba68031336453","80af7f1a6f466c6c","9130bda66c891a9b","1da9e3f85750424b","f5def4bd01112a2a","e70f3173d4d87e3c","2dfea36809e7d531"],"x":94,"y":11.5,"w":1852,"h":429.5},{"id":"6876b96d361ff3fd","type":"time-inject","z":"68add8f2f0cd539b","g":"9ef52fcc57f7f45d","name":"Reset to Auto at Sunrise each day","nameInt":"⏲ sunrise end = \"OFF\"","positionConfig":"e2e4f670.cd19a8","props":[{"p":"","pt":"msgPayload","v":"OFF","vt":"str","o":"","oT":"none","oM":"60000","f":0,"fS":0,"fT":"UNIX timestamp (ms)","fI":"0","next":false,"days":"*","months":"*","onlyOddDays":false,"onlyEvenDays":false,"onlyOddWeeks":false,"onlyEvenWeeks":false}],"injectTypeSelect":"time","intervalCount":1,"intervalCountType":"num","intervalCountMultiplier":60000,"time":"sunriseEnd","timeType":"pdsTime","offset":"","offsetType":"none","offsetMultiplier":60000,"timeEnd":"","timeEndType":"entered","timeEndOffset":0,"timeEndOffsetType":"none","timeEndOffsetMultiplier":60000,"timeDays":"*","timeOnlyOddDays":false,"timeOnlyEvenDays":false,"timeOnlyOddWeeks":false,"timeOnlyEvenWeeks":false,"timeMonths":"*","timedatestart":"","timedateend":"","property":"","propertyType":"none","propertyCompare":"true","propertyThreshold":"","propertyThresholdType":"num","timeAlt":"","timeAltType":"entered","timeAltDays":"*","timeAltOnlyOddDays":false,"timeAltOnlyEvenDays":false,"timeAltOnlyOddWeeks":false,"timeAltOnlyEvenWeeks":false,"timeAltMonths":"*","timeAltOffset":0,"timeAltOffsetType":"none","timeAltOffsetMultiplier":60000,"once":false,"onceDelay":0.1,"recalcTime":2,"x":1120,"y":240,"wires":[["072fdf6cc92a1140"]]},{"id":"d8dc5dafdb8bc7d1","type":"time-inject","z":"68add8f2f0cd539b","g":"9ef52fcc57f7f45d","name":"Turn on just before Sunset each day","nameInt":"⏲ sunset start = \"ON\"","positionConfig":"e2e4f670.cd19a8","props":[{"p":"","pt":"msgPayload","v":"ON","vt":"str","o":"","oT":"none","oM":"60000","f":0,"fS":0,"fT":"UNIX timestamp (ms)","fI":"0","next":true,"days":"*","months":"*","onlyOddDays":false,"onlyEvenDays":false,"onlyOddWeeks":false,"onlyEvenWeeks":false},{"p":"","pt":"msgTopic","v":"","vt":"str","o":"","oT":"none","oM":"60000","f":0,"fS":0,"fT":"UNIX timestamp (ms)","fI":"0","next":false,"days":"*","months":"*","onlyOddDays":false,"onlyEvenDays":false,"onlyOddWeeks":false,"onlyEvenWeeks":false}],"injectTypeSelect":"time","intervalCount":1,"intervalCountType":"num","intervalCountMultiplier":60000,"time":"sunsetStart","timeType":"pdsTime","offset":"","offsetType":"none","offsetMultiplier":60000,"timeEnd":"","timeEndType":"entered","timeEndOffset":0,"timeEndOffsetType":"none","timeEndOffsetMultiplier":60000,"timeDays":"*","timeOnlyOddDays":false,"timeOnlyEvenDays":false,"timeOnlyOddWeeks":false,"timeOnlyEvenWeeks":false,"timeMonths":"*","timedatestart":"","timedateend":"","property":"","propertyType":"none","propertyCompare":"true","propertyThreshold":"","propertyThresholdType":"num","timeAlt":"","timeAltType":"entered","timeAltDays":"*","timeAltOnlyOddDays":false,"timeAltOnlyEvenDays":false,"timeAltOnlyOddWeeks":false,"timeAltOnlyEvenWeeks":false,"timeAltMonths":"*","timeAltOffset":0,"timeAltOffsetType":"none","timeAltOffsetMultiplier":60000,"once":false,"onceDelay":0.1,"recalcTime":2,"x":280,"y":340,"wires":[["df24536b5e187398"]]},{"id":"b3d05afe545e2de1","type":"time-inject","z":"68add8f2f0cd539b","g":"9ef52fcc57f7f45d","name":"Turn off at 10PM","nameInt":"⏲ 22:00 = \"OFF\"","positionConfig":"e2e4f670.cd19a8","props":[{"p":"","pt":"msgPayload","v":"OFF","vt":"str","o":"","oT":"none","oM":"60000","f":0,"fS":0,"fT":"UNIX timestamp (ms)","fI":"0","next":true,"days":"*","months":"*","onlyOddDays":false,"onlyEvenDays":false,"onlyOddWeeks":false,"onlyEvenWeeks":false},{"p":"","pt":"msgTopic","v":"","vt":"str","o":"","oT":"none","oM":"60000","f":0,"fS":0,"fT":"UNIX timestamp (ms)","fI":"0","next":false,"days":"*","months":"*","onlyOddDays":false,"onlyEvenDays":false,"onlyOddWeeks":false,"onlyEvenWeeks":false}],"injectTypeSelect":"time","intervalCount":1,"intervalCountType":"num","intervalCountMultiplier":60000,"time":"22:00","timeType":"entered","offset":"","offsetType":"none","offsetMultiplier":60000,"timeEnd":"","timeEndType":"entered","timeEndOffset":0,"timeEndOffsetType":"none","timeEndOffsetMultiplier":60000,"timeDays":"*","timeOnlyOddDays":false,"timeOnlyEvenDays":false,"timeOnlyOddWeeks":false,"timeOnlyEvenWeeks":false,"timeMonths":"*","timedatestart":"","timedateend":"","property":"","propertyType":"none","propertyCompare":"true","propertyThreshold":"","propertyThresholdType":"num","timeAlt":"","timeAltType":"entered","timeAltDays":"*","timeAltOnlyOddDays":false,"timeAltOnlyEvenDays":false,"timeAltOnlyOddWeeks":false,"timeAltOnlyEvenWeeks":false,"timeAltMonths":"*","timeAltOffset":0,"timeAltOffsetType":"none","timeAltOffsetMultiplier":60000,"once":false,"onceDelay":0.1,"recalcTime":2,"x":340,"y":400,"wires":[["df24536b5e187398"]]},{"id":"df24536b5e187398","type":"switch","z":"68add8f2f0cd539b","g":"9ef52fcc57f7f45d","name":"Check for operating Mode","property":"Lighting.FrontDoor_External.Mode","propertyType":"global","rules":[{"t":"eq","v":"Auto","vt":"str"},{"t":"eq","v":"Manual","vt":"str"},{"t":"eq","v":"Off","vt":"str"}],"checkall":"true","repair":false,"outputs":3,"x":650,"y":360,"wires":[["2dfea36809e7d531"],["9130bda66c891a9b"],["1da9e3f85750424b"]]},{"id":"3e15ccd85843551f","type":"inject","z":"68add8f2f0cd539b","g":"9ef52fcc57f7f45d","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"ON","payloadType":"str","x":1550,"y":360,"wires":[["2dfea36809e7d531"]]},{"id":"a68207217b63f2bf","type":"inject","z":"68add8f2f0cd539b","g":"9ef52fcc57f7f45d","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"OFF","payloadType":"str","x":1550,"y":400,"wires":[["2dfea36809e7d531"]]},{"id":"072fdf6cc92a1140","type":"change","z":"68add8f2f0cd539b","g":"9ef52fcc57f7f45d","name":"Setup Globals","rules":[{"t":"set","p":"Lighting.FrontDoor_External.Mode","pt":"global","to":"Auto","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"OFF","tot":"str"},{"t":"set","p":"Lighting.FrontDoor_External.UpdatedBy","pt":"global","to":"\"Front Door Lighting - Sunrise Reset Timer\"","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1480,"y":220,"wires":[["2dfea36809e7d531"]]},{"id":"6893d27a3579280c","type":"within-time-switch","z":"68add8f2f0cd539b","g":"9ef52fcc57f7f45d","name":"Check for Motion when Dark","nameInt":"","positionConfig":"e2e4f670.cd19a8","startTime":"22:00","startTimeType":"entered","startOffset":0,"startOffsetType":"none","startOffsetMultiplier":60000,"endTime":"sunriseStart","endTimeType":"pdsTime","endOffset":0,"endOffsetType":"none","endOffsetMultiplier":60000,"timeRestrictions":0,"timeRestrictionsType":"none","timeDays":"*","timeOnlyOddDays":false,"timeOnlyEvenDays":false,"timeOnlyOddWeeks":false,"timeOnlyEvenWeeks":false,"timeMonths":"*","timedatestart":"","timedateend":"","propertyStart":"","propertyStartType":"none","propertyStartCompare":"true","propertyStartThreshold":"","propertyStartThresholdType":"num","startTimeAlt":"","startTimeAltType":"entered","startOffsetAlt":0,"startOffsetAltType":"none","startOffsetAltMultiplier":60000,"propertyEnd":"","propertyEndType":"none","propertyEndCompare":"true","propertyEndThreshold":"","propertyEndThresholdType":"num","endTimeAlt":"","endTimeAltType":"entered","endOffsetAlt":0,"endOffsetAltType":"none","endOffsetAltMultiplier":60000,"withinTimeValue":"","withinTimeValueType":"msgInput","outOfTimeValue":"false","outOfTimeValueType":"msgInput","tsCompare":"0","x":640,"y":60,"wires":[["2f6f4e46f45e337f","c15ba68031336453"],["21982fa0c3c827aa"]]},{"id":"b81b415744cee2ce","type":"mqtt in","z":"68add8f2f0cd539b","g":"9ef52fcc57f7f45d","name":"","topic":"stat/Tasmota_Front_Door_Lighting/RESULT","qos":"2","datatype":"auto-detect","broker":"75b60904.4fed08","nl":false,"rap":true,"rh":0,"inputs":0,"x":310,"y":160,"wires":[["80af7f1a6f466c6c"]]},{"id":"48a93d66beb16d8e","type":"trigger","z":"68add8f2f0cd539b","g":"9ef52fcc57f7f45d","name":"Lights on for 5 minutes","op1":"ON","op2":"OFF","op1type":"str","op2type":"str","duration":"5","extend":true,"overrideDelay":false,"units":"min","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":1420,"y":60,"wires":[["087ebe70f676d609","2dfea36809e7d531"]]},{"id":"087ebe70f676d609","type":"debug","z":"68add8f2f0cd539b","g":"9ef52fcc57f7f45d","name":"Trigger Node for Night time Movement","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1750,"y":60,"wires":[]},{"id":"2f6f4e46f45e337f","type":"switch","z":"68add8f2f0cd539b","g":"9ef52fcc57f7f45d","name":"Check if Motion on Sensor","property":"payload.Switch2.Action","propertyType":"msg","rules":[{"t":"eq","v":"TOGGLE","vt":"str"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":920,"y":60,"wires":[["48a93d66beb16d8e"],[]]},{"id":"21982fa0c3c827aa","type":"debug","z":"68add8f2f0cd539b","g":"9ef52fcc57f7f45d","name":"Motion when under program control","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":940,"y":140,"wires":[]},{"id":"c15ba68031336453","type":"debug","z":"68add8f2f0cd539b","g":"9ef52fcc57f7f45d","name":"Into Trigger Node","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":890,"y":100,"wires":[]},{"id":"80af7f1a6f466c6c","type":"switch","z":"68add8f2f0cd539b","g":"9ef52fcc57f7f45d","name":"Check for operating Mode","property":"Lighting.FrontDoor_External.Mode","propertyType":"global","rules":[{"t":"eq","v":"Auto","vt":"str"},{"t":"eq","v":"Manual","vt":"str"},{"t":"eq","v":"Off","vt":"str"}],"checkall":"true","repair":false,"outputs":3,"x":350,"y":60,"wires":[["6893d27a3579280c"],[],[]]},{"id":"9130bda66c891a9b","type":"change","z":"68add8f2f0cd539b","g":"9ef52fcc57f7f45d","name":"Light ON - Manual Mode","rules":[{"t":"set","p":"payload","pt":"msg","to":"ON","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1030,"y":360,"wires":[["2dfea36809e7d531"]]},{"id":"1da9e3f85750424b","type":"change","z":"68add8f2f0cd539b","g":"9ef52fcc57f7f45d","name":"Light OFF - Manual Mode","rules":[{"t":"set","p":"payload","pt":"msg","to":"OFF","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1030,"y":400,"wires":[["2dfea36809e7d531"]]},{"id":"f5def4bd01112a2a","type":"comment","z":"68add8f2f0cd539b","g":"9ef52fcc57f7f45d","name":"Motion Sensing for Front Lights","info":"","x":1310,"y":100,"wires":[]},{"id":"e70f3173d4d87e3c","type":"comment","z":"68add8f2f0cd539b","g":"9ef52fcc57f7f45d","name":"Timed Control based on Sunset and Sunrise","info":"","x":670,"y":280,"wires":[]},{"id":"2dfea36809e7d531","type":"link out","z":"68add8f2f0cd539b","g":"9ef52fcc57f7f45d","name":"Link Out to MQTT - Front Door Lighting - Power Control","mode":"link","links":["1c8bc693e38cdc88"],"x":1745,"y":220,"wires":[]},{"id":"e2e4f670.cd19a8","type":"position-config","name":"Sydney - Castle Cove","isValide":"true","longitude":"0","latitude":"0","angleType":"deg","timeZoneOffset":99,"timeZoneDST":0,"stateTimeFormat":"3","stateDateFormat":"12"},{"id":"75b60904.4fed08","type":"mqtt-broker","name":"Ubuntu-31","broker":"172.16.100.31","port":"1883","clientid":"NewLiveVM","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""}]

Note that i use the Enhanced Trigger and Within time nodes from time inject · rdmtc/node-red-contrib-sun-position Wiki · GitHub

This flow sets the light on at 10 minutes to sunset each night and then turns it off at 10PM - after 10PM if it recevies any input from the motion sensor it tunrs the lights on again for another 5 minutes (depending on the mode they are runnign in)

Craig

3 Likes

So the trigger will be the BigTimer set to Dusk -> Dawn.
There is a one-shot trigger that the BigTimer has on state change (Start of Dusk or Start of Dawn), which would work, but, the issue with one-shot triggers of this length (6+ hours) is if something like a reboot occurred in the middle of the cycle, we loose the remaining of the cycle.
So I'm using the 1-minute output of the BigTimer, which every minutes outputs a 1 if it's between Dusk and Dawn, or a 0 otherwise. I want this to be the basis for the loop, where the loop only occurs when it's between Dusk and Dawn.
Within that time, I want to loop through having the light on for a random time between 7~24 minutes and then off for a separate random time between 9~27 minutes.
As far as using HA nodes, well I don't know that that's really in the mix because my issue is with the fundamental function of node-RED, not the interaction between HA and NR, but if it helps to make things clearer I can replace those two service calls with a MQTT topic set. I wouldn't change anything else in the flow though, as my purpose of using NR is to do complex automations utilizing the single-threaded nature of the message queue to my advantage.
This does bring up one thing that I do have a question about, do all flows run on a single thread as a single context or does each flow run in it's own thread context? If it's the first, I can see where a long delay could impact other flows because it's most likely causing a block on the thread as a whole, but then again maybe it's not, I'm not 100% sure since I am only recently moving into NR.
Looking at the GH link, wouldn't this have the same issue where each iteration of the loop would have to look at some value in the condition, unless the loop has a better time of reacting to changes in the msg.payload rather than what would be in a flow variable.
Also, I have tried to use a while-loop instead of the basic loop but it's no different. Maybe using a message will be better than just looking at a change in a flow variable...
If what I'm doing is a fundamental aversion to NR, then I would probably fall back to writing my own C++ code to do the loop, which uses another C++ binary I wrote years ago for ZoneMinder to have things occur based on Dusk and Dawn. I just wanted to try my hand at NR.

Thats why i do not use Triggers like BigTimer.

INstead use the enhanced trigger in the contrib node - set it to run every minute - then feed it to the Within-time node - you know it will run all the time that way in the within-time node you simply list the times when you want to let packets pass through (or indeed go to the out of time port if specified)

Craig

It is a just a mindset thing that you need to get right - your concept with using a timed trigger in bigtimer is correct (i think Peter has added too many things to the one node so it is confusing now - part of which goes against the ethos of Node Red - specialised nodes that do a single job well that you then string together)

Here is a random number generator as an example that can have high and low bounds set - so you could use that to get your random effect and put a gate node in to stop messages for that period of time (number of messages)

Or you could seed the mytimeout node with the output from the random node to set the outputs for the timeout node

Lots of creative ways to do it without having to resort to no flow based programming

You could look at the gate node as well as the counter node both of which would help achieve what you want in combination with switch/change nodes and flow variables

Craig

So using the while-loop node, I came across a single sentence that lead me in a direction. The sentence is:
Flow and global context can be accessed using `flow.get("key")` and `global.get("key")
So, I put flow.get("loop_run") == true in the conditional statement field and well, it seems to work. Each iteration of the loop does run the code and exits the loop when the flow.loop_run variable toggles to "false", which is exactly what I wanted to occur. I will need to run many more iterations to ensure that it's working as intended. Simulations can only go so far.

I did put a gate node in front of things so only when the flow.loop_run is false will a message pass through, which wasn't any different than before which is why I only ever expected a single message to go through, thinking things where single threaded and linear based on message passing.

For me, it's not really much different than simple gate logic. I suppose you could have multiple "threads" running in a flow by branching off of a node, but I think that my assumption that the flow is linear and is a gate type of flow is correct.

I really didn't want to fall back to "old habits" by doing something as brute force as writing an entire C++ binary just to perform what I thought was a simple workflow, and really glad I didn't give up and go that route.

Now being the continual improvement person I am, I will look at ways of optimizing this flow as I learn more about NR. Overall this one flow has really expanded my knowledge in NR and changed it from this new big unknown to something that's really not all that bad, as long one thinks logically. I do continue to plan on using this as my automation platform for HA, possibly other things as well. Maybe even replacing automations I have in other frameworks like Alexa and HomeKit, but this was a way for me to get started by solving a real-world problem.

I do wish the debugging was a bit more in-depth in NR. I did find where I can single-step through a flow, but I still can't really peek under the hood like I'm used to, so will have to learn more creative ways and even pull back to the ole Basic days where print was the debugger and you had hundreds of those throughout the code, just in this case it's the debug node.

Are you aware that there's a dedicated debugger plugin available? Might still be different to what you're used to - but better than going with debug nodes only.

BTW: Would you mind sharing your flow (as json!)? That would make it easier to provide support...

I did stumble across that when I was trying to figure out the debugging. It does provide a bunch more than a simple debug node, but not a ton more to be honest. Maybe it's just my expectations coming form general programming to this is a bit askew and I need to reset my own expectations.

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