25 template<
typename F>
TemplatableFn(F f)
requires std::convertible_to<F, T (*)(X...)> :
f_(f) {}
32 [[deprecated(
"Lambda return type does not match TemplatableFn<T> — use the correct type in "
33 "codegen")]]
TemplatableFn(F)
requires(!std::convertible_to<F, T (*)(X...)>) &&
34 std::invocable<F, X...> &&std::convertible_to<std::invoke_result_t<F, X...>, T> &&std::is_empty_v<F>
35 &&std::default_initializable<F> :
f_([](X...
x) -> T { return static_cast<T>(F{}(
x...)); }) {}
40 (!std::convertible_to<F, T (*)(X...)>) &&(!std::is_empty_v<F> ||
41 !std::convertible_to<std::invoke_result_t<F, X...>, T> ||
42 !std::default_initializable<F>) =
delete;
48 template<
typename V>
TemplatableFn(V)
requires(!std::invocable<V, X...>) && (!std::convertible_to<V, T (*)(X...)>) {
49 static_assert(
sizeof(V) == 0,
"Missing cg.templatable(...) in Python codegen for this TEMPLATABLE_VALUE "
50 "field. The wrapper was always required; it worked by accident because the old "
51 "TemplatableValue implicitly converted raw constants. TemplatableFn cannot. See "
52 "https://developers.esphome.io/blog/2026/04/09/"
53 "templatablefn-4-byte-templatable-storage-for-trivially-copyable-types/");
56 bool has_value()
const {
return this->f_ !=
nullptr; }
58 T
value(X...
x)
const {
return this->f_ ? this->f_(
x...) : T{}; }
63 return this->f_(
x...);
66 T
value_or(X...
x, T default_value)
const {
return this->f_ ? this->f_(
x...) : default_value; }
69 T (*f_)(X...){
nullptr};
73template<
typename T,
typename... X>
class TemplatableValue;
78template<
typename T,
typename... X>
82#define TEMPLATABLE_VALUE_(type, name) \
84 TemplatableStorage<type, Ts...> name##_{}; \
87 template<typename V> void set_##name(V name) { this->name##_ = name; }
89#define TEMPLATABLE_VALUE(type, name) TEMPLATABLE_VALUE_(type, name)
100 template<
typename V>
TemplatableValue(V value)
requires(!std::invocable<V, X...>) : tag_(VALUE) {
101 new (&this->storage_.value_) T(
static_cast<T
>(std::move(value)));
105 template<
typename F>
TemplatableValue(F f)
requires std::convertible_to<F, T (*)(X...)> : tag_(FN) {
106 this->storage_.f_ = f;
111 [[deprecated(
"Lambda return type does not match TemplatableValue<T> — use the correct type in "
113 std::invocable<F, X...> &&std::convertible_to<std::invoke_result_t<F, X...>, T> &&std::is_empty_v<F>
114 &&std::default_initializable<F> : tag_(FN) {
115 this->storage_.f_ = [](X...
x) -> T {
return static_cast<T
>(F{}(
x...)); };
121 (!std::convertible_to<F, T (*)(X...)>) &&(!std::is_empty_v<F> ||
122 !std::convertible_to<std::invoke_result_t<F, X...>, T> ||
123 !std::default_initializable<F>) =
delete;
126 if (this->tag_ == VALUE) {
128 }
else if (this->tag_ == FN) {
134 if (this->tag_ == VALUE) {
135 new (&this->storage_.value_) T(std::move(other.storage_.value_));
137 }
else if (this->tag_ == FN) {
138 this->storage_.f_ = other.storage_.f_;
144 if (
this != &other) {
146 this->tag_ = other.tag_;
147 if (this->tag_ == VALUE) {
149 }
else if (this->tag_ == FN) {
157 if (
this != &other) {
159 this->tag_ = other.tag_;
160 if (this->tag_ == VALUE) {
161 new (&this->storage_.value_) T(std::move(other.storage_.value_));
163 }
else if (this->tag_ == FN) {
164 this->storage_.f_ = other.storage_.f_;
176 if (this->tag_ == FN)
177 return this->storage_.f_(
x...);
178 if (this->tag_ == VALUE)
179 return this->storage_.value_;
184 if (this->tag_ == NONE)
186 return this->value(
x...);
190 if (this->tag_ == NONE)
191 return default_value;
192 return this->value(
x...);
197 if constexpr (!std::is_trivially_destructible_v<T>) {
198 if (this->tag_ == VALUE)
199 this->storage_.value_.~T();
203 enum Tag : uint8_t { NONE, VALUE, FN } tag_{NONE};
230 this->static_str_ =
reinterpret_cast<const char *
>(str);
234 template<
typename F>
TemplatableValue(F value)
requires(!std::invocable<F, X...>) : type_(VALUE) {
235 this->value_ =
new std::string(std::move(value));
240 TemplatableValue(F f)
requires std::invocable<F, X...> && std::convertible_to<F, std::string (*)(X...)>
241 : type_(STATELESS_LAMBDA) {
242 this->stateless_f_ = f;
247 TemplatableValue(F f)
requires std::invocable<F, X...> &&(!std::convertible_to<F, std::string (*)(X...)>)
249 this->f_ =
new std::function<std::string(X...)>(std::move(f));
254 if (this->type_ == VALUE) {
255 this->value_ =
new std::string(*other.value_);
256 }
else if (this->type_ == LAMBDA) {
257 this->f_ =
new std::function<std::string(X...)>(*other.f_);
258 }
else if (this->type_ == STATELESS_LAMBDA) {
259 this->stateless_f_ = other.stateless_f_;
260 }
else if (this->type_ == STATIC_STRING || this->type_ == FLASH_STRING) {
261 this->static_str_ = other.static_str_;
267 if (this->type_ == VALUE) {
268 this->value_ = other.value_;
269 other.value_ =
nullptr;
270 }
else if (this->type_ == LAMBDA) {
273 }
else if (this->type_ == STATELESS_LAMBDA) {
274 this->stateless_f_ = other.stateless_f_;
275 }
else if (this->type_ == STATIC_STRING || this->type_ == FLASH_STRING) {
276 this->static_str_ = other.static_str_;
283 if (
this != &other) {
291 if (
this != &other) {
299 if (this->type_ == VALUE) {
301 }
else if (this->type_ == LAMBDA) {
310 switch (this->type_) {
311 case STATELESS_LAMBDA:
312 return this->stateless_f_(
x...);
314 return (*this->f_)(
x...);
316 return *this->value_;
318 return std::string(this->static_str_);
322 size_t len = strlen_P(this->static_str_);
323 std::string result(
len,
'\0');
324 memcpy_P(result.data(), this->static_str_,
len);
335 if (!this->has_value())
337 return this->value(
x...);
340 std::string
value_or(X...
x, std::string default_value)
const {
341 if (!this->has_value())
342 return default_value;
343 return this->value(
x...);
359 switch (this->type_) {
363 return this->static_str_ ==
nullptr || this->static_str_[0] ==
'\0';
367 return this->static_str_ ==
nullptr ||
371 return this->value_->empty();
373 return this->value().empty();
385 switch (this->type_) {
389 if (this->static_str_ ==
nullptr)
391 return StringRef(this->static_str_, strlen(this->static_str_));
394 if (this->static_str_ ==
nullptr)
398 size_t len = strlen_P(this->static_str_);
399 size_t copy_len = std::min(
len, lambda_buf_size - 1);
400 memcpy_P(lambda_buf, this->static_str_, copy_len);
401 lambda_buf[copy_len] =
'\0';
406 return StringRef(this->value_->data(), this->value_->size());
408 std::string result = this->value();
409 size_t copy_len = std::min(result.size(), lambda_buf_size - 1);
410 memcpy(lambda_buf, result.data(), copy_len);
411 lambda_buf[copy_len] =
'\0';
428 std::function<std::string(X...)> *
f_;
429 std::string (*stateless_f_)(X...);
445 return this->check_tuple_(tuple, std::make_index_sequence<
sizeof...(Ts)>{});
449 template<
size_t... S>
bool check_tuple_(
const std::tuple<Ts...> &tuple, std::index_sequence<S...> ) {
450 return this->check(std::get<S>(tuple)...);
454template<
typename... Ts>
class Automation;
461 inline void trigger(
const Ts &...
x) ESPHOME_ALWAYS_INLINE {
462 if (this->automation_parent_ ==
nullptr)
464 this->automation_parent_->trigger(
x...);
470 if (this->automation_parent_ ==
nullptr)
472 this->automation_parent_->stop();
476 if (this->automation_parent_ ==
nullptr)
478 return this->automation_parent_->is_running();
485template<
typename... Ts>
class ActionList;
490 this->num_running_++;
492 this->play_next_(
x...);
497 this->num_running_ = 0;
502 virtual bool is_running() {
return this->num_running_ > 0 || this->is_running_next_(); }
507 int total = this->num_running_;
508 if (this->next_ !=
nullptr)
509 total += this->next_->num_running_total();
517 virtual void play(
const Ts &...
x) = 0;
519 if (this->num_running_ > 0) {
520 this->num_running_--;
521 if (this->next_ !=
nullptr) {
522 this->next_->play_complex(
x...);
526 template<
size_t... S>
void play_next_tuple_(
const std::tuple<Ts...> &tuple, std::index_sequence<S...> ) {
527 this->play_next_(std::get<S>(tuple)...);
530 this->play_next_tuple_(tuple, std::make_index_sequence<
sizeof...(Ts)>{});
535 if (this->next_ !=
nullptr) {
536 this->next_->stop_complex();
541 if (this->next_ ==
nullptr)
543 return this->next_->is_running();
557 Action<Ts...> **tail = &this->actions_;
558 while (*tail !=
nullptr)
559 tail = &(*tail)->
next_;
564 Action<Ts...> **tail = &this->actions_;
565 while (*tail !=
nullptr)
566 tail = &(*tail)->
next_;
567 for (
auto *action : actions) {
569 tail = &action->next_;
574 inline void play(
const Ts &...
x) ESPHOME_ALWAYS_INLINE {
575 if (this->actions_ !=
nullptr)
576 this->actions_->play_complex(
x...);
579 this->play_tuple_(tuple, std::make_index_sequence<
sizeof...(Ts)>{});
582 if (this->actions_ !=
nullptr)
583 this->actions_->stop_complex();
585 bool empty()
const {
return this->actions_ ==
nullptr; }
589 if (this->actions_ ==
nullptr)
591 return this->actions_->is_running();
595 if (this->actions_ ==
nullptr)
597 return this->actions_->num_running_total();
601 template<
size_t... S>
void play_tuple_(
const std::tuple<Ts...> &tuple, std::index_sequence<S...> ) {
602 this->play(std::get<S>(tuple)...);
617 void stop() { this->actions_.stop(); }
621 inline void trigger(
const Ts &...
x) ESPHOME_ALWAYS_INLINE { this->actions_.play(
x...); }
662static_assert(
sizeof(TriggerForwarder<>) <=
sizeof(
void *));
663static_assert(
sizeof(TriggerOnTrueForwarder) <=
sizeof(
void *));
664static_assert(
sizeof(TriggerOnFalseForwarder) <=
sizeof(
void *));
665static_assert(std::is_trivially_copyable_v<TriggerForwarder<>>);
666static_assert(std::is_trivially_copyable_v<TriggerOnTrueForwarder>);
667static_assert(std::is_trivially_copyable_v<TriggerOnFalseForwarder>);
virtual bool is_running()
Check if this or any of the following actions are currently running.
void play_next_(const Ts &...x)
virtual void stop_complex()
virtual void play(const Ts &...x)=0
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... >)
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)
void play_tuple_(const std::tuple< Ts... > &tuple, std::index_sequence< S... >)
void play(const Ts &...x) ESPHOME_ALWAYS_INLINE
int num_running()
Return the number of actions in this action list that are currently running.
void add_action(Action< Ts... > *action)
Automation()=default
Default constructor for use with TriggerForwarder (no Trigger object needed).
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_
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.
Simple continuation action that calls play_next_ on a parent action.
StringRef is a reference to a string owned by something else.
Function-pointer-only templatable storage (4 bytes on 32-bit).
T value_or(X... x, T default_value) const
TemplatableFn(std::nullptr_t)=delete
optional< T > optional_value(X... x) const
TemplatableValue(const __FlashStringHelper *str)
TemplatableValue(TemplatableValue &&other) noexcept
TemplatableValue(const char *str)
TemplatableValue & operator=(const TemplatableValue &other)
bool is_empty() const
Check if the string value is empty without allocating.
bool is_static_string() const
Check if this holds a static string (const char* stored without allocation) The pointer is always dir...
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
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)
T value_or(X... x, T default_value) const
TemplatableValue(F)=delete
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.
const char int const __FlashStringHelper va_list args
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)
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