Condition loop only exits during testing

I have a conditional loop node that is supposed to exit when a counter gets to 100. It starts with an injection node that is set to repeat at a specific time, early in the morning before I get up, and when I log onto the server later on, the debug window is showing it's still running well past 100. I copied the loop nodes, and set a smaller delay to test, and it works as expected when I run it separately.

I added a line to the function node to keep it to maximum of 100, I'll check to see how that affects the runtime tomorrow morning, but I assume instead of seeing it counting up past 100, I'll end up with it continuing to output '100' to the debug window.

Is there a reason why the timed injection would work differently? This is exactly the same code I am using, minus the repeat option (and a lower delay value).

[
    {
        "id": "68f320f33a70cd0b",
        "type": "change",
        "z": "0fb6b4cf527db2b5",
        "name": "Set initial value",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "1",
                "tot": "num"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 400,
        "y": 660,
        "wires": [
            [
                "8aa222b7986a6f3e"
            ]
        ]
    },
    {
        "id": "8aa222b7986a6f3e",
        "type": "loop",
        "z": "0fb6b4cf527db2b5",
        "name": "",
        "kind": "cond",
        "count": "10",
        "initial": "1",
        "step": "1",
        "condition": "msg.payload < 100",
        "conditionType": "js",
        "when": "before",
        "enumeration": "enum",
        "enumerationType": "msg",
        "limit": "1200000000",
        "loopPayload": "loop-keep",
        "finalPayload": "final-last",
        "x": 400,
        "y": 700,
        "wires": [
            [
                "a9b392782147f78f"
            ],
            [
                "c4afafb52d072ee5"
            ]
        ]
    },
    {
        "id": "c4afafb52d072ee5",
        "type": "function",
        "z": "0fb6b4cf527db2b5",
        "name": "Increment function",
        "func": "var i = msg.payload\n\ni = 1 + i\n\nmsg.payload = Math.min(i, 100)\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 410,
        "y": 780,
        "wires": [
            [
                "337b4ea4fac670dc",
                "602cfb94efe73a8e"
            ]
        ]
    },
    {
        "id": "337b4ea4fac670dc",
        "type": "delay",
        "z": "0fb6b4cf527db2b5",
        "name": "",
        "pauseType": "delay",
        "timeout": "100",
        "timeoutUnits": "milliseconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "x": 390,
        "y": 820,
        "wires": [
            [
                "8aa222b7986a6f3e"
            ]
        ]
    },
    {
        "id": "602cfb94efe73a8e",
        "type": "debug",
        "z": "0fb6b4cf527db2b5",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 730,
        "y": 780,
        "wires": []
    },
    {
        "id": "a9b392782147f78f",
        "type": "debug",
        "z": "0fb6b4cf527db2b5",
        "name": "Exit loop",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "loop",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 720,
        "y": 680,
        "wires": []
    },
    {
        "id": "d83cd8c82738598e",
        "type": "inject",
        "z": "0fb6b4cf527db2b5",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "true",
        "payloadType": "bool",
        "x": 120,
        "y": 660,
        "wires": [
            [
                "68f320f33a70cd0b"
            ]
        ]
    }
]

Your flow has an non-core node in it (loop). What type is that (node-red-contrib-something probably).

node-red-contrib-loop maybe??

Yes, it's node-red-contrib-loop.

Is there an easy way to tell what's used on the flow? I can search in the sidebar, but if there are similar items, I don't see a way to tell where the one being used came from. This one had a "Help' entry, so I could see where it came from, but not all nodes do.

If you are looking to see what nodes are in your flow, one thing you cn do is go to the right sidebar and select the I (Information) tab so you see somehting like this:

1 Like

Your flow works perfectly for me. What does it do when you run it?

Not sure what the issue was, but when I would test it, it would end at 100:
Screen Shot 2021-09-02 at 7.28.45 AM

But when it ran at a timed interval in my full flow, it would keep going after it reached 100:
Screen Shot 2021-09-02 at 7.28.07 AM

