第08章 配网
约 2411 字大约 8 分钟
2026-03-23
如果要实现音视频信息的实时推送和接收, 必须先连接网络. 在门锁项目中,我们是在项目中把wifi的ssid和密码写死的, 这在实际的产品中是不可行的!
由于每个用户要连接的wifi是不同的, 必须给用户提供自助选择ssid和输入密码的功能
1. 配网方式
esp提供了2种配网方式:
(1)Wi-Fi(SoftAP + HTTP 服务器)
就是先在esp中开启wifi热点, 并启动一个http服务器. 手机wifi连接这个热点, 然后访问http服务器提供的配网页面, 在页面中选择esp将来要连接的wifi ap, 并填写密码. 然后重启esp, esp就会连接刚刚配置的WiFi了
(2)低功耗蓝牙(基于 GATT)传输方式。
就是通过蓝牙把相应的wifi信息传输给esp32
我们这次选择使用低功耗蓝牙配网
下载esp专用的手机配网App:
下载地址:
2. 用户配置


3. WIFI 模块
3.1 移植示例代码并修改
① 示例代码

② 安装二维码组件

③ 基于官方示例实现wifi模块代码
dri_wifi.h
#ifndef __dri_wifi_H__
#define __dri_wifi_H__
#include <stdio.h>
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/event_groups.h>
#include <esp_log.h>
#include <esp_wifi.h>
#include <esp_event.h>
#include <nvs_flash.h>
#include <wifi_provisioning/manager.h>
#include <wifi_provisioning/scheme_ble.h>
#include "qrcode.h"
/**
* @brief 配网初始化
*/
void dri_wifi_init(void);
#endif /* __dri_wifi_H__ */dri_wifi.c
#include "dri_wifi.h"
static const char *TAG = "wifi:";
/* Signal Wi-Fi events on this event-group */
const int WIFI_CONNECTED_EVENT = BIT0;
static EventGroupHandle_t wifi_event_group;
#define PROV_QR_VERSION "v1"
#define PROV_TRANSPORT_SOFTAP "softap"
#define PROV_TRANSPORT_BLE "ble"
#define QRCODE_BASE_URL "https://espressif.github.io/esp-jumpstart/qrcode.html"
// 配网事件回调函数
static void event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
// 配网信息发送重试次数
static int retries;
static int wifi_retries;
// event_base就是当前的事件类型
// WIFI_PROV_EVENT : 配网事件
if (event_base == WIFI_PROV_EVENT)
{
switch (event_id)
{
// WIFI_PROV_START : 配网开始
case WIFI_PROV_START:
ESP_LOGI(TAG, "配网开始");
break;
// WIFI_PROV_CRED_SENT : 配网信息发送成功 接收到了wifi的账号和密码
case WIFI_PROV_CRED_RECV:
{
wifi_sta_config_t *wifi_sta_cfg = (wifi_sta_config_t *)event_data;
ESP_LOGI(TAG, "Received Wi-Fi credentials"
"\n\tSSID : %s\n\tPassword : %s",
(const char *)wifi_sta_cfg->ssid,
(const char *)wifi_sta_cfg->password);
break;
}
// WIFI_PROV_CRED_FAIL : 配网信息发送失败
case WIFI_PROV_CRED_FAIL:
{
wifi_prov_sta_fail_reason_t *reason = (wifi_prov_sta_fail_reason_t *)event_data;
ESP_LOGE(TAG, "配网发送信息失败!\n\tReason : %s"
"\n\tPlease reset to factory and retry provisioning",
(*reason == WIFI_PROV_STA_AUTH_ERROR) ? "Wi-Fi station authentication failed" : "Wi-Fi access-point not found");
// 如果配网信息发送失败,次数累加
retries++;
if (retries >= 5)
{
ESP_LOGI(TAG, "Failed to connect with provisioned AP, resetting provisioned credentials");
wifi_prov_mgr_reset_sm_state_on_failure();
retries = 0;
}
break;
}
// WIFI_PROV_CRED_SUCCESS : 配网信息发送成功
case WIFI_PROV_CRED_SUCCESS:
ESP_LOGI(TAG, "配网信息发送成功");
retries = 0;
break;
case WIFI_PROV_END:
/* De-initialize manager once provisioning is finished */
wifi_prov_mgr_deinit();
break;
default:
break;
}
}
// WIFI_EVENT : Wi-Fi事件
else if (event_base == WIFI_EVENT)
{
switch (event_id)
{
// WIFI_EVENT_STA_START: wifi连接开始
case WIFI_EVENT_STA_START:
esp_wifi_connect();
break;
// WIFI_EVENT_STA_CONNECTED: wifi连接失败
case WIFI_EVENT_STA_DISCONNECTED:
ESP_LOGI(TAG, "wifi连接不稳定,断开连接...");
esp_wifi_connect();
break;
default:
break;
}
}
// IP_EVENT : IP事件 (拿到了ip 说明联网成功)
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
{
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
ESP_LOGI(TAG, "连接成功 with IP Address:" IPSTR, IP2STR(&event->ip_info.ip));
// 一旦wifi连接成功 把错误次数清0
wifi_retries = 0;
// 事件标志组有一个位表示是否连接wifi成功,现在它把这个标志位置1 ,表示wifi联网成功
xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_EVENT);
}
// PROTOCOMM_TRANSPORT_BLE_EVENT:蓝牙事件
else if (event_base == PROTOCOMM_TRANSPORT_BLE_EVENT)
{
switch (event_id)
{
case PROTOCOMM_TRANSPORT_BLE_CONNECTED:
ESP_LOGI(TAG, "BLE transport: 蓝牙已连接!");
break;
case PROTOCOMM_TRANSPORT_BLE_DISCONNECTED:
ESP_LOGI(TAG, "BLE transport: 蓝牙已断开!");
break;
default:
break;
}
}
// PROTOCOMM_SECURITY_SESSION_EVENT:安全会话事件
else if (event_base == PROTOCOMM_SECURITY_SESSION_EVENT)
{
switch (event_id)
{
case PROTOCOMM_SECURITY_SESSION_SETUP_OK:
ESP_LOGI(TAG, "Secured session established!");
break;
case PROTOCOMM_SECURITY_SESSION_INVALID_SECURITY_PARAMS:
ESP_LOGE(TAG, "Received invalid security parameters for establishing secure session!");
break;
case PROTOCOMM_SECURITY_SESSION_CREDENTIALS_MISMATCH:
ESP_LOGE(TAG, "Received incorrect username and/or PoP for establishing secure session!");
break;
default:
break;
}
}
}
static void wifi_init_sta(void)
{
/* Start Wi-Fi in station mode */
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());
}
static void get_device_service_name(char *service_name, size_t max)
{
uint8_t eth_mac[6];
const char *ssid_prefix = "PROV_";
esp_wifi_get_mac(WIFI_IF_STA, eth_mac);
snprintf(service_name, max, "%s%02X%02X%02X",
ssid_prefix, eth_mac[3], eth_mac[4], eth_mac[5]);
}
/* Handler for the optional provisioning endpoint registered by the application.
* The data format can be chosen by applications. Here, we are using plain ascii text.
* Applications can choose to use other formats like protobuf, JSON, XML, etc.
* Note that memory for the response buffer must be allocated using heap as this buffer
* gets freed by the protocomm layer once it has been sent by the transport layer.
*/
esp_err_t custom_prov_data_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen,
uint8_t **outbuf, ssize_t *outlen, void *priv_data)
{
if (inbuf)
{
ESP_LOGI(TAG, "Received data: %.*s", inlen, (char *)inbuf);
}
char response[] = "SUCCESS";
*outbuf = (uint8_t *)strdup(response);
if (*outbuf == NULL)
{
ESP_LOGE(TAG, "System out of memory");
return ESP_ERR_NO_MEM;
}
*outlen = strlen(response) + 1; /* +1 for NULL terminating byte */
return ESP_OK;
}
static void wifi_prov_print_qr(const char *name, const char *username, const char *pop, const char *transport)
{
if (!name || !transport)
{
ESP_LOGW(TAG, "Cannot generate QR code payload. Data missing.");
return;
}
char payload[150] = {0};
snprintf(payload, sizeof(payload), "{\"ver\":\"%s\",\"name\":\"%s\""
",\"transport\":\"%s\"}",
PROV_QR_VERSION, name, transport);
ESP_LOGI(TAG, "Scan this QR code from the provisioning application for Provisioning.");
esp_qrcode_config_t cfg = ESP_QRCODE_CONFIG_DEFAULT();
esp_qrcode_generate(&cfg, payload);
ESP_LOGI(TAG, "If QR code is not visible, copy paste the below URL in a browser.\n%s?data=%s", QRCODE_BASE_URL, payload);
}
void dri_wifi_init(void)
{
// 1. 初始化nvs(未来用来保存收到的wifi名和密码)
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
/* NVS partition was truncated
* and needs to be erased */
ESP_ERROR_CHECK(nvs_flash_erase());
/* Retry nvs_flash_init */
ESP_ERROR_CHECK(nvs_flash_init());
}
// 2. 初始化TCP/IP (在esp中,如果需要做网络连接,首先要初始化tcp/ip)
ESP_ERROR_CHECK(esp_netif_init());
// 3. 创建事件循环组实例(esp32本身就有一个事件事件循环组,我们创建一个实例,用来给他注册事件)
ESP_ERROR_CHECK(esp_event_loop_create_default());
wifi_event_group = xEventGroupCreate();
// 4. 给事件循环组实例 注册某个事件和事件的回调函数
// 4.1 wifi配网事件
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_PROV_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
// 4.2 蓝牙相关事件
ESP_ERROR_CHECK(esp_event_handler_register(PROTOCOMM_TRANSPORT_BLE_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
// 4.3 安全会话事件
ESP_ERROR_CHECK(esp_event_handler_register(PROTOCOMM_SECURITY_SESSION_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
// 4.4 wifi事件
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
// 4.5 ip事件
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));
// 5. 初始化wifi-sta模式 将来拿到密码之后 连接wifi
esp_netif_create_default_wifi_sta();
// 6. 初始化wifi配置
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// 7. 配网初始化
wifi_prov_mgr_config_t config = {
.scheme = wifi_prov_scheme_ble,
.scheme_event_handler = WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM};
ESP_ERROR_CHECK(wifi_prov_mgr_init(config));
// 8. 定义一个变量 用来表示是否已经配网
bool provisioned = false;
// #ifdef CONFIG_EXAMPLE_RESET_PROVISIONED
// wifi_prov_mgr_reset_provisioning();
// #else
// 9. 让变量获取配网状态
ESP_ERROR_CHECK(wifi_prov_mgr_is_provisioned(&provisioned));
/* 如果没有配网 */
if (!provisioned)
{
ESP_LOGI(TAG, "Starting provisioning");
// 当前是蓝牙配网,service_name就是保存的是当前的设备名称
char service_name[12];
get_device_service_name(service_name, sizeof(service_name));
// 蓝牙配网 我们当前设备要广播自己的蓝牙信息,蓝牙信息中需要有一个独一无二的id
uint8_t custom_service_uuid[] = {
/* LSB <---------------------------------------
* ---------------------------------------> MSB */
0xb4,
0xdf,
0x5a,
0x1c,
0x3f,
0x6b,
0xf4,
0xbf,
0xea,
0x4a,
0x82,
0x03,
0x04,
0x90,
0x1a,
0x02,
};
// 把uuid设置给蓝牙配网的配置中
wifi_prov_scheme_ble_set_service_uuid(custom_service_uuid);
// 给当前设备蓝牙 另开了一个接入点,用来接收其他信息
wifi_prov_mgr_endpoint_create("custom-data");
// 开启配网
ESP_ERROR_CHECK(wifi_prov_mgr_start_provisioning(WIFI_PROV_SECURITY_0, NULL, service_name, NULL));
// 给当前设备蓝牙 另开了一个接入点,用来接收其他信息,这个是接收其他信息的事件回调函数
wifi_prov_mgr_endpoint_register("custom-data", custom_prov_data_handler, NULL);
wifi_prov_print_qr(service_name, NULL, NULL, PROV_TRANSPORT_BLE);
}
// 如果已经配网
else
{
ESP_LOGI(TAG, "Already provisioned, starting Wi-Fi STA");
// 把配网的配置反初始化
wifi_prov_mgr_deinit();
// 启动wifi连接
wifi_init_sta();
}
/* 等待wifi连接标志位置位 */
xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_EVENT, true, true, portMAX_DELAY);
}② 添加弱函数联网成功后调用
dri_wifi.c
// 代码省略 ...
// 定义弱函数,联网成功后被调用
void __attribute__((weak)) dri_wifi_connected_cb(void)
{
}
// 代码省略 ...
/* 事件处理函数 */
static void event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
// 代码省略 ...
// IP事件
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
{
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
ESP_LOGI(TAG, "Connected with IP Address:" IPSTR, IP2STR(&event->ip_info.ip));
xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_EVENT);
// 调用联网成功后的弱函数
dri_wifi_connected_cb();
}
// 代码省略 ...
}3.2 长按按钮重新配网
① 安装 button 组件

