Help setting up a customizable delay node (controlled via Lovelace)

Hi everyone,
I recently started using a little bit more Node-RED in my HA, and stumbled upon a problem that I couldn’t solve with a simple google search so here I am:
I am trying to set up a ‘delay node’ that will be controlled via Lovelace via home assistant helpers. I have set up an automation helper called “Fan duration” and I can read its values in node-red via state changed node. My main automation is a simple action that starts a fan every two hours and then turns it off after 30 seconds. I want to customize those 30 seconds to be easily changeable via lovelace.

Current situation: Main loop works well, the fan starts every 2 hours and turns off after 30 seconds. Changing the state of “fan duration” results in sending a msg.delay to delay node and an expected delay of X seconds.

Two issues here:

  • The delay node starts upon receiving the msg.delay - it should only start upon receiving the signal from the ‘main loop’ / ‘store’ the value from state changed node
  • The supplied msg.delay isn’t taken into account when delay node is triggered via the ‘svc: switch:turn_on’ aka the main loop and uses the default 30 seconds

Here is my flow:

[{"id":"577f93c7.ee91bc","type":"debug","z":"a56901d8.cb95c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"delay","targetType":"msg","statusVal":"","statusType":"auto","x":1050,"y":280,"wires":[]},{"id":"77cee045.4b177","type":"server-state-changed","z":"a56901d8.cb95c","name":"State changed: Fan duration","server":"3d0bc4e7.618a6c","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_number.air_exchanges_per_day","entityidfiltertype":"exact","outputinitially":false,"state_type":"num","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"x":280,"y":220,"wires":[["764e28d.2a67cd8"]]},{"id":"b8eccb98.3ae318","type":"delay","z":"a56901d8.cb95c","name":"variable delay","pauseType":"delayv","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":820,"y":360,"wires":[["577f93c7.ee91bc","1e60f318.6f084d"]]},{"id":"764e28d.2a67cd8","type":"change","z":"a56901d8.cb95c","name":"","rules":[{"t":"move","p":"payload","pt":"msg","to":"delay","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":330,"y":280,"wires":[["7a4008c7.c7f258"]]},{"id":"7a4008c7.c7f258","type":"function","z":"a56901d8.cb95c","name":"Multiply msg.delay * 1000","func":"msg.delay = msg.delay * 1000;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":550,"y":280,"wires":[["b8eccb98.3ae318"]]},{"id":"d65b8954.6bae08","type":"api-call-service","z":"a56901d8.cb95c","name":"","server":"3d0bc4e7.618a6c","version":1,"debugenabled":false,"service_domain":"switch","service":"turn_on","entityId":"switch.air_exchange_intake_fan","data":"","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":570,"y":360,"wires":[["b8eccb98.3ae318"]]},{"id":"1e60f318.6f084d","type":"api-call-service","z":"a56901d8.cb95c","name":"","server":"3d0bc4e7.618a6c","version":1,"debugenabled":false,"service_domain":"switch","service":"turn_off","entityId":"switch.air_exchange_intake_fan","data":"","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":1070,"y":360,"wires":[[]]},{"id":"b5be1798.4a4de8","type":"inject","z":"a56901d8.cb95c","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":340,"y":360,"wires":[["d65b8954.6bae08"]]},{"id":"92ba5148.16d","type":"inject","z":"a56901d8.cb95c","name":"Run every 2h","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"3600","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":320,"y":440,"wires":[["d65b8954.6bae08"]]},{"id":"3d0bc4e7.618a6c","type":"server","name":"Home Assistant","addon":true}]

Sorry if this is a basic question, I just can’t figure this out and would appreciate some pointers. Thank you!

You need to have the delay node set to allow delay overrides:

image

Your flow code doesn’t match the image, so I can only suggest that you examine the path between svc: switch.turn_on and the delay node.

1 Like

Your flow code doesn’t match the image, so I can only suggest that you examine the path between svc: switch.turn_on and the delay node.

Hi Bill,
Sorry I must have pasted the wrong code somehow - this should be fixed now!

I’m not sure if I made myself clear enough - I have set the delay node to accept msg.delay and this is working well - I can have a variable delay set properly when the message comes from state_changed node. The problem is that when the delay node is triggered via ‘svc: switch:turn_on’ node it uses the default 5 second delay. I’m not sure how I can set this up to use the custom value when triggered this way.

Also, I would like to fix the fact that the flow starts when I change the value in lovelace. So my goal is to have this:

  • main loop (fan turns on → delay → turns off) every 2 hours
  • delay can be customized via lovelace (via input number helper - slider)
  • each time the main loop repeats it reads the input number helper and uses that value as the ‘delay’
  • the whole loop DOESN’T start itself upon any slider value change

Hope this makes it a little bit clearer. Thanks a lot for taking time to help out
EDIT: I seem to have failed to copy “Run every 2h” node but this is fixed now as well

Gotcha. Ok, this should fix it:

[{"id":"b74406b1.234ea8","type":"change","z":"611f2c9c.6217e4","name":"Set Flow Delay","rules":[{"t":"set","p":"delay","pt":"flow","to":"$.payload * 1000","tot":"jsonata"},{"t":"set","p":"delay","pt":"msg","to":"delay","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":680,"y":220,"wires":[["b8eccb98.3ae318"]]}]

Instead of the function node, I setup a change node that does two things: Converts the input from the input_text to a flow variable (delay) and did the math there for that (* 1000). I also added msg.delay (from flow.delay) into the node as well.

I also added a change node in front of the switch that checks if the flow variable exists, and if not, sets the msg.delay to 5000.

[Edit] I didn’t test it, so you might need to tweak a few things here and there. But otherwise, it should work as intended.

[Edit2] Another way to accomplish this would be to have the switch go into the the Set Flow Delay node and have that check if payload is a number (using JSONata).

1 Like

Thanks Bill. I just tested this and unfortunately it’s not doing what I originally meant. Running the main loop (I’m testing with simple inject node) causes the delay to revert to default 5 seconds. I would like this delay to use the value found in msg.delay, but I’m not sure how to set this up. Maybe I recreated the bottom change node incorrectly, would you mind sharing it as well?

Anyway I need to read up on flow/node variables. Thanks for all the guidance!

1 Like

I think I fixed it!

Here’s the code, I had to change the payload of the inject nodes and now it seems to be working ok! There’s a minor issue that setting the ‘fan duration’ via Lovelace causes the delay to start and trigger turn_off node afterwards - but since this is just a fan turn off node nothing really happens with the physical device.

[{"id":"577f93c7.ee91bc","type":"debug","z":"a56901d8.cb95c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"delay","targetType":"msg","statusVal":"","statusType":"auto","x":1090,"y":280,"wires":[]},{"id":"77cee045.4b177","type":"server-state-changed","z":"a56901d8.cb95c","name":"State changed: Fan duration","server":"3d0bc4e7.618a6c","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_number.air_exchanges_per_day","entityidfiltertype":"exact","outputinitially":false,"state_type":"num","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"x":360,"y":300,"wires":[["b74406b1.234ea8"]]},{"id":"b8eccb98.3ae318","type":"delay","z":"a56901d8.cb95c","name":"variable delay","pauseType":"delayv","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":860,"y":360,"wires":[["577f93c7.ee91bc","1e60f318.6f084d"]]},{"id":"d65b8954.6bae08","type":"api-call-service","z":"a56901d8.cb95c","name":"","server":"3d0bc4e7.618a6c","version":1,"debugenabled":false,"service_domain":"switch","service":"turn_on","entityId":"switch.air_exchange_intake_fan","data":"","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":330,"y":360,"wires":[["f8a0978e.4f6eb8"]]},{"id":"1e60f318.6f084d","type":"api-call-service","z":"a56901d8.cb95c","name":"","server":"3d0bc4e7.618a6c","version":1,"debugenabled":false,"service_domain":"switch","service":"turn_off","entityId":"switch.air_exchange_intake_fan","data":"","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":1070,"y":360,"wires":[[]]},{"id":"b5be1798.4a4de8","type":"inject","z":"a56901d8.cb95c","name":"","props":[{"p":"delay","v":"delay","vt":"flow"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"str","x":150,"y":320,"wires":[["d65b8954.6bae08"]]},{"id":"92ba5148.16d","type":"inject","z":"a56901d8.cb95c","name":"Run every 2h","props":[{"p":"delay","v":"delay","vt":"flow"}],"repeat":"3600","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":120,"y":360,"wires":[["d65b8954.6bae08"]]},{"id":"b74406b1.234ea8","type":"change","z":"a56901d8.cb95c","name":"Set Flow Delay","rules":[{"t":"set","p":"delay","pt":"flow","to":"$.payload * 1000","tot":"jsonata"},{"t":"set","p":"delay","pt":"msg","to":"delay","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":640,"y":300,"wires":[["b8eccb98.3ae318"]]},{"id":"f8a0978e.4f6eb8","type":"change","z":"a56901d8.cb95c","name":"Set flow.delay to msg.delay","rules":[{"t":"set","p":"delay","pt":"flow","to":"delay","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":600,"y":360,"wires":[["b8eccb98.3ae318"]]},{"id":"3d0bc4e7.618a6c","type":"server","name":"Home Assistant","addon":true}]

Thanks again for you help Bill!

1 Like

Well, an easy solution would be to have the input_text only trigger setting the flow variable. But otherwise, yeah, sending the turn_off more than once isn’t going to really affect anything.

Glad I could be of help!

I spend some extra time and re-wrote the flow. Turns out it was possible to meet all the requirements using different HA nodes. I swapped state_changed node to current_state and managed to pull this off. To anyone looking for something similar you can find my flows below:

[{"id":"6655e6c8.afe1e8","type":"debug","z":"9c80586e.6046e8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"delay","targetType":"msg","statusVal":"","statusType":"auto","x":930,"y":400,"wires":[]},{"id":"32d7709.2b44c9","type":"delay","z":"9c80586e.6046e8","name":"variable delay","pauseType":"delayv","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":640,"y":500,"wires":[["6655e6c8.afe1e8","465f1d80.a0e634"]]},{"id":"fa81b431.8c30f8","type":"api-call-service","z":"9c80586e.6046e8","name":"","server":"3d0bc4e7.618a6c","version":1,"debugenabled":false,"service_domain":"switch","service":"turn_on","entityId":"switch.air_exchange_intake_fan","data":"","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":330,"y":500,"wires":[["d8dfe0b4.b1481"]]},{"id":"465f1d80.a0e634","type":"api-call-service","z":"9c80586e.6046e8","name":"","server":"3d0bc4e7.618a6c","version":1,"debugenabled":false,"service_domain":"switch","service":"turn_off","entityId":"switch.air_exchange_intake_fan","data":"","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":930,"y":500,"wires":[[]]},{"id":"18e22349.60c6cd","type":"inject","z":"9c80586e.6046e8","name":"","props":[{"p":"delay","v":"delay","vt":"flow"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"str","x":150,"y":420,"wires":[["fa81b431.8c30f8"]]},{"id":"5355b81c.7dfd28","type":"inject","z":"9c80586e.6046e8","name":"Run every 2h","props":[{"p":"delay","v":"delay","vt":"flow"}],"repeat":"3600","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"str","x":120,"y":500,"wires":[["fa81b431.8c30f8"]]},{"id":"d8dfe0b4.b1481","type":"api-current-state","z":"9c80586e.6046e8","name":"Read Air exchange time","server":"3d0bc4e7.618a6c","version":1,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","override_topic":false,"entity_id":"input_number.air_exchanges_per_day","state_type":"str","state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","blockInputOverrides":false,"x":630,"y":320,"wires":[["127e70d6.a4e80f"]]},{"id":"127e70d6.a4e80f","type":"change","z":"9c80586e.6046e8","name":"Change payload to delay","rules":[{"t":"move","p":"payload","pt":"msg","to":"delay","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":630,"y":380,"wires":[["a9c356c7.1b8488"]]},{"id":"a9c356c7.1b8488","type":"function","z":"9c80586e.6046e8","name":"Convert ms to s","func":"msg.delay = msg.delay * 1000;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":640,"y":440,"wires":[["32d7709.2b44c9"]]},{"id":"3d0bc4e7.618a6c","type":"server","name":"Home Assistant","addon":true}]

Similar question… I have a delay but I want to be able to break (or cancel) the delay if another specific action occurs…

E.g. if I turn off the extractor manual override after 30minutes I need that delay to be killed

Anyone ideas on how to do this?

I might be answering my own question here, but I think using a trigger rather than delay works - then send msg.reset out of the initial trigger to the input of the trigger I want to stop…

I fall on this thread and it helped me setting a customizable delay node.
I improved the given solution by using only one node for reading and the value and converting to ms :
image

Hope this help others.