Wait until node in nodered doesn't work anymore as expected

From a couple of days, the node “wait until” is not reliable. Sometimes works, sometimes not. I’ve found a topic on the nodered forum ( “wait until” node changed behavior after recent update - General - Node-RED Forum (nodered.org)), but seems HA related, so no help there.

Anyone has found a solution?

1 Like

Could you provide your flow. The post you linked off to, they are not sending the entity variable the right way. It needs to be in the format

{
  "entities": ["light.living_room", "light.bedroom"]
}

https://zachowj.github.io/node-red-contrib-home-assistant-websocket/node/wait-until.html#full-payload

Edit : For a single entity use

{
  "entities": "light.living_room"
}
[{"id":"393cf2754fd83302","type":"ha-wait-until","z":"0a325c35fc29f44e","name":"","server":"","version":3,"outputs":2,"entities":{"entity":["{{payload}}"],"substring":[],"regex":[]},"property":"state","comparator":"is","value":"on","valueType":"str","timeout":"1","timeoutType":"num","timeoutUnits":"minutes","checkCurrentState":true,"blockInputOverrides":false,"outputProperties":[],"x":520,"y":4320,"wires":[["4acd45b38442a0a3"],[]]},{"id":"c72a95eb9ea49951","type":"inject","z":"0a325c35fc29f44e","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{   \"entities\": \"light.dj\" }","payloadType":"jsonata","x":290,"y":4320,"wires":[["393cf2754fd83302"]]},{"id":"4acd45b38442a0a3","type":"debug","z":"0a325c35fc29f44e","name":"debug 208","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":770,"y":4320,"wires":[]}]

Hi @mikefila, thanks for your reply.
I’m actually copying my reply from the Node-RED forum here just to keep track.

The “wait until” node reacts to the “entity” message which contains the current entity that has been triggered (this node waits until this current entity reports that it stopped). The “entities” message is passed on as a string, it contains the next entity that is going to be triggered by other nodes that are not in this example.

R.

Maybe i found a solution. Days ago i’ve updated the HA websocket nodes, and i think now you have to fill out “output proprieties” otherwise the node doesn’t return anything. Now seems to work (at least have worked the last 3 times). I’ve added the output proprieties like this:

image

Correct me if i’m wrong.

It still doesn’t follow the documentation. Sometimes you will find a method that seemingly works yet is undocumented. It may work forever or may stop with the next update.

Best practice is to follow what is documented. For your case you would use

image

{"entities": $entity().entity_id}

The above uses the $entity() variable. I wrote a short explanation of how it works here.

I just tried both @Mikefila and @Herian recommendations with no success.

I don’t need any output properties to be sent from this node. I just need the previous message to continue to a function node that sets the next entity to be actioned.

The flow works fine when I create separate nodes for each entity (which is not ideal for scaling this flow to different sets of blinds). When I try to use a single node that receives the entity it has to “pay attention to” in msg.entity, it works only once for each time I fire the flow. But if I use this node multiple times in a day it only fails once each time I use it. For example: I arrive home. The first blind opens, te node works fine and continues when the blind stops moving. The second blind opens, and this is where the node fails to report this blind’s status and waits until timeout.
Then if I leave home later, it does this again, it reacts to the first msg.entity received and then for the next one it times out. I tried switching the order of the devices to see if the problem was with the “middle” blind but nope.

@Mikefila you answered about this in the Node-RED forum, could you please send me an example of the proper way to set the key? I’m seeing the same message in the debug page, no matter if it comes from the actual node or from the inject node I created to show you the example.

Thanks!
R.

Did you try the flow I posted earlier in the thread? Does that work?

Mmmm… i’ve a flow that wait for the motion sensor to trigger and turning on tv. Today is the 3rd days in a row that works when i get home, maybe just luck? I think you need some kind of output to trigger the next node, if nothing is returned you have no payload and no trigger. Am i right?

As I understand, in the “wait until” node the incoming message is always output when the target is reached. The “output” configuration you mention adds the indicated property to the message. I’m checking everything with debug nodes before and after, and the complete incoming message is being output with no issues. The problem is that the node only reacts correctly for the first device and then for the next devices it just waits until timeout.

R.

Here’s an updated flow I created to be able to test this with Hue lights.

