Mastering State Machines in Home Assistant: Building Robust, Multi-Stage Automations

Represent Mastering State Machines in Home Assistant: Building Robust, Multi-Stage Automations article
8m read

Standard Home Assistant automations, while powerful, can become brittle and complex when trying to manage multi-stage processes. Imagine automating a laundry cycle: detect machine on, wait for wash completion, then detect dry cycle start, and finally, notify when done. A series of if/then conditions across multiple automations can quickly lead to race conditions, missed states, and debugging nightmares. This fragility often results in unreliable smart home experiences, frustrating tech enthusiasts and practical homeowners alike.

The elegant solution lies in implementing a state machine. By explicitly defining distinct states and the allowed transitions between them, you can create automations that are not only robust and predictable but also easier to understand, debug, and scale. This guide will walk you through building sophisticated state machines in Home Assistant, transforming your complex, multi-stage automations into rock-solid, reliable systems.

What is a State Machine?

At its core, a state machine is a mathematical model of computation. It's an abstract machine that can be in exactly one of a finite number of states at any given time. The machine changes from one state to another (a "transition") when initiated by an event or condition. In Home Assistant, we can simulate this by using an input_select helper to represent the current state and a series of automations and scripts to manage state transitions based on sensor data and logical conditions. This approach enforces a clear, sequential flow, preventing illogical or conflicting actions.

Step-by-Step Setup: Building a Laundry State Machine

Let's build a practical example: a state machine for tracking your laundry. This machine will have four states: idle, washing, drying, and finished.

1. Define Your States with an input_select Helper

First, create an input_select helper to hold the current state of your laundry process. This is the central component of our state machine.

# configuration.yaml or helpers.yaml
input_select:
  laundry_state:
    name: Laundry Cycle State
    options:
      - idle
      - washing
      - drying
      - finished
    initial: idle
    icon: mdi:washing-machine

After adding this, restart Home Assistant or reload Input Selects. You should see input_select.laundry_state in your Developer Tools.

Screenshot Placeholder: Screenshot of input_select.laundry_state in Developer Tools.

2. Create Template Sensors for Machine Status

We'll need a way to detect if your washing machine or dryer is running. This is often done by monitoring power consumption with a smart plug (e.g., Shelly Plug, Zooz Zen15) or vibration sensors. For this example, let's assume you have smart plugs reporting power. We'll create simple template binary sensors for "running" status.

# configuration.yaml or sensors.yaml
template:
  - binary_sensor:
      - name: Washing Machine Running
        unique_id: washing_machine_running
        state: "{{ states('sensor.washing_machine_power') | float(0) > 10 }}" # Adjust threshold based on your machine's idle vs. running power
        delay_off:
          seconds: 120 # Prevent false negatives during brief power dips
        device_class: running
      - name: Dryer Running
        unique_id: dryer_running
        state: "{{ states('sensor.dryer_power') | float(0) > 10 }}" # Adjust threshold
        delay_off:
          seconds: 120 # Prevent false negatives
        device_class: running

Adjust sensor.washing_machine_power and sensor.dryer_power to your actual power entities and set appropriate power thresholds (e.g., 10W for idle, much higher for running). The delay_off ensures stability.

3. Implement State Transition Automations

Now, we'll create automations that trigger state changes in input_select.laundry_state based on our machine sensors.

Automation: Idle to Washing

This automation triggers when the washing machine starts and the laundry state is idle.

# automations.yaml
- id: laundry_state_idle_to_washing
  alias: "Laundry State: Idle to Washing"
  trigger:
    - platform: state
      entity_id: binary_sensor.washing_machine_running
      to: "on"
  condition:
    - condition: state
      entity_id: input_select.laundry_state
      state: "idle"
  action:
    - service: input_select.select_option
      target:
        entity_id: input_select.laundry_state
      data:
        option: "washing"
    - service: persistent_notification.create # Optional: notify state change
      data:
        message: "Washing machine started. Laundry state is now 'washing'."
        title: "Laundry Update"
  mode: single

Automation: Washing to Drying

This triggers when the washing machine finishes (goes off) and the dryer starts, and the state is washing. This assumes you move laundry immediately. A more robust system might have a "waiting for dryer" state.

# automations.yaml
- id: laundry_state_washing_to_drying
  alias: "Laundry State: Washing to Drying"
  trigger:
    - platform: state
      entity_id: binary_sensor.dryer_running
      to: "on"
  condition:
    - condition: state
      entity_id: input_select.laundry_state
      state: "washing"
  action:
    - service: input_select.select_option
      target:
        entity_id: input_select.laundry_state
      data:
        option: "drying"
    - service: persistent_notification.create
      data:
        message: "Dryer started. Laundry state is now 'drying'."
        title: "Laundry Update"
  mode: single

Automation: Drying to Finished

When the dryer finishes, and the state is drying.

