ESPHome 2025.6.3
Loading...
Searching...
No Matches
application.cpp
Go to the documentation of this file.
2#include "esphome/core/log.h"
4#include "esphome/core/hal.h"
5#include <algorithm>
6
7#ifdef USE_STATUS_LED
9#endif
10
11#ifdef USE_SOCKET_SELECT_SUPPORT
12#include <cerrno>
13
14#ifdef USE_SOCKET_IMPL_LWIP_SOCKETS
15// LWIP sockets implementation
16#include <lwip/sockets.h>
17#elif defined(USE_SOCKET_IMPL_BSD_SOCKETS)
18// BSD sockets implementation
19#ifdef USE_ESP32
20// ESP32 "BSD sockets" are actually LWIP under the hood
21#include <lwip/sockets.h>
22#else
23// True BSD sockets (e.g., host platform)
24#include <sys/select.h>
25#endif
26#endif
27#endif
28
29namespace esphome {
30
31static const char *const TAG = "app";
32
34 if (comp == nullptr) {
35 ESP_LOGW(TAG, "Tried to register null component!");
36 return;
37 }
38
39 for (auto *c : this->components_) {
40 if (comp == c) {
41 ESP_LOGW(TAG, "Component %s already registered! (%p)", c->get_component_source(), c);
42 return;
43 }
44 }
45 this->components_.push_back(comp);
46}
48 ESP_LOGI(TAG, "Running through setup()");
49 ESP_LOGV(TAG, "Sorting components by setup priority");
50 std::stable_sort(this->components_.begin(), this->components_.end(), [](const Component *a, const Component *b) {
51 return a->get_actual_setup_priority() > b->get_actual_setup_priority();
52 });
53
54 for (uint32_t i = 0; i < this->components_.size(); i++) {
55 Component *component = this->components_[i];
56
57 // Update loop_component_start_time_ before calling each component during setup
59 component->call();
61 this->feed_wdt();
62 if (component->can_proceed())
63 continue;
64
65 std::stable_sort(this->components_.begin(), this->components_.begin() + i + 1,
66 [](Component *a, Component *b) { return a->get_loop_priority() > b->get_loop_priority(); });
67
68 do {
69 uint8_t new_app_state = STATUS_LED_WARNING;
70 this->scheduler.call();
71 this->feed_wdt();
72 for (uint32_t j = 0; j <= i; j++) {
73 // Update loop_component_start_time_ right before calling each component
75 this->components_[j]->call();
76 new_app_state |= this->components_[j]->get_component_state();
77 this->app_state_ |= new_app_state;
78 this->feed_wdt();
79 }
80 this->app_state_ = new_app_state;
81 yield();
82 } while (!component->can_proceed());
83 }
84
85 ESP_LOGI(TAG, "setup() finished successfully!");
88}
90 uint8_t new_app_state = 0;
91
92 this->scheduler.call();
93
94 // Get the initial loop time at the start
95 uint32_t last_op_end_time = millis();
96
97 // Feed WDT with time
98 this->feed_wdt(last_op_end_time);
99
100 for (Component *component : this->looping_components_) {
101 // Update the cached time before each component runs
102 this->loop_component_start_time_ = last_op_end_time;
103
104 {
105 this->set_current_component(component);
106 WarnIfComponentBlockingGuard guard{component, last_op_end_time};
107 component->call();
108 // Use the finish method to get the current time as the end time
109 last_op_end_time = guard.finish();
110 }
111 new_app_state |= component->get_component_state();
112 this->app_state_ |= new_app_state;
113 this->feed_wdt(last_op_end_time);
114 }
115 this->app_state_ = new_app_state;
116
117 // Use the last component's end time instead of calling millis() again
118 auto elapsed = last_op_end_time - this->last_loop_;
120 // Even if we overran the loop interval, we still need to select()
121 // to know if any sockets have data ready
122 this->yield_with_select_(0);
123 } else {
124 uint32_t delay_time = this->loop_interval_ - elapsed;
125 uint32_t next_schedule = this->scheduler.next_schedule_in().value_or(delay_time);
126 // next_schedule is max 0.5*delay_time
127 // otherwise interval=0 schedules result in constant looping with almost no sleep
128 next_schedule = std::max(next_schedule, delay_time / 2);
129 delay_time = std::min(next_schedule, delay_time);
130
131 this->yield_with_select_(delay_time);
132 }
133 this->last_loop_ = last_op_end_time;
134
135 if (this->dump_config_at_ < this->components_.size()) {
136 if (this->dump_config_at_ == 0) {
137 ESP_LOGI(TAG, "ESPHome version " ESPHOME_VERSION " compiled on %s", this->compilation_time_);
138#ifdef ESPHOME_PROJECT_NAME
139 ESP_LOGI(TAG, "Project " ESPHOME_PROJECT_NAME " version " ESPHOME_PROJECT_VERSION);
140#endif
141 }
142
143 this->components_[this->dump_config_at_]->call_dump_config();
144 this->dump_config_at_++;
145 }
146}
147
148void IRAM_ATTR HOT Application::feed_wdt(uint32_t time) {
149 static uint32_t last_feed = 0;
150 // Use provided time if available, otherwise get current time
151 uint32_t now = time ? time : millis();
152 // Compare in milliseconds (3ms threshold)
153 if (now - last_feed > 3) {
155 last_feed = now;
156#ifdef USE_STATUS_LED
157 if (status_led::global_status_led != nullptr) {
159 }
160#endif
161 }
162}
164 ESP_LOGI(TAG, "Forcing a reboot");
165 for (auto it = this->components_.rbegin(); it != this->components_.rend(); ++it) {
166 (*it)->on_shutdown();
167 }
168 arch_restart();
169}
171 ESP_LOGI(TAG, "Rebooting safely");
173 teardown_components(TEARDOWN_TIMEOUT_REBOOT_MS);
175 arch_restart();
176}
177
179 for (auto it = this->components_.rbegin(); it != this->components_.rend(); ++it) {
180 (*it)->on_safe_shutdown();
181 }
182 for (auto it = this->components_.rbegin(); it != this->components_.rend(); ++it) {
183 (*it)->on_shutdown();
184 }
185}
186
188 for (auto it = this->components_.rbegin(); it != this->components_.rend(); ++it) {
189 (*it)->on_powerdown();
190 }
191}
192
193void Application::teardown_components(uint32_t timeout_ms) {
194 uint32_t start_time = millis();
195
196 // Copy all components in reverse order using reverse iterators
197 // Reverse order matches the behavior of run_safe_shutdown_hooks() above and ensures
198 // components are torn down in the opposite order of their setup_priority (which is
199 // used to sort components during Application::setup())
200 std::vector<Component *> pending_components(this->components_.rbegin(), this->components_.rend());
201
202 uint32_t now = start_time;
203 while (!pending_components.empty() && (now - start_time) < timeout_ms) {
204 // Feed watchdog during teardown to prevent triggering
205 this->feed_wdt(now);
206
207 // Use iterator to safely erase elements
208 for (auto it = pending_components.begin(); it != pending_components.end();) {
209 if ((*it)->teardown()) {
210 // Component finished teardown, erase it
211 it = pending_components.erase(it);
212 } else {
213 // Component still needs time
214 ++it;
215 }
216 }
217
218 // Give some time for I/O operations if components are still pending
219 if (!pending_components.empty()) {
220 this->yield_with_select_(1);
221 }
222
223 // Update time for next iteration
224 now = millis();
225 }
226
227 if (!pending_components.empty()) {
228 // Note: At this point, connections are either disconnected or in a bad state,
229 // so this warning will only appear via serial rather than being transmitted to clients
230 for (auto *component : pending_components) {
231 ESP_LOGW(TAG, "%s did not complete teardown within %" PRIu32 " ms", component->get_component_source(),
232 timeout_ms);
233 }
234 }
235}
236
238 for (auto *obj : this->components_) {
239 if (obj->has_overridden_loop())
240 this->looping_components_.push_back(obj);
241 }
242}
243
244#ifdef USE_SOCKET_SELECT_SUPPORT
246 // WARNING: This function is NOT thread-safe and must only be called from the main loop
247 // It modifies socket_fds_ and related variables without locking
248 if (fd < 0)
249 return false;
250
251 if (fd >= FD_SETSIZE) {
252 ESP_LOGE(TAG, "Cannot monitor socket fd %d: exceeds FD_SETSIZE (%d)", fd, FD_SETSIZE);
253 ESP_LOGE(TAG, "Socket will not be monitored for data - may cause performance issues!");
254 return false;
255 }
256
257 this->socket_fds_.push_back(fd);
258 this->socket_fds_changed_ = true;
259
260 if (fd > this->max_fd_) {
261 this->max_fd_ = fd;
262 }
263
264 return true;
265}
266
268 // WARNING: This function is NOT thread-safe and must only be called from the main loop
269 // It modifies socket_fds_ and related variables without locking
270 if (fd < 0)
271 return;
272
273 auto it = std::find(this->socket_fds_.begin(), this->socket_fds_.end(), fd);
274 if (it != this->socket_fds_.end()) {
275 // Swap with last element and pop - O(1) removal since order doesn't matter
276 if (it != this->socket_fds_.end() - 1) {
277 std::swap(*it, this->socket_fds_.back());
278 }
279 this->socket_fds_.pop_back();
280 this->socket_fds_changed_ = true;
281
282 // Only recalculate max_fd if we removed the current max
283 if (fd == this->max_fd_) {
284 if (this->socket_fds_.empty()) {
285 this->max_fd_ = -1;
286 } else {
287 // Find new max using std::max_element
288 this->max_fd_ = *std::max_element(this->socket_fds_.begin(), this->socket_fds_.end());
289 }
290 }
291 }
292}
293
295 // This function is thread-safe for reading the result of select()
296 // However, it should only be called after select() has been executed in the main loop
297 // The read_fds_ is only modified by select() in the main loop
298 if (fd < 0 || fd >= FD_SETSIZE)
299 return false;
300
301 return FD_ISSET(fd, &this->read_fds_);
302}
303#endif
304
305void Application::yield_with_select_(uint32_t delay_ms) {
306 // Delay while monitoring sockets. When delay_ms is 0, always yield() to ensure other tasks run
307 // since select() with 0 timeout only polls without yielding.
308#ifdef USE_SOCKET_SELECT_SUPPORT
309 if (!this->socket_fds_.empty()) {
310 // Update fd_set if socket list has changed
311 if (this->socket_fds_changed_) {
312 FD_ZERO(&this->base_read_fds_);
313 for (int fd : this->socket_fds_) {
314 if (fd >= 0 && fd < FD_SETSIZE) {
315 FD_SET(fd, &this->base_read_fds_);
316 }
317 }
318 this->socket_fds_changed_ = false;
319 }
320
321 // Copy base fd_set before each select
322 this->read_fds_ = this->base_read_fds_;
323
324 // Convert delay_ms to timeval
325 struct timeval tv;
326 tv.tv_sec = delay_ms / 1000;
327 tv.tv_usec = (delay_ms - tv.tv_sec * 1000) * 1000;
328
329 // Call select with timeout
330#if defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || (defined(USE_ESP32) && defined(USE_SOCKET_IMPL_BSD_SOCKETS))
331 int ret = lwip_select(this->max_fd_ + 1, &this->read_fds_, nullptr, nullptr, &tv);
332#else
333 int ret = ::select(this->max_fd_ + 1, &this->read_fds_, nullptr, nullptr, &tv);
334#endif
335
336 // Process select() result:
337 // ret < 0: error (except EINTR which is normal)
338 // ret > 0: socket(s) have data ready - normal and expected
339 // ret == 0: timeout occurred - normal and expected
340 if (ret < 0 && errno != EINTR) {
341 // Actual error - log and fall back to delay
342 ESP_LOGW(TAG, "select() failed with errno %d", errno);
343 delay(delay_ms);
344 }
345 // When delay_ms is 0, we need to yield since select(0) doesn't yield
346 if (delay_ms == 0) {
347 yield();
348 }
349 } else {
350 // No sockets registered, use regular delay
351 delay(delay_ms);
352 }
353#else
354 // No select support, use regular delay
355 delay(delay_ms);
356#endif
357}
358
359Application App; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
360
361} // namespace esphome
void setup()
Set up all the registered components. Call this at the end of your setup() function.
void set_current_component(Component *component)
bool is_socket_ready(int fd) const
Check if there's data available on a socket without blocking This function is thread-safe for reading...
std::vector< int > socket_fds_
std::vector< Component * > components_
uint32_t loop_component_start_time_
void teardown_components(uint32_t timeout_ms)
Teardown all components with a timeout.
const char * compilation_time_
std::vector< Component * > looping_components_
void feed_wdt(uint32_t time=0)
void loop()
Make a loop iteration. Call this in your loop() function.
void unregister_socket_fd(int fd)
bool register_socket_fd(int fd)
Register/unregister a socket file descriptor to be monitored for read events.
void calculate_looping_components_()
void yield_with_select_(uint32_t delay_ms)
Perform a delay while also monitoring socket file descriptors for readiness.
void register_component_(Component *comp)
virtual bool can_proceed()
static bool is_high_frequency()
Check whether the loop is running continuously.
Definition helpers.cpp:685
optional< uint32_t > next_schedule_in()
value_type value_or(U const &v) const
Definition optional.h:93
const char *const TAG
Definition spi.cpp:8
StatusLED * global_status_led
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
void IRAM_ATTR HOT yield()
Definition core.cpp:27
void IRAM_ATTR HOT arch_feed_wdt()
Definition core.cpp:56
const uint8_t STATUS_LED_WARNING
Definition component.cpp:42
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:29
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:28
void arch_restart()
Definition core.cpp:32
Application App
Global storage of Application pointer - only one Application can exist.