From 1b13f49e1b2affaf45533213f5d476ecd9b2d6d2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 10 Feb 2026 14:28:26 -0600 Subject: [PATCH] [mdns] Throttle MDNS.update() polling on ESP8266 and RP2040 On ESP8266 and RP2040, MDNS.update() is called every loop iteration (~120 Hz) but only manages timer-driven probe/announce state machines. Incoming mDNS packets are handled independently via the lwIP onRx UDP callback and are unaffected by update() frequency. The shortest internal timer is the 250ms probe interval (RFC 6762). Throttling to 50ms provides sufficient resolution while reducing CPU overhead by ~84% (from ~360ms to ~60ms per 60s measurement period). --- esphome/components/mdns/mdns_component.h | 22 ++++++++++++++++++++++ esphome/components/mdns/mdns_esp8266.cpp | 12 +++++++++++- esphome/components/mdns/mdns_rp2040.cpp | 12 +++++++++++- 3 files changed, 44 insertions(+), 2 deletions(-) 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();