ESPHome 2026.5.0
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 <array>
13#include <list>
14#include <vector>
15
16namespace esphome {
17
18template<size_t N, typename... Ts> class AndCondition : public Condition<Ts...> {
19 public:
20 explicit AndCondition(std::initializer_list<Condition<Ts...> *> conditions) {
21 init_array_from(this->conditions_, conditions);
22 }
23 bool check(const Ts &...x) override {
24 for (auto *condition : this->conditions_) {
25 if (!condition->check(x...))
26 return false;
27 }
28
29 return true;
30 }
31
32 protected:
33 std::array<Condition<Ts...> *, N> conditions_{};
34};
35
36template<size_t N, typename... Ts> class OrCondition : public Condition<Ts...> {
37 public:
38 explicit OrCondition(std::initializer_list<Condition<Ts...> *> conditions) {
39 init_array_from(this->conditions_, conditions);
40 }
41 bool check(const Ts &...x) override {
42 for (auto *condition : this->conditions_) {
43 if (condition->check(x...))
44 return true;
45 }
46
47 return false;
48 }
49
50 protected:
51 std::array<Condition<Ts...> *, N> conditions_{};
52};
53
54template<typename... Ts> class NotCondition : public Condition<Ts...> {
55 public:
56 explicit NotCondition(Condition<Ts...> *condition) : condition_(condition) {}
57 bool check(const Ts &...x) override { return !this->condition_->check(x...); }
58
59 protected:
61};
62
63template<size_t N, typename... Ts> class XorCondition : public Condition<Ts...> {
64 public:
65 explicit XorCondition(std::initializer_list<Condition<Ts...> *> conditions) {
66 init_array_from(this->conditions_, conditions);
67 }
68 bool check(const Ts &...x) override {
69 size_t result = 0;
70 for (auto *condition : this->conditions_) {
71 result += condition->check(x...);
72 }
73
74 return result == 1;
75 }
76
77 protected:
78 std::array<Condition<Ts...> *, N> conditions_{};
79};
80
81template<typename... Ts> class LambdaCondition : public Condition<Ts...> {
82 public:
83 explicit LambdaCondition(std::function<bool(Ts...)> &&f) : f_(std::move(f)) {}
84 bool check(const Ts &...x) override { return this->f_(x...); }
85
86 protected:
87 std::function<bool(Ts...)> f_;
88};
89
93template<typename... Ts> class StatelessLambdaCondition : public Condition<Ts...> {
94 public:
95 explicit StatelessLambdaCondition(bool (*f)(Ts...)) : f_(f) {}
96 bool check(const Ts &...x) override { return this->f_(x...); }
97
98 protected:
99 bool (*f_)(Ts...);
100};
101
102template<typename... Ts> class ForCondition : public Condition<Ts...>, public Component {
103 public:
104 explicit ForCondition(Condition<> *condition) : condition_(condition) {}
105
107
108 void loop() override {
109 // Safe to use cached time - only called from Application::loop()
111 }
112
113 float get_setup_priority() const override { return setup_priority::DATA; }
114
115 bool check(const Ts &...x) override {
116 auto now = millis();
117 if (!this->check_internal_(now))
118 return false;
119 return now - this->last_inactive_ >= this->time_.value(x...);
120 }
121
122 protected:
124 bool cond = this->condition_->check();
125 if (!cond)
126 this->last_inactive_ = now;
127 return cond;
128 }
129
132};
133
134class StartupTrigger : public Trigger<>, public Component {
135 public:
136 explicit StartupTrigger(float setup_priority) : setup_priority_(setup_priority) {}
137 void setup() override { this->trigger(); }
138 float get_setup_priority() const override { return this->setup_priority_; }
139
140 protected:
142};
143
144class ShutdownTrigger : public Trigger<>, public Component {
145 public:
146 explicit ShutdownTrigger(float setup_priority) : setup_priority_(setup_priority) {}
147 void on_shutdown() override { this->trigger(); }
148 float get_setup_priority() const override { return this->setup_priority_; }
149
150 protected:
152};
153
154class LoopTrigger : public Trigger<>, public Component {
155 public:
156 void loop() override { this->trigger(); }
157 float get_setup_priority() const override { return setup_priority::DATA; }
158};
159
160#ifdef ESPHOME_PROJECT_NAME
161class ProjectUpdateTrigger : public Trigger<std::string>, public Component {
162 public:
163 void setup() override {
164 uint32_t hash = fnv1_hash(ESPHOME_PROJECT_NAME);
165 ESPPreferenceObject pref = global_preferences->make_preference<char[30]>(hash, true);
166 char previous_version[30];
167 char current_version[30] = ESPHOME_PROJECT_VERSION_30;
168 if (pref.load(&previous_version)) {
169 int cmp = strcmp(previous_version, current_version);
170 if (cmp < 0) {
171 this->trigger(previous_version);
172 }
173 }
174 pref.save(&current_version);
176 }
177 float get_setup_priority() const override { return setup_priority::PROCESSOR; }
178};
179#endif
180
181template<typename... Ts> class DelayAction : public Action<Ts...> {
182 public:
183 explicit DelayAction() = default;
184
186
187 void play_complex(const Ts &...x) override {
188 this->num_running_++;
189
190 // If num_running_ > 1, we have multiple instances running in parallel
191 // In single/restart/queued modes, only one instance runs at a time
192 // Parallel mode uses skip_cancel=true to allow multiple delays to coexist
193 // WARNING: This can accumulate delays if scripts are triggered faster than they complete!
194 // Users should set max_runs on parallel scripts to limit concurrent executions.
195 // Issue #10264: This is a workaround for parallel script delays interfering with each other.
196
197 // Optimization: For no-argument delays (most common case), use direct lambda
198 // to avoid overhead from capturing arguments by value
199 if constexpr (sizeof...(Ts) == 0) {
200 App.scheduler.set_timer_common_(
201 /* component= */ nullptr, Scheduler::SchedulerItem::TIMEOUT, Scheduler::NameType::SELF_POINTER,
202 /* static_name= */ reinterpret_cast<const char *>(this), /* hash_or_id= */ 0, this->delay_.value(),
203 [this]() { this->play_next_(); },
204 /* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1);
205 } else {
206 // For delays with arguments, capture by value to preserve argument values
207 // Arguments must be copied because original references may be invalid after delay
208 // `mutable` is required so captured copies of non-const reference args (e.g. std::string&)
209 // are passed as non-const lvalues to play_next_(const Ts&...) where Ts may be `T&`
210 auto f = [this, x...]() mutable { this->play_next_(x...); };
211 App.scheduler.set_timer_common_(
212 /* component= */ nullptr, Scheduler::SchedulerItem::TIMEOUT, Scheduler::NameType::SELF_POINTER,
213 /* static_name= */ reinterpret_cast<const char *>(this), /* hash_or_id= */ 0, this->delay_.value(x...),
214 std::move(f),
215 /* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1);
216 }
217 }
218
219 void play(const Ts &...x) override { /* ignore - see play_complex */
220 }
221
222 void stop() override { App.scheduler.cancel_timeout(this); }
223};
224
225template<typename... Ts> class LambdaAction : public Action<Ts...> {
226 public:
227 explicit LambdaAction(std::function<void(Ts...)> &&f) : f_(std::move(f)) {}
228
229 void play(const Ts &...x) override { this->f_(x...); }
230
231 protected:
232 std::function<void(Ts...)> f_;
233};
234
238template<typename... Ts> class StatelessLambdaAction : public Action<Ts...> {
239 public:
240 explicit StatelessLambdaAction(void (*f)(Ts...)) : f_(f) {}
241
242 void play(const Ts &...x) override { this->f_(x...); }
243
244 protected:
245 void (*f_)(Ts...);
246};
247
251template<typename... Ts> class ContinuationAction : public Action<Ts...> {
252 public:
253 explicit ContinuationAction(Action<Ts...> *parent) : parent_(parent) {}
254
255 void play(const Ts &...x) override { this->parent_->play_next_(x...); }
256
257 protected:
259};
260
261// Forward declaration for WhileLoopContinuation
262template<typename... Ts> class WhileAction;
263
266template<typename... Ts> class WhileLoopContinuation : public Action<Ts...> {
267 public:
268 explicit WhileLoopContinuation(WhileAction<Ts...> *parent) : parent_(parent) {}
269
270 void play(const Ts &...x) override;
271
272 protected:
274};
275
276// Wraps a ContinuationAction when Enabled, empty otherwise.
277// Lets IfAction elide the else continuation when HasElse is false.
278template<bool Enabled, typename... Ts> struct OptionalContinuation {
280 explicit OptionalContinuation(Action<Ts...> *parent) : action(parent) {}
281};
282template<typename... Ts> struct OptionalContinuation<false, Ts...> {
283 explicit OptionalContinuation(Action<Ts...> * /*parent*/) {}
284};
285
286template<bool HasElse, typename... Ts> class IfAction : public Action<Ts...> {
287 public:
288 explicit IfAction(Condition<Ts...> *condition) : condition_(condition) {}
289
290 // Precondition: add_then/add_else must be called at most once per instance.
291 // Codegen always batches the full action list into a single call. Calling
292 // twice would re-append the same inline continuation pointer and form a
293 // self-loop in the next_ chain.
294 void add_then(const std::initializer_list<Action<Ts...> *> &actions) {
295 this->then_.add_actions(actions);
296 this->then_.add_action(&this->then_continuation_);
297 }
298
299 void add_else(const std::initializer_list<Action<Ts...> *> &actions) requires(HasElse) {
300 this->else_.add_actions(actions);
301 this->else_.add_action(&this->else_continuation_.action);
302 }
303
304 void play_complex(const Ts &...x) override {
305 this->num_running_++;
306 if (this->condition_->check(x...)) {
307 if (!this->then_.empty() && this->num_running_ > 0) {
308 this->then_.play(x...);
309 return;
310 }
311 } else if constexpr (HasElse) {
312 if (!this->else_.empty() && this->num_running_ > 0) {
313 this->else_.play(x...);
314 return;
315 }
316 }
317 this->play_next_(x...);
318 }
319
320 void play(const Ts &...x) override { /* ignore - see play_complex */
321 }
322
323 void stop() override {
324 this->then_.stop();
325 if constexpr (HasElse) {
326 this->else_.stop();
327 }
328 }
329
330 protected:
334 struct NoElse {};
335 [[no_unique_address]] std::conditional_t<HasElse, ActionList<Ts...>, NoElse> else_;
336 [[no_unique_address]] OptionalContinuation<HasElse, Ts...> else_continuation_{this};
337};
338
339template<typename... Ts> class WhileAction : public Action<Ts...> {
340 public:
341 WhileAction(Condition<Ts...> *condition) : condition_(condition) {}
342
343 // Precondition: must be called at most once per instance (see IfAction::add_then).
344 void add_then(const std::initializer_list<Action<Ts...> *> &actions) {
345 this->then_.add_actions(actions);
346 this->then_.add_action(&this->loop_continuation_);
347 }
348
349 friend class WhileLoopContinuation<Ts...>;
350
351 void play_complex(const Ts &...x) override {
352 this->num_running_++;
353 // Initial condition check
354 if (!this->condition_->check(x...)) {
355 // If new condition check failed, stop loop if running
356 this->then_.stop();
357 this->play_next_(x...);
358 return;
359 }
360
361 if (this->num_running_ > 0) {
362 this->then_.play(x...);
363 }
364 }
365
366 void play(const Ts &...x) override { /* ignore - see play_complex */
367 }
368
369 void stop() override { this->then_.stop(); }
370
371 protected:
375};
376
377// Implementation of WhileLoopContinuation::play
378template<typename... Ts> void WhileLoopContinuation<Ts...>::play(const Ts &...x) {
379 if (this->parent_->num_running_ > 0 && this->parent_->condition_->check(x...)) {
380 // play again
381 this->parent_->then_.play(x...);
382 } else {
383 // condition false, play next
384 this->parent_->play_next_(x...);
385 }
386}
387
388// Forward declaration for RepeatLoopContinuation
389template<typename... Ts> class RepeatAction;
390
393template<typename... Ts> class RepeatLoopContinuation : public Action<uint32_t, Ts...> {
394 public:
396
397 void play(const uint32_t &iteration, const Ts &...x) override;
398
399 protected:
401};
402
403template<typename... Ts> class RepeatAction : public Action<Ts...> {
404 public:
406
407 // Precondition: must be called at most once per instance (see IfAction::add_then).
408 void add_then(const std::initializer_list<Action<uint32_t, Ts...> *> &actions) {
409 this->then_.add_actions(actions);
410 this->then_.add_action(&this->loop_continuation_);
411 }
412
413 friend class RepeatLoopContinuation<Ts...>;
414
415 void play_complex(const Ts &...x) override {
416 this->num_running_++;
417 if (this->count_.value(x...) > 0) {
418 this->then_.play(0, x...);
419 } else {
420 this->play_next_(x...);
421 }
422 }
423
424 void play(const Ts &...x) override { /* ignore - see play_complex */
425 }
426
427 void stop() override { this->then_.stop(); }
428
429 protected:
432};
433
434// Implementation of RepeatLoopContinuation::play
435template<typename... Ts> void RepeatLoopContinuation<Ts...>::play(const uint32_t &iteration, const Ts &...x) {
436 uint32_t next_iteration = iteration + 1;
437 if (next_iteration >= this->parent_->count_.value(x...)) {
438 this->parent_->play_next_(x...);
439 } else {
440 this->parent_->then_.play(next_iteration, x...);
441 }
442}
443
451template<typename... Ts> class WaitUntilAction : public Action<Ts...>, public Component {
452 public:
453 WaitUntilAction(Condition<Ts...> *condition) : condition_(condition) {}
454
456
457 void setup() override {
458 // Start with loop disabled - only enable when there's work to do
459 // IMPORTANT: Only disable if num_running_ is 0, otherwise play_complex() was already
460 // called before our setup() (e.g., from on_boot trigger at same priority level)
461 // and we must not undo its enable_loop() call
462 if (this->num_running_ == 0) {
463 this->disable_loop();
464 }
465 }
466
467 void play_complex(const Ts &...x) override {
468 this->num_running_++;
469 // Check if we can continue immediately.
470 if (this->condition_->check(x...)) {
471 if (this->num_running_ > 0) {
472 this->play_next_(x...);
473 }
474 return;
475 }
476
477 // Store for later processing
478 auto now = millis();
479 auto timeout = this->timeout_value_.optional_value(x...);
480 this->var_queue_.emplace_back(now, timeout, std::make_tuple(x...));
481
482 // Do immediate check with fresh timestamp - don't call loop() synchronously!
483 // Let the event loop call it to avoid reentrancy issues
484 if (this->process_queue_(now)) {
485 // Only enable loop if we still have pending items
486 this->enable_loop();
487 }
488 }
489
490 void loop() override {
491 // Safe to use cached time - only called from Application::loop()
493 // If queue is now empty, disable loop until next play_complex
494 this->disable_loop();
495 }
496 }
497
498 void stop() override {
499 this->var_queue_.clear();
500 this->disable_loop();
501 }
502
503 float get_setup_priority() const override { return setup_priority::DATA; }
504
505 void play(const Ts &...x) override { /* ignore - see play_complex */
506 }
507
508 protected:
509 // Helper: Process queue, triggering completed items and removing them
510 // Returns true if queue still has pending items
512 // Process each queued wait_until and remove completed ones
513 this->var_queue_.remove_if([&](auto &queued) {
514 auto start = std::get<uint32_t>(queued);
515 auto timeout = std::get<optional<uint32_t>>(queued);
516 auto &var = std::get<std::tuple<Ts...>>(queued);
517
518 // Check if timeout has expired
519 auto expired = timeout && (now - start) >= *timeout;
520
521 // Keep waiting if not expired and condition not met
522 if (!expired && !this->condition_->check_tuple(var)) {
523 return false;
524 }
525
526 // Condition met or timed out - trigger next action
527 this->play_next_tuple_(var);
528 return true;
529 });
530
531 return !this->var_queue_.empty();
532 }
533
535 std::list<std::tuple<uint32_t, optional<uint32_t>, std::tuple<Ts...>>> var_queue_{};
536};
537
538template<typename... Ts> class UpdateComponentAction : public Action<Ts...> {
539 public:
541
542 void play(const Ts &...x) override {
543 if (!this->component_->is_ready())
544 return;
545 this->component_->update();
546 }
547
548 protected:
550};
551
552template<typename... Ts> class SuspendComponentAction : public Action<Ts...> {
553 public:
555
556 void play(const Ts &...x) override {
557 if (!this->component_->is_ready())
558 return;
559 this->component_->stop_poller();
560 }
561
562 protected:
564};
565
566template<typename... Ts> class ResumeComponentAction : public Action<Ts...> {
567 public:
569 TEMPLATABLE_VALUE(uint32_t, update_interval)
570
571 void play(const Ts &...x) override {
572 if (!this->component_->is_ready()) {
573 return;
574 }
575 optional<uint32_t> update_interval = this->update_interval_.optional_value(x...);
576 if (update_interval.has_value()) {
577 this->component_->set_update_interval(update_interval.value());
578 }
579 this->component_->start_poller();
580 }
581
582 protected:
584};
585
586} // namespace esphome
void play_next_(const Ts &...x)
Definition automation.h:539
virtual void play(const Ts &...x)=0
void play_next_tuple_(const std::tuple< Ts... > &tuple, std::index_sequence< S... >)
Definition automation.h:547
virtual void play_complex(const Ts &...x)
Definition automation.h:510
void add_action(Action< Ts... > *action)
Definition automation.h:576
bool empty() const
Definition automation.h:606
void add_actions(const std::initializer_list< Action< Ts... > * > &actions)
Definition automation.h:583
void play(const Ts &...x) ESPHOME_ALWAYS_INLINE
Definition automation.h:595
AndCondition(std::initializer_list< Condition< Ts... > * > conditions)
bool check(const Ts &...x) override
std::array< Condition< Ts... > *, N > 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.
Definition component.cpp:89
bool is_ready() const
void enable_loop()
Enable this component's loop.
Definition component.h:258
void disable_loop()
Disable this component's loop.
Base class for all automation conditions.
Definition automation.h:459
bool check_tuple(const std::tuple< Ts... > &tuple)
Call check with a tuple of values as parameter.
Definition automation.h:465
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
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)
ActionList< Ts... > then_
Condition< Ts... > * condition_
OptionalContinuation< HasElse, Ts... > else_continuation_
void play(const Ts &...x) override
void add_then(const std::initializer_list< Action< Ts... > * > &actions)
void play_complex(const Ts &...x) override
void stop() override
std::conditional_t< HasElse, ActionList< Ts... >, NoElse > else_
ContinuationAction< Ts... > then_continuation_
IfAction(Condition< Ts... > *condition)
void add_else(const std::initializer_list< Action< Ts... > * > &actions)
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)
OrCondition(std::initializer_list< Condition< Ts... > * > conditions)
bool check(const Ts &...x) override
std::array< Condition< Ts... > *, N > conditions_
This class simplifies creating components that periodically check a state.
Definition component.h:602
void set_update_interval(uint32_t update_interval)
Manually set the update interval in ms for this polling object.
Definition component.h:616
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
RepeatLoopContinuation< Ts... > loop_continuation_
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) ESPHOME_ALWAYS_INLINE
Definition automation.h:482
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_
WhileLoopContinuation< Ts... > loop_continuation_
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_
std::array< Condition< Ts... > *, N > conditions_
bool check(const Ts &...x) override
XorCondition(std::initializer_list< Condition< Ts... > * > conditions)
const Component * component
Definition component.cpp:34
constexpr float DATA
For components that import data from directly connected sensors like DHT.
Definition component.h:43
constexpr float PROCESSOR
For components that use data from sensors like displays.
Definition component.h:45
ESPPreferences * global_preferences
uint32_t fnv1_hash(const char *str)
Calculate a FNV-1 hash of str.
Definition helpers.cpp:161
void init_array_from(std::array< T, N > &dest, std::initializer_list< T > src)
Initialize a std::array from an initializer_list.
Definition helpers.h:512
void HOT delay(uint32_t ms)
Definition hal.cpp:82
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:28
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t
ContinuationAction< Ts... > action
OptionalContinuation(Action< Ts... > *parent)
ESPPreferenceObject make_preference(size_t, uint32_t, bool)
Definition preferences.h:24
bool sync()
Commit pending writes to flash.
Definition preferences.h:32
uint16_t x
Definition tt21100.cpp:5