From 09fe59c5ccdb724838273ce2242d27b220bceea2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 23 Apr 2026 19:57:48 -0500 Subject: [PATCH] [mdns] Drop fallback paths, collapse everything under USE_MDNS_EVENT_DRIVEN_POLLING MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mDNS on ESP8266/RP2040 always runs over a network interface (WiFi on ESP8266; WiFi or W5500/etc. ethernet on RP2040), and every such interface already publishes an IP state listener in tree. USE_MDNS_EVENT_DRIVEN_POLLING is therefore always defined in production, so the fallback set_interval() paths in both platform files and the was_connected_ bookkeeping are dead code. Also drop the ethernet-specific test — the existing wifi and ethernet+mdns combos are already covered by the mdns test fixtures paired with their network component tests. Trim redundant comments throughout: the header now documents the ~9s probe/announce window and why update() can stop afterward in a few lines instead of a full essay; platform files keep only the non-obvious bits (why RP2040 needs to drive begin()/notifyAPChange() itself, why re-arming on any listener notification is correct). --- esphome/components/mdns/__init__.py | 12 +--- esphome/components/mdns/mdns_component.cpp | 5 +- esphome/components/mdns/mdns_component.h | 47 ++++----------- esphome/components/mdns/mdns_esp8266.cpp | 27 ++------- esphome/components/mdns/mdns_rp2040.cpp | 58 +++---------------- .../mdns/common-enabled-ethernet.yaml | 23 -------- .../test-enabled-ethernet.rp2040-ard.yaml | 1 - 7 files changed, 29 insertions(+), 144 deletions(-) delete mode 100644 tests/components/mdns/common-enabled-ethernet.yaml delete mode 100644 tests/components/mdns/test-enabled-ethernet.rp2040-ard.yaml diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index db915e5f89..b15a778b59 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -169,15 +169,9 @@ async def to_code(config): elif CORE.is_rp2040: cg.add_library("LEAmDNS", None) - # ESP8266 and RP2040 use a network IP state listener to arm a bounded - # MDNS.update() polling window only while the library is in its probe+announce - # phase. This eliminates the steady-state 50ms interval that ran forever - # (1200+ dispatches per minute) and its scheduler overhead. - # - # We subscribe to any listener interface that's configured (WiFi and/or - # Ethernet); the same on_ip_state() override satisfies both because the - # listener signatures match. If neither is available the component falls - # back to the legacy polling loop at MDNS_UPDATE_INTERVAL_MS. + # Subscribe to the network IP state listener(s) so MDNS.update() is only + # scheduled during the probe+announce phase. Same on_ip_state() override + # serves both WiFi and Ethernet (signatures match). if CORE.is_esp8266 or CORE.is_rp2040: if "wifi" in CORE.config: from esphome.components import wifi diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index c80b9224f4..80e12ade71 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -192,9 +192,8 @@ void MDNSComponent::compile_records_(StaticVectorset_interval(MDNS_POLL_ID, MDNS_UPDATE_INTERVAL_MS, mdns_pump_update); this->set_timeout(MDNS_POLL_STOP_ID, MDNS_POLL_WINDOW_MS, [this]() { this->cancel_interval(MDNS_POLL_ID); }); } diff --git a/esphome/components/mdns/mdns_component.h b/esphome/components/mdns/mdns_component.h index ea74e22818..0e848ba975 100644 --- a/esphome/components/mdns/mdns_component.h +++ b/esphome/components/mdns/mdns_component.h @@ -5,12 +5,8 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" -// Event-driven polling replaces the legacy set_interval() loop on platforms that need -// scheduler-backed MDNS.update() (ESP8266, RP2040). It's enabled when at least one -// compatible network IP state listener slot is available — the mdns Python to_code() -// requests a WiFi slot when WiFi is in the config and an Ethernet slot when Ethernet -// is in the config. If neither is available (e.g. clang-tidy running without full -// codegen), the component falls back to the legacy polling loop. +// On ESP8266 and RP2040 the scheduler-backed MDNS.update() polling window is armed by +// IP state listener events on whichever network interface is configured. #if (defined(USE_ESP8266) || defined(USE_RP2040)) && \ ((defined(USE_WIFI) && defined(USE_WIFI_IP_STATE_LISTENERS)) || \ (defined(USE_ETHERNET) && defined(USE_ETHERNET_IP_STATE_LISTENERS))) @@ -29,9 +25,8 @@ namespace esphome::mdns { #ifdef USE_MDNS_EVENT_DRIVEN_POLLING -/// Call MDNS.update() on the target platform. Defined in the per-platform cpp file so -/// the shared component code can drive the polling window without pulling in -/// platform-specific mDNS headers. +/// Platform-specific MDNS.update() trampoline. Defined in mdns_.cpp so the +/// shared code can schedule it without including the platform's mDNS header. void mdns_pump_update(); #endif @@ -81,28 +76,13 @@ class MDNSComponent final : public Component void setup() override; void dump_config() override; - // On ESP8266 and RP2040, 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 work has a bounded lifetime: after MDNS.begin() (or _restart() triggered by a - // network interface change) the library sends 3 probes 250ms apart followed by 8 - // announcements 1000ms apart, after which all internal timeouts are set to - // resetToNeverExpires(). ESPHome does not issue mDNS service queries, so the service - // query cache is always empty. Every subsequent update() call is pure overhead. - // - // When USE_MDNS_EVENT_DRIVEN_POLLING is defined we arm a bounded polling window from - // WiFiIPStateListener events so update() only runs during the probe+announce phase; - // outside that window no update() calls occur. Otherwise (fallback), we poll at - // MDNS_UPDATE_INTERVAL_MS forever. - static constexpr uint32_t MDNS_UPDATE_INTERVAL_MS = 50; #ifdef USE_MDNS_EVENT_DRIVEN_POLLING - // Boot probe+announce phase is ~9.0s (3*250ms probes + 8*1000ms announces). Window - // includes margin for the initial `rand() % MDNS_PROBE_DELAY` jitter and for the - // debounced internal restart triggered by netif status changes on ESP8266. - static constexpr uint32_t MDNS_POLL_WINDOW_MS = 12000; - // Scheduler IDs (uint32_t variants avoid name hashing/strcmp on cancel paths) + // LEAmDNS has meaningful work only during the probe+announce phase (3×250ms probes + + // 8×1000ms announces, ~9s). Afterwards every internal timer is resetToNeverExpires() + // and update() becomes pure overhead. We arm a bounded polling window from IP state + // listener events so update() runs only during that phase. + static constexpr uint32_t MDNS_UPDATE_INTERVAL_MS = 50; + static constexpr uint32_t MDNS_POLL_WINDOW_MS = 12000; // ~9s phase + jitter/restart margin static constexpr uint32_t MDNS_POLL_ID = 0; static constexpr uint32_t MDNS_POLL_STOP_ID = 1; #endif @@ -133,8 +113,8 @@ class MDNSComponent final : public Component protected: #ifdef USE_MDNS_EVENT_DRIVEN_POLLING - /// Arm a bounded polling window so MDNS.update() runs at MDNS_UPDATE_INTERVAL_MS - /// for MDNS_POLL_WINDOW_MS. A subsequent call replaces the previous window. + /// Arm a fresh MDNS_POLL_WINDOW_MS polling window. Idempotent — re-arming replaces + /// the previous window via the scheduler's atomic cancel-and-add on matching IDs. void start_polling_window_(); #endif /// Helper to set up services and MAC buffers, then call platform-specific registration @@ -181,9 +161,6 @@ class MDNSComponent final : public Component #endif #ifdef USE_RP2040 bool initialized_{false}; -#if !defined(USE_MDNS_EVENT_DRIVEN_POLLING) - bool was_connected_{false}; -#endif #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 c7737bcb14..d1ffee6e61 100644 --- a/esphome/components/mdns/mdns_esp8266.cpp +++ b/esphome/components/mdns/mdns_esp8266.cpp @@ -8,9 +8,7 @@ #include "esphome/core/hal.h" #include "esphome/core/log.h" #include "mdns_component.h" -#ifdef USE_MDNS_WIFI_LISTENER #include "esphome/components/wifi/wifi_component.h" -#endif namespace esphome::mdns { @@ -39,41 +37,24 @@ static void register_esp8266(MDNSComponent *, StaticVectorsetup_buffers_and_register_(register_esp8266); -#ifdef USE_MDNS_EVENT_DRIVEN_POLLING - // Arduino LEAmDNS registers its own LwipIntf::statusChangeCB that calls _restart() - // on every netif status change (link up, IP up, etc.), so we don't trigger begin() - // or restart here — we just cover the probe+announce window with a bounded polling - // schedule. The listener catches subsequent reconnects and re-arms the window. - // ESP8266 has no ethernet driver in the Arduino build so only the WiFi listener - // branch is reachable here. + // LEAmDNS's own LwipIntf::statusChangeCB drives _restart() on netif changes; we only + // need to arm the polling window around the initial probe/announce and each reconnect. wifi::global_wifi_component->add_ip_state_listener(this); this->start_polling_window_(); -#else - // Fallback for builds without a WiFi IP state listener (e.g. clang-tidy without - // codegen defines). Matches the pre-PR behaviour: poll forever at 50ms. - this->set_interval(MDNS_UPDATE_INTERVAL_MS, []() { MDNS.update(); }); -#endif } -#ifdef USE_MDNS_EVENT_DRIVEN_POLLING void MDNSComponent::on_ip_state(const network::IPAddresses &ips, const network::IPAddress &, const network::IPAddress &) { - // ESPHome's WiFiIPStateListener only notifies on IP acquisition (GOT_IP events on - // ESP8266 — see wifi_component_esp8266.cpp), not on IP loss, so every notification - // represents a fresh IP that the LEAmDNS library's lwIP callback will trigger a - // _restart() for. Always re-arm the polling window — start_polling_window_() is - // idempotent (scheduler does atomic cancel-and-add on matching IDs). + // IP listener only fires on acquisition (not loss), so any notification is a fresh + // IP worth re-arming for. start_polling_window_() is idempotent. if (ips[0].is_set()) { this->start_polling_window_(); } } -#endif void MDNSComponent::on_shutdown() { MDNS.close(); diff --git a/esphome/components/mdns/mdns_rp2040.cpp b/esphome/components/mdns/mdns_rp2040.cpp index 8a6a36096f..1ecadea36e 100644 --- a/esphome/components/mdns/mdns_rp2040.cpp +++ b/esphome/components/mdns/mdns_rp2040.cpp @@ -14,7 +14,6 @@ #endif // Arduino-Pico's PolledTimeout.h (pulled in by ESP8266mDNS.h) redefines IRAM_ATTR to empty. -// Save and restore our definition around the include to avoid a redefinition warning. #pragma push_macro("IRAM_ATTR") #undef IRAM_ATTR #include @@ -26,10 +25,7 @@ static void register_rp2040(MDNSComponent *, StaticVectoradd_ip_state_listener(this); - // AFTER_CONNECTION priority means the network may already be up when setup() runs; - // the listener only fires on subsequent state changes, so seed the current state. + // AFTER_CONNECTION priority means the network may already be up; the listener only + // fires on subsequent changes, so seed the current state. { const auto ips = wifi::global_wifi_component->wifi_sta_ip_addresses(); if (ips[0].is_set()) { @@ -78,8 +63,6 @@ void MDNSComponent::setup() { #endif #ifdef USE_MDNS_ETHERNET_LISTENER ethernet::global_eth_component->add_ip_state_listener(this); - // Seed current Ethernet state for the same reason — if the interface is already up - // when mdns setup() runs, we need to kick off begin() + polling here. if (ethernet::global_eth_component->is_connected()) { const auto ips = ethernet::global_eth_component->get_ip_addresses(); if (ips[0].is_set()) { @@ -87,35 +70,11 @@ void MDNSComponent::setup() { } } #endif -#else - // Fallback (no IP state listener available): poll forever, checking connection state - // each tick. - this->set_interval(MDNS_UPDATE_INTERVAL_MS, [this]() { - bool connected = network::is_connected(); - if (connected && !this->was_connected_) { - if (!this->initialized_) { - this->setup_buffers_and_register_(register_rp2040); - this->initialized_ = true; - } else { - MDNS.notifyAPChange(); - } - } - this->was_connected_ = connected; - if (this->initialized_) { - MDNS.update(); - } - }); -#endif } -#ifdef USE_MDNS_EVENT_DRIVEN_POLLING void MDNSComponent::on_ip_state(const network::IPAddresses &ips, const network::IPAddress &, const network::IPAddress &) { - // Both WiFi and Ethernet IP state listeners only notify on IP acquisition (see - // wifi_component_pico_w.cpp and ethernet_component.cpp), not on IP loss, so every - // notification represents a fresh IP that needs a probe/announce cycle. The library's - // internal LwipIntf::stateUpCB is stubbed out on arduino-pico (see setup()), so we - // drive begin/restart ourselves from this callback. + // Listener only fires on IP acquisition (not loss); every event is a fresh IP. if (!ips[0].is_set()) { return; } @@ -127,7 +86,6 @@ void MDNSComponent::on_ip_state(const network::IPAddresses &ips, const network:: } this->start_polling_window_(); } -#endif void MDNSComponent::on_shutdown() { MDNS.close(); diff --git a/tests/components/mdns/common-enabled-ethernet.yaml b/tests/components/mdns/common-enabled-ethernet.yaml deleted file mode 100644 index bfa9321d43..0000000000 --- a/tests/components/mdns/common-enabled-ethernet.yaml +++ /dev/null @@ -1,23 +0,0 @@ -ethernet: - type: W5500 - clk_pin: 18 - mosi_pin: 19 - miso_pin: 16 - cs_pin: 17 - interrupt_pin: 21 - reset_pin: 20 - manual_ip: - static_ip: 192.168.178.56 - gateway: 192.168.178.1 - subnet: 255.255.255.0 - domain: .local - mac_address: "02:AA:BB:CC:DD:01" - -mdns: - disabled: false - services: - - service: _test_service - protocol: _tcp - port: 8888 - txt: - static_string: Anything diff --git a/tests/components/mdns/test-enabled-ethernet.rp2040-ard.yaml b/tests/components/mdns/test-enabled-ethernet.rp2040-ard.yaml deleted file mode 100644 index f84a0bc276..0000000000 --- a/tests/components/mdns/test-enabled-ethernet.rp2040-ard.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common-enabled-ethernet.yaml