Files
lucky-cat/components/platform/src/wifi_portal_bridge.cpp
T

450 lines
14 KiB
C++

#include "wifi_portal.h"
#include <cstring>
#include <string>
#include "esp_check.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "platform.h"
#include "sdkconfig.h"
#include "ssid_manager.h"
#include "wifi_manager.h"
#if __has_include("wifi_storage_hooks.h")
#include "wifi_storage_hooks.h"
#define WIFI_PORTAL_HAS_STORAGE_HOOKS 1
#else
#define WIFI_PORTAL_HAS_STORAGE_HOOKS 0
#endif
static const char *TAG = "wifi_portal";
#define WIFI_PORTAL_LOCK_TIMEOUT_MS 2000
#define WIFI_PORTAL_DEFAULT_CONNECT_TIMEOUT_MS 10000
#define WIFI_PORTAL_CONNECT_POLL_MS 100
#ifndef CONFIG_TQ_WIFI_STA_POWER_SAVE
#define CONFIG_TQ_WIFI_STA_POWER_SAVE 0
#endif
#ifndef CONFIG_TQ_WIFI_STA_INACTIVE_TIME_SEC
#define CONFIG_TQ_WIFI_STA_INACTIVE_TIME_SEC 15
#endif
static SemaphoreHandle_t s_portal_mutex;
#if ( configSUPPORT_STATIC_ALLOCATION == 1 )
static StaticSemaphore_t s_portal_mutex_buf;
#endif
static bool s_wifi_manager_initialized;
static std::string s_config_ap_ssid_prefix = "TQ-CAT";
static wifi_portal_behavior_hooks_t s_behavior_hooks = {};
static esp_err_t apply_sta_stability_policy(void);
static void install_wifi_storage_hooks(void)
{
static bool s_installed;
if (s_installed) {
return;
}
#if WIFI_PORTAL_HAS_STORAGE_HOOKS
const wifi_storage_hooks_t hooks = {
.set_string = platform_storage_set_string,
.get_string = platform_storage_get_string,
.set_u8 = platform_storage_set_u8,
.get_u8 = platform_storage_get_u8,
.set_i8 = platform_storage_set_i8,
.get_i8 = platform_storage_get_i8,
.erase = platform_storage_erase,
};
wifi_storage_set_hooks(&hooks);
#else
ESP_LOGW(TAG, "Wi-Fi storage hook API unavailable; using component default storage");
#endif
s_installed = true;
}
static void log_saved_wifi_candidates(const char *context)
{
install_wifi_storage_hooks();
const auto &ssid_list = SsidManager::GetInstance().GetSsidList();
ESP_LOGI(TAG,
"%s: %u saved Wi-Fi credential candidate(s)",
context != NULL ? context : "Wi-Fi credentials",
(unsigned)ssid_list.size());
for (size_t i = 0; i < ssid_list.size(); ++i) {
ESP_LOGI(TAG,
"%s: candidate[%u] SSID=\"%s\"",
context != NULL ? context : "Wi-Fi credentials",
(unsigned)i,
ssid_list[i].ssid.c_str());
}
}
struct PortalUnlockGuard {
~PortalUnlockGuard()
{
if (active) {
(void)xSemaphoreGiveRecursive(s_portal_mutex);
}
}
bool active = true;
};
static esp_err_t wifi_portal_lock(void)
{
if (!s_portal_mutex) {
#if ( configSUPPORT_STATIC_ALLOCATION == 1 )
s_portal_mutex = xSemaphoreCreateRecursiveMutexStatic(&s_portal_mutex_buf);
#else
s_portal_mutex = xSemaphoreCreateRecursiveMutex();
#endif
if (!s_portal_mutex) {
return ESP_ERR_NO_MEM;
}
}
TickType_t timeout_ticks = pdMS_TO_TICKS(WIFI_PORTAL_LOCK_TIMEOUT_MS);
if (timeout_ticks == 0) {
timeout_ticks = 1;
}
if (xSemaphoreTakeRecursive(s_portal_mutex, timeout_ticks) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
return ESP_OK;
}
static wifi_portal_behavior_hooks_t snapshot_behavior_hooks(void)
{
wifi_portal_behavior_hooks_t hooks = {};
if (wifi_portal_lock() != ESP_OK) {
return hooks;
}
hooks = s_behavior_hooks;
(void)xSemaphoreGiveRecursive(s_portal_mutex);
return hooks;
}
static void notify_keep_awake(bool keep_awake)
{
wifi_portal_behavior_hooks_t hooks = snapshot_behavior_hooks();
if (hooks.set_keep_awake != NULL) {
hooks.set_keep_awake(keep_awake);
}
}
static void wifi_manager_event_callback(WifiEvent event, const std::string &data)
{
switch (event) {
case WifiEvent::Connecting:
ESP_LOGI(TAG, "Connecting to \"%s\"", data.c_str());
break;
case WifiEvent::Connected:
ESP_LOGI(TAG, "Connected to \"%s\"", data.c_str());
(void)apply_sta_stability_policy();
break;
case WifiEvent::Disconnected:
if (!data.empty()) {
ESP_LOGW(TAG, "Wi-Fi disconnected, reason %s", data.c_str());
}
break;
case WifiEvent::ConfigModeEnter:
ESP_LOGI(TAG, "Wi-Fi configuration portal started");
break;
case WifiEvent::ConfigModeExit:
ESP_LOGI(TAG, "Wi-Fi configuration portal stopped");
notify_keep_awake(false);
break;
case WifiEvent::Scanning:
default:
break;
}
}
static esp_err_t ensure_wifi_manager_initialized(void)
{
install_wifi_storage_hooks();
if (s_wifi_manager_initialized) {
return ESP_OK;
}
WifiManagerConfig config;
config.ssid_prefix = s_config_ap_ssid_prefix;
config.language = "zh-CN";
auto &manager = WifiManager::GetInstance();
if (!manager.Initialize(config)) {
ESP_LOGE(TAG, "Wi-Fi manager initialization failed");
return ESP_FAIL;
}
manager.SetEventCallback(wifi_manager_event_callback);
s_wifi_manager_initialized = true;
return ESP_OK;
}
static void seed_default_credentials(const char *ssid, const char *password)
{
install_wifi_storage_hooks();
auto &manager = SsidManager::GetInstance();
const auto saved_count = manager.GetSsidList().size();
if (ssid == NULL || ssid[0] == '\0') {
ESP_LOGI(TAG,
"No configured Wi-Fi SSID; using %u saved credential candidate(s)",
(unsigned)saved_count);
log_saved_wifi_candidates("Configured Wi-Fi empty; NVS fallback");
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 : "");
ESP_LOGI(TAG,
"Ensured configured Wi-Fi credentials for SSID \"%s\" with %u previous candidate(s)",
ssid,
(unsigned)saved_count);
}
static int normalize_connect_timeout_ms(int timeout_ms)
{
return timeout_ms > 0 ? timeout_ms : WIFI_PORTAL_DEFAULT_CONNECT_TIMEOUT_MS;
}
static esp_err_t apply_sta_stability_policy(void)
{
wifi_ps_type_t ps_type = CONFIG_TQ_WIFI_STA_POWER_SAVE ?
WIFI_PS_MIN_MODEM :
WIFI_PS_NONE;
esp_err_t err = esp_wifi_set_ps(ps_type);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Set Wi-Fi power save failed: %s", esp_err_to_name(err));
return err;
}
uint16_t inactive_sec = CONFIG_TQ_WIFI_STA_INACTIVE_TIME_SEC;
if (inactive_sec < 3U) {
inactive_sec = 3U;
}
err = esp_wifi_set_inactive_time(WIFI_IF_STA, inactive_sec);
if (err != ESP_OK) {
ESP_LOGW(TAG,
"Set Wi-Fi STA inactive time to %u s failed: %s",
(unsigned)inactive_sec,
esp_err_to_name(err));
return err;
}
ESP_LOGI(TAG,
"Wi-Fi STA stability policy: ps=%s inactive=%us",
ps_type == WIFI_PS_NONE ? "none" : "min_modem",
(unsigned)inactive_sec);
return ESP_OK;
}
esp_err_t wifi_portal_init_defaults(const char *seed_ssid, const char *seed_password)
{
ESP_RETURN_ON_ERROR(wifi_portal_lock(), TAG, "Failed to lock Wi-Fi portal");
PortalUnlockGuard guard;
seed_default_credentials(seed_ssid, seed_password);
return ESP_OK;
}
bool wifi_portal_has_saved_credentials(void)
{
install_wifi_storage_hooks();
return !SsidManager::GetInstance().GetSsidList().empty();
}
bool wifi_portal_sta_is_connected(void)
{
if (!s_wifi_manager_initialized) {
return false;
}
return WifiManager::GetInstance().IsConnected();
}
esp_err_t wifi_portal_start_config_ap(const char *ssid_prefix)
{
ESP_RETURN_ON_ERROR(wifi_portal_lock(), TAG, "Failed to lock Wi-Fi portal");
PortalUnlockGuard guard;
if (ssid_prefix != NULL && ssid_prefix[0] != '\0') {
s_config_ap_ssid_prefix = ssid_prefix;
}
ESP_RETURN_ON_ERROR(ensure_wifi_manager_initialized(), TAG, "Failed to initialize Wi-Fi manager");
WifiManager::GetInstance().StartConfigAp();
return ESP_OK;
}
esp_err_t wifi_portal_connect_sta(int timeout_ms)
{
{
ESP_RETURN_ON_ERROR(wifi_portal_lock(), TAG, "Failed to lock Wi-Fi portal");
PortalUnlockGuard guard;
if (!wifi_portal_has_saved_credentials()) {
return ESP_ERR_NOT_FOUND;
}
ESP_LOGI(TAG,
"Starting STA connection with %u saved credential candidate(s)",
(unsigned)SsidManager::GetInstance().GetSsidList().size());
log_saved_wifi_candidates("STA connect");
ESP_RETURN_ON_ERROR(ensure_wifi_manager_initialized(), TAG, "Failed to initialize Wi-Fi manager");
auto &manager = WifiManager::GetInstance();
if (manager.IsConnected()) {
(void)apply_sta_stability_policy();
return ESP_OK;
}
manager.StartStation();
(void)apply_sta_stability_policy();
}
int effective_timeout_ms = normalize_connect_timeout_ms(timeout_ms);
int64_t deadline_us = esp_timer_get_time() + (int64_t)effective_timeout_ms * 1000;
while (esp_timer_get_time() < deadline_us) {
if (WifiManager::GetInstance().IsConnected()) {
return ESP_OK;
}
vTaskDelay(pdMS_TO_TICKS(WIFI_PORTAL_CONNECT_POLL_MS));
}
WifiManager::GetInstance().StopStation();
ESP_LOGW(TAG, "Wi-Fi connect timed out after %d ms", effective_timeout_ms);
return ESP_ERR_TIMEOUT;
}
esp_err_t wifi_portal_reconnect_sta(int timeout_ms)
{
int effective_timeout_ms = normalize_connect_timeout_ms(timeout_ms);
int64_t deadline_us = esp_timer_get_time() + (int64_t)effective_timeout_ms * 1000;
{
ESP_RETURN_ON_ERROR(wifi_portal_lock(), TAG, "Failed to lock Wi-Fi portal");
PortalUnlockGuard guard;
if (!wifi_portal_has_saved_credentials()) {
return ESP_ERR_NOT_FOUND;
}
ESP_RETURN_ON_ERROR(ensure_wifi_manager_initialized(),
TAG,
"Failed to initialize Wi-Fi manager");
ESP_LOGW(TAG,
"Controlled STA reassociation started timeout_ms=%d connected=%d",
effective_timeout_ms,
WifiManager::GetInstance().IsConnected() ? 1 : 0);
esp_err_t disconnect_err = esp_wifi_disconnect();
if (disconnect_err != ESP_OK && disconnect_err != ESP_ERR_WIFI_NOT_CONNECT) {
ESP_LOGW(TAG,
"Controlled STA disconnect returned %s",
esp_err_to_name(disconnect_err));
}
}
int64_t disconnect_deadline_us = esp_timer_get_time() + 2000LL * 1000LL;
if (disconnect_deadline_us > deadline_us) {
disconnect_deadline_us = deadline_us;
}
while (esp_timer_get_time() < disconnect_deadline_us &&
WifiManager::GetInstance().IsConnected()) {
vTaskDelay(pdMS_TO_TICKS(WIFI_PORTAL_CONNECT_POLL_MS));
}
esp_err_t connect_err = esp_wifi_connect();
if (connect_err != ESP_OK) {
ESP_LOGI(TAG,
"Controlled STA reconnect command returned %s; wait for manager auto-reconnect",
esp_err_to_name(connect_err));
}
while (esp_timer_get_time() < deadline_us) {
if (WifiManager::GetInstance().IsConnected()) {
(void)apply_sta_stability_policy();
ESP_LOGI(TAG, "Controlled STA reassociation completed");
return ESP_OK;
}
vTaskDelay(pdMS_TO_TICKS(WIFI_PORTAL_CONNECT_POLL_MS));
}
ESP_LOGW(TAG,
"Controlled STA reassociation timed out after %d ms",
effective_timeout_ms);
return ESP_ERR_TIMEOUT;
}
esp_err_t wifi_portal_save_credentials(const char *ssid, const char *password)
{
install_wifi_storage_hooks();
ESP_RETURN_ON_ERROR(wifi_portal_lock(), TAG, "Failed to lock Wi-Fi portal");
PortalUnlockGuard guard;
ESP_RETURN_ON_FALSE(ssid != NULL && ssid[0] != '\0',
ESP_ERR_INVALID_ARG,
TAG,
"SSID missing");
ESP_RETURN_ON_FALSE(strlen(ssid) <= 32, ESP_ERR_INVALID_ARG, TAG, "SSID too long");
ESP_RETURN_ON_FALSE(password == NULL || strlen(password) <= 64,
ESP_ERR_INVALID_ARG,
TAG,
"Password too long");
SsidManager::GetInstance().AddSsid(ssid, password != NULL ? password : "");
ESP_LOGI(TAG, "Saved Wi-Fi credentials for SSID \"%s\"", ssid);
return ESP_OK;
}
esp_err_t wifi_portal_clear_saved_credentials(void)
{
install_wifi_storage_hooks();
ESP_RETURN_ON_ERROR(wifi_portal_lock(), TAG, "Failed to lock Wi-Fi portal");
PortalUnlockGuard guard;
SsidManager::GetInstance().Clear();
ESP_LOGI(TAG, "Cleared stored Wi-Fi credentials");
return ESP_OK;
}
void wifi_portal_set_behavior_hooks(const wifi_portal_behavior_hooks_t *hooks)
{
if (wifi_portal_lock() != ESP_OK) {
return;
}
if (hooks == NULL) {
memset(&s_behavior_hooks, 0, sizeof(s_behavior_hooks));
} else {
s_behavior_hooks = *hooks;
}
(void)xSemaphoreGiveRecursive(s_portal_mutex);
}