ESP-ADF wifi_service子模块esp_wifi_setting配网之smart_config详解
版本信息: v2.7-65-gcf908721
本章节分析的源码位于
/components/wifi_service/smart_config/smart_config.c
文件
模块概览
Smart Config在ESP-ADF的wifi_service组件架构中处于配网接口实现层,下图展示了其在整体架构中的位置:
ESP-ADF的smart_config模块是基于ESP-IDF的SmartConfig功能实现的一种WiFi配网方式。从架构图可以看出,它是esp_wifi_setting抽象接口的一种具体实现,与其他配网方式(blufi_config、airkiss_config和softap_config)并列。
Smart Config遵循esp_wifi_setting接口规范,实现了通过手机APP向ESP设备传输WiFi配置信息的功能。它的最大特点是无需显示配置界面,用户只需在手机APP上输入WiFi信息,通过特殊的数据包将信息传输给ESP设备。配网完成后,获取的WiFi凭证会被保存到wifi_ssid_manager模块中进行统一管理。
SmartConfig的主要工作原理是:
- ESP设备进入监听模式,在所有WiFi信道上扫描特定的数据包
- 用户在手机APP上输入WiFi的SSID和密码
- 手机APP将这些信息编码成特殊的数据包发送到空气中
- ESP设备接收并解码这些数据包,获取到WiFi的SSID和密码
- ESP设备使用获取到的信息连接WiFi网络
SmartConfig支持多种类型,在ESP-ADF中主要有:
- SC_TYPE_ESPTOUCH:ESP-Touch协议,由乐鑫开发
- SC_TYPE_AIRKISS:微信硬件平台使用的协议
数据结构
本章节分析的源码位于
/components/wifi_service/include/smart_config.h
文件
smart_config模块定义了以下关键数据结构:
/**
* @brief esp smartconfig配置信息
*/
typedef struct {
smartconfig_type_t type; /*!< SmartConfig类型,如SC_TYPE_ESPTOUCH */
} smart_config_info_t;
// 默认配置宏
#define SMART_CONFIG_INFO_DEFAULT() { \
.type = SC_TYPE_ESPTOUCH, \
}
// 内部使用的结构体,与对外的smart_config_info_t对应
// /components/wifi_service/smart_config/smart_config.c
typedef struct {
smartconfig_type_t type;
} smart_config_info;
esp_wifi_setting生命周期实现
smart_config模块作为esp_wifi_setting接口的实现,遵循了接口定义的生命周期,下面我们按照生命周期的各个阶段详细分析其实现。
1. 创建和初始化阶段
SmartConfig配网方式通过smart_config_create
函数创建,此函数是遵循esp_wifi_setting接口规范的实现。
/**
* @brief 创建SmartConfig配网实例
*
* 该函数完成以下主要操作:
* 1. 创建esp_wifi_setting接口实例
* 2. 分配和初始化配置结构体
* 3. 注册配网实现函数
*
* @param info SmartConfig配置信息
* @return esp_wifi_setting_handle_t 配网接口句柄
*/
esp_wifi_setting_handle_t smart_config_create(smart_config_info_t *info)
{
// 创建esp_wifi_setting接口实例
sm_setting_handle = esp_wifi_setting_create("esp_smart_config");
AUDIO_MEM_CHECK(TAG, sm_setting_handle, return NULL);
// 分配配置结构体内存
smart_config_info *cfg = audio_calloc(1, sizeof(smart_config_info));
AUDIO_MEM_CHECK(TAG, cfg, {
audio_free(sm_setting_handle);
return NULL;
});
// 初始化配置
cfg->type = info->type;
// 设置用户数据
esp_wifi_setting_set_data(sm_setting_handle, cfg);
// 注册配网实现函数
esp_wifi_setting_register_function(sm_setting_handle,
_smart_config_start,
_smart_config_stop,
NULL);
return sm_setting_handle;
}
创建阶段的流程如下:
创建阶段的关键点:
- 使用
esp_wifi_setting_create
创建通用接口 - 分配和初始化配置结构体
- 通过
esp_wifi_setting_set_data
存储配置 - 注册
_smart_config_start
和_smart_config_stop
函数实现配网控制
2. 启动配网阶段
SmartConfig的启动由_smart_config_start
函数实现,该函数在WiFi服务调用esp_wifi_setting_start
时被触发。
/**
* @brief 启动SmartConfig配网
*
* 该函数完成以下主要操作:
* 1. 获取配置信息
* 2. 设置SmartConfig类型
* 3. 启用快速模式
* 4. 注册回调函数
* 5. 启动SmartConfig
*
* @param self 配网接口句柄
* @return esp_err_t 成功返回ESP_OK,失败返回ESP_FAIL
*/
static esp_err_t _smart_config_start(esp_wifi_setting_handle_t self)
{
// 获取配置信息
smart_config_info *info = esp_wifi_setting_get_data(self);
esp_err_t ret = ESP_OK;
// 设置SmartConfig类型
ret = esp_smartconfig_set_type(info->type);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "esp_smartconfig_set_type fail");
return ESP_FAIL;
}
// 启用快速模式
ret = esp_smartconfig_fast_mode(true);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "esp_smartconfig_fast_mode fail");
return ESP_FAIL;
}
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0))
// IDF 4.0及以上版本,使用新的事件系统
smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT();
ret = esp_smartconfig_start(&cfg);
esp_event_handler_register(SC_EVENT, ESP_EVENT_ANY_ID, &smartconfg_cb, NULL);
#else
// IDF 4.0以下版本,使用老的回调方式
ret = esp_smartconfig_start(smartconfg_cb, 1);
#endif
if (ret != ESP_OK) {
ESP_LOGE(TAG, "esp_smartconfig_start fail");
return ESP_FAIL;
}
return ret;
}
启动配网阶段的流程如下:
启动阶段的关键点:
- 通过
esp_wifi_setting_get_data
获取之前保存的配置 - 设置SmartConfig类型和快速模式
- 根据ESP-IDF版本选择不同的事件处理方式
- 启动SmartConfig并注册回调函数
3. 回调处理阶段
SmartConfig模块通过回调函数处理配网过程中的各种事件,包括扫描完成、找到通道、获取到SSID和密码等。
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0))
/**
* @brief SmartConfig事件回调函数(ESP-IDF 4.0+版本)
*
* 处理SmartConfig过程中的各种事件,包括:
* - SC_EVENT_SCAN_DONE: 扫描完成
* - SC_EVENT_FOUND_CHANNEL: 找到通道
* - SC_EVENT_GOT_SSID_PSWD: 获取到SSID和密码
* - SC_EVENT_SEND_ACK_DONE: 发送ACK完成
*
* 在ESP-IDF 4.0及以上版本中,SmartConfig使用基于事件的回调机制,
* 该函数注册为SC_EVENT事件组的处理函数。
*/
static void smartconfg_cb(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
wifi_config_t sta_conf;
switch (event_id) {
case SC_EVENT_SCAN_DONE:
// SmartConfig完成WiFi扫描,这是配网的第一步
// 此时ESP设备已经扫描了所有可用WiFi信道,准备接收配网数据包
ESP_LOGI(TAG, "SC_EVENT_SCAN_DONE");
break;
case SC_EVENT_FOUND_CHANNEL:
// SmartConfig已经找到手机发送配网数据的信道
// 此时ESP设备已锁定在特定WiFi信道上监听数据包
ESP_LOGI(TAG, "SC_EVENT_FOUND_CHANNEL");
break;
case SC_EVENT_GOT_SSID_PSWD:
// 已成功接收并解码出WiFi的SSID和密码,这是最关键的事件
ESP_LOGI(TAG, "SC_EVENT_GOT_SSID_PSWD");
// 首先断开当前可能存在的WiFi连接
esp_wifi_disconnect();
// 从事件数据中提取WiFi信息
smartconfig_event_got_ssid_pswd_t *evt = (smartconfig_event_got_ssid_pswd_t *)event_data;
// 清空WiFi配置结构体并填充获取到的信息
memset(&sta_conf, 0x00, sizeof(sta_conf));
memcpy(sta_conf.sta.ssid, evt->ssid, sizeof(sta_conf.sta.ssid));
memcpy(sta_conf.sta.password, evt->password, sizeof(sta_conf.sta.password));
// 如果有特定的AP BSSID信息,也一并设置
sta_conf.sta.bssid_set = evt->bssid_set;
if (sta_conf.sta.bssid_set == true) {
memcpy(sta_conf.sta.bssid, evt->bssid, sizeof(sta_conf.sta.bssid));
}
// 记录获取到的WiFi信息
ESP_LOGI(TAG, "SSID=%s, PASS=%s", sta_conf.sta.ssid, sta_conf.sta.password);
// 通知WiFi服务,将触发WiFi服务连接到该网络
// 这里通过esp_wifi_setting接口的通知机制将信息传递给WiFi服务
if (sm_setting_handle) {
esp_wifi_setting_info_notify(sm_setting_handle, &sta_conf);
}
break;
case SC_EVENT_SEND_ACK_DONE:
// 已经向手机发送确认信息,表示配网成功
// 此时可以停止SmartConfig过程
ESP_LOGI(TAG, "SC_EVENT_SEND_ACK_DONE");
esp_smartconfig_stop();
break;
}
}
#else
/**
* @brief SmartConfig状态回调函数(ESP-IDF 4.0以下版本)
*
* 处理SmartConfig过程中的各种状态,包括:
* - SC_STATUS_WAIT: 等待配置
* - SC_STATUS_FIND_CHANNEL: 查找通道
* - SC_STATUS_GETTING_SSID_PSWD: 获取SSID和密码
* - SC_STATUS_LINK: 链接到AP
* - SC_STATUS_LINK_OVER: 配置完成
*
* 在ESP-IDF 4.0以下版本中,SmartConfig使用基于状态回调的机制,
* 该函数直接作为回调函数传递给esp_smartconfig_start。
*/
static void smartconfg_cb(smartconfig_status_t status, void *pdata)
{
wifi_config_t sta_conf;
switch (status) {
case SC_STATUS_WAIT:
// SmartConfig已初始化,等待配置
// 此时ESP设备已准备好接收配网数据
ESP_LOGI(TAG, "SC_STATUS_WAIT");
break;
case SC_STATUS_FIND_CHANNEL:
// SmartConfig正在尝试找到正确的WiFi信道
// 此时ESP设备正在扫描各个信道以接收配网数据包
ESP_LOGI(TAG, "SC_STATUS_FIND_CHANNEL");
break;
case SC_STATUS_GETTING_SSID_PSWD:
// SmartConfig正在接收SSID和密码信息
// 此时可以从pdata中获取当前的配网类型
ESP_LOGI(TAG, "SC_STATUS_GETTING_SSID_PSWD");
smartconfig_type_t *type = pdata;
if (*type == SC_TYPE_ESPTOUCH) {
// ESP-Touch是乐鑫开发的配网协议
ESP_LOGD(TAG, "SC_TYPE:SC_TYPE_ESPTOUCH");
} else {
// AirKiss是微信硬件平台使用的配网协议
ESP_LOGD(TAG, "SC_TYPE:SC_TYPE_AIRKISS");
}
break;
case SC_STATUS_LINK:
// 已成功获取SSID和密码,准备连接AP
ESP_LOGI(TAG, "SC_STATUS_LINK");
// 首先断开当前可能存在的WiFi连接
esp_wifi_disconnect();
// 清空WiFi配置结构体并填充从pdata中获取到的信息
// 在旧版API中,pdata直接包含了wifi_sta_config_t结构
memset(&sta_conf, 0x00, sizeof(sta_conf));
memcpy(&(sta_conf.sta), pdata, sizeof(wifi_sta_config_t));
// 记录获取到的WiFi信息
ESP_LOGI(TAG, "<link>ssid:%s", sta_conf.sta.ssid);
ESP_LOGI(TAG, "<link>pass:%s", sta_conf.sta.password);
// 通知WiFi服务,将触发WiFi服务连接到该网络
if (sm_setting_handle) {
esp_wifi_setting_info_notify(sm_setting_handle, &sta_conf);
}
break;
case SC_STATUS_LINK_OVER:
// 配网过程完成,可以停止SmartConfig
ESP_LOGI(TAG, "SC_STATUS_LINK_OVER");
// 某些配网协议会返回手机的IP地址,可用于后续通信
if (pdata != NULL) {
uint8_t phone_ip[4] = { 0 };
memcpy(phone_ip, (const void *)pdata, 4);
ESP_LOGI(TAG, "Phone ip: %d.%d.%d.%d\n", phone_ip[0], phone_ip[1], phone_ip[2], phone_ip[3]);
}
// 停止SmartConfig过程,释放相关资源
esp_smartconfig_stop();
break;
}
}
#endif
回调处理阶段的流程如下:
回调阶段的关键点:
- 根据ESP-IDF版本不同,有两种不同的回调实现
- 当获取到SSID和密码后,通过
esp_wifi_setting_info_notify
通知WiFi服务 - 在配网完成后,调用
esp_smartconfig_stop
停止配网过程
4. 停止配网阶段
SmartConfig的停止由_smart_config_stop
函数实现,该函数在WiFi服务调用esp_wifi_setting_stop
时被触发。
/**
* @brief 停止SmartConfig配网
*
* 该函数直接调用esp_smartconfig_stop()停止SmartConfig过程
*
* @param self 配网接口句柄
* @return esp_err_t 始终返回ESP_OK
*/
static esp_err_t _smart_config_stop(esp_wifi_setting_handle_t self)
{
esp_smartconfig_stop();
return ESP_OK;
}
停止配网阶段的流程如下:
停止阶段的关键点:
- 直接调用ESP-IDF的
esp_smartconfig_stop
函数停止SmartConfig过程 - SmartConfig不需要特别的清理工作,因此teardown函数为NULL
完整流程分析
下面的时序图展示了SmartConfig从创建到使用的完整流程:
使用示例
下面是一个使用SmartConfig配网方式的完整示例:
#include "esp_log.h"
#include "esp_wifi.h"
#include "audio_element.h"
#include "periph_service.h"
#include "wifi_service.h"
#include "smart_config.h"
static const char *TAG = "SMART_CONFIG_EXAMPLE";
// WiFi事件回调函数
static esp_err_t wifi_service_cb(periph_service_handle_t handle, periph_service_event_t *evt, void *ctx)
{
if (evt->type == WIFI_SERV_EVENT_SETTING_FINISHED) {
ESP_LOGI(TAG, "WIFI_SERV_EVENT_SETTING_FINISHED");
ESP_LOGI(TAG, "获取到WiFi配置,准备连接");
} else if (evt->type == WIFI_SERV_EVENT_CONNECTED) {
ESP_LOGI(TAG, "WIFI_SERV_EVENT_CONNECTED");
ESP_LOGI(TAG, "已成功连接到WiFi网络");
} else if (evt->type == WIFI_SERV_EVENT_DISCONNECTED) {
ESP_LOGI(TAG, "WIFI_SERV_EVENT_DISCONNECTED");
ESP_LOGI(TAG, "与WiFi网络断开连接");
} else if (evt->type == WIFI_SERV_EVENT_SETTING_TIMEOUT) {
ESP_LOGW(TAG, "WIFI_SERV_EVENT_SETTING_TIMEOUT");
ESP_LOGW(TAG, "配网超时,请重试");
}
return ESP_OK;
}
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) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// 初始化TCP/IP组件
tcpip_adapter_init();
// 初始化WiFi
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());
// 创建WiFi服务
wifi_service_config_t wifi_cfg = WIFI_SERVICE_DEFAULT_CONFIG();
wifi_cfg.evt_cb = wifi_service_cb;
wifi_cfg.setting_timeout_s = 60; // 配网超时时间为60秒
periph_service_handle_t wifi_handle = wifi_service_create(&wifi_cfg);
if (wifi_handle == NULL) {
ESP_LOGE(TAG, "Failed to create WiFi service");
return;
}
// 创建SmartConfig配网方式
smart_config_info_t sc_cfg = SMART_CONFIG_INFO_DEFAULT();
esp_wifi_setting_handle_t sc_handle = smart_config_create(&sc_cfg);
if (sc_handle == NULL) {
ESP_LOGE(TAG, "Failed to create SmartConfig");
return;
}
// 注册配网方式到WiFi服务
int setting_index = 0;
ESP_ERROR_CHECK(wifi_service_register_setting_handle(wifi_handle, sc_handle, &setting_index));
// 启动配网
ESP_LOGI(TAG, "Starting SmartConfig");
ESP_LOGI(TAG, "Please use ESP-Touch APP to send WiFi configuration");
ESP_ERROR_CHECK(wifi_service_setting_start(wifi_handle, setting_index));
// 应用程序主循环...
// 在不再需要时销毁WiFi服务(会自动清理相关的配网资源)
// wifi_service_destroy(wifi_handle);
}
总结
ESP-ADF的smart_config模块实现了基于ESP-IDF SmartConfig功能的WiFi配网方式,通过遵循esp_wifi_setting接口规范,它成为了WiFi服务的可插拔组件之一。使用SmartConfig,用户可以通过手机APP向ESP设备传递WiFi配置信息,无需设备具备显示界面或输入能力。
SmartConfig的生命周期遵循esp_wifi_setting接口定义的模式:
- 创建和初始化:
smart_config_create
分配资源并注册功能函数 - 启动配网:
_smart_config_start
配置并启动ESP-IDF的SmartConfig功能 - 事件处理:回调函数处理配网过程中的各种事件,当获取到WiFi信息后通知WiFi服务
- 停止配网:
_smart_config_stop
停止SmartConfig过程 - 资源释放:由WiFi服务自动管理
SmartConfig具有以下特点:
- 无界面配网:不需要设备具备显示界面或输入能力
- 简单易用:用户只需在手机APP上输入WiFi信息即可
- 多协议支持:支持ESP-Touch和AirKiss等协议
- IDF兼容性:兼容不同版本的ESP-IDF,自动适应事件处理机制的变化
通过合理使用SmartConfig配网方式,开发者可以为ESP设备提供便捷的WiFi配置体验,特别适合没有显示屏或输入设备的物联网产品。