ESPHome 2026.4.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#include <esp_ota_ops.h>
13#include <esp_bootloader_desc.h>
14#endif
15#ifdef USE_LWIP_FAST_SELECT
17#endif // USE_LWIP_FAST_SELECT
19#include "esphome/core/hal.h"
20#include <algorithm>
21#include <ranges>
22
23#ifdef USE_STATUS_LED
25#endif
26
27#if (defined(USE_ESP8266) || defined(USE_RP2040)) && defined(USE_SOCKET_IMPL_LWIP_TCP)
29#endif
30
31#ifdef USE_HOST
32#include <cerrno>
33#endif
34
35namespace esphome {
36
37static const char *const TAG = "app";
38
39// Helper function for insertion sort of components by priority
40// Using insertion sort instead of std::stable_sort saves ~1.3KB of flash
41// by avoiding template instantiations (std::rotate, std::stable_sort, lambdas)
42// IMPORTANT: This sort is stable (preserves relative order of equal elements),
43// which is necessary to maintain user-defined component order for same priority
44template<typename Iterator, float (Component::*GetPriority)() const>
45static void insertion_sort_by_priority(Iterator first, Iterator last) {
46 for (auto it = first + 1; it != last; ++it) {
47 auto key = *it;
48 float key_priority = (key->*GetPriority)();
49 auto j = it - 1;
50
51 // Using '<' (not '<=') ensures stability - equal priority components keep their order
52 while (j >= first && ((*j)->*GetPriority)() < key_priority) {
53 *(j + 1) = *j;
54 j--;
55 }
56 *(j + 1) = key;
57 }
58}
59
61 if (has_loop) {
63 }
64 this->components_.push_back(comp);
65}
67 ESP_LOGI(TAG, "Running through setup()");
68 ESP_LOGV(TAG, "Sorting components by setup priority");
69
70 // Sort by setup priority using our helper function
71 insertion_sort_by_priority<decltype(this->components_.begin()), &Component::get_actual_setup_priority>(
72 this->components_.begin(), this->components_.end());
73
74 // Initialize looping_components_ early so enable_pending_loops_() works during setup
76
77 for (uint32_t i = 0; i < this->components_.size(); i++) {
79
80 // Update loop_component_start_time_ before calling each component during setup
82 component->call();
83 this->scheduler.process_to_add();
84 this->feed_wdt();
85 if (component->can_proceed())
86 continue;
87
88 do {
89 uint8_t new_app_state = STATUS_LED_WARNING;
90 uint32_t now = millis();
91
92 // Process pending loop enables to handle GPIO interrupts during setup
93 this->before_loop_tasks_(now);
94
95 for (uint32_t j = 0; j <= i; j++) {
96 // Update loop_component_start_time_ right before calling each component
98 this->components_[j]->call();
99 new_app_state |= this->components_[j]->get_component_state();
100 this->app_state_ |= new_app_state;
101 this->feed_wdt();
102 }
103
104 this->after_loop_tasks_();
105 this->app_state_ = new_app_state;
106 yield();
107 } while (!component->can_proceed() && !component->is_failed());
108 }
109
110 ESP_LOGI(TAG, "setup() finished successfully!");
111
112#ifdef USE_SETUP_PRIORITY_OVERRIDE
113 // Clear setup priority overrides to free memory
115#endif
116
117#if defined(USE_ESP32) || defined(USE_LIBRETINY)
118 // Save main loop task handle for wake_loop_*() / fast select FreeRTOS notifications.
119 esphome_main_task_handle = xTaskGetCurrentTaskHandle();
120#endif
121#ifdef USE_HOST
122 // Set up wake socket for waking main loop from tasks (platforms without fast select only)
124#endif
125
126 // Ensure all active looping components are in LOOP state.
127 // Components after the last blocking component only got one call() during setup
128 // (CONSTRUCTION→SETUP) and never received the second call() (SETUP→LOOP).
129 // The main loop calls loop() directly, bypassing call()'s state machine.
130 for (uint16_t i = 0; i < this->looping_components_active_end_; i++) {
131 this->looping_components_[i]->set_component_state_(COMPONENT_STATE_LOOP);
132 }
133
134 this->schedule_dump_config();
135}
136
137void Application::process_dump_config_() {
138 if (this->dump_config_at_ == 0) {
139 char build_time_str[Application::BUILD_TIME_STR_SIZE];
140 this->get_build_time_string(build_time_str);
141 ESP_LOGI(TAG, "ESPHome version " ESPHOME_VERSION " compiled on %s", build_time_str);
142#ifdef ESPHOME_PROJECT_NAME
143 ESP_LOGI(TAG, "Project " ESPHOME_PROJECT_NAME " version " ESPHOME_PROJECT_VERSION);
144#endif
145#ifdef USE_ESP32
146 esp_chip_info_t chip_info;
147 esp_chip_info(&chip_info);
148 ESP_LOGI(TAG, "ESP32 Chip: %s rev%d.%d, %d core(s)", ESPHOME_VARIANT, chip_info.revision / 100,
149 chip_info.revision % 100, chip_info.cores);
150#if defined(USE_ESP32_VARIANT_ESP32) && (!defined(USE_ESP32_MIN_CHIP_REVISION_SET) || !defined(USE_ESP32_SRAM1_AS_IRAM))
151 static const char *const ESP32_ADVANCED_PATH = "under esp32 > framework > advanced";
152#endif
153#if defined(USE_ESP32_VARIANT_ESP32) && !defined(USE_ESP32_MIN_CHIP_REVISION_SET)
154 {
155 // Suggest optimization for chips that don't need the PSRAM cache workaround
156 if (chip_info.revision >= 300) {
157#ifdef USE_PSRAM
158 ESP_LOGW(TAG, "Chip rev >= 3.0 detected. Set minimum_chip_revision: \"%d.%d\" %s to save ~10KB IRAM",
159 chip_info.revision / 100, chip_info.revision % 100, ESP32_ADVANCED_PATH);
160#else
161 ESP_LOGW(TAG, "Chip rev >= 3.0 detected. Set minimum_chip_revision: \"%d.%d\" %s to reduce binary size",
162 chip_info.revision / 100, chip_info.revision % 100, ESP32_ADVANCED_PATH);
163#endif
164 }
165 }
166#endif
167 {
168 // esp_bootloader_desc_t is available in ESP-IDF >= 5.2; if readable the bootloader is modern.
169 //
170 // Design decision: We intentionally do NOT mention sram1_as_iram when the bootloader is too old.
171 // Enabling sram1_as_iram with an old bootloader causes a hard brick (device fails to boot,
172 // requires USB reflash to recover). Users don't always read warnings carefully, so we only
173 // suggest the option once we've confirmed the bootloader can handle it. In practice this
174 // means a user with an old bootloader may need to flash twice: once via USB to update the
175 // bootloader (they'll see the suggestion on next boot), then OTA with sram1_as_iram: true.
176 // Two flashes is a better outcome than a bricked device.
177 esp_bootloader_desc_t boot_desc;
178 if (esp_ota_get_bootloader_description(nullptr, &boot_desc) != ESP_OK) {
179#ifdef USE_ESP32_VARIANT_ESP32
180 ESP_LOGW(TAG, "Bootloader too old for OTA rollback and SRAM1 as IRAM (+40KB). "
181 "Flash via USB once to update the bootloader");
182#else
183 ESP_LOGW(TAG, "Bootloader too old for OTA rollback. Flash via USB once to update the bootloader");
184#endif
185 }
186#if defined(USE_ESP32_VARIANT_ESP32) && !defined(USE_ESP32_SRAM1_AS_IRAM)
187 else {
188 ESP_LOGW(TAG, "Bootloader supports SRAM1 as IRAM (+40KB). Set sram1_as_iram: true %s", ESP32_ADVANCED_PATH);
189 }
190#endif
191 }
192#endif // USE_ESP32
193 }
194
195 this->components_[this->dump_config_at_]->call_dump_config_();
196 this->dump_config_at_++;
197}
198
200 static uint32_t last_feed = 0;
201 // Use provided time if available, otherwise get current time
202 uint32_t now = time ? time : millis();
203 // Compare in milliseconds (3ms threshold)
204 if (now - last_feed > 3) {
206 last_feed = now;
207#ifdef USE_STATUS_LED
208 if (status_led::global_status_led != nullptr) {
210 }
211#endif
212 }
213}
215 ESP_LOGI(TAG, "Forcing a reboot");
216 for (auto &component : std::ranges::reverse_view(this->components_)) {
217 component->on_shutdown();
218 }
219 arch_restart();
220}
222 ESP_LOGI(TAG, "Rebooting safely");
224 teardown_components(TEARDOWN_TIMEOUT_REBOOT_MS);
226 arch_restart();
227}
228
230 for (auto &component : std::ranges::reverse_view(this->components_)) {
231 component->on_safe_shutdown();
232 }
233 for (auto &component : std::ranges::reverse_view(this->components_)) {
234 component->on_shutdown();
235 }
236}
237
239 for (auto &component : std::ranges::reverse_view(this->components_)) {
240 component->on_powerdown();
241 }
242}
243
245 uint32_t start_time = millis();
246
247 // Use a StaticVector instead of std::vector to avoid heap allocation
248 // since we know the actual size at compile time
250
251 // Copy all components in reverse order
252 // Reverse order matches the behavior of run_safe_shutdown_hooks() above and ensures
253 // components are torn down in the opposite order of their setup_priority (which is
254 // used to sort components during Application::setup())
255 size_t num_components = this->components_.size();
256 for (size_t i = 0; i < num_components; ++i) {
257 pending_components[i] = this->components_[num_components - 1 - i];
258 }
259
260 uint32_t now = start_time;
261 size_t pending_count = num_components;
262
263 // Teardown Algorithm
264 // ==================
265 // We iterate through pending components, calling teardown() on each.
266 // Components that return false (need more time) are copied forward
267 // in the array. Components that return true (finished) are skipped.
268 //
269 // The compaction happens in-place during iteration:
270 // - still_pending tracks the write position (where to put next pending component)
271 // - i tracks the read position (which component we're testing)
272 // - When teardown() returns false, we copy component[i] to component[still_pending]
273 // - When teardown() returns true, we just skip it (don't increment still_pending)
274 //
275 // Example with 4 components where B can teardown immediately:
276 //
277 // Start:
278 // pending_components: [A, B, C, D]
279 // pending_count: 4 ^----------^
280 //
281 // Iteration 1:
282 // i=0: A needs more time → keep at pos 0 (no copy needed)
283 // i=1: B finished → skip
284 // i=2: C needs more time → copy to pos 1
285 // i=3: D needs more time → copy to pos 2
286 //
287 // After iteration 1:
288 // pending_components: [A, C, D | D]
289 // pending_count: 3 ^--------^
290 //
291 // Iteration 2:
292 // i=0: A finished → skip
293 // i=1: C needs more time → copy to pos 0
294 // i=2: D finished → skip
295 //
296 // After iteration 2:
297 // pending_components: [C | C, D, D] (positions 1-3 have old values)
298 // pending_count: 1 ^--^
299
300 while (pending_count > 0 && (now - start_time) < timeout_ms) {
301 // Feed watchdog during teardown to prevent triggering
302 this->feed_wdt(now);
303
304 // Process components and compact the array, keeping only those still pending
305 size_t still_pending = 0;
306 for (size_t i = 0; i < pending_count; ++i) {
307 if (!pending_components[i]->teardown()) {
308 // Component still needs time, copy it forward
309 if (still_pending != i) {
310 pending_components[still_pending] = pending_components[i];
311 }
312 ++still_pending;
313 }
314 // Component finished teardown, skip it (don't increment still_pending)
315 }
316 pending_count = still_pending;
317
318 // Give some time for I/O operations if components are still pending
319 if (pending_count > 0) {
320 this->yield_with_select_(1);
321 }
322
323 // Update time for next iteration
324 now = millis();
325 }
326
327 if (pending_count > 0) {
328 // Note: At this point, connections are either disconnected or in a bad state,
329 // so this warning will only appear via serial rather than being transmitted to clients
330 for (size_t i = 0; i < pending_count; ++i) {
331 ESP_LOGW(TAG, "%s did not complete teardown within %" PRIu32 " ms",
332 LOG_STR_ARG(pending_components[i]->get_component_log_str()), timeout_ms);
333 }
334 }
335}
336
338 for (auto *obj : this->components_) {
339 if (obj->has_overridden_loop() &&
340 ((obj->get_component_state() & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP_DONE) == match_loop_done) {
341 this->looping_components_.push_back(obj);
342 }
343 }
344}
345
347 // This method must be reentrant - components can disable themselves during their own loop() call
348 // Linear search to find component in active section
349 // Most configs have 10-30 looping components (30 is on the high end)
350 // O(n) is acceptable here as we optimize for memory, not complexity
351 for (uint16_t i = 0; i < this->looping_components_active_end_; i++) {
352 if (this->looping_components_[i] == component) {
353 // Move last active component to this position
354 this->looping_components_active_end_--;
355 if (i != this->looping_components_active_end_) {
356 std::swap(this->looping_components_[i], this->looping_components_[this->looping_components_active_end_]);
357
358 // If we're currently iterating and just swapped the current position
359 if (this->in_loop_ && i == this->current_loop_index_) {
360 // Decrement so we'll process the swapped component next
361 this->current_loop_index_--;
362 // Update the loop start time to current time so the swapped component
363 // gets correct timing instead of inheriting stale timing.
364 // This prevents integer underflow in timing calculations by ensuring
365 // the swapped component starts with a fresh timing reference, avoiding
366 // errors caused by stale or wrapped timing values.
368 }
369 }
370 return;
371 }
372 }
373}
374
376 // Helper to move component from inactive to active section
377 if (index != this->looping_components_active_end_) {
378 std::swap(this->looping_components_[index], this->looping_components_[this->looping_components_active_end_]);
379 }
381}
382
384 // This method is only called when component state is LOOP_DONE, so we know
385 // the component must be in the inactive section (if it exists in looping_components_)
386 // Only search the inactive portion for better performance
387 // With typical 0-5 inactive components, O(k) is much faster than O(n)
388 const uint16_t size = this->looping_components_.size();
389 for (uint16_t i = this->looping_components_active_end_; i < size; i++) {
390 if (this->looping_components_[i] == component) {
391 // Found in inactive section - move to active
393 return;
394 }
395 }
396 // Component not found in looping_components_ - this is normal for components
397 // that don't have loop() or were not included in the partitioned vector
398}
399
401 // Process components that requested enable_loop from ISR context
402 // Only iterate through inactive looping_components_ (typically 0-5) instead of all components
403 //
404 // Race condition handling:
405 // 1. We check if component is already in LOOP state first - if so, just clear the flag
406 // This handles reentrancy where enable_loop() was called between ISR and processing
407 // 2. We only clear pending_enable_loop_ after checking state, preventing lost requests
408 // 3. If any components aren't in LOOP_DONE state, we set has_pending_enable_loop_requests_
409 // back to true to ensure we check again next iteration
410 // 4. ISRs can safely set flags at any time - worst case is we process them next iteration
411 // 5. The global flag (has_pending_enable_loop_requests_) is cleared before this method,
412 // so any ISR that fires during processing will be caught in the next loop
413 const uint16_t size = this->looping_components_.size();
414 bool has_pending = false;
415
416 for (uint16_t i = this->looping_components_active_end_; i < size; i++) {
418 if (!component->pending_enable_loop_) {
419 continue; // Skip components without pending requests
420 }
421
422 // Check current state
424
425 // If already in LOOP state, nothing to do - clear flag and continue
427 component->pending_enable_loop_ = false;
428 continue;
429 }
430
431 // If not in LOOP_DONE state, can't enable yet - keep flag set
433 has_pending = true; // Keep tracking this component
434 continue; // Keep the flag set - try again next iteration
435 }
436
437 // Clear the pending flag and enable the loop
438 component->pending_enable_loop_ = false;
439 ESP_LOGVV(TAG, "%s loop enabled from ISR", LOG_STR_ARG(component->get_component_log_str()));
440 component->set_component_state_(COMPONENT_STATE_LOOP);
441
442 // Move to active section
444 }
445
446 // If we couldn't process some requests, ensure we check again next iteration
447 if (has_pending) {
449 }
450}
451
452#ifdef USE_LWIP_FAST_SELECT
453bool Application::register_socket(struct lwip_sock *sock) {
454 // It modifies monitored_sockets_ without locking — must only be called from the main loop.
455 if (sock == nullptr)
456 return false;
458 this->monitored_sockets_.push_back(sock);
459 return true;
460}
461
462void Application::unregister_socket(struct lwip_sock *sock) {
463 // It modifies monitored_sockets_ without locking — must only be called from the main loop.
464 for (size_t i = 0; i < this->monitored_sockets_.size(); i++) {
465 if (this->monitored_sockets_[i] != sock)
466 continue;
467
468 // Swap with last element and pop - O(1) removal since order doesn't matter.
469 // No need to unhook the netconn callback — all LwIP sockets share the same
470 // static event_callback, and the socket will be closed by the caller.
471 if (i < this->monitored_sockets_.size() - 1)
472 this->monitored_sockets_[i] = this->monitored_sockets_.back();
473 this->monitored_sockets_.pop_back();
474 return;
475 }
476}
477#elif defined(USE_HOST)
479 // WARNING: This function is NOT thread-safe and must only be called from the main loop
480 // It modifies socket_fds_ and related variables without locking
481 if (fd < 0)
482 return false;
483
484 if (fd >= FD_SETSIZE) {
485 ESP_LOGE(TAG, "fd %d exceeds FD_SETSIZE %d", fd, FD_SETSIZE);
486 return false;
487 }
488
489 this->socket_fds_.push_back(fd);
490 this->socket_fds_changed_ = true;
491 if (fd > this->max_fd_) {
492 this->max_fd_ = fd;
493 }
494
495 return true;
496}
497
499 // WARNING: This function is NOT thread-safe and must only be called from the main loop
500 // It modifies socket_fds_ and related variables without locking
501 if (fd < 0)
502 return;
503
504 for (size_t i = 0; i < this->socket_fds_.size(); i++) {
505 if (this->socket_fds_[i] != fd)
506 continue;
507
508 // Swap with last element and pop - O(1) removal since order doesn't matter.
509 if (i < this->socket_fds_.size() - 1)
510 this->socket_fds_[i] = this->socket_fds_.back();
511 this->socket_fds_.pop_back();
512 this->socket_fds_changed_ = true;
513 // Only recalculate max_fd if we removed the current max
514 if (fd == this->max_fd_) {
515 this->max_fd_ = -1;
516 for (int sock_fd : this->socket_fds_) {
517 if (sock_fd > this->max_fd_)
518 this->max_fd_ = sock_fd;
519 }
520 }
521 return;
522 }
523}
524
525#endif
526
527// Only the select() fallback path remains in the .cpp — all other paths are inlined in application.h
528#ifdef USE_HOST
530 // Fallback select() path (host platform and any future platforms without fast select).
531 if (!this->socket_fds_.empty()) [[likely]] {
532 // Update fd_set if socket list has changed
533 if (this->socket_fds_changed_) [[unlikely]] {
534 FD_ZERO(&this->base_read_fds_);
535 // fd bounds are validated in register_socket_fd()
536 for (int fd : this->socket_fds_) {
537 FD_SET(fd, &this->base_read_fds_);
538 }
539 this->socket_fds_changed_ = false;
540 }
541
542 // Copy base fd_set before each select
543 this->read_fds_ = this->base_read_fds_;
544
545 // Convert delay_ms to timeval
546 struct timeval tv;
547 tv.tv_sec = delay_ms / 1000;
548 tv.tv_usec = (delay_ms - tv.tv_sec * 1000) * 1000;
549
550 // Call select with timeout
551 int ret = ::select(this->max_fd_ + 1, &this->read_fds_, nullptr, nullptr, &tv);
552
553 // Process select() result:
554 // ret > 0: socket(s) have data ready - normal and expected
555 // ret == 0: timeout occurred - normal and expected
556 if (ret >= 0) [[likely]] {
557 // Yield if zero timeout since select(0) only polls without yielding
558 if (delay_ms == 0) [[unlikely]] {
559 yield();
560 }
561 return;
562 }
563 // ret < 0: error (EINTR is normal, anything else is unexpected)
564 const int err = errno;
565 if (err == EINTR) {
566 return;
567 }
568 // select() error - log and fall through to delay()
569 ESP_LOGW(TAG, "select() failed with errno %d", err);
570 }
571 // No sockets registered or select() failed - use regular delay
572 delay(delay_ms);
573}
574#endif // USE_HOST
575
576// App storage — asm label shares the linker symbol with "extern Application App".
577// char[] is trivially destructible, so no __cxa_atexit or destructor chain is emitted.
578// Constructed via placement new in the generated setup().
579#ifndef __GXX_ABI_VERSION
580#error "Application placement new requires Itanium C++ ABI (GCC/Clang)"
581#endif
582static_assert(std::is_default_constructible<Application>::value, "Application must be default-constructible");
583// __USER_LABEL_PREFIX__ is "_" on Mach-O (macOS) and empty on ELF (embedded targets).
584// String literal concatenation produces the correct platform-specific mangled symbol.
585// Two-level macro needed: # stringifies before expansion, so the
586// indirection forces __USER_LABEL_PREFIX__ to expand first.
587#define ESPHOME_STRINGIFY_IMPL_(x) #x
588#define ESPHOME_STRINGIFY_(x) ESPHOME_STRINGIFY_IMPL_(x)
589// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
590alignas(Application) char app_storage[sizeof(Application)] asm(
591 ESPHOME_STRINGIFY_(__USER_LABEL_PREFIX__) "_ZN7esphome3AppE");
592#undef ESPHOME_STRINGIFY_
593#undef ESPHOME_STRINGIFY_IMPL_
594
595// Host platform wake_loop_threadsafe() and setup — needs wake_socket_fd_
596// ESP32/LibreTiny/ESP8266/RP2040 implementations are in wake.cpp
597#ifdef USE_HOST
598
600 // Create UDP socket for wake notifications
601 this->wake_socket_fd_ = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
602 if (this->wake_socket_fd_ < 0) {
603 ESP_LOGW(TAG, "Wake socket create failed: %d", errno);
604 return;
605 }
606
607 // Bind to loopback with auto-assigned port
608 struct sockaddr_in addr = {};
609 addr.sin_family = AF_INET;
610 addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
611 addr.sin_port = 0; // Auto-assign port
612
613 if (::bind(this->wake_socket_fd_, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
614 ESP_LOGW(TAG, "Wake socket bind failed: %d", errno);
615 ::close(this->wake_socket_fd_);
616 this->wake_socket_fd_ = -1;
617 return;
618 }
619
620 // Get the assigned address and connect to it
621 // Connecting a UDP socket allows using send() instead of sendto() for better performance
622 struct sockaddr_in wake_addr;
623 socklen_t len = sizeof(wake_addr);
624 if (::getsockname(this->wake_socket_fd_, (struct sockaddr *) &wake_addr, &len) < 0) {
625 ESP_LOGW(TAG, "Wake socket address failed: %d", errno);
626 ::close(this->wake_socket_fd_);
627 this->wake_socket_fd_ = -1;
628 return;
629 }
630
631 // Connect to self (loopback) - allows using send() instead of sendto()
632 // After connect(), no need to store wake_addr - the socket remembers it
633 if (::connect(this->wake_socket_fd_, (struct sockaddr *) &wake_addr, sizeof(wake_addr)) < 0) {
634 ESP_LOGW(TAG, "Wake socket connect failed: %d", errno);
635 ::close(this->wake_socket_fd_);
636 this->wake_socket_fd_ = -1;
637 return;
638 }
639
640 // Set non-blocking mode
641 int flags = ::fcntl(this->wake_socket_fd_, F_GETFL, 0);
642 ::fcntl(this->wake_socket_fd_, F_SETFL, flags | O_NONBLOCK);
643
644 // Register with application's select() loop
645 if (!this->register_socket_fd(this->wake_socket_fd_)) {
646 ESP_LOGW(TAG, "Wake socket register failed");
647 ::close(this->wake_socket_fd_);
648 this->wake_socket_fd_ = -1;
649 return;
650 }
651}
652
653#endif // USE_HOST
654
655void Application::get_build_time_string(std::span<char, BUILD_TIME_STR_SIZE> buffer) {
656 ESPHOME_strncpy_P(buffer.data(), ESPHOME_BUILD_TIME_STR, buffer.size());
657 buffer[buffer.size() - 1] = '\0';
658}
659
660void Application::get_comment_string(std::span<char, ESPHOME_COMMENT_SIZE_MAX> buffer) {
661 ESPHOME_strncpy_P(buffer.data(), ESPHOME_COMMENT_STR, ESPHOME_COMMENT_SIZE);
662 buffer[ESPHOME_COMMENT_SIZE - 1] = '\0';
663}
664
665uint32_t Application::get_config_hash() { return ESPHOME_CONFIG_HASH; }
666
667uint32_t Application::get_config_version_hash() { return fnv1a_hash_extend(ESPHOME_CONFIG_HASH, ESPHOME_VERSION); }
668
669time_t Application::get_build_time() { return ESPHOME_BUILD_TIME; }
670
671} // namespace esphome
std::vector< struct lwip_sock * > monitored_sockets_
void setup()
Reserve space for components to avoid memory fragmentation.
uint16_t looping_components_active_end_
void yield_with_select_(uint32_t delay_ms)
Perform a delay while also monitoring socket file descriptors for readiness.
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 enable_component_loop_(Component *component)
uint32_t loop_component_start_time_
void disable_component_loop_(Component *component)
void ESPHOME_ALWAYS_INLINE before_loop_tasks_(uint32_t loop_start_time)
void activate_looping_component_(uint16_t index)
void ESPHOME_ALWAYS_INLINE after_loop_tasks_()
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 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 register_component_impl_(Component *comp, bool has_loop)
void unregister_socket(struct lwip_sock *sock)
float get_actual_setup_priority() const
virtual bool can_proceed()
uint8_t component_state_
State of this component - each bit has a purpose: Bits 0-2: Component state (0x00=CONSTRUCTION,...
Definition component.h:574
Minimal static vector - saves memory by avoiding std::vector overhead.
Definition helpers.h:210
size_t size() const
Definition helpers.h:270
const Component * component
Definition component.cpp:34
uint16_t flags
bool state
Definition fan.h:2
uint32_t socklen_t
Definition headers.h:99
void esphome_lwip_hook_socket(struct lwip_sock *sock)
Hook a socket's netconn callback to notify the main loop task on receive events.
TaskHandle_t esphome_main_task_handle
Main loop task handle and wake helpers — shared between wake.h (C++) and lwip_fast_select....
Definition main_task.c:4
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
constexpr uint8_t COMPONENT_HAS_LOOP
Definition component.h:91
constexpr uint32_t fnv1a_hash_extend(uint32_t hash, const char *str)
Extend a FNV-1a hash with additional string data.
Definition helpers.h:799
constexpr uint8_t COMPONENT_STATE_LOOP
Definition component.h:82
constexpr uint8_t STATUS_LED_WARNING
Definition component.h:88
constexpr uint8_t COMPONENT_STATE_MASK
Definition component.h:79
std::string size_t len
Definition helpers.h:1045
uint16_t size
Definition helpers.cpp:25
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:84
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