目录
一、关于WIFI配网
一个WIFI设备需要连上路由器发出的WIFI信号(即AP),才能够上网并通过网络进行数据交换。设备通过某些方式获得WIFI的名称(SSID)和密码(Password)信息的整个过程叫做配网。在ESP32中有几种配网模式
- SmartConfig配网,这是由TI研发的一种配网方式,手机发出一组特殊长度编码的带WIFI信息的UDP包,设备循环在每一个通道上监听UDP包,当监听到符合长度规则的UDP包时,获取UDP包中的SSID和Password,然后配置WIFI芯片为STA模式去连接WIFI,如果没有监听到UDP包则切换至下一个通道,如此往复。
- SoftAP配网,先将设备设置为AP模式,手机连接到此AP,将WIFI信息发送给设备,然后设备配置WIFI到STA模式,去连接WIFI,是较常见的配网方式。
- 蓝牙配网,开启设备的蓝牙,使用蓝牙传输WIFI信息,再将设备配置为STA模式去连接WIFI,也是常用的配网方式。
本文通过调用ESP32-IDF的WIFI_PROV模块,完成SoftAP配网和BLE配网,参考ESP历程:examples/provisioning/wifi_prov_mgr
二、SoftAP配网
1、使用wifi_prov_mgr_init初始化配网模式
- 设置WIFI_PROV中的wifi_prov_mgr_config_t的scheme成员为wifi_prov_scheme_softap
- 设置WIFI_PROV中的wifi_prov_mgr_config_t的scheme_event_handler为WIFI_PROV_EVENT_HANDLER_NONE
在官方文档中有说明,scheme_event_handler是wifi_prov API为方案定义的专属事件处理程序,通过设置此变量,wifi_prov API会在使用蓝牙配网完成时,自动释放掉蓝牙所使用的内存。SoftAP配网则不需要特殊处理程序。
- 使用wifi_prov_mgr_init初始化配网模式与处理信息
wifi_prov_mgr_config_t config = {
.scheme = wifi_prov_scheme_softap,
.scheme_event_handler = WIFI_PROV_EVENT_HANDLER_NONE
};
ESP_ERROR_CHECK( wifi_prov_mgr_init(config) )
2、绑定事件处理回调函数
- 通过esp_event_handler_register()将WIFI_PROV的事件绑定到事件处理函数中,同时也要绑定WIFI事件和IP事件
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_PROV_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
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));
- 在事件处理函数中获取WIFI的SSID和Password,WIFI_PROV_CRED_RECV事件触发时,表示已经获取到WIFI的SSID和Password,PROV API会使用这组数据连接WIFI
- 事件处理函数中,主要关注WIFI_PROV_CRED_RECV事件获取WIFI信息和WIFI_PROV_END事件,释放内存。
static void event_handler(void* arg, esp_event_base_t event_base,
int event_id, void* event_data)
{
if (event_base == WIFI_PROV_EVENT) {
switch (event_id) {
case WIFI_PROV_START:
ESP_LOGI(TAG, "Provisioning started");
break;
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;
}
case WIFI_PROV_CRED_FAIL: {
wifi_prov_sta_fail_reason_t *reason = (wifi_prov_sta_fail_reason_t *)event_data;
ESP_LOGE(TAG, "Provisioning failed!\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");
break;
}
case WIFI_PROV_CRED_SUCCESS:
ESP_LOGI(TAG, "Provisioning successful");
break;
case WIFI_PROV_END:
/*配网完成后,释放占用内存。*/
wifi_prov_mgr_deinit();
break;
default:
break;
}
}
}
3、启动配网
- 在启动配网之前,需要使用wifi_prov_mgr_is_provisioned()函数来检测Flash中是否存在WIFI信息,判断是否完成配网
- 在WIFI_PROV配网API中,会将获取的WIFI信息直接写入到Flash中,所以每次启动配网前使用此函数检测是否已有WIFI信息,当有信息存在时则可以直接启动WIFI
- 官方文档中说明,此方式保存的SSID和Password并没有自己的 NVS 命名空间来存储 Wi-Fi,而是通过WIFI库的函数来保存,所以不能使用NVS函数来获取WIFI信息,但可以使用esp_wifi_get_config ()函数来获取Flash中的WIFI信息
- 调用wifi_prov_mgr_start_provisioning来启动配网
wifi_prov_mgr_start_provisioning(wifi_prov_security_t security, const void *wifi_prov_sec_params,
const char *service_name, const char *service_key)
- 参数security是表示安全等级,在官方文档中表明有0,1,2三种等级,具体可参考官网文档
- 0是明文
- 1是基于 Curve25519 的密钥交换、共享密钥派生和 AES256-CTR 模式的数据加密
- 2则是Secure Remote Password (SRP6a) 协议加密
- 参数wifi_prov_sec_params 是加密参数,有设置加密则需要传入加密信息,没有传入NULL
- 参数service_name是设备作为AP热点时的SSID
- 参数service_key是设备作为AP热点时的Password
4、整体代码
/* Wi-Fi Provisioning Manager Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#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_softap.h>
static const char *TAG = "wifi_provisioning_test";
const int WIFI_CONNECTED_EVENT = BIT0;
static EventGroupHandle_t wifi_event_group;
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
#ifdef CONFIG_EXAMPLE_RESET_PROV_MGR_ON_FAILURE
static int retries;
#endif
if (event_base == WIFI_PROV_EVENT) {
switch (event_id) {
//启动配网事件
case WIFI_PROV_START:
ESP_LOGI(TAG, "Provisioning started");
break;
//收到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信息接收失败事件
case WIFI_PROV_CRED_FAIL: {
wifi_prov_sta_fail_reason_t *reason = (wifi_prov_sta_fail_reason_t *)event_data;
ESP_LOGE(TAG, "Provisioning failed!\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");
break;
}
//配网成功事件
case WIFI_PROV_CRED_SUCCESS:
ESP_LOGI(TAG, "Provisioning successful");
break;
//配网结束事件
case WIFI_PROV_END:
//配网结束 关闭配网
wifi_prov_mgr_deinit();
break;
default:
break;
}
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
printf("WIFI STA START\r\n");
esp_wifi_connect();
} 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);
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
ESP_LOGI(TAG, "Disconnected. Connecting to the AP again...");
esp_wifi_connect();
}
}
static void wifi_init_sta(void)
{
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());
}
void app_main(void)
{
//每次上电都擦除Flash,方便后续调试
//nvs_flash_erase();
/* 初始化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());
ESP_ERROR_CHECK(nvs_flash_init());
}
/* TCP/TP协议栈 */
ESP_ERROR_CHECK(esp_netif_init());
/* 初始化默认事件循环 */
ESP_ERROR_CHECK(esp_event_loop_create_default());
wifi_event_group = xEventGroupCreate();
/* 注册事件处理函数 */
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_PROV_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
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_netif_create_default_wifi_sta();
/* 设置WIFI模式为AP模式 */
esp_netif_create_default_wifi_ap();
/* 使用默认WIFI配置参数 */
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
/* 设置配网模式为SoftAP */
wifi_prov_mgr_config_t config = {
.scheme = wifi_prov_scheme_softap,
.scheme_event_handler = WIFI_PROV_EVENT_HANDLER_NONE
};
/* 初始化配网 */
ESP_ERROR_CHECK(wifi_prov_mgr_init(config));
bool provisioned = false;
/* 判断是否有WIFI数据 */
ESP_ERROR_CHECK(wifi_prov_mgr_is_provisioned(&provisioned));
if (!provisioned) {
ESP_LOGI(TAG, "Starting provisioning");
/* AP模式时SSID */
char service_name[12] = "wifi_test";
/* 设置安全等级 */
wifi_prov_security_t security = WIFI_PROV_SECURITY_1;
/* 加密秘钥 */
const char *pop = "abcd1234";
wifi_prov_security1_params_t *sec_params = pop;
//const char *username = NULL;
/* AP模式时的WIFI Password */
const char *service_key = "88888888";
/* 启动配网 */
ESP_ERROR_CHECK(wifi_prov_mgr_start_provisioning(security, (const void *) sec_params, service_name, service_key));
}
/* 已配网,直接设置为STA模式连接WIFI */
else {
ESP_LOGI(TAG, "Already provisioned, starting Wi-Fi STA");
printf("start WIFI\r\n");
wifi_prov_mgr_deinit();
wifi_init_sta();
}
//等待WIFI连接成功
xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_EVENT, false, true, portMAX_DELAY);
while (1) {
ESP_LOGI(TAG, "Hello World!");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
5、使用乐鑫APP配网
- 使用手机连接到设备发出的AP热点,这里名字为wifi_test,密码为88888888
- 在APP中设置为SoftAP配网,连接上设备后,在界面点击连接并输入加密秘钥,此处使用了等级1加密,加密秘钥为abcd1234
- 选择你要连接的WIFI,并输入对于的WIFI密码
- 查看控制台情况,是否连接成功
三、BLE配网
蓝牙配网与SoftAP配网在API调用上基本一致,唯一的区别的是在配网config_t结构体中的模式的选择和自定义一个蓝牙UUID
- 区别点1
/* 设置配网模式为BLE模式 */
wifi_prov_mgr_config_t config = {
.scheme = wifi_prov_scheme_ble,
.scheme_event_handler = WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM
};
这里的scheme_event_handler 处理事件,要根据情况选择,如果后续不使用BLE和经典蓝牙,选择WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM,即释放掉全部蓝牙内存,如后续要继续使用蓝牙,则根据情况选择释放经典蓝牙或者BLE部分的内存
- 区别点2
/* 自定义128的UUID 16个字节 */
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);
1、完整代码
/* Wi-Fi Provisioning Manager Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#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_softap.h>
#include <wifi_provisioning/scheme_ble.h>
static const char *TAG = "wifi_provisioning_test";
const int WIFI_CONNECTED_EVENT = BIT0;
static EventGroupHandle_t wifi_event_group;
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
#ifdef CONFIG_EXAMPLE_RESET_PROV_MGR_ON_FAILURE
static int retries;
#endif
if (event_base == WIFI_PROV_EVENT) {
switch (event_id) {
//启动配网事件
case WIFI_PROV_START:
ESP_LOGI(TAG, "Provisioning started");
break;
//收到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信息接收失败事件
case WIFI_PROV_CRED_FAIL: {
wifi_prov_sta_fail_reason_t *reason = (wifi_prov_sta_fail_reason_t *)event_data;
ESP_LOGE(TAG, "Provisioning failed!\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");
break;
}
//配网成功事件
case WIFI_PROV_CRED_SUCCESS:
ESP_LOGI(TAG, "Provisioning successful");
break;
//配网结束事件
case WIFI_PROV_END:
//配网结束 关闭配网
wifi_prov_mgr_deinit();
break;
default:
break;
}
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
printf("WIFI STA START\r\n");
esp_wifi_connect();
} 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);
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
ESP_LOGI(TAG, "Disconnected. Connecting to the AP again...");
esp_wifi_connect();
}
}
static void wifi_init_sta(void)
{
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());
}
void app_main(void)
{
//每次上电都擦除Flash,方便后续调试
nvs_flash_erase();
/* 初始化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());
ESP_ERROR_CHECK(nvs_flash_init());
}
/* TCP/TP协议栈 */
ESP_ERROR_CHECK(esp_netif_init());
/* 初始化默认事件循环 */
ESP_ERROR_CHECK(esp_event_loop_create_default());
wifi_event_group = xEventGroupCreate();
/* 注册事件处理函数 */
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_PROV_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
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));
/* 设置WIFI模式为STA模式 */
esp_netif_create_default_wifi_sta();
/* 设置WIFI模式为AP模式 */
//esp_netif_create_default_wifi_ap();
/* 使用默认WIFI配置参数 */
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
/* 设置配网模式为BLE模式 */
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));
bool provisioned = false;
/* 判断是否有WIFI数据 */
ESP_ERROR_CHECK(wifi_prov_mgr_is_provisioned(&provisioned));
if (!provisioned) {
ESP_LOGI(TAG, "Starting provisioning");
/* BLE模式时SSID */
char service_name[12] = "ble_test";
/* 自定义128的UUID 8个字节 */
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_security_t security = WIFI_PROV_SECURITY_1;
/* 加密秘钥 */
const char *pop = "abcd1234";
wifi_prov_security1_params_t *sec_params = pop;
//const char *username = NULL;
/* AP模式时的WIFI Password */
//const char *service_key = "88888888";
/* 启动配网 */
ESP_ERROR_CHECK(wifi_prov_mgr_start_provisioning(security, (const void *) sec_params, service_name, NULL));
}
/* 已配网,直接设置为STA模式连接WIFI */
else {
ESP_LOGI(TAG, "Already provisioned, starting Wi-Fi STA");
printf("start WIFI\r\n");
wifi_prov_mgr_deinit();
wifi_init_sta();
}
//等待WIFI连接成功
xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_EVENT, false, true, portMAX_DELAY);
while (1) {
ESP_LOGI(TAG, "Hello World!");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
2、查看配网情况
- 在乐鑫APP中选择BLE模式配网,和上面的SoftAP模式配网一样,选择对应的蓝牙设备,填写加密秘钥后输入WIFI信息,即可完成配网
- 在nRFconnect中查看,蓝牙服务UUID为 L b4df5a1c3f6bf4bfea4a820304901a02 M
- 查看控制台信息
四、总结
- 两种模式的配网各有各的优点,需要根据不同的产品和使用场景来进行选择。
- 基于乐鑫的配网API,对于两种配网模式的使用都比较好上手