目录
ESP-ADF wifi_service子模块wifi_ssid_manager凭证管理函数详解
版本信息: v2.7-65-gcf908721
本章节分析的源码位于
/components/wifi_service/src/wifi_ssid_manager.c
文件
WiFi凭证管理函数分析
ESP-ADF的wifi_ssid_manager模块提供了一组函数用于实现WiFi凭证的存储和管理,包括保存SSID和密码、擦除存储的凭证等功能。这些函数构成了WiFi服务的持久化存储基础,使设备能够保存和管理多个WiFi网络信息。
wifi_ssid_manager_save
wifi_ssid_manager_save
函数用于保存WiFi的SSID和密码到NVS闪存。下面是其源码实现:
/**
* @brief 保存WiFi SSID和密码
*
* 该函数完成以下主要操作:
* 1. 验证SSID和密码长度是否合法
* 2. 检查SSID是否已存在
* 3. 确定存储位置(新增或覆盖)
* 4. 将SSID和密码保存到NVS存储
* 5. 更新配置信息
*
* 本函数在SSID数量达到上限时使用FIFO(先进先出)策略,
* 即会删除最早保存的SSID以腾出空间保存新的SSID。
*
* @param handle WiFi SSID管理器句柄
* @param ssid WiFi网络名称
* @param pwd WiFi密码
* @return ESP_OK表示成功,ESP_FAIL表示失败
*/
esp_err_t wifi_ssid_manager_save(wifi_ssid_manager_handle_t handle, const char *ssid, const char *pwd)
{
esp_err_t ret = ESP_OK;
// 检查SSID和密码长度是否超出限制
if (strlen(ssid) >= WIFI_SSID_MAX_LENGTH || strlen(pwd) >= WIFI_PWD_MAX_LENGTH) {
ESP_LOGE(TAG, "The length of wifi ssid or password is too long");
return ESP_FAIL;
}
AUDIO_NULL_CHECK(TAG, handle, return ESP_FAIL);
// 获取当前SSID配置信息
nvs_ssid_conf_t conf = {0};
nvs_ssid_list_conf_get(handle, &conf);
// 检查SSID是否已存在
int8_t stored_id = get_stored_id_by_ssid(handle, conf.exsit_ssid_num, ssid);
uint8_t key_id = 0;
if (stored_id < 0) { // SSID不存在,需要新增
if (conf.exsit_ssid_num < conf.max_ssid_num) {
// 还有存储空间,直接添加
key_id = conf.exsit_ssid_num;
conf.latest_ssid = conf.exsit_ssid_num;
conf.exsit_ssid_num++;
} else {
// 存储空间已满,使用FIFO策略
key_id = nvs_get_write_id(handle, conf.exsit_ssid_num);
conf.latest_ssid = key_id;
}
} else {
// SSID已存在,直接更新
key_id = stored_id;
conf.latest_ssid = stored_id;
}
// 准备并保存WiFi信息
nvs_stored_info_t info = {
.cnt = 0,
.choosen = false,
};
memcpy(info.ssid, ssid, strlen(ssid));
memcpy(info.pwd, pwd, strlen(pwd));
// 执行存储操作
ret |= nvs_wifi_info_save(handle, key_id, &info);
ret |= nvs_set_counter(handle, conf.exsit_ssid_num);
ret |= nvs_ssid_list_conf_save(handle, &conf);
ret |= nvs_reset_choosen_flag(handle, &conf);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Fail to save url to nvs, ret = 0x%x", ret);
return ESP_FAIL;
}
return ESP_OK;
}
下面的流程图展示了wifi_ssid_manager_save
函数的执行流程:
这个函数实现了以下几个关键特性:
- 自动去重:当保存已存在的SSID时,直接更新其密码
- 有限队列:当SSID数量达到上限时,使用FIFO策略覆盖最旧的SSID
- 标记最新:总是将最新保存的SSID标记为latest_ssid,便于优先连接
- 重置标志:保存新SSID后重置所有choosen标志,确保连接逻辑的正确性
wifi_ssid_manager_erase_all
wifi_ssid_manager_erase_all
函数用于擦除所有保存的WiFi凭证。下面是其源码实现:
/**
* @brief 擦除所有保存的WiFi凭证
*
* 该函数完成以下主要操作:
* 1. 获取当前最大SSID数量限制
* 2. 清除所有WiFi信息命名空间数据
* 3. 清除配置命名空间数据
* 4. 重置配置信息(仅保留最大SSID数量设置)
*
* 调用此函数后,所有保存的WiFi网络信息将被删除,
* 但管理器的最大SSID数量限制会被保留。
*
* @param handle WiFi SSID管理器句柄
* @return ESP_OK表示成功,或者错误码
*/
esp_err_t wifi_ssid_manager_erase_all(wifi_ssid_manager_handle_t handle)
{
AUDIO_NULL_CHECK(TAG, handle, return ESP_FAIL);
esp_err_t ret = ESP_OK;
// 保存最大SSID数量限制
uint8_t max_ssid_num = 0;
nvs_ssid_conf_t conf = {0};
ret |= nvs_ssid_list_conf_get(handle, &conf);
max_ssid_num = conf.max_ssid_num;
// 重置配置,仅保留最大数量限制
memset(&conf, 0, sizeof(nvs_ssid_conf_t));
conf.max_ssid_num = max_ssid_num;
// 清除所有NVS数据
action_result_t result = { 0 };
ret |= esp_dispatcher_execute_with_func(handle->dispatcher, nvs_action_erase_all, (void *)handle->info_nvs, NULL, &result);
ret |= esp_dispatcher_execute_with_func(handle->dispatcher, nvs_action_erase_all, (void *)handle->conf_nvs, NULL, &result);
// 保存重置后的配置
ret |= nvs_ssid_list_conf_save(handle, &conf);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Fail to erase nvs flash");
}
return ret;
}
下面的流程图展示了wifi_ssid_manager_erase_all
函数的执行流程:
这个函数的主要特点包括:
- 保留配置限制:清除所有凭证但保留最大SSID数量设置,保持管理器功能的一致性
- 全面清除:同时清除WiFi信息和配置命名空间,确保所有凭证都被删除
- 重置状态:重新初始化配置信息,将存储状态恢复到初始状态
- 错误累积:使用
|=
运算符累积错误码,确保任何步骤失败都能被检测到
内部实现机制
存储策略分析
WiFi凭证管理函数采用了以下存储策略:
-
双空间存储:使用两个NVS命名空间分别存储配置元数据和实际WiFi凭证
WIFI_CONF_NVS_NAMESPACE
:存储配置元数据,如最大SSID数量、存储状态等WIFI_INFO_NVS_NAMESPACE
:存储实际的WiFi凭证(SSID和密码)
-
FIFO替换策略:当存储空间已满时,采用先进先出策略替换最早存储的凭证
- 使用计数器(
cnt
)跟踪每个SSID的存储顺序 - 通过
nvs_get_write_id
函数识别最旧的SSID
- 使用计数器(
-
自动去重:保存时自动检查SSID是否已存在,避免重复存储
- 使用
get_stored_id_by_ssid
函数查找SSID是否已存在 - 如已存在,直接更新对应的密码信息
- 使用
-
状态标记:使用多个标志位管理凭证状态
latest_ssid
:标记最新保存的SSID,用于优先连接choosen
:标记当前选择的SSID,用于跟踪连接状态
内部数据流向
凭证管理函数内部的数据流向如下:
关键辅助函数分析
凭证管理函数依赖几个关键的内部辅助函数:
重要的内部辅助函数详解
以下三个函数是WiFi凭证管理的重要内部组件,虽然不直接暴露给用户,但它们实现了凭证管理的核心功能:
get_stored_id_by_ssid
/**
* @brief 查找指定SSID在存储中的索引ID
*
* 遍历所有已存储的WiFi凭证,查找与给定参数匹配的SSID。
* 该函数是实现自动去重功能的核心,确保相同的SSID不会
* 多次添加到存储中。
*
* @param handle WiFi SSID管理器句柄
* @param exsit_ssid_num 当前存在的SSID数量
* @param ssid 要查找的SSID字符串
* @return 成功返回找到的SSID索引,失败返回ESP_FAIL(-1)
*/
static int8_t get_stored_id_by_ssid(wifi_ssid_manager_handle_t handle, uint8_t exsit_ssid_num, const char *ssid)
{
// 初始化信息结构体
nvs_stored_info_t info = {0};
// 遍历所有存储的WiFi凭证
for (int i = 0; i < exsit_ssid_num; i++) {
// 清空信息结构,准备读取下一个凭证
memset(&info, 0, sizeof(nvs_stored_info_t));
// 从存储中获取凭证信息
nvs_wifi_info_get(handle, i, &info);
// 快速过滤:比较SSID长度,如果不同则跳过
if (strlen(info.ssid) != strlen(ssid)) {
continue;
}
// 比较SSID内容是否相同
if (strcmp(info.ssid, ssid) == 0) {
ESP_LOGD(TAG, "Found the same ssid in flash, update it");
return i; // 返回找到的凭证索引
}
}
// 未找到匹配项,返回失败
return ESP_FAIL;
}
该函数用于遍历已存储的所有WiFi凭证,判断指定的SSID是否已存在。它先比较SSID长度进行快速过滤,如果长度相同才进一步比较内容。这个函数在凭证保存时起到了关键作用,确保了同一网络的凭证不会重复存储,而是直接更新密码。
nvs_get_write_id
/**
* @brief 获取应覆盖的WiFi凭证ID
*
* 当存储空间已满时,该函数用于确定应该覆盖哪一个现有的
* WiFi凭证。该函数实现了FIFO(先进先出)策略,即覆盖存储时间
* 最长的凭证。
*
* @param handle WiFi SSID管理器句柄
* @param exsit_ssid_num 当前存在的SSID数量
* @return 需覆盖的WiFi凭证ID
*/
static uint8_t nvs_get_write_id(wifi_ssid_manager_handle_t handle, uint8_t exsit_ssid_num)
{
// 初始化计数器最大值和对应的ID
uint8_t max_cnt = 0, max_cnt_id = 0;
nvs_stored_info_t info = {0};
// 遍历所有存储的WiFi凭证
for (int i = 0; i < exsit_ssid_num; i++) {
// 清空信息结构体
memset(&info, 0, sizeof(nvs_stored_info_t));
// 获取凭证信息
nvs_wifi_info_get(handle, i, &info);
// 如果当前凭证的计数器值大于或等于已记录的最大值
// 则更新最大计数器值和对应ID
if (info.cnt >= max_cnt) {
max_cnt = info.cnt;
max_cnt_id = i;
}
}
// 返回计数器值最大的凭证ID
return max_cnt_id;
}
该函数在存储空间已满需要替换现有凭证时使用。它通过遍历所有凭证并查找计数器值最大的凭证,实现了FIFO(先进先出)替换策略。这确保了在空间不足时,始终会覆盖最早添加的WiFi凭证。
nvs_set_counter 函数
/**
* @brief 递增所有WiFi凭证的计数器值
*
* 递增所有已存储凭证的计数器值,用于跟踪凭证的“年龄”。
* 该函数是实现FIFO(先进先出)替换策略的关键部分,确保
* 新添加的凭证计数器值保持最小。
*
* @param handle WiFi SSID管理器句柄
* @param exsit_ssid_num 当前存在的SSID数量
* @return 成功返回ESP_OK,失败返回ESP_FAIL
*/
static esp_err_t nvs_set_counter(wifi_ssid_manager_handle_t handle, uint8_t exsit_ssid_num)
{
// 初始化返回值和信息结构体
esp_err_t ret = ESP_OK;
nvs_stored_info_t info = {0};
// 遍历所有存在的WiFi凭证
for (int i = 0; i < exsit_ssid_num; i++) {
// 清空信息结构体,准备读取下一个凭证
memset(&info, 0, sizeof(nvs_stored_info_t));
// 从存储中获取凭证信息,并累积错误码
ret |= nvs_wifi_info_get(handle, i, &info);
// 将计数器值递增1,这是实现FIFO策略的关键
info.cnt ++;
// 保存更新后的凭证信息,并累积错误码
ret |= nvs_wifi_info_save(handle, i, &info);
}
// 检查操作过程中是否有错误发生
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Fail to set counter");
return ESP_FAIL;
}
// 所有操作成功,返回ESP_OK
return ESP_OK;
}
该函数的作用是递增所有已存储WiFi凭证的计数器值,是实现FIFO替换策略的关键辅助函数。它确保了计数器值能准确反映凭证的“年龄”。
nvs_reset_choosen_flag
重置所有凭证的选择标志
- 确保连接逻辑的正确性
- 防止多个凭证同时被标记为已选择
应用示例
下面是一个使用WiFi凭证管理函数的完整示例,展示了这些函数在实际应用中的使用方式:
#include <string.h>
#include "esp_log.h"
#include "wifi_ssid_manager.h"
static const char *TAG = "WIFI_CRED_EXAMPLE";
// 凭证管理示例函数
void wifi_credential_management_example(void)
{
// 创建WiFi SSID管理器,最多保存5个SSID
wifi_ssid_manager_handle_t mgr = wifi_ssid_manager_create(5);
if (mgr == NULL) {
ESP_LOGE(TAG, "创建WiFi SSID管理器失败");
return;
}
// 保存多个WiFi凭证
ESP_LOGI(TAG, "保存WiFi凭证1");
esp_err_t ret = wifi_ssid_manager_save(mgr, "HomeWiFi", "home123456");
if (ret != ESP_OK) {
ESP_LOGE(TAG, "保存WiFi凭证1失败");
}
ESP_LOGI(TAG, "保存WiFi凭证2");
ret = wifi_ssid_manager_save(mgr, "OfficeWiFi", "office789");
if (ret != ESP_OK) {
ESP_LOGE(TAG, "保存WiFi凭证2失败");
}
ESP_LOGI(TAG, "保存WiFi凭证3");
ret = wifi_ssid_manager_save(mgr, "CafeWiFi", "cafe2023");
if (ret != ESP_OK) {
ESP_LOGE(TAG, "保存WiFi凭证3失败");
}
// 显示当前存储的所有WiFi凭证
ESP_LOGI(TAG, "显示所有存储的WiFi凭证:");
wifi_ssid_manager_list_show(mgr);
// 更新已存在的WiFi凭证密码
ESP_LOGI(TAG, "更新HomeWiFi的密码");
ret = wifi_ssid_manager_save(mgr, "HomeWiFi", "newhome888");
if (ret != ESP_OK) {
ESP_LOGE(TAG, "更新WiFi密码失败");
}
// 尝试获取最佳WiFi配置(通常会扫描并匹配信号最强的网络)
wifi_config_t wifi_config = {0};
ret = wifi_ssid_manager_get_best_config(mgr, &wifi_config);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "获取到最佳WiFi配置: SSID=%s", (char *)wifi_config.sta.ssid);
} else {
ESP_LOGE(TAG, "获取最佳WiFi配置失败");
}
// 获取最新保存的WiFi配置
memset(&wifi_config, 0, sizeof(wifi_config_t));
ret = wifi_ssid_manager_get_latest_config(mgr, &wifi_config);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "获取到最新WiFi配置: SSID=%s", (char *)wifi_config.sta.ssid);
} else {
ESP_LOGE(TAG, "获取最新WiFi配置失败");
}
// 擦除所有WiFi凭证
ESP_LOGI(TAG, "擦除所有WiFi凭证");
ret = wifi_ssid_manager_erase_all(mgr);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "擦除WiFi凭证失败");
}
// 确认所有凭证已被擦除
ESP_LOGI(TAG, "确认所有凭证已被擦除:");
wifi_ssid_manager_list_show(mgr);
// 销毁WiFi SSID管理器
ESP_LOGI(TAG, "销毁WiFi SSID管理器");
wifi_ssid_manager_destroy(mgr);
}
void app_main(void)
{
// 初始化NVS闪存(通常在应用程序开始时执行)
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
// NVS分区被充满或发现新版本,需要擦除处理
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// 执行凭证管理示例
wifi_credential_management_example();
}
在这个示例中,我们演示了WiFi凭证管理函数的典型用法:
- 创建管理器并设置最大存储数量
- 保存多个WiFi凭证(包括新增和更新)
- 列出当前存储的所有凭证
- 获取最佳配置和最新配置
- 擦除所有凭证
- 销毁管理器释放资源
常见使用场景
WiFi凭证管理函数在以下场景中特别有用:
- 多网络管理:设备需要在多个WiFi网络间切换,如家庭/办公室/公共场所
- 配网后存储:通过配网(如SmartConfig、蓝牙配网等)获取的WiFi凭证需要持久化存储
- 备用网络:存储多个备用网络,当主网络不可用时自动切换
- 网络优先级:基于信号强度或保存顺序确定连接优先级
- 恢复出厂设置:需要清除所有存储的网络凭证
总结
ESP-ADF的WiFi凭证管理函数提供了一套完整的接口,用于管理WiFi网络凭证的存储、检索和清除。这些函数具有以下几个关键特性:
- 有限存储管理:通过最大SSID数量限制和FIFO替换策略,有效管理有限的存储空间
- 自动去重:保存时自动检查SSID是否已存在,避免重复存储
- 优先级管理:通过latest_ssid和choosen标志管理网络连接优先级
- 持久化存储:利用NVS闪存实现WiFi凭证的持久化存储,断电不丢失
- 完整的生命周期:提供从保存到擦除的完整生命周期管理
通过合理使用这些凭证管理函数,开发者可以轻松实现设备的多网络管理,提升用户体验和设备连接可靠性。