From 5b234bcc201b44774a678bccf4adf1f39000b2bb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 15 Oct 2026 12:53:51 -1000 Subject: [PATCH] [wifi] Leave the cooldown from the loop after a credential save, not from the caller's context (#20802) --- esphome/components/wifi/wifi_component.cpp | 28 ++++++++-------------- esphome/components/wifi/wifi_component.h | 2 -- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 156ba6e9ffa..c37ec44384b 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -854,10 +854,11 @@ void WiFiComponent::loop() { switch (this->state_) { case WIFI_COMPONENT_STATE_COOLDOWN: { this->status_set_warning(LOG_STR("waiting to reconnect")); - // Skip cooldown if new credentials were provided while connecting + // Credentials not tried yet: retry whatever the driver reports. The flag outlives an adapter restart, + // which only lands back here, and start_connecting() clears it once they are tried if (this->skip_cooldown_next_cycle_) { - this->skip_cooldown_next_cycle_ = false; - this->check_connecting_finished(now); + ESP_LOGD(TAG, "Exiting cooldown early due to new WiFi credentials"); + this->retry_connect(); break; } // Use longer cooldown when a portal/improv is active to avoid disrupting a user on the AP @@ -1183,7 +1184,7 @@ void WiFiComponent::set_sta(const WiFiAP &ap) { this->init_sta(1); this->add_sta(ap); this->selected_sta_index_ = 0; - // When new credentials are set (e.g., from improv), skip cooldown to retry immediately + // New credentials end the next cooldown early; start_connecting() clears this once they are tried this->skip_cooldown_next_cycle_ = true; #ifdef USE_WIFI_LEASE_CACHE // Index 0 is a different network now, so its cached lease must not be re-applied @@ -1252,29 +1253,18 @@ void WiFiComponent::save_wifi_sta(const char *ssid, const char *password) { WiFiAP sta{}; sta.set_ssid(ssid); sta.set_password(password); + // No retry here: a captive portal save runs in lwIP's context on the ESP8266, where retry_connect() yields and + // panics. set_sta() flags the credentials and loop() tries them this->set_sta(sta); - - // Trigger connection attempt (exits cooldown if needed, no-op if already connecting/connected) - this->connect_soon_(); -} - -void WiFiComponent::connect_soon_() { #ifdef USE_WIFI_AP_EXCLUSIVE - // New credentials from the portal: the AP pauses for them once its answer - // is out, and the STA state machine picks them up from there. + // The AP pauses for the new credentials once its answer is out, and the STA state machine picks them up if (this->ap_setup_) { this->set_timeout(AP_HANDOVER_TIMEOUT_ID, WIFI_AP_EXCLUSIVE_HANDOVER_MS, [this]() { if (this->ap_setup_) this->pause_exclusive_ap_(); }); - return; } #endif - // Only trigger retry if we're in cooldown - if already connecting/connected, do nothing - if (this->state_ == WIFI_COMPONENT_STATE_COOLDOWN) { - ESP_LOGD(TAG, "Exiting cooldown early due to new WiFi credentials"); - this->retry_connect(); - } } void WiFiComponent::start_connecting(const WiFiAP &ap) { @@ -1366,6 +1356,8 @@ void WiFiComponent::start_connecting(const WiFiAP &ap) { // enters COOLDOWN without calling start_connecting(), and ESP8266 clears it again // after wifi_station_connect(), whose callbacks fire synchronously. this->error_from_callback_ = false; + // Every attempt passes through here, so the credentials set_sta() flagged are tried now + this->skip_cooldown_next_cycle_ = false; if (!this->wifi_sta_connect_(ap)) { ESP_LOGE(TAG, "wifi_sta_connect_ failed"); diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 8cfdaaff00c..1048df19c39 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -822,8 +822,6 @@ class WiFiComponent final : public Component { return true; } - void connect_soon_(); - bool wifi_loop_(); #ifdef USE_ESP8266 void process_pending_callbacks_();