diff --git a/esphome/core/application.h b/esphome/core/application.h index 923cbfdb03..f8e9b0e360 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -829,12 +829,13 @@ inline void ESPHOME_ALWAYS_INLINE Application::before_loop_tasks_(uint32_t loop_ this->drain_wake_notifications_(); #endif - // Process scheduled tasks + // Process scheduled tasks. Scheduler::call now feeds the watchdog itself + // after each scheduled item that actually runs, so we no longer need an + // unconditional feed here — when Scheduler::call has no work to do, the + // only elapsed time is a sleep wake + a few instructions, and when it does + // have work, it fed the wdt as it went. this->scheduler.call(loop_start_time); - // Feed the watchdog timer - this->feed_wdt(loop_start_time); - // Process any pending enable_loop requests from ISRs // This must be done before marking in_loop_ = true to avoid race conditions if (this->has_pending_enable_loop_requests_) { diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index dff50b03ef..7c9263583d 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -739,7 +739,13 @@ uint32_t HOT Scheduler::execute_item_(SchedulerItem *item, uint32_t now) { App.set_current_component(item->component); WarnIfComponentBlockingGuard guard{item->component, now}; item->callback(); - return guard.finish(); + uint32_t end = guard.finish(); + // Feed the watchdog after each scheduled item (both main heap and defer + // queue paths go through here). A run of back-to-back callbacks cannot + // starve the wdt. The inline fast path is a load + sub + branch — nearly + // free when the 3 ms rate limit hasn't elapsed. + App.feed_wdt(end); + return end; } // Common implementation for cancel operations - handles locking