|
| 1 | +# Implementing Automations |
| 2 | + |
| 3 | +We have an example, minimal component which implements an Action, a Condition and a Trigger |
| 4 | +[here](https://github.com/esphome/starter-components/tree/main/components/empty_automation). |
| 5 | + |
| 6 | +Let's take a closer look at this example. |
| 7 | + |
| 8 | +## Python |
| 9 | + |
| 10 | +In addition to the usual requisite imports, we have: |
| 11 | + |
| 12 | +```python |
| 13 | +from esphome import automation |
| 14 | +``` |
| 15 | + |
| 16 | +This allows us to use some functions we'll need for validation and code generation later on. |
| 17 | + |
| 18 | +```python |
| 19 | +empty_automation_ns = cg.esphome_ns.namespace("empty_automation") |
| 20 | +EmptyAutomation = empty_automation_ns.class_("EmptyAutomation", cg.Component) |
| 21 | +EmptyAutomationSetStateAction = empty_automation_ns.class_( |
| 22 | + "EmptyAutomationSetStateAction", automation.Action |
| 23 | +) |
| 24 | +EmptyAutomationCondition = empty_automation_ns.class_( |
| 25 | + "EmptyAutomationCondition", automation.Condition |
| 26 | +) |
| 27 | +StateTrigger = empty_automation_ns.class_( |
| 28 | + "StateTrigger", automation.Trigger.template(bool) |
| 29 | +) |
| 30 | + |
| 31 | +``` |
| 32 | + |
| 33 | +This is some boilerplate code which allows ESPHome code generation to understand the namespace and class names that the |
| 34 | +component will use: |
| 35 | + |
| 36 | +- The namespace and class for the component which will implement the automations |
| 37 | +- A class to be used to implement each automation: |
| 38 | + - Action |
| 39 | + - Condition |
| 40 | + - Trigger |
| 41 | + |
| 42 | +```python |
| 43 | +CONFIG_SCHEMA = ... |
| 44 | +``` |
| 45 | + |
| 46 | +This defines the configuration schema for the component as discussed [here](index.md#configuration-validation). |
| 47 | +In particular, note that the schema includes: |
| 48 | +```python |
| 49 | +cv.Optional(CONF_ON_STATE): automation.validate_automation(...) |
| 50 | +``` |
| 51 | +Of note: |
| 52 | + |
| 53 | +- This allows the user to define an automation which will be triggered any time the component's "state" changes. In |
| 54 | + this case, the automation is called "on_state". |
| 55 | +- `cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger)` identifies the list of actions the component should |
| 56 | + "trigger" as appropriate. |
| 57 | + |
| 58 | +In addition to `CONFIG_SCHEMA`, we define two additional schemas: |
| 59 | + |
| 60 | +- `EMPTY_AUTOMATION_ACTION_SCHEMA`: Will be used to validate the action (`empty_automation.set_state`) when the user |
| 61 | + calls it. |
| 62 | +- `EMPTY_AUTOMATION_CONDITION_SCHEMA`: Will be used to validate the conditions when invoked by the user: |
| 63 | + either `empty_automation.component_off` or `empty_automation.component_on`. |
| 64 | + |
| 65 | +After defining the various schemas we need for our component and its automations, we must register them so that they |
| 66 | +can be understood by the parser and codegen. |
| 67 | + |
| 68 | +```python |
| 69 | +@automation.register_action( |
| 70 | + "empty_automation.set_state", |
| 71 | + EmptyAutomationSetStateAction, |
| 72 | + EMPTY_AUTOMATION_ACTION_SCHEMA, |
| 73 | +) |
| 74 | +# ... |
| 75 | +@automation.register_condition( |
| 76 | + "empty_automation.component_off", |
| 77 | + EmptyAutomationCondition, |
| 78 | + EMPTY_AUTOMATION_CONDITION_SCHEMA, |
| 79 | +) |
| 80 | +``` |
| 81 | + |
| 82 | +...registers the action and the condition. Note that each contains the name of the automation, the class that defines |
| 83 | +what the automation will do, and finally the schema used for validation. |
| 84 | + |
| 85 | +In addition, note that each automation has a `to_code` function used for code generation: |
| 86 | + |
| 87 | +```python |
| 88 | +async def empty_automation_set_state_to_code(...): |
| 89 | +# ... |
| 90 | +async def empty_automation_component_on_to_code(...): |
| 91 | +``` |
| 92 | + |
| 93 | +Finally, we have the `to_code` function for the component itself, just as discussed [here](index.md#code-generation). |
| 94 | + |
| 95 | +## C++ |
| 96 | + |
| 97 | +The C++ class for this example component is quite simple. |
| 98 | + |
| 99 | +```cpp |
| 100 | +class EmptyAutomation : public Component { ... }; |
| 101 | +``` |
| 102 | +
|
| 103 | +As mentioned [here](/contributing/code/#c), all components/platforms must inherit from either `Component` or |
| 104 | +`PollingComponent`; our example here is no different. |
| 105 | +
|
| 106 | +The component also also implements two additional methods: |
| 107 | +
|
| 108 | +- The `void set_state(bool state)` method simply allows setting the state of the component. |
| 109 | +- The `void add_on_state_callback(std::function<void(bool)> &&callback)` method allows adding the actions which will be |
| 110 | + triggered when the state of the component changes. |
| 111 | +
|
| 112 | +In addition to the component's class, additional classes which implement the various automations are defined in |
| 113 | +`automation.h`: |
| 114 | +
|
| 115 | +- `EmptyAutomationSetStateAction` |
| 116 | +- `EmptyAutomationCondition` |
| 117 | +- `StateTrigger` |
| 118 | +
|
| 119 | +```cpp |
| 120 | +template<typename... Ts> class EmptyAutomationSetStateAction : public Action<Ts...> { |
| 121 | + public: |
| 122 | + explicit EmptyAutomationSetStateAction(EmptyAutomation *ea) : ea_(ea) {} |
| 123 | + TEMPLATABLE_VALUE(bool, state) |
| 124 | +
|
| 125 | + void play(Ts... x) override { |
| 126 | + auto val = this->state_.value(x...); |
| 127 | + this->ea_->set_state(val); |
| 128 | + } |
| 129 | +
|
| 130 | + protected: |
| 131 | + EmptyAutomation *ea_; |
| 132 | +}; |
| 133 | +
|
| 134 | +template<typename... Ts> class EmptyAutomationCondition : public Condition<Ts...> { |
| 135 | + public: |
| 136 | + EmptyAutomationCondition(EmptyAutomation *parent, bool state) : parent_(parent), state_(state) {} |
| 137 | + bool check(Ts... x) override { return this->parent_->state == this->state_; } |
| 138 | +
|
| 139 | + protected: |
| 140 | + EmptyAutomation *parent_; |
| 141 | + bool state_; |
| 142 | +}; |
| 143 | +``` |
| 144 | + |
| 145 | +Actions and Conditions are implemented as template classes which are instantiated with pointers to their "parent" (the |
| 146 | +instance of the component on which they will act). |
| 147 | + |
| 148 | +In this particular component: |
| 149 | + |
| 150 | +- The action accepts a templatable value--the state--which the component will be set to when the action is called. |
| 151 | +- The condition accepts an additional parameter which determines whether it will test for the "off" or "on" state, |
| 152 | +allowing it to be reused for both conditions (`component_off` and `component_on`). |
| 153 | + |
| 154 | +```cpp |
| 155 | +class StateTrigger : public Trigger<bool> { |
| 156 | + public: |
| 157 | + explicit StateTrigger(EmptyAutomation *parent) { |
| 158 | + parent->add_on_state_callback([this](bool state) { this->trigger(state); }); |
| 159 | + } |
| 160 | +}; |
| 161 | +``` |
| 162 | +
|
| 163 | +Finally, `StateTrigger` is a simple class which does little more than call the trigger when it's instantiated. |
0 commit comments