当前位置: 首页 > news >正文

SOC-ESP32S3部分:24-WiFi配网

飞书文档https://x509p6c8to.feishu.cn/wiki/OD4pwTE8Jift2IkYKdNcSckOnfd

对于WiFi类设备,最重要的功能之一就是联网,WiFi需要联网,就需要知道我们家里路由的账号和密码,像手机类型的高端设备没什么问题,我们可以直接输入,但对没有屏幕的设备,或者小屏幕类没有输入键盘的设备,我们应该如何把家里的账号和密码告知设备呢?这就需要用到WiFi配网的功能。

需要特别说明的是,无论哪种配网方式,目前只能用2.4G的WiFi,不支持5GWiFi,这是模组本身频段决定的。

ESP32S3支持多种配网方式

  1. Wi-Fi AP配网(SoftAP + HTTP 服务器)
  2. 低功耗蓝牙配网
  3. Wi-Fi SmartConfig配网
  4. Wi-Fi Easy Connect(TM) (DPP)配网

方法一,方法二支持自定义协议,可扩展性更强,但是方法一体验不佳,方法二需要蓝牙支持。

方法三、方法四开发更快,其中方法四更多用于带屏幕设备,因为需要显示二维码。

那是不是方法三就是最好的呢?也不一定,我们可以了解下这种配网的技术原理:

ESP32处于混杂模式下,监听网络中的所有报文,手机APP将当前连接的ssid和password编码到UDP报文中,通过广播或者组播的方式发送报文,ESP32接收到UDP报文后解码,得到ssid和password,然后使用该组ssid和password去连接网络。
优缺点:这种方式简洁,用户也很容易操作,但是配网成功率受环境影响较大。

但是相对来说,方法三对用户的体验是较好的,产品化时,一般会考虑同时实现多种配网模式,这里我们主要讲解下方法三,其它方法的流程都是类似的,就是获取信息的管道有差异而已。

Wi-Fi SmartConfig配网

smartconfig配网支持多种协议,具体如下:

typedef enum {SC_TYPE_ESPTOUCH = 0,       /**< protocol: ESPTouch */SC_TYPE_AIRKISS,            /**< protocol: AirKiss */SC_TYPE_ESPTOUCH_AIRKISS,   /**< protocol: ESPTouch and AirKiss */SC_TYPE_ESPTOUCH_V2,        /**< protocol: ESPTouch v2*/
} smartconfig_type_t;ESPTouch:ESPTouch 是乐鑫科技推出的一种智能配网协议。
AirKiss:适用于微信生态。
ESPTouch_AirKiss:兼容两种协议。
ESPTouch_V2:ESPTouch的改进版,效率和稳定性更好,支持加密和自定义数据。ESPTouch类型的配网方式,可以使用ESPTouch APP进行配网,该APP源码开源,可以自行集成。
AirKiss类型的配网方式,可以使用微信小程序AirKiss进行配网,在乐鑫官方小程序也有这个工具。

ESPTouch,可以手机应用商店下载
 

乐鑫微信小程序
 

我们直接来看下代码部分,这类型的代码就不建议自己敲了,不用去记这些函数,是没有意义的,我们只需要懂得找到对应的官方例程,会结合AI工具看懂源码,然后参考例程去实现功能即可。

具体流程如下:

  1. 初始化NVS,因为配网成功后,路由的账号和密码需要存储在本地,下次启动可以直接联网,所以需要NVS
  2. 初始化网络协议栈
  3. 注册WiFi事件回调
  4. 开启WiFi,设置为STA模式
  5. 监听WiFi STA模式开启成功回调,初始化SmartConfig配网模式
  6. 监听WiFi 获取账号密码回调,进行联网,联网成功后关闭SmartConfig配网模式

初始化NVS

ESP_ERROR_CHECK( nvs_flash_init() );

