29 requires std::convertible_to<F, T (*)(X...)>
37 [[deprecated(
"Lambda return type does not match TemplatableFn<T> — use the correct type in "
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...)); }) {}
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>)
57 requires(!std::invocable<V, X...>) && (!std::convertible_to<V, T (*)(X...)>)
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/");
66 bool has_value()
const {
return this->f_ !=
nullptr; }
68 T
value(X...
x)
const {
return this->f_ ? this->f_(
x...) : T{}; }
73 return this->f_(
x...);
76 T
value_or(X...
x, T default_value)
const {
return this->f_ ? this->f_(
x...) : default_value; }
79 T (*f_)(X...){
nullptr};
87 this->code_.func = func;
91 this->code_.data = data;
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 {
101 return this->code_.func(
x...);
102 return to_vector(this->code_.data, this->size());
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());
116 fn(buf.
get(), this->size());
118 fn(this->code_.data, this->size());
123 static std::vector<uint8_t>
to_vector(
const uint8_t *data,
size_t len) {
124 std::vector<uint8_t>
out(
len);
132 std::vector<uint8_t> (*func)(Ts...);
138#define TEMPLATABLE_BYTES(name) \
140 TemplatableBytes<Ts...> name##_{}; \
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); }
147template<
typename T,
typename... X>
class TemplatableValue;
152template<
typename T,
typename... X>
156#define TEMPLATABLE_VALUE_(type, name) \
158 TemplatableStorage<type, Ts...> name##_{}; \
161 template<typename V> void set_##name(V name) { this->name##_ = name; }
163#define TEMPLATABLE_VALUE(type, name) TEMPLATABLE_VALUE_(type, name)
176 requires(!std::invocable<V, X...>)
178 new (&this->storage_.value_) T(
static_cast<T
>(std::move(value)));
184 requires std::convertible_to<F, T (*)(X...)>
186 this->storage_.f_ = f;
191 [[deprecated(
"Lambda return type does not match TemplatableValue<T> — use the correct type in "
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>
196 this->storage_.f_ = [](X...
x) -> T {
return static_cast<T
>(F{}(
x...)); };
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>)
208 if (this->tag_ == VALUE) {
210 }
else if (this->tag_ == FN) {
216 if (this->tag_ == VALUE) {
217 new (&this->storage_.value_) T(std::move(other.storage_.value_));
219 }
else if (this->tag_ == FN) {
220 this->storage_.f_ = other.storage_.f_;
226 if (
this != &other) {
228 this->tag_ = other.tag_;
229 if (this->tag_ == VALUE) {
231 }
else if (this->tag_ == FN) {
239 if (
this != &other) {
241 this->tag_ = other.tag_;
242 if (this->tag_ == VALUE) {
243 new (&this->storage_.value_) T(std::move(other.storage_.value_));
245 }
else if (this->tag_ == FN) {
246 this->storage_.f_ = other.storage_.f_;
258 if (this->tag_ == FN)
259 return this->storage_.f_(
x...);
260 if (this->tag_ == VALUE)
261 return this->storage_.value_;
266 if (this->tag_ == NONE)
268 return this->value(
x...);
272 if (this->tag_ == NONE)
273 return default_value;
274 return this->value(
x...);
279 if constexpr (!std::is_trivially_destructible_v<T>) {
280 if (this->tag_ == VALUE)
281 this->storage_.value_.~T();
285 enum Tag : uint8_t { NONE, VALUE, FN } tag_{NONE};
312 this->static_str_ =
reinterpret_cast<const char *
>(str);
318 requires(!std::invocable<F, X...>)
320 this->value_ =
new std::string(std::move(value));
326 requires std::invocable<F, X...> && std::convertible_to<F, std::string (*)(X...)>
327 : type_(STATELESS_LAMBDA) {
328 this->stateless_f_ = f;
334 requires std::invocable<F, X...> && (!std::convertible_to<F, std::string (*)(X...)>)
336 this->f_ =
new std::function<std::string(X...)>(std::move(f));
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_;
354 if (this->type_ == VALUE) {
355 this->value_ = other.value_;
356 other.value_ =
nullptr;
357 }
else if (this->type_ == LAMBDA) {
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_;
370 if (
this != &other) {
378 if (
this != &other) {
386 if (this->type_ == VALUE) {
388 }
else if (this->type_ == LAMBDA) {
396 bool is_lambda()
const {
return this->type_ == LAMBDA || this->type_ == STATELESS_LAMBDA; }
399 switch (this->type_) {
400 case STATELESS_LAMBDA:
401 return this->stateless_f_(
x...);
403 return (*this->f_)(
x...);
405 return *this->value_;
407 return std::string(this->static_str_);
419 if (!this->has_value())
421 return this->value(
x...);
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...);
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; }
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_; }
447 switch (this->type_) {
451 return this->static_str_ ==
nullptr || this->static_str_[0] ==
'\0';
455 return this->static_str_ ==
nullptr ||
459 return this->value_->empty();
461 return this->value().empty();
473 switch (this->type_) {
477 if (this->static_str_ ==
nullptr)
479 return StringRef(this->static_str_, strlen(this->static_str_));
482 if (this->static_str_ ==
nullptr)
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';
494 return StringRef(this->value_->data(), this->value_->size());
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';
516 std::function<std::string(X...)> *
f_;
517 std::string (*stateless_f_)(X...);
527 std::conditional_t<std::is_copy_constructible_v<std::remove_cvref_t<T>>, std::remove_cvref_t<T>, T>;
540 return this->check_tuple_(tuple, std::make_index_sequence<
sizeof...(Ts)>{});
544 template<
size_t... S>
bool check_tuple_(
const std::tuple<Ts...> &tuple, std::index_sequence<S...> ) {
545 return this->check(std::get<S>(tuple)...);
549template<
typename... Ts>
class Automation;
556 inline void trigger(
const Ts &...
x) ESPHOME_ALWAYS_INLINE {
557 if (this->automation_parent_ ==
nullptr)
559 this->automation_parent_->trigger(
x...);
565 if (this->automation_parent_ ==
nullptr)
567 this->automation_parent_->stop();
571 if (this->automation_parent_ ==
nullptr)
573 return this->automation_parent_->is_running();
580template<
typename... Ts>
class ActionList;
585 this->num_running_++;
587 this->play_next_(
x...);
592 this->num_running_ = 0;
597 bool is_running() {
return this->num_running_ > 0 || this->is_running_next_(); }
602 int total = this->num_running_;
604 total +=
next->num_running_total();
612 virtual void play(
const Ts &...
x) = 0;
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...);
623 template<
size_t... S>
void play_next_tuple_(
const std::tuple<Ts...> &tuple, std::index_sequence<S...> ) {
624 this->play_next_(std::get<S>(tuple)...);
627 this->play_next_tuple_(tuple, std::make_index_sequence<
sizeof...(Ts)>{});
633 next->stop_complex();
638 return next !=
nullptr &&
next->is_running();
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); }
649 return reinterpret_cast<Action *
>(
reinterpret_cast<char *
>(owner) + OWNER_TAG);
660static_assert(
alignof(Action<>) >= 2,
"the owner tag needs a free low bit in Action object pointers");
666 *this->tail_() = action;
670 Action<Ts...> **tail = this->tail_();
671 for (
auto *action : actions) {
673 tail = &action->next_;
679 if (this->actions_ ==
nullptr)
685 inline void play(
const Ts &...
x) ESPHOME_ALWAYS_INLINE {
686 if (this->actions_ !=
nullptr)
687 this->actions_->play_complex(
x...);
690 this->play_tuple_(tuple, std::make_index_sequence<
sizeof...(Ts)>{});
693 if (this->actions_ !=
nullptr)
694 this->actions_->stop_complex();
696 bool empty()
const {
return this->actions_ ==
nullptr; }
700 if (this->actions_ ==
nullptr)
702 return this->actions_->is_running();
706 if (this->actions_ ==
nullptr)
708 return this->actions_->num_running_total();
712 template<
size_t... S>
void play_tuple_(
const std::tuple<Ts...> &tuple, std::index_sequence<S...> ) {
713 this->play(std::get<S>(tuple)...);
717 Action<Ts...> **tail = &this->actions_;
718 while (*tail !=
nullptr) {
721 ESPHOME_DEBUG_ASSERT(!(*tail)->next_is_owner_());
722 tail = &(*tail)->next_;
741 void stop() { this->actions_.stop(); }
745 inline void trigger(
const Ts &...
x) ESPHOME_ALWAYS_INLINE { this->actions_.play(
x...); }
761 void operator()(
const Ts &...args)
const { this->automation->trigger(args...); }
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>);
bool is_running()
Check if this or any of the following actions are currently running.
Action * chain_next_() const
Action * owner_() const
Only valid when next_is_owner_().
static Action * tag_owner(Action *owner)
void play_next_(const Ts &...x)
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)
int num_running_total()
The total number of actions that are currently running in this plus any of the following actions in t...
void play_next_tuple_(const std::tuple< Ts... > &tuple, std::index_sequence< S... >)
bool next_is_owner_() const
virtual void play_complex(const Ts &...x)
void add_action(Action< Ts... > *action)
void play_tuple(const std::tuple< Ts... > &tuple)
bool is_running()
Check if any action in this action list is currently running.
void add_actions(const std::initializer_list< Action< Ts... > * > &actions)
Action< Ts... > ** tail_()
void play_tuple_(const std::tuple< Ts... > &tuple, std::index_sequence< S... >)
void play(const Ts &...x) ESPHOME_ALWAYS_INLINE
void set_owner(Action< Ts... > *owner)
Resume owner via play() when the list finishes; call once, after adding actions.
int num_running()
Return the number of actions in this action list that are currently running.
void add_action(Action< Ts... > *action)
int num_running()
Return the number of actions in the action part of this automation that are currently running.
void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE
Automation(Trigger< Ts... > *trigger)
void add_actions(const std::initializer_list< Action< Ts... > * > &actions)
ActionList< Ts... > actions_
constexpr Automation() noexcept
Default constructor for use with TriggerForwarder (no Trigger object needed).
Base class for all automation conditions.
bool check_tuple_(const std::tuple< Ts... > &tuple, std::index_sequence< S... >)
bool check_tuple(const std::tuple< Ts... > &tuple)
Call check with a tuple of values as parameter.
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...
StringRef is a reference to a string owned by something else.
Byte payload that is either a stateless lambda or a static table, which may be in PROGMEM.
static std::vector< uint8_t > to_vector(const uint8_t *data, size_t len)
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,...
size_t size() const
Only valid when is_static().
void set_static(const uint8_t *data, uint16_t len)
void set_template(std::vector< uint8_t >(*func)(Ts...))
std::vector< uint8_t > value(const Ts &...x) const
const uint8_t * data() const
Only valid when is_static(); may point to PROGMEM, so read it with progmem_memcpy or progmem_read_byt...
Function-pointer-only templatable storage (4 bytes on 32-bit).
constexpr TemplatableFn()=default
T value_or(X... x, T default_value) const
constexpr TemplatableFn(F f)
TemplatableFn(std::nullptr_t)=delete
optional< T > optional_value(X... x) const
TemplatableValue(const __FlashStringHelper *str)
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...
TemplatableValue(TemplatableValue &&other) noexcept
TemplatableValue(const char *str)
TemplatableValue & operator=(const TemplatableValue &other)
bool is_lambda() const
Whether the value comes from a lambda, so it may differ between reads.
bool is_empty() const
Check if the string value is empty without allocating.
TemplatableValue(const TemplatableValue &other)
TemplatableValue & operator=(TemplatableValue &&other) noexcept
TemplatableValue(F value)
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.
std::string value_or(X... x, std::string default_value) const
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...
std::string value(X... x) const
optional< std::string > optional_value(X... x) const
std::function< std::string(X...)> * f_
Primary TemplatableValue: stores either a constant value or a function pointer.
TemplatableValue(const TemplatableValue &other)
TemplatableValue & operator=(TemplatableValue &&other) noexcept
union esphome::TemplatableValue::Storage storage_
TemplatableValue(V value)
TemplatableValue(TemplatableValue &&other) noexcept
optional< T > optional_value(X... x) const
TemplatableValue()=default
TemplatableValue & operator=(const TemplatableValue &other)
TemplatableValue(F)=delete
T value_or(X... x, T default_value) const
TemplatableValue(std::nullptr_t)=delete
void stop_action()
Stop any action connected to this trigger.
bool is_action_running()
Returns true if any action connected to this trigger is running.
void set_automation_parent(Automation< Ts... > *automation_parent)
void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE
Inform the parent automation that the event has triggered.
std::vector< uint8_t > & out
std::string progmem_string(ProgmemStr str)
Copies a string stored with ESPHOME_F into a std::string.
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...
void progmem_memcpy(void *dst, const void *src, size_t len)
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.
uint8_t progmem_read_byte(const uint8_t *addr)
const __FlashStringHelper * ProgmemStr
Callback forwarder that triggers an Automation directly.
Automation< Ts... > * automation
void operator()(const Ts &...args) const
Callback forwarder that triggers an Automation<> only when the bool arg is false.
void operator()(bool state) const
Callback forwarder that triggers an Automation<> only when the bool arg is true.
void operator()(bool state) const