[{"id":"cb40f23231a4d26e","type":"function","z":"b84e61f8ff473dab","name":"List","func":"if (msg.devices) {\n    var devices = msg.devices.split(\",\");\n    if (devices.length > 0) {\n        var firstDevice = devices.shift();\n        var remainingDevices = devices.join(\",\");\n        var newMsg = { device: firstDevice, devices: remainingDevices };\n        newMsg.data = msg.data;\n        newMsg.topic = msg.topic;\n        newMsg.target = msg.target;\n        node.send(newMsg);\n    } else {\n        node.warn(\"The input msg.devices is empty\");\n    }\n} else {\n    node.warn(\"The input msg.devices is missing\");\n}\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":330,"y":2400,"wires":[["4aa3c040b63441bb","088803648811567d"]]},{"id":"b9a400c262c7dcaa","type":"change","z":"b84e61f8ff473dab","name":"","rules":[{"t":"set","p":"reset","pt":"msg","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":760,"y":2400,"wires":[["af69145241bd9b87"]]},{"id":"72bdc96fe937acf6","type":"debug","z":"b84e61f8ff473dab","name":"waituntil_separate_ok","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1000,"y":2700,"wires":[]},{"id":"4aa3c040b63441bb","type":"switch","z":"b84e61f8ff473dab","name":"end?","property":"device","propertyType":"msg","rules":[{"t":"eq","v":"end","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":510,"y":2440,"wires":[["b9a400c262c7dcaa"],["bc8cdefb63574206","87af21aba4d6c287","74b511448e29fbaa"]]},{"id":"87af21aba4d6c287","type":"ha-wait-until","z":"b84e61f8ff473dab","d":true,"name":"1) ENABLE EITHER THIS","server":"8a86b0cc.b2bac","version":3,"outputs":2,"entities":{"entity":["{{device}}"],"substring":[],"regex":[]},"property":"data.new_state.last_updated","comparator":"is_not","value":"0","valueType":"str","timeout":"23","timeoutType":"num","timeoutUnits":"seconds","checkCurrentState":false,"blockInputOverrides":true,"outputProperties":[],"entityLocation":"data","entityLocationType":"none","x":690,"y":2520,"wires":[["0d7fc763889fcaba","62cdb4c707691ef7"],["0d7fc763889fcaba","8082d66bdcc10ebf"]]},{"id":"bc8cdefb63574206","type":"debug","z":"b84e61f8ff473dab","name":"next device","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"device","targetType":"msg","statusVal":"","statusType":"auto","x":750,"y":2440,"wires":[]},{"id":"088803648811567d","type":"debug","z":"b84e61f8ff473dab","name":"debug devicelist","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"devices","targetType":"msg","statusVal":"","statusType":"auto","x":540,"y":2360,"wires":[]},{"id":"6a8df29af0d3f859","type":"ha-wait-until","z":"b84e61f8ff473dab","name":"Light 1","server":"8a86b0cc.b2bac","version":3,"outputs":2,"entities":{"entity":["light.tv_light_hue"],"substring":[],"regex":[]},"property":"data.event.new_state.last_updated","comparator":"is_not","value":"0","valueType":"str","timeout":"23","timeoutType":"num","timeoutUnits":"seconds","checkCurrentState":false,"blockInputOverrides":true,"outputProperties":[],"entityLocation":"data","entityLocationType":"none","x":710,"y":2640,"wires":[["0d7fc763889fcaba","72bdc96fe937acf6"],["5943f872251b4383"]]},{"id":"74b511448e29fbaa","type":"switch","z":"b84e61f8ff473dab","name":"2) OR THIS","property":"device","propertyType":"msg","rules":[{"t":"eq","v":"light.tv_light_hue","vt":"str"},{"t":"eq","v":"light.door_bulb_hue","vt":"str"},{"t":"eq","v":"light.globe_light_hue","vt":"str"}],"checkall":"true","repair":false,"outputs":3,"x":490,"y":2640,"wires":[["6a8df29af0d3f859"],["b30a045490d44ca2"],["613e6e535283cc93"]]},{"id":"b30a045490d44ca2","type":"ha-wait-until","z":"b84e61f8ff473dab","name":"Light 2","server":"8a86b0cc.b2bac","version":3,"outputs":2,"entities":{"entity":["light.door_bulb_hue"],"substring":[],"regex":[]},"property":"data.event.new_state.last_updated","comparator":"is_not","value":"0","valueType":"str","timeout":"23","timeoutType":"num","timeoutUnits":"seconds","checkCurrentState":false,"blockInputOverrides":true,"outputProperties":[],"entityLocation":"data","entityLocationType":"none","x":710,"y":2680,"wires":[["0d7fc763889fcaba","72bdc96fe937acf6"],["5943f872251b4383"]]},{"id":"613e6e535283cc93","type":"ha-wait-until","z":"b84e61f8ff473dab","name":"Light 3","server":"8a86b0cc.b2bac","version":3,"outputs":2,"entities":{"entity":["light.globe_light_hue"],"substring":[],"regex":[]},"property":"data.event.new_state.last_updated","comparator":"is_not","value":"0","valueType":"str","timeout":"23","timeoutType":"num","timeoutUnits":"seconds","checkCurrentState":false,"blockInputOverrides":true,"outputProperties":[],"entityLocation":"data","entityLocationType":"none","x":710,"y":2720,"wires":[["0d7fc763889fcaba","72bdc96fe937acf6"],["5943f872251b4383"]]},{"id":"5943f872251b4383","type":"debug","z":"b84e61f8ff473dab","name":"waituntil_separate_timeout","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1020,"y":2740,"wires":[]},{"id":"dc0cdc9fe14ea8e1","type":"inject","z":"b84e61f8ff473dab","name":"Device List 1","props":[{"p":"devices","v":"light.tv_light_hue,light.door_bulb_hue,light.globe_light_hue,end","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":130,"y":2460,"wires":[["cb40f23231a4d26e"]]},{"id":"98e92fce6d295276","type":"link in","z":"b84e61f8ff473dab","name":"NEXT","links":["0d7fc763889fcaba","d15e1afd77fc168c","202951defb7e5f21"],"x":150,"y":2400,"wires":[["cb40f23231a4d26e"]],"l":true},{"id":"0d7fc763889fcaba","type":"link out","z":"b84e61f8ff473dab","name":"NEXT","mode":"link","links":["98e92fce6d295276"],"x":950,"y":2520,"wires":[],"l":true},{"id":"126c26d667790842","type":"inject","z":"b84e61f8ff473dab","name":"Stop","props":[{"p":"devices","v":"end","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":110,"y":2580,"wires":[["cb40f23231a4d26e"]]},{"id":"e4d0d300826a0c4a","type":"inject","z":"b84e61f8ff473dab","name":"Device List 2","props":[{"p":"devices","v":"light.door_bulb_hue,light.globe_light_hue,light.tv_light_hue,end","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":130,"y":2520,"wires":[["cb40f23231a4d26e"]]},{"id":"b2a416dc9a65f762","type":"link in","z":"b84e61f8ff473dab","name":"RESET","links":["af69145241bd9b87"],"x":510,"y":2720,"wires":[["87af21aba4d6c287","6a8df29af0d3f859","b30a045490d44ca2","613e6e535283cc93"]],"l":true},{"id":"af69145241bd9b87","type":"link out","z":"b84e61f8ff473dab","name":"RESET","mode":"link","links":["b2a416dc9a65f762"],"x":950,"y":2400,"wires":[],"l":true},{"id":"62cdb4c707691ef7","type":"debug","z":"b84e61f8ff473dab","name":"waituntil_single_ok","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":990,"y":2560,"wires":[]},{"id":"8082d66bdcc10ebf","type":"debug","z":"b84e61f8ff473dab","name":"waituntil_single_timeout","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1010,"y":2600,"wires":[]},{"id":"8a86b0cc.b2bac","type":"server","name":"Home Assistant R","version":5,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":"30","areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m","enableGlobalContextStore":true}]

It is different to the blinds because the blinds take around 20 seconds to fully open or close, and then they report a state change. The lights report the change immediately, so I removed the call service from this flow. The way to test this is:

  1. Edit the device list in the Inject node (Device List 1) with three Hue lights (different light brands might report their state change in a different path, this can be changed in the wait-until parameter inside the wait-until node).
  2. Edit the Light 1, 2 and 3 wait-until nodes with these devices.
  3. Activate the Inject node.
  4. Toggle each light in the order that you set them in the list.

This should always output the message from the first output of the wait nodes (to the debug nodes that end in _ok). This is the way it currently works for me after the update, but it is not dynamic to different sets of devices that can be configured just from the message in the Inject node.

Now, this is the way it worked before:

[{"id":"cb40f23231a4d26e","type":"function","z":"b84e61f8ff473dab","name":"List","func":"if (msg.devices) {\n    var devices = msg.devices.split(\",\");\n    if (devices.length > 0) {\n        var firstDevice = devices.shift();\n        var remainingDevices = devices.join(\",\");\n        var newMsg = { device: firstDevice, devices: remainingDevices };\n        newMsg.data = msg.data;\n        newMsg.topic = msg.topic;\n        newMsg.target = msg.target;\n        node.send(newMsg);\n    } else {\n        node.warn(\"The input msg.devices is empty\");\n    }\n} else {\n    node.warn(\"The input msg.devices is missing\");\n}\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":330,"y":2400,"wires":[["4aa3c040b63441bb","088803648811567d"]]},{"id":"b9a400c262c7dcaa","type":"change","z":"b84e61f8ff473dab","name":"","rules":[{"t":"set","p":"reset","pt":"msg","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":760,"y":2400,"wires":[["af69145241bd9b87"]]},{"id":"72bdc96fe937acf6","type":"debug","z":"b84e61f8ff473dab","name":"waituntil_separate_ok","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1000,"y":2700,"wires":[]},{"id":"4aa3c040b63441bb","type":"switch","z":"b84e61f8ff473dab","name":"end?","property":"device","propertyType":"msg","rules":[{"t":"eq","v":"end","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":510,"y":2440,"wires":[["b9a400c262c7dcaa"],["bc8cdefb63574206","87af21aba4d6c287","74b511448e29fbaa"]]},{"id":"87af21aba4d6c287","type":"ha-wait-until","z":"b84e61f8ff473dab","name":"1) ENABLE EITHER THIS","server":"8a86b0cc.b2bac","version":3,"outputs":2,"entities":{"entity":["{{device}}"],"substring":[],"regex":[]},"property":"data.new_state.last_updated","comparator":"is_not","value":"0","valueType":"str","timeout":"23","timeoutType":"num","timeoutUnits":"seconds","checkCurrentState":false,"blockInputOverrides":true,"outputProperties":[],"entityLocation":"data","entityLocationType":"none","x":690,"y":2520,"wires":[["0d7fc763889fcaba","62cdb4c707691ef7"],["0d7fc763889fcaba","8082d66bdcc10ebf"]]},{"id":"bc8cdefb63574206","type":"debug","z":"b84e61f8ff473dab","name":"next device","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"device","targetType":"msg","statusVal":"","statusType":"auto","x":750,"y":2440,"wires":[]},{"id":"088803648811567d","type":"debug","z":"b84e61f8ff473dab","name":"debug devicelist","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"devices","targetType":"msg","statusVal":"","statusType":"auto","x":540,"y":2360,"wires":[]},{"id":"6a8df29af0d3f859","type":"ha-wait-until","z":"b84e61f8ff473dab","name":"Light 1","server":"8a86b0cc.b2bac","version":3,"outputs":2,"entities":{"entity":["light.tv_light_hue"],"substring":[],"regex":[]},"property":"data.event.new_state.last_updated","comparator":"is_not","value":"0","valueType":"str","timeout":"23","timeoutType":"num","timeoutUnits":"seconds","checkCurrentState":false,"blockInputOverrides":true,"outputProperties":[],"entityLocation":"data","entityLocationType":"none","x":710,"y":2640,"wires":[["0d7fc763889fcaba","72bdc96fe937acf6"],["5943f872251b4383"]]},{"id":"74b511448e29fbaa","type":"switch","z":"b84e61f8ff473dab","d":true,"name":"2) OR THIS","property":"device","propertyType":"msg","rules":[{"t":"eq","v":"light.tv_light_hue","vt":"str"},{"t":"eq","v":"light.door_bulb_hue","vt":"str"},{"t":"eq","v":"light.globe_light_hue","vt":"str"}],"checkall":"true","repair":false,"outputs":3,"x":490,"y":2640,"wires":[["6a8df29af0d3f859"],["b30a045490d44ca2"],["613e6e535283cc93"]]},{"id":"b30a045490d44ca2","type":"ha-wait-until","z":"b84e61f8ff473dab","name":"Light 2","server":"8a86b0cc.b2bac","version":3,"outputs":2,"entities":{"entity":["light.door_bulb_hue"],"substring":[],"regex":[]},"property":"data.event.new_state.last_updated","comparator":"is_not","value":"0","valueType":"str","timeout":"23","timeoutType":"num","timeoutUnits":"seconds","checkCurrentState":false,"blockInputOverrides":true,"outputProperties":[],"entityLocation":"data","entityLocationType":"none","x":710,"y":2680,"wires":[["0d7fc763889fcaba","72bdc96fe937acf6"],["5943f872251b4383"]]},{"id":"613e6e535283cc93","type":"ha-wait-until","z":"b84e61f8ff473dab","name":"Light 3","server":"8a86b0cc.b2bac","version":3,"outputs":2,"entities":{"entity":["light.globe_light_hue"],"substring":[],"regex":[]},"property":"data.event.new_state.last_updated","comparator":"is_not","value":"0","valueType":"str","timeout":"23","timeoutType":"num","timeoutUnits":"seconds","checkCurrentState":false,"blockInputOverrides":true,"outputProperties":[],"entityLocation":"data","entityLocationType":"none","x":710,"y":2720,"wires":[["0d7fc763889fcaba","72bdc96fe937acf6"],["5943f872251b4383"]]},{"id":"5943f872251b4383","type":"debug","z":"b84e61f8ff473dab","name":"waituntil_separate_timeout","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1020,"y":2740,"wires":[]},{"id":"dc0cdc9fe14ea8e1","type":"inject","z":"b84e61f8ff473dab","name":"Device List 1","props":[{"p":"devices","v":"light.tv_light_hue,light.door_bulb_hue,light.globe_light_hue,end","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":130,"y":2460,"wires":[["cb40f23231a4d26e"]]},{"id":"98e92fce6d295276","type":"link in","z":"b84e61f8ff473dab","name":"NEXT","links":["0d7fc763889fcaba","d15e1afd77fc168c","202951defb7e5f21"],"x":150,"y":2400,"wires":[["cb40f23231a4d26e"]],"l":true},{"id":"0d7fc763889fcaba","type":"link out","z":"b84e61f8ff473dab","name":"NEXT","mode":"link","links":["98e92fce6d295276"],"x":950,"y":2520,"wires":[],"l":true},{"id":"126c26d667790842","type":"inject","z":"b84e61f8ff473dab","name":"Stop","props":[{"p":"devices","v":"end","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":110,"y":2580,"wires":[["cb40f23231a4d26e"]]},{"id":"e4d0d300826a0c4a","type":"inject","z":"b84e61f8ff473dab","name":"Device List 2","props":[{"p":"devices","v":"light.door_bulb_hue,light.globe_light_hue,light.tv_light_hue,end","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":130,"y":2520,"wires":[["cb40f23231a4d26e"]]},{"id":"b2a416dc9a65f762","type":"link in","z":"b84e61f8ff473dab","name":"RESET","links":["af69145241bd9b87"],"x":510,"y":2720,"wires":[["87af21aba4d6c287","6a8df29af0d3f859","b30a045490d44ca2","613e6e535283cc93"]],"l":true},{"id":"af69145241bd9b87","type":"link out","z":"b84e61f8ff473dab","name":"RESET","mode":"link","links":["b2a416dc9a65f762"],"x":950,"y":2400,"wires":[],"l":true},{"id":"62cdb4c707691ef7","type":"debug","z":"b84e61f8ff473dab","name":"waituntil_single_ok","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":990,"y":2560,"wires":[]},{"id":"8082d66bdcc10ebf","type":"debug","z":"b84e61f8ff473dab","name":"waituntil_single_timeout","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1010,"y":2600,"wires":[]},{"id":"8a86b0cc.b2bac","type":"server","name":"Home Assistant R","version":5,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":"30","areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m","enableGlobalContextStore":true}]

(I just disabled the “2)” wait-until node and enabled the “1)”).
As I explained before with the blinds, this is working correctly only for th first device it receives (goes to the _ok debug node), and then with the next device, it gets stuck and timeouts.

This makes me remember that I don’t know if this is the right way to check for a state change. I’m setting the wait-until node to wait until the data.new_state.last_updated attribute “is not 0”.

Any help on trying to make this work as before is appreciated. Thanks!
Rodrigo

To say that a node does not work as expected, without providing the context of the flow, message data being passed, and the entire story of what you want to achieve makes it impossible to debug or provide help. Now that we have some actual flows, these can be replicated and investigate properly - thank you!

As I understand, the flow is intended to identify a list of entities requiring attention (blinds need closing), to pass each entity from the list singularly to an Action node (close the blind), use the Wait Until node to wait until the blind responds as closed, then loop back to pick the next entity from the list. Repeat until done.

As I understand, the simplistic test-bed flow takes a list of things (switches will do) and loops through them, waiting for each entity (switch) to change property, before triggering then looping back to the next entity in the list.

The conjecture is that the Wait Until node is not working.

I have set up a similar flow to your test, in a machine with the latest updates, and I can confirm that it all works for me, as I would expect, with each entity change causing the Wait Until node to trigger in turn, as expected.

My conjecture is that the way you are attempting to use the node is the problem.

Here is my test flow.

I call a list of two smart plugs (switches), the flow picks the first from the list, sets the Wait Until node to wait until the switch state object is updated, then loops round for the next entity, and repeats until the list is empty.

Here is the flow, in case you want to try this for yourself.

[{"id":"5a6e9d7abd7ada4e","type":"inject","z":"3d7cf3b151968f34","name":"Device List","props":[{"p":"devices","v":"switch.t2_2,switch.t3_2","vt":"str"},{"p":"list","v":"$split(devices,\",\")","vt":"jsonata"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":120,"y":3080,"wires":[["473e9b7e43625363"]]},{"id":"0d800e638c79ca63","type":"switch","z":"3d7cf3b151968f34","name":"Until list is empty","property":"payload","propertyType":"msg","rules":[{"t":"nempty"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":510,"y":3080,"wires":[["6dc2575d4549d0db"],["ec78cd0c67170ccd"]]},{"id":"473e9b7e43625363","type":"change","z":"3d7cf3b151968f34","name":"Pick entity","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\t   \"entities\": list[0]\t}","tot":"jsonata"},{"t":"set","p":"list","pt":"msg","to":"list[[1..$count($$.list)]]","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":290,"y":3080,"wires":[["0d800e638c79ca63","ef352fe25636be99"]]},{"id":"709de0a68a6913dc","type":"link in","z":"3d7cf3b151968f34","name":"NEXT","links":["e290ca8a62b12c75"],"x":130,"y":3120,"wires":[["473e9b7e43625363"]],"l":true},{"id":"e290ca8a62b12c75","type":"link out","z":"3d7cf3b151968f34","name":"NEXT","mode":"link","links":["709de0a68a6913dc"],"x":1030,"y":3060,"wires":[],"l":true},{"id":"54f0fa6fe33fd57f","type":"server-state-changed","z":"3d7cf3b151968f34","name":"","server":"","version":6,"outputs":1,"exposeAsEntityConfig":"","entities":{"entity":["switch.t2_2","switch.t3_2"],"substring":[],"regex":[]},"outputInitially":true,"stateType":"str","ifState":"","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":false,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":290,"y":2980,"wires":[["26a49bba6d54dc2b"]]},{"id":"26a49bba6d54dc2b","type":"debug","z":"3d7cf3b151968f34","name":"Watch state change","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"data.entity_id & \" has gone \" & payload","targetType":"jsonata","statusVal":"data.entity_id & \" has gone \" & payload","statusType":"auto","x":590,"y":2980,"wires":[]},{"id":"6dc2575d4549d0db","type":"ha-wait-until","z":"3d7cf3b151968f34","name":"Wait in turn","server":"","version":3,"outputs":2,"entities":{"entity":[],"substring":[],"regex":[]},"property":"state","comparator":"jsonata","value":"$millis()-$toMillis($entity().last_updated)<7000","valueType":"jsonata","timeout":"30","timeoutType":"num","timeoutUnits":"seconds","checkCurrentState":false,"blockInputOverrides":false,"outputProperties":[],"x":710,"y":3080,"wires":[["c4e611a5b7f454ec","f247ea0dbf7d6e1a"],["25b64679aa79ae07"]]},{"id":"c4e611a5b7f454ec","type":"debug","z":"3d7cf3b151968f34","name":"Responded","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":910,"y":3000,"wires":[]},{"id":"25b64679aa79ae07","type":"debug","z":"3d7cf3b151968f34","name":"Timed out","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":900,"y":3120,"wires":[]},{"id":"ef352fe25636be99","type":"debug","z":"3d7cf3b151968f34","name":"each entity","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload.entities","targetType":"msg","statusVal":"payload","statusType":"auto","x":290,"y":3120,"wires":[]},{"id":"f247ea0dbf7d6e1a","type":"delay","z":"3d7cf3b151968f34","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":900,"y":3060,"wires":[["e290ca8a62b12c75"]]},{"id":"ec78cd0c67170ccd","type":"debug","z":"3d7cf3b151968f34","name":"stop","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"\"All Done\"","targetType":"jsonata","statusVal":"\"All Done\"","statusType":"auto","x":530,"y":3120,"wires":[]}]

So why can I make it work and your flow appears to fail? Three reasons I think.

First, the use of templates. It is a hard fact that whilst Home Assistant runs on templates, Node-RED does not support them. There are a few nodes where it is documented that templates can be used, however the nodes and UI fields are very limited and correct functioning is not assured. I would strongly advise anyone using Node-RED to avoid templates and use JSONata for data management and the documented input-overrides provided for nodes that permit this method of dynamically setting the UI fields.

The Wait Until node accepts msg.payload as an object with “entities” field for a singular entity id in place of the UI entity field. I have used a bit of JSONata to capture the string list into an array, then at each loop
list[0] picks of the first item in the list, and constructs the correct input object for msg.payload to set the entity id. The remaining part of the list can be extracted simply by list[[1..$count($$.list)]] which may look incomprehensible but simply takes the array from index 1 to the end, thus doing the work of your function node.

This now removes the need for using {{device}} to set the entity.

Second, the Wait Until state property is incorrect.
I don’t like using this node, simply because every time I do it freezes my webpage. The property UI box does a global search for every single property in every single entity. For the ‘state’ property this is a) easy to type, quickly before my screen freezes, and b) guaranteed as every entity has a state.

You are using data.new_state.last_updated which is I believe only related to the Event: state node. The Event: state node (triggers from a state/attriibute change) will return an event object in the msg.data field with both old_state and new_state objects, being the pre and post entity object and each include state and last_changed/updated fields. When using the output from the Event: state node, we have to specifiy which object we want.
The Wait Until node outputs just the entity object. You can check this by setting an output property and you will see only the ‘entity’ option, which returns the singular post-trigger entity object.

I believe that the correct property to use in your case would be last_updated. This, of course, would explain why the node is not responding - it is looking at the property change of a completely different node.

Third, the conditional test look to me as though it should never work. The last_updated entity field has an ISO UTC timestamp string, hence 2024-10-11T13:04:17.831329+00:00 which cannot be compared to “0”. It can’t actually be compared to very much of real use.

My presumption is that you wish for the Wait Until node to trigger when the entity in question updates for any reason. A test for ‘state’ as ‘on’ or ‘off’ would work, hence state in [“on”, “off”] or something similar.

A direct conditional test against the last_updated field would be difficult, hence I have changed the conditional test to use a JSONata predicate. Rather than test

‘property’ ‘is’ ‘value’

the JSONata expression option allows for entering an expression which needs to evaluate as either true or false. When the expression is evaluated and returns true, the node will fire, treating this as the ‘until’ in the Wait Until action.

I have used

$millis()-$toMillis($entity().last_updated)<7000

This get the Unix millisecond timestamp integer for now, and subtracts the millisecond equivalent of the node’s entity, last _updated field. If this is less than seven seconds, the Wait node will trigger.

When the entity object under consideration (the entity-id of the node) changes, the last_updated field will be set, and the node will run the ‘until’ condition test, thus evaluating the JSONata expression and testing for true. I have allowed 7 seconds here since my switches, when manually operated, can take up to 4 or 5 seconds before the change is seen.

This test works, for any change hence the blind could be closed, open, or just updated, hence it may not be quite what you want, however it is working for me.

And finally. Since the Wait Until node is just a message gatekeeper, when the ‘until’ condition is met, the node simply passes on the orginal message it first received. As is, and without any change. Yes you can add output properties if you so wish, but these are only set on successful trigger, and you should use the $entity() JSONata function which operates on the post-event entity object as is.

I hope this has been of some use.

1 Like

Whoa! Many thanks @Biscuit for your dedication, this really helps a lot. Not only did you solve my main issue, you also improved many things in my flow that are easier to understand and implement now.

I use templates a lot in call-service nodes and also in some current-state nodes. In fact, there are some nodes (both call-service and current-state nodes) that I deleted from the flow example because they were working fine. But now, I needed to update their {{device}} template to {{payload.entities}} for the flow to work. I tried just leaving the field alone and checking if they accepted the msg.payload object but they don’t seem to work this way. Any recommendations here?

This might be the explanation, but it’s weird that it worked before for more than a year. Anyway I recognize that when I created this flow I just searched for the first solution that worked, I understand that I am not as advanced to have found the correct way of using it.

Thanks a lot again, I already updated my flow and it seems to be working fine.
R.

The simple answer is, I used a different way of holding and processing the list of devices (entities) and slightly different message variables, so for each item the ‘device’ would be in msg.payload.entities.

That said…

Home Assistant is a ‘state-machine’ that responds to events, and as such it does not of itself include a data processing language. Jinja templating is provided across Home Assistant, and yes I use templates in sensors and automation. At the simplest, templates perform string-substitution, so any string with {{ states('sensor.temp')|int }} will be processed and the ‘{{ }}’ section replaced with the outcome of the expression.

Node-RED does not inherently support templating in any way. There is one core node, the Template Node, that will use Jinja, and other contrib-nodes may, where documented, provide Jinja or Mustache templating ability on some UI fields. The WebSocket nodes that we are discussing here are an example where the author of these nodes has added Mustache Template processing to certain inputs.

Clearly, as everyone uses templating for almost everything in Home Assistant, it is understandably natural to assume that you can / should / must use templates in Node-RED. I am going to rise above the parapet and say that you should never use templates in Node-RED at all, for the following reasons:

  • Templating is not provided in Node-RED by default. It is an add-on.
  • Templating is not good at data processing (even the Jinja website says it should not be used for this)
  • Templating does not work with JS objects at all well, if at all
  • Only fully documented templating opportunities will be supported, and there are situations where templating has/is being used by accident, and these are subject to deprecation without notice
  • Node-RED has much better (cleaner / easier / supported) options

I accept that, where template availability is documented, the use of "My dog has no {{payload}}" is a quick and easy way to get msg.payload value dynamically into a node UI.
However this requires getting msg.payload into the format you want first, and it does not generally work well with objects.

Stepping back, your flow is all about a list of things. You first build a list of devices, then you iterate through the list doing a task. At each item you want to obtain the entity-id, then use the entity-id. So your original flow works by using JavaScript in a Function node to build the list, then pick each one in turn. Then you use templates to insert this into the UI fields as required.

I would welcome an opportunity to encourage you (and anyone else reading this later…) to switch to an alternative approach.

First step - using variables in node UI

All nodes have UI input. How each node works and what it accepts in each UI field is down to how the node was written. Many nodes offer the ability to input variables into UI fields by using the input message itself. This is widely available, much easier to use, and more reliable than templates. If you check the documentation on any node, you may see an “Input” section detailing what, if anything, can go into the node in the input message that the node can see and use.

The HA WebSocket nodes use msg.payload in may places as an ‘override’ input. This works by the node looking at msg.payload to see if it is an object (not a primitive value). If it is, the node looks at the keys in the object. Certain documented key values may then be used in place of the UI input.

Call Service [this is now the Action node] accepts (there is a missing “,” in this structure in the docs):

{
    "action": "homeassistant.turn_on",
    "target": {
        "floor_id": ["first_floor"],
        "area_id": ["kitchen"],
        "device_id": ["8932894082930482903"],
        "entity_id": ["light.kitchen", "switch.garage_light"],
        "label_id": ["outdoor_lights"]
    },
    "data": {
        "brightness_pct": 50
    }
}

Current State accepts

payload.entityId

Wait Until accepts

{
  "entities": {
    "entity": ["light.living_room", "light.bedroom"],
    "substring": ["light."],
    "regex": ["light.*"]
  },
  "property": "state",
  "comparator": "is",
  "value": "on",
  "valueType": "str",
  "timeout": 10,
  "timeoutUnits": "seconds",
  "checkCurrentState": false
}

There is more detail in the individual node documentation, but the gist is that there is no need to use templates if you set the input msg.payload as an object with the dynamic UI variables you want.

The plan is, get the list of things you want to work on, hold this list in the message (not in msg.payload as we are going to use that) and for each node in turn, construct the necessary input msg.payload object.

Note: there is a degree of inconsistency in what each node accepts, and the most used entity-id field come as entity-id, entity_id, entityID, entitiy, entities, entities {“entity”} so you do have to read the documentation for each node. Yes, I admit templates are easier…

Second - use JSONata

JSONata is the ‘official’ data processing language of Node-RED. It is fully supported, and available across a wide range of core and contrib nodes. It is a powerful tool and performs JS object/array processing with ease.
The downside is that it is quite different and can be [understatement] challenging to learn…

Many Node-RED users turn to a Function node to do data process, when there is a perfectly good (but difficult to learn) tool already there. JSONata will work in the Inject node, the Change node, the Switch node, and is widely avaiable and fully supported in the HA WebSocket nodes.

If you look at each (well, almost all) of the WebSocket nodes, you will see that they all permit some form of msg.payload.object input override, they all offer the use of JSONata to process UI input fields, and many provide Output properties section which can be used to manipulate message objects using JSONata as the message leaves the node.

This way, we can hold our list of devices, and for each node pre-process the UI fields before input to pick one of the list, process the data as we go, and post-process the message data ready for the next node.

Breaking down your flow:

Getting a list of devices from the string literal input

(
    $devices:="switch.t1, switch.t2, switch.t3";
    $d:= $replace($devices, " ", "");
    $split($d, ",");

)

You can bung this into any Change node and set msg.list using J: JSONata expression. I personally like to use “, " with a space, so my code did not find the second item as it was looking for " switch.t2” and I have added a bit to strip spaces out first.

This gives:

[
  "switch.t1",
  "switch.t2",
  "switch.t3"
]

Neat huh?!

With msg.list holding this (throughout the flow), getting the first item off the list is just list[0], hence if you want to get the current state of the first entity, use a Change node and JSONata to set msg.payload to

{"entityId": list[0]}

before this goes into a Current State node. Simple! In the same Change node you can modify the remaining list using

list[[1..$count($$.list)]]

which pops the first item off the list. Eventually the list will become empty, and JSONata will return nothing. This is not null, it is not an error, just nothing at all. There will be no msg.list, so your looping Switch node needs to be setup to test for this correctly.

To perform an action, based on the entity from the Current State node, you must first uncheck “Block input overrides” in the Action node. Then you can use the following to set up the Action call - this code goes into an output property to set msg.payload using JSONata in the preceding Current State node!

{
   "action": "switch.turn_off",
   "target": {
       "entity_id": [$entity().entity_id]   
   } 
}

The code will re-pick the entity-id from the Current State node using $entity().entity_id, and construct the necessary input object. This holds all the settings for the Action node, and you do not need to put anything into the action node for this to work - all the UI inputs come from the input message.

The final step is to set up the Wait Until node, which I can do again using an output property in the Action node, with JSONata expression setting msg.payload as

{"entities": payload.target.entity_id[0]}

I could have set msg.myentity right at the start of the flow and used that each time, but this is an exercise to show how JSONata can be used in each node in the output property to set up the input required for the next node, using just the information to hand.

Does it work?

[{"id":"a81e9873e56cb482","type":"api-current-state","z":"3d7cf3b151968f34","name":"","server":"","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","entity_id":"","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"{\t   \"action\": \"switch.turn_off\",\t   \"target\": {\t       \"entity_id\": [$entity().entity_id]   \t   } \t}","valueType":"jsonata"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":380,"y":3640,"wires":[["5756e59e5a70f2ab"]]},{"id":"961439d00ceb0157","type":"inject","z":"3d7cf3b151968f34","name":"Start the list","props":[{"p":"list","v":"(     $devices:=\"switch.t1, switch.t2, switch.t3\";     $d:= $replace($devices, \" \", \"\");     $split($d, \",\");  )","vt":"jsonata"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":170,"y":3600,"wires":[["f3044679d600927c"]]},{"id":"5756e59e5a70f2ab","type":"api-call-service","z":"3d7cf3b151968f34","name":"","server":"","version":7,"debugenabled":false,"action":"","floorId":[],"areaId":[],"deviceId":[],"entityId":[],"labelId":[],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"{\"entities\": payload.target.entity_id[0]}","valueType":"jsonata"}],"queue":"none","blockInputOverrides":false,"domain":"switch","service":"turn_on","x":550,"y":3640,"wires":[["31cb35b2a650526e"]]},{"id":"31cb35b2a650526e","type":"ha-wait-until","z":"3d7cf3b151968f34","name":"Wait in turn","server":"","version":3,"outputs":2,"entities":{"entity":[],"substring":[],"regex":[]},"property":"state","comparator":"jsonata","value":"$millis()-$toMillis($entity().last_updated)<7000","valueType":"jsonata","timeout":"30","timeoutType":"num","timeoutUnits":"seconds","checkCurrentState":false,"blockInputOverrides":false,"outputProperties":[],"x":850,"y":3640,"wires":[[],[]]},{"id":"f3044679d600927c","type":"change","z":"3d7cf3b151968f34","name":"Pick item from list","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"entityId\": list[0]}","tot":"jsonata"},{"t":"set","p":"list","pt":"msg","to":"list[[1..$count($$.list)]]","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":170,"y":3640,"wires":[["a81e9873e56cb482"]]}]

Works perfectly. This is just the main bit, no checks on the state, no looping. Just three WebSocket nodes chained together - no templates, no function nodes.

If you have a large investment in many WebSocket nodes, all using templates, then clearly you are not going to invest time and effort learning JSONata and shifting all your code to this approach. However I would like to encouage anyone who can spare the time to try moving away from templating to using JSONata. This is not to say it is entirely without issues, since you can already see that each node-input structure is slightly different, and they do change with updates to the WebSocket nodes. My conjecture is

  • this approach is fully supported going forward
  • this approach can manipulate complex object with ease
  • this is (potentiall) neater and more efficient

Just my opinion, and this post does not reflect the views of anyone else.

Good luck!

2 Likes