From ae27d5bbaa149ae6fa2a5697cc17b70b938a3699 Mon Sep 17 00:00:00 2001 From: chenjintao Date: Mon, 24 Aug 2026 16:16:16 +0800 Subject: [PATCH] feat(wifi): retain multiple network credentials --- .../runtime/app_composition_default_runtime.c | 6 +- .../runtime/control_plane_runtime_workers.c | 11 +++- components/platform/internal/wifi_portal.h | 10 ++-- .../platform/src/wifi_portal_bridge.cpp | 55 ++++++++++++------- main/Kconfig.projbuild | 13 ++++- sdkconfig.defaults | 1 + 6 files changed, 67 insertions(+), 29 deletions(-) diff --git a/components/app_composition/src/runtime/app_composition_default_runtime.c b/components/app_composition/src/runtime/app_composition_default_runtime.c index 1c9ffc0..b07fcca 100644 --- a/components/app_composition/src/runtime/app_composition_default_runtime.c +++ b/components/app_composition/src/runtime/app_composition_default_runtime.c @@ -22,6 +22,10 @@ #define CONFIG_TQ_BOOT_SOUND_TIMEOUT_MS 3000 #endif +#ifndef CONFIG_TQ_WIFI_CONNECT_CYCLE_TIMEOUT_MS +#define CONFIG_TQ_WIFI_CONNECT_CYCLE_TIMEOUT_MS 20000 +#endif + static const char *TAG = "app_comp_boot"; typedef enum { @@ -194,7 +198,7 @@ static esp_err_t app_composition_run_plan_a_boot_sequence(void) "control_plane_bootstrap_connectivity"); esp_err_t wifi_err = control_plane_bootstrap_connectivity(CONFIG_MULTIMODAL_WIFI_SSID, CONFIG_MULTIMODAL_WIFI_PASSWORD, - CONFIG_MULTIMODAL_CONNECT_TIMEOUT_MS); + CONFIG_TQ_WIFI_CONNECT_CYCLE_TIMEOUT_MS); if (wifi_err != ESP_OK) { ESP_LOGW(TAG, "Connectivity bootstrap failed: %s", esp_err_to_name(wifi_err)); } diff --git a/components/control_plane/src/runtime/control_plane_runtime_workers.c b/components/control_plane/src/runtime/control_plane_runtime_workers.c index d05fd73..322a73e 100644 --- a/components/control_plane/src/runtime/control_plane_runtime_workers.c +++ b/components/control_plane/src/runtime/control_plane_runtime_workers.c @@ -39,7 +39,7 @@ #define CONTROL_PLANE_PTT_ACTIVE_POLL_INTERVAL_MS 80 #define CONTROL_PLANE_WIFI_DISCONNECTED_REFRESH_MS 5000 #define CONTROL_PLANE_WIFI_DISCONNECT_GRACE_MS 3000 -#define CONTROL_PLANE_WIFI_RECONNECT_TIMEOUT_MS 10000 +#define CONTROL_PLANE_WIFI_REASSOCIATION_TIMEOUT_MS 10000 #define CONTROL_PLANE_WIFI_RECONNECT_BASE_DELAY_MS 5000 #define CONTROL_PLANE_WIFI_RECONNECT_MAX_DELAY_MS 60000 #define CONTROL_PLANE_USER_WEB_RETRY_MS 5000 @@ -128,6 +128,10 @@ #define CONFIG_TQ_MINI_PROGRAM_PROVISIONING_RECONNECT_FAILURES 2 #endif +#ifndef CONFIG_TQ_WIFI_CONNECT_CYCLE_TIMEOUT_MS +#define CONFIG_TQ_WIFI_CONNECT_CYCLE_TIMEOUT_MS 20000 +#endif + #ifndef CONFIG_TQ_MINI_PROGRAM_PROVISIONING_ENABLE #define CONFIG_TQ_MINI_PROGRAM_PROVISIONING_ENABLE 0 #endif @@ -1618,7 +1622,8 @@ static esp_err_t control_plane_attempt_wifi_reconnect(void) ESP_LOGI(TAG, "Attempting Wi-Fi STA reconnect"); domain_runtime_behavior_set_keep_awake(true); - esp_err_t err = domain_runtime_wifi_portal_connect_sta(CONTROL_PLANE_WIFI_RECONNECT_TIMEOUT_MS); + esp_err_t err = domain_runtime_wifi_portal_connect_sta( + CONFIG_TQ_WIFI_CONNECT_CYCLE_TIMEOUT_MS); domain_runtime_behavior_set_keep_awake(false); if (err == ESP_OK) { @@ -2195,7 +2200,7 @@ static void control_plane_wifi_state_task(void *arg) (unsigned)CONTROL_PLANE_NETWORK_FAILURE_REASSOC_THRESHOLD); domain_runtime_behavior_set_keep_awake(true); esp_err_t reassoc_err = domain_runtime_wifi_portal_reconnect_sta( - CONTROL_PLANE_WIFI_RECONNECT_TIMEOUT_MS); + CONTROL_PLANE_WIFI_REASSOCIATION_TIMEOUT_MS); domain_runtime_behavior_set_keep_awake(false); connected = control_plane_is_sta_connected(); ESP_LOGW(TAG, diff --git a/components/platform/internal/wifi_portal.h b/components/platform/internal/wifi_portal.h index 6422ac0..b59bb69 100644 --- a/components/platform/internal/wifi_portal.h +++ b/components/platform/internal/wifi_portal.h @@ -15,8 +15,9 @@ typedef struct { /** * @brief Initialize Wi-Fi portal defaults and seed credentials. * - * This writes the provided SSID/password into NVS if no credentials exist yet. - * The underlying Wi-Fi manager is initialized lazily when starting STA or AP. + * This saves the provided SSID/password as the highest-priority entry while + * retaining existing credentials. The underlying Wi-Fi manager is initialized + * lazily when starting STA or AP. */ esp_err_t wifi_portal_init_defaults(const char *seed_ssid, const char *seed_password); @@ -50,8 +51,9 @@ esp_err_t wifi_portal_connect_sta(int timeout_ms); /** * @brief Trial-connect with credentials without persisting them first. * - * On success, the trial credentials atomically replace the saved credential - * list. On failure, the previous in-memory and NVS credentials remain intact. + * On success, the trial credentials are saved as the highest-priority entry + * while older credentials are retained. On failure, the previous in-memory + * and NVS credentials remain intact. */ esp_err_t wifi_portal_connect_sta_with_credentials(const char *ssid, const char *password, diff --git a/components/platform/src/wifi_portal_bridge.cpp b/components/platform/src/wifi_portal_bridge.cpp index 5c05250..6c750be 100644 --- a/components/platform/src/wifi_portal_bridge.cpp +++ b/components/platform/src/wifi_portal_bridge.cpp @@ -125,6 +125,35 @@ static esp_err_t wifi_portal_lock(void) return ESP_OK; } +static void promote_saved_wifi(const std::string &ssid) +{ + if (ssid.empty()) { + return; + } + + if (wifi_portal_lock() != ESP_OK) { + ESP_LOGW(TAG, "Could not promote connected Wi-Fi \"%s\": portal lock timed out", + ssid.c_str()); + return; + } + PortalUnlockGuard guard; + + install_wifi_storage_hooks(); + auto &credentials = SsidManager::GetInstance(); + const auto &ssid_list = credentials.GetSsidList(); + for (size_t i = 0; i < ssid_list.size(); ++i) { + if (ssid_list[i].ssid != ssid) { + continue; + } + if (i > 0U) { + credentials.SetDefaultSsid((int)i); + ESP_LOGI(TAG, "Promoted connected Wi-Fi \"%s\" to highest priority", + ssid.c_str()); + } + return; + } +} + static wifi_portal_behavior_hooks_t snapshot_behavior_hooks(void) { wifi_portal_behavior_hooks_t hooks = {}; @@ -153,6 +182,7 @@ static void wifi_manager_event_callback(WifiEvent event, const std::string &data break; case WifiEvent::Connected: ESP_LOGI(TAG, "Connected to \"%s\"", data.c_str()); + promote_saved_wifi(data); (void)apply_sta_stability_policy(); break; case WifiEvent::Disconnected: @@ -209,24 +239,10 @@ static void seed_default_credentials(const char *ssid, const char *password) return; } - bool configured_ssid_saved = false; - for (const auto &item : manager.GetSsidList()) { - if (item.ssid == ssid) { - configured_ssid_saved = true; - break; - } - } - if (saved_count > 0U && !configured_ssid_saved) { - ESP_LOGI(TAG, - "Configured seed SSID changed to \"%s\"; replacing %u stale credential candidate(s)", - ssid, - (unsigned)saved_count); - manager.Clear(); - } - manager.AddSsid(ssid, password != NULL ? password : ""); + promote_saved_wifi(ssid); ESP_LOGI(TAG, - "Ensured configured Wi-Fi credentials for SSID \"%s\" with %u previous candidate(s)", + "Ensured configured Wi-Fi credentials for SSID \"%s\" while preserving %u previous candidate(s)", ssid, (unsigned)saved_count); } @@ -456,10 +472,10 @@ esp_err_t wifi_portal_connect_sta_with_credentials(const char *ssid, ESP_RETURN_ON_ERROR(wifi_portal_lock(), TAG, "Failed to lock Wi-Fi portal"); PortalUnlockGuard guard; auto &credentials = SsidManager::GetInstance(); - credentials.Clear(); credentials.AddSsid(ssid, password != NULL ? password : ""); + promote_saved_wifi(ssid); ESP_LOGI(TAG, - "Credential trial succeeded and replaced saved Wi-Fi for SSID \"%s\"", + "Credential trial succeeded and saved Wi-Fi \"%s\" as highest priority", ssid); } @@ -558,7 +574,8 @@ esp_err_t wifi_portal_save_credentials(const char *ssid, const char *password) "Password too long"); SsidManager::GetInstance().AddSsid(ssid, password != NULL ? password : ""); - ESP_LOGI(TAG, "Saved Wi-Fi credentials for SSID \"%s\"", ssid); + promote_saved_wifi(ssid); + ESP_LOGI(TAG, "Saved Wi-Fi credentials for SSID \"%s\" as highest priority", ssid); return ESP_OK; } diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index ec0b0ae..d88d17c 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -1090,10 +1090,19 @@ menu "Example Configuration" Password for the seed SSID. config MULTIMODAL_CONNECT_TIMEOUT_MS - int "Wi-Fi connect timeout (ms)" + int "Cloud transport connect timeout (ms)" default 10000 help - Timeout for Wi-Fi STA connection attempts in milliseconds. + Timeout for multimodal HTTP and WebSocket connection attempts. + + config TQ_WIFI_CONNECT_CYCLE_TIMEOUT_MS + int "Wi-Fi candidate connection cycle timeout (ms)" + default 20000 + range 5000 120000 + help + Total time allowed for one scan and connection cycle across + visible saved Wi-Fi candidates. Failed cycles continue through + the runtime reconnect backoff policy. config VOICE_PROMPT_WIFI_SUCCESS_FILE string "Wi-Fi success prompt file" diff --git a/sdkconfig.defaults b/sdkconfig.defaults index f37e7be..99512ec 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -19,6 +19,7 @@ CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=y CONFIG_TQ_MINI_PROGRAM_PROVISIONING_ENABLE=y CONFIG_TQ_MINI_PROGRAM_PROVISIONING_CONNECT_TIMEOUT_MS=30000 CONFIG_TQ_MINI_PROGRAM_PROVISIONING_RECONNECT_FAILURES=2 +CONFIG_TQ_WIFI_CONNECT_CYCLE_TIMEOUT_MS=20000 # External PSRAM (ESP32-S3 octal mode) CONFIG_SPIRAM=y