ESP32_WiFi连接
一、WiFi连接
1.ESP32 WiFi功能简介
- ESP32硬件支持2.4G频段的WiFi和BLE 4.2;
- ESP32对WiFi的驱动支持十分完善,我们不需要花费过多精力去研究底层实现,可以将更多的精力放在自己的应用设计上;
2.ESP32 WiFi的三种工作方式
- STA(Station)模式,也称为站点模式或客户端模式,也是ESP32最常用的模式:
- 在这种模式下,ESP32作为WiFi客户端,主动连接到现有的WiFi接入点(AP,Access Point),从而实现与设备或网络的通信。
- 例如:ESP32通过连接路由器的WiFi来连接网络;
- SoftAP模式,机软接入点模式:
- 此时ESP32设备自身充当WiFi接入点,允许其他WiFi客户端连接到它,可以将其看作一个小型的无线热点;
- 例如:在没有现成的WiFi网络环境中,让手机或其他设备连接到ESP32创建的无线网络中,实现设备间的近距离通信,而无需外部的WiFi网络;
- Station + SoftAP模式:
- 这种模式结合了上述两种模式的特点,ESP32设备既可以作为WiFi客户端连接到其他接入点,同时又能作为软接入点为其他设备提供WiFi接入服务;
- 例如:一些物联网场景中,ESP32可以连接到家庭网络以获取网络连接,同时又可以作为附近一些低功耗传感器设备提供本地的WiFi接入,实现数据的收集和上传;
3.ESP32 STA模式代码实现
- ESP-IDF中在esp-idf/examples/wifi/getting_started/station路径下有对应的示例代码
- 加注释代码
#include <stdio.h> #include "nvs_flash.h" #include "esp_wifi.h" #include "esp_event.h" #include "esp_log.h" #include "esp_err.h" //? 错误检查的头文件 #define CON_SSID "ling" #define CON_PASSWORD "527628..." #define TAG "wifi_sta" // #define MY_CFG_ORDER void event_handle(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { if(event_base == WIFI_EVENT) { switch (event_id) { case WIFI_EVENT_STA_START: //? STA工作模式已启动 esp_wifi_connect(); //? 连接WiFi break; case WIFI_EVENT_STA_CONNECTED: //? EPS32已经成功连接到路由器 ESP_LOGI(TAG, "esp32 connected to api!"); break; case WIFI_EVENT_STA_DISCONNECTED: //? 断连 esp_wifi_connect(); default: break; } } else if(event_base == IP_EVENT) { switch (event_id) { case IP_EVENT_STA_GOT_IP: //? EPS32获取到无线路由器分配的IP,此时ESP32才真正的连接到路由器(如果路由器能连接到互联网,ESP32也能连接到互联网) ESP_LOGI(TAG, "esp32 get ip address"); break; default: break; } } } void app_main(void) { #ifndef MY_CFG_ORDER /* 1.初始化NVS 默认状态下,当我们使用一组SSID和密码连接WiFi成功后,ESP-IDF的底层会帮我们把这组SSID和密码保存到NVS中; 下次系统重启后,当进入STA模式并进行连接时,就会用这组SSID和密码进行连接; */ ESP_ERROR_CHECK(nvs_flash_init()); //? 宏ESP_ERROR_CHECK,用来检查NVS初始化是否有问题; // 2.初始化TCP/IP协议栈(ESP-IDF中使用的时LWIP) ESP_ERROR_CHECK(esp_netif_init()); //? netif--->net interface,网络接口的缩写; /* 3.创建事件系统循环 WiFi连接过程中会产生各种的事件,这些事件都是通过回调函数来通知我们的 */ ESP_ERROR_CHECK(esp_event_loop_create_default()); // 4.创建STA esp_netif_create_default_wifi_sta(); //? 该函数会返回一个STA网卡对象,这里我们使用不到,可以忽略该返回值; /* 5.初始化WiFi 定义一个WiFi初始化结构体,将其设置到WiFi初始化函数中; 该步骤会设置WiFi的缓冲区数量,加密功能等,我们按默认功能设置就可以; */ wifi_init_config_t wifi_init_cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&wifi_init_cfg)); // 6.注册事件响应 esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, event_handle, NULL); //? 注册WiFi事件回调 esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, event_handle, NULL); //? 注册IP事件回调(连接到路由器,获取到IP地址后就会触发该事件) // 7.WiFi参数配置 wifi_config_t wifi_config = { .sta = { .ssid = CON_SSID, //? SSID,服务器集标识符,即WiFi名称; .password = CON_PASSWORD, //? WiFi密码; .threshold.authmode = WIFI_AUTH_WPA2_PSK, //? 配置加密模式,目前常用的时WPA2_PSK; .pmf_cfg.capable = true, //? 是否启用保护管理帧,启用后可以增强安全性; .pmf_cfg.required = false, //? 是否只和有保护管理帧功能的设备通信; }, }; esp_wifi_set_config(WIFI_IF_STA, &wifi_config); // 8.设置WiFi模式 esp_wifi_set_mode(WIFI_MODE_STA); // 9.启动WiFi esp_wifi_start(); #else /* 1.初始化NVS 默认状态下,当我们使用一组SSID和密码连接WiFi成功后,ESP-IDF的底层会帮我们把这组SSID和密码保存到NVS中; 下次系统重启后,当进入STA模式并进行连接时,就会用这组SSID和密码进行连接; */ ESP_ERROR_CHECK(nvs_flash_init()); //? 宏ESP_ERROR_CHECK,用来检查NVS初始化是否有问题; // 2.初始化TCP/IP协议栈(ESP-IDF中使用的时LWIP) ESP_ERROR_CHECK(esp_netif_init()); //? netif--->net interface,网络接口的缩写; /* 3.创建事件系统循环 WiFi连接过程中会产生各种的事件,这些事件都是通过回调函数来通知我们的 */ ESP_ERROR_CHECK(esp_event_loop_create_default()); // 6.注册事件响应 esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, event_handle, NULL); //? 注册WiFi事件回调 esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, event_handle, NULL); //? 注册IP事件回调(连接到路由器,获取到IP地址后就会触发该事件) // 4.创建STA esp_netif_create_default_wifi_sta(); //? 该函数会返回一个STA网卡对象,这里我们使用不到,可以忽略该返回值; // 8.设置WiFi模式为STA esp_wifi_set_mode(WIFI_MODE_STA); /* 5.初始化WiFi 定义一个WiFi初始化结构体,将其设置到WiFi初始化函数中; 该步骤会设置WiFi的缓冲区数量,加密功能等,我们按默认功能设置就可以; */ wifi_init_config_t wifi_init_cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&wifi_init_cfg)); // 7.WiFi参数配置 wifi_config_t wifi_config = { .sta = { .ssid = CON_SSID, //? SSID,服务器集标识符,即WiFi名称; .password = CON_PASSWORD, //? WiFi密码; .threshold.authmode = WIFI_AUTH_WPA2_PSK, //? 配置加密模式,目前常用的时WPA2_PSK; .pmf_cfg.capable = true, //? 是否启用保护管理帧,启用后可以增强安全性; .pmf_cfg.required = false, //? 是否只和有保护管理帧功能的设备通信; }, }; esp_wifi_set_config(WIFI_IF_STA, &wifi_config); // 9.启动WiFi esp_wifi_start(); #endif }
二、SmartConfig 配网
1.配网
- 上节WiFi连接是把WiFi名称和密码写入代码中进行连接的,但实际应用中不可能这么做,需要把用户自己的WiFi名称和密码输入到设备中。对于没有屏幕输入功能的设备,就需要其他方法把SSID和密码写入到设备中。
- 通过某种方法,把WiFi的名称和密码高数设备,让设备能够正确连接WiFi的过程,称为配网;
- 配网的种类
- AP配网;
- SmartConfig配网;(相对简单)
- 蓝牙配网;
2.SmartConfig配网的原理
- SmartConfig步骤
- 1.先让芯片处于混杂模式下,监听网络中的所有报文;
- 2.手机APP将SSID和密码编码到UDP报文中,通过广播报或组播报文的形式发送;
- 3.芯片收到UDP报文后,解码得到正确的SSID和密码,之后连接到指定的路由;
- SmartConfig的原理
-
传输数据的特点——加密
在802.11无线协议中,MAC帧的数据是加密的,没有连上热点的设备是无法读取数据帧的具体内容,具体帧格式如下图所示
-
数据内容如何表示?——长度
如果手机发送的是UDP广播博文,则这部分加密报文就是IP报文------>虽然我们无法知道报文的确切内容,但我们可以知道报文的长度;------>SmartConfig就是根据UDP报文的长度传输SSID和Password的;- IP报文的组成:IPv4头部(20 Bytes) + UDP报文头部(8 Bytes) + UDP报文;UDP报文的长度称为“明文长度”
假设IPD报文长度为500 Bytes,则UDP报文长度: 500 - 20 - 8 = 472 Bytes; - 密文长度 = 明文长度 + 算法常量(固定值,由APP和WiFi设备默认);
- 示例:算法常量 = 10;当APP传输“1234”这个数据时,只需要在UDP报文中1196个字节即可【1234 - 20(IPv2头部长度) -8(UDP报文长度) -10(算法常量)】,UDP报文内容可任意填充;
- IP报文的组成:IPv4头部(20 Bytes) + UDP报文头部(8 Bytes) + UDP报文;UDP报文的长度称为“明文长度”
-
如何区分进行SmartConfig配网的数据——前导码
当设备在混杂模式时,会在所处环境中快速切换各条信道来抓取每个信道中的数据包------>当遇到正在发送前导码的的信道时,设备锁定该信道并继续接受UDP广播包,直到收到足够的数据来解码出WiFi的SSID和Password;------>为了方便和其他UDP广播包区分,前导码由几个特殊的字节组成------>在发送时,APP先发送3个前导码(3个UDP广播包),之后发送用于SmartConfig的UDP广播包,最后发送3个终止码; -
SmartConfig的流程:
- APP部分:假设手机APP要发送“test”这4个字符,算法常量为16,则具体流程如下
- (1)APP连续发送3个前导码(3个UDP广播包);
- (2)APP发送1个UDP广播包,报文总长度为’t’ - 16;
- (3)APP发送1个UDP广播包,报文总长度为’e’ - 16;
- (4)APP发送1个UDP广播包,报文总长度为’s’ - 16;
- (5)APP发送1个UDP广播包,报文总长度为’t’ - 16;
- (6)APP连续发送3个终止码
- (7)APP切换WiFi信道重复上述步骤
2.设备部分
- (1)设备进入混杂模式,监听网络中所有的报文;
- (2)连续收到3个前导码报文且来自同一个发射源;
- (3)持续捕获发射源的数据,直到连续收到3个终止码报文;
- (4)将捕获到的数据进行解码并缓存;
- (5)重复2~4步骤,二次验证数据;
- (6)使用上述步骤得到的WiFi SSID、Password连接WiFi,成功后向APP汇报;
3.注意:上述是传输的基本原理,但每家厂商的算法常量、前导码、终止码、传输内容格式会不太一样,因此不同厂家的SmartConfig一般是没有办法通用的;
-
3.ESP32 SmartConfig的APP——ESP Touch下载
- 乐鑫官网下载链接,需要在Github上下载(网速比较慢),下载后发送到手机安装即可
- 蓝奏云下载链接,下载密码:ga7x
4.示例代码(带注释)
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "esp_wifi.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "esp_smartconfig.h"
#define TAG "wifi_smartconfig"
void event_handle(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
{
if(event_base == WIFI_EVENT)
{
switch (event_id)
{
case WIFI_EVENT_STA_START: //? STA工作模式已启动
esp_wifi_connect(); //? 连接WiFi
break;
case WIFI_EVENT_STA_CONNECTED: //? EPS32已经成功连接到路由器
ESP_LOGI(TAG, "esp32 connected to api!");
break;
case WIFI_EVENT_STA_DISCONNECTED: //? 断连
esp_wifi_connect();
default:
break;
}
}
else if(event_base == IP_EVENT)
{
switch (event_id)
{
case IP_EVENT_STA_GOT_IP: //? EPS32获取到无线路由器分配的IP,此时ESP32才真正的连接到路由器(如果路由器能连接到互联网,ESP32也能连接到互联网)
ESP_LOGI(TAG, "esp32 get ip address");
break;
default:
break;
}
}
else if(event_base == SC_EVENT)
{
switch (event_id)
{
case SC_EVENT_SCAN_DONE: //? 扫描完成
ESP_LOGI(TAG,"sc scan done!");
break;
case SC_EVENT_GOT_SSID_PSWD: //? 收到了APP发过来的SSID和密码
smartconfig_event_got_ssid_pswd_t *ev = (smartconfig_event_got_ssid_pswd_t *)event_data;
wifi_config_t wifi_config;
memset(&wifi_config, 0, sizeof(wifi_config));
snprintf((char *) wifi_config.sta.ssid, sizeof(wifi_config.sta.ssid), (char *)ev->ssid);
snprintf((char *) wifi_config.sta.password, sizeof(wifi_config.sta.ssid), (char *)ev->password);
wifi_config.sta.bssid_set = ev->bssid_set;
if(wifi_config.sta.bssid_set) //? 是否设置接入点的MAC地址,共48位,6字节;
memcpy(wifi_config.sta.bssid, ev->bssid, 6);
esp_wifi_disconnect(); //? 如果有WiFi连接,先断开连接
esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
esp_wifi_connect();
break;
case SC_EVENT_SEND_ACK_DONE: //? 通知手机APP,Smartconfig结束
esp_smartconfig_stop(); //? 停止Smartconfig
break;
default:
break;
}
}
}
void app_main(void)
{
/* 1.初始化NVS
默认状态下,当我们使用一组SSID和密码连接WiFi成功后,ESP-IDF的底层会帮我们把这组SSID和密码保存到NVS中;
下次系统重启后,当进入STA模式并进行连接时,就会用这组SSID和密码进行连接;
*/
ESP_ERROR_CHECK(nvs_flash_init()); //? 宏ESP_ERROR_CHECK,用来检查NVS初始化是否有问题;
// 2.初始化TCP/IP协议栈(ESP-IDF中使用的时LWIP)
ESP_ERROR_CHECK(esp_netif_init()); //? netif--->net interface,网络接口的缩写;
/* 3.创建事件系统循环
WiFi连接过程中会产生各种的事件,这些事件都是通过回调函数来通知我们的
*/
ESP_ERROR_CHECK(esp_event_loop_create_default());
// 4.创建STA
esp_netif_create_default_wifi_sta(); //? 该函数会返回一个STA网卡对象,这里我们使用不到,可以忽略该返回值;
/* 5.初始化WiFi
定义一个WiFi初始化结构体,将其设置到WiFi初始化函数中;
该步骤会设置WiFi的缓冲区数量,加密功能等,我们按默认功能设置就可以;
*/
wifi_init_config_t wifi_init_cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&wifi_init_cfg));
// 6.注册事件响应
esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, event_handle, NULL); //? 注册WiFi事件回调
esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, event_handle, NULL); //? 注册IP事件回调(连接到路由器,获取到IP地址后就会触发该事件)
esp_event_handler_register(SC_EVENT, ESP_EVENT_ANY_ID, event_handle, NULL);
// 8.设置WiFi模式
esp_wifi_set_mode(WIFI_MODE_STA);
// 9.启动WiFi
esp_wifi_start();
// 10.开启SmartConfig
esp_smartconfig_set_type(SC_TYPE_ESPTOUCH); //? 设置SmartConfig类型
smartconfig_start_config_t sc_cfg = SMARTCONFIG_START_CONFIG_DEFAULT();
esp_smartconfig_start(&sc_cfg);
return;
}