初始化网络协议栈

    // 初始化网络接口ESP_ERROR_CHECK(esp_netif_init());// 创建默认事件循环ESP_ERROR_CHECK(esp_event_loop_create_default());// 创建默认的 WiFi 站点模式网络接口esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();assert(sta_netif);// 初始化 WiFi 配置wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();ESP_ERROR_CHECK( esp_wifi_init(&cfg) );

注册WiFi事件回调

注册事件处理函数,这里需要注册多类事件,WIFI_EVENT、IP_EVENT、SC_EVENT
WIFI_EVENT: 处理Wi-Fi连接状态的变化。
IP_EVENT: 处理IP地址的获取和丢失。
SC_EVENT: 处理智能配置相关的事件。

1. WIFI_EVENT
描述: 涉及Wi-Fi连接的状态变化事件。
常见事件:
WIFI_EVENT_STA_START: Wi-Fi 站点模式启动。
WIFI_EVENT_STA_CONNECTED: Wi-Fi 站点成功连接到AP(接入点)。
WIFI_EVENT_STA_DISCONNECTED: Wi-Fi 站点与AP断开连接。
WIFI_EVENT_STA_STOP: Wi-Fi 站点模式停止。
WIFI_EVENT_SCAN_DONE: Wi-Fi 扫描完成。
用途: 用于监控Wi-Fi连接状态的变化,例如检测是否成功连接到Wi-Fi网络或是否断开连接。2. IP_EVENT
描述: 涉及IP地址分配或释放的事件。
常见事件:
IP_EVENT_STA_GOT_IP: 站点模式下成功获取IP地址。
IP_EVENT_STA_LOST_IP: 站点模式下丢失了IP地址。
用途: 用于确认设备是否已经成功获取到可用的IP地址,从而可以进行网络通信。3. SC_EVENT (Smart Config Event)
描述: 涉及智能配置(Smart Config)的事件。
常见事件:
SC_EVENT_SCAN_DONE: 智能配置扫描完成。
SC_EVENT_FOUND_CHANNEL: 智能配置找到目标Wi-Fi信道。
SC_EVENT_GOT_SSID_PSWD: 智能配置成功获取到Wi-Fi的SSID和密码。
SC_EVENT_CONN_SUCCESS: 智能配置成功连接到Wi-Fi。
用途: 用于通过Smart Config技术(如通过手机APP或其他设备)将Wi-Fi配置信息发送到ESP32设备。
注册事件处理函数
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) );事件回调函数
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) {// WiFi 站点模式启动后,创建 SmartConfig 任务} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {// WiFi 断开连接时,重新连接并清除连接标志位} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {// 获取到 IP 地址后,代表联网成功} else if (event_base == SC_EVENT && event_id == SC_EVENT_GOT_SSID_PSWD) {// SmartConfig 获取到 SSID 和密码事件ESP_LOGI(TAG, "Got SSID and password");} else if (event_base == SC_EVENT && event_id == SC_EVENT_SEND_ACK_DONE) {// SmartConfig 发送 ACK 完成事件,设置 SmartConfig 完成标志位}
}

开启WiFi,设置为STA模式

    // 设置 WiFi 模式为站点模式并启动 WiFiESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );ESP_ERROR_CHECK( esp_wifi_start() );

监听WiFi STA模式开启成功回调,初始化SmartConfig配网模式

事件回调函数
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) {// WiFi 站点模式启动后,创建 SmartConfig 任务xTaskCreate(smartconfig_example_task, "smartconfig_example_task", 4096, NULL, 3, NULL);}//....//...
}static void smartconfig_example_task(void * parm)
{EventBits_t uxBits;// 设置 SmartConfig 类型为 SC_TYPE_ESPTOUCH_AIRKISSESP_ERROR_CHECK( esp_smartconfig_set_type(SC_TYPE_ESPTOUCH_AIRKISS) );smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT();// 启动 SmartConfigESP_ERROR_CHECK( esp_smartconfig_start(&cfg) );while (1) {// 等待连接标志位或 SmartConfig 完成标志位uxBits = xEventGroupWaitBits(s_wifi_event_group, CONNECTED_BIT | ESPTOUCH_DONE_BIT, true, false, portMAX_DELAY);if(uxBits & CONNECTED_BIT) {// 连接到 AP 后的日志ESP_LOGI(TAG, "WiFi Connected to ap");}if(uxBits & ESPTOUCH_DONE_BIT) {// SmartConfig 完成后的日志ESP_LOGI(TAG, "smartconfig over");// 停止 SmartConfigesp_smartconfig_stop();// 删除 SmartConfig 任务vTaskDelete(NULL);}}
}

