Are automations run in a single thread?

I’m trying to figure out how to automate the lights in the first floor hallway of my home.

What I’d like: if motion is detected at night, turn the lights on dimly for a while (in “night light mode”). If I hit a button to turn the lights on – turn them on. And if I hit a button to turn them off, turn them off (and don’t immediately sense motion and turn them back on again).

What I don’t have a handle on is the execution model of automations. The documentation doesn’t seem to make this clear, and I’ve been burned too much by the unclear execution model of my other home automation system (a Vantage system which I’m trying to control using HASS).

So I have some questions:

  • If an automation’s action starts to execute, should I always expect it to run to completion? Or are there cases where it will abort and restart?
  • If an event triggers repeatedly (such as my motion detector generating many motion events per second, as it sometimes does), will two instances of the same automation action ever execute concurrently?
  • If events that trigger different automations happen in rapid succession, will two different automation actions ever execute concurrently?

In other words – do automation actions run in a single thread, or is it multi-threaded? Do I have to worry about race conditions or locking between automation actions?

If it is helpful to frame my question, here is the code I’ve written for this automation. It relies on the Vantage Infusion integration:

- alias: 'Main hall lights button or off button pressed, lights currently on'
  trigger:
    - platform: event
      event_data:
        button: main_hall_hall_b
      event_type: vantage_button_pressed
    - platform: event
      event_data:
        button: main_hall_hall_b_295
      event_type: vantage_button_pressed
    - platform: event
      event_data:
        button: main_hall_lights_b
      event_type: vantage_button_pressed
    - platform: event
      event_data:
        button: main_kitchen_hall_b
      event_type: vantage_button_pressed
    - platform: event
      event_data:
        button: main_kitchen_hall_b_531
      event_type: vantage_button_pressed
    - platform: event
      event_data:
        button: main_lea_office_hall_b
      event_type: vantage_button_pressed
    - platform: event
      event_data:
        button: main_living_hall_b
      event_type: vantage_button_pressed
    - platform: event
      event_data:
        button: main_hall_off_b
      event_type: vantage_button_pressed
  condition:
    - condition: state
      entity_id: sensor.main_hall_lights_button_state
      state: '2.0' # On
  action:
    # Turn off the lights
    - service: light.turn_off
      data:
        entity_id: light.main_hall_cans
        transition: 1
    # Set button to green:
    - service: vantage.set_variable_vid
      data:
        vid: 872 # A.k.a. sensor.main_hall_lights_button_state
        value: 1 # Off
    # Suppress motion sensing for 10 seconds so the lights don't immediately
    # turn on just after I turn them off:
    - service: timer.start
      data:
        entity_id: timer.main_hall_nightlights_suppress_motion
        duration: '00:00:10'

- alias: 'Main hall off button pressed, lights currently dim'
  trigger:
    - platform: event
      event_data:
        button: main_hall_off_b
      event_type: vantage_button_pressed
  condition:
    - condition: state
      entity_id: sensor.main_hall_lights_button_state
      state: '3.0' # Dim
  action:
    # Turn off the lights
    - service: light.turn_off
      data:
        entity_id: light.main_hall_cans
        transition: 1
    # Set button to green:
    - service: vantage.set_variable_vid
      data:
        vid: 872 # A.k.a. sensor.main_hall_lights_button_state
        value: 1 # Off
    # Suppress motion sensing for 10 seconds so the lights don't immediately
    # turn on just after I turn them off:
    - service: timer.start
      data:
        entity_id: timer.main_hall_nightlights_suppress_motion
        duration: '00:00:10'

- alias: 'Main hall lights button pressed, lights currently off or motion dim'
  trigger:
    - platform: event
      event_data:
        button: main_hall_hall_b
      event_type: vantage_button_pressed
    - platform: event
      event_data:
        button: main_hall_hall_b_295
      event_type: vantage_button_pressed
    - platform: event
      event_data:
        button: main_hall_lights_b
      event_type: vantage_button_pressed
    - platform: event
      event_data:
        button: main_kitchen_hall_b
      event_type: vantage_button_pressed
    - platform: event
      event_data:
        button: main_kitchen_hall_b_531
      event_type: vantage_button_pressed
    - platform: event
      event_data:
        button: main_lea_office_hall_b
      event_type: vantage_button_pressed
    - platform: event
      event_data:
        button: main_living_hall_b
      event_type: vantage_button_pressed
  condition:
    - condition: or
      conditions:
        - condition: state
          entity_id: sensor.main_hall_lights_button_state
          state: '1.0' # Off
        - condition: state
          entity_id: sensor.main_hall_lights_button_state
          state: '3.0' # Dimly lit
  action:
    # Turn on the lights
    - service: light.turn_on
      data:
        entity_id: light.main_hall_cans
        transition: 1
        brightness_pct: 100
    # Set button to red:
    - service: vantage.set_variable_vid
      data:
        vid: 872 # A.k.a. sensor.main_hall_lights_button_state
        value: 2 # On

