ESPHome 2026.6.2
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 // Record the owning script (if any) so the blocking warning can name it; propagates across
206 // chained delays via the scheduler.
207 /* source= */ App.get_current_source());
208 } else {
209 // For delays with arguments, capture by value to preserve argument values
210 // Arguments must be copied because original references may be invalid after delay
211 // `mutable` is required so captured copies of non-const reference args (e.g. std::string&)
212 // are passed as non-const lvalues to play_next_(const Ts&...) where Ts may be `T&`
213 auto f = [this, x...]() mutable { this->play_next_(x...); };
214 App.scheduler.set_timer_common_(
215 /* component= */ nullptr, Scheduler::SchedulerItem::TIMEOUT, Scheduler::NameType::SELF_POINTER,
216 /* static_name= */ reinterpret_cast<const char *>(this), /* hash_or_id= */ 0, this->delay_.value(x...),
217 std::move(f),
218 /* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1,
219 // See the no-argument branch above: record the owning script for log attribution.
220 /* source= */ App.get_current_source());
221 }
222 }
223
224 void play(const Ts &...x) override { /* ignore - see play_complex */
225 }
226
227 void stop() override { App.scheduler.cancel_timeout(this); }
228};
229
230template<typename... Ts> class LambdaAction : public Action<Ts...> {
231 public:
232 explicit LambdaAction(std::function<void(Ts...)> &&f) : f_(std::move(f)) {}
233
234 void play(const Ts &...x) override { this->f_(x...); }
235
236 protected:
237 std::function<void(Ts...)> f_;
238};
239
243template<typename... Ts> class StatelessLambdaAction : public Action<Ts...> {
244 public:
245 explicit StatelessLambdaAction(void (*f)(Ts...)) : f_(f) {}
246
247 void play(const Ts &...x) override { this->f_(x...); }
248
249 protected:
250 void (*f_)(Ts...);
251};
252
256template<typename... Ts> class ContinuationAction : public Action<Ts...> {
257 public:
258 explicit ContinuationAction(Action<Ts...> *parent) : parent_(parent) {}
259
260 void play(const Ts &...x) override { this->parent_->play_next_(x...); }
261
262 protected:
264};
265
266// Forward declaration for WhileLoopContinuation
267template<typename... Ts> class WhileAction;
268
271template<typename... Ts> class WhileLoopContinuation : public Action<Ts...> {
272 public:
273 explicit WhileLoopContinuation(WhileAction<Ts...> *parent) : parent_(parent) {}
274
275 void play(const Ts &...x) override;
276
277 protected:
279};
280
281// Wraps a ContinuationAction when Enabled, empty otherwise.
282// Lets IfAction elide the else continuation when HasElse is false.
283template<bool Enabled, typename... Ts> struct OptionalContinuation {
285 explicit OptionalContinuation(Action<Ts...> *parent) : action(parent) {}
286};
287template<typename... Ts> struct OptionalContinuation<false, Ts...> {
288 explicit OptionalContinuation(Action<Ts...> * /*parent*/) {}
289};
290
291template<bool HasElse, typename... Ts> class IfAction : public Action<Ts...> {
292 public:
293 explicit IfAction(Condition<Ts...> *condition) : condition_(condition) {}
294
295 // Precondition: add_then/add_else must be called at most once per instance.
296 // Codegen always batches the full action list into a single call. Calling
297 // twice would re-append the same inline continuation pointer and form a
298 // self-loop in the next_ chain.
299 void add_then(const std::initializer_list<Action<Ts...> *> &actions) {
300 this->then_.add_actions(actions);
301 this->then_.add_action(&this->then_continuation_);
302 }
303
304 void add_else(const std::initializer_list<Action<Ts...> *> &actions) requires(HasElse) {
305 this->else_.add_actions(actions);
306 this->else_.add_action(&this->else_continuation_.action);
307 }
308
309 void play_complex(const Ts &...x) override {
310 this->num_running_++;
311 if (this->condition_->check(x...)) {
312 if (!this->then_.empty() && this->num_running_ > 0) {
313 this->then_.play(x...);
314 return;
315 }
316 } else if constexpr (HasElse) {
317 if (!this->else_.empty() && this->num_running_ > 0) {
318 this->else_.play(x...);
319 return;
320 }
321 }
322 this->play_next_(x...);
323 }
324
325 void play(const Ts &...x) override { /* ignore - see play_complex */
326 }
327
328 void stop() override {
329 this->then_.stop();
330 if constexpr (HasElse) {
331 this->else_.stop();
332 }
333 }
334
335 protected:
339 struct NoElse {};
340 [[no_unique_address]] std::conditional_t<HasElse, ActionList<Ts...>, NoElse> else_;
341 [[no_unique_address]] OptionalContinuation<HasElse, Ts...> else_continuation_{this};
342};
343
344template<typename... Ts> class WhileAction : public Action<Ts...> {
345 public:
346 WhileAction(Condition<Ts...> *condition) : condition_(condition) {}
347
348 // Precondition: must be called at most once per instance (see IfAction::add_then).
349 void add_then(const std::initializer_list<Action<Ts...> *> &actions) {
350 this->then_.add_actions(actions);
351 this->then_.add_action(&this->loop_continuation_);
352 }
353
354 friend class WhileLoopContinuation<Ts...>;
355
356 void play_complex(const Ts &...x) override {
357 this->num_running_++;
358 // Initial condition check
359 if (!this->condition_->check(x...)) {
360 // If new condition check failed, stop loop if running
361 this->then_.stop();
362 this->play_next_(x...);
363 return;
364 }
365
366 if (this->num_running_ > 0) {
367 this->then_.play(x...);
368 }
369 }
370
371 void play(const Ts &...x) override { /* ignore - see play_complex */
372 }
373
374 void stop() override { this->then_.stop(); }
375
376 protected:
380};
381
382// Implementation of WhileLoopContinuation::play
383template<typename... Ts> void WhileLoopContinuation<Ts...>::play(const Ts &...x) {
384 if (this->parent_->num_running_ > 0 && this->parent_->condition_->check(x...)) {
385 // play again
386 this->parent_->then_.play(x...);
387 } else {
388 // condition false, play next
389 this->parent_->play_next_(x...);
390 }
391}
392
393// Forward declaration for RepeatLoopContinuation
394template<typename... Ts> class RepeatAction;
395
398template<typename... Ts> class RepeatLoopContinuation : public Action<uint32_t, Ts...> {
399 public:
401
402 void play(const uint32_t &iteration, const Ts &...x) override;
403
404 protected:
406};
407
408template<typename... Ts> class RepeatAction : public Action<Ts...> {
409 public:
411
412 // Precondition: must be called at most once per instance (see IfAction::add_then).
413 void add_then(const std::initializer_list<Action<uint32_t, Ts...> *> &actions) {
414 this->then_.add_actions(actions);
415 this->then_.add_action(&this->loop_continuation_);
416 }
417
418 friend class RepeatLoopContinuation<Ts...>;
419
420 void play_complex(const Ts &...x) override {
421 this->num_running_++;
422 if (this->count_.value(x...) > 0) {
423 this->then_.play(0, x...);
424 } else {
425 this->play_next_(x...);
426 }
427 }
428
429 void play(const Ts &...x) override { /* ignore - see play_complex */
430 }
431
432 void stop() override { this->then_.stop(); }
433
434 protected:
437};
438
439// Implementation of RepeatLoopContinuation::play
440template<typename... Ts> void RepeatLoopContinuation<Ts...>::play(const uint32_t &iteration, const Ts &...x) {
441 uint32_t next_iteration = iteration + 1;
442 if (next_iteration >= this->parent_->count_.value(x...)) {
443 this->parent_->play_next_(x...);
444 } else {
445 this->parent_->then_.play(next_iteration, x...);
446 }
447}
448
456template<typename... Ts> class WaitUntilAction : public Action<Ts...>, public Component {
457 public:
458 WaitUntilAction(Condition<Ts...> *condition) : condition_(condition) {}
459
461
462 void setup() override {
463 // Start with loop disabled - only enable when there's work to do
464 // IMPORTANT: Only disable if num_running_ is 0, otherwise play_complex() was already
465 // called before our setup() (e.g., from on_boot trigger at same priority level)
466 // and we must not undo its enable_loop() call
467 if (this->num_running_ == 0) {
468 this->disable_loop();
469 }
470 }
471
472 void play_complex(const Ts &...x) override {
473 this->num_running_++;
474 // Check if we can continue immediately.
475 if (this->condition_->check(x...)) {
476 if (this->num_running_ > 0) {
477 this->play_next_(x...);
478 }
479 return;
480 }
481
482 // Store for later processing
483 auto now = millis();
484 auto timeout = this->timeout_value_.optional_value(x...);
485 this->var_queue_.emplace_back(now, timeout, std::make_tuple(x...));
486
487 // Do immediate check with fresh timestamp - don't call loop() synchronously!
488 // Let the event loop call it to avoid reentrancy issues
489 if (this->process_queue_(now)) {
490 // Only enable loop if we still have pending items
491 this->enable_loop();
492 }
493 }
494
495 void loop() override {
496 // Safe to use cached time - only called from Application::loop()
498 // If queue is now empty, disable loop until next play_complex
499 this->disable_loop();
500 }
501 }
502
503 void stop() override {
504 this->var_queue_.clear();
505 this->disable_loop();
506 }
507
508 float get_setup_priority() const override { return setup_priority::DATA; }
509
510 void play(const Ts &...x) override { /* ignore - see play_complex */
511 }
512
513 protected:
514 // Helper: Process queue, triggering completed items and removing them
515 // Returns true if queue still has pending items
517 // Process each queued wait_until and remove completed ones
518 this->var_queue_.remove_if([&](auto &queued) {
519 auto start = std::get<uint32_t>(queued);
520 auto timeout = std::get<optional<uint32_t>>(queued);
521 auto &var = std::get<std::tuple<Ts...>>(queued);
522
523 // Check if timeout has expired
524 auto expired = timeout && (now - start) >= *timeout;
525
526 // Keep waiting if not expired and condition not met
527 if (!expired && !this->condition_->check_tuple(var)) {
528 return false;
529 }
530
531 // Condition met or timed out - trigger next action
532 this->play_next_tuple_(var);
533 return true;
534 });
535
536 return !this->var_queue_.empty();
537 }
538
540 std::list<std::tuple<uint32_t, optional<uint32_t>, std::tuple<Ts...>>> var_queue_{};
541};
542
543template<typename... Ts> class UpdateComponentAction : public Action<Ts...> {
544 public:
546
547 void play(const Ts &...x) override {
548 if (!this->component_->is_ready())
549 return;
550 this->component_->update();
551 }
552
553 protected:
555};
556
557template<typename... Ts> class SuspendComponentAction : public Action<Ts...> {
558 public:
560
561 void play(const Ts &...x) override {
562 if (!this->component_->is_ready())
563 return;
564 this->component_->stop_poller();
565 }
566
567 protected:
569};
570
571template<typename... Ts> class ResumeComponentAction : public Action<Ts...> {
572 public:
574 TEMPLATABLE_VALUE(uint32_t, update_interval)
575
576 void play(const Ts &...x) override {
577 if (!this->component_->is_ready()) {
578 return;
579 }
580 optional<uint32_t> update_interval = this->update_interval_.optional_value(x...);
581 if (update_interval.has_value()) {
582 this->component_->set_update_interval(update_interval.value());
583 }
584 this->component_->start_poller();
585 }
586
587 protected:
589};
590
591} // namespace esphome
void play_next_(const Ts &...x)
Definition automation.h:518
virtual void play(const Ts &...x)=0
void play_next_tuple_(const std::tuple< Ts... > &tuple, std::index_sequence< S... >)
Definition automation.h:526
virtual void play_complex(const Ts &...x)
Definition automation.h:489
void add_action(Action< Ts... > *action)
Definition automation.h:555
bool empty() const
Definition automation.h:585
void add_actions(const std::initializer_list< Action< Ts... > * > &actions)
Definition automation.h:562
void play(const Ts &...x) ESPHOME_ALWAYS_INLINE
Definition automation.h:574
AndCondition(std::initializer_list< Condition< Ts... > * > conditions)
bool check(const Ts &...x) override
std::array< Condition< Ts... > *, N > conditions_
const LogString * get_current_source()
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:84
bool is_ready() const
void enable_loop()
Enable this component's loop.
Definition component.h:246
void disable_loop()
Disable this component's loop.
Base class for all automation conditions.
Definition automation.h:438
bool check_tuple(const std::tuple< Ts... > &tuple)
Call check with a tuple of values as parameter.
Definition automation.h:444
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:585
void set_update_interval(uint32_t update_interval)
Manually set the update interval in ms for this polling object.
Definition component.h:599
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:461
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:160
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:85
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