From 20d4fe1a4178444c77375a562dd0498237db88ba Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 26 Aug 2026 10:16:44 -0500 Subject: [PATCH] [mdns] Skip MDNS.update() while the ESP8266 radio cannot transmit (#18785) --- esphome/components/mdns/mdns_esp8266.cpp | 14 +++++++++++++- esphome/components/wifi/wifi_component.cpp | 6 +++--- esphome/components/wifi/wifi_component.h | 7 +++++++ esphome/components/wifi/wifi_component_esp8266.cpp | 2 +- esphome/components/wifi/wifi_component_esp_idf.cpp | 2 +- 5 files changed, 25 insertions(+), 6 deletions(-) diff --git a/esphome/components/mdns/mdns_esp8266.cpp b/esphome/components/mdns/mdns_esp8266.cpp index f6d5786675..1f0b3c9519 100644 --- a/esphome/components/mdns/mdns_esp8266.cpp +++ b/esphome/components/mdns/mdns_esp8266.cpp @@ -41,7 +41,19 @@ static void register_esp8266(MDNSComponent *, StaticVectorset_interval(MDNS_POLL_ID, MDNS_UPDATE_INTERVAL_MS, []() { MDNS.update(); }); + this->set_interval(MDNS_POLL_ID, MDNS_UPDATE_INTERVAL_MS, []() { +#ifdef USE_MDNS_WIFI_LISTENER + // MDNS.update() can suspend the loop in UdpContext::sendTimeout() while a send is + // failing (radio off-channel during a roam scan, or mid reconnect); an incoming + // packet then re-enters LEAmDNS from lwIP and corrupts shared UdpContext state. + // Skip the tick while the radio cannot transmit (#18760), but keep polling while + // the AP is serving clients (AP-only or fallback AP with the STA down). + auto *wifi = wifi::global_wifi_component; + if (wifi->is_roaming() || (!wifi->is_connected() && !wifi->is_ap_active())) + return; +#endif + MDNS.update(); + }); this->set_timeout(MDNS_POLL_STOP_ID, MDNS_POLL_WINDOW_MS, [this]() { this->cancel_interval(MDNS_POLL_ID); }); } #endif diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 127eb50df1..3a42ace424 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -530,7 +530,7 @@ void WiFiComponent::log_discarded_scan_result_(const char *ssid, const uint8_t * #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE // Skip logging during roaming scans to avoid log buffer overflow // (roaming scans typically find many networks but only care about same-SSID APs) - if (this->roaming_state_ == RoamingState::SCANNING) { + if (this->is_roaming_scan_active()) { return; } char bssid_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; @@ -835,7 +835,7 @@ void WiFiComponent::loop() { // Post-connect roaming: check for better AP if (this->post_connect_roaming_) { - if (this->roaming_state_ == RoamingState::SCANNING) { + if (this->is_roaming_scan_active()) { if (this->scan_done_) { this->process_roaming_scan_(); } @@ -2152,7 +2152,7 @@ void WiFiComponent::retry_connect() { // Roam connection failed - transition to reconnecting ESP_LOGD(TAG, "Roam failed, reconnecting (attempt %u/%u)", this->roaming_attempts_, ROAMING_MAX_ATTEMPTS); this->roaming_state_ = RoamingState::RECONNECTING; - } else if (this->roaming_state_ == RoamingState::SCANNING) { + } else if (this->is_roaming_scan_active()) { // Disconnected during roam scan - transition to RECONNECTING so the attempts // counter is preserved when reconnection succeeds (IDLE would reset it) ESP_LOGD(TAG, "Disconnected during roam scan (attempt %u/%u)", this->roaming_attempts_, ROAMING_MAX_ATTEMPTS); diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index ea043fd5c6..bf991beece 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -475,6 +475,13 @@ class WiFiComponent final : public Component { bool is_connected() const { return this->connected_; } + /// True while a post-connect roaming scan holds the radio off-channel. + bool is_roaming_scan_active() const { return this->roaming_state_ == RoamingState::SCANNING; } + + /// True while a post-connect roam is in progress (scanning off-channel, reassociating, + /// or recovering from a failed roam). + bool is_roaming() const { return this->roaming_state_ != RoamingState::IDLE; } + #ifdef USE_ESP32 /// esp_netif handle of the station interface, used by network for default-route /// arbitration. nullptr until wifi_lazy_init_() has run. diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index acaa94b13c..10c973a624 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -717,7 +717,7 @@ bool WiFiComponent::wifi_scan_start_(bool passive) { static constexpr uint32_t SCAN_ACTIVE_MAX_DEFAULT_MS = 500; static constexpr uint32_t SCAN_ACTIVE_MIN_ROAMING_MS = 100; static constexpr uint32_t SCAN_ACTIVE_MAX_ROAMING_MS = 300; - bool roaming = this->roaming_state_ == RoamingState::SCANNING; + bool roaming = this->is_roaming_scan_active(); if (passive) { config.scan_time.passive = roaming ? SCAN_PASSIVE_ROAMING_MS : SCAN_PASSIVE_DEFAULT_MS; } else { diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 24cb060edb..4b339528a2 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -1064,7 +1064,7 @@ bool WiFiComponent::wifi_scan_start_(bool passive) { // When scanning while connected (roaming), return to home channel between // each scanned channel to maintain the connection (helps with BLE/WiFi coexistence) #ifdef CONFIG_SOC_WIFI_SUPPORTED - if (this->roaming_state_ == RoamingState::SCANNING) { + if (this->is_roaming_scan_active()) { config.coex_background_scan = true; } #endif