一、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_ORDERvoid 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(); //? 连接WiFibreak;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.创建STAesp_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.启动WiFiesp_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.创建STAesp_netif_create_default_wifi_sta(); //? 该函数会返回一个STA网卡对象,这里我们使用不到,可以忽略该返回值;// 8.设置WiFi模式为STAesp_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.启动WiFiesp_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(); //? 连接WiFibreak;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(); //? 停止Smartconfigbreak;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.创建STAesp_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.启动WiFiesp_wifi_start();// 10.开启SmartConfigesp_smartconfig_set_type(SC_TYPE_ESPTOUCH); //? 设置SmartConfig类型smartconfig_start_config_t sc_cfg = SMARTCONFIG_START_CONFIG_DEFAULT();esp_smartconfig_start(&sc_cfg);return;
}