② wifi 模块添加重新配网函数
dri_wifi.h
/**
* @brief 重新配网
*
*/
void dri_wifi_reset_provisioning(void);dri_wifi.c
/**
* @brief 重新配网
*
*/
void dri_wifi_reset_provisioning(void)
{
// 重新配网,删除原来的wifi信息
wifi_prov_mgr_reset_provisioning();
// 重启设备
esp_restart();
}③ 按键模块
int_button.h
#ifndef __INT_BUTTON_H__
#define __INT_BUTTON_H__
#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "unity.h"
#include "iot_button.h"
#include "button_gpio.h"
#include "esp_err.h"
/**
* @brief 按键SW2初始化
*
*/
void int_button_sw2_init(void);
#endif /* __INT_BUTTON_H__ */int_button.c
#include "int_button.h"
// 定义弱函数:按键事件触发后被调用
__attribute__((weak)) void int_button_sw2_long_press(void)
{
}
// 按键事件的回调函数
static void button_event_cb(void *arg, void *data)
{
// 调用弱函数
int_button_sw2_long_press();
}
/**
* @brief 按键SW2初始化
*
*/
void int_button_sw2_init(void)
{
// 1. 定义按键的时间参数
const button_config_t btn_cfg = {
.long_press_time = 3000,
.short_press_time = 200,
};
// 2. 定义按键的引脚参数
const button_gpio_config_t btn_gpio_cfg = {
.gpio_num = 8, // 按键连接GPIO8
.active_level = 0, // 按键按下输入低电平
.disable_pull = false, // 启用上拉电阻
};
// 3. 创建按键设备
button_handle_t btn = NULL;
iot_button_new_gpio_device(&btn_cfg, &btn_gpio_cfg, &btn);
// 4. 注册按键长按事件触发后的回调函数
iot_button_register_cb(btn, BUTTON_LONG_PRESS_HOLD, NULL, button_event_cb, NULL);
}4. 配网测试
4.1 代码
main.c
#include <stdio.h>
#include "esp_task.h"
#include "com_debug.h"
#include "dri_wifi.h"
#include "int_button.h"
// 重定义wifi联网成功后的弱函数
void dri_wifi_connected_cb(void)
{
MY_LOGI("WIFI联网成功!....");
}
// 重定义按键SW2长按事件触发后的弱函数
void int_button_sw2_long_press(void)
{
MY_LOGI("按键SW2长按事件触发!....");
// 重新配网
dri_wifi_reset_provisioning();
}
void app_main(void)
{
MY_LOGI("WIFI联网测试:");
// 初始化WIFI
dri_wifi_init();
// 初始化按键SW2
int_button_sw2_init();
while (1)
{
vTaskDelay(10);
}
}4.2 下载软件并安装
下载地址:
4.3 使用手机app进行配网
程序启动后, 会在控制台打印二维码, 每台设备的二维码是唯一的

打开手机app, 关掉安全通信(我们在代码中设置的安全级别为0)

然后扫描二维码, 输入要连接的wifi和密码, 就可以配网成功了. Wifi名和密码会存在nvs中,以后就会自动读取了
