450 lines
14 KiB
C++
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);
|
|
}
|