监听WiFi 获取账号密码回调,进行联网,联网成功后关闭SmartConfig配网模式

static void event_handler(void* arg, esp_event_base_t event_base,int32_t event_id, void* event_data)
{if (event_base == SC_EVENT && event_id == SC_EVENT_GOT_SSID_PSWD) {// SmartConfig 获取到 SSID 和密码事件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));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);if (evt->type == SC_TYPE_ESPTOUCH_V2) {// 如果使用的是 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");}// 断开当前 WiFi 连接,设置新的 WiFi 配置并重新连接ESP_ERROR_CHECK( esp_wifi_disconnect() );ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );esp_wifi_connect();} else if (event_base == SC_EVENT && event_id == SC_EVENT_SEND_ACK_DONE) {// SmartConfig 发送 ACK 完成事件,设置 SmartConfig 完成标志位xEventGroupSetBits(s_wifi_event_group, ESPTOUCH_DONE_BIT);}
}

最终整体代码如下:

#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_eap_client.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_smartconfig.h"
#include "esp_mac.h"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_example_task(void * parm);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) {// WiFi 站点模式启动后,创建 SmartConfig 任务xTaskCreate(smartconfig_example_task, "smartconfig_example_task", 4096, NULL, 3, NULL);} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {// WiFi 断开连接时,重新连接并清除连接标志位esp_wifi_connect();xEventGroupClearBits(s_wifi_event_group, CONNECTED_BIT);} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {// 获取到 IP 地址后,设置连接标志位xEventGroupSetBits(s_wifi_event_group, CONNECTED_BIT);} else if (event_base == SC_EVENT && event_id == SC_EVENT_SCAN_DONE) {// SmartConfig 扫描完成事件ESP_LOGI(TAG, "Scan done");} else if (event_base == SC_EVENT && event_id == SC_EVENT_FOUND_CHANNEL) {// SmartConfig 找到信道事件ESP_LOGI(TAG, "Found channel");} else if (event_base == SC_EVENT && event_id == SC_EVENT_GOT_SSID_PSWD) {// SmartConfig 获取到 SSID 和密码事件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));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);if (evt->type == SC_TYPE_ESPTOUCH_V2) {// 如果使用的是 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");}// 断开当前 WiFi 连接,设置新的 WiFi 配置并重新连接ESP_ERROR_CHECK( esp_wifi_disconnect() );ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );esp_wifi_connect();} else if (event_base == SC_EVENT && event_id == SC_EVENT_SEND_ACK_DONE) {// SmartConfig 发送 ACK 完成事件,设置 SmartConfig 完成标志位xEventGroupSetBits(s_wifi_event_group, ESPTOUCH_DONE_BIT);}
}static void initialise_wifi(void)
{// 初始化网络接口ESP_ERROR_CHECK(esp_netif_init());// 创建事件组s_wifi_event_group = xEventGroupCreate();// 创建默认事件循环ESP_ERROR_CHECK(esp_event_loop_create_default());// 创建默认的 WiFi 站点模式网络接口esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();assert(sta_netif);// 初始化 WiFi 配置wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();ESP_ERROR_CHECK( esp_wifi_init(&cfg) );// 注册事件处理函数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) );// 设置 WiFi 模式为站点模式并启动 WiFiESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );ESP_ERROR_CHECK( esp_wifi_start() );
}static void smartconfig_example_task(void * parm)
{EventBits_t uxBits;// 设置 SmartConfig 类型为 SC_TYPE_ESPTOUCH_AIRKISSESP_ERROR_CHECK( esp_smartconfig_set_type(SC_TYPE_ESPTOUCH_AIRKISS) );smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT();// 启动 SmartConfigESP_ERROR_CHECK( esp_smartconfig_start(&cfg) );while (1) {// 等待连接标志位或 SmartConfig 完成标志位uxBits = xEventGroupWaitBits(s_wifi_event_group, CONNECTED_BIT | ESPTOUCH_DONE_BIT, true, false, portMAX_DELAY);if(uxBits & CONNECTED_BIT) {// 连接到 AP 后的日志ESP_LOGI(TAG, "WiFi Connected to ap");}if(uxBits & ESPTOUCH_DONE_BIT) {// SmartConfig 完成后的日志ESP_LOGI(TAG, "smartconfig over");// 停止 SmartConfigesp_smartconfig_stop();// 删除 SmartConfig 任务vTaskDelete(NULL);}}
}void app_main(void)
{// 初始化 NVS 闪存ESP_ERROR_CHECK( nvs_flash_init() );// 初始化 WiFiinitialise_wifi();
}

这部分代码编译运行后,就会进入SC_TYPE_ESPTOUCH_AIRKISS配网模式,并打印以下日志。

I (568) smartconfig: SC version: V3.0.2
I (5388) wifi:ic_enable_sniffer
I (5388) smartconfig: Start to find channel...
I (5388) smartconfig_example: Scan done

接着我们手机连接一个2.4G的路由(重要,不支持5G WiFi,打开乐鑫小程序,点击Airkiss配网,输入密码,点击配网即可。当然你下载ESPTouch APP进行配网也是可以的。

注意:Smartconfig配网通过UDP广播实现,所以手机离板卡越近越好,周围的环境干扰越少越好。

一般配网失败就两种情况

  1. 1、连接了5G的路由
  2. 2、输入密码错误,或者有特殊的字符
  3. 3、周围的2.4G干扰多

所以按上述流程排查下,多试几次即可,如果配网成功,就可以看到以下日志啦,主要看下接收到的SSID和PASSWORD对不对。

I (48578) smartconfig: TYPE: AIRKISS
I (48578) smartconfig: T|AP MAC: 00:94:ec:89:df:34
I (48578) smartconfig: Found channel on 6-0. Start to get ssid and password...
I (48588) smartconfig_example: Found channel
I (52068) smartconfig: T|pswd: 123456789
I (52068) smartconfig: T|ssid: leo
I (52068) wifi:ic_disable_sniffer
I (52068) smartconfig_example: Got SSID and password
I (52068) smartconfig_example: SSID:leo
I (52078) smartconfig_example: PASSWORD:123456789
W (52078) wifi:Password length matches WPA2 standards, authmode threshold changes from OPEN to WPA2
I (53168) wifi:new:<6,0>, old:<6,0>, ap:<255,255>, sta:<6,0>, prof:1, snd_ch_cfg:0x0
I (53168) wifi:state: init -> auth (0xb0)
I (53178) wifi:state: auth -> assoc (0x0)
I (53178) wifi:state: assoc -> run (0x10)
I (53208) wifi:connected with smart_013, aid = 50, channel 6, BW20, bssid = 00:94:ec:89:df:34
I (53208) wifi:security: WPA2-PSK, phy: bgn, rssi: -35
I (53228) wifi:pm start, type: 1I (53228) wifi:dp: 1, bi: 102400, li: 3, scale listen interval from 307200 us to 307200 us
I (53228) wifi:set rx beacon pti, rx_bcn_pti: 0, bcn_timeout: 25000, mt_pti: 0, mt_time: 10000
I (53288) wifi:AP's beacon interval = 102400 us, DTIM period = 1
I (54338) esp_netif_handlers: sta ip: 192.168.3.56, mask: 255.255.255.0, gw: 192.168.3.1
I (54338) smartconfig_example: WiFi Connected to ap
I (57418) smartconfig_example: smartconfig over

上述代码,每次启动后,不管之前有没有配置过,都会进入配网模式,不符合实际的应用,这里对代码进行修改,使配网的信息(帐号和密码)被保存在 NVS 中,每次配网之前读取NVS中的信息,如果未配网过,则进行配网操作,如果已经配网过,则直接连接路由器,只需要修改smartconfig_example_task函数即可。

static void smartconfig_example_task(void * parm)
{EventBits_t uxBits;wifi_config_t myconfig = {0};ESP_LOGI(TAG, "creat smartconfig_example_task");// 获取wifi配置信息esp_wifi_get_config(ESP_IF_WIFI_STA, &myconfig);if (strlen((char*)myconfig.sta.ssid) > 0){//如果配置过,就直接连接wifiESP_LOGI(TAG, "alrealy set, SSID is :%s,start connect", myconfig.sta.ssid);esp_wifi_connect();}else{// 如果没有配置过,就进行配网操作ESP_LOGI(TAG, "have no set, start to config");ESP_ERROR_CHECK( esp_smartconfig_set_type(SC_TYPE_ESPTOUCH_AIRKISS) );//支持APP ESPTOUCH和微信AIRKISSsmartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT();ESP_ERROR_CHECK( esp_smartconfig_start(&cfg) );}while (1) {// 等待连接标志位或 SmartConfig 完成标志位uxBits = xEventGroupWaitBits(s_wifi_event_group, CONNECTED_BIT | ESPTOUCH_DONE_BIT, true, false, portMAX_DELAY);if(uxBits & CONNECTED_BIT) {// 连接到 AP 后的日志ESP_LOGI(TAG, "WiFi Connected to ap");}if(uxBits & ESPTOUCH_DONE_BIT) {// SmartConfig 完成后的日志ESP_LOGI(TAG, "smartconfig over");// 停止 SmartConfigesp_smartconfig_stop();// 删除 SmartConfig 任务vTaskDelete(NULL);}}
}

想要重新配网,需要删除原来配网保存的信息,这里提供两种方法。

方法一:调试阶段,执行idf.py erase_flash擦除flash

idf.py erase_flash

方法二:产品阶段,增加一个按键或交互入口来重置配网信息,适用实际项目开发。

通过触发按键,例如长按10秒来调用esp_wifi_restore()函数,重置配网信息。

http://www.xdnf.cn/news/759547.html

相关文章:

  • 【PyQt5】从零开始的PyQt5 - QLabel篇
  • 18. Qt系统相关:多线程
  • Matlab作图之 subplot
  • 抖音客户端训练营--day2
  • 一:UML类图
  • JMeter 直连数据库
  • 1Remote远程会话管理以及一键启动虚拟机
  • Spring 中 @Value 注解多实例配置方案详解
  • 看问题的本质背后是什么?
  • pycharm打印时不换行,方便对比观察
  • Vue3.5 企业级管理系统实战(二十一):菜单权限
  • 前端面经 两栏布局
  • 远程管理SSH服务的搭建
  • 论文略读:Uncertainty-Aware Graph Structure Learning
  • 开源版 PyMOL 如何绘制 新冠病毒 分子结构?
  • Vue单文件组件
  • 打卡day42
  • 【Netty】EventLoopGroup
  • waitpid的waitstatus 含义源码解读
  • Linux 内核中 skb_orphan 的深度解析:从版本差异到核心机制
  • JOIN使用的注意事项
  • HTTP协议解析
  • 话题通信之python实现
  • 【免杀】C2免杀技术(十三)Inline Hook 概念篇
  • C# winform 教程(一)
  • Hartree-Fock 自洽场计算流程
  • Oracle正则表达式学习
  • 正则表达式笔记
  • yolo目标检测助手:具有模型预测、图像标注功能
  • 【复杂网络分析】什么是modularity?