# automations.yaml
- id: laundry_state_drying_to_finished
  alias: "Laundry State: Drying to Finished"
  trigger:
    - platform: state
      entity_id: binary_sensor.dryer_running
      to: "off"
      for:
        minutes: 5 # Ensure it's truly off
  condition:
    - condition: state
      entity_id: input_select.laundry_state
      state: "drying"
  action:
    - service: input_select.select_option
      target:
        entity_id: input_select.laundry_state
      data:
        option: "finished"
    - service: persistent_notification.create
      data:
        message: "Dryer finished! Laundry state is now 'finished'. Time to fold!"
        title: "Laundry Update"
    - service: script.turn_on # Optional: Trigger a voice announcement
      target:
        entity_id: script.announce_laundry_finished
  mode: single

Automation: Manual Reset (Optional)

You might want an easy way to reset the state back to idle, perhaps after you've folded the laundry or if something goes wrong. An input_button or a simple automation to reset after a delay works well.

# automations.yaml
- id: laundry_state_finished_to_idle
  alias: "Laundry State: Finished to Idle (Manual/Delay)"
  trigger:
    - platform: state
      entity_id: input_select.laundry_state
      to: "finished"
      for:
        minutes: 60 # Automatically reset after 1 hour if not manually reset
  action:
    - service: input_select.select_option
      target:
        entity_id: input_select.laundry_state
      data:
        option: "idle"
    - service: persistent_notification.create
      data:
        message: "Laundry cycle reset to 'idle'."
        title: "Laundry Update"
  mode: single

Alternatively, create an input_button and an automation that triggers on its press to reset the state immediately.

# configuration.yaml
input_button:
  reset_laundry_cycle:
    name: Reset Laundry Cycle
    icon: mdi:restart
# automations.yaml
- id: laundry_state_reset_button
  alias: "Laundry State: Reset Button"
  trigger:
    - platform: state
      entity_id: input_button.reset_laundry_cycle
      to: "{{ states('input_button.reset_laundry_cycle') }}" # Trigger on any state change (button press)
  action:
    - service: input_select.select_option
      target:
        entity_id: input_select.laundry_state
      data:
        option: "idle"
    - service: persistent_notification.create
      data:
        message: "Laundry cycle manually reset to 'idle'."
        title: "Laundry Update"
  mode: single

Troubleshooting Section

State Not Transitioning as Expected

  • Check Sensor Data: Verify that binary_sensor.washing_machine_running and binary_sensor.dryer_running are accurately reflecting your machines' status. Use Developer Tools -> States to monitor their values in real-time.
  • Review Automation Traces: Home Assistant's automation tracing (Config -> Automations -> [Your Automation] -> Traces) is invaluable. Check if the trigger fired, if conditions were met, and if the action executed successfully. Look for "not met" conditions or "skipped" actions.
  • Input Select State: Ensure input_select.laundry_state is in the expected state before a transition. For example, the "Idle to Washing" automation requires it to be idle.
  • Service Call Errors: In traces, confirm that input_select.select_option service calls are succeeding without errors.

False Positives or Negatives from Power Sensors

  • Adjust Thresholds: The power threshold (e.g., > 10) is crucial. Use your smart plug's historical data to find a reliable value for when the machine is truly idle versus actively running. Some machines have "phantom load" even when off.
  • Refine delay_off: If sensors briefly report "off" during a cycle (e.g., between rinse and spin), increase the delay_off in your template binary sensors. This adds hysteresis, making the sensor more stable.
  • Consider Multiple Inputs: For maximum reliability, combine power sensing with other indicators like vibration sensors (e.g., Aqara vibration sensor) or even audio detection (e.g., with ESPHome and a microphone). Use a template binary sensor with multiple conditions for a more robust "running" state.

Race Conditions or Unexpected State Skips

  • mode: single: Ensure your state transition automations use mode: single (the default) or queued to prevent multiple instances from running concurrently, which can lead to unexpected behavior.
  • Clear Conditions: Each automation should have clear conditions ensuring it only acts when the state machine is in the preceding state. This prevents skipping states (e.g., directly from idle to finished).

Advanced Config / Optimization

Manual State Overrides and Error Handling

Sometimes you need to manually intervene or correct a state. An input_button helper, as shown in the reset example, is perfect for manual resets. For more granular control, you could expose the input_select.laundry_state in Lovelace with a dropdown.

# Example Lovelace card (Entities card)
type: entities
entities:
  - entity: input_select.laundry_state
  - entity: input_button.reset_laundry_cycle
title: Laundry Control

You can also build an "emergency reset" automation that triggers if a state remains unchanged for an unreasonably long time, indicating a stuck state.

Dynamic Notifications and Announcements

Leverage the state changes to trigger context-aware notifications. For instance, when input_select.laundry_state changes to finished, trigger a voice announcement on your smart speakers or send a rich notification to your phone.

