ESP32-S3的一键配网模式是一种方便快捷的WiFi配置方式。在这种模式下,用户无需手动输入 WiFi 的 SSID 和密码等信息,只需要通过一键操作,即可完成 WiFi 的配置和连接。本章节,作者使用乐鑫官方提供的 SmartConfig 软件一键配置 WiFi 账号与密码。
本章分为如下几个小节:
51.1 主流 WIFI 配网方式简介
51.2 硬件设计
51.3 软件设计
51.4 下载验证
51.1 主流 WIFI 配网方式简介
目前主流的 WIFI 配网方式主要有以下三种:
(1)SoftAP 配网
ESP32-S3 会建立一个 WiFi 热点(AP 模式),用户将手机连接到这个热点后,将要连接的WiFi 信息发送给 ESP32-S3, ESP32-S3 得到 SSID 和密码。
①:优点:很可靠,成功率基本达到 100%,设备端的代码简单;
②:缺点:需要手动切换手机 WiFi 连接的网络,先连接到 ESP32 的 AP 网络,配置完成后再恢复连接正常 WiFi 网络,操作上存在复杂性,可能给用户带来困扰;
③:官方支持:没有提供 Demo。
(2) Smartconfig 配网
ESP32-S3 处于混杂模式下,监听网络中的所有报文,手机 APP 将当前连接的 SSID 和密码编码到 UDP 报文中,通过广播或组播的方式发送报文, ESP32-S3 接收到 UDP 报文后解码,得到 SSID 和密码,然后使用该组 SSID 和密码去连接网络。
①:优缺点:简洁,用户容易操作,但配网成功率受环境影响较大;
②:官方支持:提供 Demo 和 smart_config 例程。
(3)Airkiss 配网
AirKiss 是微信硬件平台提供的一种 WIFI 设备快速入网配置技术。要使用微信客户端的方式配置设备入网,需要设备支持 AirKiss 技术。 Airkiss 的原理和 Smartconfig 很类似,设备工作在混杂模式下,微信客户端发送包含 SSID 和密码的广播包,设备收到广播包解码得到 SSID 和密码。详细的可以参考微信官方的介绍。
①:优缺点:简洁,用户容易操作,但配网成功率受环境影响较大;
②:官方支持:提供 Demo 和 smart_config 例程。
本实验以 Smartconfig 软件对 ESP32-S3 设备进行一键配网,该软件的安装包可在乐鑫官方网站下载,如下图所示。
图 51.1.1 Smartconfig 软件下载
下载成功后,需把安装包转移到安卓手机或者苹果手机上安装。
51.2 硬件设计
1. 例程功能
本章实验功能简介:设备进入初始化状态,开启混监听所有网络数据包,此时 LCD显示"In the distribution network......",表示设备已进入混监听模式。手机连上自己的 WiFi,开启 APP(EspTouch)软件,输入手机所在 WiFi 密码,请求配网,发送 UDP 广播包。 ESP32 -S3 通过UDP 包(长度)获取配置信息捕捉到路由 SSID 和 PASSWD,连接路由器,此时 LCD 显示路由的账号与密码,表示连接路由成功。
2. 硬件资源
1) LED 灯
LED-IO1
2) XL9555
IIC_INT-IO0(需在 P5 连接 IO0)
IIC_SDA-IO41
IIC_SCL-IO42
3) SPILCD
CS-IO21
SCK-IO12
SDA-IO11
DC-IO40(在 P5 端口,使用跳线帽将 IO_SET 和 LCD_DC 相连)
PWR- IO1_3(XL9555)
RST- IO1_2(XL9555)
4) ESP32-S3 内部 WiFi
3. 原理图
本章实验使用的 WiFi 为 ESP32-S3 的片上资源,因此并没有相应的连接原理图。
51.3 软件设计
51.3.1 程序流程图
本实验的程序流程图:
图 51.3.1.1 程序流程图
51.3.2 程序解析
51.3.2.1 本次实验用到库函数说明
本次实验了解下这个函数esp_smartconfig_get_rvd_data(),该函数主要作用是在配网成功后,获取来自手机 APP 的额外自定义数据。
函数原型:
esp_err_t esp_smartconfig_get_rvd_data(uint8_t *rvd_data, uint8_t len);
参数说明:
参数名 | 类型 | 方向 | 说明 |
---|---|---|---|
rvd_data | uint8_t * | 输出 | 指向一个缓冲区的指针,用于存储获取到的额外数据。 |
len | uint8_t | 输入 | 期望获取的数据长度,同时也是缓冲区的长度。 |
表 51.3.2.1.1 函数esp_smartconfig_get_rvd_data()参数说明
返回值:
返回值 | 类型 | 说明 |
---|---|---|
|
| 获取成功 |
其他错误码 |
| 获取失败,可能是配网未完成或长度错误。 |
表 51.3.2.1.2 函数esp_smartconfig_get_rvd_data()返回值说明
RVD Data 是什么?
- RVD 代表 Reserved Data(保留数据);
- 在 SmartConfig(特别是 EspTouch v2)协议中,除了传输主信息(SSID 和 密码) 外,还预留了一个字段用于传输一小段额外的自定义数据;
- 这段数据由手机 APP 端在发起配网时决定并发送,设备端在配网过程中被动接收并可通过本函数读取。
工作原理与流程:
它的工作流程如下图所示:
图 51.3.2.1.1 函数esp_smartconfig_get_rvd_data()接收数据流程图
应用场景:
这个功能为产品设计提供了更大的灵活性。以下是几个典型应用场景:
1. 设备标识与绑定:
场景:一个手机APP需要控制多个相同的设备(如多个灯泡)。配网时,APP可以发送一个自定义标识符(如目标设备的别名、房间号)。
好处:设备收到后,可以把自己命名为“客厅主灯”,从而与“卧室台灯”区分开。APP在后续控制时,可以根据这个标识符精准控制,无需用户再次区分。
2. 预共享密钥(PSK)或令牌传输:
场景:设备连接WiFi后,需要与一个特定的云服务器或用户账号进行绑定,这个过程需要一个安全的认证令牌。
好处:APP可以在配网阶段,将用户登录后获得的认证令牌(Token) 通过这个通道传给设备。设备联网后,直接使用该令牌与服务器通信,无需再走一遍复杂的绑定流程,实现“配网即绑定”。
3. 批量生产与配置:
场景:在工厂生产环节,需要将一些通用参数(如产品型号、固件版本、默认服务器地址)写入设备。
好处:可以通过一个工具APP,利用SmartConfig同时为一批设备配置相同的WiFi信息和这些通用参数,大大提高生产效率。
4. 提供附加信息:
场景:设备需要知道一些简单的网络配置或操作指令。
好处:APP可以传送一个指令,例如告诉设备连接后使用静态IP,或者配网成功后立即开启某种模式。
代码示例,只是简单地将数据以十六进制形式打印出来。在实际产品中,会在这里解析数据并实现上述的应用场景逻辑。
/* 手机APPEspTouch软件使用ESPTOUCH V2模式,会执行以下代码 */
if (evt->type == SC_TYPE_ESPTOUCH_V2) { // 1. 检查协议类型
ESP_ERROR_CHECK( esp_smartconfig_get_rvd_data(rvd_data, sizeof(rvd_data)) ); // 2. 获取数据
ESP_LOGI(TAG, "RVD_DATA:"); // 3. 打印日志(示例处理)
for (int i = 0; i < 33; i++) {
printf("%02x ", rvd_data[i]);
}
printf("\n");
}
重要注意事项:
- 数据长度限制:保留数据的长度非常有限(通常最多几十个字节,例如 EspTouch v2 规定是 33 字节)。它不适合传输大量数据;
- 协议版本依赖:此功能仅支持 EspTouch V2 协议。传统的 EspTouch V1 或 AirKiss 协议不支持此功能。在手机APP端,也需要使用支持 V2 协议的 SDK 或代码来发送数据;
- 调用时机:必须在接收到 SC_EVENT_GOT_SSID_PSWD事件之后、调用esp_smartconfig_stop()之前调用此函数,否则可能无法获取到数据。
51.3.2.2 main函数解析
在本章节实验中,只关心 main.c 文件内容即可,该文件内容如下:
特别注意:
测试遇到问题:在实际测试发现ESP32 的 SmartConfig(使用EspTouch V2)一直连接失败或超时问题?
解决方法:最终分析发现代码中调用esp_smartconfig_set_type()函数设置配网协议参数不对。使用ESPTOUCH V1此处要设置为SC_TYPE_ESPTOUCH,使用ESPTOUCH V2此处要设置为SC_TYPE_ESPTOUCH_V2,否则配网会失败。
/* 定义事件 */
static EventGroupHandle_t s_wifi_event_group;
static const int CONNECTED_BIT = BIT0;
static const int ESPTOUCH_DONE_BIT = BIT1;
static const char *TAG = "smartconfig_example";
static void smartconfig_task(void * parm);
static char lcd_buff[100] = {0};
/**
* @brief WIFI链接糊掉函数
* @param arg:传入网卡控制块
* @param event_base:WIFI事件
* @param event_id:事件ID
* @param event_data:事件数据
* @retval 无
*/
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
{
xTaskCreate(smartconfig_task, "smartconfig_task", 4096, NULL, 3, NULL);
}
// WIFI_EVENT_STA_DISCONNECTED(连接失败,会自动重连)
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
{
esp_wifi_connect();
xEventGroupClearBits(s_wifi_event_group, CONNECTED_BIT);
}
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
{
xEventGroupSetBits(s_wifi_event_group, CONNECTED_BIT);
}
else if (event_base == SC_EVENT && event_id == SC_EVENT_SCAN_DONE)
{
ESP_LOGI(TAG, "Scan done");
spilcd_show_string(0, 90, 320, 16, 16, "In the distribution network......", BLUE);
}
else if (event_base == SC_EVENT && event_id == SC_EVENT_FOUND_CHANNEL)
{
ESP_LOGI(TAG, "Found channel");
}
/* 已获取SSID和密码,表示配网成功 */
else if (event_base == SC_EVENT && event_id == SC_EVENT_GOT_SSID_PSWD)
{
ESP_LOGI(TAG, "Got SSID and password");
smartconfig_event_got_ssid_pswd_t *evt = (smartconfig_event_got_ssid_pswd_t *)event_data;
wifi_config_t wifi_config;
uint8_t ssid[33] = { 0 };
uint8_t password[65] = { 0 };
uint8_t rvd_data[33] = { 0 };
bzero(&wifi_config, sizeof(wifi_config_t));
memcpy(wifi_config.sta.ssid, evt->ssid, sizeof(wifi_config.sta.ssid));
memcpy(wifi_config.sta.password, evt->password, sizeof(wifi_config.sta.password));
wifi_config.sta.bssid_set = evt->bssid_set;
if (wifi_config.sta.bssid_set == true)
{
memcpy(wifi_config.sta.bssid, evt->bssid, sizeof(wifi_config.sta.bssid));
}
memcpy(ssid, evt->ssid, sizeof(evt->ssid));
memcpy(password, evt->password, sizeof(evt->password));
ESP_LOGI(TAG, "SSID:%s", ssid);
ESP_LOGI(TAG, "PASSWORD:%s", password);
spilcd_fill(0,90,320,240,WHITE);
sprintf(lcd_buff, "%s",ssid);
spilcd_show_string(0, 90, 320, 16, 16, lcd_buff, BLUE);
sprintf(lcd_buff, "%s",password);
spilcd_show_string(0, 110, 320, 16, 16, lcd_buff, BLUE);
spilcd_show_string(0, 130, 320, 16, 16, "Successful distribution network", BLUE);
/* 手机APPEspTouch软件使用ESPTOUCH V2模式,会执行以下代码 */
if (evt->type == SC_TYPE_ESPTOUCH_V2)
{
ESP_ERROR_CHECK( esp_smartconfig_get_rvd_data(rvd_data, sizeof(rvd_data)) );
ESP_LOGI(TAG, "RVD_DATA:");
for (int i = 0; i < 33; i++)
{
printf("%02x ", rvd_data[i]);
}
printf("\n");
}
ESP_ERROR_CHECK( esp_wifi_disconnect() );
//将获取到的SSID和密码设置到Wi-Fi驱动中
ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
//尝试连接目标Wi-Fi网络
esp_wifi_connect();
}
// 配网完成 (SC_EVENT_SEND_ACK_DONE)
else if (event_base == SC_EVENT && event_id == SC_EVENT_SEND_ACK_DONE)
{
xEventGroupSetBits(s_wifi_event_group, ESPTOUCH_DONE_BIT);
}
}
/**
* @brief WiFi一键配网
* @param 无
* @retval 无
*/
static void wifi_smartconfig_sta(void)
{
/* 初始化网卡 */
ESP_ERROR_CHECK(esp_netif_init());
/* 创建事件 */
s_wifi_event_group = xEventGroupCreate();
/* 使用默认配置初始化包括netif的Wi-Fi */
ESP_ERROR_CHECK(esp_event_loop_create_default());
/* 把WIFI网卡设置为STA模式 */
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
assert(sta_netif);
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
/* WIFI初始化 */
ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
/* 注册WIFI事件 */
ESP_ERROR_CHECK( esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL) );
ESP_ERROR_CHECK( esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL) );
ESP_ERROR_CHECK( esp_event_handler_register(SC_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL) );
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK( esp_wifi_start() );
}
/**
* @brief 一键配网回调函数
* @param parm:传入的形参(未使用)
* @retval 无
*/
static void smartconfig_task(void * parm)
{
parm = parm;
EventBits_t uxBits;
/* 设置配网协议,注意:如果使用ESPTOUCH V2,此处必须SC_TYPE_ESPTOUCH_V2,如果使用ESPTOUCH 此处必须SC_TYPE_ESPTOUCH*/
ESP_ERROR_CHECK( esp_smartconfig_set_type(SC_TYPE_ESPTOUCH_V2) );
/* 设置配网参数 */
smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT();
/* 开始配网,让ESP32进入混杂模式(Promiscuous Mode) */
ESP_ERROR_CHECK( esp_smartconfig_start(&cfg) );
while (1)
{
/* 获取事件 */
uxBits = xEventGroupWaitBits(s_wifi_event_group, CONNECTED_BIT | ESPTOUCH_DONE_BIT, true, false, portMAX_DELAY);
/* 配网成功 */
if(uxBits & CONNECTED_BIT)
{
ESP_LOGI(TAG, "WiFi Connected to ap");
}
/* 智能配置结束 */
if(uxBits & ESPTOUCH_DONE_BIT)
{
ESP_LOGI(TAG, "smartconfig over");
esp_smartconfig_stop();
vTaskDelete(NULL);
}
}
}
/**
* @brief 程序入口
* @param 无
* @retval 无
*/
void app_main(void)
{
esp_err_t ret;
ret = nvs_flash_init(); /* 初始化NVS */
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ESP_ERROR_CHECK(nvs_flash_init());
}
led_init(); /* LED初始化 */
my_spi_init(); /* SPI初始化 */
myiic_init(); /* IIC初始化 */
xl9555_init(); /* 初始化按键 */
spilcd_init(); /* LCD屏初始化 */
spilcd_show_string(0, 0, 320, 32, 32, "ESP32-S3", RED);
spilcd_show_string(0, 40, 320, 24, 24, "WiFi SmartConfig Test", RED);
spilcd_show_string(0, 70, 320, 16, 16, "ATOM@ALIENTEK", RED);
wifi_smartconfig_sta();
while (1)
{
LED0_TOGGLE();
vTaskDelay(pdMS_TO_TICKS(500));
}
}
上述源码是把 ESP32-S3 设备配置为 STA 模式,然后开启配网任务并启动配网,此时,ESP32-S3 处于混杂模式下,监听网络中的所有报文,当手机 APP 将当前连接的 SSID 和密码编
码到 UDP 报文中,通过广播或组播的方式发送报文, ESP32-S3 接收到 UDP 报文后解码,得到
SSID 和密码,然后使用该组 SSID 和密码去连接当前网络。
接下来再详细分析实现,分析之前我们再理解下发送端(手机APP)和接收端(ESP32设备)动作:
发送端(手机APP):
- 手机连接着目标Wi-Fi路由器;
- APP将目标Wi-Fi的SSID和密码进行编码。EspTouch V1协议的一种典型编码方式是:将字符信息转换为特定的UDP数据包长度序列。另一种方式是使用特定的UDP目标端口号来代表不同的数据;
- 手机在局域网内持续发送这些特殊编码的UDP广播包或组播包。由于路由器不会阻拦局域网内的广播包,这些包能够顺利被广播到整个Wi-Fi网络环境中。
接收端(ESP32设备):
- ESP32启动后,处于STA模式但未配置任何网络信息;
- 它启动SmartConfig后,Wi-Fi射频会进入混杂模式,接收空气中所有它能抓到的无线数据包,而不仅仅是发给它的包;
- SmartConfig组件过滤并分析这些数据包。它只关心符合EspTouch协议格式的UDP包;
- 通过分析这些包的长度序列或端口号序列,ESP32可以反向解码出原始的SSID和密码信息;
- 一旦解码成功,它就获得了连接网络所需的全部信息,进而发起连接。
代码实现中涉及多个状态转换,是理解的关键,主要在event_handler()和smartconfig_task()两个函数中实现,详细状态转换如下表所示:
当前状态 | 事件 | 动作 | 下一状态 |
---|---|---|---|
未初始化 | 系统启动 | 初始化NVS和外设 | 初始化完成 |
初始化完成 | 调用wifi_smartconfig_sta | 创建事件组,初始化网络 | WiFi就绪 |
WiFi就绪 | WIFI_EVENT_STA_START | 创建smartconfig_task | 配网就绪 |
配网就绪 | esp_smartconfig_start | 启动配网监听 | 监听中 |
监听中 | SC_EVENT_GOT_SSID_PSWD | 提取凭证,设置WiFi配置 | 凭证获取 |
凭证获取 | esp_wifi_connect | 尝试连接WiFi | 连接中 |
连接中 | IP_EVENT_STA_GOT_IP | 设置CONNECTED_BIT | 已连接 |
监听中 | SC_EVENT_SEND_ACK_DONE | 设置ESPTOUCH_DONE_BIT | 配网完成 |
已连接 & 配网完成 | 两个标志位都置位 | 停止配网,删除任务 | 完成 |
表51.3.2.2.1 WiFi 一键配网代码实现状态转换表
51.4 下载验证
程序下载成功后,ESP扫描热点结束,等待配网,显示如下:
图 51.4.1 程序下载成功ESP32等待配网
手机连接WiFi,打开EspTouch软件,主页可以选择EspTouch或者EspTouch V2 如下图所示:
图 51.4.2 打开EspTouch软件界面显示
选则EspTouch,也就是V1版本:
特别注意:代码调用esp_smartconfig_set_type()函数入参必须为SC_TYPE_ESPTOUCH,设置界面如下,设置后点击最下方确定,开始配网:
图 51.4.2 打开EspTouch V1界面显示
图 51.4.3 打开EspTouch V1配网成功界面显示
选则EspTouch V2:
特别注意:代码调用esp_smartconfig_set_type()函数入参必须为SC_TYPE_ESPTOUCH_V2,设置界面如下,设置后点击最下方确定,开始配网:
图 51.4.4 打开EspTouch V2界面显示
图 51.4.5 打开EspTouch V2配网成功界面显示
最终ESP 32 Wifi 配网成功显示:
图 51.4.6 ESP 32 Wifi 配网成功显示