ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
automation.h
Go to the documentation of this file.
1#pragma once
2
9#include <concepts>
10#include <functional>
11#include <type_traits>
12#include <utility>
13#include <vector>
14
15namespace esphome {
16
20template<typename T, typename... X> class TemplatableFn {
21 public:
22 constexpr TemplatableFn() = default;
23 TemplatableFn(std::nullptr_t) = delete;
24
25 // Exact return type match — direct function pointer storage
26 // constexpr so codegen can place arrays of these in flash.
27 template<typename F>
28 constexpr TemplatableFn(F f)
29 requires std::convertible_to<F, T (*)(X...)>
30 : f_(f) {}
31
32 // Convertible return type (e.g., int -> uint8_t) — casting trampoline.
33 // Stateless lambdas are default-constructible in C++20, so F{} recreates the lambda inside
34 // the trampoline without capturing. This compiles to the same code as a direct call + cast.
35 // Deprecated: codegen should use the correct output type to avoid the trampoline.
36 template<typename F>
37 [[deprecated("Lambda return type does not match TemplatableFn<T> — use the correct type in "
38 "codegen")]] TemplatableFn(F)
39 requires(!std::convertible_to<F, T (*)(X...)>) && std::invocable<F, X...> &&
40 std::convertible_to<std::invoke_result_t<F, X...>, T> && std::is_empty_v<F> && std::default_initializable<F>
41 : f_([](X... x) -> T { return static_cast<T>(F{}(x...)); }) {}
42
43 // Reject any callable that didn't match the above (stateful lambdas or inconvertible return types)
44 template<typename F>
46 requires std::invocable<F, X...> && (!std::convertible_to<F, T (*)(X...)>) &&
47 (!std::is_empty_v<F> || !std::convertible_to<std::invoke_result_t<F, X...>, T> ||
48 !std::default_initializable<F>)
49 = delete;
50
51 // Reject raw (non-callable) values with a helpful diagnostic pointing at the Python-side fix.
52 // TemplatableFn stores only a function pointer (4 bytes), so constants must be wrapped in a
53 // stateless lambda by codegen. External components hitting this error should use
54 // `cg.templatable(value, args, type)` in their Python __init__.py before passing to the setter.
55 template<typename V>
57 requires(!std::invocable<V, X...>) && (!std::convertible_to<V, T (*)(X...)>)
58 {
59 static_assert(sizeof(V) == 0, "Missing cg.templatable(...) in Python codegen for this TEMPLATABLE_VALUE "
60 "field. The wrapper was always required; it worked by accident because the old "
61 "TemplatableValue implicitly converted raw constants. TemplatableFn cannot. See "
62 "https://developers.esphome.io/blog/2026/04/09/"
63 "templatablefn-4-byte-templatable-storage-for-trivially-copyable-types/");
64 }
65
66 bool has_value() const { return this->f_ != nullptr; }
67
68 T value(X... x) const { return this->f_ ? this->f_(x...) : T{}; }
69
70 optional<T> optional_value(X... x) const {
71 if (!this->f_)
72 return {};
73 return this->f_(x...);
74 }
75
76 T value_or(X... x, T default_value) const { return this->f_ ? this->f_(x...) : default_value; }
77
78 protected:
79 T (*f_)(X...){nullptr};
80};
81
84template<typename... Ts> class TemplatableBytes {
85 public:
86 void set_template(std::vector<uint8_t> (*func)(Ts...)) {
87 this->code_.func = func;
88 this->len_ = -1;
89 }
90 void set_static(const uint8_t *data, uint16_t len) {
91 this->code_.data = data;
92 this->len_ = len;
93 }
94 bool is_static() const { return this->len_ >= 0; }
96 const uint8_t *data() const { return this->code_.data; }
98 size_t size() const { return static_cast<size_t>(this->len_); }
99 std::vector<uint8_t> value(const Ts &...x) const {
100 if (this->len_ < 0)
101 return this->code_.func(x...);
102 return to_vector(this->code_.data, this->size());
103 }
106 template<size_t N = 64, typename F> void visit(F &&fn, const Ts &...x) const {
107 if (this->len_ < 0) {
108 const std::vector<uint8_t> bytes = this->code_.func(x...);
109 fn(bytes.data(), bytes.size());
110 return;
111 }
112#ifdef USE_ESP8266
113 SmallBufferWithHeapFallback<N> buf(this->size());
114 if (this->len_ != 0)
115 progmem_memcpy(buf.get(), this->code_.data, this->size());
116 fn(buf.get(), this->size());
117#else
118 fn(this->code_.data, this->size());
119#endif
120 }
121
122 protected:
123 static std::vector<uint8_t> to_vector(const uint8_t *data, size_t len) {
124 std::vector<uint8_t> out(len);
125 // An empty payload is (nullptr, 0), and memcpy from nullptr is undefined even for zero bytes.
126 if (len != 0)
127 progmem_memcpy(out.data(), data, len); // byte loads from flash fault on ESP8266
128 return out;
129 }
130
131 union {
132 std::vector<uint8_t> (*func)(Ts...);
133 const uint8_t *data;
134 } code_{};
135 int32_t len_{-1}; // -1: lambda, otherwise the length of the static table
136};
137
138#define TEMPLATABLE_BYTES(name) \
139 protected: \
140 TemplatableBytes<Ts...> name##_{}; \
141\
142 public: \
143 void set_##name##_template(std::vector<uint8_t> (*func)(Ts...)) { this->name##_.set_template(func); } \
144 void set_##name##_static(const uint8_t *data, uint16_t len) { this->name##_.set_static(data, len); }
145
146// Forward declaration for TemplatableValue (string specialization needs it)
147template<typename T, typename... X> class TemplatableValue;
148
152template<typename T, typename... X>
154 std::conditional_t<std::is_trivially_copyable_v<T>, TemplatableFn<T, X...>, TemplatableValue<T, X...>>;
155
156#define TEMPLATABLE_VALUE_(type, name) \
157 protected: \
158 TemplatableStorage<type, Ts...> name##_{}; \
159\
160 public: \
161 template<typename V> void set_##name(V name) { this->name##_ = name; }
162
163#define TEMPLATABLE_VALUE(type, name) TEMPLATABLE_VALUE_(type, name)
164
168template<typename T, typename... X> class TemplatableValue {
169 public:
170 TemplatableValue() = default;
171 TemplatableValue(std::nullptr_t) = delete;
172
173 // Accept raw constants
174 template<typename V>
176 requires(!std::invocable<V, X...>)
177 : tag_(VALUE) {
178 new (&this->storage_.value_) T(static_cast<T>(std::move(value)));
179 }
180
181 // Accept stateless lambdas (convertible to function pointer)
182 template<typename F>
184 requires std::convertible_to<F, T (*)(X...)>
185 : tag_(FN) {
186 this->storage_.f_ = f;
187 }
188
189 // Convertible return type (e.g., int -> uint8_t) — casting trampoline
190 template<typename F>
191 [[deprecated("Lambda return type does not match TemplatableValue<T> — use the correct type in "
192 "codegen")]] TemplatableValue(F)
193 requires(!std::convertible_to<F, T (*)(X...)>) && std::invocable<F, X...> &&
194 std::convertible_to<std::invoke_result_t<F, X...>, T> && std::is_empty_v<F> && std::default_initializable<F>
195 : tag_(FN) {
196 this->storage_.f_ = [](X... x) -> T { return static_cast<T>(F{}(x...)); };
197 }
198
199 // Reject any callable that didn't match the above
200 template<typename F>
202 requires std::invocable<F, X...> && (!std::convertible_to<F, T (*)(X...)>) &&
203 (!std::is_empty_v<F> || !std::convertible_to<std::invoke_result_t<F, X...>, T> ||
204 !std::default_initializable<F>)
205 = delete;
206
207 TemplatableValue(const TemplatableValue &other) : tag_(other.tag_) {
208 if (this->tag_ == VALUE) {
209 new (&this->storage_.value_) T(other.storage_.value_);
210 } else if (this->tag_ == FN) {
211 this->storage_.f_ = other.storage_.f_;
212 }
213 }
214
215 TemplatableValue(TemplatableValue &&other) noexcept : tag_(other.tag_) {
216 if (this->tag_ == VALUE) {
217 new (&this->storage_.value_) T(std::move(other.storage_.value_));
218 other.destroy_();
219 } else if (this->tag_ == FN) {
220 this->storage_.f_ = other.storage_.f_;
221 }
222 other.tag_ = NONE;
223 }
224
226 if (this != &other) {
227 this->destroy_();
228 this->tag_ = other.tag_;
229 if (this->tag_ == VALUE) {
230 new (&this->storage_.value_) T(other.storage_.value_);
231 } else if (this->tag_ == FN) {
232 this->storage_.f_ = other.storage_.f_;
233 }
234 }
235 return *this;
236 }
237
239 if (this != &other) {
240 this->destroy_();
241 this->tag_ = other.tag_;
242 if (this->tag_ == VALUE) {
243 new (&this->storage_.value_) T(std::move(other.storage_.value_));
244 other.destroy_();
245 } else if (this->tag_ == FN) {
246 this->storage_.f_ = other.storage_.f_;
247 }
248 other.tag_ = NONE;
249 }
250 return *this;
251 }
252
253 ~TemplatableValue() { this->destroy_(); }
254
255 bool has_value() const { return this->tag_ != NONE; }
256
257 T value(X... x) const {
258 if (this->tag_ == FN)
259 return this->storage_.f_(x...);
260 if (this->tag_ == VALUE)
261 return this->storage_.value_;
262 return T{};
263 }
264
265 optional<T> optional_value(X... x) const {
266 if (this->tag_ == NONE)
267 return {};
268 return this->value(x...);
269 }
270
271 T value_or(X... x, T default_value) const {
272 if (this->tag_ == NONE)
273 return default_value;
274 return this->value(x...);
275 }
276
277 protected:
278 void destroy_() {
279 if constexpr (!std::is_trivially_destructible_v<T>) {
280 if (this->tag_ == VALUE)
281 this->storage_.value_.~T();
282 }
283 }
284
285 enum Tag : uint8_t { NONE, VALUE, FN } tag_{NONE};
286 // Union with explicit ctor/dtor to support non-trivially-constructible/destructible T
287 // (e.g., std::vector<uint8_t>). Lifetime of value_ is managed externally via
288 // placement new and destroy_().
289 union Storage {
290 constexpr Storage() : f_(nullptr) {}
291 constexpr ~Storage() {}
293 T (*f_)(X...);
294 } storage_;
295};
296
299template<typename... X> class TemplatableValue<std::string, X...> {
300 public:
301 TemplatableValue() : type_(NONE) {}
302
303 // For const char*: store pointer directly, no heap allocation.
304 // String remains in flash and is only converted to std::string when value() is called.
305 TemplatableValue(const char *str) : type_(STATIC_STRING) { this->static_str_ = str; }
306
307#ifdef USE_ESP8266
308 // On ESP8266, __FlashStringHelper* is a distinct type from const char*.
309 // ESPHOME_F(s) expands to F(s) which returns __FlashStringHelper* pointing to PROGMEM.
310 // Store as FLASH_STRING — value()/is_empty()/ref_or_copy_to() use _P functions.
311 TemplatableValue(const __FlashStringHelper *str) : type_(FLASH_STRING) {
312 this->static_str_ = reinterpret_cast<const char *>(str);
313 }
314#endif
315
316 template<typename F>
318 requires(!std::invocable<F, X...>)
319 : type_(VALUE) {
320 this->value_ = new std::string(std::move(value));
321 }
322
323 // For stateless lambdas (convertible to function pointer): use function pointer
324 template<typename F>
326 requires std::invocable<F, X...> && std::convertible_to<F, std::string (*)(X...)>
327 : type_(STATELESS_LAMBDA) {
328 this->stateless_f_ = f; // Implicit conversion to function pointer
329 }
330
331 // For stateful lambdas (not convertible to function pointer): use std::function
332 template<typename F>
334 requires std::invocable<F, X...> && (!std::convertible_to<F, std::string (*)(X...)>)
335 : type_(LAMBDA) {
336 this->f_ = new std::function<std::string(X...)>(std::move(f));
337 }
338
339 // Copy constructor
340 TemplatableValue(const TemplatableValue &other) : type_(other.type_) {
341 if (this->type_ == VALUE) {
342 this->value_ = new std::string(*other.value_);
343 } else if (this->type_ == LAMBDA) {
344 this->f_ = new std::function<std::string(X...)>(*other.f_);
345 } else if (this->type_ == STATELESS_LAMBDA) {
346 this->stateless_f_ = other.stateless_f_;
347 } else if (this->type_ == STATIC_STRING || this->type_ == FLASH_STRING) {
348 this->static_str_ = other.static_str_;
349 }
350 }
351
352 // Move constructor
353 TemplatableValue(TemplatableValue &&other) noexcept : type_(other.type_) {
354 if (this->type_ == VALUE) {
355 this->value_ = other.value_;
356 other.value_ = nullptr;
357 } else if (this->type_ == LAMBDA) {
358 this->f_ = other.f_;
359 other.f_ = nullptr;
360 } else if (this->type_ == STATELESS_LAMBDA) {
361 this->stateless_f_ = other.stateless_f_;
362 } else if (this->type_ == STATIC_STRING || this->type_ == FLASH_STRING) {
363 this->static_str_ = other.static_str_;
364 }
365 other.type_ = NONE;
366 }
367
368 // Assignment operators
370 if (this != &other) {
371 this->~TemplatableValue();
372 new (this) TemplatableValue(other);
373 }
374 return *this;
375 }
376
378 if (this != &other) {
379 this->~TemplatableValue();
380 new (this) TemplatableValue(std::move(other));
381 }
382 return *this;
383 }
384
386 if (this->type_ == VALUE) {
387 delete this->value_;
388 } else if (this->type_ == LAMBDA) {
389 delete this->f_;
390 }
391 // STATELESS_LAMBDA/STATIC_STRING/FLASH_STRING/NONE: no cleanup needed (pointers, not heap-allocated)
392 }
393
394 bool has_value() const { return this->type_ != NONE; }
396 bool is_lambda() const { return this->type_ == LAMBDA || this->type_ == STATELESS_LAMBDA; }
397
398 std::string value(X... x) const {
399 switch (this->type_) {
400 case STATELESS_LAMBDA:
401 return this->stateless_f_(x...); // Direct function pointer call
402 case LAMBDA:
403 return (*this->f_)(x...); // std::function call
404 case VALUE:
405 return *this->value_;
406 case STATIC_STRING:
407 return std::string(this->static_str_);
408#ifdef USE_ESP8266
409 case FLASH_STRING:
410 return progmem_string(reinterpret_cast<ProgmemStr>(this->static_str_));
411#endif
412 case NONE:
413 default:
414 return {};
415 }
416 }
417
418 optional<std::string> optional_value(X... x) const {
419 if (!this->has_value())
420 return {};
421 return this->value(x...);
422 }
423
424 std::string value_or(X... x, std::string default_value) const {
425 if (!this->has_value())
426 return default_value;
427 return this->value(x...);
428 }
429
433 // Remove before 2027.4.0
434 ESPDEPRECATED("Read the value with ref_or_copy_to() instead. Removed in 2027.4.0", "2026.10.0")
435 bool is_static_string() const { return this->type_ == STATIC_STRING; }
436
439 // Remove before 2027.4.0
440 ESPDEPRECATED("Use ref_or_copy_to() instead. Removed in 2027.4.0", "2026.10.0")
441 const char *get_static_string() const { return this->static_str_; }
442
446 bool is_empty() const {
447 switch (this->type_) {
448 case NONE:
449 return true;
450 case STATIC_STRING:
451 return this->static_str_ == nullptr || this->static_str_[0] == '\0';
452#ifdef USE_ESP8266
453 case FLASH_STRING:
454 // PROGMEM pointer — must use progmem_read_byte on ESP8266
455 return this->static_str_ == nullptr ||
456 progmem_read_byte(reinterpret_cast<const uint8_t *>(this->static_str_)) == '\0';
457#endif
458 case VALUE:
459 return this->value_->empty();
460 default: // LAMBDA/STATELESS_LAMBDA - must call value()
461 return this->value().empty();
462 }
463 }
464
472 StringRef ref_or_copy_to(char *lambda_buf, size_t lambda_buf_size) const {
473 switch (this->type_) {
474 case NONE:
475 return StringRef();
476 case STATIC_STRING:
477 if (this->static_str_ == nullptr)
478 return StringRef();
479 return StringRef(this->static_str_, strlen(this->static_str_));
480#ifdef USE_ESP8266
481 case FLASH_STRING:
482 if (this->static_str_ == nullptr)
483 return StringRef();
484 {
485 // PROGMEM pointer — copy to buffer via _P functions
486 size_t len = strlen_P(this->static_str_);
487 size_t copy_len = std::min(len, lambda_buf_size - 1);
488 memcpy_P(lambda_buf, this->static_str_, copy_len);
489 lambda_buf[copy_len] = '\0';
490 return StringRef(lambda_buf, copy_len);
491 }
492#endif
493 case VALUE:
494 return StringRef(this->value_->data(), this->value_->size());
495 default: { // LAMBDA/STATELESS_LAMBDA - must call value() and copy
496 std::string result = this->value();
497 size_t copy_len = std::min(result.size(), lambda_buf_size - 1);
498 memcpy(lambda_buf, result.data(), copy_len);
499 lambda_buf[copy_len] = '\0';
500 return StringRef(lambda_buf, copy_len);
501 }
502 }
503 }
504
505 protected:
506 enum : uint8_t {
511 STATIC_STRING, // For const char* — avoids heap allocation
512 FLASH_STRING, // PROGMEM pointer on ESP8266; never set on other platforms
513 } type_;
514 union {
515 std::string *value_; // Heap-allocated string (VALUE)
516 std::function<std::string(X...)> *f_; // Heap-allocated std::function (LAMBDA)
517 std::string (*stateless_f_)(X...); // Function pointer (STATELESS_LAMBDA)
518 const char *static_str_; // For STATIC_STRING and FLASH_STRING types
519 };
520};
521
525template<typename T>
527 std::conditional_t<std::is_copy_constructible_v<std::remove_cvref_t<T>>, std::remove_cvref_t<T>, T>;
528
533template<typename... Ts> class Condition {
534 public:
536 virtual bool check(const Ts &...x) = 0;
537
539 bool check_tuple(const std::tuple<Ts...> &tuple) {
540 return this->check_tuple_(tuple, std::make_index_sequence<sizeof...(Ts)>{});
541 }
542
543 protected:
544 template<size_t... S> bool check_tuple_(const std::tuple<Ts...> &tuple, std::index_sequence<S...> /*unused*/) {
545 return this->check(std::get<S>(tuple)...);
546 }
547};
548
549template<typename... Ts> class Automation;
550
551template<typename... Ts> class Trigger {
552 public:
554 // Force-inline: collapses the Trigger→Automation→ActionList forwarding
555 // chain into a single frame, reducing automation call stack depth.
556 inline void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE {
557 if (this->automation_parent_ == nullptr)
558 return;
559 this->automation_parent_->trigger(x...);
560 }
561 void set_automation_parent(Automation<Ts...> *automation_parent) { this->automation_parent_ = automation_parent; }
562
564 void stop_action() {
565 if (this->automation_parent_ == nullptr)
566 return;
567 this->automation_parent_->stop();
568 }
571 if (this->automation_parent_ == nullptr)
572 return false;
573 return this->automation_parent_->is_running();
574 }
575
576 protected:
577 Automation<Ts...> *automation_parent_{nullptr};
578};
579
580template<typename... Ts> class ActionList;
581
582template<typename... Ts> class Action {
583 public:
584 virtual void play_complex(const Ts &...x) {
585 this->num_running_++;
586 this->play(x...);
587 this->play_next_(x...);
588 }
590 if (num_running_) {
591 this->stop();
592 this->num_running_ = 0;
593 }
594 this->stop_next_();
595 }
597 bool is_running() { return this->num_running_ > 0 || this->is_running_next_(); }
598
602 int total = this->num_running_;
603 if (Action *next = this->chain_next_())
604 total += next->num_running_total();
605 return total;
606 }
607
608 protected:
609 friend ActionList<Ts...>;
610
612 virtual void play(const Ts &...x) = 0;
613 void play_next_(const Ts &...x) {
614 if (this->num_running_ > 0) {
615 this->num_running_--;
616 if (this->next_is_owner_()) {
617 this->owner_()->play(x...);
618 } else if (this->next_ != nullptr) {
619 this->next_->play_complex(x...);
620 }
621 }
622 }
623 template<size_t... S> void play_next_tuple_(const std::tuple<Ts...> &tuple, std::index_sequence<S...> /*unused*/) {
624 this->play_next_(std::get<S>(tuple)...);
625 }
626 void play_next_tuple_(const std::tuple<Ts...> &tuple) {
627 this->play_next_tuple_(tuple, std::make_index_sequence<sizeof...(Ts)>{});
628 }
629
630 virtual void stop() {}
631 void stop_next_() {
632 if (Action *next = this->chain_next_())
633 next->stop_complex();
634 }
635
637 Action *next = this->chain_next_();
638 return next != nullptr && next->is_running();
639 }
640
641 // A branch's last action points at its owner with the low bit set. Only Action objects are
642 // tagged, never function pointers, which use that bit on ARM Thumb (#9283).
643 static constexpr uintptr_t OWNER_TAG = 1;
644 bool next_is_owner_() const { return reinterpret_cast<uintptr_t>(this->next_) & OWNER_TAG; }
646 Action *owner_() const { return reinterpret_cast<Action *>(reinterpret_cast<char *>(this->next_) - OWNER_TAG); }
647 Action *chain_next_() const { return this->next_is_owner_() ? nullptr : this->next_; }
648 static Action *tag_owner(Action *owner) {
649 return reinterpret_cast<Action *>(reinterpret_cast<char *>(owner) + OWNER_TAG);
650 }
651
652 Action<Ts...> *next_{nullptr};
653
656 int num_running_{0};
657};
658
659// The vtable pointer gives every instantiation this alignment.
660static_assert(alignof(Action<>) >= 2, "the owner tag needs a free low bit in Action object pointers");
661
662template<typename... Ts> class ActionList {
663 public:
664 void add_action(Action<Ts...> *action) {
665 // Walk to end of chain - action lists are short and only built during setup()
666 *this->tail_() = action;
667 }
668 void add_actions(const std::initializer_list<Action<Ts...> *> &actions) {
669 // Find tail once, then append all actions in a single pass
670 Action<Ts...> **tail = this->tail_();
671 for (auto *action : actions) {
672 *tail = action;
673 tail = &action->next_;
674 }
675 }
678 // Nothing to tag in an empty list
679 if (this->actions_ == nullptr)
680 return;
681 *this->tail_() = Action<Ts...>::tag_owner(owner);
682 }
683 // Force-inline: part of the Trigger→Automation→ActionList forwarding
684 // chain collapsed to reduce automation call stack depth.
685 inline void play(const Ts &...x) ESPHOME_ALWAYS_INLINE {
686 if (this->actions_ != nullptr)
687 this->actions_->play_complex(x...);
688 }
689 void play_tuple(const std::tuple<Ts...> &tuple) {
690 this->play_tuple_(tuple, std::make_index_sequence<sizeof...(Ts)>{});
691 }
692 void stop() {
693 if (this->actions_ != nullptr)
694 this->actions_->stop_complex();
695 }
696 bool empty() const { return this->actions_ == nullptr; }
697
699 bool is_running() {
700 if (this->actions_ == nullptr)
701 return false;
702 return this->actions_->is_running();
703 }
706 if (this->actions_ == nullptr)
707 return 0;
708 return this->actions_->num_running_total();
709 }
710
711 protected:
712 template<size_t... S> void play_tuple_(const std::tuple<Ts...> &tuple, std::index_sequence<S...> /*unused*/) {
713 this->play(std::get<S>(tuple)...);
714 }
715 // Lists with an owner are complete; never append after set_owner()
716 Action<Ts...> **tail_() {
717 Action<Ts...> **tail = &this->actions_;
718 while (*tail != nullptr) {
719 // Not checked in release builds: only automation.py builds these lists, appending each action and
720 // then finishing the branch once, so appending after set_owner() would be a codegen bug.
721 ESPHOME_DEBUG_ASSERT(!(*tail)->next_is_owner_());
722 tail = &(*tail)->next_;
723 }
724 return tail;
725 }
726
727 Action<Ts...> *actions_{nullptr};
728};
729
730template<typename... Ts> class Automation {
731 public:
733 // User provided, not "= default": `new(p) Automation()` would zero-fill .bss that is already zero.
734 // constexpr and noexcept keep the rest of the implicit constructor's contract.
735 constexpr Automation() noexcept {}
736 explicit Automation(Trigger<Ts...> *trigger) { trigger->set_automation_parent(this); }
737
738 void add_action(Action<Ts...> *action) { this->actions_.add_action(action); }
739 void add_actions(const std::initializer_list<Action<Ts...> *> &actions) { this->actions_.add_actions(actions); }
740
741 void stop() { this->actions_.stop(); }
742
743 // Force-inline: part of the Trigger→Automation→ActionList forwarding
744 // chain collapsed to reduce automation call stack depth.
745 inline void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE { this->actions_.play(x...); }
746
747 bool is_running() { return this->actions_.is_running(); }
748
750 int num_running() { return this->actions_.num_running(); }
751
752 protected:
754};
755
759template<typename... Ts> struct TriggerForwarder {
761 void operator()(const Ts &...args) const { this->automation->trigger(args...); }
762};
763
768 void operator()(bool state) const {
769 if (state)
770 this->automation->trigger();
771 }
772};
773
778 void operator()(bool state) const {
779 if (!state)
780 this->automation->trigger();
781 }
782};
783
784// Ensure forwarders fit in Callback::ctx_ (pointer-sized inline storage).
785// If these fail, the forwarder would heap-allocate in Callback::create().
786static_assert(sizeof(TriggerForwarder<>) <= sizeof(void *));
787static_assert(sizeof(TriggerOnTrueForwarder) <= sizeof(void *));
788static_assert(sizeof(TriggerOnFalseForwarder) <= sizeof(void *));
789static_assert(std::is_trivially_copyable_v<TriggerForwarder<>>);
790static_assert(std::is_trivially_copyable_v<TriggerOnTrueForwarder>);
791static_assert(std::is_trivially_copyable_v<TriggerOnFalseForwarder>);
792
793} // namespace esphome
bool is_running()
Check if this or any of the following actions are currently running.
Definition automation.h:597
Action * chain_next_() const
Definition automation.h:647
void stop_complex()
Definition automation.h:589
Action * owner_() const
Only valid when next_is_owner_().
Definition automation.h:646
static Action * tag_owner(Action *owner)
Definition automation.h:648
void play_next_(const Ts &...x)
Definition automation.h:613
virtual void play(const Ts &...x)=0
For a branch owner (if, while), play() runs when the branch finishes.
void play_next_tuple_(const std::tuple< Ts... > &tuple)
Definition automation.h:626
virtual void stop()
Definition automation.h:630
bool is_running_next_()
Definition automation.h:636
int num_running_total()
The total number of actions that are currently running in this plus any of the following actions in t...
Definition automation.h:601
void play_next_tuple_(const std::tuple< Ts... > &tuple, std::index_sequence< S... >)
Definition automation.h:623
bool next_is_owner_() const
Definition automation.h:644
virtual void play_complex(const Ts &...x)
Definition automation.h:584
void add_action(Action< Ts... > *action)
Definition automation.h:664
void play_tuple(const std::tuple< Ts... > &tuple)
Definition automation.h:689
bool is_running()
Check if any action in this action list is currently running.
Definition automation.h:699
bool empty() const
Definition automation.h:696
void add_actions(const std::initializer_list< Action< Ts... > * > &actions)
Definition automation.h:668
Action< Ts... > ** tail_()
Definition automation.h:716
void play_tuple_(const std::tuple< Ts... > &tuple, std::index_sequence< S... >)
Definition automation.h:712
void play(const Ts &...x) ESPHOME_ALWAYS_INLINE
Definition automation.h:685
void set_owner(Action< Ts... > *owner)
Resume owner via play() when the list finishes; call once, after adding actions.
Definition automation.h:677
int num_running()
Return the number of actions in this action list that are currently running.
Definition automation.h:705
void add_action(Action< Ts... > *action)
Definition automation.h:738
int num_running()
Return the number of actions in the action part of this automation that are currently running.
Definition automation.h:750
void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE
Definition automation.h:745
Automation(Trigger< Ts... > *trigger)
Definition automation.h:736
void add_actions(const std::initializer_list< Action< Ts... > * > &actions)
Definition automation.h:739
ActionList< Ts... > actions_
Definition automation.h:753
constexpr Automation() noexcept
Default constructor for use with TriggerForwarder (no Trigger object needed).
Definition automation.h:735
Base class for all automation conditions.
Definition automation.h:533
bool check_tuple_(const std::tuple< Ts... > &tuple, std::index_sequence< S... >)
Definition automation.h:544
bool check_tuple(const std::tuple< Ts... > &tuple)
Call check with a tuple of values as parameter.
Definition automation.h:539
virtual bool check(const Ts &...x)=0
Check whether this condition passes. This condition check must be instant, and not cause any delays.
Helper class for efficient buffer allocation - uses stack for small sizes, heap for large This is use...
Definition helpers.h:827
StringRef is a reference to a string owned by something else.
Definition string_ref.h:31
Byte payload that is either a stateless lambda or a static table, which may be in PROGMEM.
Definition automation.h:84
static std::vector< uint8_t > to_vector(const uint8_t *data, size_t len)
Definition automation.h:123
void visit(F &&fn, const Ts &...x) const
Calls fn(const uint8_t *data, size_t len) with the payload readable from RAM: a lambda's vector,...
Definition automation.h:106
size_t size() const
Only valid when is_static().
Definition automation.h:98
void set_static(const uint8_t *data, uint16_t len)
Definition automation.h:90
void set_template(std::vector< uint8_t >(*func)(Ts...))
Definition automation.h:86
std::vector< uint8_t > value(const Ts &...x) const
Definition automation.h:99
const uint8_t * data() const
Only valid when is_static(); may point to PROGMEM, so read it with progmem_memcpy or progmem_read_byt...
Definition automation.h:96
Function-pointer-only templatable storage (4 bytes on 32-bit).
Definition automation.h:20
constexpr TemplatableFn()=default
T value_or(X... x, T default_value) const
Definition automation.h:76
bool has_value() const
Definition automation.h:66
constexpr TemplatableFn(F f)
Definition automation.h:28
TemplatableFn(std::nullptr_t)=delete
optional< T > optional_value(X... x) const
Definition automation.h:70
T value(X... x) const
Definition automation.h:68
TemplatableValue(const __FlashStringHelper *str)
Definition automation.h:311
ESPDEPRECATED("Read the value with ref_or_copy_to() instead. Removed in 2027.4.0", "2026.10.0") bool is_static_string() const
Check if this holds a static string (const char* stored without allocation) The pointer is always dir...
Definition automation.h:434
TemplatableValue(TemplatableValue &&other) noexcept
Definition automation.h:353
TemplatableValue & operator=(const TemplatableValue &other)
Definition automation.h:369
bool is_lambda() const
Whether the value comes from a lambda, so it may differ between reads.
Definition automation.h:396
bool is_empty() const
Check if the string value is empty without allocating.
Definition automation.h:446
TemplatableValue(const TemplatableValue &other)
Definition automation.h:340
TemplatableValue & operator=(TemplatableValue &&other) noexcept
Definition automation.h:377
StringRef ref_or_copy_to(char *lambda_buf, size_t lambda_buf_size) const
Get a StringRef to the string value without heap allocation when possible.
Definition automation.h:472
std::string value_or(X... x, std::string default_value) const
Definition automation.h:424
ESPDEPRECATED("Use ref_or_copy_to() instead. Removed in 2027.4.0", "2026.10.0") const char *get_static_string() const
Get the static string pointer (only valid if is_static_string() returns true) The pointer is always d...
Definition automation.h:440
optional< std::string > optional_value(X... x) const
Definition automation.h:418
std::function< std::string(X...)> * f_
Definition automation.h:516
Primary TemplatableValue: stores either a constant value or a function pointer.
Definition automation.h:168
TemplatableValue(const TemplatableValue &other)
Definition automation.h:207
TemplatableValue & operator=(TemplatableValue &&other) noexcept
Definition automation.h:238
union esphome::TemplatableValue::Storage storage_
TemplatableValue(TemplatableValue &&other) noexcept
Definition automation.h:215
T value(X... x) const
Definition automation.h:257
optional< T > optional_value(X... x) const
Definition automation.h:265
TemplatableValue & operator=(const TemplatableValue &other)
Definition automation.h:225
T value_or(X... x, T default_value) const
Definition automation.h:271
TemplatableValue(std::nullptr_t)=delete
void stop_action()
Stop any action connected to this trigger.
Definition automation.h:564
bool is_action_running()
Returns true if any action connected to this trigger is running.
Definition automation.h:570
void set_automation_parent(Automation< Ts... > *automation_parent)
Definition automation.h:561
void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE
Inform the parent automation that the event has triggered.
Definition automation.h:556
NameCopy * next
bool state
Definition fan.h:2
std::vector< uint8_t > & out
Definition gif.cpp:49
std::string progmem_string(ProgmemStr str)
Copies a string stored with ESPHOME_F into a std::string.
Definition helpers.cpp:24
std::conditional_t< std::is_copy_constructible_v< std::remove_cvref_t< T > >, std::remove_cvref_t< T >, T > stored_trigger_arg_t
How deferring actions store a trigger argument: a copy, since a referenced object may be gone once th...
Definition automation.h:526
void progmem_memcpy(void *dst, const void *src, size_t len)
Definition hal.h:56
std::conditional_t< std::is_trivially_copyable_v< T >, TemplatableFn< T, X... >, TemplatableValue< T, X... > > TemplatableStorage
Selects TemplatableFn (4 bytes) for trivially copyable types, TemplatableValue (8 bytes) otherwise.
Definition automation.h:153
uint8_t progmem_read_byte(const uint8_t *addr)
Definition hal.h:51
STL namespace.
const __FlashStringHelper * ProgmemStr
Definition progmem.h:31
Callback forwarder that triggers an Automation directly.
Definition automation.h:759
Automation< Ts... > * automation
Definition automation.h:760
void operator()(const Ts &...args) const
Definition automation.h:761
Callback forwarder that triggers an Automation<> only when the bool arg is false.
Definition automation.h:776
void operator()(bool state) const
Definition automation.h:778
Callback forwarder that triggers an Automation<> only when the bool arg is true.
Definition automation.h:766
void operator()(bool state) const
Definition automation.h:768
uint32_t len
uint16_t x
Definition tt21100.cpp:5