Actions, Triggers, Conditions

ESPHome actions are how we make an ESPHome device do something.

Let’s begin with an example. Suppose you have a configuration file which contains:

switch:
  - platform: gpio
    pin: GPIOXX
    name: "Living Room Dehumidifier"

binary_sensor:
  - platform: gpio
    pin: GPIOXX
    name: "Living Room Dehumidifier Toggle Button"

With this file you can already perform some basic tasks. You can control the ON/OFF state of the dehumidifier in your living room from Home Assistant’s front-end. But in many cases, controlling everything strictly from the frontend is not desirable. That’s why you’ve also installed a simple push button next to the dehumidifier wired to pin GPIOXX. A simple push of this button should toggle the state of the dehumidifier.

You could write an automation to do this task in Home Assistant’s automation engine, but IoT devices should not depend on network connections to perform their jobs – especially not for something as simple as switching on/off a dehumidifier.

With ESPHome’s automation engine, you can define automations using a syntax that is (hopefully) about as easy to use as Home Assistant’s. For example, this configuration would achieve your desired behavior for the dehumidifier:

switch:
  - platform: gpio
    pin: GPIOXX
    name: "Living Room Dehumidifier"
    id: dehumidifier1

binary_sensor:
  - platform: gpio
    pin: GPIOXX
    name: "Living Room Dehumidifier Toggle Button"
    on_press:
      then:
        - switch.toggle: dehumidifier1

Let’s step through what’s happening here:

switch:
   - platform: gpio
     # ...
     id: dehumidifier1

First, we have to give the dehumidifier switch an ID so that we can refer to it inside of our automation.

Triggers

binary_sensor:
  - platform: gpio
    # ...
    on_press:

We now attach a special attribute on_press to the binary sensor (which represents the button). This part is called a “trigger”. In this example, the automation which follows on the next few lines will execute whenever someone begins to press the button. Note the terminology follows what you would call these events on mouse buttons. A press happens when you begin pressing the button. There are also other triggers like on_release, on_click or on_double_click available.

# ...
on_press:
  then:
    - switch.toggle: dehumidifier1

Actions

Now comes the actual automation block. With then, you tell ESPHome what should happen when the press happens. Within this block, you can define several “actions” that will be executed sequentially. For example, switch.toggle and the line after that form an action. Each action is separated by a dash and multiple actions can be executed in sequence simply by adding another - like so:

# ...
on_press:
  then:
    - switch.toggle: dehumidifier1
    - delay: 2s
    - switch.toggle: dehumidifier1

With this automation, a press of the push button would cause the dehumidifier to turn on/off for 2 seconds, and then cycle back to its original state. You can also have a single trigger with multiple automations:

# ...
on_press:
  - then:
      - switch.toggle: dehumidifier1
  - then:
      - light.toggle: dehumidifier_indicator_light

# Same as:
on_press:
  then:
    - switch.toggle: dehumidifier1
    - light.toggle: dehumidifier_indicator_light

As a final example, let’s make our dehumidifier “smart”. Let’s make it turn on automatically when the humidity reported by a sensor is above 65% and make it turn off again when it falls below 50%:

sensor:
  - platform: dht
    humidity:
      name: "Living Room Humidity"
      on_value_range:
        - above: 65.0
          then:
            - switch.turn_on: dehumidifier1
        - below: 50.0
          then:
            - switch.turn_off: dehumidifier1
    temperature:
      name: "Living Room Temperature"

That’s a lot of indentation. 😉

on_value_range is a special trigger for sensors that triggers when the value of the sensor is within/above/below the specified range. In the first example, this range is defined as “any value above or including 65.0” and the second range refers to any (humidity) value 50% or below.

Finally, for the cases where the “pure” YAML automations just don’t quite reach far enough, ESPHome has another extremely powerful tool to offer: Templates.

Now that concludes the introduction to actions in ESPHome. They’re a powerful tool to automate almost everything on your device with an easy-to-use syntax. What follows below is an index of common actions which you’re sure to find useful (and even essential) for building all sorts of automations.

Common Actions

delay Action

This action delays the execution of the next action in the action list by a specified time period.

on_...:
  then:
    - switch.turn_on: relay_1
    - delay: 2s
    - switch.turn_off: relay_1
    # Templated, waits for 1s (1000ms) only if a reed switch is active
    - delay: !lambda "if (id(reed_switch).state) return 1000; else return 0;"

Note

