From 874b30d97d932df8985cf4daaec691c6d77baffd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Mar 2026 18:04:58 -1000 Subject: [PATCH] [wifi] Fix roaming attempt counter reset on disconnect during scan When WiFi disconnects while a roaming scan is in progress, the SCANNING state was transitioning to IDLE in retry_connect(). When the device then reconnected, check_connecting_finished() treated the IDLE state as a normal connection and reset roaming_attempts_ to 0. This caused the roaming counter to perpetually restart at 1/3 instead of progressing to 2/3, 3/3, creating an infinite loop of roaming scans every 5 minutes. This is most likely to occur on ESP8266 where disconnect callbacks run immediately in SDK system context (setting error_from_callback_ before the main loop can process scan results), compared to ESP32 IDF where events are queued and the scan-done event is typically processed before the disconnect. Fix by transitioning to RECONNECTING instead of IDLE when a disconnect occurs during SCANNING. This preserves the attempts counter on successful reconnection, allowing the device to reach the 3-attempt limit and stop scanning. --- esphome/components/wifi/wifi_component.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 08065a7544e..8585d49ee15 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -269,11 +269,11 @@ bool CompactString::operator==(const StringRef &other) const { /// │ │ │ /// │ ┌──────────────┼──────────────┐ │ /// │ ↓ ↓ ↓ │ -/// │ scan error no better AP +10 dB better AP │ +/// │ disconnect no better AP +10 dB better AP │ /// │ │ │ │ │ /// │ ↓ ↓ ↓ │ /// │ ┌──────────────────────────────┐ ┌──────────────────────────┐ │ -/// │ │ → IDLE │ │ CONNECTING │ │ +/// │ │ → RECONNECTING │ │ CONNECTING │ │ /// │ │ (counter preserved) │ │ (process_roaming_scan_) │ │ /// │ └──────────────────────────────┘ └────────────┬─────────────┘ │ /// │ │ │ @@ -296,7 +296,7 @@ bool CompactString::operator==(const StringRef &other) const { /// │ Key behaviors: │ /// │ - After 3 checks: attempts >= 3, stop checking │ /// │ - Non-roaming disconnect: clear_roaming_state_() resets counter │ -/// │ - Scan error (SCANNING→IDLE): counter preserved │ +/// │ - Disconnect during scan (SCANNING→RECONNECTING): counter preserved │ /// │ - Roaming success (CONNECTING→IDLE): counter reset (can roam again) │ /// │ - Roaming fail (RECONNECTING→IDLE): counter preserved (ping-pong) │ /// └──────────────────────────────────────────────────────────────────────┘ @@ -2072,9 +2072,10 @@ void WiFiComponent::retry_connect() { 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) { - // Roam scan failed (e.g., scan error on ESP8266) - go back to idle, keep counter - ESP_LOGD(TAG, "Roam scan failed (attempt %u/%u)", this->roaming_attempts_, ROAMING_MAX_ATTEMPTS); - this->roaming_state_ = RoamingState::IDLE; + // 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); + this->roaming_state_ = RoamingState::RECONNECTING; } else if (this->roaming_state_ == RoamingState::IDLE) { // Not a roaming-triggered reconnect, reset state this->clear_roaming_state_();