diff --git a/esphome/components/mdns/mdns_component.h b/esphome/components/mdns/mdns_component.h index 74fa24fdba..32f8f16ec1 100644 --- a/esphome/components/mdns/mdns_component.h +++ b/esphome/components/mdns/mdns_component.h @@ -45,11 +45,7 @@ class MDNSComponent : public Component { void setup() override; void dump_config() override; -#if (defined(USE_ESP8266) || defined(USE_RP2040)) && defined(USE_ARDUINO) - void loop() override; -#endif - - // Throttle interval for MDNS.update() on platforms that require polling (ESP8266, RP2040). + // Polling interval for MDNS.update() on platforms that require it (ESP8266, RP2040). // // On these platforms, MDNS.update() calls _process(true) which only manages timer-driven // state machines (probe/announce timeouts and service query cache TTLs). Incoming mDNS @@ -59,13 +55,17 @@ class MDNSComponent : public Component { // The shortest internal timer is the 250ms probe interval (RFC 6762 Section 8.1). // Announcement intervals are 1000ms and cache TTL checks are on the order of seconds // to minutes. A 50ms polling interval provides sufficient resolution for all timers - // while reducing CPU overhead by ~84% compared to calling every loop iteration. + // while completely removing mDNS from the per-iteration loop list. // // In steady state (after the ~8 second boot probe/announce phase completes), update() // checks timers that are set to never expire, making every call pure overhead. // // Tasmota uses a 50ms main loop cycle with mDNS working correctly, confirming this // interval is safe in production. + // + // By using set_interval() instead of overriding loop(), the component is excluded from + // the main loop list via has_overridden_loop(), eliminating all per-iteration overhead + // including virtual dispatch. static constexpr uint32_t MDNS_UPDATE_INTERVAL_MS = 50; float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; } @@ -129,9 +129,6 @@ class MDNSComponent : public Component { #endif #ifdef USE_MDNS_STORE_SERVICES StaticVector services_{}; -#endif -#if (defined(USE_ESP8266) || defined(USE_RP2040)) && defined(USE_ARDUINO) - uint32_t last_update_{0}; #endif void compile_records_(StaticVector &services, char *mac_address_buf); }; diff --git a/esphome/components/mdns/mdns_esp8266.cpp b/esphome/components/mdns/mdns_esp8266.cpp index e23e82954e..295a408cbd 100644 --- a/esphome/components/mdns/mdns_esp8266.cpp +++ b/esphome/components/mdns/mdns_esp8266.cpp @@ -36,18 +36,13 @@ static void register_esp8266(MDNSComponent *, StaticVectorsetup_buffers_and_register_(register_esp8266); } - -void MDNSComponent::loop() { - // Throttle MDNS.update() to avoid calling it every loop iteration (~120 Hz). - // The update() function only manages timer-driven probe/announce state machines - // and service query cache TTLs. Incoming mDNS packets are processed independently - // via the lwIP onRx callback and do not depend on update() frequency. - const uint32_t now = App.get_loop_component_start_time(); - if (now - this->last_update_ < MDNS_UPDATE_INTERVAL_MS) - return; - this->last_update_ = now; - MDNS.update(); +void MDNSComponent::setup() { + this->setup_buffers_and_register_(register_esp8266); + // Schedule MDNS.update() via set_interval() instead of overriding loop(). + // This removes the component from the per-iteration loop list entirely, + // eliminating virtual dispatch overhead on every main loop cycle. + // See MDNS_UPDATE_INTERVAL_MS comment in mdns_component.h for safety analysis. + this->set_interval(MDNS_UPDATE_INTERVAL_MS, []() { MDNS.update(); }); } void MDNSComponent::on_shutdown() { diff --git a/esphome/components/mdns/mdns_rp2040.cpp b/esphome/components/mdns/mdns_rp2040.cpp index 19d9b12497..05d991c1fa 100644 --- a/esphome/components/mdns/mdns_rp2040.cpp +++ b/esphome/components/mdns/mdns_rp2040.cpp @@ -35,18 +35,13 @@ static void register_rp2040(MDNSComponent *, StaticVectorsetup_buffers_and_register_(register_rp2040); } - -void MDNSComponent::loop() { - // Throttle MDNS.update() to avoid calling it every loop iteration (~120 Hz). - // The update() function only manages timer-driven probe/announce state machines - // and service query cache TTLs. Incoming mDNS packets are processed independently - // via the lwIP onRx callback and do not depend on update() frequency. - const uint32_t now = App.get_loop_component_start_time(); - if (now - this->last_update_ < MDNS_UPDATE_INTERVAL_MS) - return; - this->last_update_ = now; - MDNS.update(); +void MDNSComponent::setup() { + this->setup_buffers_and_register_(register_rp2040); + // Schedule MDNS.update() via set_interval() instead of overriding loop(). + // This removes the component from the per-iteration loop list entirely, + // eliminating virtual dispatch overhead on every main loop cycle. + // See MDNS_UPDATE_INTERVAL_MS comment in mdns_component.h for safety analysis. + this->set_interval(MDNS_UPDATE_INTERVAL_MS, []() { MDNS.update(); }); } void MDNSComponent::on_shutdown() {