(十五)ESP32-S3之WiFi 一键配网
SoftAP 配网ESP32-S3 会建立一个 WiFi 热点(AP 模式),用户将手机连接到这个热点后,将要连接的WiFi 信息发送给 ESP32-S3,ESP32-S3 得到 SSID 和密码。①:优点:很可靠,成功率基本达到 100%,设备端的代码简单。②:缺点:需要手动切换手机 WiFi 连接的网络,先连接到 ESP32 的 AP 网络,配置完成后再恢复连接正常 WiFi 网络,操作上存在
ESP32-S3的一键配网模式是一种方便快捷的WiFi配置方式。在这种模式下,用户无需手动输入 WiFi 的 SSID 和密码等信息,只需要通过一键操作,即可完成 WiFi 的配置和连接。本章节,作者使用乐鑫官方提供的 SmartConfig 软件一键配置 WiFi 账号与密码。
主流 WIFI 配网方式简介
目前主流的 WIFI 配网方式主要有以下三种:
SoftAP 配网
ESP32-S3 会建立一个 WiFi 热点(AP 模式),用户将手机连接到这个热点后,将要连接的
WiFi 信息发送给 ESP32-S3,ESP32-S3 得到 SSID 和密码。
①:优点:很可靠,成功率基本达到 100%,设备端的代码简单。
②:缺点:需要手动切换手机 WiFi 连接的网络,先连接到 ESP32 的 AP 网络,配置完成后
再恢复连接正常 WiFi 网络,操作上存在复杂性,可能给用户带来困扰。
③:官方支持:没有提供 Demo。
Smartconfig 配网
ESP32-S3 处于混杂模式下,监听网络中的所有报文,手机 APP 将当前连接的 SSID 和密码
编码到 UDP 报文中,通过广播或组播的方式发送报文,ESP32-S3 接收到 UDP 报文后解码,得
到 SSID 和密码,然后使用该组 SSID 和密码去连接网络。
①:优缺点:简洁,用户容易操作,但配网成功率受环境影响较大。
②:官方支持:提供 Demo 和 smart_config 例程。
Airkiss 配网
AirKiss 是微信硬件平台提供的一种 WIFI 设备快速入网配置技术。要使用微信客户端的方
式配置设备入网,需要设备支持 AirKiss 技术。Airkiss 的原理和 Smartconfig 很类似,设备工作
在混杂模式下,微信客户端发送包含 SSID 和密码的广播包,设备收到广播包解码得到 SSID 和
密码。详细的可以参考微信官方的介绍。
①:优缺点:简洁,用户容易操作,但配网成功率受环境影响较大。
②:官方支持:提供 Demo 和 smart_config 例程。
本实验以 Smartconfig 软件对 ESP32-S3 设备进行一键配网,该软件的安装包可在乐鑫官方
网站的相关下载网页找到https://www.espressif.com.cn/zh-hans/support/download/apps,如下图所示。
下载成功后,需把安装包转移到安卓手机或者苹果手机上安装。
打开“EspTouch”软件,在此软件下点击 “EspTouch”选项,注意:手机必须连接 WiFi,才能一键配网,如下图所示。
esp_smartconfig.c
#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"
//一个事件组,用于表示
static EventGroupHandle_t s_wifi_event_group;
//连接成功事件
static const int CONNECTED_BIT = BIT0;
//smartconfig完成事件
static const int ESPTOUCH_DONE_BIT = BIT1;
static const char *TAG = "smartconfig_example";
#define NVS_WIFI_NAMESPACE_NAME "DEV_WIFI"
#define NVS_SSID_KEY "ssid"
#define NVS_PASSWORD_KEY "password"
//缓存一份ssid
static char s_ssid_value[33] = {0};
//缓存一份password
static char s_password_value[65] = {0};
//用一个标志来表示是否处于smartconfig中
static bool s_is_smartconfig = false;
/** 从NVS中读取SSID
* @param ssid 读到的ssid
* @param maxlen 外部存储ssid数组的最大值
* @return 读取到的字节数
*/
static size_t read_nvs_ssid(char* ssid,int maxlen)
{
nvs_handle_t nvs_handle;
esp_err_t ret_val = ESP_FAIL;
size_t required_size = 0;
ESP_ERROR_CHECK(nvs_open(NVS_WIFI_NAMESPACE_NAME, NVS_READWRITE, &nvs_handle));
ret_val = nvs_get_str(nvs_handle, NVS_SSID_KEY, NULL, &required_size);
if(ret_val == ESP_OK && required_size <= maxlen)
{
nvs_get_str(nvs_handle,NVS_SSID_KEY,ssid,&required_size);
}
else
required_size = 0;
nvs_close(nvs_handle);
return required_size;
}
/** 写入SSID到NVS中
* @param ssid 需写入的ssid
* @return ESP_OK or ESP_FAIL
*/
static esp_err_t write_nvs_ssid(char* ssid)
{
nvs_handle_t nvs_handle;
esp_err_t ret;
ESP_ERROR_CHECK(nvs_open(NVS_WIFI_NAMESPACE_NAME, NVS_READWRITE, &nvs_handle));
ret = nvs_set_str(nvs_handle, NVS_SSID_KEY, ssid);
nvs_commit(nvs_handle);
nvs_close(nvs_handle);
return ret;
}
/** 从NVS中读取PASSWORD
* @param ssid 读到的password
* @param maxlen 外部存储password数组的最大值
* @return 读取到的字节数
*/
static size_t read_nvs_password(char* pwd,int maxlen)
{
nvs_handle_t nvs_handle;
esp_err_t ret_val = ESP_FAIL;
size_t required_size = 0;
ESP_ERROR_CHECK(nvs_open(NVS_WIFI_NAMESPACE_NAME, NVS_READWRITE, &nvs_handle));
ret_val = nvs_get_str(nvs_handle, NVS_PASSWORD_KEY, NULL, &required_size);
if(ret_val == ESP_OK && required_size <= maxlen)
{
nvs_get_str(nvs_handle,NVS_SSID_KEY,pwd,&required_size);
}
else
required_size = 0;
nvs_close(nvs_handle);
return required_size;
}
/** 写入PASSWORD到NVS中
* @param pwd 需写入的password
* @return ESP_OK or ESP_FAIL
*/
static esp_err_t write_nvs_password(char* pwd)
{
nvs_handle_t nvs_handle;
esp_err_t ret;
ESP_ERROR_CHECK(nvs_open(NVS_WIFI_NAMESPACE_NAME, NVS_READWRITE, &nvs_handle));
ret = nvs_set_str(nvs_handle, NVS_PASSWORD_KEY, pwd);
nvs_commit(nvs_handle);
nvs_close(nvs_handle);
return ret;
}
static void smartconfig_example_task(void * parm);
/** 各种网络事件的回调函数
* @param arg 自定义参数
* @param event_base 事件类型
* @param event_id 事件标识ID,不同的事件类型都有不同的实际标识ID
* @param event_data 事件携带的数据
*/
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) {
if(s_ssid_value[0] != 0)
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
//WIFI断开连接后,再次发起连接
if(!s_is_smartconfig)
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 };
//从event_data中提取SSID和密码
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));
wifi_config.sta.bssid_set = evt->bssid_set;
if (wifi_config.sta.bssid_set == true) {
memcpy(wifi_config.sta.bssid, evt->bssid, sizeof(wifi_config.sta.bssid));
}
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);
snprintf(s_ssid_value,33,"%s",(char*)ssid);
snprintf(s_password_value,65,"%s",(char*)password);
//重新连接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 已发起回应
xEventGroupSetBits(s_wifi_event_group, ESPTOUCH_DONE_BIT);
}
}
void initialise_wifi(void)
{
ESP_ERROR_CHECK(esp_netif_init());
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
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) );
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
//从NVS中读出SSID
read_nvs_ssid(s_ssid_value,32);
//从NVS中读取PASSWORD
read_nvs_password(s_password_value,64);
if(s_ssid_value[0] != 0) //通过SSID第一个字节是否是0,判断是否读取成功,然后设置wifi_config_t
{
wifi_config_t wifi_config =
{
.sta =
{
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
.pmf_cfg =
{
.capable = true,
.required = false
},
},
};
snprintf((char*)wifi_config.sta.ssid,32,"%s",s_ssid_value);
snprintf((char*)wifi_config.sta.password,64,"%s",s_password_value);
}
ESP_ERROR_CHECK( esp_wifi_start() );
}
/** 启动smartconfig
* @param 无
* @return 无
*/
void smartconfig_start(void)
{
if(!s_is_smartconfig)
{
s_is_smartconfig = true;
esp_wifi_disconnect();
xTaskCreate(smartconfig_example_task, "smartconfig_example_task", 4096, NULL, 3, NULL);
}
}
/** smartconfig处理任务
* @param 无
* @return 无
*/
static void smartconfig_example_task(void * parm)
{
EventBits_t uxBits;
ESP_ERROR_CHECK( esp_smartconfig_set_type(SC_TYPE_ESPTOUCH_V2) ); //设定SmartConfig版本
smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT();
ESP_ERROR_CHECK( esp_smartconfig_start(&cfg) ); //启动SmartConfig
while (1) {
uxBits = xEventGroupWaitBits(s_wifi_event_group, CONNECTED_BIT | ESPTOUCH_DONE_BIT, true, false, portMAX_DELAY);
if(uxBits & CONNECTED_BIT) {
ESP_LOGI(TAG, "WiFi Connected to ap");
}
if(uxBits & ESPTOUCH_DONE_BIT) { //收到smartconfig配网完成通知
ESP_LOGI(TAG, "smartconfig over");
esp_smartconfig_stop(); //停止smartconfig配网
write_nvs_ssid(s_ssid_value); //将ssid写入NVS
write_nvs_password(s_password_value); //将password写入NVS
s_is_smartconfig = false;
vTaskDelete(NULL); //退出任务
}
}
}
esp_smartconfig.h
#ifndef _WIFI_SMARTCONFIG_H_
#define _WIFI_SMARTCONFIG_H_
#include "esp_err.h"
/** 启动smartconfig
* @param 无
* @return 无
*/
void smartconfig_start(void);
/** 初始化
* @param 无
* @return 无
*/
void initialise_wifi(void);
#endif
AP配网
apcfg.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>设备配网</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 500px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
h1 {
color: #333;
text-align: center;
font-size: 24px;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input[type="text"],
input[type="password"],
select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
button {
padding: 12px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background-color: #45a049;
}
.btn-scan {
background-color: #2196F3;
margin-bottom: 15px;
}
.btn-scan:hover {
background-color: #0b7dda;
}
.status {
margin-top: 20px;
padding: 10px;
border-radius: 4px;
display: none;
}
.success {
background-color: #dff0d8;
color: #3c763d;
display: block;
}
.error {
background-color: #f2dede;
color: #a94442;
display: block;
}
.loading {
text-align: center;
display: none;
margin-top: 20px;
}
.spinner {
border: 4px solid rgba(0, 0, 0, 0.1);
border-radius: 50%;
border-top: 4px solid #3498db;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
margin: 0 auto 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.wifi-list {
margin-bottom: 20px;
max-height: 200px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
}
.wifi-item {
padding: 8px;
border-bottom: 1px solid #eee;
cursor: pointer;
}
.wifi-item:hover {
background-color: #f5f5f5;
}
.wifi-item:last-child {
border-bottom: none;
}
.wifi-signal {
display: inline-block;
width: 60px;
}
.signal-strong {
color: #4CAF50;
}
.signal-medium {
color: #FFC107;
}
.signal-weak {
color: #F44336;
}
.hidden {
display: none;
}
</style>
</head>
<body>
<div class="container">
<h1>设备网络配置</h1>
<button id="scan-btn" class="btn-scan">扫描附近Wi-Fi网络</button>
<div id="wifi-list-container" class="wifi-list hidden">
<h3>可用Wi-Fi网络</h3>
<div id="wifi-list"></div>
</div>
<div class="form-group">
<label for="ssid">Wi-Fi网络名称 (SSID)</label>
<input type="text" id="ssid" placeholder="请输入或选择Wi-Fi名称">
</div>
<div class="form-group">
<label for="password">Wi-Fi密码</label>
<input type="password" id="password" placeholder="请输入Wi-Fi密码">
</div>
<button id="submit-btn" class="btn-submit">配置网络</button>
<div id="status" class="status"></div>
<div id="loading" class="loading">
<div class="spinner"></div>
<p id="loading-text">正在配置网络,请稍候...</p>
</div>
</div>
<script>
var gateway = `ws://${window.location.hostname}/ws`;
var websocket;
let scanTimeId;
console.log('script run');
window.addEventListener('load', onLoad);// <--页面加载完毕,执行onLoad-->
function onLoad(event) {
initWebSocket();
}
function initWebSocket() {
console.log('Trying to open a WebSocket connection...');
websocket = new WebSocket(gateway);
websocket.onopen = onOpen;
websocket.onclose = onClose;
websocket.onmessage = onMessage; // <-- add this line
}
function onOpen(event) {
console.log('Connection opened');
}
function onClose(event) {
console.log('Connection closed');
setTimeout(initWebSocket, 3000);
}
function onMessage(event) {
var state;
const data = JSON.parse(event.data);
console.log(event.data);
const wifiListContainer = document.getElementById('wifi-list-container');
const wifiList = document.getElementById('wifi-list');
const btn = document.getElementById('scan-btn');
clearTimeout(scanTimeId);
document.getElementById('scan-btn').textContent = '重新扫描Wi-Fi网络';
wifiListContainer.classList.remove('hidden');
for (let i = 0; i < data.wifi_list.length; i++) {
console.log(`ssid: ${data.wifi_list[i].ssid}, rssi: ${data.wifi_list[i].rssi},encrypted: ${data.wifi_list[i].encrypted}`);
const item = document.createElement('div');
item.className = 'wifi-item';
item.innerHTML = `
<div>
<strong>${data.wifi_list[i].ssid}</strong>
<span class="wifi-signal ${getSignalStrengthClass(data.wifi_list[i].rssi)}">
${getSignalStrengthIcon(data.wifi_list[i].rssi)} ${data.wifi_list[i].rssi}dBm
</span>
${data.wifi_list[i].encrypted ? '🔒' : '🌐'}
</div>
`;
item.addEventListener('click', () => {
document.getElementById('ssid').value = data.wifi_list[i].ssid;
document.getElementById('password').focus();
});
wifiList.appendChild(item);
}
document.getElementById('loading').style.display = 'none';
btn.disabled = false;
showStatus('扫描完成','success');
}
// 扫描Wi-Fi网络
document.getElementById('scan-btn').addEventListener('click', function() {
const btn = this;
const wifiListContainer = document.getElementById('wifi-list-container');
const wifiList = document.getElementById('wifi-list');
btn.disabled = true;
btn.textContent = '正在扫描...';
document.getElementById('loading-text').textContent = '正在扫描Wi-Fi网络...';
document.getElementById('loading').style.display = 'block';
wifiList.innerHTML = '';
scanNetwork('start');
scanTimeId = setTimeout(() => {
btn.textContent = '重新扫描Wi-Fi网络';
btn.disabled = false;
document.getElementById('loading').style.display = 'none';
showStatus('扫描Wi-Fi网络失败,请稍后重试', 'error');
}, 20*1000);
});
// 提交网络配置
document.getElementById('submit-btn').addEventListener('click', function() {
const ssid = document.getElementById('ssid').value;
const password = document.getElementById('password').value;
if (!ssid) {
showStatus('请输入Wi-Fi网络名称', 'error');
return;
}
document.getElementById('loading-text').textContent = '正在配置网络,请稍候...';
document.getElementById('loading').style.display = 'block';
this.disabled = true;
// 这里应该是实际的配网API调用
configureNetwork(ssid, password);
});
// 模拟配置网络
function configureNetwork(ssid, password) {
const manualJson = '{"ssid":"' + ssid+ '","password":"' + password + '"}';
websocket.send(manualJson);
showStatus('开始配置网络!请留意设备信息...','success');
}
// 开始扫描
function scanNetwork(msg) {
const manualJson = '{"scan":"' + msg+ '"}';
websocket.send(manualJson);
showStatus('开始扫描....','success');
}
// 显示状态信息
function showStatus(message, type) {
const statusElement = document.getElementById('status');
statusElement.textContent = message;
statusElement.className = 'status ' + type;
}
// 获取信号强度图标
function getSignalStrengthIcon(rssi) {
if (rssi >= -50) return '📶'; // 强信号
if (rssi >= -70) return '📶'; // 中等信号
return '📶'; // 弱信号
}
// 获取信号强度CSS类
function getSignalStrengthClass(rssi) {
if (rssi >= -50) return 'signal-strong';
if (rssi >= -70) return 'signal-medium';
return 'signal-weak';
}
</script>
</body>
</html>
ap_wifi.c
#include "ap_wifi.h"
#include "ws_server.h"
#include "cJSON.h"
#include "esp_spiffs.h"
#include "esp_log.h"
#include <sys/stat.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#define TAG "apcfg"
//html网页在spiffs文件系统中的路径
#define INDEX_HTML_PATH "/spiffs/apcfg.html"
//html网页缓存
static char* index_html = NULL;
//配网事件
static EventGroupHandle_t apcfg_event = NULL;
//接收到ap配网的ssid和密码
static char current_ssid[32];
static char current_password[64];
#define APCFG_BIT (BIT0)
/** 从spiffs中加载html页面到内存
* @param 无
* @return 无
*/
static char* initi_web_page_buffer(void)
{
//定义挂载点
esp_vfs_spiffs_conf_t conf = {
.base_path = "/spiffs", //挂载点
.partition_label = "html", //分区名称
.max_files = 5, //最大打开的文件数
.format_if_mount_failed = false //挂载失败是否执行格式化
};
//挂载spiffs
ESP_ERROR_CHECK(esp_vfs_spiffs_register(&conf));
//查找文件是否存在
struct stat st;
if (stat(INDEX_HTML_PATH, &st))
{
ESP_LOGE(TAG, "apcfg.html not found");
return NULL;
}
//打开html文件并且读取到内存中
char* page = (char*)malloc(st.st_size + 1);
if(!page)
{
return NULL;
}
memset(page,0,st.st_size + 1);
FILE *fp = fopen(INDEX_HTML_PATH, "r");
if (fread(page, st.st_size, 1, fp) == 0)
{
free(page);
page = NULL;
ESP_LOGE(TAG, "fread failed");
}
fclose(fp);
return page;
}
/** wifi扫描完成
* @param numbers 扫描到的ap个数
* @param ap_records ap信息
* @return 无
*/
static void wifi_scan_finish_handle(int numbers,wifi_ap_record_t *ap_records)
{
cJSON* root = cJSON_CreateObject();
cJSON* wifilist_js = cJSON_AddArrayToObject(root,"wifi_list");
for(int i = 0;i < numbers;i++)
{
cJSON* wifi_js = cJSON_CreateObject();
cJSON_AddStringToObject(wifi_js,"ssid",(char*)ap_records[i].ssid);
cJSON_AddNumberToObject(wifi_js,"rssi",ap_records[i].rssi);
if(ap_records[i].authmode == WIFI_AUTH_OPEN)
cJSON_AddBoolToObject(wifi_js,"encrypted",0);
else
cJSON_AddBoolToObject(wifi_js,"encrypted",1);
cJSON_AddItemToArray(wifilist_js,wifi_js);
}
char* data = cJSON_Print(root);
ESP_LOGI(TAG,"WS send:%s",data);
web_ws_send((uint8_t*)data,strlen(data));
cJSON_free(data);
cJSON_Delete(root);
}
/** ws接收回调函数
* @param payload 数据
* @param len 数据长度
* @return 无
*/
static void ws_receive_handle(uint8_t* payload,int len)
{
cJSON* root = cJSON_Parse((char*)payload);
if(root)
{
cJSON* scan_js = cJSON_GetObjectItem(root,"scan");
cJSON* ssid_js = cJSON_GetObjectItem(root,"ssid");
cJSON* password_js = cJSON_GetObjectItem(root,"password");
if(scan_js)
{
char* scan_value = cJSON_GetStringValue(scan_js);
if(strcmp(scan_value,"start") == 0)
{
wifi_manager_scan(wifi_scan_finish_handle);
}
}
if(ssid_js && password_js)
{
char* ssid = cJSON_GetStringValue(ssid_js);
char* password = cJSON_GetStringValue(password_js);
snprintf(current_ssid,sizeof(current_ssid),"%s",ssid);
snprintf(current_password,sizeof(current_password),"%s",password);
ESP_LOGI(TAG,"Receive ssid:%s,password:%s,now stop http server",current_ssid,current_password);
//此回调函数里面由websocket底层调用,不宜直接调用关闭服务器操作
xEventGroupSetBits(apcfg_event,APCFG_BIT);
}
}
else
{
ESP_LOGE(TAG,"Receive invaild json");
}
}
static void ap_wifi_task(void* param)
{
EventBits_t ev;
while(1)
{
ev = xEventGroupWaitBits(apcfg_event,APCFG_BIT,pdTRUE,pdFALSE,pdMS_TO_TICKS(10*1000));
if(ev &APCFG_BIT)
{
web_ws_stop();
wifi_manager_connect(current_ssid,current_password);
}
}
}
/** wifi功能和ap配网功能初始化
* @param f wifi连接状态回调函数
* @return 无
*/
void ap_wifi_init(p_wifi_state_callback f)
{
index_html = initi_web_page_buffer();
wifi_manager_init(f);
apcfg_event = xEventGroupCreate();
xTaskCreatePinnedToCore(ap_wifi_task,"apcfg",4096,NULL,2,NULL,1);
}
/** 连接某个热点
* @param ssid
* @param password
* @return 无
*/
void ap_wifi_set(const char* ssid,const char* password)
{
wifi_manager_connect(ssid,password);
}
/** 启动配网模式
* @param enable 暂无用,强制true
* @return 无
*/
void ap_wifi_apcfg(bool enable)
{
if(enable)
{
wifi_manager_ap();
ws_cfg_t ws =
{
.html_code = index_html,
.receive_fn = ws_receive_handle,
};
web_ws_start(&ws);
}
}
ap_wifi.h
#ifndef _APCFG_H_
#define _APCFG_H_
#include "wifi_manager.h"
/** wifi功能和ap配网功能初始化
* @param f wifi连接状态回调函数
* @return 无
*/
void ap_wifi_init(p_wifi_state_callback f);
/** 连接某个热点
* @param ssid
* @param password
* @return 无
*/
void ap_wifi_set(const char* ssid,const char* password);
/** 启动配网模式
* @param enable 暂无用,强制true
* @return 无
*/
void ap_wifi_apcfg(bool enable);
#endif
wifi_manager.c
#include "wifi_manager.h"
#include <stdio.h>
#include "esp_log.h"
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_netif.h"
#include "esp_mac.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "lwip/ip4_addr.h"
#define TAG "wifi_manager"
//重连次数
#define MAX_CONNECT_RETRY 6
static int sta_connect_count = 0;
static esp_netif_t *esp_netif_sta = NULL;
static esp_netif_t *esp_netif_ap = NULL;
//AP模式下的SSID名称
static const char* ap_ssid_name = "ESP32-AP";
//AP模式下的密码
static const char* ap_password = "12345678";
//回调函数
static p_wifi_state_callback wifi_state_cb = NULL;
//当前sta连接状态
static bool is_sta_connected = false;
/** 事件回调函数
* @param arg 用户传递的参数
* @param event_base 事件类别
* @param event_id 事件ID
* @param event_data 事件携带的数据
* @return 无
*/
static void event_handler(void* 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: //WIFI以STA模式启动后触发此事件
{
wifi_mode_t mode;
esp_wifi_get_mode(&mode);
if(mode == WIFI_MODE_STA)
esp_wifi_connect(); //启动WIFI连接
break;
}
case WIFI_EVENT_STA_CONNECTED: //WIFI连上路由器后,触发此事件
ESP_LOGI(TAG, "Connected to AP");
break;
case WIFI_EVENT_STA_DISCONNECTED: //WIFI从路由器断开连接后触发此事件
if(is_sta_connected)
{
if(wifi_state_cb)
wifi_state_cb(WIFI_STATE_DISCONNECTED);
is_sta_connected = false;
}
if(sta_connect_count < MAX_CONNECT_RETRY)
{
wifi_mode_t mode;
esp_wifi_get_mode(&mode);
if(mode == WIFI_MODE_STA)
esp_wifi_connect(); //继续重连
sta_connect_count++;
}
ESP_LOGI(TAG,"connect to the AP fail,retry now");
break;
case WIFI_EVENT_AP_STACONNECTED:
{
//有设备连接了热点,把它的MAC打印出来
wifi_event_ap_staconnected_t *event = (wifi_event_ap_staconnected_t *) event_data;
ESP_LOGI(TAG, "Station "MACSTR" joined, AID=%d",
MAC2STR(event->mac), event->aid);
break;
}
case WIFI_EVENT_AP_STADISCONNECTED:
{
//有设备断开了热点
wifi_event_ap_stadisconnected_t *event = (wifi_event_ap_stadisconnected_t *) event_data;
ESP_LOGI(TAG, "Station "MACSTR" left, AID=%d",
MAC2STR(event->mac), event->aid);
break;
}
default:
break;
}
}
if(event_base == IP_EVENT) //IP相关事件
{
switch(event_id)
{
case IP_EVENT_STA_GOT_IP: //只有获取到路由器分配的IP,才认为是连上了路由器
ESP_LOGI(TAG,"Get ip address");
is_sta_connected = true;
if(wifi_state_cb)
wifi_state_cb(WIFI_STATE_CONNECTED);
break;
default:break;
}
}
}
/** 初始化wifi,默认进入STA模式
* @param 无
* @return 无
*/
void wifi_manager_init(p_wifi_state_callback f)
{
ESP_ERROR_CHECK(esp_netif_init()); //用于初始化tcpip协议栈
ESP_ERROR_CHECK(esp_event_loop_create_default()); //创建一个默认系统事件调度循环,之后可以注册回调函数来处理系统的一些事件
esp_netif_sta = esp_netif_create_default_wifi_sta(); //使用默认配置创建STA对象
esp_netif_ap = esp_netif_create_default_wifi_ap(); //使用默认配置创建AP对象
//初始化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));
wifi_state_cb = f;
//启动WIFI
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); //设置工作模式为STA
ESP_ERROR_CHECK(esp_wifi_start() ); //启动WIFI
ESP_LOGI(TAG, "wifi_init finished.");
}
/** 进入ap+sta模式
* @param 无
* @return 成功/失败
*/
esp_err_t wifi_manager_ap(void)
{
wifi_mode_t mode;
esp_wifi_get_mode(&mode);
if(mode == WIFI_MODE_APSTA) //需要使用AP+STA模式,才可以执行扫描同时保持客户端连接
return ESP_OK;
esp_wifi_disconnect();
esp_wifi_stop();
esp_wifi_set_mode(WIFI_MODE_APSTA);
wifi_config_t wifi_config =
{
.ap =
{
.channel = 5, //wifi的通信信道
.max_connection = 2, //最大连接数
.authmode = WIFI_AUTH_WPA2_PSK, //加密方式
}
};
//填充ap的ssid名称
snprintf((char*)wifi_config.ap.ssid,31,"%s",ap_ssid_name);
wifi_config.ap.ssid_len = strlen(ap_ssid_name);
//填充密码
snprintf((char*)wifi_config.ap.password,63,"%s",ap_password);
//设置wifi
esp_wifi_set_config(WIFI_IF_AP,&wifi_config);
//如果是AP模式,则需要设置如下网络层信息
esp_netif_ip_info_t ipInfo;
IP4_ADDR(&ipInfo.ip, 192,168,100,1); //本地的IP地址
IP4_ADDR(&ipInfo.gw, 192,168,100,1); //网关IP地址
IP4_ADDR(&ipInfo.netmask, 255,255,255,0); //子网掩码
esp_netif_dhcps_stop(esp_netif_ap);
esp_netif_set_ip_info(esp_netif_ap, &ipInfo);
esp_netif_dhcps_start(esp_netif_ap);
esp_wifi_start();
return ESP_OK;
}
static SemaphoreHandle_t scan_sem = NULL;
/** 扫描任务
* @param 无
* @return 成功/失败
*/
static void scan_task(void* param)
{
p_wifi_scan_callback callback = (p_wifi_scan_callback)param;
uint16_t number = 20;
wifi_ap_record_t *ap_info = malloc(sizeof(wifi_ap_record_t)*number);
uint16_t ap_count = 0;
ESP_LOGI(TAG,"Start wifi scan");
esp_wifi_scan_start(NULL, true);
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_num(&ap_count));
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&number, ap_info));
ESP_LOGI(TAG, "Total APs scanned = %u, actual AP number ap_info holds = %u", ap_count, number);
if(callback)
callback(number,ap_info);
xSemaphoreGive(scan_sem);
vTaskDelete(NULL);
}
/** 启动扫描
* @param 无
* @return 成功/失败
*/
esp_err_t wifi_manager_scan(p_wifi_scan_callback f)
{
if(!scan_sem)
{
scan_sem = xSemaphoreCreateBinary();
xSemaphoreGive(scan_sem);
}
if(pdTRUE == xSemaphoreTake(scan_sem,0))
{
//清除上次的扫描信息
esp_wifi_clear_ap_list();
//启动一个扫描任务
if(pdTRUE == xTaskCreatePinnedToCore(scan_task,"scan",8192,f,3,NULL,0))
return ESP_OK;
}
return ESP_FAIL;
}
/** 连接wifi
* @param ssid
* @param password
* @return 成功/失败
*/
esp_err_t wifi_manager_connect(const char* ssid,const char* password)
{
sta_connect_count = 0;
wifi_config_t wifi_config =
{
.sta =
{
.threshold.authmode = WIFI_AUTH_WPA2_PSK, //加密方式
},
};
snprintf((char*)wifi_config.sta.ssid,31,"%s",ssid);
snprintf((char*)wifi_config.sta.password,63,"%s",password);
ESP_ERROR_CHECK(esp_wifi_disconnect());
wifi_mode_t mode;
esp_wifi_get_mode(&mode);
if(mode != WIFI_MODE_STA)
{
ESP_ERROR_CHECK(esp_wifi_stop());
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
esp_wifi_start();
}
else
{
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
esp_wifi_connect();
}
return ESP_OK;
}
wifi_manager.h
#ifndef _WIFI_MANAGER_H_
#define _WIFI_MANAGER_H_
#include "esp_err.h"
#include "esp_wifi.h"
typedef enum
{
WIFI_STATE_CONNECTED,
WIFI_STATE_DISCONNECTED,
}WIFI_STATE;
//扫描完成回调函数
typedef void(*p_wifi_scan_callback)(int numbers,wifi_ap_record_t *ap_records);
//wifi状态变化回调函数
typedef void(*p_wifi_state_callback)(WIFI_STATE state);
/** 初始化wifi,默认进入STA模式
* @param f wifi状态变化回调函数
* @return 无
*/
void wifi_manager_init(p_wifi_state_callback f);
/** 进入ap+sta模式
* @param 无
* @return 成功/失败
*/
esp_err_t wifi_manager_ap(void);
/** 启动扫描
* @param 无
* @return 成功/失败
*/
esp_err_t wifi_manager_scan(p_wifi_scan_callback f);
/** 连接wifi
* @param ssid
* @param password
* @return 成功/失败
*/
esp_err_t wifi_manager_connect(const char* ssid,const char* password);
#endif
ws_server.c
#include "esp_http_server.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "ws_server.h"
static const char *TAG = "WebSocket Server";
//html页面
static const char* http_html = NULL;
//接收回调函数
static ws_receive_cb ws_receive_fn = NULL;
//http服务器句柄
httpd_handle_t http_ws_server = NULL;
//连接的客户端fds
static int client_sockfd = -1;
/** 当其他设备WS访问时触发此回调函数
* @param req http请求
* @return ESP_OK or ESP_FAIL
*/
static esp_err_t handle_ws_req(httpd_req_t *req)
{
if (req->method == HTTP_GET)
{
ESP_LOGI(TAG, "Handshake done, the new connection was opened");
//把套接字描述符保存下来,方便后续发送数据用
client_sockfd = httpd_req_to_sockfd(req);
ESP_LOGI(TAG,"Save client_fds:%d",client_sockfd);
return ESP_OK;
}
httpd_ws_frame_t ws_pkt;
uint8_t *buf = NULL;
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0);
if (ret != ESP_OK)
{
return ret;
}
if (ws_pkt.len)
{
buf = calloc(1, ws_pkt.len + 1);
if (buf == NULL)
{
ESP_LOGE(TAG, "Failed to calloc memory for buf");
return ESP_ERR_NO_MEM;
}
ws_pkt.payload = buf;
ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "httpd_ws_recv_frame failed with %d", ret);
free(buf);
return ret;
}
ESP_LOGI(TAG, "Got packet with message: %s", ws_pkt.payload);
}
ESP_LOGI(TAG, "frame len is %d", ws_pkt.len);
if (ws_pkt.type == HTTPD_WS_TYPE_TEXT)
{
if(ws_receive_fn)
ws_receive_fn(ws_pkt.payload,ws_pkt.len);
free(buf);
}
return ESP_OK;
}
/** 当其他设备http HTTP_GET 访问时,返回html页面
* @param req http请求
* @return ESP_OK or ESP_FAIL
*/
esp_err_t get_req_handler(httpd_req_t *req)
{
esp_err_t response = ESP_FAIL;
if(http_html)
{
response = httpd_resp_send(req, http_html, HTTPD_RESP_USE_STRLEN);
}
return response;
}
esp_err_t web_ws_send(uint8_t* data, int len)
{
httpd_ws_frame_t pkt;
memset(&pkt, 0, sizeof(httpd_ws_frame_t));
pkt.payload = data;
pkt.len = len;
pkt.type = HTTPD_WS_TYPE_TEXT;
return httpd_ws_send_data(http_ws_server,client_sockfd,&pkt);
}
/** 初始化ws
* @param cfg ws一些配置,请看ws_cfg_t定义
* @return ESP_OK or ESP_FAIL
*/
esp_err_t web_ws_start(ws_cfg_t *cfg)
{
if(cfg == NULL)
return ESP_FAIL;
http_html = cfg->html_code;
ws_receive_fn = cfg->receive_fn;
//http和websocket初始化
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_uri_t uri_get =
{
.uri = "/",
.method = HTTP_GET,
.handler = get_req_handler,
};
httpd_uri_t ws =
{
.uri = "/ws",
.method = HTTP_GET,
.handler = handle_ws_req,
.is_websocket = true
};
if (httpd_start(&http_ws_server, &config) == ESP_OK)
{
httpd_register_uri_handler(http_ws_server, &uri_get);
httpd_register_uri_handler(http_ws_server, &ws);
}
return ESP_OK;
}
esp_err_t web_ws_stop(void)
{
if(http_ws_server)
{
return httpd_stop(http_ws_server);
http_ws_server = NULL;
}
return ESP_OK;
}
ws_server.h
#ifndef _WS_SERVER_H_
#define _WS_SERVER_H_
#include "esp_err.h"
//ws接收到的处理回调函数
typedef void(*ws_receive_cb)(uint8_t* payload,int len);
typedef struct
{
const char* html_code; //当执行http访问时返回的html页面
ws_receive_cb receive_fn; //当ws接收到数据时,调用此函数
}ws_cfg_t;
/** 启动ws
* @param cfg ws一些配置,请看ws_cfg_t定义
* @return ESP_OK or ESP_FAIL
*/
esp_err_t web_ws_start(ws_cfg_t *cfg);
esp_err_t web_ws_stop(void);
esp_err_t web_ws_send(uint8_t* data, int len);
#endif

更多推荐



所有评论(0)