diff --git a/esphome/components/mdns/mdns_component.h b/esphome/components/mdns/mdns_component.h index f696cfff1c..74fa24fdba 100644 --- a/esphome/components/mdns/mdns_component.h +++ b/esphome/components/mdns/mdns_component.h @@ -48,6 +48,25 @@ class MDNSComponent : public Component { #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). + // + // 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 + // packets are handled independently via the lwIP onRx UDP callback and are NOT affected + // by how often update() is called. + // + // 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. + // + // 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. + static constexpr uint32_t MDNS_UPDATE_INTERVAL_MS = 50; float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; } #ifdef USE_MDNS_EXTRA_SERVICES @@ -110,6 +129,9 @@ 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 dcbe5ebd52..e23e82954e 100644 --- a/esphome/components/mdns/mdns_esp8266.cpp +++ b/esphome/components/mdns/mdns_esp8266.cpp @@ -38,7 +38,17 @@ static void register_esp8266(MDNSComponent *, StaticVectorsetup_buffers_and_register_(register_esp8266); } -void MDNSComponent::loop() { MDNS.update(); } +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::on_shutdown() { MDNS.close(); diff --git a/esphome/components/mdns/mdns_rp2040.cpp b/esphome/components/mdns/mdns_rp2040.cpp index e4a9b60cdb..19d9b12497 100644 --- a/esphome/components/mdns/mdns_rp2040.cpp +++ b/esphome/components/mdns/mdns_rp2040.cpp @@ -37,7 +37,17 @@ static void register_rp2040(MDNSComponent *, StaticVectorsetup_buffers_and_register_(register_rp2040); } -void MDNSComponent::loop() { MDNS.update(); } +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::on_shutdown() { MDNS.close();