ESPHome 2026.3.0-dev
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_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 this->schedule_dump_config();
157}
159 uint8_t new_app_state = 0;
160
161 // Get the initial loop time at the start
162 uint32_t last_op_end_time = millis();
163
164 this->before_loop_tasks_(last_op_end_time);
165
167 this->current_loop_index_++) {
169
170 // Update the cached time before each component runs
171 this->loop_component_start_time_ = last_op_end_time;
172
173 {
174 this->set_current_component(component);
175 WarnIfComponentBlockingGuard guard{component, last_op_end_time};
176 component->call();
177 // Use the finish method to get the current time as the end time
178 last_op_end_time = guard.finish();
179 }
180 new_app_state |= component->get_component_state();
181 this->app_state_ |= new_app_state;
182 this->feed_wdt(last_op_end_time);
183 }
184
185 this->after_loop_tasks_();
186 this->app_state_ = new_app_state;
187
188#ifdef USE_RUNTIME_STATS
189 // Process any pending runtime stats printing after all components have run
190 // This ensures stats printing doesn't affect component timing measurements
191 if (global_runtime_stats != nullptr) {
193 }
194#endif
195
196 // Use the last component's end time instead of calling millis() again
197 auto elapsed = last_op_end_time - this->last_loop_;
199 // Even if we overran the loop interval, we still need to select()
200 // to know if any sockets have data ready
201 this->yield_with_select_(0);
202 } else {
203 uint32_t delay_time = this->loop_interval_ - elapsed;
204 uint32_t next_schedule = this->scheduler.next_schedule_in(last_op_end_time).value_or(delay_time);
205 // next_schedule is max 0.5*delay_time
206 // otherwise interval=0 schedules result in constant looping with almost no sleep
207 next_schedule = std::max(next_schedule, delay_time / 2);
208 delay_time = std::min(next_schedule, delay_time);
209
210 this->yield_with_select_(delay_time);
211 }
212 this->last_loop_ = last_op_end_time;
213
214 if (this->dump_config_at_ < this->components_.size()) {
215 this->process_dump_config_();
216 }
217}
218
219void Application::process_dump_config_() {
220 if (this->dump_config_at_ == 0) {
221 char build_time_str[Application::BUILD_TIME_STR_SIZE];
222 this->get_build_time_string(build_time_str);
223 ESP_LOGI(TAG, "ESPHome version " ESPHOME_VERSION " compiled on %s", build_time_str);
224#ifdef ESPHOME_PROJECT_NAME
225 ESP_LOGI(TAG, "Project " ESPHOME_PROJECT_NAME " version " ESPHOME_PROJECT_VERSION);
226#endif
227#ifdef USE_ESP32
228 esp_chip_info_t chip_info;
229 esp_chip_info(&chip_info);
230 ESP_LOGI(TAG, "ESP32 Chip: %s rev%d.%d, %d core(s)", ESPHOME_VARIANT, chip_info.revision / 100,
231 chip_info.revision % 100, chip_info.cores);
232#if defined(USE_ESP32_VARIANT_ESP32) && !defined(USE_ESP32_MIN_CHIP_REVISION_SET)
233 // Suggest optimization for chips that don't need the PSRAM cache workaround
234 if (chip_info.revision >= 300) {
235#ifdef USE_PSRAM
236 ESP_LOGW(TAG, "Set minimum_chip_revision: \"%d.%d\" to save ~10KB IRAM", chip_info.revision / 100,
237 chip_info.revision % 100);
238#else
239 ESP_LOGW(TAG, "Set minimum_chip_revision: \"%d.%d\" to reduce binary size", chip_info.revision / 100,
240 chip_info.revision % 100);
241#endif
242 }
243#endif
244#endif
245 }
246
247 this->components_[this->dump_config_at_]->call_dump_config_();
248 this->dump_config_at_++;
249}
250
251void HOT Application::feed_wdt(uint32_t time) {
252 static uint32_t last_feed = 0;
253 // Use provided time if available, otherwise get current time
254 uint32_t now = time ? time : millis();
255 // Compare in milliseconds (3ms threshold)
256 if (now - last_feed > 3) {
258 last_feed = now;
259#ifdef USE_STATUS_LED
260 if (status_led::global_status_led != nullptr) {
262 }
263#endif
264 }
265}
267 ESP_LOGI(TAG, "Forcing a reboot");
268 for (auto &component : std::ranges::reverse_view(this->components_)) {
269 component->on_shutdown();
270 }
271 arch_restart();
272}
274 ESP_LOGI(TAG, "Rebooting safely");
276 teardown_components(TEARDOWN_TIMEOUT_REBOOT_MS);
278 arch_restart();
279}
280
282 for (auto &component : std::ranges::reverse_view(this->components_)) {
283 component->on_safe_shutdown();
284 }
285 for (auto &component : std::ranges::reverse_view(this->components_)) {
286 component->on_shutdown();
287 }
288}
289
291 for (auto &component : std::ranges::reverse_view(this->components_)) {
292 component->on_powerdown();
293 }
294}
295
296void Application::teardown_components(uint32_t timeout_ms) {
297 uint32_t start_time = millis();
298
299 // Use a StaticVector instead of std::vector to avoid heap allocation
300 // since we know the actual size at compile time
302
303 // Copy all components in reverse order
304 // Reverse order matches the behavior of run_safe_shutdown_hooks() above and ensures
305 // components are torn down in the opposite order of their setup_priority (which is
306 // used to sort components during Application::setup())
307 size_t num_components = this->components_.size();
308 for (size_t i = 0; i < num_components; ++i) {
309 pending_components[i] = this->components_[num_components - 1 - i];
310 }
311
312 uint32_t now = start_time;
313 size_t pending_count = num_components;
314
315 // Teardown Algorithm
316 // ==================
317 // We iterate through pending components, calling teardown() on each.
318 // Components that return false (need more time) are copied forward
319 // in the array. Components that return true (finished) are skipped.
320 //
321 // The compaction happens in-place during iteration:
322 // - still_pending tracks the write position (where to put next pending component)
323 // - i tracks the read position (which component we're testing)
324 // - When teardown() returns false, we copy component[i] to component[still_pending]
325 // - When teardown() returns true, we just skip it (don't increment still_pending)
326 //
327 // Example with 4 components where B can teardown immediately:
328 //
329 // Start:
330 // pending_components: [A, B, C, D]
331 // pending_count: 4 ^----------^
332 //
333 // Iteration 1:
334 // i=0: A needs more time → keep at pos 0 (no copy needed)
335 // i=1: B finished → skip
336 // i=2: C needs more time → copy to pos 1
337 // i=3: D needs more time → copy to pos 2
338 //
339 // After iteration 1:
340 // pending_components: [A, C, D | D]
341 // pending_count: 3 ^--------^
342 //
343 // Iteration 2:
344 // i=0: A finished → skip
345 // i=1: C needs more time → copy to pos 0
346 // i=2: D finished → skip
347 //
348 // After iteration 2:
349 // pending_components: [C | C, D, D] (positions 1-3 have old values)
350 // pending_count: 1 ^--^
351
352 while (pending_count > 0 && (now - start_time) < timeout_ms) {
353 // Feed watchdog during teardown to prevent triggering
354 this->feed_wdt(now);
355
356 // Process components and compact the array, keeping only those still pending
357 size_t still_pending = 0;
358 for (size_t i = 0; i < pending_count; ++i) {
359 if (!pending_components[i]->teardown()) {
360 // Component still needs time, copy it forward
361 if (still_pending != i) {
362 pending_components[still_pending] = pending_components[i];
363 }
364 ++still_pending;
365 }
366 // Component finished teardown, skip it (don't increment still_pending)
367 }
368 pending_count = still_pending;
369
370 // Give some time for I/O operations if components are still pending
371 if (pending_count > 0) {
372 this->yield_with_select_(1);
373 }
374
375 // Update time for next iteration
376 now = millis();
377 }
378
379 if (pending_count > 0) {
380 // Note: At this point, connections are either disconnected or in a bad state,
381 // so this warning will only appear via serial rather than being transmitted to clients
382 for (size_t i = 0; i < pending_count; ++i) {
383 ESP_LOGW(TAG, "%s did not complete teardown within %" PRIu32 " ms",
384 LOG_STR_ARG(pending_components[i]->get_component_log_str()), timeout_ms);
385 }
386 }
387}
388
390 // FixedVector capacity was pre-initialized by codegen with the exact count
391 // of components that override loop(), computed at C++ compile time.
392
393 // Add all components with loop override that aren't already LOOP_DONE
394 // Some components (like logger) may call disable_loop() during initialization
395 // before setup runs, so we need to respect their LOOP_DONE state
397
399
400 // Then add any components that are already LOOP_DONE to the inactive section
401 // This handles components that called disable_loop() during initialization
403}
404
406 for (auto *obj : this->components_) {
407 if (obj->has_overridden_loop() &&
408 ((obj->get_component_state() & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP_DONE) == match_loop_done) {
409 this->looping_components_.push_back(obj);
410 }
411 }
412}
413
415 // This method must be reentrant - components can disable themselves during their own loop() call
416 // Linear search to find component in active section
417 // Most configs have 10-30 looping components (30 is on the high end)
418 // O(n) is acceptable here as we optimize for memory, not complexity
419 for (uint16_t i = 0; i < this->looping_components_active_end_; i++) {
420 if (this->looping_components_[i] == component) {
421 // Move last active component to this position
422 this->looping_components_active_end_--;
423 if (i != this->looping_components_active_end_) {
424 std::swap(this->looping_components_[i], this->looping_components_[this->looping_components_active_end_]);
425
426 // If we're currently iterating and just swapped the current position
427 if (this->in_loop_ && i == this->current_loop_index_) {
428 // Decrement so we'll process the swapped component next
429 this->current_loop_index_--;
430 // Update the loop start time to current time so the swapped component
431 // gets correct timing instead of inheriting stale timing.
432 // This prevents integer underflow in timing calculations by ensuring
433 // the swapped component starts with a fresh timing reference, avoiding
434 // errors caused by stale or wrapped timing values.
436 }
437 }
438 return;
439 }
440 }
441}
442
444 // Helper to move component from inactive to active section
445 if (index != this->looping_components_active_end_) {
446 std::swap(this->looping_components_[index], this->looping_components_[this->looping_components_active_end_]);
447 }
449}
450
452 // This method is only called when component state is LOOP_DONE, so we know
453 // the component must be in the inactive section (if it exists in looping_components_)
454 // Only search the inactive portion for better performance
455 // With typical 0-5 inactive components, O(k) is much faster than O(n)
456 const uint16_t size = this->looping_components_.size();
457 for (uint16_t i = this->looping_components_active_end_; i < size; i++) {
458 if (this->looping_components_[i] == component) {
459 // Found in inactive section - move to active
461 return;
462 }
463 }
464 // Component not found in looping_components_ - this is normal for components
465 // that don't have loop() or were not included in the partitioned vector
466}
467
469 // Process components that requested enable_loop from ISR context
470 // Only iterate through inactive looping_components_ (typically 0-5) instead of all components
471 //
472 // Race condition handling:
473 // 1. We check if component is already in LOOP state first - if so, just clear the flag
474 // This handles reentrancy where enable_loop() was called between ISR and processing
475 // 2. We only clear pending_enable_loop_ after checking state, preventing lost requests
476 // 3. If any components aren't in LOOP_DONE state, we set has_pending_enable_loop_requests_
477 // back to true to ensure we check again next iteration
478 // 4. ISRs can safely set flags at any time - worst case is we process them next iteration
479 // 5. The global flag (has_pending_enable_loop_requests_) is cleared before this method,
480 // so any ISR that fires during processing will be caught in the next loop
481 const uint16_t size = this->looping_components_.size();
482 bool has_pending = false;
483
484 for (uint16_t i = this->looping_components_active_end_; i < size; i++) {
486 if (!component->pending_enable_loop_) {
487 continue; // Skip components without pending requests
488 }
489
490 // Check current state
492
493 // If already in LOOP state, nothing to do - clear flag and continue
495 component->pending_enable_loop_ = false;
496 continue;
497 }
498
499 // If not in LOOP_DONE state, can't enable yet - keep flag set
501 has_pending = true; // Keep tracking this component
502 continue; // Keep the flag set - try again next iteration
503 }
504
505 // Clear the pending flag and enable the loop
506 component->pending_enable_loop_ = false;
507 ESP_LOGVV(TAG, "%s loop enabled from ISR", LOG_STR_ARG(component->get_component_log_str()));
508 component->set_component_state_(COMPONENT_STATE_LOOP);
509
510 // Move to active section
512 }
513
514 // If we couldn't process some requests, ensure we check again next iteration
515 if (has_pending) {
517 }
518}
519
520void Application::before_loop_tasks_(uint32_t loop_start_time) {
521#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) && !defined(USE_LWIP_FAST_SELECT)
522 // Drain wake notifications first to clear socket for next wake
524#endif
525
526 // Process scheduled tasks
527 this->scheduler.call(loop_start_time);
528
529 // Feed the watchdog timer
530 this->feed_wdt(loop_start_time);
531
532 // Process any pending enable_loop requests from ISRs
533 // This must be done before marking in_loop_ = true to avoid race conditions
535 // Clear flag BEFORE processing to avoid race condition
536 // If ISR sets it during processing, we'll catch it next loop iteration
537 // This is safe because:
538 // 1. Each component has its own pending_enable_loop_ flag that we check
539 // 2. If we can't process a component (wrong state), enable_pending_loops_()
540 // will set this flag back to true
541 // 3. Any new ISR requests during processing will set the flag again
543 this->enable_pending_loops_();
544 }
545
546 // Mark that we're in the loop for safe reentrant modifications
547 this->in_loop_ = true;
548}
549
551 // Clear the in_loop_ flag to indicate we're done processing components
552 this->in_loop_ = false;
553}
554
555#ifdef USE_SOCKET_SELECT_SUPPORT
557 // WARNING: This function is NOT thread-safe and must only be called from the main loop
558 // It modifies socket_fds_ and related variables without locking
559 if (fd < 0)
560 return false;
561
562#ifndef USE_ESP32
563 // Only check on non-ESP32 platforms
564 // On ESP32 (both Arduino and ESP-IDF), CONFIG_LWIP_MAX_SOCKETS is always <= FD_SETSIZE by design
565 // (LWIP_SOCKET_OFFSET = FD_SETSIZE - CONFIG_LWIP_MAX_SOCKETS per lwipopts.h)
566 // Other platforms may not have this guarantee
567 if (fd >= FD_SETSIZE) {
568 ESP_LOGE(TAG, "fd %d exceeds FD_SETSIZE %d", fd, FD_SETSIZE);
569 return false;
570 }
571#endif
572
573 this->socket_fds_.push_back(fd);
574#ifdef USE_LWIP_FAST_SELECT
575 // Hook the socket's netconn callback for instant wake on receive events
577#else
578 this->socket_fds_changed_ = true;
579 if (fd > this->max_fd_) {
580 this->max_fd_ = fd;
581 }
582#endif
583
584 return true;
585}
586
588 // WARNING: This function is NOT thread-safe and must only be called from the main loop
589 // It modifies socket_fds_ and related variables without locking
590 if (fd < 0)
591 return;
592
593 for (size_t i = 0; i < this->socket_fds_.size(); i++) {
594 if (this->socket_fds_[i] != fd)
595 continue;
596
597 // Swap with last element and pop - O(1) removal since order doesn't matter.
598 // No need to unhook the netconn callback on fast select platforms — all LwIP
599 // sockets share the same static event_callback, and the socket will be closed
600 // by the caller.
601 if (i < this->socket_fds_.size() - 1)
602 this->socket_fds_[i] = this->socket_fds_.back();
603 this->socket_fds_.pop_back();
604#ifndef USE_LWIP_FAST_SELECT
605 this->socket_fds_changed_ = true;
606 // Only recalculate max_fd if we removed the current max
607 if (fd == this->max_fd_) {
608 this->max_fd_ = -1;
609 for (int sock_fd : this->socket_fds_) {
610 if (sock_fd > this->max_fd_)
611 this->max_fd_ = sock_fd;
612 }
613 }
614#endif
615 return;
616 }
617}
618
619#endif
620
621void Application::yield_with_select_(uint32_t delay_ms) {
622 // Delay while monitoring sockets. When delay_ms is 0, always yield() to ensure other tasks run.
623#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_LWIP_FAST_SELECT)
624 // Fast path (ESP32/LibreTiny): reads rcvevent directly via lwip_socket_dbg_get_socket().
625 // Safe because this runs on the main loop which owns socket lifetime (create, read, close).
626 if (delay_ms == 0) [[unlikely]] {
627 yield();
628 return;
629 }
630
631 // Check if any socket already has pending data before sleeping.
632 // If a socket still has unread data (rcvevent > 0) but the task notification was already
633 // consumed, ulTaskNotifyTake would block until timeout — adding up to delay_ms latency.
634 // This scan preserves select() semantics: return immediately when any fd is ready.
635 for (int fd : this->socket_fds_) {
637 yield();
638 return;
639 }
640 }
641
642 // Sleep with instant wake via FreeRTOS task notification.
643 // Woken by: callback wrapper (socket data arrives), wake_loop_threadsafe() (other tasks), or timeout.
644 // Without USE_WAKE_LOOP_THREADSAFE, only hooked socket callbacks wake the task —
645 // background tasks won't call wake, so this degrades to a pure timeout (same as old select path).
646 ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(delay_ms));
647
648#elif defined(USE_SOCKET_SELECT_SUPPORT)
649 // Fallback select() path (host platform and any future platforms without fast select).
650 // ESP32 and LibreTiny are excluded by the #if above — they use the fast path.
651 if (!this->socket_fds_.empty()) [[likely]] {
652 // Update fd_set if socket list has changed
653 if (this->socket_fds_changed_) [[unlikely]] {
654 FD_ZERO(&this->base_read_fds_);
655 // fd bounds are validated in register_socket_fd()
656 for (int fd : this->socket_fds_) {
657 FD_SET(fd, &this->base_read_fds_);
658 }
659 this->socket_fds_changed_ = false;
660 }
661
662 // Copy base fd_set before each select
663 this->read_fds_ = this->base_read_fds_;
664
665 // Convert delay_ms to timeval
666 struct timeval tv;
667 tv.tv_sec = delay_ms / 1000;
668 tv.tv_usec = (delay_ms - tv.tv_sec * 1000) * 1000;
669
670 // Call select with timeout
671#ifdef USE_SOCKET_IMPL_LWIP_SOCKETS
672 int ret = lwip_select(this->max_fd_ + 1, &this->read_fds_, nullptr, nullptr, &tv);
673#else
674 int ret = ::select(this->max_fd_ + 1, &this->read_fds_, nullptr, nullptr, &tv);
675#endif
676
677 // Process select() result:
678 // ret < 0: error (except EINTR which is normal)
679 // ret > 0: socket(s) have data ready - normal and expected
680 // ret == 0: timeout occurred - normal and expected
681 if (ret >= 0 || errno == EINTR) [[likely]] {
682 // Yield if zero timeout since select(0) only polls without yielding
683 if (delay_ms == 0) [[unlikely]] {
684 yield();
685 }
686 return;
687 }
688 // select() error - log and fall through to delay()
689 ESP_LOGW(TAG, "select() failed with errno %d", errno);
690 }
691 // No sockets registered or select() failed - use regular delay
692 delay(delay_ms);
693#elif defined(USE_ESP8266) && defined(USE_SOCKET_IMPL_LWIP_TCP)
694 // No select support but can wake on socket activity via esp_schedule()
695 socket::socket_delay(delay_ms);
696#else
697 // No select support, use regular delay
698 delay(delay_ms);
699#endif
700}
701
702// App storage — asm label shares the linker symbol with "extern Application App".
703// char[] is trivially destructible, so no __cxa_atexit or destructor chain is emitted.
704// Constructed via placement new in the generated setup().
705#ifndef __GXX_ABI_VERSION
706#error "Application placement new requires Itanium C++ ABI (GCC/Clang)"
707#endif
708static_assert(std::is_default_constructible<Application>::value, "Application must be default-constructible");
709// __USER_LABEL_PREFIX__ is "_" on Mach-O (macOS) and empty on ELF (embedded targets).
710// String literal concatenation produces the correct platform-specific mangled symbol.
711// Two-level macro needed: # stringifies before expansion, so the
712// indirection forces __USER_LABEL_PREFIX__ to expand first.
713#define ESPHOME_STRINGIFY_IMPL_(x) #x
714#define ESPHOME_STRINGIFY_(x) ESPHOME_STRINGIFY_IMPL_(x)
715// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
716alignas(Application) char app_storage[sizeof(Application)] asm(
717 ESPHOME_STRINGIFY_(__USER_LABEL_PREFIX__) "_ZN7esphome3AppE");
718#undef ESPHOME_STRINGIFY_
719#undef ESPHOME_STRINGIFY_IMPL_
720
721#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
722
723#ifdef USE_LWIP_FAST_SELECT
725 // Direct FreeRTOS task notification — <1 us, task context only (NOT ISR-safe)
727}
728#else // !USE_LWIP_FAST_SELECT
729
731 // Create UDP socket for wake notifications
732 this->wake_socket_fd_ = lwip_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
733 if (this->wake_socket_fd_ < 0) {
734 ESP_LOGW(TAG, "Wake socket create failed: %d", errno);
735 return;
736 }
737
738 // Bind to loopback with auto-assigned port
739 struct sockaddr_in addr = {};
740 addr.sin_family = AF_INET;
741 addr.sin_addr.s_addr = lwip_htonl(INADDR_LOOPBACK);
742 addr.sin_port = 0; // Auto-assign port
743
744 if (lwip_bind(this->wake_socket_fd_, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
745 ESP_LOGW(TAG, "Wake socket bind failed: %d", errno);
746 lwip_close(this->wake_socket_fd_);
747 this->wake_socket_fd_ = -1;
748 return;
749 }
750
751 // Get the assigned address and connect to it
752 // Connecting a UDP socket allows using send() instead of sendto() for better performance
753 struct sockaddr_in wake_addr;
754 socklen_t len = sizeof(wake_addr);
755 if (lwip_getsockname(this->wake_socket_fd_, (struct sockaddr *) &wake_addr, &len) < 0) {
756 ESP_LOGW(TAG, "Wake socket address failed: %d", errno);
757 lwip_close(this->wake_socket_fd_);
758 this->wake_socket_fd_ = -1;
759 return;
760 }
761
762 // Connect to self (loopback) - allows using send() instead of sendto()
763 // After connect(), no need to store wake_addr - the socket remembers it
764 if (lwip_connect(this->wake_socket_fd_, (struct sockaddr *) &wake_addr, sizeof(wake_addr)) < 0) {
765 ESP_LOGW(TAG, "Wake socket connect failed: %d", errno);
766 lwip_close(this->wake_socket_fd_);
767 this->wake_socket_fd_ = -1;
768 return;
769 }
770
771 // Set non-blocking mode
772 int flags = lwip_fcntl(this->wake_socket_fd_, F_GETFL, 0);
773 lwip_fcntl(this->wake_socket_fd_, F_SETFL, flags | O_NONBLOCK);
774
775 // Register with application's select() loop
776 if (!this->register_socket_fd(this->wake_socket_fd_)) {
777 ESP_LOGW(TAG, "Wake socket register failed");
778 lwip_close(this->wake_socket_fd_);
779 this->wake_socket_fd_ = -1;
780 return;
781 }
782}
783
785 // Called from FreeRTOS task context when events need immediate processing
786 // Wakes up lwip_select() in main loop by writing to connected loopback socket
787 if (this->wake_socket_fd_ >= 0) {
788 const char dummy = 1;
789 // Non-blocking send - if it fails (unlikely), select() will wake on timeout anyway
790 // No error checking needed: we control both ends of this loopback socket.
791 // This is safe to call from FreeRTOS tasks - send() is thread-safe in lwip
792 // Socket is already connected to loopback address, so send() is faster than sendto()
793 lwip_send(this->wake_socket_fd_, &dummy, 1, 0);
794 }
795}
796#endif // USE_LWIP_FAST_SELECT
797
798#endif // defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
799
800void Application::get_build_time_string(std::span<char, BUILD_TIME_STR_SIZE> buffer) {
801 ESPHOME_strncpy_P(buffer.data(), ESPHOME_BUILD_TIME_STR, buffer.size());
802 buffer[buffer.size() - 1] = '\0';
803}
804
805void Application::get_comment_string(std::span<char, ESPHOME_COMMENT_SIZE_MAX> buffer) {
806 ESPHOME_strncpy_P(buffer.data(), ESPHOME_COMMENT_STR, ESPHOME_COMMENT_SIZE);
807 buffer[ESPHOME_COMMENT_SIZE - 1] = '\0';
808}
809
810uint32_t Application::get_config_hash() { return ESPHOME_CONFIG_HASH; }
811
812uint32_t Application::get_config_version_hash() { return fnv1a_hash_extend(ESPHOME_CONFIG_HASH, ESPHOME_VERSION); }
813
814time_t Application::get_build_time() { return ESPHOME_BUILD_TIME; }
815
816} // namespace esphome
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)
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)
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_impl_(Component *comp, bool has_loop)
float get_actual_setup_priority() const
uint8_t get_component_state() const
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:517
static bool is_high_frequency()
Check whether the loop is running continuously.
Definition helpers.h:1735
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:97
void esphome_lwip_fast_select_init(void)
Initialize fast select — must be called from the main loop task during setup().
bool esphome_lwip_socket_has_data(int fd)
Check if a LwIP socket has data ready via direct rcvevent read (~215 ns per socket).
void esphome_lwip_hook_socket(int fd)
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:576
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:817
size_t size
Definition helpers.h:854
void clear_setup_priority_overrides()
void HOT yield()
Definition core.cpp:24
void HOT arch_feed_wdt()
Definition core.cpp:48
constexpr uint8_t COMPONENT_STATE_LOOP_DONE
Definition component.h:73
void HOT delay(uint32_t ms)
Definition core.cpp:27
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:25
void arch_restart()
Definition core.cpp:30
struct in_addr sin_addr
Definition headers.h:65
sa_family_t sin_family
Definition headers.h:63
in_port_t sin_port
Definition headers.h:64