Selora Homes Selora Homes

Fixing Motion Automation Race Conditions in Home Assistant

A guide to solving common race conditions in motion-activated lighting automations, where lights turn on and then immediately off.

Automation Motion Race-Condition Timing Debounce Lighting

Understanding the Race Condition

A race condition in motion-activated lighting typically occurs when the automation logic for turning lights on and turning them off overlap or execute too quickly in succession. In Home Assistant, a motion sensor changing state from on (motion detected) to off (no motion detected) often triggers the ’lights off’ sequence. If a person moves just enough to briefly clear the sensor’s field of view, the sensor might flip to off and immediately back to on.

If the ’lights off’ automation is triggered by the brief off state, it starts running. If the sensor immediately flips back to on, the ’lights on’ automation might also run. The result is often that the lights turn on, and then almost immediately turn off, leading to a frustrating user experience.

We can solve this by introducing timing checks and conditional logic to ensure the automations only run when appropriate.

Solution 1: Debouncing the Off Trigger with for:

The most effective way to prevent lights from turning off prematurely is to require the motion sensor to remain in the off state for a specific duration before the ’lights off’ action is executed. This is known as debouncing.

In Home Assistant, you achieve this by adding a for: statement to the off trigger. This ensures the trigger only fires if the state change persists for the specified time (e.g., 60 seconds).

automation:
  - alias: 'Motion Light - Turn Off'
    trigger:
      - platform: state
        entity_id: binary_sensor.hallway_motion
        to: 'off'
        for: '00:01:00'  # Must be off for 60 seconds
    action:
      - service: light.turn_off
        target:
          entity_id: light.hallway_light

Solution 2: Using choose: for Conditional Execution

Sometimes, you want an automation to run only under specific environmental conditions. For instance, there is no need to turn on lights if the sun is up or if the room is already bright. Using the choose: action allows you to implement complex conditional logic within a single automation.

This technique is particularly useful for the ’lights on’ automation, preventing unnecessary execution.

automation:
  - alias: 'Motion Light - Turn On Conditionally'
    trigger:
      - platform: state
        entity_id: binary_sensor.hallway_motion
        to: 'on'
    action:
      - choose:
          - conditions:
              # Condition 1: Only run if the sun is below the horizon
              - condition: state
                entity_id: sun.sun
                state: 'below_horizon'
              # Condition 2: Only run if illuminance is below 50 lux
              - condition: numeric_state
                entity_id: sensor.hallway_illuminance
                below: 50
            sequence:
              - service: light.turn_on
                target:
                  entity_id: light.hallway_light
        default: []

Solution 3: Managing Automation Behavior with mode:

The mode: setting controls how Home Assistant handles multiple simultaneous executions of the same automation. Understanding this setting is crucial for managing race conditions, especially when motion sensors rapidly switch states.

  • mode: single (Default): If the automation is triggered while it is already running, the new trigger is ignored. This is the safest default for most lighting automations, as it prevents the ’lights off’ sequence from being interrupted by a new ’lights on’ trigger, or vice versa.
  • mode: restart: If the automation is triggered while it is already running, the current execution is stopped and restarted from the beginning. This is particularly useful for motion lighting where you want the automation to respond immediately to new motion, even if it’s currently in a delay or cooldown period.
  • mode: queued: If the automation is triggered while running, the new trigger is added to a queue and executed sequentially after the current run finishes. This is generally not recommended for motion lighting as it can lead to delayed or unexpected behavior.

For motion lighting, mode: restart is often preferred over mode: single when using a single automation with choose blocks, as it ensures rapid response to new motion events.

Complete YAML Automation Examples

Here are two complete examples combining the techniques above for robust motion lighting.

Example 1: Hallway Light Automation (On/Off with Debounce and Sun Condition)

This example uses two separate automations for clarity, incorporating for: on the off trigger and a condition check on the on trigger.

# Automation 1: Turn ON the light
- alias: 'Hallway Motion Light ON'
  id: 'hallway_motion_on_2025'
  mode: single
  trigger:
    - platform: state
      entity_id: binary_sensor.hallway_motion
      to: 'on'
  condition:
    # Only turn on if it is dark
    - condition: state
      entity_id: sun.sun
      state: 'below_horizon'
  action:
    - service: light.turn_on
      target:
        entity_id: light.hallway_light

# Automation 2: Turn OFF the light (Debounced)
- alias: 'Hallway Motion Light OFF'
  id: 'hallway_motion_off_2025'
  mode: single
  trigger:
    - platform: state
      entity_id: binary_sensor.hallway_motion
      to: 'off'
      # Wait 90 seconds before turning off
      for: '00:01:30'
  action:
    - service: light.turn_off
      target:
        entity_id: light.hallway_light

Example 2: Bathroom Light Automation (Single Automation with choose: and mode: restart)

This example combines the logic into a single automation using choose: to handle both the on and off states, ensuring the off action is debounced. The choose block ties specific sequences to trigger IDs, and mode: restart ensures immediate response to new motion events.

- alias: 'Bathroom Motion Light Combined'
  id: 'bathroom_motion_combined_2025'
  mode: restart  # Restart on new motion for immediate response
  trigger:
    # Trigger on motion start (no delay to avoid bounce)
    - platform: state
      entity_id: binary_sensor.bathroom_motion
      to: 'on'
      id: 'motion_on'
    # Trigger on motion end (must persist for 2 minutes)
    - platform: state
      entity_id: binary_sensor.bathroom_motion
      to: 'off'
      for: '00:02:00'
      id: 'motion_off'
  action:
    # Using choose keeps on/off logic in one automation and ties sequences to specific trigger ids
    - choose:
        # Case 1: Motion started (turn on)
        - conditions:
            - condition: trigger
              id: 'motion_on'
            - condition: numeric_state
              entity_id: sensor.bathroom_illuminance
              below: 75
          sequence:
            - service: light.turn_on
              target:
                entity_id: light.bathroom_light
        # Case 2: Motion ended (turn off)
        - conditions:
            - condition: trigger
              id: 'motion_off'
          sequence:
            - service: light.turn_off
              target:
                entity_id: light.bathroom_light
      default: []

Advanced Technique: PIR with mmWave Presence Sensor

For more reliable motion detection, consider pairing a PIR sensor with an mmWave presence sensor. The PIR provides immediate motion detection, while the mmWave sensor detects subtle presence (breathing, small movements) to prevent false offs.

Create a binary sensor group that requires both sensors to report ‘off’ before considering the room clear:

binary_sensor:
  - platform: group
    name: Bathroom Presence
    unique_id: bathroom_presence_group
    device_class: motion
    entities:
      - binary_sensor.bathroom_pir_motion
      - binary_sensor.bathroom_mmwave_presence

Then use this group sensor in your automation:

- alias: 'Bathroom Motion with Dual Sensors'
  id: 'bathroom_dual_sensors_2025'
  mode: restart
  trigger:
    - platform: state
      entity_id: binary_sensor.bathroom_presence
      to: 'on'
      id: 'motion_on'
    - platform: state
      entity_id: binary_sensor.bathroom_presence
      to: 'off'
      for: '00:03:00'  # Longer timeout with dual sensors
      id: 'motion_off'
  action:
    - choose:
        - conditions:
            - condition: trigger
              id: 'motion_on'
            - condition: numeric_state
              entity_id: sensor.bathroom_illuminance
              below: 75
          sequence:
            - service: light.turn_on
              target:
                entity_id: light.bathroom_light
        - conditions:
            - condition: trigger
              id: 'motion_off'
          sequence:
            - service: light.turn_off
              target:
                entity_id: light.bathroom_light
      default: []