【esp32-s3】如何进行WiFi配网

0. 前言

飞书文档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去连接网络。
优缺点:这种方式简洁,用户也很容易操作,但是配网成功率受环境影响较大。

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

1. 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,可以手机应用商店下载
 

乐鑫微信小程序
 

2. 代码的具体流程

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

具体流程如下:

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

2-1 初始化NVS

ESP_ERROR_CHECK( nvs_flash_init() );

2-2 初始化网络协议栈

    // 初始化网络接口
    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 模式为站点模式并启动 WiFi
    ESP_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_AIRKISS
    ESP_ERROR_CHECK( esp_smartconfig_set_type(SC_TYPE_ESPTOUCH_AIRKISS) );
    smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT();
    // 启动 SmartConfig
    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");
            // 停止 SmartConfig
            esp_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 模式为站点模式并启动 WiFi
    ESP_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_AIRKISS
    ESP_ERROR_CHECK( esp_smartconfig_set_type(SC_TYPE_ESPTOUCH_AIRKISS) );
    smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT();
    // 启动 SmartConfig
    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");
            // 停止 SmartConfig
            esp_smartconfig_stop();
            // 删除 SmartConfig 任务
            vTaskDelete(NULL);
        }
    }
}

void app_main(void)
{
    // 初始化 NVS 闪存
    ESP_ERROR_CHECK( nvs_flash_init() );
    // 初始化 WiFi
    initialise_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: 1

I (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)
    {
        //如果配置过,就直接连接wifi
        ESP_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和微信AIRKISS
        smartconfig_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");
            // 停止 SmartConfig
            esp_smartconfig_stop();
            // 删除 SmartConfig 任务
            vTaskDelete(NULL);
        }
    }
}

 

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

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

idf.py erase_flash

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

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

posted @ 2025-07-30 01:03  FBshark  阅读(77)  评论(0)    收藏  举报