Automation - keep light on as long as the door is open or turn off after a specified time interval

Hi,
I have written this automation that turns on the light at the outside door (with Sonoff relay) for “x” seconds, after sunset, when the reed switch on sliding door disengages.

- id: '1656649797945'
  alias: A-Test
  description: ''
  trigger:
  - platform: state
    entity_id:
    - binary_sensor.window_b10
    from: 'off'
    to: 'on'
    for:
      hours: 0
      minutes: 0
      seconds: 2
  condition: []
  action:
  - type: turn_on
    device_id: 2ccdc1687b78569a1d259801e333a128
    entity_id: switch.basic02_relay
    domain: switch
  - delay:
      hours: 0
      minutes: 0
      seconds: 5
      milliseconds: 0
  - type: turn_off
    device_id: 2ccdc1687b78569a1d259801e333a128
    entity_id: switch.basic02_relay
    domain: switch
  mode: single

The automation is working but I need to make it smarter and its getting too complicated in my head. So essentially what I want is as follows:

Once the door opens then light should stay “on” as long as the door is open or till a specified time, I will hard code “5 seconds” for now but will replace with a helper eventually.

Thanks!

Replace the delay with a wait for trigger.

  - wait_for_trigger:
      - platform: state
        entity_id:
        - binary_sensor.window_b10
        from: 'on'
        to: 'off'
    continue_on_timeout: true
    timeout: '15'  

This gives you 15 seconds or when the window state changes

@Hellis81 Thanks, I’ve never used “wait_for_trigger” so learnt something new!

So now the light turns off when door closes or if the 15 seconds is up (even if door is still open). Is there an easy fix to change it to greater of 15 seconds or the door open duration?

Thanks.

Change the timeout time

    timeout: '15'  

Or did I missunderstand?

Lets assume two events when the door opened:

  1. I’m leaving the house, just need time (~15 seconds) to lock and walk away and the light should turn off within some hard coded value - 15 seconds in code above.

  2. I open the door and walk up to the mailbox and then walk back, I’d like the light to stay on as long as the door is open. It will be more than 15 seconds for sure in this scenario.

- id: '1656649797945'
  alias: Door Open
  description: ''
  trigger:
  - platform: state
    entity_id: binary_sensor.window_b10
    from: 'off'
    to: 'on'
  condition: []
  action:
  - service: switch.turn_on
    target:
      entity_id: switch.basic02_relay
- id: '1656649797936'
  alias: Door Closed
  description: ''
  trigger:
  - platform: state
    entity_id: binary_sensor.window_b10
    from: 'on'
    to: 'off'
    for:
      seconds: 15
  condition: []
  action:
  - service: switch.turn_off
    target:
      entity_id: switch.basic02_relay
2 Likes

So how should HA know that you are walking to the mailbox?
It seems to me like you want to conflicting automations with nothing to separate them

The key is that he does not close the door when walking to the mailbox but does when leaving the house. So all that is needed is on when the door opens and a 15 second delayed off when the door closes.

1 Like

How about the following? Also note that restart mode seems more appropriate.

- id: '1656649797945'
  alias: A-Test
  description: ''
  trigger:
  - platform: state
    entity_id:
    - binary_sensor.window_b10
    from: 'off'
    to: 'on'
    for: 2
  action:
  - type: turn_on
    device_id: 2ccdc1687b78569a1d259801e333a128
    entity_id: switch.basic02_relay
    domain: switch
  # Wait for either door to close or 15 seconds,
  # whichever comes first.
  - wait_for_trigger:
    - platform: state
      entity_id:
      - binary_sensor.window_b10
      from: 'on'
      to: 'off'
    timeout: 15
  - if: "{{ wait.trigger is none }}"
    then:
    # Door is still open after 15 seconds.
    # Continue to wait for it to close.
    - wait_for_trigger:
      - platform: state
        entity_id:
        - binary_sensor.window_b10
        from: 'on'
        to: 'off'
    else:
    # Door closed within 15 seconds.
    # Wait for remainder of 15 seconds to elapse.
    - delay: "{{ wait.remaining }}"
  - type: turn_off
    device_id: 2ccdc1687b78569a1d259801e333a128
    entity_id: switch.basic02_relay
    domain: switch
  mode: restart

That will turn the light off after 15 seconds when they walk down the drive to get the mail. It should be:

Stay on while the door is open. Wait 15 seconds after the door closes, then turn off.

No, that is only part of the implementation. See the if that comes next.

The main point is the automation action needs to wait for one of two events: the door closing or 15 seconds after it opens. By using a wait_for_trigger with a timeout it can do both. There are two outcomes to that wait: 1) the door does not close within 15 seconds (wait.trigger is none), in which case it needs to continue waiting for the door to close; or 2) the door closes within 15 seconds, in which case it needs to wait for whatever time is remaining on the 15 second timeout.

That’s not what was requested (at least how I interpreted it), although it’s also a reasonable way to do it.

An alternate solution (assumes timer.door exists):