# scripts.yaml
announce_laundry_finished:
  alias: "Announce Laundry Finished"
  sequence:
    - service: tts.google_say # Or your preferred TTS service
      data:
        entity_id: media_player.living_room_speaker
        message: "The laundry cycle is complete. Time to fold!"
    - service: notify.mobile_app_your_phone # Or other notification service
      data:
        message: "Laundry Finished! Don't forget to fold."
        title: "Home Assistant"
        data:
          priority: high
          channel: laundry_alerts

Scaling State Machines for Multiple Devices/Processes

The beauty of this pattern is its reusability. You can apply the same state machine logic to other multi-stage processes:

  • Dishwasher: idle -> running -> finished
  • Garage Door: closed -> opening -> open -> closing -> closed
  • Complex Security System: disarmed -> arming_home -> armed_home -> arming_away -> armed_away -> alarm_triggered

For each, you would define a separate input_select (e.g., input_select.dishwasher_state) and corresponding sensors and automations. Using packages in Home Assistant (where related configurations for a feature are grouped in one file) can help manage this complexity.

Real-World Example: Advanced HVAC Control with Seasonal Modes

Consider an HVAC system that needs to operate differently based on the season, outside temperature, and occupancy. A state machine can manage its mode of operation.

States: off, heating_winter, cooling_summer, fan_only_transitional, away_eco

input_select: input_select.hvac_operating_mode

Sensors: sensor.outside_temperature, binary_sensor.home_occupancy, input_boolean.winter_mode, input_boolean.summer_mode

Example Transition (simplified):

# Automation: Transition to Heating in Winter
- id: hvac_transition_to_heating_winter
  alias: "HVAC: Transition to Heating (Winter)"
  trigger:
    - platform: numeric_state
      entity_id: sensor.outside_temperature
      below: 10 # C
      for:
        hours: 2
  condition:
    - condition: state
      entity_id: input_select.hvac_operating_mode
      state: ["off", "fan_only_transitional", "away_eco"] # Only transition from these states
    - condition: state
      entity_id: input_boolean.winter_mode
      state: "on"
    - condition: state
      entity_id: binary_sensor.home_occupancy
      state: "on"
  action:
    - service: input_select.select_option
      target:
        entity_id: input_select.hvac_operating_mode
      data:
        option: "heating_winter"
    - service: climate.set_hvac_mode # Adjust HVAC to heating mode
      target:
        entity_id: climate.main_thermostat
      data:
        hvac_mode: heat
  mode: single

This state machine, combined with other automations that set thermostat target temperatures based on the hvac_operating_mode, creates a highly intelligent and robust HVAC control system that accounts for multiple environmental and occupancy factors. Each mode can have its own set of rules, making the entire system much clearer than a single, monolithic automation.

Best Practices / Wrap-up

Clear Naming Conventions

Use clear, descriptive names for your input_select entities, options, and automations. This greatly improves readability and maintainability, especially as your smart home grows.

Modular Design with Scripts

For more complex actions or common sequences of events during a state transition, encapsulate them in Home Assistant scripts. Instead of directly calling multiple services in an automation's action, call a single script. This makes your automations cleaner and promotes reusability.

# Example script to transition state and notify
script:
  set_laundry_state:
    fields:
      new_state:
        name: New State
        required: true
        selector:
          text:
    sequence:
      - service: input_select.select_option
        target:
          entity_id: input_select.laundry_state
        data:
          option: "{{ new_state }}"
      - service: persistent_notification.create
        data:
          message: "Laundry state changed to '{{ new_state }}'."
          title: "Laundry Update"

Then, your automation's action would be:

action:
  - service: script.set_laundry_state
    data:
      new_state: "washing"

Visualize States in Lovelace

Create Lovelace dashboards or specific cards (e.g., toggle-lock-card, Tile card, or a simple Entities card) to display the current state of your machines. This provides immediate feedback and allows for manual intervention when needed.

Thorough Testing and Debugging

State machines require rigorous testing. Use the Developer Tools to manually set input_select states and trigger your sensor entities to simulate conditions. Leverage automation traces extensively to follow the logic flow. Consider using a separate "test mode" input_boolean to prevent real-world disruptions during development.

Backup and Version Control

As you build complex logic, ensure your Home Assistant configuration is regularly backed up. Using Git for version control (as covered in other articles) is highly recommended for tracking changes and reverting if necessary.

By adopting the state machine pattern, you elevate your Home Assistant automations from reactive scripts to robust, predictable, and maintainable systems. This approach provides a solid foundation for handling even the most intricate multi-stage processes in your smart home, leading to a more reliable and enjoyable experience.

Avatar picture of NGC 224
Written by:

NGC 224

Author bio: DIY Smart Home Creator

Comments (1)

loading...
  • Avatar picture of Andy Haluza
    Andy Haluza

    that moment when you realize the benefits of learning patterns