This is a “smart” asynchronous delay - other code will still run in the background while the delay is happening. When using a lambda call, you should return the delay value in milliseconds.

if Action

This action first evaluates the condition: and then either executes the then: branch if the condition returns true or the else: branch if the condition returns false.

After the chosen branch (then or else) is done with execution, the next action is performed.

For example below you can see an automation that checks if a sensor value is below 30 and if so turns on a light for 5 seconds. Otherwise, the light is turned off immediately.

on_...:
  then:
    - if:
        condition:
          lambda: 'return id(some_sensor).state < 30;'
        then:
          - logger.log: "The sensor value is below 30!"
          - light.turn_on: my_light
          - delay: 5s
        else:
          - logger.log: "The sensor value is above 30!"
    - light.turn_off: my_light

Configuration variables:

At least one of condition, all or any must be provided.

  • condition (Optional, Condition): The condition to check to determine which branch to take. If this is configured with a list of conditions then they must all be true for the condition to be true.

  • all (Optional, Condition): Takes a list of conditions, all of which must be true (and is therefore equivalent to condition.)

  • any (Optional, Condition): Takes a list of conditions; if at least one is true, the condition will be true.

  • then (Optional, Action): The action to perform if the condition evaluates to true. Defaults to doing nothing.

  • else (Optional, Action): The action to perform if the condition evaluates to false. Defaults to doing nothing.

lambda Action

This action executes an arbitrary piece of C++ code (see Lambda).

on_...:
  then:
    - lambda: |-
        id(some_binary_sensor).publish_state(false);

repeat Action

This action allows you to repeat a block a given number of times. For example, the automation below will flash the light five times.

on_...:
  - repeat:
      count: 5
      then:
        - light.turn_on: some_light
        - delay: 1s
        - light.turn_off: some_light
        - delay: 10s

Configuration variables:

  • count (Required, int): The number of times the action should be repeated. The counter is available to lambdas using the reserved word “iteration”.

  • then (Required, Action): The action to repeat.

wait_until Action

This action allows your automations to wait until a condition evaluates to true. (So this is just a shorthand way of writing a while action with an empty then block.)

# In a trigger:
on_...:
  - logger.log: "Waiting for binary sensor"
  - wait_until:
      binary_sensor.is_on: some_binary_sensor
  - logger.log: "Binary sensor is ready"

If you want to use a timeout, the term “condition” is required:

# In a trigger:
on_...:
  - logger.log: "Waiting for binary sensor"
  - wait_until:
      condition:
        binary_sensor.is_on: some_binary_sensor
      timeout: 8s
  - logger.log: "Binary sensor might be ready"

Configuration variables:

  • condition (Required, Condition): The condition to wait to become true.

  • timeout (Optional, Time): Time to wait before timing out. Defaults to never timing out.

while Action

This action is similar to the if Action. The while action loops through a block as long as the given condition is true.

# In a trigger:
on_...:
  - while:
      condition:
        binary_sensor.is_on: some_binary_sensor
      then:
      - logger.log: "Still executing"
      - light.toggle: some_light
      - delay: 5s

Configuration variables:

  • condition (Required, Condition): The condition to check to determine whether or not to execute.

  • then (Required, Action): The action to perform until the condition evaluates to false.

component.update Action

Using this action you can manually call the update() method of a component.

Please note that this only works with some component types and others will result in a compile error.

on_...:
  then:
    - component.update: my_component

    # The same as:
    - lambda: 'id(my_component).update();'

component.suspend Action

Using this action you can manually call the stop_poller() method of a component.

After this action the component will stop being refreshed.

While the poller is suspendend, it’s still possible to trigger on-demand updates by using component.update

Please note that this only works with PollingComponent types and others will result in a compile error.

on_...:
  then:
    - component.suspend: my_component

    # The same as:
    - lambda: 'id(my_component).stop_poller();'

component.resume Action

Using this action you can manually call the start_poller() method of a component.

After this action the component will refresh at the original update_interval rate

This will allow the component to resume automatic update at the defined interval.

This action also allows to change the update interval, calling it without suspend, replace the poller directly.

Please note that this only works with PollingComponent types and others will result in a compile error.

on_...:
  then:
    - component.resume: my_component

    # The same as:
    - lambda: 'id(my_component).start_poller();'

# Change the poller interval
on_...:
  then:
    - component.resume:
        id: my_component
        update_interval: 15s

Common Conditions

“Conditions” provide a way for your device to take an action only when a specific (set of) condition(s) is satisfied.

