From 169c0550119a96f1677029b1478542c4e38390a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edvard=20Filistovi=C4=8D?= Date: Tue, 25 Aug 2026 04:25:44 +0300 Subject: [PATCH] [wifi] libretiny: reset the STA state on synchronous connect failure (#18719) --- esphome/components/wifi/wifi_component.h | 8 +++++ .../wifi/wifi_component_libretiny.cpp | 35 ++++++++++++++++--- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index ada7be4ba4..382d3d5932 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -951,6 +951,14 @@ class WiFiComponent final : public Component { // On ESP8266, written from SDK system context (wifi_event_callback) — // uint8_t writes are atomic on Xtensa LX106 so no synchronization is needed. uint8_t sta_state_{0}; +#endif +#ifdef USE_LIBRETINY + // First attempt since STA-up (re-armed on every STA off->on); the + // pre-attempt teardown is skipped then. + bool lt_first_connect_attempt_{true}; + // A self-inflicted disconnect from that teardown is pending; it must not + // consume an ignored-disconnect slot. + bool lt_teardown_event_pending_{false}; #endif RetryHiddenMode retry_hidden_mode_{RetryHiddenMode::BLIND_RETRY}; RoamingState roaming_state_{RoamingState::IDLE}; diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index e3c08416e8..63a63e7342 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -115,6 +115,8 @@ bool WiFiComponent::wifi_mode_(optional sta, optional ap) { if (enable_sta && !current_sta) { ESP_LOGV(TAG, "Enabling STA"); + // Fresh STA stack: skip the pre-attempt teardown again. + this->lt_first_connect_attempt_ = true; } else if (!enable_sta && current_sta) { ESP_LOGV(TAG, "Disabling STA"); } @@ -202,10 +204,21 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { if (!this->wifi_mode_(true, {})) return false; - String ssid = WiFi.SSID(); - if (ssid && strcmp(ssid.c_str(), ap.ssid_.c_str()) != 0) { - WiFi.disconnect(); + // Tear down any live session so begin() re-fires its events; skipped on the + // first attempt after STA-up (nothing to tear down, and BK7231N on the older + // Beken SDK did not come back from it). The flag is per-attempt and armed + // only for a live session: an idle disconnect may emit no event, and a stale + // flag would swallow this attempt's first real failure. + this->lt_teardown_event_pending_ = false; + if (!this->lt_first_connect_attempt_) { + const bool was_live = WiFi.status() == WL_CONNECTED; + if (WiFi.disconnect()) { + this->lt_teardown_event_pending_ = was_live; + } else { + ESP_LOGD(TAG, "Pre-connect teardown returned false"); + } } + this->lt_first_connect_attempt_ = false; #ifdef USE_WIFI_MANUAL_IP if (!this->wifi_sta_ip_config_(ap.get_manual_ip())) { @@ -227,7 +240,10 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { ap.get_channel(), // 0 = auto ap.has_bssid() ? ap.get_bssid().data() : NULL); if (status != WL_CONNECTED) { - ESP_LOGW(TAG, "esp_wifi_connect failed: %d", status); + ESP_LOGW(TAG, "WiFi.begin failed: %d", status); + // Without this reset the state machine stays at CONNECTING and each retry + // stalls for the full connect timeout (46 s). + this->sta_state_ = static_cast(LTWiFiSTAState::ERROR_FAILED); return false; } @@ -455,6 +471,9 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) { break; } case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: { + // Processed in queue order, so a teardown event still ahead of this + // CONNECTED was already consumed; a leftover flag is stale. + this->lt_teardown_event_pending_ = false; auto &it = event->data.sta_connected; char bssid_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; format_mac_addr_upper(it.bssid, bssid_buf); @@ -482,6 +501,14 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) { case ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED: { auto &it = event->data.sta_disconnected; + // Consume the disconnect our own teardown queued, without spending an + // ignore slot. Ungated on SSID and state: the flag is armed only for this + // attempt's teardown of a live session. + if (this->lt_teardown_event_pending_ && it.reason != WIFI_REASON_NO_AP_FOUND) { + this->lt_teardown_event_pending_ = false; + break; + } + // LibreTiny can send spurious disconnect events with empty ssid/bssid during connection. // These are typically "Association Leave" events that don't indicate actual failures: // [W][wifi_lt]: Disconnected ssid='' bssid=00:00:00:00:00:00 reason='Association Leave'