- alias: 'Main hall motion sensor triggered, lights currently off'
  trigger:
    - platform: state
      entity_id: sensor.main_hall_motion
      to: Violated
  condition:
    - condition: state
      entity_id: sensor.main_hall_lights_button_state
      state: '1.0' # Off
    # Only trigger at night:
    - condition: template
      value_template: '{{ state_attr("sun.sun", "elevation") < -2 }}'
    - condition: state
      entity_id: timer.main_hall_nightlights_suppress_motion
      state: idle
  action:
    # Turn the lights on dimly:
    - service: light.turn_on
      data:
        entity_id: light.main_hall_cans
        transition: 1
        brightness_pct: 20
    # Set button to blue:
    - service: vantage.set_variable_vid
      data:
        vid: 872 # A.k.a. sensor.main_hall_lights_button_state
        value: 3 # Dimly lit
    # Start a timer to turn them off again:
    - service: timer.start
      data:
        entity_id: timer.main_hall_nightlights
        duration: '00:02:00'

- alias: 'Main hall motion sensor triggered'
  trigger:
    - platform: state
      entity_id: sensor.main_hall_motion
      to: Violated
    - platform: state
      entity_id: sensor.main_hall_motion
      from: Violated
  condition:
    # Only trigger at night:
    - condition: template
      value_template: '{{ state_attr("sun.sun", "elevation") < -2 }}'
  action:
    # Restart the timer since we want to know when 2 minutes have elapsed with
    # no motion:
    - service: timer.start
      data:
        entity_id: timer.main_hall_nightlights
        duration: '00:02:00'

- alias: 'Main hall motion sensor timer expired'
  trigger:
    - platform: event
      event_type: timer.finished
      event_data:
        entity_id: timer.main_hall_nightlights
  condition:
    - condition: state
      entity_id: sensor.main_hall_lights_button_state
      state: '3.0' # Dimly lit
  action:
    # Turn off the lights
    - service: light.turn_off
      data:
        entity_id: light.main_hall_cans
        transition: 1
    # Set button to green:
    - service: vantage.set_variable_vid
      data:
        vid: 872 # A.k.a. sensor.main_hall_lights_button_state
        value: 1 # Off

Why not split up the actions? So for example:
A script that is triggered by motion that does “disable this script, light turn on dim, wait for 10 minutes, turn dim lights off, enable script again”

Then a second one that is triggered by a switch which turns the lights on or off.

I believe that this would cover your entire use case

Yes, unless it is halted by an invalid service (e.g. missing entity_id in a template result), in which case it will abort execution of the actions.

No. However, if the automation is waiting in a delay or wait state and a second trigger occurs then the delay/wait will be cancelled and execution will continue.

If I understand what you are suggesting, that would turn the lights on dimly for 10 minutes following the first detection of a motion event, then turn them off again 10 minutes later – even if I was still standing in the hallway at the time. (If another motion event was generated right after the lights go out, they’d come back on again of course.)

I believe the code I posted will keep the lights on for 2 minutes after the last motion event was detected, which avoids flickering lights when I’m still there. (Assuming I don’t get hurt by multithreading.)

There is an example here that turns lights off 10 minutes after the last movement, no timers required:

2 Likes

Automations (usually) are triggered by state changes, so unless your motion detector sends a “motion detected” and then a “motion cleared” as fast as you said you may have a problem, but if it only sends “motion detected” then its ok, because there’s only one state change and all the next “motion detected” events will do nothing. Even if your sensor sends a “motion cleared” event, you may create a automation which only triggers IF the motion has ben cleared for X time.

About the second, as I understand, yes, two automations may run concurrently (think about someone pressing a button on the living room, while motion is detected in the kitchen).

One strategy that I had used and seems a common pattern is to create a input boolean and flip it on/off by one automation and check it on another as a way to decide if it should run/continue or not.

There’s a custom component called entity controller that may do what you want (not sure about the dimly part)

Definitely: