ESPHome 2026.3.0
Loading...
Searching...
No Matches
application.cpp
Go to the documentation of this file.
3#include "esphome/core/log.h"
5#include <cstring>
6
7#ifdef USE_ESP8266
8#include <pgmspace.h>
9#endif
10#ifdef USE_ESP32
11#include <esp_chip_info.h>
12#endif
13#ifdef USE_LWIP_FAST_SELECT
15#ifdef USE_ESP32
16#include <freertos/FreeRTOS.h>
17#include <freertos/task.h>
18#else
19#include <FreeRTOS.h>
20#include <task.h>
21#endif
22#endif // USE_LWIP_FAST_SELECT
24#include "esphome/core/hal.h"
25#include <algorithm>
26#include <ranges>
27#ifdef USE_RUNTIME_STATS
29#endif
30
31#ifdef USE_STATUS_LED
33#endif
34
35#if (defined(USE_ESP8266) || defined(USE_RP2040)) && defined(USE_SOCKET_IMPL_LWIP_TCP)
37#endif
38
39#ifdef USE_SOCKET_SELECT_SUPPORT
40#include <cerrno>
41
42#ifdef USE_SOCKET_IMPL_LWIP_SOCKETS
43// LWIP sockets implementation
44#include <lwip/sockets.h>
45#elif defined(USE_SOCKET_IMPL_BSD_SOCKETS)
46// BSD sockets implementation
47#ifdef USE_ESP32
48// ESP32 "BSD sockets" are actually LWIP under the hood
49#include <lwip/sockets.h>
50#else
51// True BSD sockets (e.g., host platform)
52#include <sys/select.h>
53#endif
54#endif
55#endif
56
57namespace esphome {
58
59static const char *const TAG = "app";
60
61// Helper function for insertion sort of components by priority
62// Using insertion sort instead of std::stable_sort saves ~1.3KB of flash
63// by avoiding template instantiations (std::rotate, std::stable_sort, lambdas)
64// IMPORTANT: This sort is stable (preserves relative order of equal elements),
65// which is necessary to maintain user-defined component order for same priority
66template<typename Iterator, float (Component::*GetPriority)() const>
67static void insertion_sort_by_priority(Iterator first, Iterator last) {
68 for (auto it = first + 1; it != last; ++it) {
69 auto key = *it;
70 float key_priority = (key->*GetPriority)();
71 auto j = it - 1;
72
73 // Using '<' (not '<=') ensures stability - equal priority components keep their order
74 while (j >= first && ((*j)->*GetPriority)() < key_priority) {
75 *(j + 1) = *j;
76 j--;
77 }
78 *(j + 1) = key;
79 }
80}
81
83 if (has_loop) {
85 }
86 this->components_.push_back(comp);
87}
89 ESP_LOGI(TAG, "Running through setup()");
90 ESP_LOGV(TAG, "Sorting components by setup priority");
91
92 // Sort by setup priority using our helper function
93 insertion_sort_by_priority<decltype(this->components_.begin()), &Component::get_actual_setup_priority>(
94 this->components_.begin(), this->components_.end());
95
96 // Initialize looping_components_ early so enable_pending_loops_() works during setup
98
99 for (uint32_t i = 0; i < this->components_.size(); i++) {
100 Component *component = this->components_[i];
101
102 // Update loop_component_start_time_ before calling each component during setup
104 component->call();
105 this->scheduler.process_to_add();
106 this->feed_wdt();
107 if (component->can_proceed())
108 continue;
109
110#ifdef USE_LOOP_PRIORITY
111 // Sort components 0 through i by loop priority
112 insertion_sort_by_priority<decltype(this->components_.begin()), &Component::get_loop_priority>(
113 this->components_.begin(), this->components_.begin() + i + 1);
114#endif
115
116 do {
117 uint8_t new_app_state = STATUS_LED_WARNING;
118 uint32_t now = millis();
119
120 // Process pending loop enables to handle GPIO interrupts during setup
121 this->before_loop_tasks_(now);
122
123 for (uint32_t j = 0; j <= i; j++) {
124 // Update loop_component_start_time_ right before calling each component
126 this->components_[j]->call();
127 new_app_state |= this->components_[j]->get_component_state();
128 this->app_state_ |= new_app_state;
129 this->feed_wdt();
130 }
131
132 this->after_loop_tasks_();
133 this->app_state_ = new_app_state;
134 yield();
135 } while (!component->can_proceed() && !component->is_failed());
136 }
137
138 ESP_LOGI(TAG, "setup() finished successfully!");
139
140#ifdef USE_SETUP_PRIORITY_OVERRIDE
141 // Clear setup priority overrides to free memory
143#endif
144
145#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_LWIP_FAST_SELECT)
146 // Initialize fast select: saves main loop task handle for xTaskNotifyGive wake.
147 // The fast path (rcvevent reads + ulTaskNotifyTake) is used unconditionally
148 // when USE_LWIP_FAST_SELECT is enabled (ESP32 and LibreTiny).
150#endif
151#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) && !defined(USE_LWIP_FAST_SELECT)
152 // Set up wake socket for waking main loop from tasks (platforms without fast select only)
154#endif
155
156 // Ensure all active looping components are in LOOP state.
157 // Components after the last blocking component only got one call() during setup
158 // (CONSTRUCTION→SETUP) and never received the second call() (SETUP→LOOP).
159 // The main loop calls loop() directly, bypassing call()'s state machine.
160 for (uint16_t i = 0; i < this->looping_components_active_end_; i++) {
161 this->looping_components_[i]->set_component_state_(COMPONENT_STATE_LOOP);
162 }
163
164 this->schedule_dump_config();
165}
167 uint8_t new_app_state = 0;
168
169 // Get the initial loop time at the start
170 uint32_t last_op_end_time = millis();
171
172 this->before_loop_tasks_(last_op_end_time);
173
175 this->current_loop_index_++) {
177
178 // Update the cached time before each component runs
179 this->loop_component_start_time_ = last_op_end_time;
180
181 {
182 this->set_current_component(component);
183 WarnIfComponentBlockingGuard guard{component, last_op_end_time};
184 component->loop();
185 // Use the finish method to get the current time as the end time
186 last_op_end_time = guard.finish();
187 }
188 new_app_state |= component->get_component_state();
189 this->app_state_ |= new_app_state;
190 this->feed_wdt(last_op_end_time);
191 }
192
193 this->after_loop_tasks_();
194 this->app_state_ = new_app_state;
195
196#ifdef USE_RUNTIME_STATS
197 // Process any pending runtime stats printing after all components have run
198 // This ensures stats printing doesn't affect component timing measurements
199 if (global_runtime_stats != nullptr) {
201 }
202#endif
203
204 // Use the last component's end time instead of calling millis() again
205 auto elapsed = last_op_end_time - this->last_loop_;
207 // Even if we overran the loop interval, we still need to select()
208 // to know if any sockets have data ready
209 this->yield_with_select_(0);
210 } else {
211 uint32_t delay_time = this->loop_interval_ - elapsed;
212 uint32_t next_schedule = this->scheduler.next_schedule_in(last_op_end_time).value_or(delay_time);
213 // next_schedule is max 0.5*delay_time
214 // otherwise interval=0 schedules result in constant looping with almost no sleep
215 next_schedule = std::max(next_schedule, delay_time / 2);
216 delay_time = std::min(next_schedule, delay_time);
217
218 this->yield_with_select_(delay_time);
219 }
220 this->last_loop_ = last_op_end_time;
221
222 if (this->dump_config_at_ < this->components_.size()) {
223 this->process_dump_config_();
224 }
225}
226
227void Application::process_dump_config_() {
228 if (this->dump_config_at_ == 0) {
229 char build_time_str[Application::BUILD_TIME_STR_SIZE];
230 this->get_build_time_string(build_time_str);
231 ESP_LOGI(TAG, "ESPHome version " ESPHOME_VERSION " compiled on %s", build_time_str);
232#ifdef ESPHOME_PROJECT_NAME
233 ESP_LOGI(TAG, "Project " ESPHOME_PROJECT_NAME " version " ESPHOME_PROJECT_VERSION);
234#endif
235#ifdef USE_ESP32
236 esp_chip_info_t chip_info;
237 esp_chip_info(&chip_info);
238 ESP_LOGI(TAG, "ESP32 Chip: %s rev%d.%d, %d core(s)", ESPHOME_VARIANT, chip_info.revision / 100,
239 chip_info.revision % 100, chip_info.cores);
240#if defined(USE_ESP32_VARIANT_ESP32) && !defined(USE_ESP32_MIN_CHIP_REVISION_SET)
241 // Suggest optimization for chips that don't need the PSRAM cache workaround
242 if (chip_info.revision >= 300) {
243#ifdef USE_PSRAM
244 ESP_LOGW(TAG, "Set minimum_chip_revision: \"%d.%d\" to save ~10KB IRAM", chip_info.revision / 100,
245 chip_info.revision % 100);
246#else
247 ESP_LOGW(TAG, "Set minimum_chip_revision: \"%d.%d\" to reduce binary size", chip_info.revision / 100,
248 chip_info.revision % 100);
249#endif
250 }
251#endif
252#endif
253 }
254
255 this->components_[this->dump_config_at_]->call_dump_config_();
256 this->dump_config_at_++;
257}
258
260 static uint32_t last_feed = 0;
261 // Use provided time if available, otherwise get current time
262 uint32_t now = time ? time : millis();
263 // Compare in milliseconds (3ms threshold)
264 if (now - last_feed > 3) {
266 last_feed = now;
267#ifdef USE_STATUS_LED
268 if (status_led::global_status_led != nullptr) {
270 }
271#endif
272 }
273}
275 ESP_LOGI(TAG, "Forcing a reboot");
276 for (auto &component : std::ranges::reverse_view(this->components_)) {
277 component->on_shutdown();
278 }
279 arch_restart();
280}
282 ESP_LOGI(TAG, "Rebooting safely");
284 teardown_components(TEARDOWN_TIMEOUT_REBOOT_MS);
286 arch_restart();
287}
288
290 for (auto &component : std::ranges::reverse_view(this->components_)) {
291 component->on_safe_shutdown();
292 }
293 for (auto &component : std::ranges::reverse_view(this->components_)) {
294 component->on_shutdown();
295 }
296}
297
299 for (auto &component : std::ranges::reverse_view(this->components_)) {
300 component->on_powerdown();
301 }
302}
303
305 uint32_t start_time = millis();
306
307 // Use a StaticVector instead of std::vector to avoid heap allocation
308 // since we know the actual size at compile time
310
311 // Copy all components in reverse order
312 // Reverse order matches the behavior of run_safe_shutdown_hooks() above and ensures
313 // components are torn down in the opposite order of their setup_priority (which is
314 // used to sort components during Application::setup())
315 size_t num_components = this->components_.size();
316 for (size_t i = 0; i < num_components; ++i) {
317 pending_components[i] = this->components_[num_components - 1 - i];
318 }
319
320 uint32_t now = start_time;
321 size_t pending_count = num_components;
322
323 // Teardown Algorithm
324 // ==================
325 // We iterate through pending components, calling teardown() on each.
326 // Components that return false (need more time) are copied forward
327 // in the array. Components that return true (finished) are skipped.
328 //
329 // The compaction happens in-place during iteration:
330 // - still_pending tracks the write position (where to put next pending component)
331 // - i tracks the read position (which component we're testing)
332 // - When teardown() returns false, we copy component[i] to component[still_pending]
333 // - When teardown() returns true, we just skip it (don't increment still_pending)
334 //
335 // Example with 4 components where B can teardown immediately:
336 //
337 // Start:
338 // pending_components: [A, B, C, D]
339 // pending_count: 4 ^----------^
340 //
341 // Iteration 1:
342 // i=0: A needs more time → keep at pos 0 (no copy needed)
343 // i=1: B finished → skip
344 // i=2: C needs more time → copy to pos 1
345 // i=3: D needs more time → copy to pos 2
346 //
347 // After iteration 1:
348 // pending_components: [A, C, D | D]
349 // pending_count: 3 ^--------^
350 //
351 // Iteration 2:
352 // i=0: A finished → skip
353 // i=1: C needs more time → copy to pos 0
354 // i=2: D finished → skip
355 //
356 // After iteration 2:
357 // pending_components: [C | C, D, D] (positions 1-3 have old values)
358 // pending_count: 1 ^--^
359
360 while (pending_count > 0 && (now - start_time) < timeout_ms) {
361 // Feed watchdog during teardown to prevent triggering
362 this->feed_wdt(now);
363
364 // Process components and compact the array, keeping only those still pending
365 size_t still_pending = 0;
366 for (size_t i = 0; i < pending_count; ++i) {
367 if (!pending_components[i]->teardown()) {
368 // Component still needs time, copy it forward
369 if (still_pending != i) {
370 pending_components[still_pending] = pending_components[i];
371 }
372 ++still_pending;
373 }
374 // Component finished teardown, skip it (don't increment still_pending)
375 }
376 pending_count = still_pending;
377
378 // Give some time for I/O operations if components are still pending
379 if (pending_count > 0) {
380 this->yield_with_select_(1);
381 }
382
383 // Update time for next iteration
384 now = millis();
385 }
386
387 if (pending_count > 0) {
388 // Note: At this point, connections are either disconnected or in a bad state,
389 // so this warning will only appear via serial rather than being transmitted to clients
390 for (size_t i = 0; i < pending_count; ++i) {
391 ESP_LOGW(TAG, "%s did not complete teardown within %" PRIu32 " ms",
392 LOG_STR_ARG(pending_components[i]->get_component_log_str()), timeout_ms);
393 }
394 }
395}
396
398 // FixedVector capacity was pre-initialized by codegen with the exact count
399 // of components that override loop(), computed at C++ compile time.
400
401 // Add all components with loop override that aren't already LOOP_DONE
402 // Some components (like logger) may call disable_loop() during initialization
403 // before setup runs, so we need to respect their LOOP_DONE state
405
407
408 // Then add any components that are already LOOP_DONE to the inactive section
409 // This handles components that called disable_loop() during initialization
411}
412
414 for (auto *obj : this->components_) {
415 if (obj->has_overridden_loop() &&
416 ((obj->get_component_state() & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP_DONE) == match_loop_done) {
417 this->looping_components_.push_back(obj);
418 }
419 }
420}
421
423 // This method must be reentrant - components can disable themselves during their own loop() call
424 // Linear search to find component in active section
425 // Most configs have 10-30 looping components (30 is on the high end)
426 // O(n) is acceptable here as we optimize for memory, not complexity
427 for (uint16_t i = 0; i < this->looping_components_active_end_; i++) {
428 if (this->looping_components_[i] == component) {
429 // Move last active component to this position
430 this->looping_components_active_end_--;
431 if (i != this->looping_components_active_end_) {
432 std::swap(this->looping_components_[i], this->looping_components_[this->looping_components_active_end_]);
433
434 // If we're currently iterating and just swapped the current position
435 if (this->in_loop_ && i == this->current_loop_index_) {
436 // Decrement so we'll process the swapped component next
437 this->current_loop_index_--;
438 // Update the loop start time to current time so the swapped component
439 // gets correct timing instead of inheriting stale timing.
440 // This prevents integer underflow in timing calculations by ensuring
441 // the swapped component starts with a fresh timing reference, avoiding
442 // errors caused by stale or wrapped timing values.
444 }
445 }
446 return;
447 }
448 }
449}
450
452 // Helper to move component from inactive to active section
453 if (index != this->looping_components_active_end_) {
454 std::swap(this->looping_components_[index], this->looping_components_[this->looping_components_active_end_]);
455 }
457}
458
460 // This method is only called when component state is LOOP_DONE, so we know
461 // the component must be in the inactive section (if it exists in looping_components_)
462 // Only search the inactive portion for better performance
463 // With typical 0-5 inactive components, O(k) is much faster than O(n)
464 const uint16_t size = this->looping_components_.size();
465 for (uint16_t i = this->looping_components_active_end_; i < size; i++) {
466 if (this->looping_components_[i] == component) {
467 // Found in inactive section - move to active
469 return;
470 }
471 }
472 // Component not found in looping_components_ - this is normal for components
473 // that don't have loop() or were not included in the partitioned vector
474}
475
477 // Process components that requested enable_loop from ISR context
478 // Only iterate through inactive looping_components_ (typically 0-5) instead of all components
479 //
480 // Race condition handling:
481 // 1. We check if component is already in LOOP state first - if so, just clear the flag
482 // This handles reentrancy where enable_loop() was called between ISR and processing
483 // 2. We only clear pending_enable_loop_ after checking state, preventing lost requests
484 // 3. If any components aren't in LOOP_DONE state, we set has_pending_enable_loop_requests_
485 // back to true to ensure we check again next iteration
486 // 4. ISRs can safely set flags at any time - worst case is we process them next iteration
487 // 5. The global flag (has_pending_enable_loop_requests_) is cleared before this method,
488 // so any ISR that fires during processing will be caught in the next loop
489 const uint16_t size = this->looping_components_.size();
490 bool has_pending = false;
491
492 for (uint16_t i = this->looping_components_active_end_; i < size; i++) {
494 if (!component->pending_enable_loop_) {
495 continue; // Skip components without pending requests
496 }
497
498 // Check current state
500
501 // If already in LOOP state, nothing to do - clear flag and continue
503 component->pending_enable_loop_ = false;
504 continue;
505 }
506
507 // If not in LOOP_DONE state, can't enable yet - keep flag set
509 has_pending = true; // Keep tracking this component
510 continue; // Keep the flag set - try again next iteration
511 }
512
513 // Clear the pending flag and enable the loop
514 component->pending_enable_loop_ = false;
515 ESP_LOGVV(TAG, "%s loop enabled from ISR", LOG_STR_ARG(component->get_component_log_str()));
516 component->set_component_state_(COMPONENT_STATE_LOOP);
517
518 // Move to active section
520 }
521
522 // If we couldn't process some requests, ensure we check again next iteration
523 if (has_pending) {
525 }
526}
527
529#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) && !defined(USE_LWIP_FAST_SELECT)
530 // Drain wake notifications first to clear socket for next wake
532#endif
533
534 // Process scheduled tasks
535 this->scheduler.call(loop_start_time);
536
537 // Feed the watchdog timer
538 this->feed_wdt(loop_start_time);
539
540 // Process any pending enable_loop requests from ISRs
541 // This must be done before marking in_loop_ = true to avoid race conditions
543 // Clear flag BEFORE processing to avoid race condition
544 // If ISR sets it during processing, we'll catch it next loop iteration
545 // This is safe because:
546 // 1. Each component has its own pending_enable_loop_ flag that we check
547 // 2. If we can't process a component (wrong state), enable_pending_loops_()
548 // will set this flag back to true
549 // 3. Any new ISR requests during processing will set the flag again
551 this->enable_pending_loops_();
552 }
553
554 // Mark that we're in the loop for safe reentrant modifications
555 this->in_loop_ = true;
556}
557
559 // Clear the in_loop_ flag to indicate we're done processing components
560 this->in_loop_ = false;
561}
562
563#ifdef USE_LWIP_FAST_SELECT
564bool Application::register_socket(struct lwip_sock *sock) {
565 // It modifies monitored_sockets_ without locking — must only be called from the main loop.
566 if (sock == nullptr)
567 return false;
569 this->monitored_sockets_.push_back(sock);
570 return true;
571}
572
573void Application::unregister_socket(struct lwip_sock *sock) {
574 // It modifies monitored_sockets_ without locking — must only be called from the main loop.
575 for (size_t i = 0; i < this->monitored_sockets_.size(); i++) {
576 if (this->monitored_sockets_[i] != sock)
577 continue;
578
579 // Swap with last element and pop - O(1) removal since order doesn't matter.
580 // No need to unhook the netconn callback — all LwIP sockets share the same
581 // static event_callback, and the socket will be closed by the caller.
582 if (i < this->monitored_sockets_.size() - 1)
583 this->monitored_sockets_[i] = this->monitored_sockets_.back();
584 this->monitored_sockets_.pop_back();
585 return;
586 }
587}
588#elif defined(USE_SOCKET_SELECT_SUPPORT)
590 // WARNING: This function is NOT thread-safe and must only be called from the main loop
591 // It modifies socket_fds_ and related variables without locking
592 if (fd < 0)
593 return false;
594
595#ifndef USE_ESP32
596 // Only check on non-ESP32 platforms
597 // On ESP32 (both Arduino and ESP-IDF), CONFIG_LWIP_MAX_SOCKETS is always <= FD_SETSIZE by design
598 // (LWIP_SOCKET_OFFSET = FD_SETSIZE - CONFIG_LWIP_MAX_SOCKETS per lwipopts.h)
599 // Other platforms may not have this guarantee
600 if (fd >= FD_SETSIZE) {
601 ESP_LOGE(TAG, "fd %d exceeds FD_SETSIZE %d", fd, FD_SETSIZE);
602 return false;
603 }
604#endif
605
606 this->socket_fds_.push_back(fd);
607 this->socket_fds_changed_ = true;
608 if (fd > this->max_fd_) {
609 this->max_fd_ = fd;
610 }
611
612 return true;
613}
614
616 // WARNING: This function is NOT thread-safe and must only be called from the main loop
617 // It modifies socket_fds_ and related variables without locking
618 if (fd < 0)
619 return;
620
621 for (size_t i = 0; i < this->socket_fds_.size(); i++) {
622 if (this->socket_fds_[i] != fd)
623 continue;
624
625 // Swap with last element and pop - O(1) removal since order doesn't matter.
626 if (i < this->socket_fds_.size() - 1)
627 this->socket_fds_[i] = this->socket_fds_.back();
628 this->socket_fds_.pop_back();
629 this->socket_fds_changed_ = true;
630 // Only recalculate max_fd if we removed the current max
631 if (fd == this->max_fd_) {
632 this->max_fd_ = -1;
633 for (int sock_fd : this->socket_fds_) {
634 if (sock_fd > this->max_fd_)
635 this->max_fd_ = sock_fd;
636 }
637 }
638 return;
639 }
640}
641
642#endif
643
645 // Delay while monitoring sockets. When delay_ms is 0, always yield() to ensure other tasks run.
646#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_LWIP_FAST_SELECT)
647 // Fast path (ESP32/LibreTiny): reads rcvevent directly from cached lwip_sock pointers.
648 // Safe because this runs on the main loop which owns socket lifetime (create, read, close).
649 if (delay_ms == 0) [[unlikely]] {
650 yield();
651 return;
652 }
653
654 // Check if any socket already has pending data before sleeping.
655 // If a socket still has unread data (rcvevent > 0) but the task notification was already
656 // consumed, ulTaskNotifyTake would block until timeout — adding up to delay_ms latency.
657 // This scan preserves select() semantics: return immediately when any fd is ready.
658 for (struct lwip_sock *sock : this->monitored_sockets_) {
659 if (esphome_lwip_socket_has_data(sock)) {
660 yield();
661 return;
662 }
663 }
664
665 // Sleep with instant wake via FreeRTOS task notification.
666 // Woken by: callback wrapper (socket data arrives), wake_loop_threadsafe() (other tasks), or timeout.
667 // Without USE_WAKE_LOOP_THREADSAFE, only hooked socket callbacks wake the task —
668 // background tasks won't call wake, so this degrades to a pure timeout (same as old select path).
669 ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(delay_ms));
670
671#elif defined(USE_SOCKET_SELECT_SUPPORT)
672 // Fallback select() path (host platform and any future platforms without fast select).
673 // ESP32 and LibreTiny are excluded by the #if above — they use the fast path.
674 if (!this->socket_fds_.empty()) [[likely]] {
675 // Update fd_set if socket list has changed
676 if (this->socket_fds_changed_) [[unlikely]] {
677 FD_ZERO(&this->base_read_fds_);
678 // fd bounds are validated in register_socket_fd()
679 for (int fd : this->socket_fds_) {
680 FD_SET(fd, &this->base_read_fds_);
681 }
682 this->socket_fds_changed_ = false;
683 }
684
685 // Copy base fd_set before each select
686 this->read_fds_ = this->base_read_fds_;
687
688 // Convert delay_ms to timeval
689 struct timeval tv;
690 tv.tv_sec = delay_ms / 1000;
691 tv.tv_usec = (delay_ms - tv.tv_sec * 1000) * 1000;
692
693 // Call select with timeout
694#ifdef USE_SOCKET_IMPL_LWIP_SOCKETS
695 int ret = lwip_select(this->max_fd_ + 1, &this->read_fds_, nullptr, nullptr, &tv);
696#else
697 int ret = ::select(this->max_fd_ + 1, &this->read_fds_, nullptr, nullptr, &tv);
698#endif
699
700 // Process select() result:
701 // ret < 0: error (except EINTR which is normal)
702 // ret > 0: socket(s) have data ready - normal and expected
703 // ret == 0: timeout occurred - normal and expected
704 if (ret >= 0 || errno == EINTR) [[likely]] {
705 // Yield if zero timeout since select(0) only polls without yielding
706 if (delay_ms == 0) [[unlikely]] {
707 yield();
708 }
709 return;
710 }
711 // select() error - log and fall through to delay()
712 ESP_LOGW(TAG, "select() failed with errno %d", errno);
713 }
714 // No sockets registered or select() failed - use regular delay
715 delay(delay_ms);
716#elif (defined(USE_ESP8266) || defined(USE_RP2040)) && defined(USE_SOCKET_IMPL_LWIP_TCP)
717 // No select support but can wake on socket activity
718 // ESP8266: via esp_schedule()
719 // RP2040: via __sev()/__wfe() hardware sleep/wake
720 socket::socket_delay(delay_ms);
721#else
722 // No select support, use regular delay
723 delay(delay_ms);
724#endif
725}
726
727// App storage — asm label shares the linker symbol with "extern Application App".
728// char[] is trivially destructible, so no __cxa_atexit or destructor chain is emitted.
729// Constructed via placement new in the generated setup().
730#ifndef __GXX_ABI_VERSION
731#error "Application placement new requires Itanium C++ ABI (GCC/Clang)"
732#endif
733static_assert(std::is_default_constructible<Application>::value, "Application must be default-constructible");
734// __USER_LABEL_PREFIX__ is "_" on Mach-O (macOS) and empty on ELF (embedded targets).
735// String literal concatenation produces the correct platform-specific mangled symbol.
736// Two-level macro needed: # stringifies before expansion, so the
737// indirection forces __USER_LABEL_PREFIX__ to expand first.
738#define ESPHOME_STRINGIFY_IMPL_(x) #x
739#define ESPHOME_STRINGIFY_(x) ESPHOME_STRINGIFY_IMPL_(x)
740// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
741alignas(Application) char app_storage[sizeof(Application)] asm(
742 ESPHOME_STRINGIFY_(__USER_LABEL_PREFIX__) "_ZN7esphome3AppE");
743#undef ESPHOME_STRINGIFY_
744#undef ESPHOME_STRINGIFY_IMPL_
745
746#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
747
748#ifdef USE_LWIP_FAST_SELECT
750 // Direct FreeRTOS task notification — <1 us, task context only (NOT ISR-safe)
752}
753#else // !USE_LWIP_FAST_SELECT
754
756 // Create UDP socket for wake notifications
757 this->wake_socket_fd_ = lwip_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
758 if (this->wake_socket_fd_ < 0) {
759 ESP_LOGW(TAG, "Wake socket create failed: %d", errno);
760 return;
761 }
762
763 // Bind to loopback with auto-assigned port
764 struct sockaddr_in addr = {};
765 addr.sin_family = AF_INET;
766 addr.sin_addr.s_addr = lwip_htonl(INADDR_LOOPBACK);
767 addr.sin_port = 0; // Auto-assign port
768
769 if (lwip_bind(this->wake_socket_fd_, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
770 ESP_LOGW(TAG, "Wake socket bind failed: %d", errno);
771 lwip_close(this->wake_socket_fd_);
772 this->wake_socket_fd_ = -1;
773 return;
774 }
775
776 // Get the assigned address and connect to it
777 // Connecting a UDP socket allows using send() instead of sendto() for better performance
778 struct sockaddr_in wake_addr;
779 socklen_t len = sizeof(wake_addr);
780 if (lwip_getsockname(this->wake_socket_fd_, (struct sockaddr *) &wake_addr, &len) < 0) {
781 ESP_LOGW(TAG, "Wake socket address failed: %d", errno);
782 lwip_close(this->wake_socket_fd_);
783 this->wake_socket_fd_ = -1;
784 return;
785 }
786
787 // Connect to self (loopback) - allows using send() instead of sendto()
788 // After connect(), no need to store wake_addr - the socket remembers it
789 if (lwip_connect(this->wake_socket_fd_, (struct sockaddr *) &wake_addr, sizeof(wake_addr)) < 0) {
790 ESP_LOGW(TAG, "Wake socket connect failed: %d", errno);
791 lwip_close(this->wake_socket_fd_);
792 this->wake_socket_fd_ = -1;
793 return;
794 }
795
796 // Set non-blocking mode
797 int flags = lwip_fcntl(this->wake_socket_fd_, F_GETFL, 0);
798 lwip_fcntl(this->wake_socket_fd_, F_SETFL, flags | O_NONBLOCK);
799
800 // Register with application's select() loop
801 if (!this->register_socket_fd(this->wake_socket_fd_)) {
802 ESP_LOGW(TAG, "Wake socket register failed");
803 lwip_close(this->wake_socket_fd_);
804 this->wake_socket_fd_ = -1;
805 return;
806 }
807}
808
810 // Called from FreeRTOS task context when events need immediate processing
811 // Wakes up lwip_select() in main loop by writing to connected loopback socket
812 if (this->wake_socket_fd_ >= 0) {
813 const char dummy = 1;
814 // Non-blocking send - if it fails (unlikely), select() will wake on timeout anyway
815 // No error checking needed: we control both ends of this loopback socket.
816 // This is safe to call from FreeRTOS tasks - send() is thread-safe in lwip
817 // Socket is already connected to loopback address, so send() is faster than sendto()
818 lwip_send(this->wake_socket_fd_, &dummy, 1, 0);
819 }
820}
821#endif // USE_LWIP_FAST_SELECT
822
823#endif // defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
824
825void Application::get_build_time_string(std::span<char, BUILD_TIME_STR_SIZE> buffer) {
826 ESPHOME_strncpy_P(buffer.data(), ESPHOME_BUILD_TIME_STR, buffer.size());
827 buffer[buffer.size() - 1] = '\0';
828}
829
830void Application::get_comment_string(std::span<char, ESPHOME_COMMENT_SIZE_MAX> buffer) {
831 ESPHOME_strncpy_P(buffer.data(), ESPHOME_COMMENT_STR, ESPHOME_COMMENT_SIZE);
832 buffer[ESPHOME_COMMENT_SIZE - 1] = '\0';
833}
834
835uint32_t Application::get_config_hash() { return ESPHOME_CONFIG_HASH; }
836
837uint32_t Application::get_config_version_hash() { return fnv1a_hash_extend(ESPHOME_CONFIG_HASH, ESPHOME_VERSION); }
838
839time_t Application::get_build_time() { return ESPHOME_BUILD_TIME; }
840
841} // namespace esphome
std::vector< struct lwip_sock * > monitored_sockets_
void setup()
Reserve space for components to avoid memory fragmentation.
void wake_loop_threadsafe()
Wake the main event loop from another FreeRTOS task.
uint16_t looping_components_active_end_
void set_current_component(Component *component)
bool register_socket(struct lwip_sock *sock)
Register/unregister a socket to be monitored for read events.
static constexpr size_t BUILD_TIME_STR_SIZE
Size of buffer required for build time string (including null terminator)
uint32_t get_config_hash()
Get the config hash as a 32-bit integer.
std::vector< int > socket_fds_
StaticVector< Component *, ESPHOME_COMPONENT_COUNT > components_
void get_build_time_string(std::span< char, BUILD_TIME_STR_SIZE > buffer)
Copy the build time string into the provided buffer Buffer must be BUILD_TIME_STR_SIZE bytes (compile...
void feed_wdt(uint32_t time=0)
void drain_wake_notifications_()
void enable_component_loop_(Component *component)
uint32_t loop_component_start_time_
void disable_component_loop_(Component *component)
void activate_looping_component_(uint16_t index)
void teardown_components(uint32_t timeout_ms)
Teardown all components with a timeout.
FixedVector< Component * > looping_components_
void add_looping_components_by_state_(bool match_loop_done)
volatile bool has_pending_enable_loop_requests_
void get_comment_string(std::span< char, ESPHOME_COMMENT_SIZE_MAX > buffer)
Copy the comment string into the provided buffer.
uint16_t current_loop_index_
time_t get_build_time()
Get the build time as a Unix timestamp.
void before_loop_tasks_(uint32_t loop_start_time)
void loop()
Make a loop iteration. Call this in your loop() function.
void unregister_socket_fd(int fd)
uint32_t get_config_version_hash()
Get the config hash extended with ESPHome version.
bool register_socket_fd(int fd)
Fallback select() path: monitors file descriptors.
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_impl_(Component *comp, bool has_loop)
void unregister_socket(struct lwip_sock *sock)
float get_actual_setup_priority() const
uint8_t get_component_state() const
Definition component.h:145
virtual bool can_proceed()
virtual float get_loop_priority() const
priority of loop().
Definition component.cpp:89
uint8_t component_state_
State of this component - each bit has a purpose: Bits 0-2: Component state (0x00=CONSTRUCTION,...
Definition component.h:528
static bool is_high_frequency()
Check whether the loop is running continuously.
Definition helpers.h:1840
Minimal static vector - saves memory by avoiding std::vector overhead.
Definition helpers.h:209
size_t size() const
Definition helpers.h:269
void process_pending_stats(uint32_t current_time)
const Component * component
Definition component.cpp:37
uint16_t flags
bool state
Definition fan.h:2
uint32_t socklen_t
Definition headers.h:99
void esphome_lwip_fast_select_init(void)
Initialize fast select — must be called from the main loop task during setup().
void esphome_lwip_hook_socket(struct lwip_sock *sock)
Hook a socket's netconn callback to notify the main loop task on receive events.
void esphome_lwip_wake_main_loop(void)
Wake the main loop task from another FreeRTOS task — costs <1 us.
void socket_delay(uint32_t ms)
Delay that can be woken early by socket activity.
const char *const TAG
Definition spi.cpp:7
StatusLED * global_status_led
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
runtime_stats::RuntimeStatsCollector * global_runtime_stats
constexpr uint8_t COMPONENT_HAS_LOOP
Definition component.h:80
constexpr uint32_t fnv1a_hash_extend(uint32_t hash, const char *str)
Extend a FNV-1a hash with additional string data.
Definition helpers.h:651
constexpr uint8_t COMPONENT_STATE_LOOP
Definition component.h:71
constexpr uint8_t STATUS_LED_WARNING
Definition component.h:77
constexpr uint8_t COMPONENT_STATE_MASK
Definition component.h:68
std::string size_t len
Definition helpers.h:892
size_t size
Definition helpers.h:929
void clear_setup_priority_overrides()
void HOT yield()
Definition core.cpp:25
void HOT arch_feed_wdt()
Definition core.cpp:54
constexpr uint8_t COMPONENT_STATE_LOOP_DONE
Definition component.h:73
void HOT delay(uint32_t ms)
Definition core.cpp:28
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:26
void arch_restart()
Definition core.cpp:31
static void uint32_t
struct in_addr sin_addr
Definition headers.h:67
sa_family_t sin_family
Definition headers.h:65
in_port_t sin_port
Definition headers.h:66