ESPHome 2026.6.2
Loading...
Searching...
No Matches
script.h
Go to the documentation of this file.
1#pragma once
2
3#include <list>
4#include <memory>
5#include <tuple>
10#include "esphome/core/log.h"
11
12namespace esphome::script {
13
15 protected:
16#ifdef USE_STORE_LOG_STR_IN_FLASH
17 void esp_logw_(int line, const __FlashStringHelper *format, const char *param) {
18 esp_log_(ESPHOME_LOG_LEVEL_WARN, line, format, param);
19 }
20 void esp_logd_(int line, const __FlashStringHelper *format, const char *param) {
21 esp_log_(ESPHOME_LOG_LEVEL_DEBUG, line, format, param);
22 }
23 void esp_log_(int level, int line, const __FlashStringHelper *format, const char *param);
24#else
25 void esp_logw_(int line, const char *format, const char *param) {
26 esp_log_(ESPHOME_LOG_LEVEL_WARN, line, format, param);
27 }
28 void esp_logd_(int line, const char *format, const char *param) {
29 esp_log_(ESPHOME_LOG_LEVEL_DEBUG, line, format, param);
30 }
31 void esp_log_(int level, int line, const char *format, const char *param);
32#endif
33};
34
36template<typename... Ts> class Script : public ScriptLogger, public Trigger<Ts...> {
37 public:
42 virtual void execute(Ts...) = 0;
44 virtual bool is_running() { return this->is_action_running(); }
46 virtual void stop() { this->stop_action(); }
47
48 // execute this script using a tuple that contains the arguments
49 void execute_tuple(const std::tuple<Ts...> &tuple) {
50 this->execute_tuple_(tuple, std::make_index_sequence<sizeof...(Ts)>{});
51 }
52
53 // Internal function to give scripts readable names.
54 void set_name(const LogString *name) { name_ = name; }
55
56 protected:
57 template<size_t... S> void execute_tuple_(const std::tuple<Ts...> &tuple, std::index_sequence<S...> /*unused*/) {
58 this->execute(std::get<S>(tuple)...);
59 }
60
61 // Run the action chain with this script's name published as the current source (RAII save/restore,
62 // so nesting composes), so deferred work inside the script is attributed to it in blocking
63 // warnings. Force-inlined to fold into the always-inlined trigger chain (no extra stack frame).
64 inline void run_actions_(const Ts &...x) ESPHOME_ALWAYS_INLINE {
65 ScopedSourceGuard source_guard{this->name_};
66 this->trigger(x...);
67 }
68
69 const LogString *name_{nullptr};
70};
71
77template<typename... Ts> class SingleScript : public Script<Ts...> {
78 public:
79 void execute(Ts... x) override {
80 if (this->is_action_running()) {
81 this->esp_logw_(__LINE__, ESPHOME_LOG_FORMAT("Script '%s' is already running! (mode: single)"),
82 LOG_STR_ARG(this->name_));
83 return;
84 }
85
86 this->run_actions_(x...);
87 }
88};
89
95template<typename... Ts> class RestartScript : public Script<Ts...> {
96 public:
97 void execute(Ts... x) override {
98 if (this->is_action_running()) {
99 this->esp_logd_(__LINE__, ESPHOME_LOG_FORMAT("Script '%s' restarting (mode: restart)"), LOG_STR_ARG(this->name_));
100 this->stop_action();
101 }
102
103 this->run_actions_(x...);
104 }
105};
106
121template<typename... Ts> class QueueingScript : public Script<Ts...>, public Component {
122 public:
123 void execute(Ts... x) override {
124 if (this->is_action_running() || this->num_queued_ > 0) {
125 // num_queued_ is the number of *queued* instances (waiting, not including currently running)
126 // max_runs_ is the maximum *total* instances (running + queued)
127 // So we reject when num_queued_ + 1 >= max_runs_ (queued + running >= max)
128 if (this->num_queued_ + 1 >= this->max_runs_) {
129 this->esp_logw_(__LINE__, ESPHOME_LOG_FORMAT("Script '%s' max instances (running + queued) reached!"),
130 LOG_STR_ARG(this->name_));
131 return;
132 }
133
134 // Initialize queue on first queued item (after capacity check)
135 this->lazy_init_queue_();
136
137 this->esp_logd_(__LINE__, ESPHOME_LOG_FORMAT("Script '%s' queueing new instance (mode: queued)"),
138 LOG_STR_ARG(this->name_));
139 // Ring buffer: write to (queue_front_ + num_queued_) % queue_capacity
140 const size_t queue_capacity = static_cast<size_t>(this->max_runs_ - 1);
141 size_t write_pos = (this->queue_front_ + this->num_queued_) % queue_capacity;
142 // Use std::make_unique to replace the unique_ptr
143 this->var_queue_[write_pos] = std::make_unique<std::tuple<Ts...>>(x...);
144 this->num_queued_++;
145 return;
146 }
147
148 this->run_actions_(x...);
149 // Check if the trigger was immediate and we can continue right away.
150 this->loop();
151 }
152
153 void stop() override {
154 // Clear all queued items to free memory immediately
155 // Resetting the array automatically destroys all unique_ptrs and their contents
156 this->var_queue_.reset();
157 this->num_queued_ = 0;
158 this->queue_front_ = 0;
160 }
161
162 void loop() override {
163 if (this->num_queued_ != 0 && !this->is_action_running()) {
164 // Dequeue: decrement count, move tuple out (frees slot), advance read position
165 this->num_queued_--;
166 const size_t queue_capacity = static_cast<size_t>(this->max_runs_ - 1);
167 auto tuple_ptr = std::move(this->var_queue_[this->queue_front_]);
168 this->queue_front_ = (this->queue_front_ + 1) % queue_capacity;
169 this->trigger_tuple_(*tuple_ptr, std::make_index_sequence<sizeof...(Ts)>{});
170 }
171 }
172
173 void set_max_runs(int max_runs) { max_runs_ = max_runs; }
174
175 protected:
176 // Lazy init queue on first use - avoids setup() ordering issues and saves memory
177 // if script is never executed during this boot cycle
178 inline void lazy_init_queue_() {
179 if (!this->var_queue_) {
180 // Allocate array of max_runs_ - 1 slots for queued items (running item is separate)
181 // unique_ptr array is zero-initialized, so all slots start as nullptr
182 this->var_queue_ = std::make_unique<std::unique_ptr<std::tuple<Ts...>>[]>(this->max_runs_ - 1);
183 }
184 }
185
186 template<size_t... S> void trigger_tuple_(const std::tuple<Ts...> &tuple, std::index_sequence<S...> /*unused*/) {
187 this->run_actions_(std::get<S>(tuple)...);
188 }
189
190 int num_queued_ = 0; // Number of queued instances (not including currently running)
191 int max_runs_ = 0; // Maximum total instances (running + queued)
192 size_t queue_front_ = 0; // Ring buffer read position (next item to execute)
193 std::unique_ptr<std::unique_ptr<std::tuple<Ts...>>[]> var_queue_; // Ring buffer of queued parameters
194};
195
201template<typename... Ts> class ParallelScript : public Script<Ts...> {
202 public:
203 void execute(Ts... x) override {
204 if (this->max_runs_ != 0 && this->automation_parent_->num_running() >= this->max_runs_) {
205 this->esp_logw_(__LINE__, ESPHOME_LOG_FORMAT("Script '%s' maximum number of parallel runs exceeded!"),
206 LOG_STR_ARG(this->name_));
207 return;
208 }
209 this->run_actions_(x...);
210 }
211 void set_max_runs(int max_runs) { max_runs_ = max_runs; }
212
213 protected:
214 int max_runs_ = 0;
215};
216
217template<class S, typename... Ts> class ScriptExecuteAction;
218
219template<class... As, typename... Ts> class ScriptExecuteAction<Script<As...>, Ts...> : public Action<Ts...> {
220 public:
221 ScriptExecuteAction(Script<As...> *script) : script_(script) {}
222
223 using Args = std::tuple<TemplatableFn<As, Ts...>...>;
224
225 template<typename... F> void set_args(F... x) { args_ = Args{x...}; }
226
227 void play(const Ts &...x) override { this->script_->execute_tuple(this->eval_args_(x...)); }
228
229 protected:
230 // NOTE:
231 // `eval_args_impl` functions evaluates `I`th the functions in `args` member.
232 // and then recursively calls `eval_args_impl` for the `I+1`th arg.
233 // if `I` = `N` all args have been stored, and nothing is done.
234
235 template<std::size_t N>
236 void eval_args_impl_(std::tuple<As...> & /*unused*/, std::integral_constant<std::size_t, N> /*unused*/,
237 std::integral_constant<std::size_t, N> /*unused*/, Ts... /*unused*/) {}
238
239 template<std::size_t I, std::size_t N>
240 void eval_args_impl_(std::tuple<As...> &evaled_args, std::integral_constant<std::size_t, I> /*unused*/,
241 std::integral_constant<std::size_t, N> n, Ts... x) {
242 std::get<I>(evaled_args) = std::get<I>(args_).value(x...); // NOTE: evaluate `i`th arg, and store in tuple.
243 eval_args_impl_(evaled_args, std::integral_constant<std::size_t, I + 1>{}, n,
244 x...); // NOTE: recurse to next index.
245 }
246
247 std::tuple<As...> eval_args_(Ts... x) {
248 std::tuple<As...> evaled_args;
249 eval_args_impl_(evaled_args, std::integral_constant<std::size_t, 0>{}, std::tuple_size<Args>{}, x...);
250 return evaled_args;
251 }
252
255};
256
257template<class C, typename... Ts> class ScriptStopAction : public Action<Ts...> {
258 public:
259 ScriptStopAction(C *script) : script_(script) {}
260
261 void play(const Ts &...x) override { this->script_->stop(); }
262
263 protected:
265};
266
267template<class C, typename... Ts> class IsRunningCondition : public Condition<Ts...> {
268 public:
269 explicit IsRunningCondition(C *parent) : parent_(parent) {}
270
271 bool check(const Ts &...x) override { return this->parent_->is_running(); }
272
273 protected:
275};
276
284template<class C, typename... Ts> class ScriptWaitAction : public Action<Ts...>, public Component {
285 public:
286 ScriptWaitAction(C *script) : script_(script) {}
287
288 void setup() override {
289 // Start with loop disabled - only enable when there's work to do
290 // IMPORTANT: Only disable if num_running_ is 0, otherwise play_complex() was already
291 // called before our setup() (e.g., from on_boot trigger at same priority level)
292 // and we must not undo its enable_loop() call
293 if (this->num_running_ == 0) {
294 this->disable_loop();
295 }
296 }
297
298 void play_complex(const Ts &...x) override {
299 this->num_running_++;
300 // Check if we can continue immediately.
301 if (!this->script_->is_running()) {
302 this->play_next_(x...);
303 return;
304 }
305
306 // Store parameters for later execution
307 this->param_queue_.emplace_back(x...);
308 // Enable loop now that we have work to do - don't call loop() synchronously!
309 // Let the event loop call it to avoid reentrancy issues
310 this->enable_loop();
311 }
312
313 void loop() override {
314 if (this->num_running_ == 0)
315 return;
316
317 if (this->script_->is_running())
318 return;
319
320 // Only process ONE queued item per loop iteration
321 // Processing all items in a while loop causes infinite loops because
322 // play_next_() can trigger more items to be queued
323 if (!this->param_queue_.empty()) {
324 auto &params = this->param_queue_.front();
325 this->play_next_tuple_(params, std::make_index_sequence<sizeof...(Ts)>{});
326 this->param_queue_.pop_front();
327 } else {
328 // Queue is now empty - disable loop until next play_complex
329 this->disable_loop();
330 }
331 }
332
333 void play(const Ts &...x) override { /* ignore - see play_complex */
334 }
335
336 void stop() override {
337 this->param_queue_.clear();
338 this->disable_loop();
339 }
340
341 protected:
342 template<size_t... S> void play_next_tuple_(const std::tuple<Ts...> &tuple, std::index_sequence<S...> /*unused*/) {
343 this->play_next_(std::get<S>(tuple)...);
344 }
345
347 std::list<std::tuple<Ts...>> param_queue_;
348};
349
350} // namespace esphome::script
void play_next_(const Ts &...x)
Definition automation.h:518
int num_running()
Return the number of actions in the action part of this automation that are currently running.
Definition automation.h:626
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
RAII guard that publishes a current source (e.g.
Function-pointer-only templatable storage (4 bytes on 32-bit).
Definition automation.h:19
Automation< Ts... > * automation_parent_
Definition automation.h:482
void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE
Definition automation.h:461
bool check(const Ts &...x) override
Definition script.h:271
A script type that executes new instances in parallel.
Definition script.h:201
void execute(Ts... x) override
Definition script.h:203
void set_max_runs(int max_runs)
Definition script.h:211
A script type that queues new instances that are created.
Definition script.h:121
void trigger_tuple_(const std::tuple< Ts... > &tuple, std::index_sequence< S... >)
Definition script.h:186
void execute(Ts... x) override
Definition script.h:123
void set_max_runs(int max_runs)
Definition script.h:173
std::unique_ptr< std::unique_ptr< std::tuple< Ts... > >[]> var_queue_
Definition script.h:193
A script type that restarts scripts from the beginning when a new instance is started.
Definition script.h:95
void execute(Ts... x) override
Definition script.h:97
void eval_args_impl_(std::tuple< As... > &evaled_args, std::integral_constant< std::size_t, I >, std::integral_constant< std::size_t, N > n, Ts... x)
Definition script.h:240
void eval_args_impl_(std::tuple< As... > &, std::integral_constant< std::size_t, N >, std::integral_constant< std::size_t, N >, Ts...)
Definition script.h:236
std::tuple< TemplatableFn< As, Ts... >... > Args
Definition script.h:223
The abstract base class for all script types.
Definition script.h:36
virtual bool is_running()
Check if any instance of this script is currently running.
Definition script.h:44
virtual void execute(Ts...)=0
Execute a new instance of this script.
void execute_tuple(const std::tuple< Ts... > &tuple)
Definition script.h:49
virtual void stop()
Stop all instances of this script.
Definition script.h:46
void run_actions_(const Ts &...x) ESPHOME_ALWAYS_INLINE
Definition script.h:64
void set_name(const LogString *name)
Definition script.h:54
const LogString * name_
Definition script.h:69
void execute_tuple_(const std::tuple< Ts... > &tuple, std::index_sequence< S... >)
Definition script.h:57
void esp_logd_(int line, const char *format, const char *param)
Definition script.h:28
void esp_logd_(int line, const __FlashStringHelper *format, const char *param)
Definition script.h:20
void esp_logw_(int line, const char *format, const char *param)
Definition script.h:25
void esp_logw_(int line, const __FlashStringHelper *format, const char *param)
Definition script.h:17
void esp_log_(int level, int line, const __FlashStringHelper *format, const char *param)
Definition script.cpp:9
void play(const Ts &...x) override
Definition script.h:261
Wait for a script to finish before continuing.
Definition script.h:284
void play(const Ts &...x) override
Definition script.h:333
std::list< std::tuple< Ts... > > param_queue_
Definition script.h:347
void play_next_tuple_(const std::tuple< Ts... > &tuple, std::index_sequence< S... >)
Definition script.h:342
void play_complex(const Ts &...x) override
Definition script.h:298
A script type for which only a single instance at a time is allowed.
Definition script.h:77
void execute(Ts... x) override
Definition script.h:79
const char int line
Definition log.h:74
const char int const __FlashStringHelper * format
Definition log.h:74
uint16_t x
Definition tt21100.cpp:5