and / all / or / any / xor / not Condition

Check a combination of conditions. all is a synonym for and, and any is a synonym for or. all and any may also be used directly in place of condition.

on_...:
  then:
    - if:
        condition:
          # Same syntax for `and` as well as `xor` conditions
          or:
            - binary_sensor.is_on: some_binary_sensor
            - binary_sensor.is_on: other_binary_sensor
        # ...

    - if:
        any:
          - not:
              binary_sensor.is_off: some_binary_sensor
          - binary_sensor.is_on: some_other_sensor

for Condition

Allows you to check if a given condition has been true for at least a given amount of time.

on_...:
  if:
    condition:
      for:
        time: 5min
        condition:
          api.connected:
    then:
      - logger.log: API has stayed connected for at least 5 minutes!

Configuration variables:

  • time (Required, templatable, Time): The time for which the condition has to have been true.

  • condition (Required, condition): The condition to check.

lambda Condition

This condition performs an arbitrary piece of C++ code (see Lambda) and can be used to create conditional flow in actions.

on_...:
  then:
    - if:
        condition:
          # Should return either true or false
          lambda: |-
            return id(some_sensor).state < 30;
        # ...

All Actions

See the respective component’s page(s) for more detail.

See also: Common Actions.

  • ags10: new_i2c_address, set_zero_point

  • alarm_control_panel: arm_away, arm_home, arm_night, chime, disarm, pending, ready, triggered

  • animation: next_frame, prev_frame, set_frame

  • at581x: reset, settings

  • ble: disable, enable

  • ble_client: ble_write, connect, disconnect, numeric_comparison_reply, passkey_reply, remove_bond

  • bluetooth_password: set

  • button: press

  • canbus: send

  • climate: control

  • component: resume, suspend, update

  • cover: close, control, open, stop, toggle

  • cs5460a: restart

  • deep_sleep: allow, enter, prevent

  • dfplayer: pause, play, play_folder, play_mp3, play_next, play_previous, random, reset, set_device, set_eq, set_volume, sleep, start, stop, volume_down, volume_up

  • dfrobot_sen0395: reset, settings

  • display_menu: down, enter, hide, left, right, show, show_main, up

  • ds1307: read_time, write_time

  • esp32_ble_tracker: start_scan, stop_scan

  • event: trigger

  • ezo_pmp: arbitrary_command, change_i2c_address, clear_calibration, clear_total_volume_dosed, dose_continuously, dose_volume, dose_volume_over_time, dose_with_constant_flow_rate, find, pause_dosing, set_calibration_volume, stop_dosing

  • fan: cycle_speed, toggle, turn_off, turn_on

  • fingerprint_grow: aura_led_control, cancel_enroll, delete, delete_all, enroll, led_control

  • globals: set

  • grove_tb6612fng: break, change_address, no_standby, run, standby, stop

  • homeassistant: event, service, tag_scanned

  • http_request: get, post, send

  • htu21d: set_heater, set_heater_level

  • light: addressable_set, control, dim_relative, toggle, turn_off, turn_on

  • lightwaverf: send_raw

  • lock: lock, open, unlock

  • logger: log

  • max6956: set_brightness_global, set_brightness_mode

  • media_player: pause, play, play_media, stop, toggle, volume_down, volume_set, volume_up

  • mhz19: abc_disable, abc_enable, calibrate_zero

  • micro_wake_word: start, stop

  • microphone: capture, stop_capture

  • midea_ac: beeper_off, beeper_on, display_toggle, follow_me, power_off, power_on, power_toggle, swing_step

  • mqtt: publish, publish_json

  • number: decrement, increment, operation, set, to_max, to_min

  • output: set_level, turn_off, turn_on

  • pcf85063: read_time, write_time

  • pcf8563: read_time, write_time

  • pmwcs3: air_calibration, new_i2c_address, water_calibration

  • pulse_counter: set_total_pulses

  • pulse_meter: set_total_pulses

  • pzemac: reset_energy

  • pzemdc: reset_energy

  • remote_transmitter: transmit_abbwelcome, transmit_aeha, transmit_byronsx, transmit_canalsat, transmit_canalsatld, transmit_coolix, transmit_dish, transmit_dooya, transmit_drayton, transmit_haier, transmit_jvc, transmit_keeloq, transmit_lg, transmit_magiquest, transmit_midea, transmit_mirage, transmit_nec, transmit_nexa, transmit_panasonic, transmit_pioneer, transmit_pronto, transmit_raw, transmit_rc5, transmit_rc6, transmit_rc_switch_raw, transmit_rc_switch_type_a, transmit_rc_switch_type_b, transmit_rc_switch_type_c, transmit_rc_switch_type_d, transmit_roomba, transmit_samsung, transmit_samsung36, transmit_sony, transmit_toshiba_ac

  • rf_bridge: beep, learn, send_advanced_code, send_code, send_raw, start_advanced_sniffing, start_bucket_sniffing, stop_advanced_sniffing

  • rtttl: play, stop

  • scd30: force_recalibration_with_reference

  • scd4x: factory_reset, perform_forced_calibration

  • script: execute, stop, wait

  • select: first, last, next, operation, previous, set, set_index

  • sen5x: start_fan_autoclean

  • senseair: abc_disable, abc_enable, abc_get_period, background_calibration, background_calibration_result

  • servo: detach, write

  • sim800l: connect, dial, disconnect, send_sms, send_ussd

  • speaker: play, stop, finish, volume_set

  • sprinkler: clear_queued_valves, next_valve, pause, previous_valve, queue_valve, resume, resume_or_start_full_cycle, set_divider, set_multiplier, set_repeat, set_valve_run_duration, shutdown, start_from_queue, start_full_cycle, start_single_valve

  • sps30: start_fan_autoclean

  • stepper: report_position, set_acceleration, set_deceleration, set_speed, set_target

  • switch: toggle, turn_off, turn_on

  • tag: emulation_off, emulation_on, polling_off, polling_on, set_clean_mode, set_emulation_message, set_format_mode, set_read_mode, set_write_message, set_write_mode

  • text: set

  • tm1651: set_brightness, set_level, set_level_percent, turn_off, turn_on

  • uart: write

  • ufire_ec: calibrate_probe, reset

  • ufire_ise: calibrate_probe_high, calibrate_probe_low, reset

  • update: perform

  • valve: close, control, open, stop, toggle

  • voice_assistant: start, start_continuous, stop

  • wifi: disable, enable

  • wireguard: disable, enable

