mirror of
https://github.com/esphome/esphome.git
synced 2026-09-17 18:18:43 +00:00
[wifi] Fix roaming counter reset from delayed disconnect after scan
On ESP8266, the roaming scan goes off-channel for ~3 seconds. Some APs aggressively disconnect the client (Beacon Timeout) 8-20 seconds after the scan finishes. By that time, process_roaming_scan_() has already set roaming_state_ back to IDLE. When retry_connect() runs, it sees IDLE and calls clear_roaming_state_(), resetting the attempts counter to 0. This creates an infinite loop of "attempt 1/3" every 5 minutes. The aggressive AP behavior cannot be fixed from the ESP side, but we can prevent the counter from resetting so roaming eventually stops after 3 attempts. Fix by recording when the roaming scan completes and adding a 30s grace period. If a disconnect occurs within that window, the state transitions to RECONNECTING (preserving the counter) instead of clearing it. Closes https://github.com/esphome/esphome/issues/15124
This commit is contained in:
@@ -297,6 +297,7 @@ bool CompactString::operator==(const StringRef &other) const {
|
||||
/// │ - After 3 checks: attempts >= 3, stop checking │
|
||||
/// │ - Non-roaming disconnect: clear_roaming_state_() resets counter │
|
||||
/// │ - Disconnect during scan (SCANNING→RECONNECTING): counter preserved │
|
||||
/// │ - Disconnect after scan (within grace period): counter preserved │
|
||||
/// │ - Roaming success (CONNECTING→IDLE): counter reset (can roam again) │
|
||||
/// │ - Roaming fail (RECONNECTING→IDLE): counter preserved (ping-pong) │
|
||||
/// └──────────────────────────────────────────────────────────────────────┘
|
||||
@@ -2073,8 +2074,16 @@ void WiFiComponent::retry_connect() {
|
||||
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_();
|
||||
// Check if a roaming scan recently completed - on ESP8266, going off-channel
|
||||
// during scan can cause a delayed Beacon Timeout 8-20 seconds after scan finishes.
|
||||
// Transition to RECONNECTING so the attempts counter is preserved on reconnect.
|
||||
if (this->roaming_scan_end_ != 0 && millis() - this->roaming_scan_end_ < ROAMING_SCAN_GRACE_PERIOD) {
|
||||
ESP_LOGD(TAG, "Disconnect after roam scan (attempt %u/%u)", this->roaming_attempts_, ROAMING_MAX_ATTEMPTS);
|
||||
this->roaming_state_ = RoamingState::RECONNECTING;
|
||||
} else {
|
||||
// Not a roaming-triggered reconnect, reset state
|
||||
this->clear_roaming_state_();
|
||||
}
|
||||
}
|
||||
// RECONNECTING: keep state and counter, still trying to reconnect
|
||||
|
||||
@@ -2307,6 +2316,7 @@ bool WiFiScanResult::operator==(const WiFiScanResult &rhs) const { return this->
|
||||
void WiFiComponent::clear_roaming_state_() {
|
||||
this->roaming_attempts_ = 0;
|
||||
this->roaming_last_check_ = 0;
|
||||
this->roaming_scan_end_ = 0;
|
||||
this->roaming_state_ = RoamingState::IDLE;
|
||||
}
|
||||
|
||||
@@ -2388,6 +2398,9 @@ void WiFiComponent::process_roaming_scan_() {
|
||||
this->scan_done_ = false;
|
||||
// Default to IDLE - will be set to CONNECTING if we find a better AP
|
||||
this->roaming_state_ = RoamingState::IDLE;
|
||||
// Record when scan completed so delayed disconnects (e.g., ESP8266 Beacon Timeout)
|
||||
// can be attributed to the scan and avoid resetting the attempts counter
|
||||
this->roaming_scan_end_ = millis();
|
||||
|
||||
// Get current connection info
|
||||
int8_t current_rssi = this->wifi_rssi();
|
||||
|
||||
@@ -779,6 +779,10 @@ class WiFiComponent final : public Component {
|
||||
static constexpr int8_t ROAMING_MIN_IMPROVEMENT = 10; // dB
|
||||
static constexpr int8_t ROAMING_GOOD_RSSI = -49; // Skip scan if signal is excellent
|
||||
static constexpr uint8_t ROAMING_MAX_ATTEMPTS = 3;
|
||||
// Grace period after roaming scan completes. If WiFi disconnects within this
|
||||
// window (e.g., ESP8266 Beacon Timeout caused by going off-channel during scan),
|
||||
// the disconnect is treated as roaming-related and the attempts counter is preserved.
|
||||
static constexpr uint32_t ROAMING_SCAN_GRACE_PERIOD = 30 * 1000; // 30 seconds
|
||||
|
||||
// 4-byte members
|
||||
float output_power_{NAN};
|
||||
@@ -786,6 +790,7 @@ class WiFiComponent final : public Component {
|
||||
uint32_t last_connected_{0};
|
||||
uint32_t reboot_timeout_{};
|
||||
uint32_t roaming_last_check_{0};
|
||||
uint32_t roaming_scan_end_{0}; // Timestamp when last roaming scan completed
|
||||
#ifdef USE_WIFI_AP
|
||||
uint32_t ap_timeout_{};
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user