- id: '1656649797945'
  alias: A-Test
  description: ''
  trigger:
  - platform: state
    entity_id:
    - binary_sensor.window_b10
    from: 'off'
    to: 'on'
    for: 2
  action:
  - type: turn_on
    device_id: 2ccdc1687b78569a1d259801e333a128
    entity_id: switch.basic02_relay
    domain: switch
  - service: timer.start
    entity_id: timer.door
    data:
      duration: 15
  # Wait for door to close.
  - wait_for_trigger:
    - platform: state
      entity_id:
      - binary_sensor.window_b10
      from: 'on'
      to: 'off'
  # Wait until it's been at least 15 seconds since the door opened.
  - wait_template: "{{ is_state('timer.door', 'idle') }}"
  - type: turn_off
    device_id: 2ccdc1687b78569a1d259801e333a128
    entity_id: switch.basic02_relay
    domain: switch
  mode: restart

D’oh! I’m getting rusty. How about:

- id: '1656649797945'
  alias: A-Test
  description: ''
  trigger:
  - platform: state
    entity_id:
    - binary_sensor.window_b10
    from: 'off'
    to: 'on'
    for: 2
  action:
  - type: turn_on
    device_id: 2ccdc1687b78569a1d259801e333a128
    entity_id: switch.basic02_relay
    domain: switch
  - service: timer.start
    entity_id: timer.door
    data:
      duration: 15
  - wait_template: >
      {{ is_state('binary_sensor.window_b10', 'off') and
         is_state('timer.door', 'idle') }}
  - type: turn_off
    device_id: 2ccdc1687b78569a1d259801e333a128
    entity_id: switch.basic02_relay
    domain: switch
  mode: restart

@tom_l & @pnbruckner - thanks for suggestions!

Tom - the split automation you’ve suggested is working like a charm, so solved the problem for now.

Phil - your automation seems interesting and I may have another use for it but I could not pass the validation check as “duration: 15” kept giving errors. What do you mean by door.timer exist?

Thanks.

I’ve implemented what I call ‘state-based’ automations, rather than ‘event-driven’ automations. Essentially, a model determines what the state of your entity should be at any moment in time, based on states of other entities. For example, here’s the automation for my garage light:

alias: Garage Light Automation
description: ''
trigger:
  - platform: time_pattern
    minutes: /1
  - platform: state
    entity_id: sensor.garage_motion
    to: Violated
  - platform: state
    entity_id: cover.garage_door_1
    to: open
  - platform: state
    entity_id: cover.garage_door_2
    to: open
  - platform: state
    entity_id: sensor.back_door
    to: Violated
condition: []
action:
  - service: |
      {%- macro delta(dt) -%}
        {{as_timestamp(dt) - as_timestamp(now())}}
      {%- endmacro -%}
      {%- set NOW = 0 -%}
      {%- set STARTUP_DELAY_SEC = 60 -%}
      {%- set LIGHT_DELAY_SEC = 300 -%}
      {%- if
        NOW < delta(states('sensor.uptime')) | int + LIGHT_DELAY_SEC + STARTUP_DELAY_SEC or
        NOW < delta(states('input_datetime.garage_light_last_manually_switched')) | int + LIGHT_DELAY_SEC
      -%}
      {# Do nothing if HA started up recently and lost last changed time or if light was manually toggled #}
        script.noop
      {%- elif
        NOW < delta(states.sensor.garage_motion.last_changed) | int + LIGHT_DELAY_SEC or
        is_state('cover.garage_door_1', 'open') or
        NOW < delta(states.cover.garage_door_1.last_changed) | int + LIGHT_DELAY_SEC or
        is_state('cover.garage_door_2', 'open') or
        NOW < delta(states.cover.garage_door_2.last_changed) | int + LIGHT_DELAY_SEC or
        is_state('sensor.back_door', 'Violated') or
        NOW < delta(states.sensor.back_door.last_changed) | int + LIGHT_DELAY_SEC
      -%}
        light.turn_on
      {%- else -%}
        light.turn_off
      {%- endif -%}
    entity_id: light.garage_light
mode: queued

The automation will turn the light for as long as either of the garage doors or the back door are open, and for the 5 minutes after any of those or the motion sensor changed state.

Every sensor that is evaluated needs to be included in the trigger, but the action is basically a large check to determine what the state of the light should be. Perhaps a bit wasteful on resources (for example, the automation is triggered every minute, on the minute), but I find it way easier and less error-prone to manage complex rules. Thoughts and feedback appreciated.

It’s horrible.
Please don’t post it again, someone may copy it.

Of all the horrible time pattern automations I have seen, this is by far the worst.

1 Like

Wow. Super-useful feedback. Perhaps you care to elaborate what’s so terrible about it?

It’s time pattern that makes it a bad automation.
You’re using so many calculations which is quite wasteful.
You could just use state change triggers and all these calculations would be gone.

The time you keep the light on is in my opinion not long enough for me to make it resistive to restarts as your probably is.
If I was to do it, then I would set an input datetime with the time the light should go off and trigger on time > that datetime.

You do realize that this too would be evaluated every minute just like the time pattern, right? It would be great if HA could register for a particular time and get a callback, but last time I checked it didn’t work that way.

Interesting code, two thoughts:

  1. I’m running HA instance on a single rack server so computation load is least of my worries.
  2. For the garage door, I also struggled with persistence of state and did not want to keep writing to D1-Mini board memory. I made peace on the project by installing two reed switch to increase reliability but your “state-based” code may have worked with single reed sensor on the garage door.