diff --git a/esphome/core/application.h b/esphome/core/application.h index 77bdae6d380..dcdf2eec0c8 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -626,6 +626,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 823a69df322..4aa8b805648 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -339,9 +339,9 @@ void IRAM_ATTR HOT Component::enable_loop_soon_any_context() { #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). - // vTaskNotifyGiveFromISR(NULL) is safe here — it skips the yield flag - // and the context switch happens at the next scheduler tick (<1ms). - Application::wake_loop_isrsafe(nullptr); + // 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() { 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