I realized I don't need the 'Loop' node (still thinking in the programming paradigm), so I switched that out for a simple Switch node.

[
    {
        "id": "7f5e73f07905d81b",
        "type": "change",
        "z": "0fb6b4cf527db2b5",
        "name": "Set initial value",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "0",
                "tot": "num"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 400,
        "y": 920,
        "wires": [
            [
                "6749ea92c06ea865"
            ]
        ]
    },
    {
        "id": "6749ea92c06ea865",
        "type": "switch",
        "z": "0fb6b4cf527db2b5",
        "name": "Condition Loop",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "gte",
                "v": "100",
                "vt": "num"
            },
            {
                "t": "else"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 400,
        "y": 960,
        "wires": [
            [
                "ba342cfb3d033c10"
            ],
            [
                "5d4b1fbb15576341"
            ]
        ]
    },
    {
        "id": "5d4b1fbb15576341",
        "type": "function",
        "z": "0fb6b4cf527db2b5",
        "name": "Increment function",
        "func": "// add ASCII char from 32 to 126\nvar i = msg.payload\n\ni = 1 + i\n\nmsg.payload = Math.min(i, 100)\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 410,
        "y": 1040,
        "wires": [
            [
                "3f083dab8d4ccf1c",
                "d33688749ecf715c"
            ]
        ]
    },
    {
        "id": "3f083dab8d4ccf1c",
        "type": "delay",
        "z": "0fb6b4cf527db2b5",
        "name": "",
        "pauseType": "delay",
        "timeout": "100",
        "timeoutUnits": "milliseconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "x": 390,
        "y": 1080,
        "wires": [
            [
                "6749ea92c06ea865"
            ]
        ]
    },
    {
        "id": "d33688749ecf715c",
        "type": "debug",
        "z": "0fb6b4cf527db2b5",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 730,
        "y": 1040,
        "wires": []
    },
    {
        "id": "ba342cfb3d033c10",
        "type": "debug",
        "z": "0fb6b4cf527db2b5",
        "name": "Exit loop",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 720,
        "y": 920,
        "wires": []
    },
    {
        "id": "42c00840263d66fe",
        "type": "inject",
        "z": "0fb6b4cf527db2b5",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "48 07 * * *",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "true",
        "payloadType": "bool",
        "x": 110,
        "y": 920,
        "wires": [
            [
                "7f5e73f07905d81b"
            ]
        ]
    }
]

The test portion I'm posting seems to work both manually injecting, and at a timed interval, but occasionally when I tested it, it would continue after it reached 100. Not sure why.

I have updated my full flow to use the Switch node in this test set, I'll see if it works as expected tomorrow morning.

Thanks, I was missing the 'Show More' option.

1 Like

In that case your flow is interacting with it in a different manner to the test flow.

I was going to come to that. Nodes such as that are a bit of an anti-pattern in node-red. I would never use it.

The only differences in the main flow is a few switch statements prior to this section, and the final output attached to the same output as the debug nodes, everything else is copied exactly from the main flow.

The issue happened once or twice while I was using the isolated Test flow as well. I don't know if the changes I made will make a difference, I'll see what happens tomorrow morning. I am using this to fade up a lamp in the morning.

I just started using Node Red about a week ago, I still have a bit of playing around to get more familiar with it. I had used the Loop node because I still have the programming mindset; I would program a loop in code, so I looked for a Loop node. It's a slight shift to think more in the 'Node' pattern, and as this dog gets older, he's finding it difficult to learn new tricks.

Thanks for the response, I'll see if things are working properly.

In that case perhaps it is a bug in the node. I went to look for the node's github page to see if there were any issues submitted against it, but it's repository is on gitlab and there doesn't seem to be anywhere to submit issues, though I am not familiar with gitlab so that may be my ignorance showing.

Did you realise that if you re-trigger your test flow before it has completed that it will start running two sequences nested together? The same applies to your modified version.