All Conditions

See the respective component’s page(s) for more detail.

See also: Common Conditions.

  • alarm_control_panel: is_armed, ready

  • api: connected

  • binary_sensor: is_off, is_on

  • ble: enabled

  • dfplayer: is_playing

  • display: is_displaying_page

  • display_menu: is_active

  • fan: is_off, is_on

  • light: is_off, is_on

  • lock: is_locked, is_unlocked

  • media_player: is_announcing, is_idle, is_paused, is_playing

  • micro_wake_word: is_running

  • microphone: is_capturing

  • mqtt: connected

  • number: in_range

  • pn532: is_writing

  • pn7150: is_writing

  • pn7160: is_writing

  • rtttl: is_playing

  • script: is_running

  • sensor: in_range

  • speaker: is_playing

  • sun: is_above_horizon, is_below_horizon

  • switch: is_off, is_on

  • text_sensor: state

  • time: has_time

  • update: is_available

  • voice_assistant: connected, is_running

  • wifi: connected, enabled

  • wireguard: enabled, peer_online

Tips and Tricks

Do Automations Work Without a Network Connection

This is a common question and the answer is YES! All automations you define in ESPHome are executed on the microcontroller itself and will continue to work even if the Wi-Fi network is down or the MQTT server is not reachable.

There is one caveat though: ESPHome will automatically reboot periodically if no connection is made to its API. This helps in the event that there is an issue in the device’s network stack preventing it from being reachable on the network. You can adjust this behavior (or even disable automatic rebooting) using the reboot_timeout option in any of the following components:

Beware, however, that disabling the reboot timeout(s) effectively disables the reboot watchdog, so you will need to power-cycle the device if it proves to be/remain unreachable on the network.

Timers and Timeouts

While ESPHome does not provide a construction for timers, you can easily implement them by combining script and delay. You can have an absolute timeout or sliding timeout by using script modes single and restart respectively.

script:
  - id: hallway_light_script
    mode: restart     # Light will be kept on during 1 minute since
                      # the latest time the script is executed
    then:
      - light.turn_on: hallway_light
      - delay: 1 min
      - light.turn_off: hallway_light

...
  on_...:           # can be called from different wall switches
    - script.execute: hallway_light_script

Sometimes you’ll also need a timer which does not perform any action; in this case, you can use a single delay action and then (in your automation) use the script.is_running condition to know if your “timer” is active or not.

See Also