diff --git a/esphome/core/application.h b/esphome/core/application.h index 13e0f638856..1a3b027f250 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -501,7 +501,7 @@ class Application { void wake_loop_threadsafe(); #endif -#if defined(USE_WAKE_LOOP_THREADSAFE) && defined(USE_LWIP_FAST_SELECT) +#ifdef USE_LWIP_FAST_SELECT /// Wake the main event loop from an ISR. /// Uses vTaskNotifyGiveFromISR() — <1 us, ISR-safe. /// Only available on platforms with fast select (ESP32, LibreTiny). @@ -509,6 +509,10 @@ class Application { static void IRAM_ATTR wake_loop_isrsafe(int *px_higher_priority_task_woken) { esphome_lwip_wake_main_loop_from_isr(px_higher_priority_task_woken); } + + /// Wake the main event loop from any context (ISR, thread, or main loop). + /// Detects the calling context and uses the appropriate FreeRTOS API. + static void IRAM_ATTR wake_loop_any_context() { esphome_lwip_wake_main_loop_any_context(); } #endif #endif diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 1fd621ea83a..e7f6f9ff378 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -323,6 +323,13 @@ void IRAM_ATTR HOT Component::enable_loop_soon_any_context() { // 8. Race condition with main loop is handled by clearing flag before processing this->pending_enable_loop_ = true; App.has_pending_enable_loop_requests_ = true; +#ifdef USE_LWIP_FAST_SELECT + // Wake the main loop if sleeping in ulTaskNotifyTake(). Without this, + // the main loop would not wake until the select timeout expires (~16ms). + // Uses xPortInIsrContext() to pick xTaskNotifyGive (task) or + // vTaskNotifyGiveFromISR (ISR) — safe from any calling context. + Application::wake_loop_any_context(); +#endif } void Component::reset_to_construction_state() { if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED) { diff --git a/esphome/core/lwip_fast_select.c b/esphome/core/lwip_fast_select.c index da0f1f337a4..8c4b26ccacd 100644 --- a/esphome/core/lwip_fast_select.c +++ b/esphome/core/lwip_fast_select.c @@ -239,4 +239,20 @@ void IRAM_ATTR esphome_lwip_wake_main_loop_from_isr(int *px_higher_priority_task } } +// Wake the main loop from any context (ISR, thread, or main loop). +// Detects ISR context and delegates to the appropriate variant: +// ESP32 (Xtensa/RISC-V): xPortInIsrContext() — checks interrupt nesting counter +// LibreTiny (ARM Cortex-M): __get_IPSR() — reads IPSR register (0 = task context) +void IRAM_ATTR esphome_lwip_wake_main_loop_any_context(void) { +#ifdef USE_ESP32 + if (xPortInIsrContext()) { +#else + if (__get_IPSR() != 0) { +#endif + esphome_lwip_wake_main_loop_from_isr(NULL); + } else { + esphome_lwip_wake_main_loop(); + } +} + #endif // defined(USE_ESP32) || defined(USE_LIBRETINY) diff --git a/esphome/core/lwip_fast_select.h b/esphome/core/lwip_fast_select.h index b7a70a8f9f2..ded05903594 100644 --- a/esphome/core/lwip_fast_select.h +++ b/esphome/core/lwip_fast_select.h @@ -33,6 +33,10 @@ void esphome_lwip_wake_main_loop(void); /// @param px_higher_priority_task_woken Set to pdTRUE if a context switch is needed. void esphome_lwip_wake_main_loop_from_isr(int *px_higher_priority_task_woken); +/// Wake the main loop task from any context (ISR, thread, or main loop). +/// Detects the calling context and uses the appropriate FreeRTOS API. +void esphome_lwip_wake_main_loop_any_context(void); + #ifdef __cplusplus } #endif