How to use automation with until clause?

Hello community, I’m trying to create a “stuck open” alert and it’s mostly there. I thought I had it working and could swear that it was prior to the latest 2021.4.X update but maybe I was imagining things. I’m trying to use the until clause to exit the automation when the open door is eventually closed but that doesn’t seem to work.

Now, when it runs if a door or garage door is open when the sun sets and stays open for a while, the speakers start telling us: “Alert. XYZ door is/are stuck open at night” which is good. Then we go and close the door and it should at that point change the group state to “off” and exit the automation but it doesn’t. After another interval, the speaker comes on and just says: “Alert.”! What the heck?? Is my logic backward or something?

  - id: sonos_stuck_open_alert
    initial_state: true
    alias: Sonos Stuck Open Alert
    mode: parallel
    trigger:
      - platform: template
        value_template: "{{ is_state('group.all_exterior_doors', 'on') and is_state('sun.sun', 'below_horizon') }}"
        for: "{{ states('input_datetime.sonos_stuck_warning_interval') }}"
      - platform: template
        value_template: "{{ is_state('group.all_garage_doors', 'on') and is_state('sun.sun', 'below_horizon') }}"
        for: "{{ states('input_datetime.sonos_stuck_warning_interval') }}"
    action:
      - alias: Repeat until the door is closed
        repeat:
          sequence:
            - service: script.all_sonos_say
              data:
                volume_level: 0.8
                message: >
                  Alert. 
                  {% if is_state('group.all_exterior_doors', 'on') %}
                    {%- for entity in states.group.all_exterior_doors.attributes.entity_id if is_state(entity, 'on') %}
                      {%- if loop.first and loop.last %}
                        {{ states[entity.split('.')[0]][entity.split('.')[1]].name }} is stuck open at night
                      {% else %}
                        {%- if loop.first %}{% elif loop.last %} & {% else %}, {% endif -%}
                        {{ states[entity.split('.')[0]][entity.split('.')[1]].name }}{{ " are stuck open at night" if loop.last }}
                      {%- endif -%}
                    {% endfor %}
                  {% elif is_state('group.all_garage_doors', 'open') %}
                    {%- for entity in states.group.all_garage_doors.attributes.entity_id if is_state(entity, 'open') %}
                      {%- if loop.first and loop.last %}
                        {{ states[entity.split('.')[0]][entity.split('.')[1]].name }} is stuck open at night
                      {% else %}
                        {%- if loop.first %}{% elif loop.last %} & {% else %}, {% endif -%}
                        {{ states[entity.split('.')[0]][entity.split('.')[1]].name }}{{ " are stuck open at night" if loop.last }}
                      {%- endif -%}
                    {% endfor %}
                  {% endif %}
            - delay: "{{ states('input_datetime.sonos_stuck_warning_interval') }}"
          until:
            - condition: template
              value_template: >
                {{ is_state('group.all_garage_doors', 'off') and is_state('group.all_exterior_doors', 'off') }}

I think I’m going to have to use the new feature for debugging the automations when I get a chance to do so. That might help shed some light on it but I’m guessing the logic in my until template equation is to blame.

The logic looks correct. Are you sure both group.all_garage_doors AND group.all_exterior_doors are 'off' when the delay completes (i.e., when the until condition is evaluated)?

I just did the following simple test with 2021.4.3 and it seems to work just fine.

input_boolean:
  test:

automation:
  - trigger:
      - platform: state
        entity_id: input_boolean.test
        to: 'on'
    action:
      - repeat:
          sequence:
            - delay: 10
          until: "{{ is_state('input_boolean.test', 'off') }}"

I.e., when I turn the boolean on, the automation runs and does so until I turn the boolean off.

One thing you might try is, rather than use a delay, use a wait_template with a timeout. That way you can cause the automation to exit as soon as all the doors are closed. E.g., replace the delay step with:

            - wait_template: >
                {{ is_state('group.all_garage_doors', 'off') and is_state('group.all_exterior_doors', 'off') }}
              timeout: "{{ states('input_datetime.sonos_stuck_warning_interval') }}"

Ooh, thanks Phil, I’m going to give this a try. It seems cleaner than what I’ve got with a fixed delay. I did confirm that the group elements went to ‘off’ which is why the for loops didn’t resolve to any results. Anyway, I think your solution might work a lot better. I’ll give it a try.

Thanks!!

Bah, I tried to implement the wait_timeout and noticed quickly that it doesn’t seem to be compatible with a repeat block. Anyway, I figured it out tonight! It turns out the group.all_garage_doors entity is not on/off but open/closed so fixing that in the template solved the problem!

            - wait_template: >
                {{ is_state('group.all_garage_doors', 'closed') and is_state('group.all_exterior_doors', 'off') }}
              timeout: "{{ states('input_datetime.sonos_stuck_warning_interval') }}"