I have a repeat until loop with a condition that checks some MQTT data, however one of the iterations could generate a notification, and after that, I need to stop everything since the purpose of repeating has completed.
Since there can be a number if different conditions that could lead to the notification, it’s not something I can (or want to) build in to the until condition. I need some way to break out of the loop by either altering a variable, or having some exit method (like a break call in many programming languages).
I know this isn’t the first time someone’s asked this, however I didn’t find anyone with a suitable solution. I can’t use a helper because that won’t work for a script that can run in parallel. Variable scope means I can’t change a variable from false to true so that I could add it to the until condition.
I’d like to accomplish this without having to resort to additional 3rd party components; IMHO this should be accomplishable using existing HA/Jinja scripting, but I must be missing something. I know Jinja supports variable namespaces, however I don’t know how I would put arbitrary Jinja code in to my YAML to define/set such a variable.
In this blueprint I solved it by breaking up the repeat loop into multiple short waits so that id the interrupt happened, I would not have to wait the fill time.
Thanks but that won’t work in my case. My situation is for Frigate MQTT events. I get repeated MQTT data for an event which is considered an “update” to what I got before.
I have a wait that listens for the next MQTT event and then I parse the JSON. If it’s not what I need, I wait for the next… until I get one that meets all my criteria and then I trigger a notification to devices. Once I do that, I no longer need to run any code.
However, the condition I have is to repeat until I get the final MQTT event (they all have a matching ID inside the JSON data) because that’s the only condition I know for certain I should expect to close out the whole scenario. So, after a notification goes out, it keeps looping until the final message is received. Sometimes this generates an error because I have timeouts configured as well. To prevent erros and needless cycles, I just want to stop the script once a notification is sent. A stop action doesn’t work either because it only stops the current chain… it still loops.
If you want someone to help with that you need to share code. I don’t think I can help, but that is a paragraph of a lot of words and there is no way to troubleshoot it or suggest changes from code descriptions.
Besides repeat for a short time, Check if you got the code check you wanted, check if you want to try again, and loop that if you do would seem to be a perfectly reasonable way to do shorten your wait after the event has happened. Call me crazy.
you could try using script.turn_off
that’s kinda a big sledge hammer, but maybe it’ll do what you want.
note that it will stop the script if the script has a delay or wait in it. it won’t kill it mid execution of an action.
Will script.turn_off work for an automation? I noticed automations have a turn_off action as well but that seems to be like a disable than a stop. If it only exits/stops execution that could work.
@WallyR I can’t use a boolean helper if the automation/script can run in parallel, because then it would run in to an issue of not knowing which active instance toggled that boolean. It needs to be something within the scope of the running instance only.
In the situation I have, timing can be arbitrarily very short (like milliseconds between execution of a wait event in my loop condition) or long. So although the delay trick you used is really neat, timing isn’t a method I can effectively use to short out of my loop.
I also would rather not get in to specifics of my exact situation too much, but rather use it to set up an extreme scenario of “what could be done in a case like this” so that hopefully everyone can benefit from some ultimate solution of how to break out of a loop effectively, regardless of specific details of their situation. Also, this thread could possibly serve as a resting place for all the different ways and ideas people have used.
automation.turn_off isn’t a viable option as it will turn off the automation (disable it), and also stop all currently active processes, not just the current one.
What I have now after playing with things is my until condition checks for my normal condition to exit, but also looks at wait.remaining since I have a wait inside my loop and it does not continue on timeout, so wait.remaining will always be null from that. Then, after I send out my notification, I have a wait template with a 0s timeout and it simply returns false (so it always times out). This causes wait.remaining to be 0 instead of null which I test for and then exit my until loop.
- repeat:
sequence:
- alias: Wait for an update to the Frigate event
wait_for_trigger:
- alias: When an update to the Frigate event is received
topic: frigate/events
payload: "{{ id }}"
value_template: "{{ value_json.after.id }}"
trigger: mqtt
continue_on_timeout: false
<conditions go here>
<send notification goes here>
- alias: Stop automation instance
wait_template: "{{ false }}"
continue_on_timeout: true
timeout: "00:00:00"
until:
- alias: Confirm Frigate event is complete
condition: template
value_template: >-
{{ wait.remaining | is_defined == 0 or
wait.trigger.payload_json.type | is_defined == 'end' }}
I will go on record to state I really don’t like this. It’s a terrible hack, but it’s necessary because HA gives us no way to redefine variables which would have easily solved this issue, or if the stop action had the ability to stop everything, not just the current sequence.
Event processing is typically handled by the automation’s trigger(s).
For certain applications, additional event processing is performed in the automation’s actions when there’s a need to process specific events in series.
What’s unusual is to process events in an automation’s actions with a repeat (what your example is doing).
I don’t understand the point about automation turnoff. Turn it off and turn it back on. That stops the current run the enables it again
another way is to create a stop trigger. Can be a input_boolean or momentary button. And on that trigger, just exit. If you set the automation mode to restart, triggering on that stop trigger will cause the automation currently running to stop.
If I turn the script off (in this case it’s actually an automation, but same deal), how do I turn it back on again? The processing stopped.
I’d then need a trigger or automation to watch for the script being turned off every time, just to turn it back on - all because I need to exit the automation to try and save a few processing loops in certain situations where it continues to loop after sending a notification? The net gain would be nothing and just add unnecessary complexity.
All of this because there’s no way to update an automation scoped variable’s value
Here is the full automation as it is currently, using the hack to wait.remaining.
alias: Security - Person at front door
description: ""
triggers:
- topic: frigate/events
payload: Front_doorbell/new
value_template: "{{ value_json.after.camera }}/{{ value_json.type }}"
variables:
id: >-
{{ trigger.payload_json.after.id if trigger is defined and
trigger.payload_json is defined else 'unknown' }}
trigger: mqtt
conditions:
- alias: Confirm Frigate event is new
condition: template
value_template: "{{ id != 'unknown' and trigger.payload_json.type | is_defined == 'new' }}"
- condition: state
entity_id: binary_sensor.lumi_lumi_sensor_magnet_aq2_opening
state: "off"
actions:
- alias: Wait for a Person on the Doorstep
repeat:
sequence:
- alias: Wait for an update to the Frigate event
wait_for_trigger:
- alias: When an update to the Frigate event is received
topic: frigate/events
payload: "{{ id }}"
value_template: "{{ value_json.after.id }}"
trigger: mqtt
continue_on_timeout: false
- alias: If a person is at the doorstep, send a notification
if:
- alias: Confirm Frigate detected zone is Doorstep
condition: template
value_template: >-
{{ 'Doorstep' in (wait.trigger.payload_json.before.current_zones
+ wait.trigger.payload_json.after.current_zones) }}
- alias: Confirm Frigate detected a Person
condition: template
value_template: "{{ wait.trigger.payload_json.after.label == 'person' }}"
- alias: Confirm Frigate snapshot is available
condition: template
value_template: "{{ wait.trigger.payload_json.after.has_snapshot == true }}"
then:
- alias: Notify all remote mobile devices
metadata: {}
data:
message: Someone is at the front door
title: Front door
data:
channel: Security
group: Doorbell
importance: high
priority: high
ttl: 0
tag: "{{ id }}"
image: /api/frigate/notifications/{{ id }}/snapshot.jpg
notification_icon: mdi:doorbell-video
url: /lovelace/doorbell
clickAction: /lovelace/doorbell
action: script.notify_all_remote_mobile_devices
- alias: Stop automation instance
wait_template: "{{ false }}"
continue_on_timeout: true
timeout: "00:00:00"
until:
- alias: Confirm Frigate event is complete
condition: template
value_template: >-
{{ wait.remaining | is_defined == 0 or
wait.trigger.payload_json.type | is_defined == 'end' }}
mode: parallel
max: 10
For some context, Frigate can send out multiple events concurrently; they are identified by an id and they can have different states: new, updated, end. To reduce multiple notifications of a very similar event, I watch for a ‘new’ event, and I monitor it (via consuming the ‘updates’ and possibly the ‘end’) until the first event where a person is in the monitored zone, and I have a snapshot image I can use in my notification. After that, the event can continue to send updates or an end, but I don’t care because I’ve already sent a notification. Those events aren’t consumed because the automation only tiggers on a ‘new’ event.
The consumer is the repeat until - I know for certain that I need to continue looping until I get an ‘end’ for that event (with the same ‘id’). Inside of the repeat loop is a wait trigger - that waits for the next MQTT event to be sent out and it ensures it’s for the same unique ‘id’ as the initial trigger - any others I don’t care about in one specific instance of the automation.
The automation can run in parallel; it will trigger for every ‘new’ event sent out by Frigate. This is why I cannot use a helper to save an “exit” state for the automation. I have a macro I created where I can save unique variables per automation process, but as mentioned in the original post, I am hoping to accomplish this without the need for extra stuff - ultimately if it is not possible without oddball hacks, I wanted to be sure that if I submitted a feature request, that it would be a legitimate ask, not just because I didn’t read the docs.
I know that this question has been asked many times before - in some cases, people were able to resolve their need by altering their logic flow and eliminating the need. Others were able to implement various tricks that resolved the situation (though could be argued it was a good “solution”). I’m just attempting to perform some due diligence before assuming it’s an unresolved issue.
just turn it off then immediately turn it on again.
automation.turn_off
automation.turn_on
however given when you described, i think i would probably do what i said in my previous post of creating a stop trigger, setting the automation mode to restart whenever a trigger is encountered. then you can stop the automation by triggering it with this stop trigger.