1 Like

Also make sure your initial payload is always of number type (not a string containing a number) or you will get unexpected results.

1 Like

The payload should definitely only be a number type, it gets set initially in the 'Set initial value' node, and the only other one that updates it is the increment function. The debug node indicates that it is a number. I believe (but would have to check) that the original Loop node didn't change the value as sent, but since I have eliminated that, it shouldn't be an issue anymore. My full flow only includes a couple of switch nodes (that determine whether to enter this loop) between the inject and setting the initial value, so the payload value that I'm using doesn't even exist at that point.

As for the retriggering, i am aware of that, but the initial issue happened when it was run at a scheduled time, and I wasn't retriggering it, only opening up a browser link to Node Red. I hadn't triggered it myself, it appears that it was still running from the earlier trigger.

I'm hoping that the issue may be fixed after substituting the Switch Node for the Loop Node. It's possible, as you suggested, that there's a bug in their node, although my situation was such a basic usage that I would assume it would have been obvious to the person who programmed it that it wasn't working properly.

Thanks again for sticking with this newbie, I greatly appreciate your assistance.

I think I would probably have done it something like this

[{"id":"9906bdddd4d17adb","type":"inject","z":"84405ff5.25fa6","name":"Start","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"date","x":90,"y":2600,"wires":[["32907c6691589470"]]},{"id":"32907c6691589470","type":"function","z":"84405ff5.25fa6","name":"Generate Messages","func":"const initialValue = 1\nconst finalValue = 100\nfor (let i=initialValue; i<=finalValue; i++) {\n    node.send({payload: i})    \n}\nnode.send({payload: \"Done\"})\nreturn null","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":260,"y":2600,"wires":[["60e1c845ee3080ab"]]},{"id":"4381113a0a3b9c76","type":"debug","z":"84405ff5.25fa6","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":750,"y":2580,"wires":[]},{"id":"36fc42df98c8920b","type":"debug","z":"84405ff5.25fa6","name":"Done","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":750,"y":2640,"wires":[]},{"id":"60e1c845ee3080ab","type":"delay","z":"84405ff5.25fa6","name":"","pauseType":"rate","timeout":"100","timeoutUnits":"milliseconds","rate":"10","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"x":460,"y":2600,"wires":[["29af9efdf198f3a0"]]},{"id":"29af9efdf198f3a0","type":"switch","z":"84405ff5.25fa6","name":"","property":"payload","propertyType":"msg","rules":[{"t":"istype","v":"number","vt":"number"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":610,"y":2600,"wires":[["4381113a0a3b9c76"],["36fc42df98c8920b"]]}]
1 Like

Thanks, I'm not sure that would be as flexible as i want to be though

I'm using this loop to fade a light on in the morning. The reason I'm doing it the way I am is that in the future, I hope to be able to get the current value of the light if it changes while the loop is running, so I can adjust the value in the loop in case the light gets turned on full (or turned off). The Lutron nodes that I'm using to control the light don't have a node to get the current value, but that's something I hope to look into creating for myself when i get more experience playing around with this, and can look into creating my own nodes. If I can create this functionality, it would be easy to insert a new node into the loop at the appropriate point.

But I do like this as an example on how to do the loop completely in a javascript function. I had read through the documentation, and forgotten about the 'node.send' function. I will try to remember to keep this in my mental toolbox.

OK. I think once you can get the current intensity the best way to do it might change completely anyway.

1 Like

Just so I'm understanding, the Function node loops through the values, and sends them all as it processes them, then the delay node holds them (in a stack/array?) and dispenses them one at a time at the rate specified? So the Function could have sent all of the messages before the Delay node has processed the first few?

EDIT: I added a Debug node to the 'Generate Messages' function output, and it looks like that's how it works.

Thanks.

And that's a good ways away, I'm still in the crawling phase.

Yes. The delay node holds them in a queue till it is time to release them.

Alternatively this might be useful to you. I found it by searching for slew in the flows site and then realised that I wrote it myself.

image

[{"id":"95daacd1.0e09f8","type":"debug","z":"84405ff5.25fa6","name":"","active":true,"console":"false","complete":"false","x":610,"y":2820,"wires":[]},{"id":"c136d3fd.21da78","type":"inject","z":"84405ff5.25fa6","name":"","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":150,"y":2780,"wires":[["fc58524d.34f02"]]},{"id":"fc58524d.34f02","type":"function","z":"84405ff5.25fa6","name":"Slew rate limit 10 unit/second","func":"// Limits the slew rate incoming payload values\n// optionally sending intermediate values at specified rate\nlet maxRate = 10;            // max slew rate units/second\nlet sendIntermediates = true;   // whether to send intermediate values\nlet period = 100;           // period in millisecs to send new values (if sendIntermediates)\nlet jumpThreshold = 100;      // if the step asked for is more that this then goes immediately to that value\n\nvar newValue = Number(msg.payload);\nvar timer = context.get('timer') || 0;\n// check the value is  a number\nif (!isNaN(newValue) && isFinite(newValue)) {\n    var target = msg.payload;\n    context.set('target', target);\n    // set last value to new one if first time through\n    var lastValue = context.get('lastValue');\n    if (typeof lastValue == \"undefined\" || lastValue === null) {\n        lastValue = newValue;\n        context.set('lastValue', newValue);\n    }\n    // calc new value\n    msg.payload = calcOutput();\n    // stop the timer\n    if (timer) {\n        clearTimeout(timer);\n        context.set('timer', null);\n    }\n    // restart it if required to send intermediate values\n    if (sendIntermediates) {\n        timer = setInterval(function(){\n            // the timer has run down calculate next value and send it\n            var newValue = calcOutput();\n            if (newValue != context.get('lastValueSent')) {\n                context.set('lastValueSent', newValue);\n                node.send({payload: newValue});\n            }\n        },period);\n        context.set('timer', timer);\n    }\n    context.set('lastValueSent', msg.payload);\n} else {\n    // payload is not a number so ignore it\n    // also stop the timer as we don't know what to send any more\n    if (timer) {\n        clearTimeout(timer);\n        context.set('timer', null);\n    }\n    msg = null;\n}\nreturn msg;\n\n// determines the required output value\nfunction calcOutput() {\n    var lastValue = context.get('lastValue');\n    var target = context.get('target');\n    // set to current value if first time through or step > threshold\n    if (typeof lastValue == \"undefined\" || lastValue === null) lastValue = target;\n    var now = new Date();\n    var lastTime = context.get('lastTime') || now;\n    // limit value to last value +- rate * time\n    var maxDelta = (now.getTime() - lastTime.getTime()) * maxRate/1000;\n    if (Math.abs(target - lastValue) > jumpThreshold) {\n        // step > threshold so go there imediately\n        newValue = target;\n    } else if (target > lastValue) {\n        newValue = Math.min( lastValue + maxDelta, target);\n    } else {\n        newValue = Math.max( lastValue - maxDelta, target);\n    }\n    context.set('lastValue', newValue);\n    context.set('lastTime', now);   \n    return newValue;\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":391,"y":2820,"wires":[["95daacd1.0e09f8"]]},{"id":"1501600b.8cd8d","type":"inject","z":"84405ff5.25fa6","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"100","payloadType":"num","x":150,"y":2900,"wires":[["fc58524d.34f02"]]},{"id":"82a4bba6819a9b5e","type":"inject","z":"84405ff5.25fa6","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"50","payloadType":"num","x":150,"y":2840,"wires":[["fc58524d.34f02"]]}]

Cool.

I'll keep that in my 'test flow' set to see if i can find a use for it in the future.

And, I know what it's like to not quite recognize code I wrote a while ago, unfortunately, 'a while ago' can be 'last week' sometimes for me.

Again, your assistance is greatly appreciated.