Mastering Advanced Automation Flow Control: Building State-Aware Automations in Home Assistant
- #Home_Assistant
- #Automation
- #Smart_Home
- #Advanced
- #Flow_Control
- #YAML

Mastering Advanced Automation Flow Control: Building State-Aware Automations in Home Assistant
Home Assistant excels at making your home smarter, but often, the automations we craft start simple: "If X happens, then do Y." While effective for basic tasks, real-world scenarios in a smart home are rarely linear. What if you need your automation to wait for a specific condition before proceeding? Or execute different actions based on a multitude of factors? What if you need to repeat a sequence until a certain state is achieved? This is where mastering Home Assistant's advanced automation flow control comes into play.
By leveraging powerful built-in actions like wait_for_trigger
, choose
, and repeat
, alongside the judicious use of variables, you can move beyond rigid "if-then" statements to create truly intelligent, state-aware automations that adapt dynamically to your home's conditions. This guide will equip you with the knowledge to build resilient and sophisticated smart home logic.
The Power of wait_for_trigger
: Pausing for Purpose
Imagine turning on a light when motion is detected, but you only want it to turn off once there's been no motion for five minutes AND a door is closed. A simple "turn off after 5 minutes of no motion" automation might leave the light on if the door is still open. wait_for_trigger
solves this by pausing the automation's execution until a specified trigger fires.
How it works: When an automation reaches a wait_for_trigger
step, it halts. It will only resume when one of its defined triggers occurs, or if a specified timeout
is reached.
Use Cases:
- Waiting for a door to close before locking it.
- Pausing a routine until a device reports a specific state (e.g., "washing machine finished").
- Waiting for a user to confirm an action via a notification.
Example: Turn off light only when no motion AND door closed
automation:
- alias: "Turn on light with motion"
trigger:
- platform: state
entity_id: binary_sensor.motion_sensor
to: "on"
action:
- service: light.turn_on
target:
entity_id: light.hallway_light
- wait_for_trigger:
- platform: state
entity_id: binary_sensor.motion_sensor
to: "off"
for: "00:05:00"
- platform: state
entity_id: binary_sensor.hallway_door
to: "off" # Assuming 'off' means closed
timeout: "00:10:00" # Max wait time before continuing
continue_on_timeout: false # If timeout, do NOT proceed
- service: light.turn_off
target:
entity_id: light.hallway_light
Best Practices for wait_for_trigger
:
- Always use
timeout
: Prevents automations from getting stuck indefinitely if the trigger never fires. - Consider
continue_on_timeout
: Set tofalse
if the subsequent actions should only occur if the trigger was successful. Set totrue
if you want the automation to proceed even if it timed out (e.g., to log an error). - Multiple Triggers: You can define multiple triggers within
wait_for_trigger
; the automation will resume when *any* of them fire.
Conditional Logic with choose
: Dynamic Branching
Traditional automations often rely on multiple separate automations or complex if/else
conditions within templates. The choose
action provides a clean, structured way to execute different sequences of actions based on a set of conditions, similar to a switch-case
statement in programming.
How it works:
The choose
action evaluates a list of conditions sequentially. When the first condition that evaluates to true
is found, its associated actions are executed, and the choose
block completes. If no conditions are met, the optional default
actions are executed.
Use Cases:
- Executing different lighting scenes based on the time of day.
- Responding differently to a button press depending on the current state of another device.
- Sending specific notifications based on who is home.
Example: Different light actions based on time
action:
- choose:
- conditions:
- condition: time
after: "06:00:00"
before: "12:00:00"
sequence:
- service: light.turn_on
target:
entity_id: light.living_room
data:
brightness_pct: 70
color_temp: 4000 # Cool white
- conditions:
- condition: time
after: "18:00:00"
before: "23:00:00"
sequence:
- service: light.turn_on
target:
entity_id: light.living_room
data:
brightness_pct: 50
color_temp: 2700 # Warm white
default:
- service: light.turn_off
target:
entity_id: light.living_room
Best Practices for choose
:
- Order Matters: Conditions are evaluated top-down. Ensure more specific conditions appear before more general ones if there's overlap.
- Utilize
default
: Always consider adefault
block for fallback actions or error handling if none of your specific conditions are met. - Clarity:
choose
makes complex conditional logic much more readable than nestedif
statements within templates.
Iterative Actions with repeat
: Looping for Efficiency
Sometimes you need to perform an action multiple times, or until a certain condition is met. The repeat
action allows you to loop through a sequence of actions.
How it works:
repeat
offers four types of looping:
count
: Repeat a fixed number of times.while
: Repeat as long as a condition remains true.until
: Repeat until a condition becomes true.for_each
: Repeat for each item in a list (e.g., a list of entities).
Use Cases:
- Flashing a light multiple times for an alert.
- Gradually dimming lights until they are off.
- Checking the status of several devices in sequence.
Example: Flash a light 5 times
action:
- repeat:
count: 5
sequence:
- service: light.turn_on
target:
entity_id: light.warning_light
- delay: "00:00:00.500" # 500 milliseconds
- service: light.turn_off
target:
entity_id: light.warning_light
- delay: "00:00:00.500"
Best Practices for repeat
:
- Prevent Infinite Loops: For
while
anduntil
loops, ensure there's a mechanism for the condition to eventually change, or include atimeout
. - Variables with
for_each
: When usingfor_each
, you can access the current item in the loop usingrepeat.item
, which is incredibly powerful for dynamic entity manipulation. - Delay within loops: Be mindful of rapid-fire service calls. Add small delays if interacting with physical devices to avoid overwhelming them or the network.
Managing Delays and Timeouts
While delay
is straightforward, wait_for_trigger
with its timeout
parameter offers a more robust way to manage time in automations. A simple delay
pauses execution for a fixed duration, regardless of external events. A wait_for_trigger
with a timeout
, however, is responsive: it waits *up to* the timeout, but proceeds immediately if the trigger fires sooner.
# Simple delay
action:
- service: light.turn_on
target:
entity_id: light.porch_light
- delay: "00:05:00" # Wait 5 minutes, then turn off
- service: light.turn_off
target:
entity_id: light.porch_light
Dynamic State Management with Variables
Home Assistant automations allow you to define variables
within a sequence or script. These variables are local to that specific automation run and can be used to store temporary values, making your logic more dynamic and cleaner than relying solely on helper entities.
action:
- variables:
current_brightness: "{{ state_attr('light.living_room', 'brightness') | default(0) }}"
desired_brightness: "{{ iif(now().hour < 12, 180, 255) }}" # Different brightness based on time
- service: light.turn_on
target:
entity_id: light.living_room
data:
brightness: "{{ desired_brightness }}"
Putting It All Together: A Complex "Good Night" Routine
Let's craft a more intelligent "Good Night" automation that ensures doors are closed, turns off lights, and arms the alarm, with user interaction.
alias: "Intelligent Good Night Routine"
description: "Ensures home is secure before arming alarm and turning off lights."
trigger:
- platform: state
entity_id: input_boolean.good_night_button
to: "on"
action:
- variables:
open_doors_windows: "{{ expand('group.all_doors_windows') | selectattr('state', 'eq', 'on') | map(attribute='name') | list }}"
- choose:
- conditions: "{{ open_doors_windows | length > 0 }}"
sequence:
- service: notify.mobile_app_your_phone
data:
message: "Warning: {{ open_doors_windows | join(', ') }} still open. Close to continue."
data:
actions:
- action: "good_night_override"
title: "Override & Continue"
- wait_for_trigger:
- platform: event
event_type: mobile_app_notification_action
event_data:
action: "good_night_override"
- platform: template
value_template: "{{ expand('group.all_doors_windows') | selectattr('state', 'eq', 'off') | list | length == expand('group.all_doors_windows') | list | length }}"
timeout: "00:05:00"
continue_on_timeout: false
- choose:
- conditions:
- condition: template
value_template: "{{ wait.trigger.platform == 'event' and wait.trigger.event.event_data.action == 'good_night_override' }}"
sequence:
- service: persistent_notification.create
data:
message: "Good Night: Override activated. Proceeding with open sensors."
- conditions:
- condition: template
value_template: "{{ wait.completed == false }}" # If timeout occurred
sequence:
- service: persistent_notification.create
data:
message: "Good Night: Timeout. Action cancelled due to open sensors."
- stop: "Automation cancelled."
default:
- service: persistent_notification.create
data:
message: "All clear. Proceeding with Good Night routine."
- service: light.turn_off
target:
area_id: "all"
- service: media_player.turn_off
target:
area_id: "all"
- service: alarm_control_panel.alarm_arm_home
target:
entity_id: alarm_control_panel.home_alarm
mode: single
This example demonstrates:
- Using a
variable
to store open doors/windows. choose
for conditional branching based on the state of doors/windows.wait_for_trigger
to wait for either all sensors to close OR a user to override via a notification action.- Another
choose
to react differently based on how thewait_for_trigger
concluded. stop
action to halt the automation if conditions aren't met or timeout occurs without override.
Best Practices for Reliable Advanced Automations
- Test Incrementally: Build complex automations piece by piece. Test each
wait_for_trigger
,choose
, orrepeat
block in isolation before combining them. - Use Scripts for Reusability: If a sequence of actions is used in multiple automations, extract it into a dedicated script. This improves maintainability and reduces duplication.
- Thorough Comments: Complex logic benefits immensely from clear, concise comments explaining the intent of each section.
- Error Handling with Timeouts and Defaults: Always consider what happens if a trigger never fires (
wait_for_trigger
timeout
) or if no conditions are met (choose
default
). - Monitor with Logs: Enable debug logging for your automations (
homeassistant.components.automation
) to trace execution flow and identify issues. - Template Sensors for Complex Conditions: If a condition is particularly convoluted, consider creating a template binary sensor or sensor that reflects that state. This makes your automation conditions much cleaner.
Conclusion
Mastering advanced flow control actions like wait_for_trigger
, choose
, and repeat
transforms your Home Assistant from a collection of simple "if-then" rules into a truly intelligent and adaptive smart home brain. By thoughtfully combining these powerful tools with variables, you can build automations that not only react to events but also manage internal states, handle multiple scenarios, and recover gracefully from unexpected conditions. Dive in, experiment, and unlock the full potential of your Home Assistant ecosystem!

NGC 224
Author bio: