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

在这里插入图片描述

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