From 2d4117f370c32ace1b959d4d47ad25f436ac1c91 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 28 Feb 2026 10:48:33 -1000 Subject: [PATCH] [core] Wake main loop from any context in enable_loop_soon_any_context() enable_loop_soon_any_context() sets volatile flags but does not wake the main loop from ulTaskNotifyTake() sleep. This means components using ISR-driven state changes (e.g. GPIO binary sensors) wait up to ~16ms for the select timeout before their loop runs. Add wake_loop_any_context() that detects the calling context (ISR vs task) using xPortInIsrContext() (ESP32) or __get_IPSR() (LibreTiny) and calls the appropriate FreeRTOS API. This is safe from ISR, thread, and main loop contexts. Also relax the wake_loop_isrsafe() guard from requiring both USE_WAKE_LOOP_THREADSAFE and USE_LWIP_FAST_SELECT to just USE_LWIP_FAST_SELECT, since the ISR wake path uses vTaskNotifyGiveFromISR directly and does not depend on the UDP socket mechanism. --- esphome/core/application.h | 6 +++++- esphome/core/component.cpp | 7 +++++++ esphome/core/lwip_fast_select.c | 16 ++++++++++++++++ esphome/core/lwip_fast_select.h | 4 ++++ 4 files changed, 32 insertions(+), 1 deletion(-) 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