diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index 3e07c91d53..dc86a314da 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -172,13 +172,10 @@ async def to_code(config): # ESP8266 and RP2040 use a WiFi 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. - # - # ESP8266 has no ethernet driver in the Arduino build, so it's always a WiFi - # device — the listener is unconditional. RP2040 supports the W5500 ethernet - # shield without WiFi, so the listener is requested only when WiFi is present; - # ethernet-only RP2040 builds fall back to the legacy polling loop. - if CORE.is_esp8266 or (CORE.is_rp2040 and "wifi" in CORE.config): + # per minute) and its scheduler overhead. When WiFi is absent (e.g. an + # ethernet-only RP2040 build) the component falls back to the legacy polling + # loop at MDNS_UPDATE_INTERVAL_MS. + if (CORE.is_esp8266 or CORE.is_rp2040) and "wifi" in CORE.config: from esphome.components import wifi wifi.request_wifi_ip_state_listener() diff --git a/esphome/components/mdns/mdns_component.h b/esphome/components/mdns/mdns_component.h index 7be2f4665e..32a365bb62 100644 --- a/esphome/components/mdns/mdns_component.h +++ b/esphome/components/mdns/mdns_component.h @@ -5,13 +5,12 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" -// Event-driven polling is used whenever the scheduler-backed MDNS.update() interval -// needs to be gated on network state (ESP8266, or RP2040 with WiFi). ESP8266 mDNS -// always runs over WiFi — there is no ethernet driver for ESP8266 in the Arduino -// build — so this path is unconditional on ESP8266. RP2040 can run mDNS over the -// W5500 ethernet shield without WiFi, so it falls back to the legacy polling loop -// when WiFi is absent. -#if defined(USE_ESP8266) || (defined(USE_RP2040) && defined(USE_WIFI) && defined(USE_WIFI_IP_STATE_LISTENERS)) +// Event-driven polling replaces the legacy set_interval() loop on platforms that need +// scheduler-backed MDNS.update() (ESP8266, RP2040). It's enabled when a WiFi IP state +// listener slot is available — the mdns Python to_code() requests one when WiFi is in +// the config. If it's not (e.g. clang-tidy running without full codegen, or an +// ethernet-only RP2040 build), the component falls back to the legacy polling loop. +#if (defined(USE_ESP8266) || defined(USE_RP2040)) && defined(USE_WIFI) && defined(USE_WIFI_IP_STATE_LISTENERS) #include "esphome/components/network/ip_address.h" #include "esphome/components/wifi/wifi_component.h" #define USE_MDNS_EVENT_DRIVEN_POLLING @@ -68,7 +67,6 @@ class MDNSComponent final : public Component void setup() override; void dump_config() override; -#ifdef USE_MDNS_EVENT_DRIVEN_POLLING // 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 @@ -80,10 +78,12 @@ class MDNSComponent final : public Component // resetToNeverExpires(). ESPHome does not issue mDNS service queries, so the service // query cache is always empty. Every subsequent update() call is pure overhead. // - // Instead of polling forever, we arm a bounded polling window driven by - // WiFiIPStateListener events. A fresh window covers each probe/announce cycle that - // follows initial connect or reconnect; outside the window no update() calls occur. + // 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. diff --git a/esphome/components/mdns/mdns_esp8266.cpp b/esphome/components/mdns/mdns_esp8266.cpp index d6254de568..072be497db 100644 --- a/esphome/components/mdns/mdns_esp8266.cpp +++ b/esphome/components/mdns/mdns_esp8266.cpp @@ -7,7 +7,9 @@ #include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" +#ifdef USE_MDNS_EVENT_DRIVEN_POLLING #include "esphome/components/wifi/wifi_component.h" +#endif #include "mdns_component.h" namespace esphome::mdns { @@ -37,18 +39,27 @@ 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. 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 @@ -60,6 +71,7 @@ void MDNSComponent::on_ip_state(const network::IPAddresses &ips, const network:: this->start_polling_window_(); } } +#endif void MDNSComponent::on_shutdown() { MDNS.close();