ESPHome 2026.3.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 <utility>
12#include <vector>
13
14namespace esphome {
15
16// C++20 std::index_sequence is now used for tuple unpacking
17// Legacy seq<>/gens<> pattern deprecated but kept for backwards compatibility
18// https://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer/7858971#7858971
19// Remove before 2026.6.0
20// NOLINTBEGIN(readability-identifier-naming)
21#if defined(__GNUC__) || defined(__clang__)
22#pragma GCC diagnostic push
23#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
24#endif
25
26template<int...> struct ESPDEPRECATED("Use std::index_sequence instead. Removed in 2026.6.0", "2025.12.0") seq {};
27template<int N, int... S>
28struct ESPDEPRECATED("Use std::make_index_sequence instead. Removed in 2026.6.0", "2025.12.0") gens
29 : gens<N - 1, N - 1, S...> {};
30template<int... S> struct gens<0, S...> { using type = seq<S...>; };
31
32#if defined(__GNUC__) || defined(__clang__)
33#pragma GCC diagnostic pop
34#endif
35// NOLINTEND(readability-identifier-naming)
36
37#define TEMPLATABLE_VALUE_(type, name) \
38 protected: \
39 TemplatableValue<type, Ts...> name##_{}; \
40\
41 public: \
42 template<typename V> void set_##name(V name) { this->name##_ = name; }
43
44#define TEMPLATABLE_VALUE(type, name) TEMPLATABLE_VALUE_(type, name)
45
46template<typename T, typename... X> class TemplatableValue {
47 // For std::string, store pointer to heap-allocated string to keep union pointer-sized.
48 // For other types, store value inline.
49 static constexpr bool USE_HEAP_STORAGE = std::same_as<T, std::string>;
50
51 public:
53
54 // For const char* when T is std::string: store pointer directly, no heap allocation
55 // String remains in flash and is only converted to std::string when value() is called
56 TemplatableValue(const char *str) requires std::same_as<T, std::string> : type_(STATIC_STRING) {
57 this->static_str_ = str;
58 }
59
60#ifdef USE_ESP8266
61 // On ESP8266, __FlashStringHelper* is a distinct type from const char*.
62 // ESPHOME_F(s) expands to F(s) which returns __FlashStringHelper* pointing to PROGMEM.
63 // Store as FLASH_STRING — value()/is_empty()/ref_or_copy_to() use _P functions
64 // to access the PROGMEM pointer safely.
65 TemplatableValue(const __FlashStringHelper *str) requires std::same_as<T, std::string> : type_(FLASH_STRING) {
66 this->static_str_ = reinterpret_cast<const char *>(str);
67 }
68#endif
69
70 template<typename F> TemplatableValue(F value) requires(!std::invocable<F, X...>) : type_(VALUE) {
71 if constexpr (USE_HEAP_STORAGE) {
72 this->value_ = new T(std::move(value));
73 } else {
74 new (&this->value_) T(std::move(value));
75 }
76 }
77
78 // For stateless lambdas (convertible to function pointer): use function pointer
79 template<typename F>
80 TemplatableValue(F f) requires std::invocable<F, X...> && std::convertible_to<F, T (*)(X...)>
82 this->stateless_f_ = f; // Implicit conversion to function pointer
83 }
84
85 // For stateful lambdas (not convertible to function pointer): use std::function
86 template<typename F>
87 TemplatableValue(F f) requires std::invocable<F, X...> &&(!std::convertible_to<F, T (*)(X...)>) : type_(LAMBDA) {
88 this->f_ = new std::function<T(X...)>(std::move(f));
89 }
90
91 // Copy constructor
93 if (this->type_ == VALUE) {
94 if constexpr (USE_HEAP_STORAGE) {
95 this->value_ = new T(*other.value_);
96 } else {
97 new (&this->value_) T(other.value_);
98 }
99 } else if (this->type_ == LAMBDA) {
100 this->f_ = new std::function<T(X...)>(*other.f_);
101 } else if (this->type_ == STATELESS_LAMBDA) {
102 this->stateless_f_ = other.stateless_f_;
103 } else if (this->type_ == STATIC_STRING || this->type_ == FLASH_STRING) {
104 this->static_str_ = other.static_str_;
105 }
106 }
107
108 // Move constructor
109 TemplatableValue(TemplatableValue &&other) noexcept : type_(other.type_) {
110 if (this->type_ == VALUE) {
111 if constexpr (USE_HEAP_STORAGE) {
112 this->value_ = other.value_;
113 other.value_ = nullptr;
114 } else {
115 new (&this->value_) T(std::move(other.value_));
116 }
117 } else if (this->type_ == LAMBDA) {
118 this->f_ = other.f_;
119 other.f_ = nullptr;
120 } else if (this->type_ == STATELESS_LAMBDA) {
121 this->stateless_f_ = other.stateless_f_;
122 } else if (this->type_ == STATIC_STRING || this->type_ == FLASH_STRING) {
123 this->static_str_ = other.static_str_;
124 }
125 other.type_ = NONE;
126 }
127
128 // Assignment operators
130 if (this != &other) {
131 this->~TemplatableValue();
132 new (this) TemplatableValue(other);
133 }
134 return *this;
135 }
136
138 if (this != &other) {
139 this->~TemplatableValue();
140 new (this) TemplatableValue(std::move(other));
141 }
142 return *this;
143 }
144
146 if (this->type_ == VALUE) {
147 if constexpr (USE_HEAP_STORAGE) {
148 delete this->value_;
149 } else {
150 this->value_.~T();
151 }
152 } else if (this->type_ == LAMBDA) {
153 delete this->f_;
154 }
155 // STATELESS_LAMBDA/STATIC_STRING/FLASH_STRING/NONE: no cleanup needed (pointers, not heap-allocated)
156 }
157
158 bool has_value() const { return this->type_ != NONE; }
159
160 T value(X... x) const {
161 switch (this->type_) {
162 case STATELESS_LAMBDA:
163 return this->stateless_f_(x...); // Direct function pointer call
164 case LAMBDA:
165 return (*this->f_)(x...); // std::function call
166 case VALUE:
167 if constexpr (USE_HEAP_STORAGE) {
168 return *this->value_;
169 } else {
170 return this->value_;
171 }
172 case STATIC_STRING:
173 // if constexpr required: code must compile for all T, but STATIC_STRING
174 // can only be set when T is std::string (enforced by constructor constraint)
175 if constexpr (std::same_as<T, std::string>) {
176 return std::string(this->static_str_);
177 }
178 __builtin_unreachable();
179#ifdef USE_ESP8266
180 case FLASH_STRING:
181 // PROGMEM pointer — must use _P functions to access on ESP8266
182 if constexpr (std::same_as<T, std::string>) {
183 size_t len = strlen_P(this->static_str_);
184 std::string result(len, '\0');
185 memcpy_P(result.data(), this->static_str_, len);
186 return result;
187 }
188 __builtin_unreachable();
189#endif
190 case NONE:
191 default:
192 return T{};
193 }
194 }
195
197 if (!this->has_value()) {
198 return {};
199 }
200 return this->value(x...);
201 }
202
203 T value_or(X... x, T default_value) {
204 if (!this->has_value()) {
205 return default_value;
206 }
207 return this->value(x...);
208 }
209
213 bool is_static_string() const { return this->type_ == STATIC_STRING; }
214
217 const char *get_static_string() const { return this->static_str_; }
218
222 bool is_empty() const requires std::same_as<T, std::string> {
223 switch (this->type_) {
224 case NONE:
225 return true;
226 case STATIC_STRING:
227 return this->static_str_ == nullptr || this->static_str_[0] == '\0';
228#ifdef USE_ESP8266
229 case FLASH_STRING:
230 // PROGMEM pointer — must use progmem_read_byte on ESP8266
231 return this->static_str_ == nullptr ||
232 progmem_read_byte(reinterpret_cast<const uint8_t *>(this->static_str_)) == '\0';
233#endif
234 case VALUE:
235 return this->value_->empty();
236 default: // LAMBDA/STATELESS_LAMBDA - must call value()
237 return this->value().empty();
238 }
239 }
240
248 StringRef ref_or_copy_to(char *lambda_buf, size_t lambda_buf_size) const requires std::same_as<T, std::string> {
249 switch (this->type_) {
250 case NONE:
251 return StringRef();
252 case STATIC_STRING:
253 if (this->static_str_ == nullptr)
254 return StringRef();
255 return StringRef(this->static_str_, strlen(this->static_str_));
256#ifdef USE_ESP8266
257 case FLASH_STRING:
258 if (this->static_str_ == nullptr)
259 return StringRef();
260 {
261 // PROGMEM pointer — copy to buffer via _P functions
262 size_t len = strlen_P(this->static_str_);
263 size_t copy_len = std::min(len, lambda_buf_size - 1);
264 memcpy_P(lambda_buf, this->static_str_, copy_len);
265 lambda_buf[copy_len] = '\0';
266 return StringRef(lambda_buf, copy_len);
267 }
268#endif
269 case VALUE:
270 return StringRef(this->value_->data(), this->value_->size());
271 default: { // LAMBDA/STATELESS_LAMBDA - must call value() and copy
272 std::string result = this->value();
273 size_t copy_len = std::min(result.size(), lambda_buf_size - 1);
274 memcpy(lambda_buf, result.data(), copy_len);
275 lambda_buf[copy_len] = '\0';
276 return StringRef(lambda_buf, copy_len);
277 }
278 }
279 }
280
281 protected : enum : uint8_t {
286 STATIC_STRING, // For const char* when T is std::string - avoids heap allocation
287 FLASH_STRING, // PROGMEM pointer on ESP8266; never set on other platforms
289 // For std::string, use heap pointer to minimize union size (4 bytes vs 12+).
290 // For other types, store value inline as before.
291 using ValueStorage = std::conditional_t<USE_HEAP_STORAGE, T *, T>;
292 union {
293 ValueStorage value_; // T for inline storage, T* for heap storage
294 std::function<T(X...)> *f_;
295 T (*stateless_f_)(X...);
296 const char *static_str_; // For STATIC_STRING and FLASH_STRING types
297 };
298};
299
304template<typename... Ts> class Condition {
305 public:
307 virtual bool check(const Ts &...x) = 0;
308
310 bool check_tuple(const std::tuple<Ts...> &tuple) {
311 return this->check_tuple_(tuple, std::make_index_sequence<sizeof...(Ts)>{});
312 }
313
314 protected:
315 template<size_t... S> bool check_tuple_(const std::tuple<Ts...> &tuple, std::index_sequence<S...> /*unused*/) {
316 return this->check(std::get<S>(tuple)...);
317 }
318};
319
320template<typename... Ts> class Automation;
321
322template<typename... Ts> class Trigger {
323 public:
325 void trigger(const Ts &...x) {
326 if (this->automation_parent_ == nullptr)
327 return;
328 this->automation_parent_->trigger(x...);
329 }
330 void set_automation_parent(Automation<Ts...> *automation_parent) { this->automation_parent_ = automation_parent; }
331
333 void stop_action() {
334 if (this->automation_parent_ == nullptr)
335 return;
336 this->automation_parent_->stop();
337 }
340 if (this->automation_parent_ == nullptr)
341 return false;
342 return this->automation_parent_->is_running();
343 }
344
345 protected:
347};
348
349template<typename... Ts> class ActionList;
350
351template<typename... Ts> class Action {
352 public:
353 virtual void play_complex(const Ts &...x) {
354 this->num_running_++;
355 this->play(x...);
356 this->play_next_(x...);
357 }
358 virtual void stop_complex() {
359 if (num_running_) {
360 this->stop();
361 this->num_running_ = 0;
362 }
363 this->stop_next_();
364 }
366 virtual bool is_running() { return this->num_running_ > 0 || this->is_running_next_(); }
367
371 int total = this->num_running_;
372 if (this->next_ != nullptr)
373 total += this->next_->num_running_total();
374 return total;
375 }
376
377 protected:
378 friend ActionList<Ts...>;
379 template<typename... Us> friend class ContinuationAction;
380
381 virtual void play(const Ts &...x) = 0;
382 void play_next_(const Ts &...x) {
383 if (this->num_running_ > 0) {
384 this->num_running_--;
385 if (this->next_ != nullptr) {
386 this->next_->play_complex(x...);
387 }
388 }
389 }
390 template<size_t... S> void play_next_tuple_(const std::tuple<Ts...> &tuple, std::index_sequence<S...> /*unused*/) {
391 this->play_next_(std::get<S>(tuple)...);
392 }
393 void play_next_tuple_(const std::tuple<Ts...> &tuple) {
394 this->play_next_tuple_(tuple, std::make_index_sequence<sizeof...(Ts)>{});
395 }
396
397 virtual void stop() {}
398 void stop_next_() {
399 if (this->next_ != nullptr) {
400 this->next_->stop_complex();
401 }
402 }
403
405 if (this->next_ == nullptr)
406 return false;
407 return this->next_->is_running();
408 }
409
410 Action<Ts...> *next_{nullptr};
411
415};
416
417template<typename... Ts> class ActionList {
418 public:
419 void add_action(Action<Ts...> *action) {
420 if (this->actions_end_ == nullptr) {
421 this->actions_begin_ = action;
422 } else {
423 this->actions_end_->next_ = action;
424 }
425 this->actions_end_ = action;
426 }
427 void add_actions(const std::initializer_list<Action<Ts...> *> &actions) {
428 for (auto *action : actions) {
429 this->add_action(action);
430 }
431 }
432 void play(const Ts &...x) {
433 if (this->actions_begin_ != nullptr)
434 this->actions_begin_->play_complex(x...);
435 }
436 void play_tuple(const std::tuple<Ts...> &tuple) {
437 this->play_tuple_(tuple, std::make_index_sequence<sizeof...(Ts)>{});
438 }
439 void stop() {
440 if (this->actions_begin_ != nullptr)
442 }
443 bool empty() const { return this->actions_begin_ == nullptr; }
444
446 bool is_running() {
447 if (this->actions_begin_ == nullptr)
448 return false;
449 return this->actions_begin_->is_running();
450 }
453 if (this->actions_begin_ == nullptr)
454 return 0;
455 return this->actions_begin_->num_running_total();
456 }
457
458 protected:
459 template<size_t... S> void play_tuple_(const std::tuple<Ts...> &tuple, std::index_sequence<S...> /*unused*/) {
460 this->play(std::get<S>(tuple)...);
461 }
462
463 Action<Ts...> *actions_begin_{nullptr};
464 Action<Ts...> *actions_end_{nullptr};
465};
466
467template<typename... Ts> class Automation {
468 public:
470
471 void add_action(Action<Ts...> *action) { this->actions_.add_action(action); }
472 void add_actions(const std::initializer_list<Action<Ts...> *> &actions) { this->actions_.add_actions(actions); }
473
474 void stop() { this->actions_.stop(); }
475
476 void trigger(const Ts &...x) { this->actions_.play(x...); }
477
478 bool is_running() { return this->actions_.is_running(); }
479
481 int num_running() { return this->actions_.num_running(); }
482
483 protected:
486};
487
488} // namespace esphome
virtual bool is_running()
Check if this or any of the following actions are currently running.
Definition automation.h:366
Action< Ts... > * next_
Definition automation.h:410
void play_next_(const Ts &...x)
Definition automation.h:382
virtual void stop_complex()
Definition automation.h:358
virtual void play(const Ts &...x)=0
void play_next_tuple_(const std::tuple< Ts... > &tuple)
Definition automation.h:393
virtual void stop()
Definition automation.h:397
bool is_running_next_()
Definition automation.h:404
int num_running_
The number of instances of this sequence in the list of actions that is currently being executed.
Definition automation.h:414
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:370
void play_next_tuple_(const std::tuple< Ts... > &tuple, std::index_sequence< S... >)
Definition automation.h:390
virtual void play_complex(const Ts &...x)
Definition automation.h:353
void add_action(Action< Ts... > *action)
Definition automation.h:419
Action< Ts... > * actions_end_
Definition automation.h:464
void play(const Ts &...x)
Definition automation.h:432
void play_tuple(const std::tuple< Ts... > &tuple)
Definition automation.h:436
bool is_running()
Check if any action in this action list is currently running.
Definition automation.h:446
bool empty() const
Definition automation.h:443
void add_actions(const std::initializer_list< Action< Ts... > * > &actions)
Definition automation.h:427
Action< Ts... > * actions_begin_
Definition automation.h:463
void play_tuple_(const std::tuple< Ts... > &tuple, std::index_sequence< S... >)
Definition automation.h:459
int num_running()
Return the number of actions in this action list that are currently running.
Definition automation.h:452
void add_action(Action< Ts... > *action)
Definition automation.h:471
void trigger(const Ts &...x)
Definition automation.h:476
Trigger< Ts... > * trigger_
Definition automation.h:484
int num_running()
Return the number of actions in the action part of this automation that are currently running.
Definition automation.h:481
Automation(Trigger< Ts... > *trigger)
Definition automation.h:469
void add_actions(const std::initializer_list< Action< Ts... > * > &actions)
Definition automation.h:472
ActionList< Ts... > actions_
Definition automation.h:485
Base class for all automation conditions.
Definition automation.h:304
bool check_tuple_(const std::tuple< Ts... > &tuple, std::index_sequence< S... >)
Definition automation.h:315
bool check_tuple(const std::tuple< Ts... > &tuple)
Call check with a tuple of values as parameter.
Definition automation.h:310
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.
Definition string_ref.h:26
TemplatableValue(const TemplatableValue &other)
Definition automation.h:92
bool is_empty() const
Check if the string value is empty without allocating (for std::string specialization).
Definition automation.h:222
std::function< T(X...)> * f_
Definition automation.h:294
TemplatableValue(const char *str)
Definition automation.h:56
TemplatableValue & operator=(TemplatableValue &&other) noexcept
Definition automation.h:137
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:248
enum esphome::TemplatableValue::@186 type_
TemplatableValue(TemplatableValue &&other) noexcept
Definition automation.h:109
T value(X... x) const
Definition automation.h:160
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:213
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:217
TemplatableValue & operator=(const TemplatableValue &other)
Definition automation.h:129
T value_or(X... x, T default_value)
Definition automation.h:203
TemplatableValue(const __FlashStringHelper *str)
Definition automation.h:65
optional< T > optional_value(X... x)
Definition automation.h:196
std::conditional_t< USE_HEAP_STORAGE, T *, T > ValueStorage
Definition automation.h:291
void trigger(const Ts &...x)
Inform the parent automation that the event has triggered.
Definition automation.h:325
Automation< Ts... > * automation_parent_
Definition automation.h:346
void stop_action()
Stop any action connected to this trigger.
Definition automation.h:333
bool is_action_running()
Returns true if any action connected to this trigger is running.
Definition automation.h:339
void set_automation_parent(Automation< Ts... > *automation_parent)
Definition automation.h:330
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string size_t len
Definition helpers.h:817
struct ESPDEPRECATED("Use std::index_sequence instead. Removed in 2026.6.0", "2025.12.0") seq
Definition automation.h:26
uint8_t progmem_read_byte(const uint8_t *addr)
Definition core.cpp:50
uint16_t seq
uint16_t x
Definition tt21100.cpp:5