diff --git a/esphome/core/application.h b/esphome/core/application.h index 13e0f638856..f3c09591ac3 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). diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 1fd621ea83a..36eef1e6c8d 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). + // 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); +#endif } void Component::reset_to_construction_state() { if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED) {