From af04eaaba046b15bcd3551fe1612ddc07f875284 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 14 Dec 2025 12:19:58 -0600 Subject: [PATCH] [wifi] Fix premature connection timeout on LibreTiny/Beken --- esphome/components/wifi/wifi_component.cpp | 19 +++++++++++++++++-- .../wifi/wifi_component_libretiny.cpp | 2 +- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index d46916bfd9..7b8148c033 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -205,6 +205,21 @@ static constexpr uint32_t WIFI_COOLDOWN_DURATION_MS = 500; /// While connecting, WiFi can't beacon the AP properly, so needs longer cooldown static constexpr uint32_t WIFI_COOLDOWN_WITH_AP_ACTIVE_MS = 30000; +/// Timeout for WiFi scan operations +/// This is a fallback in case we don't receive a scan done callback from the WiFi driver. +/// Normal scans complete via callback; this only triggers if something goes wrong. +static constexpr uint32_t WIFI_SCAN_TIMEOUT_MS = 31000; + +/// Timeout for WiFi connection attempts +/// This is a fallback in case we don't receive connection success/failure callbacks. +/// Some platforms (especially LibreTiny/Beken) can take 30-60 seconds to connect, +/// particularly with fast_connect enabled where no prior scan provides channel info. +/// Do not lower this value - connection failures are detected via callbacks, not timeout. +/// If this timeout fires prematurely while a connection is still in progress, it causes +/// cascading failures: the subsequent scan will also fail because the WiFi driver is +/// still busy with the previous connection attempt. +static constexpr uint32_t WIFI_CONNECT_TIMEOUT_MS = 61000; + static constexpr uint8_t get_max_retries_for_phase(WiFiRetryPhase phase) { switch (phase) { case WiFiRetryPhase::INITIAL_CONNECT: @@ -1035,7 +1050,7 @@ __attribute__((noinline)) static void log_scan_result(const WiFiScanResult &res) void WiFiComponent::check_scanning_finished() { if (!this->scan_done_) { - if (millis() - this->action_started_ > 30000) { + if (millis() - this->action_started_ > WIFI_SCAN_TIMEOUT_MS) { ESP_LOGE(TAG, "Scan timeout"); this->retry_connect(); } @@ -1184,7 +1199,7 @@ void WiFiComponent::check_connecting_finished() { } uint32_t now = millis(); - if (now - this->action_started_ > 30000) { + if (now - this->action_started_ > WIFI_CONNECT_TIMEOUT_MS) { ESP_LOGW(TAG, "Connection timeout"); this->retry_connect(); return; diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 4fd64bdfa3..a48b2dec69 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -322,7 +322,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ // wifi_sta_connect_status_() to return IDLE. The main loop then sees // "Unknown connection status 0" (wifi_component.cpp check_connecting_finished) // and calls retry_connect(), aborting a connection that may succeed moments later. - // Real connection failures will have ssid/bssid populated, or we'll hit the 30s timeout. + // Real connection failures will have ssid/bssid populated, or we'll hit the connection timeout. if (it.ssid_len == 0 && s_sta_connecting) { ESP_LOGV(TAG, "Ignoring disconnect event with empty ssid while connecting (reason=%s)", get_disconnect_reason_str(it.reason));