diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 182e86daed..9e78e7c48e 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -1676,7 +1676,7 @@ void WiFiComponent::check_connecting_finished(uint32_t now) { this->clear_all_bssid_priorities_(); #ifdef USE_WIFI_FAST_CONNECT - this->save_fast_connect_settings_(); + this->save_fast_connect_settings_(this->wifi_bssid(), get_wifi_channel()); #endif this->release_scan_results_(); @@ -2301,9 +2301,7 @@ bool WiFiComponent::load_fast_connect_settings_(WiFiAP ¶ms) { return false; } -void WiFiComponent::save_fast_connect_settings_() { - bssid_t bssid = wifi_bssid(); - uint8_t channel = get_wifi_channel(); +void WiFiComponent::save_fast_connect_settings_(const bssid_t &bssid, uint8_t channel) { // selected_sta_index_ is always valid here (called only after successful connection) // Fallback to 0 is defensive programming for robustness int8_t ap_index = this->selected_sta_index_ >= 0 ? this->selected_sta_index_ : 0; @@ -2416,6 +2414,25 @@ void WiFiComponent::clear_roaming_state_() { this->roaming_state_ = RoamingState::IDLE; } +#ifdef USE_ESP32 +void WiFiComponent::handle_driver_roam_(const bssid_t &bssid, uint8_t channel) { + // A driver-initiated roam (e.g. 802.11v BTM) re-associates without the state + // machine ever leaving STA_CONNECTED, so check_connecting_finished() never runs. + // Redo its post-connect bookkeeping here. roaming_state_ is deliberately left + // untouched so an in-flight roaming scan is not orphaned. The BSSID and + // channel both come from the connected event so the saved pair is consistent: + // the radio may be off-channel during a roaming scan, and a later queued + // event may have moved the driver on again by the time this one is processed. + this->roaming_last_check_ = App.get_loop_component_start_time(); + this->roaming_attempts_ = 0; + this->roaming_scan_end_ = 0; + this->clear_all_bssid_priorities_(); +#ifdef USE_WIFI_FAST_CONNECT + this->save_fast_connect_settings_(bssid, channel); +#endif +} +#endif + void WiFiComponent::release_scan_results_() { if (!this->keep_scan_results_) { ScanResultsLock lock(this); diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 43e44a135f..a851ea4015 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -781,13 +781,19 @@ class WiFiComponent final : public Component { #ifdef USE_WIFI_FAST_CONNECT bool load_fast_connect_settings_(WiFiAP ¶ms); - void save_fast_connect_settings_(); + void save_fast_connect_settings_(const bssid_t &bssid, uint8_t channel); #endif // Post-connect roaming methods void check_roaming_(uint32_t now); void process_roaming_scan_(); void clear_roaming_state_(); +#ifdef USE_ESP32 + /// Redo post-connect bookkeeping after a driver-initiated roam (e.g. 802.11v BTM) + /// @param bssid The new AP's BSSID, taken from the connected event + /// @param channel The new AP's channel, taken from the connected event + void handle_driver_roam_(const bssid_t &bssid, uint8_t channel); +#endif /// Returns true if a component has requested that roaming scans be suppressed (e.g. during audio playback). bool roaming_suppressed_() const { diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index d78cd21380..783c000f7b 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -825,6 +825,19 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { (const char *) it.ssid, bssid_buf, it.channel, get_auth_mode_str(it.authmode)); #endif s_sta_connected = true; + if (this->state_ == WIFI_COMPONENT_STATE_STA_CONNECTED) { + // Driver-initiated roam: the WIFI_REASON_ROAMING disconnect was ignored, + // so the state machine never left STA_CONNECTED. +#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_INFO + char roam_bssid_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + format_mac_addr_upper(it.bssid, roam_bssid_s); + ESP_LOGI(TAG, "Roamed ssid='%.*s' bssid=" LOG_SECRET("%s") " channel=%u", it.ssid_len, (const char *) it.ssid, + roam_bssid_s, it.channel); +#endif + bssid_t roam_bssid; + std::copy(it.bssid, it.bssid + 6, roam_bssid.begin()); + this->handle_driver_roam_(roam_bssid, it.channel); + } #ifdef USE_WIFI_CONNECT_STATE_LISTENERS // Defer listener notification until state machine reaches STA_CONNECTED // This ensures wifi.connected condition returns true in listener automations