ESPHome 2026.2.1
Loading...
Searching...
No Matches
base_automation.h
Go to the documentation of this file.
1#pragma once
2
5#include "esphome/core/hal.h"
11
12#include <list>
13#include <vector>
14
15namespace esphome {
16
17template<typename... Ts> class AndCondition : public Condition<Ts...> {
18 public:
19 explicit AndCondition(std::initializer_list<Condition<Ts...> *> conditions) : conditions_(conditions) {}
20 bool check(const Ts &...x) override {
21 for (auto *condition : this->conditions_) {
22 if (!condition->check(x...))
23 return false;
24 }
25
26 return true;
27 }
28
29 protected:
31};
32
33template<typename... Ts> class OrCondition : public Condition<Ts...> {
34 public:
35 explicit OrCondition(std::initializer_list<Condition<Ts...> *> conditions) : conditions_(conditions) {}
36 bool check(const Ts &...x) override {
37 for (auto *condition : this->conditions_) {
38 if (condition->check(x...))
39 return true;
40 }
41
42 return false;
43 }
44
45 protected:
47};
48
49template<typename... Ts> class NotCondition : public Condition<Ts...> {
50 public:
51 explicit NotCondition(Condition<Ts...> *condition) : condition_(condition) {}
52 bool check(const Ts &...x) override { return !this->condition_->check(x...); }
53
54 protected:
56};
57
58template<typename... Ts> class XorCondition : public Condition<Ts...> {
59 public:
60 explicit XorCondition(std::initializer_list<Condition<Ts...> *> conditions) : conditions_(conditions) {}
61 bool check(const Ts &...x) override {
62 size_t result = 0;
63 for (auto *condition : this->conditions_) {
64 result += condition->check(x...);
65 }
66
67 return result == 1;
68 }
69
70 protected:
72};
73
74template<typename... Ts> class LambdaCondition : public Condition<Ts...> {
75 public:
76 explicit LambdaCondition(std::function<bool(Ts...)> &&f) : f_(std::move(f)) {}
77 bool check(const Ts &...x) override { return this->f_(x...); }
78
79 protected:
80 std::function<bool(Ts...)> f_;
81};
82
86template<typename... Ts> class StatelessLambdaCondition : public Condition<Ts...> {
87 public:
88 explicit StatelessLambdaCondition(bool (*f)(Ts...)) : f_(f) {}
89 bool check(const Ts &...x) override { return this->f_(x...); }
90
91 protected:
92 bool (*f_)(Ts...);
93};
94
95template<typename... Ts> class ForCondition : public Condition<Ts...>, public Component {
96 public:
97 explicit ForCondition(Condition<> *condition) : condition_(condition) {}
98
99 TEMPLATABLE_VALUE(uint32_t, time);
100
101 void loop() override {
102 // Safe to use cached time - only called from Application::loop()
104 }
105
106 float get_setup_priority() const override { return setup_priority::DATA; }
107
108 bool check(const Ts &...x) override {
109 auto now = millis();
110 if (!this->check_internal_(now))
111 return false;
112 return now - this->last_inactive_ >= this->time_.value(x...);
113 }
114
115 protected:
116 bool check_internal_(uint32_t now) {
117 bool cond = this->condition_->check();
118 if (!cond)
119 this->last_inactive_ = now;
120 return cond;
121 }
122
124 uint32_t last_inactive_{0};
125};
126
127class StartupTrigger : public Trigger<>, public Component {
128 public:
129 explicit StartupTrigger(float setup_priority) : setup_priority_(setup_priority) {}
130 void setup() override { this->trigger(); }
131 float get_setup_priority() const override { return this->setup_priority_; }
132
133 protected:
135};
136
137class ShutdownTrigger : public Trigger<>, public Component {
138 public:
139 explicit ShutdownTrigger(float setup_priority) : setup_priority_(setup_priority) {}
140 void on_shutdown() override { this->trigger(); }
141 float get_setup_priority() const override { return this->setup_priority_; }
142
143 protected:
145};
146
147class LoopTrigger : public Trigger<>, public Component {
148 public:
149 void loop() override { this->trigger(); }
150 float get_setup_priority() const override { return setup_priority::DATA; }
151};
152
153#ifdef ESPHOME_PROJECT_NAME
154class ProjectUpdateTrigger : public Trigger<std::string>, public Component {
155 public:
156 void setup() override {
157 uint32_t hash = fnv1_hash(ESPHOME_PROJECT_NAME);
158 ESPPreferenceObject pref = global_preferences->make_preference<char[30]>(hash, true);
159 char previous_version[30];
160 char current_version[30] = ESPHOME_PROJECT_VERSION_30;
161 if (pref.load(&previous_version)) {
162 int cmp = strcmp(previous_version, current_version);
163 if (cmp < 0) {
164 this->trigger(previous_version);
165 }
166 }
167 pref.save(&current_version);
169 }
170 float get_setup_priority() const override { return setup_priority::PROCESSOR; }
171};
172#endif
173
174template<typename... Ts> class DelayAction : public Action<Ts...>, public Component {
175 public:
176 explicit DelayAction() = default;
177
179
180 void play_complex(const Ts &...x) override {
181 this->num_running_++;
182
183 // If num_running_ > 1, we have multiple instances running in parallel
184 // In single/restart/queued modes, only one instance runs at a time
185 // Parallel mode uses skip_cancel=true to allow multiple delays to coexist
186 // WARNING: This can accumulate delays if scripts are triggered faster than they complete!
187 // Users should set max_runs on parallel scripts to limit concurrent executions.
188 // Issue #10264: This is a workaround for parallel script delays interfering with each other.
189
190 // Optimization: For no-argument delays (most common case), use direct lambda
191 // instead of std::bind to avoid bind overhead (~16 bytes heap + faster execution)
192 if constexpr (sizeof...(Ts) == 0) {
193 App.scheduler.set_timer_common_(
194 this, Scheduler::SchedulerItem::TIMEOUT, Scheduler::NameType::NUMERIC_ID_INTERNAL, nullptr,
195 static_cast<uint32_t>(InternalSchedulerID::DELAY_ACTION), this->delay_.value(),
196 [this]() { this->play_next_(); },
197 /* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1);
198 } else {
199 // For delays with arguments, use std::bind to preserve argument values
200 // Arguments must be copied because original references may be invalid after delay
201 auto f = std::bind(&DelayAction<Ts...>::play_next_, this, x...);
202 App.scheduler.set_timer_common_(this, Scheduler::SchedulerItem::TIMEOUT, Scheduler::NameType::NUMERIC_ID_INTERNAL,
203 nullptr, static_cast<uint32_t>(InternalSchedulerID::DELAY_ACTION),
204 this->delay_.value(x...), std::move(f),
205 /* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1);
206 }
207 }
208 float get_setup_priority() const override { return setup_priority::HARDWARE; }
209
210 void play(const Ts &...x) override { /* ignore - see play_complex */
211 }
212
214};
215
216template<typename... Ts> class LambdaAction : public Action<Ts...> {
217 public:
218 explicit LambdaAction(std::function<void(Ts...)> &&f) : f_(std::move(f)) {}
219
220 void play(const Ts &...x) override { this->f_(x...); }
221
222 protected:
223 std::function<void(Ts...)> f_;
224};
225
229template<typename... Ts> class StatelessLambdaAction : public Action<Ts...> {
230 public:
231 explicit StatelessLambdaAction(void (*f)(Ts...)) : f_(f) {}
232
233 void play(const Ts &...x) override { this->f_(x...); }
234
235 protected:
236 void (*f_)(Ts...);
237};
238
242template<typename... Ts> class ContinuationAction : public Action<Ts...> {
243 public:
244 explicit ContinuationAction(Action<Ts...> *parent) : parent_(parent) {}
245
246 void play(const Ts &...x) override { this->parent_->play_next_(x...); }
247
248 protected:
250};
251
252// Forward declaration for WhileLoopContinuation
253template<typename... Ts> class WhileAction;
254
257template<typename... Ts> class WhileLoopContinuation : public Action<Ts...> {
258 public:
259 explicit WhileLoopContinuation(WhileAction<Ts...> *parent) : parent_(parent) {}
260
261 void play(const Ts &...x) override;
262
263 protected:
265};
266
267template<typename... Ts> class IfAction : public Action<Ts...> {
268 public:
269 explicit IfAction(Condition<Ts...> *condition) : condition_(condition) {}
270
271 void add_then(const std::initializer_list<Action<Ts...> *> &actions) {
272 this->then_.add_actions(actions);
274 }
275
276 void add_else(const std::initializer_list<Action<Ts...> *> &actions) {
277 this->else_.add_actions(actions);
279 }
280
281 void play_complex(const Ts &...x) override {
282 this->num_running_++;
283 bool res = this->condition_->check(x...);
284 if (res) {
285 if (this->then_.empty()) {
286 this->play_next_(x...);
287 } else if (this->num_running_ > 0) {
288 this->then_.play(x...);
289 }
290 } else {
291 if (this->else_.empty()) {
292 this->play_next_(x...);
293 } else if (this->num_running_ > 0) {
294 this->else_.play(x...);
295 }
296 }
297 }
298
299 void play(const Ts &...x) override { /* ignore - see play_complex */
300 }
301
302 void stop() override {
303 this->then_.stop();
304 this->else_.stop();
305 }
306
307 protected:
311};
312
313template<typename... Ts> class WhileAction : public Action<Ts...> {
314 public:
315 WhileAction(Condition<Ts...> *condition) : condition_(condition) {}
316
317 void add_then(const std::initializer_list<Action<Ts...> *> &actions) {
318 this->then_.add_actions(actions);
320 }
321
322 friend class WhileLoopContinuation<Ts...>;
323
324 void play_complex(const Ts &...x) override {
325 this->num_running_++;
326 // Initial condition check
327 if (!this->condition_->check(x...)) {
328 // If new condition check failed, stop loop if running
329 this->then_.stop();
330 this->play_next_(x...);
331 return;
332 }
333
334 if (this->num_running_ > 0) {
335 this->then_.play(x...);
336 }
337 }
338
339 void play(const Ts &...x) override { /* ignore - see play_complex */
340 }
341
342 void stop() override { this->then_.stop(); }
343
344 protected:
347};
348
349// Implementation of WhileLoopContinuation::play
350template<typename... Ts> void WhileLoopContinuation<Ts...>::play(const Ts &...x) {
351 if (this->parent_->num_running_ > 0 && this->parent_->condition_->check(x...)) {
352 // play again
353 this->parent_->then_.play(x...);
354 } else {
355 // condition false, play next
356 this->parent_->play_next_(x...);
357 }
358}
359
360// Forward declaration for RepeatLoopContinuation
361template<typename... Ts> class RepeatAction;
362
365template<typename... Ts> class RepeatLoopContinuation : public Action<uint32_t, Ts...> {
366 public:
368
369 void play(const uint32_t &iteration, const Ts &...x) override;
370
371 protected:
373};
374
375template<typename... Ts> class RepeatAction : public Action<Ts...> {
376 public:
377 TEMPLATABLE_VALUE(uint32_t, count)
378
379 void add_then(const std::initializer_list<Action<uint32_t, Ts...> *> &actions) {
380 this->then_.add_actions(actions);
382 }
383
384 friend class RepeatLoopContinuation<Ts...>;
385
386 void play_complex(const Ts &...x) override {
387 this->num_running_++;
388 if (this->count_.value(x...) > 0) {
389 this->then_.play(0, x...);
390 } else {
391 this->play_next_(x...);
392 }
393 }
394
395 void play(const Ts &...x) override { /* ignore - see play_complex */
396 }
397
398 void stop() override { this->then_.stop(); }
399
400 protected:
401 ActionList<uint32_t, Ts...> then_;
402};
403
404// Implementation of RepeatLoopContinuation::play
405template<typename... Ts> void RepeatLoopContinuation<Ts...>::play(const uint32_t &iteration, const Ts &...x) {
406 uint32_t next_iteration = iteration + 1;
407 if (next_iteration >= this->parent_->count_.value(x...)) {
408 this->parent_->play_next_(x...);
409 } else {
410 this->parent_->then_.play(next_iteration, x...);
411 }
412}
413
421template<typename... Ts> class WaitUntilAction : public Action<Ts...>, public Component {
422 public:
423 WaitUntilAction(Condition<Ts...> *condition) : condition_(condition) {}
424
425 TEMPLATABLE_VALUE(uint32_t, timeout_value)
426
427 void setup() override {
428 // Start with loop disabled - only enable when there's work to do
429 // IMPORTANT: Only disable if num_running_ is 0, otherwise play_complex() was already
430 // called before our setup() (e.g., from on_boot trigger at same priority level)
431 // and we must not undo its enable_loop() call
432 if (this->num_running_ == 0) {
433 this->disable_loop();
434 }
435 }
436
437 void play_complex(const Ts &...x) override {
438 this->num_running_++;
439 // Check if we can continue immediately.
440 if (this->condition_->check(x...)) {
441 if (this->num_running_ > 0) {
442 this->play_next_(x...);
443 }
444 return;
445 }
446
447 // Store for later processing
448 auto now = millis();
449 auto timeout = this->timeout_value_.optional_value(x...);
450 this->var_queue_.emplace_back(now, timeout, std::make_tuple(x...));
451
452 // Do immediate check with fresh timestamp - don't call loop() synchronously!
453 // Let the event loop call it to avoid reentrancy issues
454 if (this->process_queue_(now)) {
455 // Only enable loop if we still have pending items
456 this->enable_loop();
457 }
458 }
459
460 void loop() override {
461 // Safe to use cached time - only called from Application::loop()
463 // If queue is now empty, disable loop until next play_complex
464 this->disable_loop();
465 }
466 }
467
468 void stop() override {
469 this->var_queue_.clear();
470 this->disable_loop();
471 }
472
473 float get_setup_priority() const override { return setup_priority::DATA; }
474
475 void play(const Ts &...x) override { /* ignore - see play_complex */
476 }
477
478 protected:
479 // Helper: Process queue, triggering completed items and removing them
480 // Returns true if queue still has pending items
481 bool process_queue_(uint32_t now) {
482 // Process each queued wait_until and remove completed ones
483 this->var_queue_.remove_if([&](auto &queued) {
484 auto start = std::get<uint32_t>(queued);
485 auto timeout = std::get<optional<uint32_t>>(queued);
486 auto &var = std::get<std::tuple<Ts...>>(queued);
487
488 // Check if timeout has expired
489 auto expired = timeout && (now - start) >= *timeout;
490
491 // Keep waiting if not expired and condition not met
492 if (!expired && !this->condition_->check_tuple(var)) {
493 return false;
494 }
495
496 // Condition met or timed out - trigger next action
497 this->play_next_tuple_(var);
498 return true;
499 });
500
501 return !this->var_queue_.empty();
502 }
503
505 std::list<std::tuple<uint32_t, optional<uint32_t>, std::tuple<Ts...>>> var_queue_{};
506};
507
508template<typename... Ts> class UpdateComponentAction : public Action<Ts...> {
509 public:
511
512 void play(const Ts &...x) override {
513 if (!this->component_->is_ready())
514 return;
515 this->component_->update();
516 }
517
518 protected:
520};
521
522template<typename... Ts> class SuspendComponentAction : public Action<Ts...> {
523 public:
525
526 void play(const Ts &...x) override {
527 if (!this->component_->is_ready())
528 return;
529 this->component_->stop_poller();
530 }
531
532 protected:
534};
535
536template<typename... Ts> class ResumeComponentAction : public Action<Ts...> {
537 public:
539 TEMPLATABLE_VALUE(uint32_t, update_interval)
540
541 void play(const Ts &...x) override {
542 if (!this->component_->is_ready()) {
543 return;
544 }
545 optional<uint32_t> update_interval = this->update_interval_.optional_value(x...);
546 if (update_interval.has_value()) {
547 this->component_->set_update_interval(update_interval.value());
548 }
549 this->component_->start_poller();
550 }
551
552 protected:
554};
555
556} // namespace esphome
void play_next_(const Ts &...x)
Definition automation.h:336
virtual void play(const Ts &...x)=0
void play_next_tuple_(const std::tuple< Ts... > &tuple, std::index_sequence< S... >)
Definition automation.h:344
virtual void play_complex(const Ts &...x)
Definition automation.h:307
void add_action(Action< Ts... > *action)
Definition automation.h:373
void play(const Ts &...x)
Definition automation.h:386
bool empty() const
Definition automation.h:397
void add_actions(const std::initializer_list< Action< Ts... > * > &actions)
Definition automation.h:381
bool check(const Ts &...x) override
AndCondition(std::initializer_list< Condition< Ts... > * > conditions)
FixedVector< Condition< Ts... > * > conditions_
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
virtual void setup()
Where the component's initialization should happen.
bool is_ready() const
void enable_loop()
Enable this component's loop.
void disable_loop()
Disable this component's loop.
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") bool cancel_timeout(const std boo cancel_timeout)(const char *name)
Cancel a timeout function.
Definition component.h:451
ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.", "2026.2.0") void set_retry(const std uint32_t uint8_t std::function< RetryResult(uint8_t)> && f
Definition component.h:373
Base class for all automation conditions.
Definition automation.h:258
bool check_tuple(const std::tuple< Ts... > &tuple)
Call check with a tuple of values as parameter.
Definition automation.h:264
virtual bool check(const Ts &...x)=0
Check whether this condition passes. This condition check must be instant, and not cause any delays.
Simple continuation action that calls play_next_ on a parent action.
void play(const Ts &...x) override
ContinuationAction(Action< Ts... > *parent)
void play(const Ts &...x) override
TEMPLATABLE_VALUE(uint32_t, delay) void play_complex(const Ts &...x) override
float get_setup_priority() const override
bool save(const T *src)
Definition preferences.h:21
virtual bool sync()=0
Commit pending writes to flash.
virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash)=0
Fixed-capacity vector - allocates once at runtime, never reallocates This avoids std::vector template...
Definition helpers.h:227
bool check_internal_(uint32_t now)
ForCondition(Condition<> *condition)
float get_setup_priority() const override
bool check(const Ts &...x) override
TEMPLATABLE_VALUE(uint32_t, time)
Condition< Ts... > * condition_
void stop() override
void play_complex(const Ts &...x) override
void add_then(const std::initializer_list< Action< Ts... > * > &actions)
void add_else(const std::initializer_list< Action< Ts... > * > &actions)
ActionList< Ts... > else_
void play(const Ts &...x) override
ActionList< Ts... > then_
IfAction(Condition< Ts... > *condition)
LambdaAction(std::function< void(Ts...)> &&f)
void play(const Ts &...x) override
std::function< void(Ts...)> f_
bool check(const Ts &...x) override
LambdaCondition(std::function< bool(Ts...)> &&f)
std::function< bool(Ts...)> f_
float get_setup_priority() const override
Condition< Ts... > * condition_
bool check(const Ts &...x) override
NotCondition(Condition< Ts... > *condition)
FixedVector< Condition< Ts... > * > conditions_
bool check(const Ts &...x) override
OrCondition(std::initializer_list< Condition< Ts... > * > conditions)
This class simplifies creating components that periodically check a state.
Definition component.h:512
virtual void set_update_interval(uint32_t update_interval)
Manually set the update interval in ms for this polling object.
virtual void update()=0
float get_setup_priority() const override
ActionList< uint32_t, Ts... > then_
TEMPLATABLE_VALUE(uint32_t, count) void add_then(const std
void play_complex(const Ts &...x) override
void play(const Ts &...x) override
Loop continuation for RepeatAction that increments iteration and repeats or continues.
RepeatLoopContinuation(RepeatAction< Ts... > *parent)
RepeatAction< Ts... > * parent_
void play(const uint32_t &iteration, const Ts &...x) override
ResumeComponentAction(PollingComponent *component)
TEMPLATABLE_VALUE(uint32_t, update_interval) void play(const Ts &...x) override
ShutdownTrigger(float setup_priority)
float get_setup_priority() const override
float get_setup_priority() const override
StartupTrigger(float setup_priority)
Optimized lambda action for stateless lambdas (no capture).
void play(const Ts &...x) override
Optimized lambda condition for stateless lambdas (no capture).
bool check(const Ts &...x) override
SuspendComponentAction(PollingComponent *component)
void play(const Ts &...x) override
void trigger(const Ts &...x)
Definition automation.h:279
void play(const Ts &...x) override
UpdateComponentAction(PollingComponent *component)
Wait until a condition is true to continue execution.
WaitUntilAction(Condition< Ts... > *condition)
TEMPLATABLE_VALUE(uint32_t, timeout_value) void setup() override
void play_complex(const Ts &...x) override
void play(const Ts &...x) override
bool process_queue_(uint32_t now)
Condition< Ts... > * condition_
std::list< std::tuple< uint32_t, optional< uint32_t >, std::tuple< Ts... > > > var_queue_
float get_setup_priority() const override
void add_then(const std::initializer_list< Action< Ts... > * > &actions)
WhileAction(Condition< Ts... > *condition)
void play(const Ts &...x) override
void play_complex(const Ts &...x) override
Condition< Ts... > * condition_
ActionList< Ts... > then_
Loop continuation for WhileAction that checks condition and repeats or continues.
WhileLoopContinuation(WhileAction< Ts... > *parent)
void play(const Ts &...x) override
WhileAction< Ts... > * parent_
FixedVector< Condition< Ts... > * > conditions_
XorCondition(std::initializer_list< Condition< Ts... > * > conditions)
bool check(const Ts &...x) override
bool has_value() const
Definition optional.h:92
value_type const & value() const
Definition optional.h:94
const Component * component
Definition component.cpp:37
const float DATA
For components that import data from directly connected sensors like DHT.
Definition component.cpp:84
const float HARDWARE
For components that deal with hardware and are very important like GPIO switch.
Definition component.cpp:83
const float PROCESSOR
For components that use data from sensors like displays.
Definition component.cpp:85
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
ESPPreferences * global_preferences
uint32_t fnv1_hash(const char *str)
Calculate a FNV-1 hash of str.
Definition helpers.cpp:148
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:26
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:25
Application App
Global storage of Application pointer - only one Application can exist.
uint16_t x
Definition tt21100.cpp:5