ESP-ADF wifi_service子模块esp_wifi_setting配网之smart_config详解

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组件架构中处于配网接口实现层,下图展示了其在整体架构中的位置:

wifi_service组件架构
esp_wifi_setting
(配网接口抽象层)
wifi_service
(WiFi服务核心)
wifi_ssid_manager
(SSID管理器)
smart_config
(ESP Touch配网)
blufi_config
(蓝牙配网)
airkiss_config
(微信配网)
softap_config
(软AP配网)
NVS存储
(永久保存WiFi信息)
智能选择
(最佳网络选择算法)

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的主要工作原理是:

  1. ESP设备进入监听模式,在所有WiFi信道上扫描特定的数据包
  2. 用户在手机APP上输入WiFi的SSID和密码
  3. 手机APP将这些信息编码成特殊的数据包发送到空气中
  4. ESP设备接收并解码这些数据包,获取到WiFi的SSID和密码
  5. 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;
}

创建阶段的流程如下:

调用smart_config_create(info)
创建esp_wifi_setting接口: esp_wifi_setting_create
接口创建成功?
返回NULL
分配配置结构体内存
内存分配成功?
释放接口内存并返回NULL
初始化配置结构体
设置用户数据: esp_wifi_setting_set_data
注册配网函数: esp_wifi_setting_register_function
返回配网接口句柄

创建阶段的关键点:

  1. 使用esp_wifi_setting_create创建通用接口
  2. 分配和初始化配置结构体
  3. 通过esp_wifi_setting_set_data存储配置
  4. 注册_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;
}

启动配网阶段的流程如下:

调用_smart_config_start(self)
获取配置信息: esp_wifi_setting_get_data
设置SmartConfig类型: esp_smartconfig_set_type
设置成功?
返回ESP_FAIL
启用快速模式: esp_smartconfig_fast_mode
设置成功?
返回ESP_FAIL
创建默认配置
注册事件回调(IDF4.0+)或设置回调函数(IDF<4.0)
启动SmartConfig: esp_smartconfig_start
启动成功?
返回ESP_FAIL
返回ESP_OK

启动阶段的关键点:

  1. 通过esp_wifi_setting_get_data获取之前保存的配置
  2. 设置SmartConfig类型和快速模式
  3. 根据ESP-IDF版本选择不同的事件处理方式
  4. 启动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 SmartConfig smartconfg_cb sm_setting_handle WiFi服务 事件通知(SC_EVENT_SCAN_DONE) 记录日志 事件通知(SC_EVENT_FOUND_CHANNEL) 记录日志 事件通知(SC_EVENT_GOT_SSID_PSWD, data) 断开当前WiFi连接 解析SSID和密码 记录日志 esp_wifi_setting_info_notify(handle, &sta_conf) 通知WiFi服务更新信息 事件通知(SC_EVENT_SEND_ACK_DONE) esp_smartconfig_stop() ESP-IDF SmartConfig smartconfg_cb sm_setting_handle WiFi服务

回调阶段的关键点:

  1. 根据ESP-IDF版本不同,有两种不同的回调实现
  2. 当获取到SSID和密码后,通过esp_wifi_setting_info_notify通知WiFi服务
  3. 在配网完成后,调用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;
}

停止配网阶段的流程如下:

调用_smart_config_stop(self)
调用esp_smartconfig_stop()
返回ESP_OK

停止阶段的关键点:

  1. 直接调用ESP-IDF的esp_smartconfig_stop函数停止SmartConfig过程
  2. SmartConfig不需要特别的清理工作,因此teardown函数为NULL

完整流程分析

下面的时序图展示了SmartConfig从创建到使用的完整流程:

应用程序 WiFi服务 smart_config ESP-IDF SmartConfig 手机APP smart_config_create(info) 返回配网接口句柄 wifi_service_register_setting_handle(handle) 返回index wifi_service_setting_start(index) esp_wifi_setting_start(handle) _smart_config_start(handle) esp_smartconfig_set_type() esp_smartconfig_fast_mode(true) esp_smartconfig_start() ESP设备开始在所有信道上扫描数据包 通过特殊数据包发送WiFi信息 回调函数通知(GOT_SSID_PSWD) esp_wifi_setting_info_notify() 尝试连接获取到的WiFi esp_wifi_setting_stop(handle) _smart_config_stop(handle) esp_smartconfig_stop() wifi_service_destroy() 清理资源(自动释放配网接口) 应用程序 WiFi服务 smart_config ESP-IDF SmartConfig 手机APP

使用示例

下面是一个使用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接口定义的模式:

  1. 创建和初始化smart_config_create分配资源并注册功能函数
  2. 启动配网_smart_config_start配置并启动ESP-IDF的SmartConfig功能
  3. 事件处理:回调函数处理配网过程中的各种事件,当获取到WiFi信息后通知WiFi服务
  4. 停止配网_smart_config_stop停止SmartConfig过程
  5. 资源释放:由WiFi服务自动管理

SmartConfig具有以下特点:

  1. 无界面配网:不需要设备具备显示界面或输入能力
  2. 简单易用:用户只需在手机APP上输入WiFi信息即可
  3. 多协议支持:支持ESP-Touch和AirKiss等协议
  4. IDF兼容性:兼容不同版本的ESP-IDF,自动适应事件处理机制的变化

通过合理使用SmartConfig配网方式,开发者可以为ESP设备提供便捷的WiFi配置体验,特别适合没有显示屏或输入设备的物联网产品。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

omnibots

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值