From 5c78504d97182de90195784aeccae1040cf8792b Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Wed, 26 Jun 2024 18:55:41 -0500 Subject: [PATCH] Break apart automations doc (#3957) Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- all_automations.json | 421 +++++++++++++ automations/actions.rst | 558 +++++++++++++++++ automations/all_actions.rst | 76 +++ automations/all_conditions.rst | 31 + automations/index.rst | 32 + automations/templates.rst | 117 ++++ build_automations_pages.py | 45 ++ components/button/template.rst | 2 +- components/event/template.rst | 2 +- components/globals.rst | 79 +++ components/interval.rst | 34 ++ components/lock/template.rst | 2 +- components/script.rst | 187 ++++++ components/switch/template.rst | 2 +- cookbook/garage-door.rst | 2 +- guides/automations.rst | 1026 -------------------------------- index.rst | 19 +- schema_doc.py | 2 +- 18 files changed, 1604 insertions(+), 1033 deletions(-) create mode 100644 all_automations.json create mode 100644 automations/actions.rst create mode 100644 automations/all_actions.rst create mode 100644 automations/all_conditions.rst create mode 100644 automations/index.rst create mode 100644 automations/templates.rst create mode 100644 build_automations_pages.py create mode 100644 components/globals.rst create mode 100644 components/interval.rst create mode 100644 components/script.rst delete mode 100644 guides/automations.rst diff --git a/all_automations.json b/all_automations.json new file mode 100644 index 000000000..7d4f0c9cd --- /dev/null +++ b/all_automations.json @@ -0,0 +1,421 @@ +{ + "actions": [ + "ags10.new_i2c_address", + "ags10.set_zero_point", + "alarm_control_panel.arm_away", + "alarm_control_panel.arm_home", + "alarm_control_panel.arm_night", + "alarm_control_panel.chime", + "alarm_control_panel.disarm", + "alarm_control_panel.pending", + "alarm_control_panel.ready", + "alarm_control_panel.triggered", + "animation.next_frame", + "animation.prev_frame", + "animation.set_frame", + "at581x.reset", + "at581x.settings", + "binary_sensor.template.publish", + "ble.disable", + "ble.enable", + "ble_client.ble_write", + "ble_client.connect", + "ble_client.disconnect", + "ble_client.numeric_comparison_reply", + "ble_client.passkey_reply", + "ble_client.remove_bond", + "bluetooth_password.set", + "button.press", + "canbus.send", + "climate.control", + "climate.haier.beeper_off", + "climate.haier.beeper_on", + "climate.haier.display_off", + "climate.haier.display_on", + "climate.haier.health_off", + "climate.haier.health_on", + "climate.haier.power_off", + "climate.haier.power_on", + "climate.haier.power_toggle", + "climate.haier.set_horizontal_airflow", + "climate.haier.set_vertical_airflow", + "climate.haier.start_self_cleaning", + "climate.haier.start_steri_cleaning", + "climate.pid.autotune", + "climate.pid.reset_integral_term", + "climate.pid.set_control_parameters", + "component.resume", + "component.suspend", + "component.update", + "cover.close", + "cover.control", + "cover.open", + "cover.stop", + "cover.template.publish", + "cover.toggle", + "cs5460a.restart", + "datetime.date.set", + "datetime.datetime.set", + "datetime.time.set", + "deep_sleep.allow", + "deep_sleep.enter", + "deep_sleep.prevent", + "delay", + "dfplayer.pause", + "dfplayer.play", + "dfplayer.play_folder", + "dfplayer.play_mp3", + "dfplayer.play_next", + "dfplayer.play_previous", + "dfplayer.random", + "dfplayer.reset", + "dfplayer.set_device", + "dfplayer.set_eq", + "dfplayer.set_volume", + "dfplayer.sleep", + "dfplayer.start", + "dfplayer.stop", + "dfplayer.volume_down", + "dfplayer.volume_up", + "dfrobot_sen0395.reset", + "dfrobot_sen0395.settings", + "display.page.show", + "display.page.show_next", + "display.page.show_previous", + "display_menu.down", + "display_menu.enter", + "display_menu.hide", + "display_menu.left", + "display_menu.right", + "display_menu.show", + "display_menu.show_main", + "display_menu.up", + "ds1307.read_time", + "ds1307.write_time", + "esp32_ble_tracker.start_scan", + "esp32_ble_tracker.stop_scan", + "event.trigger", + "ezo_pmp.arbitrary_command", + "ezo_pmp.change_i2c_address", + "ezo_pmp.clear_calibration", + "ezo_pmp.clear_total_volume_dosed", + "ezo_pmp.dose_continuously", + "ezo_pmp.dose_volume", + "ezo_pmp.dose_volume_over_time", + "ezo_pmp.dose_with_constant_flow_rate", + "ezo_pmp.find", + "ezo_pmp.pause_dosing", + "ezo_pmp.set_calibration_volume", + "ezo_pmp.stop_dosing", + "fan.cycle_speed", + "fan.hbridge.brake", + "fan.toggle", + "fan.turn_off", + "fan.turn_on", + "fingerprint_grow.aura_led_control", + "fingerprint_grow.cancel_enroll", + "fingerprint_grow.delete", + "fingerprint_grow.delete_all", + "fingerprint_grow.enroll", + "fingerprint_grow.led_control", + "globals.set", + "grove_tb6612fng.break", + "grove_tb6612fng.change_address", + "grove_tb6612fng.no_standby", + "grove_tb6612fng.run", + "grove_tb6612fng.standby", + "grove_tb6612fng.stop", + "homeassistant.event", + "homeassistant.service", + "homeassistant.tag_scanned", + "http_request.get", + "http_request.post", + "http_request.send", + "htu21d.set_heater", + "htu21d.set_heater_level", + "if", + "lambda", + "light.addressable_set", + "light.control", + "light.dim_relative", + "light.toggle", + "light.turn_off", + "light.turn_on", + "lightwaverf.send_raw", + "lock.lock", + "lock.open", + "lock.template.publish", + "lock.unlock", + "logger.log", + "max6956.set_brightness_global", + "max6956.set_brightness_mode", + "media_player.pause", + "media_player.play", + "media_player.play_media", + "media_player.stop", + "media_player.toggle", + "media_player.volume_down", + "media_player.volume_set", + "media_player.volume_up", + "mhz19.abc_disable", + "mhz19.abc_enable", + "mhz19.calibrate_zero", + "micro_wake_word.start", + "micro_wake_word.stop", + "microphone.capture", + "microphone.stop_capture", + "midea_ac.beeper_off", + "midea_ac.beeper_on", + "midea_ac.display_toggle", + "midea_ac.follow_me", + "midea_ac.power_off", + "midea_ac.power_on", + "midea_ac.power_toggle", + "midea_ac.swing_step", + "mqtt.publish", + "mqtt.publish_json", + "number.decrement", + "number.increment", + "number.operation", + "number.set", + "number.to_max", + "number.to_min", + "ota.http_request.flash", + "output.esp8266_pwm.set_frequency", + "output.ledc.set_frequency", + "output.libretiny_pwm.set_frequency", + "output.pipsolar.set_level", + "output.rp2040_pwm.set_frequency", + "output.set_level", + "output.turn_off", + "output.turn_on", + "pcf85063.read_time", + "pcf85063.write_time", + "pcf8563.read_time", + "pcf8563.write_time", + "pmwcs3.air_calibration", + "pmwcs3.new_i2c_address", + "pmwcs3.water_calibration", + "pulse_counter.set_total_pulses", + "pulse_meter.set_total_pulses", + "pzemac.reset_energy", + "pzemdc.reset_energy", + "remote_transmitter.transmit_abbwelcome", + "remote_transmitter.transmit_aeha", + "remote_transmitter.transmit_byronsx", + "remote_transmitter.transmit_canalsat", + "remote_transmitter.transmit_canalsatld", + "remote_transmitter.transmit_coolix", + "remote_transmitter.transmit_dish", + "remote_transmitter.transmit_dooya", + "remote_transmitter.transmit_drayton", + "remote_transmitter.transmit_haier", + "remote_transmitter.transmit_jvc", + "remote_transmitter.transmit_keeloq", + "remote_transmitter.transmit_lg", + "remote_transmitter.transmit_magiquest", + "remote_transmitter.transmit_midea", + "remote_transmitter.transmit_mirage", + "remote_transmitter.transmit_nec", + "remote_transmitter.transmit_nexa", + "remote_transmitter.transmit_panasonic", + "remote_transmitter.transmit_pioneer", + "remote_transmitter.transmit_pronto", + "remote_transmitter.transmit_raw", + "remote_transmitter.transmit_rc5", + "remote_transmitter.transmit_rc6", + "remote_transmitter.transmit_rc_switch_raw", + "remote_transmitter.transmit_rc_switch_type_a", + "remote_transmitter.transmit_rc_switch_type_b", + "remote_transmitter.transmit_rc_switch_type_c", + "remote_transmitter.transmit_rc_switch_type_d", + "remote_transmitter.transmit_roomba", + "remote_transmitter.transmit_samsung", + "remote_transmitter.transmit_samsung36", + "remote_transmitter.transmit_sony", + "remote_transmitter.transmit_toshiba_ac", + "repeat", + "rf_bridge.beep", + "rf_bridge.learn", + "rf_bridge.send_advanced_code", + "rf_bridge.send_code", + "rf_bridge.send_raw", + "rf_bridge.start_advanced_sniffing", + "rf_bridge.start_bucket_sniffing", + "rf_bridge.stop_advanced_sniffing", + "rtttl.play", + "rtttl.stop", + "scd30.force_recalibration_with_reference", + "scd4x.factory_reset", + "scd4x.perform_forced_calibration", + "script.execute", + "script.stop", + "script.wait", + "select.first", + "select.last", + "select.next", + "select.operation", + "select.previous", + "select.set", + "select.set_index", + "sen5x.start_fan_autoclean", + "senseair.abc_disable", + "senseair.abc_enable", + "senseair.abc_get_period", + "senseair.background_calibration", + "senseair.background_calibration_result", + "sensor.duty_time.reset", + "sensor.duty_time.start", + "sensor.duty_time.stop", + "sensor.integration.reset", + "sensor.rotary_encoder.set_value", + "sensor.template.publish", + "servo.detach", + "servo.write", + "sim800l.connect", + "sim800l.dial", + "sim800l.disconnect", + "sim800l.send_sms", + "sim800l.send_ussd", + "speaker.play", + "speaker.stop", + "sprinkler.clear_queued_valves", + "sprinkler.next_valve", + "sprinkler.pause", + "sprinkler.previous_valve", + "sprinkler.queue_valve", + "sprinkler.resume", + "sprinkler.resume_or_start_full_cycle", + "sprinkler.set_divider", + "sprinkler.set_multiplier", + "sprinkler.set_repeat", + "sprinkler.set_valve_run_duration", + "sprinkler.shutdown", + "sprinkler.start_from_queue", + "sprinkler.start_full_cycle", + "sprinkler.start_single_valve", + "sps30.start_fan_autoclean", + "stepper.report_position", + "stepper.set_acceleration", + "stepper.set_deceleration", + "stepper.set_speed", + "stepper.set_target", + "switch.template.publish", + "switch.toggle", + "switch.turn_off", + "switch.turn_on", + "tag.emulation_off", + "tag.emulation_on", + "tag.polling_off", + "tag.polling_on", + "tag.set_clean_mode", + "tag.set_emulation_message", + "tag.set_format_mode", + "tag.set_read_mode", + "tag.set_write_message", + "tag.set_write_mode", + "text.set", + "text_sensor.template.publish", + "tm1651.set_brightness", + "tm1651.set_level", + "tm1651.set_level_percent", + "tm1651.turn_off", + "tm1651.turn_on", + "uart.write", + "ufire_ec.calibrate_probe", + "ufire_ec.reset", + "ufire_ise.calibrate_probe_high", + "ufire_ise.calibrate_probe_low", + "ufire_ise.reset", + "update.perform", + "valve.close", + "valve.control", + "valve.open", + "valve.stop", + "valve.template.publish", + "valve.toggle", + "voice_assistant.start", + "voice_assistant.start_continuous", + "voice_assistant.stop", + "wait_until", + "while", + "wifi.disable", + "wifi.enable", + "wireguard.disable", + "wireguard.enable" + ], + "conditions": [ + "alarm_control_panel.is_armed", + "alarm_control_panel.ready", + "and", + "api.connected", + "binary_sensor.is_off", + "binary_sensor.is_on", + "ble.enabled", + "dfplayer.is_playing", + "display.is_displaying_page", + "display_menu.is_active", + "fan.is_off", + "fan.is_on", + "for", + "lambda", + "light.is_off", + "light.is_on", + "lock.is_locked", + "lock.is_unlocked", + "media_player.is_idle", + "media_player.is_playing", + "micro_wake_word.is_running", + "microphone.is_capturing", + "mqtt.connected", + "not", + "number.in_range", + "or", + "pn532.is_writing", + "pn7150.is_writing", + "pn7160.is_writing", + "rtttl.is_playing", + "script.is_running", + "sensor.duty_time.is_not_running", + "sensor.duty_time.is_running", + "sensor.in_range", + "speaker.is_playing", + "sun.is_above_horizon", + "sun.is_below_horizon", + "switch.is_off", + "switch.is_on", + "text_sensor.state", + "time.has_time", + "update.is_available", + "voice_assistant.connected", + "voice_assistant.is_running", + "wifi.connected", + "wifi.enabled", + "wireguard.enabled", + "wireguard.peer_online", + "xor" + ], + "pin_providers": [ + "bk72xx", + "esp32", + "esp8266", + "host", + "max6956", + "mcp23016", + "mcp23xxx", + "mpr121", + "pca6416a", + "pca9554", + "pcf8574", + "rp2040", + "rtl87xx", + "sn74hc165", + "sn74hc595", + "sx1509", + "wk2168_i2c", + "wk2168_spi", + "wk2212_i2c", + "wk2212_spi", + "xl9535" + ] +} diff --git a/automations/actions.rst b/automations/actions.rst new file mode 100644 index 000000000..1db6d0436 --- /dev/null +++ b/automations/actions.rst @@ -0,0 +1,558 @@ +.. _actions-triggers: + +Actions, Triggers, Conditions +============================= + +.. seo:: + :description: Guide for building automations in ESPHome + :image: auto-fix.svg + +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: + +.. code-block:: yaml + + 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: + +.. code-block:: yaml + + 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: + +.. code-block:: yaml + + switch: + - platform: gpio + # ... + id: dehumidifier1 + +First, we have to give the dehumidifier ``switch`` an :ref:`config-id` so that we can refer to it inside of our +automation. + +.. _actions-trigger: + +Triggers +-------- + +.. code-block:: yaml + + 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. + +.. code-block:: yaml + + # ... + on_press: + then: + - switch.toggle: dehumidifier1 + +.. _actions-action: + +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: + +.. code-block:: yaml + + # ... + 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: + +.. code-block:: yaml + + # ... + 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%: + +.. code-block:: yaml + + 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: :doc:`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: + +Common Actions +-------------- + +.. _delay_action: + +``delay`` Action +**************** + +This action delays the execution of the next action in the action list by a specified +time period. + +.. code-block:: yaml + + 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: + +``if`` Action +************* + +This action first evaluated a certain condition (``if:``) and then either +executes the ``then:`` branch or the ``else:`` branch depending on the output of the condition. + +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. + +.. code-block:: yaml + + 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: + +- **condition** (**Required**, :ref:`Condition `): The condition to check to determine which branch to take. +- **then** (*Optional*, :ref:`Action `): The action to perform if the condition evaluates to true. + Defaults to doing nothing. +- **else** (*Optional*, :ref:`Action `): The action to perform if the condition evaluates to false. + Defaults to doing nothing. + +.. _lambda_action: + +``lambda`` Action +***************** + +This action executes an arbitrary piece of C++ code (see :ref:`Lambda `). + +.. code-block:: yaml + + on_...: + then: + - lambda: |- + id(some_binary_sensor).publish_state(false); + +.. _repeat_action: + +``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. + +.. code-block:: yaml + + 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. +- **then** (**Required**, :ref:`Action `): The action to repeat. + +.. _wait_until_action: + +``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.) + +.. code-block:: yaml + + # 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: + +.. code-block:: yaml + + # 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**, :ref:`Condition `): The condition to wait to become true. +- **timeout** (*Optional*, :ref:`config-time`): Time to wait before timing out. Defaults to never timing out. + +.. _while_action: + +``while`` Action +**************** + +This action is similar to the :ref:`if ` Action. The ``while`` action loops +through a block as long as the given condition is true. + +.. code-block:: yaml + + # 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**, :ref:`Condition `): The condition to check to determine whether or not to execute. +- **then** (**Required**, :ref:`Action `): The action to perform until the condition evaluates to false. + +.. _component-update_action: + +``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. + +.. code-block:: yaml + + on_...: + then: + - component.update: my_component + + # The same as: + - lambda: 'id(my_component).update();' + +.. _component-suspend_action: + +``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 :ref:`component.update ` + +Please note that this only works with PollingComponent types and others will result in a +compile error. + +.. code-block:: yaml + + on_...: + then: + - component.suspend: my_component + + # The same as: + - lambda: 'id(my_component).stop_poller();' + +.. _component-resume_action: + +``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. + +.. code-block:: yaml + + 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: + +Common Conditions +----------------- + +"Conditions" provide a way for your device to take an action only when a specific (set of) condition(s) is satisfied. + +.. _and_condition: +.. _or_condition: +.. _xor_condition: +.. _not_condition: + +``and`` / ``or`` / ``xor`` / ``not`` Condition +********************************************** + +Check a combination of conditions + +.. code-block:: yaml + + 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: + condition: + not: + binary_sensor.is_off: some_binary_sensor + +.. _for_condition: + +``for`` Condition +***************** + +Allows you to check if a given condition has been true for at least a given amount of time. + +.. code-block:: yaml + + 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**, :ref:`templatable `, :ref:`config-time`): + The time for which the condition has to have been true. +- **condition** (**Required**, :ref:`condition`): The condition to check. + +.. _lambda_condition: + +``lambda`` Condition +******************** + +This condition performs an arbitrary piece of C++ code (see :ref:`Lambda `) +and can be used to create conditional flow in actions. + +.. code-block:: yaml + + on_...: + then: + - if: + condition: + # Should return either true or false + lambda: |- + return id(some_sensor).state < 30; + # ... + +.. _config-action: + +All Actions +----------- + +*See the respective component's page(s) for more detail.* + +See also: :ref:`common-actions`. + +.. include:: all_actions.rst + +.. _config-condition: + +All Conditions +-------------- + +*See the respective component's page(s) for more detail.* + +See also: :ref:`common_conditions`. + +.. include:: all_conditions.rst + +.. _tips-and-tricks: + +Tips and Tricks +--------------- + +.. _automation-networkless: + +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: + +- :doc:`/components/wifi` +- :doc:`/components/api` +- :doc:`/components/mqtt` + +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-timeouts: + +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. + +.. code-block:: yaml + + 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 +-------- + +- :doc:`index` +- :doc:`templates` +- :ghedit:`Edit` diff --git a/automations/all_actions.rst b/automations/all_actions.rst new file mode 100644 index 000000000..0e9171083 --- /dev/null +++ b/automations/all_actions.rst @@ -0,0 +1,76 @@ +- **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`` +- **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`` + diff --git a/automations/all_conditions.rst b/automations/all_conditions.rst new file mode 100644 index 000000000..2b55ebdd6 --- /dev/null +++ b/automations/all_conditions.rst @@ -0,0 +1,31 @@ +- **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_idle``, ``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`` + diff --git a/automations/index.rst b/automations/index.rst new file mode 100644 index 000000000..13174e004 --- /dev/null +++ b/automations/index.rst @@ -0,0 +1,32 @@ +.. _automation: + +Automation +========== + +.. seo:: + :description: Getting started guide for automations in ESPHome + :image: auto-fix.svg + +Automations are a very powerful aspect of ESPHome; they allow you to easily perform actions given some condition(s). + +When you want your ESPHome device to respond to its environment, you use an automation. Here are some examples: + +- Switch on a light when the cover is opened +- Transmit an infrared (IR) code when I press this button +- Turn on the heat when the temperature drops + +This page serves as an index which will walk to through the process of using ESPHome automations--actions, triggers, +templates, and more--to customize your ESPHome device just how you like it. + +- :doc:`actions` +- :doc:`templates` +- :doc:`/components/globals` +- :doc:`/components/script` +- :doc:`/components/interval` + +.. toctree:: + :glob: + :maxdepth: 1 + :hidden: + + * diff --git a/automations/templates.rst b/automations/templates.rst new file mode 100644 index 000000000..56b1d00b0 --- /dev/null +++ b/automations/templates.rst @@ -0,0 +1,117 @@ +.. _config-lambda: + +Templates +========= + +.. seo:: + :description: Guide for using templates in ESPHome + :image: auto-fix.svg + +*Templates* (also known as *lambdas*) allow you to do almost *anything* in ESPHome. For example, if you want to only +perform a certain automation if a certain complex formula evaluates to true, you can do that with templates. Let's look +at an example first: + +.. code-block:: yaml + + binary_sensor: + - platform: gpio + name: "Cover End Stop" + id: top_end_stop + cover: + - platform: template + name: Living Room Cover + lambda: !lambda |- + if (id(top_end_stop).state) { + return COVER_OPEN; + } else { + return COVER_CLOSED; + } + +What's happening here? First, we define a binary sensor (notably with ``id: top_end_stop``) and then a +:doc:`template cover `. (If you're new to Home Assistant, a 'cover' is something like a +window blind, a roller shutter, or a garage door.) The *state* of the template cover is controlled by a template, or +"lambda". In lambdas, you're just writing C++ code and therefore the name lambda is used instead of Home Assistant's +"template" lingo to avoid confusion. Regardless, don't let lambdas scare you just because you saw "C++" -- writing +lambdas is not that hard! Here's a bit of a primer: + +First, you might have already wondered what the ``lambda: !lambda |-`` part is supposed to mean. ``!lambda`` tells +ESPHome that the following block is supposed to be interpreted as a lambda, or C++ code. Note that here, the +``lambda:`` key would actually implicitly make the following block a lambda, so in this context, you could also have +written ``lambda: |-``. + +Next, there's the weird ``|-`` character combination. This tells the YAML parser to treat the following **indented** +block as plaintext. Without it, the YAML parser would attempt to read the following block as if it were made up of YAML +keys like ``cover:`` for example. (You may also have seen variations of this like ``>-`` or just ``|`` or ``>``. There +is a slight difference in how these different styles deal with whitespace, but for our purposes we can ignore that). + +With ``if (...) { ... } else { ... }`` we create a *condition*. What this effectively says that if the thing inside the +first parentheses evaluates to ``true`` then execute the first block (in this case ``return COVER_OPEN;``, or else +evaluate the second block. ``return ...;`` makes the code block give back a value to the template. In this case, we're +either *returning* ``COVER_OPEN`` or ``COVER_CLOSED`` to indicate that the cover is closed or open. + +Finally, ``id(...)`` is a helper function that makes ESPHome fetch an object with the supplied ID (which you defined +somewhere else, like ``top_end_stop``) and lets you call any of ESPHome's many APIs directly. For example, here we're +retrieving the current state of the end stop using ``.state`` and using it to construct our cover state. + +.. note:: + + ESPHome does not check the validity of lambda expressions you enter and will blindly copy them into the generated + C++ code. If compilation fails or something else is not working as expected with lambdas, it's always best to look + at the generated C++ source file under ``/src/main.cpp``. + +.. tip:: + + To store local variables inside lambdas that retain their value across executions, you can create ``static`` + variables as shown in the example below. Here, the variable ``num_executions`` is incremented by one each time the + lambda is executed and the current value is logged. + + .. code-block:: yaml + + lambda: |- + static int num_executions = 0; + ESP_LOGD("main", "I am at execution number %d", num_executions); + num_executions += 1; + +.. _config-templatable: + +Templating Actions +------------------ + +ESPHome allows you to template most parameters for actions used in automations. For example, if you have a light and +want to set it to a pre-defined color when a button is pressed, you can do this: + +.. code-block:: yaml + + on_press: + then: + - light.turn_on: + id: some_light_id + transition_length: 0.5s + red: 0.8 + green: 1.0 + blue: !lambda |- + // The sensor outputs values from 0 to 100. The blue + // part of the light color will be determined by the sensor value. + return id(some_sensor).state / 100.0; + +When you see the label "templatable" in the documentation for a given action, it can be templated as in this example, +using the lambda syntax as described/shown above. + +All Lambda Calls +---------------- + +- :ref:`Sensor ` +- :ref:`Binary Sensor ` +- :ref:`Switch ` +- :ref:`Display ` +- :ref:`Cover ` +- :ref:`Text Sensor ` +- :ref:`Stepper ` +- :ref:`Number ` + +See Also +-------- + +- :doc:`index` +- :doc:`actions` +- :ghedit:`Edit` diff --git a/build_automations_pages.py b/build_automations_pages.py new file mode 100644 index 000000000..150deb3b4 --- /dev/null +++ b/build_automations_pages.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 + +import argparse +import json + +if __name__ == "__main__": + file_name = "all_automations.json" + arg_choices = ["actions", "conditions", "pin_providers"] + + parser = argparse.ArgumentParser() + parser.add_argument( + "-t", + "--type", + choices=arg_choices, + help="Automation type to extract ('actions', 'conditions', 'pin_providers')", + ) + args = parser.parse_args() + + with open(file_name) as json_file: + raw_json = json.load(json_file) + + if args.type not in arg_choices: + print("Unrecognized automation type") + exit() + + automation_list = raw_json[args.type] + + component_dict = {} + + for item in automation_list: + parts = item.split(".") + if len(parts) == 2: + if parts[0] not in component_dict: + component_dict[parts[0]] = [] + component_dict[parts[0]].append(parts[1]) + + out_str = "" + + for comp, autos in component_dict.items(): + out_str += f"- **{comp}:** " + for item in autos: + out_str += f"``{item}``, " + out_str = out_str[:-2] + "\n" + + print(out_str) diff --git a/components/button/template.rst b/components/button/template.rst index 959edfa91..11095ab82 100644 --- a/components/button/template.rst +++ b/components/button/template.rst @@ -27,6 +27,6 @@ Configuration variables: See Also -------- -- :doc:`/guides/automations` +- :doc:`/automations/index` - :doc:`/components/button/index` - :ghedit:`Edit` diff --git a/components/event/template.rst b/components/event/template.rst index d0a8d5533..f2447429a 100644 --- a/components/event/template.rst +++ b/components/event/template.rst @@ -26,6 +26,6 @@ Configuration variables: See Also -------- -- :doc:`/guides/automations` +- :doc:`/automations/index` - :doc:`/components/event/index` - :ghedit:`Edit` diff --git a/components/globals.rst b/components/globals.rst new file mode 100644 index 000000000..614254efa --- /dev/null +++ b/components/globals.rst @@ -0,0 +1,79 @@ +.. _config-globals: + +Global Variables +---------------- + +In some cases you might need to share a global variable across multiple lambdas. For example, global variables can be +used to store the state of a garage door. + +.. code-block:: yaml + + # Example configuration entry + globals: + - id: my_global_int + type: int + restore_value: no + initial_value: '0' + # Example for global string variable + - id: my_global_string + type: std::string + restore_value: yes + max_restore_data_length: 24 + initial_value: '"Global value is"' + + # In an automation + on_...: + then: + - lambda: |- + if (id(my_global_int) > 5) { + // global value is greater than 5 + id(my_global_int) += 1; + } else { + id(my_global_int) += 10; + } + + ESP_LOGD(TAG, "%s: %d", id(my_global_string).c_str(), id(my_global_int)); + +Configuration variables: + +- **id** (**Required**, :ref:`config-id`): Give the global variable an ID so that you can refer + to it later in :ref:`lambdas `. +- **type** (**Required**, string): The C++ type of the global variable, for example ``bool`` (for ``true``/``false``), + ``int`` (for integers), ``float`` (for decimal numbers), ``int[50]`` for an array of 50 integers, etc. +- **restore_value** (*Optional*, boolean): Whether to try to restore the state on boot up. + Be careful: on the ESP8266, you only have a total of 96 bytes available for this! Defaults to ``no``. + This will use storage in "RTC memory", so it won't survive a power-cycle unless you use the ``esp8266_restore_from_flash`` option to save to flash. See :doc:`esp8266_restore_from_flash ` for details. +- **max_restore_data_length** (*Optional*, integer): Only applies to variables of type ``std::string``. ESPHome will allocate enough space for this many characters, + plus single character of overhead. Strings longer than this will not be saved. The max value of this variable is 254 characters, and the default is 63 characters. +- **initial_value** (*Optional*, string): The value with which to initialize this variable if the state + can not be restored or if state restoration is not enabled. This needs to be wrapped in quotes! Defaults to + the C++ default value for this type (for example ``0`` for integers). + +.. _globals-set_action: + +``globals.set`` Action +---------------------- + +This :ref:`Action ` allows you to change the value of a :ref:`global ` +variable without having to use the lambda syntax. + +.. code-block:: yaml + + on_...: + - globals.set: + id: my_global_var + value: '10' + +Configuration variables: + +- **id** (**Required**, :ref:`config-id`): The :ref:`config-id` of the global variable to set. +- **value** (**Required**, :ref:`templatable `): The value to set the global + variable to. + +See Also +-------- + +- :doc:`index` +- :doc:`/automations/actions` +- :doc:`/automations/templates` +- :ghedit:`Edit` diff --git a/components/interval.rst b/components/interval.rst new file mode 100644 index 000000000..c5edfe0bb --- /dev/null +++ b/components/interval.rst @@ -0,0 +1,34 @@ +.. _interval: + +``interval`` Component +---------------------- + +This component allows you to run actions at fixed time intervals. For example, if you want to toggle a switch every +minute, you can use this component. Please note that it's possible to achieve the same thing with the +:ref:`time.on_time ` trigger, but this technique is more light-weight and user-friendly. + +.. code-block:: yaml + + # Example configuration entry + interval: + - interval: 1min + then: + - switch.toggle: relay_1 + + +If a startup delay is configured, the first execution of the actions will not occur before at least that time after boot. + +Configuration variables: +************************ + +- **interval** (**Required**, :ref:`config-time`): The interval to execute the action with. +- **startup_delay** (*Optional*, :ref:`config-time`): An optional startup delay - defaults to zero. +- **then** (**Required**, :ref:`Action `): The action to perform. + +See Also +-------- + +- :doc:`index` +- :doc:`/automations/actions` +- :doc:`/automations/templates` +- :ghedit:`Edit` diff --git a/components/lock/template.rst b/components/lock/template.rst index bd88d7f5a..e94a42ffd 100644 --- a/components/lock/template.rst +++ b/components/lock/template.rst @@ -106,7 +106,7 @@ Configuration options: See Also -------- -- :doc:`/guides/automations` +- :doc:`/automations/index` - :doc:`/components/lock/index` - :doc:`/components/binary_sensor/index` - :apiref:`template/lock/template_lock.h` diff --git a/components/script.rst b/components/script.rst new file mode 100644 index 000000000..1b400fd11 --- /dev/null +++ b/components/script.rst @@ -0,0 +1,187 @@ +.. _scripts: + +``script`` Component +-------------------- + +ESPHome's ``script`` component allows you to define a list of steps (actions) in a central place. You can then execute +the script from nearly anywhere in your device's configuration with a single call. + +.. code-block:: yaml + + # Example configuration entry + script: + - id: my_script + then: + - switch.turn_on: my_switch + - delay: 1s + - switch.turn_off: my_switch + +Configuration variables: + +- **id** (**Required**, :ref:`config-id`): The :ref:`config-id` of the script. Use this to interact with the script + using the script actions. +- **mode** (*Optional*, string): Controls what happens when a script is invoked while it is still running from one or + more previous invocations. Default to ``single``. + + - ``single``: Do not start a new run. Issue a warning. + - ``restart``: Start a new run after first stopping previous run. + - ``queued``: Start a new run after previous runs complete. + - ``parallel``: Start a new, independent run in parallel with previous runs. + +- **max_runs** (*Optional*, int): Allows limiting the maximum number of runs when using script modes ``queued`` and + ``parallel``, use value ``0`` for unlimited runs. Defaults to ``0``. +- **parameters** (*Optional*, :ref:`Script Parameters `): A script can define one or more parameters + that must be provided in order to execute. All parameters defined here are mandatory and must be given when calling + the script. +- **then** (**Required**, :ref:`Action `): The action to perform. + +.. _script-parameters: + +Script Parameters +----------------- + +Scripts can be defined with parameters. The arguments given when calling the script can be used within the script's +lambda actions. To define the parameters, add the parameter names under the ``parameters:`` key and specify the data +type for that parameter. + +Supported data types: + +* ``bool``: A boolean true/false. C++ type: ``bool`` +* ``int``: An integer. C++ type: ``int32_t`` +* ``float``: A floating point number. C++ type: ``float`` +* ``string``: A string. C++ type: ``std::string`` + +Each of these also exist in array form: + +* ``bool[]``: An array of boolean values. C++ type: ``std::vector`` +* Same for other types. + +.. code-block:: yaml + + script: + - id: blink_light + parameters: + delay_ms: int + then: + - light.turn_on: status_light + # The param delay_ms is accessible using a lambda + - delay: !lambda return delay_ms; + - light.turn_off: status_light + +.. _script-execute_action: + +``script.execute`` Action +------------------------- + +This action executes the script. The script **mode** dictates what will happen if the script was already running. + +.. code-block:: yaml + + # in a trigger: + on_...: + then: + - script.execute: my_script + + # Calling a non-parameterised script in a lambda + - lambda: id(my_script).execute(); + + # Calling a script with parameters + - script.execute: + id: blink_light + delay_ms: 500 + + # Calling a parameterised script inside a lambda + - lambda: id(blink_light)->execute(1000); + +.. _script-stop_action: + +``script.stop`` Action +---------------------- + +This action allows you to stop a given script during execution. If the script is not running, it does nothing. This is +useful if you want to stop a script that contains a ``delay`` action, ``wait_until`` action, or is inside a ``while`` +loop, etc. You can also call this action from the script itself, and any subsequent action will not be executed. + +.. code-block:: yaml + + # Example configuration entry + script: + - id: my_script + then: + - switch.turn_on: my_switch + - delay: 1s + - switch.turn_off: my_switch + + # in a trigger: + on_...: + then: + - script.stop: my_script + +...or as lambda: + +.. code-block:: yaml + + lambda: 'id(my_script).stop();' + +.. _script-wait_action: + +``script.wait`` Action +---------------------- + +This action suspends execution of the automation until a script has finished executing. + +Note: If no script is executing, this will continue immediately. If multiple instances of the script are running in +parallel, this will block until all of them have terminated. + +.. code-block:: yaml + + # Example configuration entry + script: + - id: my_script + then: + - switch.turn_on: my_switch + - delay: 1s + - switch.turn_off: my_switch + + # in a trigger: + on_...: + then: + - script.execute: my_script + - script.wait: my_script + +This can't be used in a lambda as it would block all functioning of the device. The script wouldn't even get to run. + +.. _script-is_running_condition: + +``script.is_running`` Condition +------------------------------- + +This :ref:`condition ` allows you to check if a given script is running. In case scripts are run in +``parallel``, this condition only tells you if at least one script of the given id is running, not how many. Not +designed for use with :ref:`while `; instead try :ref:`script.wait `. + +.. code-block:: yaml + + on_...: + if: + condition: + - script.is_running: my_script + then: + - logger.log: Script is running! + +...or as lambda: + +.. code-block:: yaml + + lambda: |- + if (id(my_script).is_running()) { + ESP_LOGI("main", "Script is running!"); + } + +See Also +-------- + +- :doc:`index` +- :doc:`/automations/actions` +- :doc:`/automations/templates` +- :ghedit:`Edit` diff --git a/components/switch/template.rst b/components/switch/template.rst index 953e22568..6164bbd58 100644 --- a/components/switch/template.rst +++ b/components/switch/template.rst @@ -97,7 +97,7 @@ Configuration options: See Also -------- -- :doc:`/guides/automations` +- :doc:`/automations/index` - :doc:`/components/switch/index` - :doc:`/components/binary_sensor/index` - :apiref:`template/switch/template_switch.h` diff --git a/cookbook/garage-door.rst b/cookbook/garage-door.rst index 92a3156e7..0382c552b 100644 --- a/cookbook/garage-door.rst +++ b/cookbook/garage-door.rst @@ -44,7 +44,7 @@ for a short period of time, the close/open action begins. See Also -------- -- :doc:`/guides/automations` +- :doc:`/automations/index` - :doc:`/components/switch/gpio` - :doc:`/components/cover/template` - :ghedit:`Edit` diff --git a/guides/automations.rst b/guides/automations.rst deleted file mode 100644 index 5dabced22..000000000 --- a/guides/automations.rst +++ /dev/null @@ -1,1026 +0,0 @@ -.. _automation: - -Automations and Templates -========================= - -.. seo:: - :description: Getting started guide for automations in ESPHome. - :image: auto-fix.svg - -Automations and templates are two very powerful aspects of ESPHome. Automations -allow you to perform actions under certain conditions and templates are a way to easily -customize everything about your node without having to dive into the full ESPHome C++ -API. - -Let's begin with an example to explain these concepts. Suppose you have this configuration file: - -.. code-block:: yaml - - 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 quite a pain. That's why you have -decided to also install a simple push button next to the dehumidifier on pin GPIOXX. -A simple push on 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 -ideally the IoT should work without an internet connection and should not break with -the MQTT server being offline. - -That's why, starting with ESPHome 1.7.0, there's a new automation engine. With it, you -can write some basic (and also some more advanced) automations using a syntax that is -hopefully a bit easier to read and understand than Home Assistant's. - -For example, this configuration would achieve your desired behavior: - -.. code-block:: yaml - - 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 - - - -Woah, hold on there. Please explain what's going on here! Sure :) Let's step through what's happening here. - -.. code-block:: yaml - - switch: - - platform: gpio - # ... - id: dehumidifier1 - -First, we have to give the dehumidifier a :ref:`config-id` so that we can -later use it inside our awesome automation. - -.. code-block:: yaml - - binary_sensor: - - platform: gpio - # ... - on_press: - -We now attach a special attribute ``on_press`` to the toggle button. This part is called a "trigger". In this example, -the automation in 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/mouse. -There are also other triggers like ``on_release``, ``on_click`` or ``on_double_click`` available. - - -.. code-block:: yaml - - # ... - on_press: - then: - - switch.toggle: dehumidifier1 - -.. _config-action: - -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 series by just adding another ``-`` -like so: - -.. code-block:: yaml - - # ... - on_press: - then: - - switch.toggle: dehumidifier1 - - delay: 2s - - switch.toggle: dehumidifier1 - -With this automation, a press on the push button would cause the dehumidifier to turn on/off for 2 seconds, and then -cycle back to its original state. Similarly you can have a single trigger with multiple automations: - -.. code-block:: yaml - - # ... - 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 last 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%: - -.. code-block:: yaml - - 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 trigger when the value output -of the sensor is within a certain range. In the first example, this range is defined as "any value above or including -65.0", and the second one refers to once the humidity reaches 50% or below. - -Now that concludes the introduction to automations in ESPHome. They're a powerful tool to automate almost -everything on your device with an easy-to-use syntax. For the cases where the "pure" YAML automations don't work, -ESPHome has another extremely powerful tool to offer: Templates. - -.. _config-lambda: - -Templates (Lambdas) -------------------- - -With templates inside ESPHome, you can do almost *everything*. If for example you want to only perform a certain -automation if a certain complex formula evaluates to true, you can do that with templates. Let's look at an example -first: - -.. code-block:: yaml - - binary_sensor: - - platform: gpio - name: "Cover End Stop" - id: top_end_stop - cover: - - platform: template - name: Living Room Cover - lambda: !lambda |- - if (id(top_end_stop).state) { - return COVER_OPEN; - } else { - return COVER_CLOSED; - } - -What's happening here? First, we define a binary sensor (with the id ``top_end_stop``) and then a -:doc:`template cover `. (If you're new to Home Assistant, a 'cover' is -something like a window blind, a roller shutter, or a garage door.) The *state* of the template cover is -controlled by a template, or "lambda". In lambdas you're effectively writing C++ code and therefore the -name lambda is used instead of Home Assistant's "template" lingo to avoid confusion. But before you go -shy away from using lambdas because you just hear C++ and think oh noes, I'm not going down *that* road: -Writing lambdas is not that hard! Here's a bit of a primer: - -First, you might have already wondered what the ``lambda: !lambda |-`` part is supposed to mean. ``!lambda`` -tells ESPHome that the following block is supposed to be interpreted as a lambda, or C++ code. Note that -here, the ``lambda:`` key would actually implicitly make the following block a lambda so in this context, -you could have just written ``lambda: |-``. - -Next, there's the weird ``|-`` character combination. This effectively tells the YAML parser to treat the following -**indented** (!) block as plaintext. Without it, the YAML parser would attempt to read the following block as if -it were made up of YAML keys like ``cover:`` for example. (You may also have seen variations of this like ``>-`` -or just ``|`` or ``>``. There's a slight difference in how these different styles deal with whitespace, but for our -purposes we can ignore that). - -With ``if (...) { ... } else { ... }`` we create a *condition*. What this effectively says that if the thing inside -the first parentheses evaluates to ``true`` then execute the first block (in this case ``return COVER_OPEN;``, -or else evaluate the second block. ``return ...;`` makes the code block give back a value to the template. In this case, -we're either *returning* ``COVER_OPEN`` or ``COVER_CLOSED`` to indicate that the cover is closed or open. - -Finally, ``id(...)`` is a helper function that makes ESPHome fetch an object with the supplied ID (which you defined -somewhere else, like ``top_end_stop``) and lets you call any of ESPHome's many APIs directly. For example, here -we're retrieving the current state of the end stop using ``.state`` and using it to construct our cover state. - -.. note:: - - ESPHome does not check the validity of lambda expressions you enter and will blindly copy - them into the generated C++ code. If compilation fails or something else is not working as expected - with lambdas, it's always best to look at the generated C++ source file under ``/src/main.cpp``. - -.. tip:: - - To store local variables inside lambdas that retain their value across executions, you can create ``static`` - variables like so. In this example the variable ``num_executions`` is incremented by one each time the - lambda is executed and the current value is logged. - - .. code-block:: yaml - - lambda: |- - static int num_executions = 0; - ESP_LOGD("main", "I am at execution number %d", num_executions); - num_executions += 1; - -.. _config-templatable: - -Bonus: Templating Actions -************************* - -Another feature of ESPHome is that you can template almost every parameter for actions in automations. For example -if you have a light and want to set it to a pre-defined color when a button is pressed, you can do this: - -.. code-block:: yaml - - on_press: - then: - - light.turn_on: - id: some_light_id - transition_length: 0.5s - red: 0.8 - green: 1.0 - blue: !lambda |- - // The sensor outputs values from 0 to 100. The blue - // part of the light color will be determined by the sensor value. - return id(some_sensor).state / 100.0; - -Every parameter in actions that has the label "templatable" in the docs can be templated like above, using -all of the usual lambda syntax. - -.. _config-globals: - -Global Variables ----------------- - -In some cases you might require to share a global variable across multiple lambdas. For example, -global variables can be used to store the state of a garage door. - -.. code-block:: yaml - - # Example configuration entry - globals: - - id: my_global_int - type: int - restore_value: no - initial_value: '0' - # Example for global string variable - - id: my_global_string - type: std::string - restore_value: yes - max_restore_data_length: 24 - initial_value: '"Global value is"' - - # In an automation - on_press: - then: - - lambda: |- - if (id(my_global_int) > 5) { - // global value is greater than 5 - id(my_global_int) += 1; - } else { - id(my_global_int) += 10; - } - - ESP_LOGD(TAG, "%s: %d", id(my_global_string).c_str(), id(my_global_int)); - -Configuration variables: - -- **id** (**Required**, :ref:`config-id`): Give the global variable an ID so that you can refer - to it later in :ref:`lambdas `. -- **type** (**Required**, string): The C++ type of the global variable, for example ``bool`` (for ``true``/``false``), - ``int`` (for integers), ``float`` (for decimal numbers), ``int[50]`` for an array of 50 integers, etc. -- **restore_value** (*Optional*, boolean): Whether to try to restore the state on boot up. - Be careful: on the ESP8266, you only have a total of 96 bytes available for this! Defaults to ``no``. - This will use storage in "RTC memory", so it won't survive a power-cycle unless you use the ``esp8266_restore_from_flash`` option to save to flash. See :doc:`esp8266_restore_from_flash ` for details. -- **max_restore_data_length** (*Optional*, integer): Only applies to variables of type ``std::string``. ESPHome will allocate enough space for this many characters, - plus single character of overhead. Strings longer than this will not be saved. The max value of this variable is 254 characters, and the default is 63 characters. -- **initial_value** (*Optional*, string): The value with which to initialize this variable if the state - can not be restored or if state restoration is not enabled. This needs to be wrapped in quotes! Defaults to - the C++ default value for this type (for example ``0`` for integers). - -.. _automation-networkless: - -Do Automations Work Without a Network Connection ------------------------------------------------- - -YES! All automations you define in ESPHome are executed on the ESP itself and will continue to -work even if the WiFi network is down or the MQTT server is not reachable. - -There is one caveat though: ESPHome automatically reboots if no connection to the MQTT broker can be -made. This is because the ESPs typically have issues in their network stacks that require a reboot to fix. -You can adjust this behavior (or even disable automatic rebooting) using the ``reboot_timeout`` option -in the :doc:`wifi component ` and :doc:`mqtt component `. -(Beware that effectively disables the reboot watchdog, so you will need to power cycle the device -if it fails to connect to the network without a reboot) - -All Triggers ------------- - -- :ref:`api.services ` / :ref:`api.on_client_connected ` / :ref:`api.on_client_disconnected ` -- :ref:`sensor.on_value ` / :ref:`sensor.on_raw_value ` / :ref:`sensor.on_value_range ` -- :ref:`binary_sensor.on_press ` / :ref:`binary_sensor.on_release ` / - :ref:`binary_sensor.on_state ` -- :ref:`binary_sensor.on_click ` / :ref:`binary_sensor.on_double_click ` / - :ref:`binary_sensor.on_multi_click ` -- :ref:`esphome.on_boot ` / :ref:`esphome.on_shutdown ` / :ref:`esphome.on_loop ` -- :ref:`light.on_turn_on / light.on_turn_off ` -- :ref:`logger.on_message ` -- :ref:`time.on_time ` / - :ref:`time.on_time_sync ` -- :ref:`mqtt.on_message ` / :ref:`mqtt.on_json_message ` / - :ref:`mqtt.on_connect / mqtt.on_disconnect ` -- :ref:`pn532.on_tag ` / :ref:`pn532.on_tag_removed ` / :ref:`rc522.on_tag ` - / :ref:`rc522.on_tag_removed ` / :ref:`rdm6300.on_tag ` -- :ref:`interval.interval ` -- :ref:`switch.on_turn_on / switch.on_turn_off ` -- :doc:`remote_receiver.on_* ` -- :doc:`sun.on_sunrise ` / :doc:`sun.on_sunset ` -- :ref:`sim800l.on_sms_received ` -- :ref:`rf_bridge.on_code_received ` -- :ref:`ota.on_begin ` / :ref:`ota.on_progress ` / - :ref:`ota.on_end ` / :ref:`ota.on_error ` / - :ref:`ota.on_state_change ` -- :ref:`display.on_page_change ` -- :ref:`cover.on_open ` / :ref:`cover.on_closed ` -- :ref:`safe_mode.on_safe_mode ` -- :ref:`wifi.on_connect / wifi.on_disconnect ` - -All Actions ------------ - -- :ref:`delay ` -- :ref:`lambda ` -- :ref:`if ` / :ref:`while ` / :ref:`wait_until ` -- :ref:`component.update ` -- :ref:`component.suspend ` / :ref:`component.resume ` -- :ref:`script.execute ` / :ref:`script.stop ` / :ref:`script.wait ` -- :ref:`logger.log ` -- :ref:`homeassistant.service ` -- :ref:`homeassistant.event ` -- :ref:`homeassistant.tag_scanned ` -- :ref:`mqtt.publish ` / :ref:`mqtt.publish_json ` -- :ref:`switch.toggle ` / :ref:`switch.turn_off ` / :ref:`switch.turn_on ` -- :ref:`light.toggle ` / :ref:`light.turn_off ` / :ref:`light.turn_on ` - / :ref:`light.control ` / :ref:`light.dim_relative ` - / :ref:`light.addressable_set ` -- :ref:`cover.open ` / :ref:`cover.close ` / :ref:`cover.stop ` / - :ref:`cover.control ` -- :ref:`fan.toggle ` / :ref:`fan.turn_off ` / :ref:`fan.turn_on ` -- :ref:`output.turn_off ` / :ref:`output.turn_on ` / :ref:`output.set_level ` -- :ref:`deep_sleep.enter ` / :ref:`deep_sleep.prevent ` / :ref:`deep_sleep.allow ` -- :ref:`sensor.template.publish ` / :ref:`binary_sensor.template.publish ` - / :ref:`cover.template.publish ` / :ref:`switch.template.publish ` - / :ref:`text_sensor.template.publish ` -- :ref:`stepper.set_target ` / :ref:`stepper.report_position ` - / :ref:`stepper.set_speed ` -- :ref:`servo.write ` / :ref:`servo.detach ` -- :ref:`sprinkler.start_full_cycle ` / :ref:`sprinkler.start_from_queue ` / - :ref:`sprinkler.start_single_valve ` / :ref:`sprinkler.shutdown ` / - :ref:`sprinkler.next_valve ` / :ref:`sprinkler.previous_valve ` / - :ref:`sprinkler.pause ` / :ref:`sprinkler.resume ` / - :ref:`sprinkler.resume_or_start_full_cycle ` / :ref:`sprinkler.queue_valve ` / - :ref:`sprinkler.clear_queued_valves ` / :ref:`sprinkler.set_multiplier ` / - :ref:`sprinkler.set_repeat ` / :ref:`sprinkler.set_divider ` / - :ref:`sprinkler.set_valve_run_duration ` -- :ref:`globals.set ` -- :ref:`remote_transmitter.transmit_* ` -- :ref:`climate.control ` -- :ref:`output.esp8266_pwm.set_frequency ` / :ref:`output.ledc.set_frequency ` -- :ref:`sensor.integration.reset ` -- :ref:`display.page.show_* ` -- :ref:`uart.write ` -- :ref:`sim800l.send_sms ` -- :ref:`mhz19.calibrate_zero ` / :ref:`mhz19.abc_enable ` / :ref:`mhz19.abc_disable ` -- :ref:`sensor.rotary_encoder.set_value ` -- :ref:`http_request.get ` / :ref:`http_request.post ` / :ref:`http_request.send ` -- :ref:`rf_bridge.send_code ` -- :ref:`rf_bridge.learn ` -- :ref:`ds1307.read_time ` / :ref:`ds1307.write_time ` -- :ref:`pcf85063.read_time ` / :ref:`pcf85063.write_time ` -- :ref:`cs5460a.restart ` -- :ref:`pzemac.reset_energy ` -- :ref:`number.set ` / :ref:`number.to_min ` / :ref:`number.to_max ` / :ref:`number.decrement ` / :ref:`number.increment ` / :ref:`number.operation ` -- :ref:`select.set ` / :ref:`select.set_index ` / :ref:`select.first ` / :ref:`select.last ` / :ref:`select.previous ` / :ref:`select.next ` / :ref:`select.operation ` -- :ref:`media_player.play ` / :ref:`media_player.pause ` / :ref:`media_player.stop ` / :ref:`media_player.toggle ` - / :ref:`media_player.volume_up ` / :ref:`media_player.volume_down ` / :ref:`media_player.volume_set ` -- :ref:`ble_client.ble_write ` -- :ref:`wireguard.disable ` / :ref:`wireguard.enable ` - -.. _config-condition: - -All Conditions --------------- - -- :ref:`lambda ` -- :ref:`and ` / :ref:`or ` / :ref:`xor ` / :ref:`not ` -- :ref:`for ` -- :ref:`binary_sensor.is_on ` / :ref:`binary_sensor.is_off ` -- :ref:`switch.is_on ` / :ref:`switch.is_off ` -- :ref:`sensor.in_range ` -- :ref:`wifi.connected ` / :ref:`api.connected ` - / :ref:`mqtt.connected ` -- :ref:`time.has_time ` -- :ref:`script.is_running ` -- :ref:`sun.is_above_horizon / sun.is_below_horizon ` -- :ref:`text_sensor.state ` -- :ref:`light.is_on ` / :ref:`light.is_off ` -- :ref:`display.is_displaying_page ` -- :ref:`number.in_range ` -- :ref:`fan.is_on ` / :ref:`fan.is_off ` -- :ref:`wireguard.enabled ` / :ref:`wireguard.peer_online ` - -All Lambda Calls ----------------- - -- :ref:`Sensor ` -- :ref:`Binary Sensor ` -- :ref:`Switch ` -- :ref:`Display ` -- :ref:`Cover ` -- :ref:`Text Sensor ` -- :ref:`Stepper ` -- :ref:`Number ` - -.. _delay_action: - -``delay`` Action ----------------- - -This action delays the execution of the next action in the action list by a specified -time period. - -.. code-block:: yaml - - 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. - -.. _lambda_action: - -``lambda`` Action ------------------ - -This action executes an arbitrary piece of C++ code (see :ref:`Lambda `). - -.. code-block:: yaml - - on_...: - then: - - lambda: |- - id(some_binary_sensor).publish_state(false); - -.. _lambda_condition: - -``lambda`` Condition --------------------- - -This condition performs an arbitrary piece of C++ code (see :ref:`Lambda `) -and can be used to create conditional flow in actions. - -.. code-block:: yaml - - on_...: - then: - - if: - condition: - # Should return either true or false - lambda: |- - return id(some_sensor).state < 30; - # ... - -.. _and_condition: -.. _or_condition: -.. _xor_condition: -.. _not_condition: - -``and`` / ``or`` / ``xor`` / ``not`` Condition ----------------------------------------------- - -Check a combination of conditions - -.. code-block:: yaml - - 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: - condition: - not: - binary_sensor.is_off: some_binary_sensor - -.. _if_action: - -``if`` Action -------------- - -This action first evaluated a certain condition (``if:``) and then either -executes the ``then:`` branch or the ``else:`` branch depending on the output of the condition. - -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. - -.. code-block:: yaml - - 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: - -- **condition** (**Required**, :ref:`config-condition`): The condition to check which branch to take. See :ref:`Conditions `. -- **then** (*Optional*, :ref:`Action `): The action to perform if the condition evaluates to true. - Defaults to doing nothing. -- **else** (*Optional*, :ref:`Action `): The action to perform if the condition evaluates to false. - Defaults to doing nothing. - -.. _while_action: - -``while`` Action ----------------- - -This action is similar to the :ref:`if ` Action. The ``while`` action loops -through a block as long as the given condition is true. - -.. code-block:: yaml - - # 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**): The condition to check whether to execute. See :ref:`Conditions `. -- **then** (**Required**, :ref:`Action `): The action to perform until the condition evaluates to false. - -.. _repeat_action: - -``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. - -.. code-block:: yaml - - 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. -- **then** (**Required**, :ref:`Action `): The action to repeat. - -.. _wait_until_action: - -``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.) - -.. code-block:: yaml - - # 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: - -.. code-block:: yaml - - # 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**): The condition to wait to become true. See :ref:`Conditions `. -- **timeout** (*Optional*, :ref:`config-time`): Time to wait before timing out. Defaults to never timing out. - -.. _component-update_action: - -``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. - -.. code-block:: yaml - - on_...: - then: - - component.update: my_component - - # The same as: - - lambda: 'id(my_component).update();' - -.. _component-suspend_action: - -``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 :ref:`component.update ` - -Please note that this only works with PollingComponent types and others will result in a -compile error. - -.. code-block:: yaml - - on_...: - then: - - component.suspend: my_component - - # The same as: - - lambda: 'id(my_component).stop_poller();' - -.. _component-resume_action: - -``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. - -.. code-block:: yaml - - 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 - - -.. _globals-set_action: - -``globals.set`` Action ----------------------- - -This :ref:`Action ` allows you to change the value of a :ref:`global ` -variable without having to go through the lambda syntax. - -.. code-block:: yaml - - on_...: - - globals.set: - id: my_global_var - value: '10' - -Configuration variables: - -- **id** (**Required**, :ref:`config-id`): The :ref:`config-id` of the global variable to set. -- **value** (**Required**, :ref:`templatable `): The value to set the global - variable to. - - -``script`` Component --------------------- - -With the ``script:`` component you can define a list of steps in a central place, and then -execute the script with a single call. - -.. code-block:: yaml - - # Example configuration entry - script: - - id: my_script - then: - - switch.turn_on: my_switch - - delay: 1s - - switch.turn_off: my_switch - - -Configuration variables: - -- **id** (**Required**, :ref:`config-id`): The :ref:`config-id` of the script. Use this - to interact with the script using the script actions. -- **mode** (*Optional*, string): Controls what happens when a script is - invoked while it is still running from one or more previous invocations. Default to ``single``. - - - ``single``: Do not start a new run. Issue a warning. - - ``restart``: Start a new run after first stopping previous run. - - ``queued``: Start a new run after previous runs complete. - - ``parallel``: Start a new, independent run in parallel with previous runs. - -- **max_runs** (*Optional*, int): Allows limiting the maxiumun number of runs when using script - modes ``queued`` and ``parallel``, use value ``0`` for unlimited runs. Defaults to ``0``. -- **parameters** (*Optional*, :ref:`Script Parameters `): A script can define one - or more parameters that must be provided in order to execute. All parameters defined here are - mandatory and must be given when calling the script. -- **then** (**Required**, :ref:`Action `): The action to perform. - - -.. _script-parameters: - -``Script Parameters`` ---------------------- - -Scripts can be defined with parameters. The arguments given when calling the script can be used within -the script's lambda actions. To define the parameters, add the parameter names under `parameters:` key -and specify the data type for that parameter. - -Supported data types: - -* `bool`: A boolean true/false. C++ type: `bool` -* `int`: An integer. C++ type: `int32_t` -* `float`: A floating point number. C++ type: `float` -* `string`: A string. C++ type: `std::string` - -Each of these also exist in array form: - -* `bool[]`: An array of boolean values. C++ type: `std::vector` -* Same for other types. - -.. code-block:: yaml - - script: - - id: blink_light - parameters: - delay_ms: int - then: - - light.turn_on: status_light - # The param delay_ms is accessible using a lambda - - delay: !lambda return delay_ms; - - light.turn_off: status_light - -.. _script-execute_action: - -``script.execute`` Action -------------------------- - -This action executes the script. The script **mode** dictates what will happen if the -script was already running. - -.. code-block:: yaml - - # in a trigger: - on_...: - then: - - script.execute: my_script - - # Calling a non-parameterised script in a lambda - - lambda: id(my_script).execute(); - - # Calling a script with parameters - - script.execute: - id: blink_light - delay_ms: 500 - - # Calling a parameterised script inside a lambda - - lambda: id(blink_light)->execute(1000); - -.. _script-stop_action: - -``script.stop`` Action ----------------------- - -This action allows you to stop a given script during execution. If the -script is not running, it does nothing. -This is useful if you want to stop a script that contains a -``delay`` action, ``wait_until`` action, or is inside a ``while`` loop, etc. -You can also call this action from the script itself, and any subsequent action -will not be executed. - -.. code-block:: yaml - - # Example configuration entry - script: - - id: my_script - then: - - switch.turn_on: my_switch - - delay: 1s - - switch.turn_off: my_switch - - # in a trigger: - on_...: - then: - - script.stop: my_script - -or as lambda - -.. code-block:: yaml - - lambda: 'id(my_script).stop();' - -.. _script-wait_action: - -``script.wait`` Action ----------------------- - -This action suspends execution of the automation until a script has finished executing. - -Note: If no script is executing, this will continue immediately. If multiple instances -of the script are running in parallel, this will block until all of them have terminated. - -.. code-block:: yaml - - # Example configuration entry - script: - - id: my_script - then: - - switch.turn_on: my_switch - - delay: 1s - - switch.turn_off: my_switch - - # in a trigger: - on_...: - then: - - script.execute: my_script - - script.wait: my_script - -This can't be used in a lambda as it would block all functioning of the device. The script wouldn't even get to run. - -.. _script-is_running_condition: - -``script.is_running`` Condition -------------------------------- - -This :ref:`condition ` allows you to check if a given script is running. -In case scripts are run in ``parallel``, this condition only tells you if at least one script -of the given id is running, not how many. Not designed for use with :ref:`while `, instead try :ref:`script.wait `. - -.. code-block:: yaml - - on_...: - if: - condition: - - script.is_running: my_script - then: - - logger.log: Script is running! - -or as lambda - -.. code-block:: yaml - - lambda: |- - if (id(my_script).is_running()) { - ESP_LOGI("main", "Script is running!"); - } - -.. _for_condition: - -``for`` Condition ------------------ - -This :ref:`Condition ` allows you to check if a given condition has been -true for at least a given amount of time. - -.. code-block:: yaml - - 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**, :ref:`templatable `, :ref:`config-time`): - The time for which the condition has to have been true. -- **condition** (**Required**, :ref:`Condition `): - The condition to check. - -.. _interval: - -``interval`` Component ----------------------- - -This component allows you to run actions at fixed time intervals. -For example if you want to toggle a switch every minute, you can use this component. -Please note that it's possible to achieve the same thing with the :ref:`time.on_time ` -trigger, but this technique is more light-weight and user-friendly. - -.. code-block:: yaml - - # Example configuration entry - interval: - - interval: 1min - then: - - switch.toggle: relay_1 - - -If a startup delay is configured, the first execution of the actions will not occur before at least that time -after boot. - -Configuration variables: - -- **interval** (**Required**, :ref:`config-time`): The interval to execute the action with. -- **startup_delay** (*Optional*, :ref:`config-time`): An optional startup delay - defaults to zero. -- **then** (**Required**, :ref:`Action `): The action to perform. - - -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. - -.. code-block:: yaml - - 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, that is ok too, just -use a single ``delay`` action, then in your automation check ``script.is_running`` condition -to know if your *timer* is going or due. - -See Also --------- - -- :doc:`configuration-types` -- :doc:`faq` -- :ghedit:`Edit` diff --git a/index.rst b/index.rst index 904b61cf0..bde2dd570 100644 --- a/index.rst +++ b/index.rst @@ -61,7 +61,7 @@ ESPHome is a system to control your microcontrollers by simple yet powerful conf
  • - + Automations
  • @@ -143,6 +143,19 @@ Peripherals which directly support the operation of the microcontroller's proces PSRAM, components/psram, psram.svg Deep Sleep, components/deep_sleep, hotel.svg, dark-invert +ESPHome Automations +------------------- + +*"When this happens, I want it to do that..."* + +Automations are how we customize ESPHome devices to respond/behave exactly how you want them to. + +.. imgtable:: + + Overview, automations/index, description.svg, dark-invert + "Actions, Triggers, Conditions", automations/actions, description.svg, dark-invert + Templates, automations/templates, description.svg, dark-invert + ESPHome Components ------------------ @@ -155,8 +168,11 @@ ESPHome-specific components or components supporting ESPHome device provisioning Copy, components/copy, content-copy.svg, dark-invert Demo, components/demo, description.svg, dark-invert External Components, components/external_components, external_components.svg, dark-invert + Globals, components/globals, description.svg, dark-invert Improv via BLE, components/esp32_improv, improv.svg, dark-invert Improv via Serial, components/improv_serial, improv.svg, dark-invert + Interval, components/interval, description.svg, dark-invert + Scripts, components/scripts, description.svg, dark-invert Network Hardware ---------------- @@ -1109,6 +1125,7 @@ If you'd like to share configurations for specific devices, please contribute to :hidden: web-api/index + automations/index components/index cookbook/index guides/index diff --git a/schema_doc.py b/schema_doc.py index 21042f584..df83ae774 100644 --- a/schema_doc.py +++ b/schema_doc.py @@ -89,7 +89,7 @@ PLATFORMS_TITLES = { } CUSTOM_DOCS = { - "guides/automations": { + "components/globals": { "Global Variables": "globals.schemas.CONFIG_SCHEMA", }, "guides/configuration-types": {