From 25601434d9739f8eb8101b5ad6775d789105914c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 23 Apr 2026 19:26:01 -0500 Subject: [PATCH] [mdns] Simplify listener logic: always re-arm on IP notify, drop transition tracking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ESPHome's WiFiIPStateListener only notifies on IP acquisition (GOT_IP events), not on IP loss — on disconnect, only the WiFiConnectStateListener's disconnect path fires (see wifi_component_esp8266.cpp:952-962 and wifi_component_pico_w.cpp:340). The previous commit's `ip_was_up_` transition tracking was broken: after the first IP-up event, `ip_was_up_` latched to true and never reset, so subsequent disconnect+reconnect cycles would see has_ip=true && ip_was_up_=true and skip re-arming the polling window. Fix: always re-arm on any IP notification. The scheduler's set_interval/set_timeout with a uint32_t ID already performs atomic cancel-and-add for matching IDs (Scheduler::set_timer_common_ line 232-234), so start_polling_window_ is idempotent and needs no explicit cancel. Drop the ip_was_up_ field and cancel_polling_window_ helper entirely. The !has_ip branch (cancel on disconnect) was dead code: it would never fire because the listener doesn't receive disconnect events. Removing it; the polling window will naturally expire on its own (at most 12s of harmless MDNS.update() calls during a disconnect that isn't followed by reconnect within the window). --- esphome/components/mdns/mdns_component.cpp | 10 +++------ esphome/components/mdns/mdns_component.h | 3 --- esphome/components/mdns/mdns_esp8266.cpp | 14 +++++------- esphome/components/mdns/mdns_rp2040.cpp | 26 ++++++++++++---------- 4 files changed, 23 insertions(+), 30 deletions(-) diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index 88d0b1be31a..c80b9224f4e 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -192,16 +192,12 @@ void MDNSComponent::compile_records_(StaticVectorcancel_polling_window_(); + // Re-arming replaces the previous window. The scheduler's set_interval/set_timeout + // with a uint32_t ID already does atomic cancel-and-add for items sharing that ID + // (see Scheduler::set_timer_common_), so no explicit cancel is needed. this->set_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); }); } - -void MDNSComponent::cancel_polling_window_() { - this->cancel_interval(MDNS_POLL_ID); - this->cancel_timeout(MDNS_POLL_STOP_ID); -} #endif void MDNSComponent::dump_config() { diff --git a/esphome/components/mdns/mdns_component.h b/esphome/components/mdns/mdns_component.h index 6f6e22ee625..7be2f4665ec 100644 --- a/esphome/components/mdns/mdns_component.h +++ b/esphome/components/mdns/mdns_component.h @@ -122,9 +122,6 @@ class MDNSComponent final : public Component /// 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. void start_polling_window_(); - /// Cancel any active polling window. - void cancel_polling_window_(); - bool ip_was_up_{false}; #endif /// Helper to set up services and MAC buffers, then call platform-specific registration using PlatformRegisterFn = void (*)(MDNSComponent *, StaticVector &); diff --git a/esphome/components/mdns/mdns_esp8266.cpp b/esphome/components/mdns/mdns_esp8266.cpp index aa8558dac1b..d6254de5681 100644 --- a/esphome/components/mdns/mdns_esp8266.cpp +++ b/esphome/components/mdns/mdns_esp8266.cpp @@ -51,16 +51,14 @@ void MDNSComponent::setup() { void MDNSComponent::on_ip_state(const network::IPAddresses &ips, const network::IPAddress &, const network::IPAddress &) { - const bool has_ip = ips[0].is_set(); - if (has_ip && !this->ip_was_up_) { - // IP came up. LEAmDNS's internal lwIP callback will call _restart() shortly after - // (if it hasn't already) — arm the polling window so the probe/announce phase is - // serviced regardless of our relative timing vs the library's callback. + // 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). + if (ips[0].is_set()) { this->start_polling_window_(); - } else if (!has_ip && this->ip_was_up_) { - this->cancel_polling_window_(); } - this->ip_was_up_ = has_ip; } void MDNSComponent::on_shutdown() { diff --git a/esphome/components/mdns/mdns_rp2040.cpp b/esphome/components/mdns/mdns_rp2040.cpp index 44db320dbb7..cc2d81fe24a 100644 --- a/esphome/components/mdns/mdns_rp2040.cpp +++ b/esphome/components/mdns/mdns_rp2040.cpp @@ -91,19 +91,21 @@ void MDNSComponent::setup() { #ifdef USE_MDNS_EVENT_DRIVEN_POLLING void MDNSComponent::on_ip_state(const network::IPAddresses &ips, const network::IPAddress &, const network::IPAddress &) { - const bool has_ip = ips[0].is_set(); - if (has_ip && !this->ip_was_up_) { - if (!this->initialized_) { - this->setup_buffers_and_register_(register_rp2040); - this->initialized_ = true; - } else { - MDNS.notifyAPChange(); - } - this->start_polling_window_(); - } else if (!has_ip && this->ip_was_up_) { - this->cancel_polling_window_(); + // ESPHome's WiFiIPStateListener only notifies on IP acquisition (see + // wifi_component_pico_w.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. + if (!ips[0].is_set()) { + return; } - this->ip_was_up_ = has_ip; + if (!this->initialized_) { + this->setup_buffers_and_register_(register_rp2040); + this->initialized_ = true; + } else { + MDNS.notifyAPChange(); + } + this->start_polling_window_(); } #endif