diff --git a/README.md b/README.md
index b6a4073..741b459 100644
--- a/README.md
+++ b/README.md
@@ -180,7 +180,7 @@
> 在线文字教程:[FreeRTOS多任务与任务管理.md](./docs/03.FreeRTOS基础/3.2-FreeRTOS多任务与任务管理/FreeRTOS多任务与任务管理.md)
> 在线视频教程:[FreeRTOS任务管理详解]
-> 教程配套代码:[FreeRTOS多任务示例代码](#)
+> 教程配套代码:[FreeRTOS多任务示例代码](#)
## 3.3 FreeRTOS任务看门狗
@@ -380,7 +380,7 @@
本节介绍ESP32的Wi-Fi模式,包括STA模式和AP模式,以及如何进行基本的Wi-Fi操作,如连接网络和设置路由器等。
-> 在线文字教程:[Wi-Fi模式与基础操作](./docs/06. Wi-Fi功能与相关协议/6.1- Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/ Wi-Fi模式与基础操作.md)
+> 在线文字教程:[Wi-Fi模式与基础操作](./docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/Wi-Fi模式与基础操作.md)
> 在线视频教程:[Wi-Fi模式开发详解]
> 教程配套代码:[Wi-Fi模式示例代码](#)
@@ -398,7 +398,7 @@
本节讲解TCP协议的原理及在ESP32中的实现,包含连接、数据传输及断开连接的操作步骤。
-> 在线文字教程:[TCP协议应用](./docs/06. Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/TCP协议应用.md)
+> 在线文字教程:[TCP协议应用](./docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/TCP协议应用.md)
> 在线视频教程:[TCP协议开发详解]
> 教程配套代码:[TCP协议示例代码](#)
@@ -406,7 +406,7 @@
本节介绍UDP协议的工作原理及在ESP32上的应用,包含数据发送与接收的实现方法。
-> 在线文字教程:[UDP协议应用](./docs/06. Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.2-UDP协议/UDP协议应用.md)
+> 在线文字教程:[UDP协议应用](./docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.2-UDP协议/UDP协议应用.md)
> 在线视频教程:[UDP协议开发详解]
> 教程配套代码:[UDP协议示例代码](#)
@@ -414,7 +414,8 @@
本节介绍ESP32如何实现HTTP协议,包括创建HTTP客户端和服务器的步骤,以及常见的应用场景。
-> 在线文字教程:[HTTP协议应用](./docs/06. Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3- HTTP协议/HTTP协议应用.md)
+> 在线文字教程-1:[HTTP协议(服务端).md](./docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/HTTP协议(服务端).md)
+> 在线文字教程-2:[HTTP协议(客户端).md](./docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(客户端)/HTTP协议(客户端).md)
> 在线视频教程:[HTTP协议开发详解]
> 教程配套代码:[HTTP协议示例代码](#)
@@ -422,7 +423,7 @@
本节讲解WebSocket协议在ESP32中的应用,如何通过WebSocket实现实时双向通信。
-> 在线文字教程:[WebSocket协议应用](./docs/06. Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/WebSocket协议应用.md)
+> 在线文字教程:[WebSocket协议应用](./docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/WebSocket协议.md)
> 在线视频教程:[WebSocket协议开发详解]
> 教程配套代码:[WebSocket协议示例代码](#)
@@ -430,7 +431,7 @@
本节介绍ESP32如何实现MQTT协议,包括客户端连接、订阅和发布消息的基本操作。
-> 在线文字教程:[MQTT协议应用](./docs/06. Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.5-MQTT协议/MQTT协议应用.md)
+> 在线文字教程:[MQTT协议应用](./docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.5-MQTT协议/MQTT协议应用.md)
> 在线视频教程:[MQTT协议开发详解]
> 教程配套代码:[MQTT协议示例代码](#)
@@ -438,7 +439,7 @@
本节介绍ESP32的ESP-NOW协议,讲解其在无需Wi-Fi路由器的情况下实现设备间直接通信的应用场景。
-> 在线文字教程:[ESP-NOW协议应用](./docs/06. Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.6-ESP-NOW协议/ESP-NOW协议应用.md)
+> 在线文字教程:[ESP-NOW协议应用](./docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.6-ESP-NOW协议/ESP-NOW协议应用.md)
> 在线视频教程:[ESP-NOW协议开发详解]
> 教程配套代码:[ESP-NOW协议示例代码](#)
diff --git a/docs/04.外设学习/4.1-基础外设/4.1.1-GPIO/GPIO入门.md b/docs/04.外设学习/4.1-基础外设/4.1.1-GPIO/GPIO入门.md
index f53097b..b8e95df 100644
--- a/docs/04.外设学习/4.1-基础外设/4.1.1-GPIO/GPIO入门.md
+++ b/docs/04.外设学习/4.1-基础外设/4.1.1-GPIO/GPIO入门.md
@@ -1,17 +1,4 @@
-### 说明:
-
-1. 本文档由DuRuofu撰写,由DuRuofu负责解释及执行。
-2. 本文档介绍GPIO的基本使用。
-
-### 修订历史:
-
-| 文档名称 | 版本 | 作者 | 时间 | 备注 |
-| ---- | ---- | ------------ | ------ |
-| ESP32外设-GPIO入门 |v1.0.0| DuRuofu | 2024-02-29 | 首次建立 |
-
-
-
# ESP32外设-GPIO入门
## 一、GPIO介绍
diff --git a/docs/04.外设学习/4.1-基础外设/4.1.2-UART/UART串口通信.md b/docs/04.外设学习/4.1-基础外设/4.1.2-UART/UART串口通信.md
index 8501d2b..5c18548 100644
--- a/docs/04.外设学习/4.1-基础外设/4.1.2-UART/UART串口通信.md
+++ b/docs/04.外设学习/4.1-基础外设/4.1.2-UART/UART串口通信.md
@@ -1,16 +1,4 @@
-### 说明:
-
-1. 本文档由DuRuofu撰写,由DuRuofu负责解释及执行。
-2. 本文档介绍ESP32外设-UART的基本使用
-
-### 修订历史:
-
-| 文档名称 | 版本 | 作者 | 时间 | 备注 |
-| ---- | ---- | ------------ | ------ |
-| ESP32外设-UART入门 |v1.0.0| DuRuofu | 2024-03-03 | 首次建立 |
-
-
# ESP32外设-UART入门
diff --git a/docs/04.外设学习/4.1-基础外设/4.1.3-定时器/定时器.md b/docs/04.外设学习/4.1-基础外设/4.1.3-定时器/定时器.md
index 7833730..0d83ab5 100644
--- a/docs/04.外设学习/4.1-基础外设/4.1.3-定时器/定时器.md
+++ b/docs/04.外设学习/4.1-基础外设/4.1.3-定时器/定时器.md
@@ -1,16 +1,4 @@
-### 说明:
-
-1. 本文档由DuRuofu撰写,由DuRuofu负责解释及执行。
-2. 本文档介绍ESP32硬件定时器的使用。(其他型号也可参考)
-
-### 修订历史:
-
-| 文档名称 | 版本 | 作者 | 时间 | 备注 |
-| ---- | ---- | ------------ | ------ |
-| ESP32外设-硬件定时器入门 |v1.0.0| DuRuofu | 2024-03-04 | 首次建立 |
-
-
# ESP32外设-硬件定时器入门
diff --git a/docs/04.外设学习/4.1-基础外设/4.1.4-ADC/ADC模数转换.md b/docs/04.外设学习/4.1-基础外设/4.1.4-ADC/ADC模数转换.md
index 55aeb0f..0e68195 100644
--- a/docs/04.外设学习/4.1-基础外设/4.1.4-ADC/ADC模数转换.md
+++ b/docs/04.外设学习/4.1-基础外设/4.1.4-ADC/ADC模数转换.md
@@ -1,16 +1,4 @@
-### 说明:
-
-1. 本文档由DuRuofu撰写,由DuRuofu负责解释及执行。
-2. 本文档介绍ADC外设的使用,个人记录使用,如有侵权请联系删除。
-
-### 修订历史:
-
-| 文档名称 | 版本 | 作者 | 时间 | 备注 |
-| ---- | ---- | ------------ | ------ |
-| ESP32外设-ADC入门 |v1.0.0| DuRuofu | 2024-03-03 | 首次建立 |
-
-
# ESP32外设-ADC入门
diff --git a/docs/04.外设学习/4.1-基础外设/4.1.5-DAC/DAC数模转换.md b/docs/04.外设学习/4.1-基础外设/4.1.5-DAC/DAC数模转换.md
index a214fe9..73ee457 100644
--- a/docs/04.外设学习/4.1-基础外设/4.1.5-DAC/DAC数模转换.md
+++ b/docs/04.外设学习/4.1-基础外设/4.1.5-DAC/DAC数模转换.md
@@ -1,14 +1,4 @@
-### 说明:
-
-1. 本文档由DuRuofu撰写,由DuRuofu负责解释及执行。
-2. 本文档介绍ESP32外设,数模转换器的使用。
-
-### 修订历史:
-
-| 文档名称 | 版本 | 作者 | 时间 | 备注 |
-| ------------- | ------ | ------- | ---------- | ---- |
-| ESP32外设-DAC入门 | v1.0.0 | DuRuofu | 2024-03-05 | 首次建立 |
diff --git a/docs/04.外设学习/4.1-基础外设/4.1.6-LED_PWM/LED_PWM控制.md b/docs/04.外设学习/4.1-基础外设/4.1.6-LED_PWM/LED_PWM控制.md
index c1ea28e..679a006 100644
--- a/docs/04.外设学习/4.1-基础外设/4.1.6-LED_PWM/LED_PWM控制.md
+++ b/docs/04.外设学习/4.1-基础外设/4.1.6-LED_PWM/LED_PWM控制.md
@@ -1,16 +1,4 @@
-### 说明:
-
-1. 本文档由DuRuofu撰写,由DuRuofu负责解释及执行。
-
-### 修订历史:
-
-| 文档名称 | 版本 | 作者 | 时间 | 备注 |
-| ---------------- | ------ | ------- | ---------- | ---- |
-| ESP32外设-LEDPWM入门 | v1.0.0 | DuRuofu | 2024-03-05 | 首次建立 |
-
-
-
# ESP32外设-LEDPWM入门
## 一、介绍:
diff --git a/docs/04.外设学习/4.1-基础外设/4.1.7-I2C/I2C通信.md b/docs/04.外设学习/4.1-基础外设/4.1.7-I2C/I2C通信.md
index 2203ab7..bfd1514 100644
--- a/docs/04.外设学习/4.1-基础外设/4.1.7-I2C/I2C通信.md
+++ b/docs/04.外设学习/4.1-基础外设/4.1.7-I2C/I2C通信.md
@@ -1,17 +1,4 @@
-### 说明:
-
-1. 本文档由DuRuofu撰写,由DuRuofu负责解释及执行。
-2. 本文档介绍ESP32 I2C外设(主机模式)的基本使用。
-
-### 修订历史:
-
-| 文档名称 | 版本 | 作者 | 时间 | 备注 |
-| ---- | ---- | ------------ | ------ |
-| ESP32外设-I2C入门 |v1.0.0| DuRuofu | 2024-03-11 | 首次建立 |
-
-
-
# ESP32外设-I2C入门
## 一、介绍
diff --git a/docs/04.外设学习/4.1-基础外设/4.1.9-I2S/I2S入门.md b/docs/04.外设学习/4.1-基础外设/4.1.9-I2S/I2S入门.md
index 6bf71e8..35803b1 100644
--- a/docs/04.外设学习/4.1-基础外设/4.1.9-I2S/I2S入门.md
+++ b/docs/04.外设学习/4.1-基础外设/4.1.9-I2S/I2S入门.md
@@ -1,17 +1,4 @@
-### 说明:
-
-1. 本文档由DuRuofu撰写,由DuRuofu负责解释及执行。
-2. 本文档记录ESP-IDF外设:I2S的使用。
-
-### 修订历史:
-
-| 文档名称 | 版本 | 作者 | 时间 | 备注 |
-| ------------- | ------ | ------- | ---------- | ---- |
-| ESP32外设-I2S入门 | v1.0.0 | DuRuofu | 2024-04-11 | 首次建立 |
-
-
-
# ESP32外设-I2S入门
## 一、概念
diff --git a/docs/04.外设学习/4.2-存储外设/4.2.1-分区表/分区表.md b/docs/04.外设学习/4.2-存储外设/4.2.1-分区表/分区表.md
index 7c7c4cb..9281d6c 100644
--- a/docs/04.外设学习/4.2-存储外设/4.2.1-分区表/分区表.md
+++ b/docs/04.外设学习/4.2-存储外设/4.2.1-分区表/分区表.md
@@ -1,16 +1,4 @@
-### 说明:
-
-1. 本文档由DuRuofu撰写,由DuRuofu负责解释及执行。
-2. 本文档主要介绍ESP32flash分区表的相关知识。
-
-### 修订历史:
-
-| 文档名称 | 版本 | 作者 | 时间 | 备注 |
-| ------------- | ------ | ------- | ---------- | ---- |
-| ESP32存储-分区表入门 | v1.0.0 | DuRuofu | 2024-03-05 | 首次建立 |
-
-
# ESP32存储-分区表入门
diff --git a/docs/04.外设学习/4.2-存储外设/4.2.2-非易失性存储/非易失性存储.md b/docs/04.外设学习/4.2-存储外设/4.2.2-非易失性存储/非易失性存储.md
index e8a2eef..f9e3e16 100644
--- a/docs/04.外设学习/4.2-存储外设/4.2.2-非易失性存储/非易失性存储.md
+++ b/docs/04.外设学习/4.2-存储外设/4.2.2-非易失性存储/非易失性存储.md
@@ -1,14 +1,4 @@
-### 说明:
-
-1. 本文档由DuRuofu撰写,由DuRuofu负责解释及执行。
-2. 本文档将详细介绍ESP32 NVS 常用的一些概念。
-
-### 修订历史:
-
-| 文档名称 | 版本 | 作者 | 时间 | 备注 |
-| -------------------- | ------ | ------- | ---------- | ---- |
-| ESP32存储-非易失性存储 (NVS) | v1.0.0 | DuRuofu | 2024-03-06 | 首次建立 |
diff --git a/docs/04.外设学习/4.2-存储外设/4.2.3-FAT文件系统/FAT文件系统.md b/docs/04.外设学习/4.2-存储外设/4.2.3-FAT文件系统/FAT文件系统.md
index 3e7ca88..8076c13 100644
--- a/docs/04.外设学习/4.2-存储外设/4.2.3-FAT文件系统/FAT文件系统.md
+++ b/docs/04.外设学习/4.2-存储外设/4.2.3-FAT文件系统/FAT文件系统.md
@@ -1,15 +1,4 @@
-### 说明:
-
-1. 本文档由DuRuofu撰写,由DuRuofu负责解释及执行。
-2. 本文档主要介绍ESP32的FAT (虚拟文件系统)
-
-### 修订历史:
-
-| 文档名称 | 版本 | 作者 | 时间 | 备注 |
-| ---------------- | ------ | ------- | ---------- | ---- |
-| ESP32存储-FAT 文件系统 | v1.0.0 | DuRuofu | 2024-03-06 | 首次建立 |
-
# ESP32存储-VFS虚拟文件系统
diff --git a/docs/04.外设学习/4.2-存储外设/4.2.4-SPIFFS文件系统/SPIFFS文件系统.md b/docs/04.外设学习/4.2-存储外设/4.2.4-SPIFFS文件系统/SPIFFS文件系统.md
index 9fead2c..fedf155 100644
--- a/docs/04.外设学习/4.2-存储外设/4.2.4-SPIFFS文件系统/SPIFFS文件系统.md
+++ b/docs/04.外设学习/4.2-存储外设/4.2.4-SPIFFS文件系统/SPIFFS文件系统.md
@@ -1,15 +1,4 @@
-### 说明:
-
-1. 本文档由DuRuofu撰写,由DuRuofu负责解释及执行。
-2. 本文档主要介绍,ESP32 flash SPIFFS文件系统的使用.
-
-### 修订历史:
-
-| 文档名称 | 版本 | 作者 | 时间 | 备注 |
-| ------------------ | ------ | ------- | ---------- | ---- |
-| ESP32存储-SPIFFS文件系统 | v1.0.0 | DuRuofu | 2024-03-06 | 首次建立 |
-
# ESP32存储-SPIFFS文件系统
diff --git a/docs/04.外设学习/4.2-存储外设/4.2.5-SD卡驱动/SD卡驱动.md b/docs/04.外设学习/4.2-存储外设/4.2.5-SD卡驱动/SD卡驱动.md
index 4a565c8..1618820 100644
--- a/docs/04.外设学习/4.2-存储外设/4.2.5-SD卡驱动/SD卡驱动.md
+++ b/docs/04.外设学习/4.2-存储外设/4.2.5-SD卡驱动/SD卡驱动.md
@@ -1,17 +1,4 @@
-### 说明:
-
-1. 本文档由DuRuofu撰写,由DuRuofu负责解释及执行。
-2. 本文档记录使用ESP-ID的FSD/SDIO/MMC 驱动来使用SD卡。
-
-### 修订历史:
-
-| 文档名称 | 版本 | 作者 | 时间 | 备注 |
-| ---------------------- | ------ | ------- | ---------- | ---- |
-| ESP32存储-SD、SDIO、MMC 驱动 | v1.0.0 | DuRuofu | 2024-04-11 | 首次建立 |
-
-
-
# ESP32存储-SD、SDIO、MMC 驱动
## 一、基本概念
diff --git a/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/Wi-Fi模式与基础操作.md b/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/Wi-Fi模式与基础操作.md
index e69de29..18383ac 100644
--- a/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/Wi-Fi模式与基础操作.md
+++ b/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/Wi-Fi模式与基础操作.md
@@ -0,0 +1,645 @@
+
+# ESP32网络入门-WIFI连接
+
+## 一、介绍
+
+在开始使用WIFI之前,我们需要掌握一些基本的概念和前置知识:
+
+>最基本的一点:Wi-Fi是物理层和数据链路层的东西,Wi-Fi取代的是以太网的网线和交换机上的口,通过无线电波来收发信息。换句话说,这里说的WIFI暂时不涉及网络层协议。
+
+### 1.1 ESP32事件机制
+
+> 如果对FreeRTOS的多线程没有任何了解,请先了解一下,可以参考:[线程是什么](https://www.bilibili.com/video/BV1au411E7K1?p=7)
+
+关于事件循环可以参考下面的链接、大致了解即可。
+
+1. [ESP32事件循环](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/system/esp_event.html)
+2. [Event Loop 大白话版](https://www.bilibili.com/video/BV1FD4y1j79J/?spm_id_from=333.788&vd_source=ef5a0ab0106372751602034cdd9ab98e)
+
+### 1.2 ESP32 WIFI的STA和AP模式
+
+#### 1.2.1 AP
+
+AP(Access Point)也就是无线接入点,是一个无线网络的创建者,是网络的中心节点。一般家庭或办公室使用的无线路由器就是一个AP。
+
+
+
+#### 1.2.2 STA
+
+站点(STA,Station)就是每一个连接到无线网络中的终端(如笔记本电脑、PDA及其它可以联网的用户设备)都可称为一个站点。
+
+
+
+
+## 二、使用
+
+>参考:[ESP-IDF:Wi-Fi 驱动程序](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-guides/wifi.html)
+
+ESP-IDFWiFi库提供的功能如下:
+
+- 支持仅 station 模式、仅 AP 模式、station/AP 共存模式
+- 支持使用 IEEE 802.11b、IEEE 802.11g、IEEE 802.11n 和 API 配置协议模式
+- 支持 WPA/WPA2/WPA3/WPA2-企业版/WPA3-企业版/WAPI/WPS 和 DPP
+- 支持 AMSDU、AMPDU、HT40、QoS 以及其它主要功能
+- 支持乐鑫专属协议,可实现 **1 km** 数据通信量
+- 空中数据传输最高可达 20 MBit/s TCP 吞吐量和 30 MBit/s UDP 吞吐量
+- 支持快速扫描和全信道扫描
+- 支持获取信道状态信息
+
+### 2.1 WIFI AP模式
+
+>官方示例程序位于:
+>
+`Espressif\frameworks\esp-idf-v4.4.3\examples\wifi\getting_started\softAP`
+
+配置流程如下:
+
+- `nvs_flash_init`,初始化默认 NVS 分区。
+- `esp_netif_init`,初始化底层TCP/IP堆栈(创建一个 LwIP 核心任务,并初始化 LwIP 相关工作。)
+- `esp_event_loop_create_default`,创建默认事件循环。
+- `esp_netif_create_default_wifi_ap`,使用默认WiFi AP配置创建esp_netif对象,将netif连接到WiFi并注册默认WiFi处理程序。
+- `esp_wifi_init`,为 WiFi 驱动初始化 WiFi 分配资源,如 WiFi 控制结构、RX/TX 缓冲区、WiFi NVS 结构等,这个 WiFi 也启动 WiFi 任务。必须先调用此API,然后才能调用所有其他WiFi API
+- `esp_event_handler_instance_register`,监听WIFI_EVENTWiFi 任意事件,触发事件后,进入回调函数
+- `esp_wifi_set_mode`,设置WiFi工作模式为station、soft-AP或station+soft-AP,默认模式为soft-AP模式。本程序设置为AP
+- `esp_wifi_set_config`,设置 ESP32 STA 或 AP 的配置
+- `esp_wifi_start`,根据配置,启动WiFi
+
+工作流程如下图所示:(看不懂可以忽略,毕竟先用起来慢慢就学会了,也可以看[Michael_ee](https://space.bilibili.com/1338335828)老师的教程:[WIFI热点工作流程](https://www.bilibili.com/video/BV1ye4y1r7XK/?spm_id_from=333.788&vd_source=ef5a0ab0106372751602034cdd9ab98e))
+
+
+
+主要的流程分为下面几个部分(图来自官方教程):
+
+
+
+#### 2.1.1 Wi-Fi准备阶段(图上没有)
+
+这个阶段我们需要初始化NVS,因为WiFi库内部是依赖这个东西的,NVS的相关知识可以看这篇博客:[ESP32存储-3.VFS虚拟文件系统](https://www.duruofu.top/2024/03/06/4.%E7%A1%AC%E4%BB%B6%E7%9B%B8%E5%85%B3/MCU/ESP32/04.ESP32%E5%AD%98%E5%82%A8%E5%99%A8%E5%85%A5%E9%97%A8/4.3-ESP32%E5%AD%98%E5%82%A8-VFS%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F/ESP32%E5%AD%98%E5%82%A8-VFS%E8%99%9A%E6%8B%9F%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E5%85%A5%E9%97%A8/)
+
+下面直接贴出代码:
+```c
+// Initialize 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());
+ ret = nvs_flash_init();
+}
+ESP_ERROR_CHECK( ret );
+```
+
+#### 2.1.2 Wi-Fi 初始化阶段
+
+这个阶段主要有下面几个步骤:
+
+- 主任务通过调用函数 [`esp_netif_init()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/network/esp_netif.html#_CPPv414esp_netif_initv "esp_netif_init") 创建一个 LwIP 核心任务,并初始化 LwIP 相关工作。
+- 主任务通过调用函数 [`esp_event_loop_create()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/system/esp_event.html#_CPPv421esp_event_loop_createPK21esp_event_loop_args_tP23esp_event_loop_handle_t "esp_event_loop_create") 创建一个系统事件任务,并初始化应用程序事件的回调函数。在此情况下,该回调函数唯一的动作就是将事件中继到应用程序任务中。
+- 主任务通过调用函数 [`esp_netif_create_default_wifi_ap()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/network/esp_netif.html#_CPPv432esp_netif_create_default_wifi_apv "esp_netif_create_default_wifi_ap") 或 [`esp_netif_create_default_wifi_sta()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/network/esp_netif.html#_CPPv433esp_netif_create_default_wifi_stav "esp_netif_create_default_wifi_sta") 创建有 TCP/IP 堆栈的默认网络接口实例绑定 station 或 AP。
+- 主任务通过调用函数 [`esp_wifi_init()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/network/esp_wifi.html#_CPPv413esp_wifi_initPK18wifi_init_config_t "esp_wifi_init") 创建 Wi-Fi 驱动程序任务,并初始化 Wi-Fi 驱动程序。
+- 主任务通过调用 OS API 创建应用程序任务。
+
+代码:
+
+```c
+ESP_ERROR_CHECK(esp_netif_init());
+
+ESP_ERROR_CHECK(esp_event_loop_create_default());
+
+// *esp_netif_ap 可以用来修改AP设置
+esp_netif_t *esp_netif_ap = esp_netif_create_default_wifi_ap();
+
+/*Initialize WiFi */
+wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+// WIFI_INIT_CONFIG_DEFAULT 是一个默认配置的宏
+
+ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+```
+
+到这里就完成了wifi初始化。
+
+#### 2.1.3 Wi-Fi 配置阶段
+
+Wi-Fi 驱动程序初始化成功后,可以进入到配置阶段。
+
+通过函数`esp_wifi_set_mode`可以设置WiFi工作模式为station、soft-AP或station+soft-AP。
+
+通过函数`esp_wifi_set_config`,设置 ESP32 STA 或 AP 的具体配置(参数很多,可以去官方文档仔细看看,下面只配置几个基本参数)。
+
+```c
+// 设置为AP模式
+ ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
+
+// AP详细配置
+wifi_config_t wifi_ap_config = {
+ .ap = {
+ .ssid = ESP_WIFI_AP_SSID, // WIFI名称
+ .ssid_len = strlen(ESP_WIFI_AP_SSID), // 名称长度
+ .channel = 1, // WIFI信道
+ .password = ESP_WIFI_AP_PASSWD, // WiFi密码
+ .max_connection = 5, // 最大连接数,默认值是 10
+ .authmode = WIFI_AUTH_WPA2_PSK, // WiFi认证方式
+ },
+};
+ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_ap_config));
+```
+
+这里只配配置了基本参数。
+#### 2.1.4 Wi-Fi 启动阶段
+
+ 使用`esp_wifi_start`,根据配置,启动WiFi
+
+```c
+/* Start WiFi */
+ESP_ERROR_CHECK(esp_wifi_start());
+```
+
+这样就完成了一个基本的WIFI AP。
+
+但是这样仅仅是最基本的程序,当有外部STA设备接入和断开我们如何在程序里得知呢?这就需要使用之前创建的默认事件循环,
+
+#### 2.1.5 事件循环
+
+调用 [`esp_event_handler_instance_register`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/system/esp_event.html#_CPPv431esp_event_handler_register_with23esp_event_loop_handle_t16esp_event_base_t7int32_t19esp_event_handler_tPv "esp_event_handler_register_with") 将事件处理函数注册到默认的事件循环中。它与 `esp_event_handler_instance_register_with `函数的功能相同,唯一的区别是将处理器注册到默认事件循环中。
+
+```c
+
+esp_err_t esp_event_handler_instance_register(esp_event_base_t event_base,
+ int32_t event_id,
+ esp_event_handler_t event_handler,
+ void *event_handler_arg,
+ esp_event_handler_instance_t *instance);
+```
+
+参数如下:
+- 参数 event_base 是要为其注册处理程序的事件的基本ID。
+- 参数 event_id 是要注册处理程序的事件的ID。
+- 参数 event_handler 是当事件被分发时调用的处理函数。
+- 参数 event_handler_arg 是传递给处理函数的除事件数据以外的数据。
+- 参数 instance 是与注册的事件处理器和数据相关的事件处理器实例对象
+
+可以在espidf文件里看到相关事件描述:
+
+
+
+
+我们可以在默认事件循环创建后,添加事件处理:
+
+```c
+void WIFI_CallBack(void *event_handler_arg,esp_event_base_t event_base,int32_t event_id,void *event_data)
+{
+ // 连接事件
+ if(event_base == IP_EVENT && event_id == IP_EVENT_AP_STAIPASSIGNED)
+ {
+ // 解析数据
+ ip_event_ap_staipassigned_t* event_info = (ip_event_ap_staipassigned_t *)event_data;
+ ESP_LOGW("WIFI_AP", "设备已连接 MAC:"MACSTR"", MAC2STR(event_info->mac));
+ }
+
+ // 断开连接事件
+ if(event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED)
+ {
+ wifi_event_ap_stadisconnected_t* event_info = (wifi_event_ap_stadisconnected_t*)event_data;
+ ESP_LOGW("WIFI_AP", "设备已断开 MAC:"MACSTR"", MAC2STR(event_info->mac));
+ }
+}
+
+void app_main(void)
+{
+ // ......
+
+ // 初始化默认事件循环
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+ // 注册设备连接事件回调
+ esp_event_handler_instance_register(IP_EVENT, IP_EVENT_AP_STAIPASSIGNED, WIFI_CallBack, NULL, NULL);
+ // 注册设备断开连接设备回调
+ esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_AP_STADISCONNECTED, WIFI_CallBack, NULL, NULL);
+
+
+ // ......
+}
+
+```
+
+我们在这里注册了IP_EVENT_AP_STAIPASSIGNED事件,当STA设备连接和断开后就会调用WIFI_CallBack回调函数,打印连接设备的MAC信息。
+
+### 2.2 WIFI STA
+
+>官方示例代码位于(与热点流程相似,但是细节有所不同):
+`Espressif\frameworks\esp-idf-v4.4.3\examples\wifi\getting_started\station
+
+前面几步配配置方式基本相同
+
+下面是官方的程序宏观流程:
+
+
+
+#### 2.2.1 Wi-Fi准备阶段(与上文AP相同)
+
+初始化NVS:
+
+```c
+// Initialize 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());
+ ret = nvs_flash_init();
+}
+ESP_ERROR_CHECK( ret );
+```
+
+#### 2.2.2 Wi-Fi初始化阶段
+
+这里与AP的配置不同了,我们需要配置设备为STA模式,
+
+这里唯一的区别是把`esp_netif_create_default_wifi_ap()`修改为` esp_netif_create_default_wifi_sta()`配置为STA模式
+
+```c
+ESP_ERROR_CHECK(esp_netif_init());
+
+ESP_ERROR_CHECK(esp_event_loop_create_default());
+
+// *esp_netif_ap 可以用来修改AP设置
+esp_netif_t *esp_netif_sta = esp_netif_create_default_wifi_sta();
+
+/*Initialize WiFi */
+wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+// WIFI_INIT_CONFIG_DEFAULT 是一个默认配置的宏
+
+ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+```
+
+#### 2.2.3 Wi-Fi配置阶段
+
+这里与AP模式类似,但是配置结构体里的内容有差异
+
+``` c
+...............
+#define ESP_WIFI_STA_SSID "duruofu_win10"
+#define ESP_WIFI_STA_PASSWD "1234567890"
+
+.................
+
+// 设置为STA模式
+ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
+
+// STA详细配置
+wifi_config_t sta_config = {
+ .sta = {
+ .ssid = ESP_WIFI_STA_SSID,
+ .password = ESP_WIFI_STA_PASSWD,
+ .bssid_set = false,
+ },
+} ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config));
+
+```
+
+这里的配置信息顾名思义就是要连接的wifi名称和密码(当然还有更多参数,可以参考ESP-IDF参数说明)。
+
+#### 2.2.4 Wi-Fi 启动阶段
+
+和AP模式不同,这里多了一个`esp_wifi_connect()`用于连接wifi。
+
+```c
+//----------------启动阶段-------------------
+ESP_ERROR_CHECK(esp_wifi_start());
+ESP_ERROR_CHECK(esp_wifi_connect());
+```
+
+#### 2.2.5 事件循环
+
+同样的,我们可以像前面一样添加事件回调函数
+
+这里监听了启动事件,连接失败事件,连接成功事件,代码很简单就不展开解释了。
+
+```c
+
+void WIFI_CallBack(void *event_handler_arg,esp_event_base_t event_base,int32_t event_id,void *event_data)
+{
+ static uint8_t connect_count = 0;
+ // WIFI 启动成功
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_START");
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ // WIFI 连接失败
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED");
+ connect_count++;
+ if (connect_count < 6)
+ {
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ else{
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED 10 times");
+ }
+ }
+ // WIFI 连接成功(获取到了IP)
+ if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_GOT_IP");
+ ip_event_got_ip_t *info = (ip_event_got_ip_t *)event_data;
+ ESP_LOGI("WIFI_EVENT", "got ip:" IPSTR "", IP2STR(&info->ip_info.ip));
+ }
+}
+
+void app_main(void)
+{
+ // ......
+
+ // 初始化默认事件循环
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+ // 注册事件(wifi启动成功)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_START, WIFI_CallBack, NULL, NULL))
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, WIFI_CallBack, NULL, NULL))
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, WIFI_CallBack, NULL, NULL))
+
+ // ......
+}
+
+```
+
+### 2.3 补充
+
+#### 2.3.1 WIFI的省电模式
+
+
+可以使用`esp_wifi_set_ps()`函数来配置WIFI的省电模式。
+
+这个函数可以有三个参数可选:
+``` c
+typedef enum {
+ WIFI_PS_NONE, /**< No power save */
+ WIFI_PS_MIN_MODEM, /**< Minimum modem power saving. In this mode, station wakes up to receive beacon every DTIM period */
+ WIFI_PS_MAX_MODEM, /**< Maximum modem power saving. In this mode, interval to receive beacons is determined by the listen_interval parameter in wifi_sta_config_t */
+} wifi_ps_type_t;
+
+```
+
+关于省电模式可以参考:[Wi-Fi场景如何选择低功耗模式](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-guides/low-power-mode.html#id8 "永久链接至标题")
+
+#### 2.3.2 静态IP配置
+
+在`esp_netif_create_default_wifi_sta()`创建好DHCP客户端后使用函数`esp_err_t esp_netif_dhcpc_stop(esp_netif_t *esp_netif)`停止DHCP客户端。
+
+使用`esp_netif_set_ip_info()`配置我们想要的静态IP,如下:
+
+```c
+// 初始化STA设备
+esp_netif_t *esp_netif = esp_netif_create_default_wifi_sta();
+
+// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
+//*******************配置静态IP*************************
+esp_netif_dhcpc_stop(esp_netif);
+esp_netif_ip_info_t ipInfo;
+ipInfo.ip.addr = inet_addr("192.168.138.2");
+ipInfo.netmask = inet_addr("255.255.255.0");
+ipInfo.gw = inet_addr("192.168.138.2");
+
+esp_netif_set_ip_info(esp_netif, &ipInfo);
+esp_netif_dhcpc_start(esp_netif);
+//*******************配置静态IP*************************
+// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
+```
+
+### 2.3.1 WIFI Scan
+
+我们使用`esp_wifi_start`后,会根据配置,启动WiFI。如果不立即连接到设定的WIFI热点,我们也可以使用WIFI Scan来寻找可连接的设备。
+
+`esp_wifi_scan_start`,扫描所有有效的AP
+`esp_wifi_scan_get_ap_records`,获取上次扫描中找到的AP列表 `esp_wifi_scan_get_ap_num`,获取上次扫描中找到的AP数
+
+这里也没什么复杂的,建议直接参考IDF官方文档:[ESP32 Wi-Fi 扫描](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-guides/wifi.html#id17 "永久链接至标题")
+## 三、实例
+
+### 3.1 WIFI AP模式
+
+下面基于第二部分的教程实现了WIFI AP模式
+代码链接:
+
+> https://github.com/DuRuofu/ESP32_Learning/tree/master/05.wifi/wifi_ap
+
+```c
+#include
+#include "freertos/FreeRTOS.h"
+#include "freertos/event_groups.h"
+#include "esp_wifi.h"
+#include "esp_log.h"
+#include "esp_event.h"
+#include "nvs_flash.h"
+#include "esp_mac.h"
+
+#define ESP_WIFI_AP_SSID "DuRuofu_ESP32"
+#define ESP_WIFI_AP_PASSWD "3.1415926"
+
+void WIFI_CallBack(void *event_handler_arg,esp_event_base_t event_base,int32_t event_id,void *event_data)
+{
+ // 连接事件
+ if(event_base == IP_EVENT && event_id == IP_EVENT_AP_STAIPASSIGNED)
+ {
+ // 解析数据
+ ip_event_ap_staipassigned_t* event_info = (ip_event_ap_staipassigned_t *)event_data;
+ ESP_LOGW("WIFI_AP", "设备已连接 MAC:"MACSTR"", MAC2STR(event_info->mac));
+ }
+
+ // 断开连接事件
+ if(event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED)
+ {
+ wifi_event_ap_stadisconnected_t* event_info = (wifi_event_ap_stadisconnected_t*)event_data;
+ ESP_LOGW("WIFI_AP", "设备已断开 MAC:"MACSTR"", MAC2STR(event_info->mac));
+ }
+}
+
+void app_main(void)
+{
+ //----------------准备阶段-------------------
+ // Initialize 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());
+ ret = nvs_flash_init();
+ }
+ ESP_ERROR_CHECK(ret);
+
+ //---------------初始化阶段------------------
+
+ ESP_ERROR_CHECK(esp_netif_init());
+
+ // 初始化默认事件循环
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+ // 注册设备连接事件回调
+ esp_event_handler_instance_register(IP_EVENT, IP_EVENT_AP_STAIPASSIGNED, WIFI_CallBack, NULL, NULL);
+ // 注册设备断开连接设备回调
+ esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_AP_STADISCONNECTED, WIFI_CallBack, NULL, NULL);
+
+ // *esp_netif_ap 可以用来修改AP设置
+ esp_netif_t *esp_netif_ap = esp_netif_create_default_wifi_ap();
+
+ /*Initialize WiFi */
+ wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+ // WIFI_INIT_CONFIG_DEFAULT 是一个默认配置的宏
+ ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+
+ //---------------配置阶段--------------------
+ // 设置为AP模式
+ ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
+
+ // AP详细配置
+ wifi_config_t wifi_ap_config = {
+ .ap = {
+ .ssid = ESP_WIFI_AP_SSID, // WIFI名称
+ .ssid_len = strlen(ESP_WIFI_AP_SSID), // 名称长度
+ .channel = 1, // WIFI信道
+ .password = ESP_WIFI_AP_PASSWD, // WiFi密码
+ .max_connection = 5, // 最大连接数,默认值是 10
+ .authmode = WIFI_AUTH_WPA2_PSK, // WiFi认证方式
+ },
+ };
+ ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_ap_config));
+
+ //---------------启动阶段--------------------
+ /* Start WiFi */
+ ESP_ERROR_CHECK(esp_wifi_start());
+}
+```
+
+效果展示:
+
+
+
+连接wifi会打印连接设备的信息。
+
+### 3.2 WIFI STA模式
+
+下面基于第二部分的教程实现了WIFI STA模式
+代码链接:
+
+> https://github.com/DuRuofu/ESP32_Learning/tree/master/05.wifi/wifi_sta
+
+```c
+
+#include
+#include
+#include "freertos/FreeRTOS.h"
+#include "freertos/event_groups.h"
+#include "esp_wifi.h"
+#include "esp_log.h"
+#include "esp_event.h"
+#include "nvs_flash.h"
+#include "esp_mac.h"
+
+#define ESP_WIFI_STA_SSID "duruofu_win10"
+#define ESP_WIFI_STA_PASSWD "1234567890"
+
+void WIFI_CallBack(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
+{
+ static uint8_t connect_count = 0;
+ // WIFI 启动成功
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_START");
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ // WIFI 连接失败
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED");
+ connect_count++;
+ if (connect_count < 6)
+ {
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ else{
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED 10 times");
+ }
+ }
+ // WIFI 连接成功(获取到了IP)
+ if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_GOT_IP");
+ ip_event_got_ip_t *info = (ip_event_got_ip_t *)event_data;
+ ESP_LOGI("WIFI_EVENT", "got ip:" IPSTR "", IP2STR(&info->ip_info.ip));
+ }
+}
+
+void app_main(void)
+{
+ //----------------准备阶段-------------------
+ // Initialize 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());
+ ret = nvs_flash_init();
+ }
+ ESP_ERROR_CHECK(ret);
+
+ //----------------初始化阶段-------------------
+ ESP_ERROR_CHECK(esp_netif_init());
+
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+ // 注册事件(wifi启动成功)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_START, WIFI_CallBack, NULL, NULL));
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, WIFI_CallBack, NULL, NULL));
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, WIFI_CallBack, NULL, NULL));
+
+ // 初始化STA设备
+ esp_netif_create_default_wifi_sta();
+
+ /*Initialize WiFi */
+ wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+ // WIFI_INIT_CONFIG_DEFAULT 是一个默认配置的宏
+
+ ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+
+ //----------------配置阶段-------------------
+ // 设置为STA模式
+ ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
+
+ // STA详细配置
+ wifi_config_t sta_config = {
+ .sta = {
+ .ssid = ESP_WIFI_STA_SSID,
+ .password = ESP_WIFI_STA_PASSWD,
+ .bssid_set = false,
+ },
+ };
+ ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config));
+
+ //----------------启动阶段-------------------
+ ESP_ERROR_CHECK(esp_wifi_start());
+
+}
+
+```
+
+效果如下:
+
+
+
+
+### 3.2 WIFI配置静态IP
+
+代码链接:
+
+> https://github.com/DuRuofu/ESP32_Learning/tree/master/05.wifi/wifi_sta_static_ip
+
+### 3.3 WIFI Scan
+
+代码链接:
+
+> https://github.com/DuRuofu/ESP32_Learning/tree/master/05.wifi/wifi_scan
+
+效果:
+
+
+# 参考链接
+
+1. https://www.bilibili.com/video/BV1au411E7K1?p=7&vd_source=ef5a0ab0106372751602034cdd9ab98e
+2. https://www.bilibili.com/video/BV1au411E7K1?p=8&vd_source=ef5a0ab0106372751602034cdd9ab98e
+3. https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-guides/wifi.html
\ No newline at end of file
diff --git a/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240313184111.png b/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240313184111.png
new file mode 100644
index 0000000..fc32aaa
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240313184111.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240313184132.png b/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240313184132.png
new file mode 100644
index 0000000..b34531b
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240313184132.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240313190734.png b/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240313190734.png
new file mode 100644
index 0000000..4ca9afc
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240313190734.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240313191730.png b/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240313191730.png
new file mode 100644
index 0000000..06cb0a0
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240313191730.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240314165844.png b/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240314165844.png
new file mode 100644
index 0000000..85b940b
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240314165844.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240314220318.png b/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240314220318.png
new file mode 100644
index 0000000..b09e1da
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240314220318.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240314220947.png b/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240314220947.png
new file mode 100644
index 0000000..b4325ee
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240314220947.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240315084947.png b/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240315084947.png
new file mode 100644
index 0000000..84d68ee
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240315084947.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240315100518.png b/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240315100518.png
new file mode 100644
index 0000000..e1ad3d3
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.1-Wi-Fi功能入门/6.1.1-Wi-Fi模式与基础操作/attachments/20240315100518.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/TCP协议应用.md b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/TCP协议应用.md
index e69de29..2f1992e 100644
--- a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/TCP协议应用.md
+++ b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/TCP协议应用.md
@@ -0,0 +1,825 @@
+
+# ESP32网络入门-TCP协议
+## 一、介绍
+
+在开始使用TCP协议之前,我们需要掌握一些基本的概念和前置知识:
+
+>最基本的一点:TCP/UDP工作在网络OSI的七层模型中的第四层——传输层,IP在第三层——网络层,WIFI(狭义上)在一二层-物理层和数据链路层。
+
+### 1.1 套接字(socket)
+
+> 下面的部分搬运自:[Socket介绍](https://zhuanlan.zhihu.com/p/106271407#:~:text=Socket%E6%98%AF%E5%BA%94%E7%94%A8%E5%B1%82%E4%B8%8ETCP%2FIP%E5%8D%8F%E8%AE%AE%E6%97%8F%E9%80%9A%E4%BF%A1%E7%9A%84%E4%B8%AD%E9%97%B4%E8%BD%AF%E4%BB%B6%E6%8A%BD%E8%B1%A1%E5%B1%82%EF%BC%8C%E5%AE%83%E6%98%AF%E4%B8%80%E7%BB%84%E6%8E%A5%E5%8F%A3%E3%80%82,%E5%9C%A8%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B8%AD%EF%BC%8CSocket%E5%85%B6%E5%AE%9E%E5%B0%B1%E6%98%AF%E4%B8%80%E4%B8%AA%E9%97%A8%E9%9D%A2%E6%A8%A1%E5%BC%8F%EF%BC%8C%E5%AE%83%E6%8A%8A%E5%A4%8D%E6%9D%82%E7%9A%84TCP%2FIP%E5%8D%8F%E8%AE%AE%E6%97%8F%E9%9A%90%E8%97%8F%E5%9C%A8Socket%E6%8E%A5%E5%8F%A3%E5%90%8E%E9%9D%A2%EF%BC%8C%E5%AF%B9%E7%94%A8%E6%88%B7%E6%9D%A5%E8%AF%B4%EF%BC%8C%E4%B8%80%E7%BB%84%E7%AE%80%E5%8D%95%E7%9A%84%E6%8E%A5%E5%8F%A3%E5%B0%B1%E6%98%AF%E5%85%A8%E9%83%A8%E3%80%82) *(如有侵权,请联系作者删除)*
+
+
+
+Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部。
+
+
+
+网络协议是很复杂的,它的硬件接口可以是WIFI,网线,4G网卡等,我们开发网络程序,不可能亲自去了解这些物理层,链路层的网络协议和实现。我们通过抽象出统一的上层建筑(Socket)来完成代码编写,这样无论底层(链路层,网络层)是何种形式,我们需要考虑的东西都是相同的(Socket的概念是一样的)。
+
+socket起源于Unix,而Unix/Linux 基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式 来操作。Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)
+
+### 1.2 C/S模式
+
+C/S分布式模式,是计算机用语。C是指Client,S是指Server,C/S模式就是指客户端/服务器模式。是计算机软件协同工作的一种模式,通常采取两层结构。服务器负责数据的管理,客户机负责完成与用户的交互任务。
+
+
+
+
+
+### 1.3 TCP协议
+
+>请自行了解
+
+## 二、使用
+
+>本节的工程基于[ESP32WIFI-1.WIFI连接](https://www.duruofu.top/2024/03/15/4.%E7%A1%AC%E4%BB%B6%E7%9B%B8%E5%85%B3/MCU/ESP32/05.ESP32WIFI%E5%85%A5%E9%97%A8/5.1-ESP32%E7%BD%91%E7%BB%9C%E5%85%A5%E9%97%A8-WIFI%E8%BF%9E%E6%8E%A5/ESP32%E7%BD%91%E7%BB%9C%E5%85%A5%E9%97%A8-WIFI%E8%BF%9E%E6%8E%A5/)中的[wifi_sta](https://github.com/DuRuofu/ESP32_Learning/tree/master/05.wifi/wifi_sta)历程
+
+下图展示了TCP协议服务端和客户端的基本流程:
+
+
+
+### 2.1 TCP客户端
+
+
+
+客户端程序流程:初始化-连接-数据交换-断开连接
+
+``` mermaid
+graph LR;
+A(Initialize) --> B(Connect);
+B --> C(Communicate);
+C --> D(Disconnect);
+```
+
+#### 2.1.1 准备工作
+
+准备工作主要是连接wifi,为下面的网络协议提供支持,可以参考:[ESP32WIFI-1.WIFI连接](https://www.duruofu.top/2024/03/15/4.%E7%A1%AC%E4%BB%B6%E7%9B%B8%E5%85%B3/MCU/ESP32/05.ESP32WIFI%E5%85%A5%E9%97%A8/5.1-ESP32%E7%BD%91%E7%BB%9C%E5%85%A5%E9%97%A8-WIFI%E8%BF%9E%E6%8E%A5/ESP32%E7%BD%91%E7%BB%9C%E5%85%A5%E9%97%A8-WIFI%E8%BF%9E%E6%8E%A5/)
+
+#### 2.1.2 创建套接字
+
+使用函数`int socket(int domain,int type,int protocol)`创建套接字,参数分别为
+
+- `domain`:指定协议家族或地址族,常用的有 `AF_INET`(IPv4 地址族)和 `AF_INET6`(IPv6 地址族)。
+- `type`:指定套接字类型,常见的有 `SOCK_STREAM`(流套接字,提供面向连接的、可靠的数据传输)和 `SOCK_DGRAM`(数据报套接字,提供无连接的、不可靠的数据传输)。
+- `protocol`:指定协议,一般为 0,默认由 `socket()` 函数根据前两个参数自动选择合适的协议。
+
+```c
+// 创建socket
+int sock = socket(AF_INET, SOCK_STREAM, 0);
+if (sock < 0) // 创建失败返回-1
+{
+ ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
+ return;
+}
+```
+#### 2.1.3 配置并连接服务端
+
+使用函数`connect(int s,const struct sockaddr *name,socklen_t namelen)`连接服务端,参数分别为:
+
+- `s`: 表示一个已经创建并绑定到本地地址的套接字描述符。
+- `name`: 是一个指向目标服务器地址结构体的指针,通常是 `struct sockaddr` 结构体或其派生结构体,用来指定要连接的远程服务器的地址信息。
+- `namelen`: 表示参数 `name` 指向的地址结构体的长度。
+
+这里的` struct sockaddr `结构体用于配置IP协议,这里以IPV4为例,参数如下:
+
+- `sin_len`: 该字段表示结构体的长度,单位为字节。在这个结构体中,用一个字节来表示结构体的长度。
+- `sin_family`: 这是一个表示地址族(Address Family)的字段,用于指示地址的类型,如IPv4或IPv6。在这里,用`sa_family_t`类型来表示,可能是一个枚举值或整数值,用于指示IPv4地址族。(和上一步的`domain`参数相同)
+- `sin_port`: 一个16位的整数,表示端口号。`in_port_t`类型通常被定义为一个16位的整数,用于存储端口号。
+- `sin_addr`: 一个`struct in_addr`类型的结构体,用于存储IPv4地址信息。通常`struct in_addr`包含一个32位的整数,表示IPv4地址。
+
+代码如下:
+
+``` c
+#define TCP_SREVER_ADDR "192.168.1.100"
+#define TCP_SREVER_PORT 8080
+
+// 设置服务器(IPV4)
+struct sockaddr_in server_config;
+server_config.sin_addr.s_addr = inet_addr(TCP_SREVER_ADDR);
+server_config.sin_family = AF_INET;
+server_config.sin_port = htons(TCP_SREVER_PORT); // 宏htons 用于将主机的无符号短整型数据转换成网络字节顺序(小端转大端)
+
+// 连接服务器
+int err = connect(sock, (struct sockaddr *)&server_config, sizeof(server_config));
+if (err != 0)
+{
+ ESP_LOGE(TAG, "Socket unable to connect: errno %d", errno);
+ break;
+}
+```
+
+#### 2.1.4 发送消息
+
+使用`send(int s,const void *dataptr,size_t size,int flags)`函数发送消息,参数为:
+- `s`:指定的套接字描述符,即要发送消息的目标套接字。
+- `dataptr`:指向要发送数据的指针,可以是任意类型的数据。
+- `size`:要发送的数据大小,以字节为单位。
+- `flags`:用于指定发送操作的附加选项,通常可以设为0。
+
+例如:
+
+```c
+// 发送数据
+const char *data = "Hello World!";
+int len = send(sock, data, strlen(data), 0);
+if (len < 0)
+{
+ ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
+ break;
+}
+
+```
+
+#### 2.1.5 接收消息
+
+使用`recv(int s,void *mem,size_t len,int flags)`接收数据,参数为:
+
+- `s`:指定要接收数据的套接字描述符。
+- `mem`:指向存放接收数据的缓冲区的指针。
+- `len`:表示接收缓冲区的长度。
+- `flags`:指定接收操作的附加选项,通常可以设置为 0。
+
+例如:
+```c
+
+int len = recv(sock, rx_buffer, sizeof(rx_buffer), 0);
+// Error occurred during receiving
+if (len < 0)
+{
+ ESP_LOGE(TAG, "recv failed: errno %d", errno);
+ return;
+}
+// Data received
+else
+{
+ ESP_LOGI(TAG, "Received %d bytes from %s:", len, TCP_SREVER_ADDR);
+ ESP_LOGI(TAG, "%.*s", len, rx_buffer);
+}
+
+```
+
+完整程序请看下面第三部分:
+### 2.1 TCP服务器
+
+#### 2.2.1 准备工作
+
+初始化NVS、 连接WIFI
+
+#### 2.2.2 创建并配置socket
+
+
+```c
+// 创建套接字
+int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
+if (listen_sock < 0)
+{
+ ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
+ vTaskDelete(NULL);
+ return;
+}
+// 设置套接字属性
+int opt = 1;
+setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+ESP_LOGI(TAG, "Socket created");
+
+```
+
+这里使用了一个用于设置 socket 属性,用函数 `setsockopt()`,函数原形如下:
+
+```c
+int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
+```
+
+- `sockfd`:指定要设置选项的套接字文件描述符。
+- `level`:指定选项的协议级别。常见的级别包括 `SOL_SOCKET`(通用套接字选项)和 `IPPROTO_TCP`(TCP 协议选项)等。
+- `optname`:指定要设置的选项名称,可以是下列之一或者协议特定的选项。常见的选项包括 `SO_REUSEADDR`(允许地址重用)、`SO_KEEPALIVE`(启用连接保活)、`SO_RCVBUF`(设置接收缓冲区大小)等。
+- `optval`:指向包含选项值的缓冲区的指针。
+- `optlen`:指定选项值的长度。
+
+#### 2.2.3 配置服务器信息
+
+代码:
+
+```c
+// 设置服务器(IPV4)
+struct sockaddr_storage dest_addr;
+struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr;
+dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY);
+dest_addr_ip4->sin_family = AF_INET;
+dest_addr_ip4->sin_port = htons(TCP_SREVER_PORT);
+
+// 绑定套接字
+int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
+if (err != 0)
+{
+ ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
+ ESP_LOGE(TAG, "IPPROTO: %d", AF_INET);
+ goto CLEAN_UP;
+}
+ESP_LOGI(TAG, "Socket bound, port %d", TCP_SREVER_PORT);
+
+```
+
+`bind` 函数用于将一个套接字与一个地址(通常是 IP 地址和端口号)绑定在一起,以便在该地址上监听连接或发送数据。它的原型如下:
+
+```c
+int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
+```
+
+
+1. `sockfd`:指定要绑定地址的套接字文件描述符。
+2. `addr`:指向一个 `sockaddr` 结构体的指针,该结构体包含了要绑定的地址信息。在 IPv4 地址族中,可以使用 `sockaddr_in` 结构体;在 IPv6 地址族中,可以使用 `sockaddr_in6` 结构体。通常,你需要将地址信息转换为 `sockaddr` 结构体的形式,然后传递给 `bind` 函数。
+3. `addrlen`:指定地址结构体的长度。
+
+#### 2.2.4 监听客户端连接
+
+```c
+// 监听套接字
+err = listen(listen_sock, 1);
+if (err != 0)
+{
+ ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);
+ goto CLEAN_UP;
+}
+```
+
+`listen()` 函数用于将一个套接字(通常是服务器套接字)转换为被动套接字,即用于接受连接请求
+
+1. `sockfd`:指定要监听的套接字文件描述符。
+2. `backlog`:指定在内核中排队等待接受连接的最大连接数。这个参数限制了同时等待连接的数量,超过这个数量的连接请求将被拒绝。这并不是一个限制同时连接的数量,而是限制等待连接队列的长度。
+3. `listen()` 函数在成功时返回 0,失败时返回 -1
+
+#### 2.2.4 建立接收
+
+```c
+ESP_LOGI(TAG, "Socket listening");
+struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6
+socklen_t addr_len = sizeof(source_addr);
+int sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len);
+if (sock < 0)
+{
+ ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);
+ break;
+}
+
+
+// Set tcp keepalive option
+int keepAlive = 1;
+int keepIdle = 5; // TCP keep-alive idle time(s)
+int keepInterval = 5; // TCP keep-alive interval time(s)
+int keepCount = 3; // TCP keep-alive packet retry send counts
+
+setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(int));
+setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &keepIdle, sizeof(int));
+setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &keepInterval, sizeof(int));
+setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &keepCount, sizeof(int));
+
+
+```
+
+`sockaddr_storage` 是一个足够大的结构体,可用于存储任意地址族(IPv4 或 IPv6)的地址信息。
+
+建立连接使用函数`accept()`,它用于接受传入的连接请求,并创建一个**新的套接字**来与客户端进行通信。它的原型如下:
+
+```c
+int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
+```
+
+参数:
+
+- `sockfd`:指定正在监听连接请求的套接字文件描述符。
+- `addr`:用于存储客户端地址信息的指针。当有连接请求到达时,`accept()` 函数会将客户端的地址信息填写到这个结构体中。
+- `addrlen`:指向一个整数的指针,表示传入的地址结构体的长度。在调用 `accept()` 函数之前,必须将这个参数设置为 `addr` 缓冲区的大小。当 `accept()` 函数返回时,这个参数会更新为实际填充到 `addr` 缓冲区中的地址结构体的长度。
+
+建立连接成功后,通过调用 `setsockopt()` 函数,设置了套接字的 Keep-Alive 选项,以确保连接保持活跃状态。
+
+- `SO_KEEPALIVE`:启用或禁用 TCP Keep-Alive 机制。
+- `TCP_KEEPIDLE`:设置 TCP Keep-Alive 空闲时间,即连接空闲多长时间后开始发送 Keep-Alive 消息。
+- `TCP_KEEPINTVL`:设置 TCP Keep-Alive 消息的发送间隔,即两次 Keep-Alive 消息之间的时间间隔。
+- `TCP_KEEPCNT`:设置 TCP Keep-Alive 消息的发送次数,即发送多少次 Keep-Alive 消息后仍未收到响应才认为连接失效。
+#### 2.2.6 接收/发送数据
+
+接收和发送依然使用`recv`和`send`,下面实现了一个简单的数据接收,回传函数,参数为建立连接的套接字。
+
+```c
+// 数据接收与回传
+static void do_retransmit(const int sock)
+{
+int len;
+char rx_buffer[128];
+
+do
+{
+ len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);
+ if (len < 0)
+ {
+ ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);
+ }
+ else if (len == 0)
+ {
+ ESP_LOGW(TAG, "Connection closed");
+ }
+ else
+ {
+ rx_buffer[len] = 0; // Null-terminate whatever is received and treat it like a string
+ ESP_LOGI(TAG, "Received %d bytes: %s", len, rx_buffer);
+
+ // send() can return less bytes than supplied length.
+ // Walk-around for robust implementation.
+ int to_write = len;
+ while (to_write > 0)
+ {
+ int written = send(sock, rx_buffer + (len - to_write), to_write, 0);
+ if (written < 0)
+ {
+ ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
+ // Failed to retransmit, giving up
+ return;
+ }
+ to_write -= written;
+ }
+ }
+} while (len > 0);
+}
+
+```
+
+程序解释如下:
+
+- `recv()` 函数用于从套接字接收数据,并将接收到的数据存储在 `rx_buffer` 中。它的参数包括套接字文件描述符 `sock`、接收缓冲区 `rx_buffer`、缓冲区大小以及一些可选的标志参数。如果 `recv()` 返回值小于 0,则表示出现了错误;如果返回值为 0,则表示连接已关闭;否则,返回接收到的字节数。
+- 如果接收到的字节数小于 0,表示发生了接收错误,这时会记录错误信息到日志。
+- 如果接收到的字节数为 0,表示连接已关闭,这时会记录警告信息到日志。
+- 如果接收到了数据,会记录接收到的数据字节数和数据内容到日志,并通过 `send()` 函数将接收到的数据回传给客户端。由于 `send()` 函数可能一次未能发送完所有数据,所以在一个循环中,将剩余的数据继续发送,直到所有数据都被发送出去。
+- 如果在发送过程中出现了发送错误(`send()` 返回值小于 0),则会记录错误信息到日志,并返回函数,放弃继续回传数据。
+- 整个函数在循环中进行,直到 `recv()` 返回值小于等于 0,表示接收到的数据长度为 0(连接关闭)或出现了接收错误。
+
+#### 2.2.6 关闭连接和销毁套接字
+
+- `shutdown();`:这个函数调用会关闭套接字的一部分或者全部通信。第二个参数指定了关闭方式:
+ - 如果为 0,则表示关闭套接字的读取功能,即不能再从套接字中读取数据。
+ - 如果为 1,则表示关闭套接字的写入功能,即不能再向套接字中写入数据。
+ - 如果为 2,则表示关闭套接字的读取和写入功能,即完全关闭套接字的通信功能。
+- `close(sock);`:这个函数调用会彻底关闭套接字,释放它占用的资源。关闭套接字后,不能再对它进行任何操作。
+
+以上就是基本的TCP服务的编程流程,关于服务器实例请参考第三部分
+## 三、示例
+
+### 3.1 TCP客户端程序
+
+>代码见: https://github.com/DuRuofu/ESP32_Learning/tree/master/05.wifi/wifi_tcp_client
+
+```c
+#include
+#include
+#include "freertos/FreeRTOS.h"
+#include "freertos/event_groups.h"
+#include "esp_wifi.h"
+#include "esp_log.h"
+#include "esp_event.h"
+#include "nvs_flash.h"
+#include "esp_mac.h"
+#include "esp_netif.h"
+#include
+
+#define ESP_WIFI_STA_SSID "duruofu_win10"
+#define ESP_WIFI_STA_PASSWD "1234567890"
+#define TCP_SREVER_ADDR "192.168.137.1"
+#define TCP_SREVER_PORT 8080
+
+static const char *TAG = "main";
+
+void WIFI_CallBack(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
+{
+ static uint8_t connect_count = 0;
+ // WIFI 启动成功
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_START");
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ // WIFI 连接失败
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED");
+ connect_count++;
+ if (connect_count < 6)
+ {
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ else
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED 10 times");
+ }
+ }
+ // WIFI 连接成功(获取到了IP)
+ if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_GOT_IP");
+ ip_event_got_ip_t *info = (ip_event_got_ip_t *)event_data;
+ ESP_LOGI("WIFI_EVENT", "got ip:" IPSTR "", IP2STR(&info->ip_info.ip));
+ }
+}
+
+static void tcp_client_task(void *pvParameters)
+{
+
+
+ // 等待wifi连接成功(暂时这样处理)
+ vTaskDelay(5000 / portTICK_PERIOD_MS);
+
+ ESP_LOGI("tcp_client_task", "tcp_client_task start");
+
+ // 创建socket
+ int sock = socket(AF_INET, SOCK_STREAM, 0);
+ if (sock < 0) // 创建失败返回-1
+ {
+ ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
+ return;
+ }
+
+ // 设置服务器(IPV4)
+ struct sockaddr_in server_config;
+ server_config.sin_addr.s_addr = inet_addr(TCP_SREVER_ADDR);
+ server_config.sin_family = AF_INET;
+ server_config.sin_port = htons(TCP_SREVER_PORT); // 宏htons 用于将主机的无符号短整型数据转换成网络字节顺序(小端转大端)
+
+ // 连接服务器
+ int err = connect(sock, (struct sockaddr *)&server_config, sizeof(server_config));
+ if (err != 0)
+ {
+ ESP_LOGE(TAG, "Socket unable to connect: errno %d", errno);
+ return;
+ }
+
+ // 发送数据
+ const char *data = "Hello World!";
+ int len = send(sock, data, strlen(data), 0);
+ if (len < 0)
+ {
+ ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
+ return;
+ }
+
+ char rx_buffer[1024];
+ // 接收数据,并发回
+ while(1)
+ {
+ int len = recv(sock, rx_buffer, sizeof(rx_buffer), 0);
+ // Error occurred during receiving
+ if (len < 0)
+ {
+ ESP_LOGE(TAG, "recv failed: errno %d", errno);
+ break;
+ }
+ // Data received
+ else
+ {
+ ESP_LOGI(TAG, "Received %d bytes from %s:", len, TCP_SREVER_ADDR);
+ ESP_LOGI(TAG, "%.*s", len, rx_buffer);
+
+ // 发送数据
+ int len_end = send(sock, rx_buffer, len, 0);
+ if (len_end < 0)
+ {
+ ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
+ break;
+ }
+ }
+ }
+
+ vTaskDelete(NULL);
+}
+
+// wifi初始化
+static void wifi_sta_init(void)
+{
+ ESP_ERROR_CHECK(esp_netif_init());
+
+ // 注册事件(wifi启动成功)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_START, WIFI_CallBack, NULL, NULL));
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, WIFI_CallBack, NULL, NULL));
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, WIFI_CallBack, NULL, NULL));
+
+ // 初始化STA设备
+ esp_netif_create_default_wifi_sta();
+
+ /*Initialize WiFi */
+ wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+ // WIFI_INIT_CONFIG_DEFAULT 是一个默认配置的宏
+
+ ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+
+ //----------------配置阶段-------------------
+ // 初始化WIFI设备( 为 WiFi 驱动初始化 WiFi 分配资源,如 WiFi 控制结构、RX/TX 缓冲区、WiFi NVS 结构等,这个 WiFi 也启动 WiFi 任务。必须先调用此API,然后才能调用所有其他WiFi API)
+ ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
+
+ // STA详细配置
+ wifi_config_t sta_config = {
+ .sta = {
+ .ssid = ESP_WIFI_STA_SSID,
+ .password = ESP_WIFI_STA_PASSWD,
+ .bssid_set = false,
+ },
+ };
+ ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config));
+
+ //----------------启动阶段-------------------
+ ESP_ERROR_CHECK(esp_wifi_start());
+
+ //----------------配置省电模式-------------------
+ // 不省电(数据传输会更快)
+ ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
+}
+
+void app_main(void)
+{
+ // Initialize 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());
+ ret = nvs_flash_init();
+ }
+ ESP_ERROR_CHECK(ret);
+
+ // 创建默认事件循环
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+
+ // 配置启动WIFI
+ wifi_sta_init();
+
+ // 创建TCP客户端任务
+ xTaskCreate(tcp_client_task, "tcp_client_task", 4096, NULL, 5, NULL);
+}
+```
+
+程序效果如下,可以正常收发数据:
+
+
+
+
+### 3.2 TCP服务端程序
+
+>代码见: https://github.com/DuRuofu/ESP32_Learning/tree/master/05.wifi/wifi_tcp_server
+
+
+```c
+#include
+#include
+#include "freertos/FreeRTOS.h"
+#include "freertos/event_groups.h"
+#include "esp_wifi.h"
+#include "esp_log.h"
+#include "esp_event.h"
+#include "nvs_flash.h"
+#include "esp_mac.h"
+#include "esp_netif.h"
+#include
+
+#define ESP_WIFI_STA_SSID "duruofu_win10"
+#define ESP_WIFI_STA_PASSWD "1234567890"
+
+#define TCP_SREVER_PORT 8080
+
+static const char *TAG = "main";
+
+void WIFI_CallBack(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
+{
+ static uint8_t connect_count = 0;
+ // WIFI 启动成功
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_START");
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ // WIFI 连接失败
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED");
+ connect_count++;
+ if (connect_count < 6)
+ {
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ else
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED 10 times");
+ }
+ }
+ // WIFI 连接成功(获取到了IP)
+ if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_GOT_IP");
+ ip_event_got_ip_t *info = (ip_event_got_ip_t *)event_data;
+ ESP_LOGI("WIFI_EVENT", "got ip:" IPSTR "", IP2STR(&info->ip_info.ip));
+ }
+}
+
+// 数据接收与回传
+static void do_retransmit(const int sock)
+{
+ int len;
+ char rx_buffer[128];
+
+ do
+ {
+ len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);
+ if (len < 0)
+ {
+ ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);
+ }
+ else if (len == 0)
+ {
+ ESP_LOGW(TAG, "Connection closed");
+ }
+ else
+ {
+ rx_buffer[len] = 0; // Null-terminate whatever is received and treat it like a string
+ ESP_LOGI(TAG, "Received %d bytes: %s", len, rx_buffer);
+
+ // send() can return less bytes than supplied length.
+ // Walk-around for robust implementation.
+ int to_write = len;
+ while (to_write > 0)
+ {
+ int written = send(sock, rx_buffer + (len - to_write), to_write, 0);
+ if (written < 0)
+ {
+ ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
+ // Failed to retransmit, giving up
+ return;
+ }
+ to_write -= written;
+ }
+ }
+ } while (len > 0);
+}
+
+// tcp服务器任务
+static void tcp_server_task(void *pvParameters)
+{
+ // 创建套接字
+ int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
+ if (listen_sock < 0)
+ {
+ ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
+ vTaskDelete(NULL);
+ return;
+ }
+ // 设置套接字属性
+ int opt = 1;
+ setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+ ESP_LOGI(TAG, "Socket created");
+
+ // 设置服务器(IPV4)
+ struct sockaddr_storage dest_addr;
+ struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr;
+ dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY);
+ dest_addr_ip4->sin_family = AF_INET;
+ dest_addr_ip4->sin_port = htons(TCP_SREVER_PORT);
+
+ // 绑定套接字
+ int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
+ if (err != 0)
+ {
+ ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
+ ESP_LOGE(TAG, "IPPROTO: %d", AF_INET);
+ goto CLEAN_UP;
+ }
+ ESP_LOGI(TAG, "Socket bound, port %d", TCP_SREVER_PORT);
+
+ // 监听套接字 (阻塞)
+ err = listen(listen_sock, 1);
+ if (err != 0)
+ {
+ ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);
+ goto CLEAN_UP;
+ }
+
+ while (1)
+ {
+ char addr_str[128];
+
+ ESP_LOGI(TAG, "Socket listening");
+ struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6
+ socklen_t addr_len = sizeof(source_addr);
+ int sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len);
+ if (sock < 0)
+ {
+ ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);
+ break;
+ }
+
+ // Set tcp keepalive option
+ int keepAlive = 1;
+ int keepIdle = 5; // TCP keep-alive idle time(s)
+ int keepInterval = 5; // TCP keep-alive interval time(s)
+ int keepCount = 3; // TCP keep-alive packet retry send counts
+
+ setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(int));
+ setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &keepIdle, sizeof(int));
+ setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &keepInterval, sizeof(int));
+ setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &keepCount, sizeof(int));
+
+ // Convert ip address to string
+ if (source_addr.ss_family == PF_INET)
+ {
+ inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr, addr_str, sizeof(addr_str) - 1);
+ }
+
+ ESP_LOGI(TAG, "Socket accepted ip address: %s", addr_str);
+
+ do_retransmit(sock);
+
+ shutdown(sock, 0);
+ close(sock);
+ }
+
+CLEAN_UP:
+ close(listen_sock);
+ vTaskDelete(NULL);
+}
+
+// wifi初始化
+static void wifi_sta_init(void)
+{
+ ESP_ERROR_CHECK(esp_netif_init());
+
+ // 注册事件(wifi启动成功)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_START, WIFI_CallBack, NULL, NULL));
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, WIFI_CallBack, NULL, NULL));
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, WIFI_CallBack, NULL, NULL));
+
+ // 初始化STA设备
+ esp_netif_create_default_wifi_sta();
+
+ /*Initialize WiFi */
+ wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+ // WIFI_INIT_CONFIG_DEFAULT 是一个默认配置的宏
+
+ ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+
+ //----------------配置阶段-------------------
+ // 初始化WIFI设备( 为 WiFi 驱动初始化 WiFi 分配资源,如 WiFi 控制结构、RX/TX 缓冲区、WiFi NVS 结构等,这个 WiFi 也启动 WiFi 任务。必须先调用此API,然后才能调用所有其他WiFi API)
+ ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
+
+ // STA详细配置
+ wifi_config_t sta_config = {
+ .sta = {
+ .ssid = ESP_WIFI_STA_SSID,
+ .password = ESP_WIFI_STA_PASSWD,
+ .bssid_set = false,
+ },
+ };
+ ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config));
+
+ //----------------启动阶段-------------------
+ ESP_ERROR_CHECK(esp_wifi_start());
+
+ //----------------配置省电模式-------------------
+ // 不省电(数据传输会更快)
+ ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
+}
+
+void app_main(void)
+{
+ // Initialize 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());
+ ret = nvs_flash_init();
+ }
+ ESP_ERROR_CHECK(ret);
+
+ // 创建默认事件循环
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+
+ // 配置启动WIFI
+ wifi_sta_init();
+
+ // 创建TCP服务器任务
+ xTaskCreate(tcp_server_task, "tcp_server_task", 4096, NULL, 5, NULL);
+}
+
+```
+
+
+效果演示:
+
+
+
+>值得注意的一点:这里将整个tcpserver的流程放在一个task里,以至于他只能一对一通信,若要连接多个,则需要将连接,接收的部分也作为task来编写。
+>每次建立连接就会创建一个新的套接字,将这个新的套接字放到一个新的线程进行通信,就能实现多个客户端连接。
+
+# 参考链接
+
+1. https://github.com/espressif/esp-idf/blob/release/v5.1/examples/protocols/sockets/tcp_server/main/tcp_server.c
\ No newline at end of file
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/attachments/20240315104749.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/attachments/20240315104749.png
new file mode 100644
index 0000000..2276c3a
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/attachments/20240315104749.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/attachments/20240315105448.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/attachments/20240315105448.png
new file mode 100644
index 0000000..c568651
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/attachments/20240315105448.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/attachments/20240315105755.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/attachments/20240315105755.png
new file mode 100644
index 0000000..cf97db2
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/attachments/20240315105755.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/attachments/20240315110509.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/attachments/20240315110509.png
new file mode 100644
index 0000000..f6bcc17
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/attachments/20240315110509.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/attachments/20240315111126.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/attachments/20240315111126.png
new file mode 100644
index 0000000..355b6c0
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/attachments/20240315111126.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/attachments/20240315144045.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/attachments/20240315144045.png
new file mode 100644
index 0000000..5fa3276
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/attachments/20240315144045.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/attachments/20240315180322.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/attachments/20240315180322.png
new file mode 100644
index 0000000..0407175
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.1-TCP协议/attachments/20240315180322.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.2-UDP协议/UDP协议应用.md b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.2-UDP协议/UDP协议应用.md
index e69de29..c8f6106 100644
--- a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.2-UDP协议/UDP协议应用.md
+++ b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.2-UDP协议/UDP协议应用.md
@@ -0,0 +1,577 @@
+
+
+# ESP32网络入门-UDP协议
+
+## 一、概述
+
+上一节教程[ESP32WIFI-2.TCP协议](https://www.duruofu.top/2024/03/15/4.%E7%A1%AC%E4%BB%B6%E7%9B%B8%E5%85%B3/MCU/ESP32/05.ESP32WIFI%E5%85%A5%E9%97%A8/5.2-ESP32%E7%BD%91%E7%BB%9C%E5%85%A5%E9%97%A8-TCP%E5%8D%8F%E8%AE%AE/ESP32%E7%BD%91%E7%BB%9C%E5%85%A5%E9%97%A8-TCP%E5%8D%8F%E8%AE%AE/)详细介绍了TCP协议,本机我们介绍UDP的使用。
+
+UDP和TCP在程序上的区别,主要如下图:
+
+>图来自[Michael_ee](https://space.bilibili.com/1338335828)的UDP教程。
+
+
+
+最大的区别就是UDP没有建立连接的过程。
+
+UDP和TCP的对比如下:
+
+
+
+## 二、使用
+
+> 学习了上一节TCP这部分就会很简单,足够简单就不多描述了。
+
+### 2.1 UDP客户端
+
+首先我们要初始化WIFI,连接WIFI,这是编写UDP程序的基础,连接WIFI在此不再赘述。
+
+>后面的部分,默认已经连接好网络
+
+#### 2.1.1 创建socket
+
+```c
+// 创建socket
+int sock = socket(AF_INET, SOCK_DGRAM, 0);
+if (sock < 0) // 创建失败返回-1
+{
+ ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
+ return;
+}
+```
+#### 2.1.2 配置发送目标服务器信息
+
+```c
+// 目标服务器地址端口
+#define UDP_SREVER_ADDR "255.255.255.255"
+#define UDP_SREVER_PORT 8080
+// 设置服务器(IPV4)
+struct sockaddr_in server_config;
+server_config.sin_addr.s_addr = inet_addr(UDP_SREVER_ADDR);
+server_config.sin_family = AF_INET;
+server_config.sin_port = htons(UDP_SREVER_PORT); // 宏htons 用于将主机的无符号短整型数据转换成网络字节顺序(小端转大端)
+```
+
+服务器的地址选择 `255.255.255.255`,意思是不指定局域网内的某一设备,局域网所有的设备如果监听了这个端口号,那么都可以收到ESP32发来的消息
+
+#### 2.1.3 发送数据
+```c
+// 发送数据
+const char *data = "Hello World!";
+int err = sendto(sock, data, strlen(data), 0, (struct sockaddr *)&server_config, sizeof(server_config));
+if (err < 0)
+{
+ ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
+ close(sock);
+}
+```
+#### 2.1.4 接收数据
+
+```c
+char rx_buffer[1024];
+// 接收数据,并发回
+while(1)
+{
+ // 清空缓存
+ memset(rx_buffer, 0, sizeof(rx_buffer));
+ // 接收数据
+
+ struct sockaddr_in source_addr;
+ socklen_t socklen = sizeof(source_addr);
+ int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);
+ if (len < 0)
+ {
+ ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);
+ break;
+ }
+ else
+ {
+ // 打印接收到的数据
+ ESP_LOGI(TAG, "Received %d bytes: %s", len, rx_buffer);
+ }
+}
+```
+
+
+### 2.2 UDP服务端
+
+>后面的部分,默认已经连接好网络
+#### 2.2.1 创建socket
+
+```c
+// 创建socket
+int sock = socket(AF_INET, SOCK_DGRAM, 0);
+if (sock < 0) // 创建失败返回-1
+{
+ ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
+ return;
+}
+```
+#### 2.2.2 配置服务器信息
+
+``` c
+// 服务器地址端口
+#define UDP_SREVER_PORT 8080
+// 设置服务器(IPV4)
+struct sockaddr_in server_config;
+server_config.sin_addr.s_addr = htonl(INADDR_ANY);
+server_config.sin_family = AF_INET;
+server_config.sin_port = htons(UDP_SREVER_PORT); // 宏htons 用于将主机的无符号短整型数据转换成网络字节顺序(小端转大端)
+```
+
+这里就不需要配置服务器IP了,使用ESP32自身分配到的IP。
+#### 2.2.3 绑定端口
+
+```c
+// 绑定端口
+int err = bind(sock, (struct sockaddr *)&server_config, sizeof(server_config));
+if (err < 0)
+{
+ ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
+}
+ESP_LOGI(TAG, "Socket bound, port %d", UDP_SREVER_PORT);
+
+```
+
+#### 2.2.4 接收数据
+
+```c
+char rx_buffer[1024];
+// 接收数据,并发回
+while(1)
+{
+ // 清空缓存
+ memset(rx_buffer, 0, sizeof(rx_buffer));
+ // 接收数据
+
+ struct sockaddr_in source_addr;
+ socklen_t socklen = sizeof(source_addr);
+ int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);
+ if (len < 0)
+ {
+ ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);
+ break;
+ }
+ else
+ {
+ // 打印接收到的数据
+ ESP_LOGI(TAG, "Received %d bytes: %s", len, rx_buffer);
+ }
+}
+```
+
+#### 2.2.5 发送数据
+
+```c
+// 发送数据
+int err = sendto(sock, rx_buffer, len, 0, (struct sockaddr *)&source_addr, sizeof(source_addr));
+if (err < 0)
+{
+ ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
+ break;
+}
+ESP_LOGI(TAG, "Send success");
+```
+
+
+>有没有发现这服务端代码相比UDP客户端除了多一个使用`bind`函数绑定地址,剩下都一模一样。(可不是我在水字数😀)
+
+这就涉及到UDP本身的知识了,从套接字编程的角度来看,它是完全相同的。由于UDP是无连接的,因此服务器和客户端都将使用相同的sendto和recvfrom方法。
+
+但是我们在通信时,一般来说以主从通信为主,我们更喜欢把主机称作为服务端。相比客户端,在DUP里服务端唯一的不同就是服务端的端口和地址是确定的。这样其他客户端才能准确无误向服务端发送消息。
+
+所以上面的示例代码了,服务端多了一个绑定端口的步骤。其余的都是一样的。
+
+使用我们使用网络调试助手会发现,协议类型一栏不分客户端和服务端。
+
+
+## 三、案例(完整代码)
+
+### 3.1 UDP客户端
+
+>代码:https://github.com/DuRuofu/ESP32_Learning/tree/master/05.wifi/wifi_udp_client
+
+```c
+#include
+#include
+#include "freertos/FreeRTOS.h"
+#include "freertos/event_groups.h"
+#include "esp_wifi.h"
+#include "esp_log.h"
+#include "esp_event.h"
+#include "nvs_flash.h"
+#include "esp_mac.h"
+#include "esp_netif.h"
+#include
+
+// 要连接的WIFI
+#define ESP_WIFI_STA_SSID "duruofu_win10"
+#define ESP_WIFI_STA_PASSWD "1234567890"
+
+// 目标服务器地址端口
+#define UDP_SREVER_ADDR "255.255.255.255"
+#define UDP_SREVER_PORT 8080
+
+static const char *TAG = "main";
+
+void WIFI_CallBack(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
+{
+ static uint8_t connect_count = 0;
+ // WIFI 启动成功
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_START");
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ // WIFI 连接失败
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED");
+ connect_count++;
+ if (connect_count < 6)
+ {
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ else
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED 10 times");
+ }
+ }
+ // WIFI 连接成功(获取到了IP)
+ if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_GOT_IP");
+ ip_event_got_ip_t *info = (ip_event_got_ip_t *)event_data;
+ ESP_LOGI("WIFI_EVENT", "got ip:" IPSTR "", IP2STR(&info->ip_info.ip));
+ }
+}
+
+// udp客户端
+static void udp_client_task(void *pvParameters)
+{
+ // 等待wifi连接成功(暂时这样处理)
+ vTaskDelay(5000 / portTICK_PERIOD_MS);
+ ESP_LOGI("udp_client_task", "udp_client_task start");
+
+ // 创建socket
+ int sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0) // 创建失败返回-1
+ {
+ ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
+ return;
+ }
+
+ // 设置服务器(IPV4)
+ struct sockaddr_in server_config;
+ server_config.sin_addr.s_addr = inet_addr(UDP_SREVER_ADDR);
+ server_config.sin_family = AF_INET;
+ server_config.sin_port = htons(UDP_SREVER_PORT); // 宏htons 用于将主机的无符号短整型数据转换成网络字节顺序(小端转大端)
+
+
+ // 发送数据
+ const char *data = "Hello World!";
+ int err = sendto(sock, data, strlen(data), 0, (struct sockaddr *)&server_config, sizeof(server_config));
+ if (err < 0)
+ {
+ ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
+ close(sock);
+ }
+
+ char rx_buffer[1024];
+ // 接收数据,并发回
+ while(1)
+ {
+ // 清空缓存
+ memset(rx_buffer, 0, sizeof(rx_buffer));
+ // 接收数据
+
+ struct sockaddr_in source_addr;
+ socklen_t socklen = sizeof(source_addr);
+ int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);
+ if (len < 0)
+ {
+ ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);
+ break;
+ }
+ else
+ {
+ // 打印接收到的数据
+ ESP_LOGI(TAG, "Received %d bytes: %s", len, rx_buffer);
+ }
+ }
+ // 关闭socket
+ close(sock);
+ vTaskDelete(NULL);
+}
+
+// wifi初始化
+static void wifi_sta_init(void)
+{
+ ESP_ERROR_CHECK(esp_netif_init());
+
+ // 注册事件(wifi启动成功)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_START, WIFI_CallBack, NULL, NULL));
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, WIFI_CallBack, NULL, NULL));
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, WIFI_CallBack, NULL, NULL));
+
+ // 初始化STA设备
+ esp_netif_create_default_wifi_sta();
+
+ /*Initialize WiFi */
+ wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+ // WIFI_INIT_CONFIG_DEFAULT 是一个默认配置的宏
+
+ ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+
+ //----------------配置阶段-------------------
+ // 初始化WIFI设备( 为 WiFi 驱动初始化 WiFi 分配资源,如 WiFi 控制结构、RX/TX 缓冲区、WiFi NVS 结构等,这个 WiFi 也启动 WiFi 任务。必须先调用此API,然后才能调用所有其他WiFi API)
+ ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
+
+ // STA详细配置
+ wifi_config_t sta_config = {
+ .sta = {
+ .ssid = ESP_WIFI_STA_SSID,
+ .password = ESP_WIFI_STA_PASSWD,
+ .bssid_set = false,
+ },
+ };
+ ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config));
+
+ //----------------启动阶段-------------------
+ ESP_ERROR_CHECK(esp_wifi_start());
+
+ //----------------配置省电模式-------------------
+ // 不省电(数据传输会更快)
+ ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
+}
+
+void app_main(void)
+{
+ // Initialize 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());
+ ret = nvs_flash_init();
+ }
+ ESP_ERROR_CHECK(ret);
+
+ // 创建默认事件循环
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+
+ // 配置启动WIFI
+ wifi_sta_init();
+
+ // 创建TCP客户端任务
+ xTaskCreate(udp_client_task, "udp_client_task", 4096, NULL, 5, NULL);
+}
+
+```
+
+效果:
+
+
+### 3.2 UDP服务端
+
+>代码:https://github.com/DuRuofu/ESP32_Learning/tree/master/05.wifi/wifi_udp_server
+
+```c
+#include
+#include
+#include "freertos/FreeRTOS.h"
+#include "freertos/event_groups.h"
+#include "esp_wifi.h"
+#include "esp_log.h"
+#include "esp_event.h"
+#include "nvs_flash.h"
+#include "esp_mac.h"
+#include "esp_netif.h"
+#include
+#include "lwip/err.h"
+#include "lwip/sys.h"
+#include
+
+
+// 要连接的WIFI
+#define ESP_WIFI_STA_SSID "duruofu_win10"
+#define ESP_WIFI_STA_PASSWD "1234567890"
+
+// 服务器端口
+#define UDP_SREVER_PORT 8080
+
+static const char *TAG = "main";
+
+void WIFI_CallBack(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
+{
+ static uint8_t connect_count = 0;
+ // WIFI 启动成功
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_START");
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ // WIFI 连接失败
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED");
+ connect_count++;
+ if (connect_count < 6)
+ {
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ else
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED 10 times");
+ }
+ }
+ // WIFI 连接成功(获取到了IP)
+ if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_GOT_IP");
+ ip_event_got_ip_t *info = (ip_event_got_ip_t *)event_data;
+ ESP_LOGI("WIFI_EVENT", "got ip:" IPSTR "", IP2STR(&info->ip_info.ip));
+ }
+}
+
+// udp客户端
+static void udp_server_task(void *pvParameters)
+{
+ // 等待wifi连接成功(暂时这样处理)
+ vTaskDelay(5000 / portTICK_PERIOD_MS);
+ ESP_LOGI("udp_server_task", "udp_server_task start");
+
+ // 创建socket
+ int sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0) // 创建失败返回-1
+ {
+ ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
+ return;
+ }
+
+ // 设置服务器(IPV4)
+ struct sockaddr_in server_config;
+ server_config.sin_addr.s_addr = htonl(INADDR_ANY);
+ server_config.sin_family = AF_INET;
+ server_config.sin_port = htons(UDP_SREVER_PORT); // 宏htons 用于将主机的无符号短整型数据转换成网络字节顺序(小端转大端)
+
+ // 绑定端口
+ int err = bind(sock, (struct sockaddr *)&server_config, sizeof(server_config));
+ if (err < 0)
+ {
+ ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
+ }
+ ESP_LOGI(TAG, "Socket bound, port %d", UDP_SREVER_PORT);
+
+ char rx_buffer[1024];
+ // 接收数据,并发回
+ while(1)
+ {
+ // 清空缓存
+ memset(rx_buffer, 0, sizeof(rx_buffer));
+ // 接收数据
+
+ struct sockaddr_in source_addr;
+ socklen_t socklen = sizeof(source_addr);
+ int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);
+ if (len < 0)
+ {
+ ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);
+ break;
+ }
+ else
+ {
+ // 打印接收到的数据
+ ESP_LOGI(TAG, "Received %d bytes: %s", len, rx_buffer);
+ // 发送数据
+ int err = sendto(sock, rx_buffer, len, 0, (struct sockaddr *)&source_addr, sizeof(source_addr));
+ if (err < 0)
+ {
+ ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
+ break;
+ }
+ ESP_LOGI(TAG, "Send success");
+ }
+ }
+ // 关闭socket
+ close(sock);
+ vTaskDelete(NULL);
+}
+
+// wifi初始化
+static void wifi_sta_init(void)
+{
+ ESP_ERROR_CHECK(esp_netif_init());
+
+ // 注册事件(wifi启动成功)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_START, WIFI_CallBack, NULL, NULL));
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, WIFI_CallBack, NULL, NULL));
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, WIFI_CallBack, NULL, NULL));
+
+ // 初始化STA设备
+ esp_netif_create_default_wifi_sta();
+
+ /*Initialize WiFi */
+ wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+ // WIFI_INIT_CONFIG_DEFAULT 是一个默认配置的宏
+
+ ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+
+ //----------------配置阶段-------------------
+ // 初始化WIFI设备( 为 WiFi 驱动初始化 WiFi 分配资源,如 WiFi 控制结构、RX/TX 缓冲区、WiFi NVS 结构等,这个 WiFi 也启动 WiFi 任务。必须先调用此API,然后才能调用所有其他WiFi API)
+ ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
+
+ // STA详细配置
+ wifi_config_t sta_config = {
+ .sta = {
+ .ssid = ESP_WIFI_STA_SSID,
+ .password = ESP_WIFI_STA_PASSWD,
+ .bssid_set = false,
+ },
+ };
+ ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config));
+
+ //----------------启动阶段-------------------
+ ESP_ERROR_CHECK(esp_wifi_start());
+
+ //----------------配置省电模式-------------------
+ // 不省电(数据传输会更快)
+ ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
+}
+
+void app_main(void)
+{
+ // Initialize 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());
+ ret = nvs_flash_init();
+ }
+ ESP_ERROR_CHECK(ret);
+
+ // 创建默认事件循环
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+
+ // 配置启动WIFI
+ wifi_sta_init();
+
+ // 创建TCP客户端任务
+ xTaskCreate(udp_server_task, "udp_server_task", 4096, NULL, 5, NULL);
+}
+
+```
+
+效果:
+
+
+
+
+# 参考链接
+
+1. https://www.bilibili.com/video/BV1iG411j7Gq/?spm_id_from=333.788&vd_source=ef5a0ab0106372751602034cdd9ab98e
\ No newline at end of file
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.2-UDP协议/attachments/20240317142714.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.2-UDP协议/attachments/20240317142714.png
new file mode 100644
index 0000000..16f1f3a
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.2-UDP协议/attachments/20240317142714.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.2-UDP协议/attachments/20240317143536.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.2-UDP协议/attachments/20240317143536.png
new file mode 100644
index 0000000..bcf48f0
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.2-UDP协议/attachments/20240317143536.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.2-UDP协议/attachments/20240317151255.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.2-UDP协议/attachments/20240317151255.png
new file mode 100644
index 0000000..5cb2600
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.2-UDP协议/attachments/20240317151255.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.2-UDP协议/attachments/20240317155445.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.2-UDP协议/attachments/20240317155445.png
new file mode 100644
index 0000000..755cbe4
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.2-UDP协议/attachments/20240317155445.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.2-UDP协议/attachments/20240317155554.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.2-UDP协议/attachments/20240317155554.png
new file mode 100644
index 0000000..f31edf8
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.2-UDP协议/attachments/20240317155554.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3- HTTP协议/HTTP协议应用.md b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3- HTTP协议/HTTP协议应用.md
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(客户端)/HTTP协议(客户端).md b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(客户端)/HTTP协议(客户端).md
new file mode 100644
index 0000000..068048d
--- /dev/null
+++ b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(客户端)/HTTP协议(客户端).md
@@ -0,0 +1,451 @@
+
+
+# ESP32网络入门-HTTP协议
+
+
+### 一、概述
+
+#### 1.1 HTTP协议介绍
+
+`HTTP`协议(超文本传输协议HyperText Transfer Protocol),它是基于TCP协议的应用层传输协议,简单来说就是客户端和服务端进行数据传输的一种规则。
+
+特点:
+
+- 支持客户/服务器模式
+- 简单快速:客户向服务器请求服务时,只需传送请求方法和路径,请求方法常用GET,HEAD,POST。每种方法规定了客户与服务器联系的类型不同,由于HTTP协议简单,是的HTTP服务器的程序规模小,因而通信速度很快。
+- 灵活:HTTP允许传输任意类型的数据对象,正在传输的类型有Content-Type加以标记。
+- 无连接:无连接的含义是限制每次连接只处理一个请求,服务器处理完客户的请求,并受到客户的应答后,即断开连接,采用这种方式可以节省传输时间。
+- 无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可以导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
+
+
+#### 1.2 HTTP内容
+
+>关于HTTP内容比较简单,这里建议直接阅读计算机网络有关部分,在此不再赘述。
+
+
+
+
+HTTP协议使用客户端/服务器模型。客户端向服务器发送HTTP请求,服务器接收到请求后,根据请求的内容进行一定的处理,并将处理结果封装在HTTP响应中返回给客户端。HTTP请求和响应都有一定的结构,通常包含三部分:起始行、首部和主体。
+
+可以参考:
+
+>[前后端交互之 HTTP 协议](https://www.bilibili.com/video/BV1KV411o7u5/?spm_id_from=333.1007.0.0&vd_source=ef5a0ab0106372751602034cdd9ab98e)
+
+
+我们在本节只需要知道发送HTTP请求的格式和接收的格式即可:
+
+##### 发送格式:
+
+
+
+
+HTTP的起始行包含了请求方法或响应状态码等信息,如GET、POST等请求方法,200 OK、404 Not Found等响应状态码。HTTP的首部包含了请求或响应的各项属性,如Accept、Content-Type、Set-Cookie等。HTTP的主体则包含了请求或响应的具体内容,如HTML、JSON等文本数据或二进制数据。
+
+
+
+##### 接收格式:
+
+
+
+
+##### URL格式:
+URL(Uniform Resource Locator)是用于定位互联网上资源的地址,常见的资源包括网页、图片、视频等。URL由多个部分组成,包括协议、主机名、端口号、路径和查询字符串等。
+
+一个标准的URL格式如下:
+
+复制代码
+
+```
+<协议>://<主机名>:<端口>/<路径>?<查询字符串>
+```
+
+其中,协议表示访问资源所采用的协议,如HTTP、HTTPS、FTP等;主机名表示资源所在的主机名或IP地址;端口号表示与主机通信的端口号,默认情况下使用协议默认的端口;路径表示请求的资源路径,可以是一个具体的文件路径,也可以是一个文件夹路径;查询字符串表示请求参数,以问号(?)开头,多个参数之间用&符号分隔。
+
+例如,以下是一个标准的URL格式:
+
+复制代码
+
+```
+https://www.example.com:80/index.html?id=123&name=test
+```
+
+其中,协议为HTTPS,[主机名为www.example.com](http://xn--www-c88de450g138a.example.com/),端口号为80(默认端口号可省略),路径为/index.html,查询字符串为id=123&name=test。
+
+> 请确保明白上面的内容,再开始学习ESP32的HTTP协议使用。
+
+### 二、使用
+
+下面我们使用ESP32充当浏览器,向HTTP服务器发送Request请求。
+
+#### 2.1 整体介绍
+
+`esp_http_client` 提供了一组 API,用于从 ESP-IDF 应用程序中发起 HTTP/S 请求,具体的使用步骤如下:
+
+- 首先调用 [`esp_http_client_init()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/protocols/esp_http_client.html#_CPPv420esp_http_client_initPK24esp_http_client_config_t "esp_http_client_init"),创建一个 [`esp_http_client_handle_t`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/protocols/esp_http_client.html#_CPPv424esp_http_client_handle_t "esp_http_client_handle_t") 实例,即基于给定的 [`esp_http_client_config_t`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/protocols/esp_http_client.html#_CPPv424esp_http_client_config_t "esp_http_client_config_t") 配置创建 HTTP 客户端句柄。此函数必须第一个被调用。若用户未明确定义参数的配置值,则使用默认值。
+
+- 其次调用 [`esp_http_client_perform()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/protocols/esp_http_client.html#_CPPv423esp_http_client_perform24esp_http_client_handle_t "esp_http_client_perform"),执行 `esp_http_client` 的所有操作,包括打开连接、交换数据、关闭连接(如需要),同时在当前任务完成前阻塞该任务。所有相关的事件(在 [`esp_http_client_config_t`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/protocols/esp_http_client.html#_CPPv424esp_http_client_config_t "esp_http_client_config_t") 中指定)将通过事件处理程序被调用。
+
+- 最后调用 [`esp_http_client_cleanup()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/protocols/esp_http_client.html#_CPPv423esp_http_client_cleanup24esp_http_client_handle_t "esp_http_client_cleanup") 来关闭连接(如有),并释放所有分配给 HTTP 客户端实例的内存。此函数必须在操作完成后最后一个被调用。
+
+#### 2.2 详细步骤
+
+首先我们要初始化WIFI,连接WIFI,这是编写HTTP程序的基础,连接WIFI在此不再赘述。
+
+>后面的部分,默认已经连接好网络.
+
+##### 2.2.1 创建`esp_http_client` 实例
+
+这里要使用[`esp_http_client_init()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/protocols/esp_http_client.html#_CPPv420esp_http_client_initPK24esp_http_client_config_t "esp_http_client_init")创建HTTP客户端句柄实例,这个函数是 ESP32/ESP-IDF 中用于初始化 HTTP 客户端会话的函数。它接受一个指向 `esp_http_client_config_t` 结构的指针作为参数,该结构包含了 HTTP 客户端的配置信息。函数返回一个 esp_http_client_handle_t 类型的句柄,需要将这个句柄作为其他接口函数的输入参数来使用。在调用这个函数之后,必须在操作完成后调用 esp_http_client_cleanup 函数来清理资源。
+
+关于`esp_http_client_config_t` 结构的参数如下:
+
+| 成员 | 描述 |
+| --------------------------- | -------------------------------------------------------------------------- |
+| url | HTTP URL,如果设置了该字段,则会覆盖其他字段 |
+| host | 域名或 IP 地址,以字符串表示 |
+| port | 连接的端口,默认取决于 `esp_http_client_transport_t`(80 或 443) |
+| username | HTTP 身份验证的用户名 |
+| password | HTTP 身份验证的密码 |
+| auth_type | HTTP 身份验证类型,参见 `esp_http_client_auth_type_t` |
+| path | HTTP 路径,默认为 `/` 如果未设置 |
+| query | HTTP 查询参数 |
+| cert_pem | SSL 服务器证书,PEM 格式的字符串,如果客户端需要验证服务器 |
+| cert_len | `cert_pem` 缓冲区的长度。对于以 null 结尾的 PEM,可能为 0 |
+| client_cert_pem | SSL 客户端证书,PEM 格式的字符串,如果服务器需要验证客户端 |
+| client_cert_len | `client_cert_pem` 缓冲区的长度。对于以 null 结尾的 PEM,可能为 0 |
+| client_key_pem | SSL 客户端私钥,PEM 格式的字符串,如果服务器需要验证客户端 |
+| client_key_len | `client_key_pem` 缓冲区的长度。对于以 null 结尾的 PEM,可能为 0 |
+| client_key_password | 客户端密钥解密密码字符串 |
+| client_key_password_len | `client_key_password` 字符串的长度 |
+| tls_version | 连接的 TLS 协议版本,例如 TLS 1.2、TLS 1.3(默认 - 无偏好) |
+| user_agent | 发送 HTTP 请求时的用户代理字符串 |
+| method | HTTP 方法 |
+| timeout_ms | 网络超时时间(毫秒) |
+| disable_auto_redirect | 禁用 HTTP 自动重定向 |
+| max_redirection_count | 在接收到 HTTP 重定向状态码时的最大重定向次数,如果为零,则使用默认值 |
+| max_authorization_retries | 在接收到 HTTP 未经授权状态码时的最大连接重试次数,如果为零,则使用默认值。如果为 -1,则禁用授权重试 |
+| event_handler | HTTP 事件处理器 |
+| transport_type | HTTP 传输类型,参见 `esp_http_client_transport_t` |
+| buffer_size | HTTP 接收缓冲区大小 |
+| buffer_size_tx | HTTP 发送缓冲区大小 |
+| user_data | HTTP 用户数据上下文 |
+| is_async | 设置异步模式,目前仅支持 HTTPS |
+| use_global_ca_store | 使用全局 CA 存储 |
+| skip_cert_common_name_check | 跳过对服务器证书 CN 字段的验证 |
+| common_name | 指向包含服务器证书公共名称的字符串的指针。如果非 NULL,则服务器证书 CN 必须匹配此名称;如果为 NULL,则服务器证书 CN 必须匹配主机名 |
+| crt_bundle_attach | 指向 esp_crt_bundle_attach 函数的函数指针。启用证书包以进行服务器验证,必须在 menuconfig 中启用 |
+| keep_alive_enable | 启用 keep-alive 超时 |
+| keep_alive_idle | keep-alive 空闲时间,默认为 5 秒 |
+| keep_alive_interval | keep-alive 间隔时间,默认为 5 秒 |
+| keep_alive_count | keep-alive 数据包重试发送计数,默认为 3 次 |
+| if_name | 数据通过的接口名称。如果不设置,则使用默认接口 |
+
+示例代码:
+
+```c
+// 初始化HTTP客户端
+char local_response_buffer[MAX_HTTP_OUTPUT_BUFFER + 1] = {0};
+esp_http_client_config_t config = {
+ .host = CONFIG_EXAMPLE_HTTP_ENDPOINT,
+ .path = "/get",
+ .query = "esp",
+ .event_handler = _http_event_handler,
+ .user_data = local_response_buffer, // Pass address of local buffer to get response
+ .disable_auto_redirect = true,
+};
+esp_http_client_handle_t client = esp_http_client_init(&config);
+```
+
+解释:
+
+1. `char local_response_buffer[MAX_HTTP_OUTPUT_BUFFER + 1] = {0};`:这一行定义了一个字符数组 `local_response_buffer`,用来存储 HTTP 响应内容。`MAX_HTTP_OUTPUT_BUFFER` 是预先定义的常量,表示了 buffer 的最大大小。
+
+2. `esp_http_client_config_t config = { ... };`:这里创建了一个 `esp_http_client_config_t` 结构体变量 `config`,并使用大括号内的初始化列表对其进行初始化。初始化列表中的字段包括:
+ - `host`:设置为宏 `CONFIG_EXAMPLE_HTTP_ENDPOINT`,表示 HTTP 请求的目标主机。
+ - `path`:设置为 `"/get"`,表示 HTTP 请求的路径。
+ - `query`:设置为 `"esp"`,表示 HTTP 请求的查询参数。
+ - `event_handler`:设置为 `_http_event_handler`,这是一个处理 HTTP 事件的回调函数。
+ - `user_data`:设置为 `local_response_buffer` 的地址,以便在 HTTP 请求完成后将响应内容存储到 `local_response_buffer` 中。
+ - `disable_auto_redirect`:设置为 `true`,禁用 HTTP 的自动重定向功能。
+3. `esp_http_client_handle_t client = esp_http_client_init(&config);`:这一行调用了 `esp_http_client_init` 函数来初始化一个 HTTP 客户端,并将上述配置传递给该函数。函数返回一个 `esp_http_client_handle_t` 类型的客户端句柄,以供后续的 HTTP 请求使用。
+
+##### 2.2.2 执行HTTP客户端的各种操作
+
+>具体使用就很简单了:
+
+###### GET请求
+esp_http_client_perform():esp_http_client需要使用init函数创建的参数。此函数执行esp_http_client的所有操作,从打开连接,发送数据,下载数据和关闭连接(如有必要)。所有相关事件都将在event_handle(由定义esp_http_client_config_t)中调用。此功能执行其工作并阻止当前任务,直到完成
+
+```c
+esp_err_t err = esp_http_client_perform(client);
+if (err == ESP_OK)
+{
+ ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %" PRId64,
+ esp_http_client_get_status_code(client),
+ esp_http_client_get_content_length(client));
+}
+else
+{
+ ESP_LOGE(TAG, "HTTP GET request failed: %s", esp_err_to_name(err));
+}
+ESP_LOG_BUFFER_HEX(TAG, local_response_buffer, strlen(local_response_buffer));
+
+```
+
+上面这段代码执行了一个HTTP GET请求,并根据执行结果打印相应的日志信息。
+###### POST请求
+
+```c
+// POST
+const char *post_data = "{\"field1\":\"value1\"}";
+esp_http_client_set_url(client, "http://" CONFIG_EXAMPLE_HTTP_ENDPOINT "/post");
+esp_http_client_set_method(client, HTTP_METHOD_POST);
+esp_http_client_set_header(client, "Content-Type", "application/json");
+esp_http_client_set_post_field(client, post_data, strlen(post_data));
+err = esp_http_client_perform(client);
+if (err == ESP_OK)
+{
+ ESP_LOGI(TAG, "HTTP POST Status = %d, content_length = %" PRId64,
+ esp_http_client_get_status_code(client),
+ esp_http_client_get_content_length(client));
+}
+else
+{
+ ESP_LOGE(TAG, "HTTP POST request failed: %s", esp_err_to_name(err));
+}
+```
+
+
+##### 2.2.3 关闭链接,并释放系统资源
+
+```c
+esp_http_client_cleanup(client);
+```
+
+完成**esp_http_client的**任务后,这是最后一个要调用的函数。它将关闭连接(如果有)并释放分配给HTTP客户端的所有内存
+
+
+### 三、案例
+
+> 使用HTTP get请求获取我的博客首页
+
+```c
+
+#include
+#include
+#include
+#include
+#include "esp_log.h"
+#include "nvs_flash.h"
+#include "esp_event.h"
+#include "esp_netif.h"
+#include "esp_tls.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_system.h"
+#include "esp_wifi.h"
+#include "esp_mac.h"
+#include
+#include "esp_http_client.h"
+
+// 要连接的WIFI
+#define ESP_WIFI_STA_SSID "duruofu_win10"
+#define ESP_WIFI_STA_PASSWD "1234567890"
+static const char *TAG = "main";
+
+// 最大http输出缓冲区
+#define MAX_HTTP_OUTPUT_BUFFER 2048
+
+// WIFI事件回调
+void WIFI_CallBack(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
+{
+ static uint8_t connect_count = 0;
+ // WIFI 启动成功
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_START");
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ // WIFI 连接失败
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED");
+ connect_count++;
+ if (connect_count < 6)
+ {
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ else
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED 10 times");
+ }
+ }
+ // WIFI 连接成功(获取到了IP)
+ if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_GOT_IP");
+ ip_event_got_ip_t *info = (ip_event_got_ip_t *)event_data;
+ ESP_LOGI("WIFI_EVENT", "got ip:" IPSTR "", IP2STR(&info->ip_info.ip));
+ }
+}
+
+// 连接WIFI
+static void wifi_sta_init(void)
+{
+ ESP_ERROR_CHECK(esp_netif_init());
+
+ // 注册事件(wifi启动成功)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_START, WIFI_CallBack, NULL, NULL));
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, WIFI_CallBack, NULL, NULL));
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, WIFI_CallBack, NULL, NULL));
+
+ // 初始化STA设备
+ esp_netif_create_default_wifi_sta();
+
+ /*Initialize WiFi */
+ wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+ // WIFI_INIT_CONFIG_DEFAULT 是一个默认配置的宏
+
+ ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+
+ //----------------配置阶段-------------------
+ // 初始化WIFI设备( 为 WiFi 驱动初始化 WiFi 分配资源,如 WiFi 控制结构、RX/TX 缓冲区、WiFi NVS 结构等,这个 WiFi 也启动 WiFi 任务。必须先调用此API,然后才能调用所有其他WiFi API)
+ ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
+
+ // STA详细配置
+ wifi_config_t sta_config = {
+ .sta = {
+ .ssid = ESP_WIFI_STA_SSID,
+ .password = ESP_WIFI_STA_PASSWD,
+ .bssid_set = false,
+ },
+ };
+ ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config));
+
+ //----------------启动阶段-------------------
+ ESP_ERROR_CHECK(esp_wifi_start());
+
+ //----------------配置省电模式-------------------
+ // 不省电(数据传输会更快)
+ ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
+}
+
+
+// HTTP事件回调
+esp_err_t _http_event_handler(esp_http_client_event_t *evt)
+{
+ switch (evt->event_id)
+ {
+ case HTTP_EVENT_ERROR: // 错误事件
+ ESP_LOGI(TAG, "HTTP_EVENT_ERROR");
+ break;
+ case HTTP_EVENT_ON_CONNECTED: // 连接成功事件
+ ESP_LOGI(TAG, "HTTP_EVENT_ON_CONNECTED");
+ break;
+ case HTTP_EVENT_HEADER_SENT: // 发送头事件
+ ESP_LOGI(TAG, "HTTP_EVENT_HEADER_SENT");
+ break;
+ case HTTP_EVENT_ON_HEADER: // 接收头事件
+ ESP_LOGI(TAG, "HTTP_EVENT_ON_HEADER");
+ printf("%.*s", evt->data_len, (char *)evt->data);
+ break;
+ case HTTP_EVENT_ON_DATA: // 接收数据事件
+ ESP_LOGI(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
+ if (!esp_http_client_is_chunked_response(evt->client))
+ {
+ printf("%.*s", evt->data_len, (char *)evt->data);
+ }
+ break;
+ case HTTP_EVENT_ON_FINISH: // 会话完成事件
+ ESP_LOGI(TAG, "HTTP_EVENT_ON_FINISH");
+ break;
+ case HTTP_EVENT_DISCONNECTED: // 断开事件
+ ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED");
+ break;
+ case HTTP_EVENT_REDIRECT:
+ ESP_LOGD(TAG, "HTTP_EVENT_REDIRECT");
+ }
+ return ESP_OK;
+}
+
+
+// HTTP客户端任务
+static void http_client_task(void)
+{
+ // 等待wifi连接成功(暂时这样处理)
+ vTaskDelay(5000 / portTICK_PERIOD_MS);
+ ESP_LOGI("http_client_task", "http_client_task start");
+
+ // 初始化HTTP客户端
+ char local_response_buffer[MAX_HTTP_OUTPUT_BUFFER + 1] = {0};
+
+ // 配置HTTP客户端目标
+ esp_http_client_config_t config = {
+ //.method = HTTP_METHOD_GET, // get请求
+ .url = "http://www.duruofu.top", // 请求url
+ .event_handler = _http_event_handler, // 事件回调
+ .user_data = local_response_buffer, // Pass address of local buffer to get response
+ };
+ esp_http_client_handle_t client = esp_http_client_init(&config);
+
+ // GET
+ esp_http_client_set_method(client, HTTP_METHOD_GET);
+ esp_err_t err = esp_http_client_perform(client);
+ if (err == ESP_OK)
+ {
+ ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %" PRId64,
+ esp_http_client_get_status_code(client),
+ esp_http_client_get_content_length(client));
+ }
+ else
+ {
+ ESP_LOGE(TAG, "HTTP GET request failed: %s", esp_err_to_name(err));
+ }
+ ESP_LOG_BUFFER_HEX(TAG, local_response_buffer, strlen(local_response_buffer));
+
+ esp_http_client_cleanup(client);
+
+ vTaskDelete(NULL);
+}
+
+void app_main(void)
+{
+ // Initialize 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());
+ ret = nvs_flash_init();
+ }
+ ESP_ERROR_CHECK(ret);
+
+ // 创建默认事件循环
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+
+ // 配置启动WIFI
+ wifi_sta_init();
+
+ // 创建HTTP客户端任务
+ xTaskCreate(http_client_task, "http_client_task", 1024*8, NULL, 5, NULL);
+}
+
+
+```
+
+效果:
+成功获取到了博客网站的内容:
+
+
+
+
+内容一致:
+
+
+
+# 参考链接
+
+1. https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/protocols/esp_http_client.html#_CPPv424esp_http_client_config_t
+2.
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(客户端)/attachments/20240324115232.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(客户端)/attachments/20240324115232.png
new file mode 100644
index 0000000..db23c6a
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(客户端)/attachments/20240324115232.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(客户端)/attachments/20240324115334.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(客户端)/attachments/20240324115334.png
new file mode 100644
index 0000000..30d98bb
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(客户端)/attachments/20240324115334.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(客户端)/attachments/20240324115801.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(客户端)/attachments/20240324115801.png
new file mode 100644
index 0000000..c68b749
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(客户端)/attachments/20240324115801.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(客户端)/attachments/20240325151411.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(客户端)/attachments/20240325151411.png
new file mode 100644
index 0000000..1638b34
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(客户端)/attachments/20240325151411.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(客户端)/attachments/20240325151513.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(客户端)/attachments/20240325151513.png
new file mode 100644
index 0000000..b0ea0b7
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(客户端)/attachments/20240325151513.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/HTTP协议(服务端).md b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/HTTP协议(服务端).md
new file mode 100644
index 0000000..fa0f97a
--- /dev/null
+++ b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/HTTP协议(服务端).md
@@ -0,0 +1,907 @@
+
+
+# ESP32网络入门-HTTP协议(服务端)
+
+## 一、介绍
+
+>关于HTTP协议的内容在上一节教程里提过,在此不再赘述。
+
+ESP-IDF的HTTP Server 组件提供了在 ESP32 上运行轻量级 Web 服务器的功能,下面介绍使用 HTTP Server 组件 API 的详细步骤:
+
+> - [`httpd_start()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/protocols/esp_http_server.html#_CPPv411httpd_startP14httpd_handle_tPK14httpd_config_t "httpd_start"): 创建 HTTP 服务器的实例,根据具体的配置为其分配内存和资源,并返回该服务器实例的句柄。服务器使用了两个套接字,一个用来监听 HTTP 流量(TCP 类型),另一个用来处理控制信号(UDP 类型),它们在服务器的任务循环中轮流使用。通过向 `httpd_start()` 传递 `httpd_config_t` 结构体,可以在创建服务器实例时配置任务的优先级和堆栈的大小。TCP 流量被解析为 HTTP 请求,根据请求的 URI 来调用用户注册的处理程序,在处理程序中需要发送回 HTTP 响应数据包。
+>
+> - [`httpd_stop()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/protocols/esp_http_server.html#_CPPv410httpd_stop14httpd_handle_t "httpd_stop"): 根据传入的句柄停止服务器,并释放相关联的内存和资源。这是一个阻塞函数,首先给服务器任务发送停止信号,然后等待其终止。期间服务器任务会关闭所有已打开的连接,删除已注册的 URI 处理程序,并将所有会话的上下文数据重置为空。
+>
+> - [`httpd_register_uri_handler()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/protocols/esp_http_server.html#_CPPv426httpd_register_uri_handler14httpd_handle_tPK11httpd_uri_t "httpd_register_uri_handler"): 通过传入 `httpd_uri_t` 结构体类型的对象来注册 URI 处理程序。该结构体包含如下成员:`uri` 名字,`method` 类型(比如 `HTTPD_GET/HTTPD_POST/HTTPD_PUT` 等等), `esp_err_t *handler (httpd_req_t *req)` 类型的函数指针,指向用户上下文数据的 `user_ctx` 指针。
+>
+## 二、使用
+
+首先我们要初始化WIFI,连接WIFI,这是编写HTTP程序的基础,连接WIFI在此不再赘述。
+
+>后面的部分,默认已经连接好网络.
+
+### 一、创建 HTTP 服务器的实例
+
+```c
+/* 启动 Web 服务器的函数 */
+httpd_handle_t start_webserver(void)
+{
+ /* 生成默认的配置参数 */
+ httpd_config_t config = HTTPD_DEFAULT_CONFIG();
+
+ /* 置空 esp_http_server 的实例句柄 */
+ httpd_handle_t server = NULL;
+
+ /* 启动 httpd server */
+ if (httpd_start(&server, &config) == ESP_OK) {
+ /* 注册 URI 处理程序 */
+ httpd_register_uri_handler(server, &uri_get);
+ httpd_register_uri_handler(server, &uri_post);
+ }
+ /* 如果服务器启动失败,返回的句柄是 NULL */
+ return server;
+}
+
+```
+
+这段代码是一个基于ESP32的HTTP服务器程序,通过调用httpd_start()函数启动HTTP服务器,并注册URI处理函数,来实现Web服务的功能。主要包括以下几个步骤:
+
+1. 初始化HTTPD配置,使用HTTPD_DEFAULT_CONFIG()函数可以获得默认的HTTPD配置对象,这里设置了LRU清理使能;
+
+2. 启动HTTPD服务器,通过调用httpd_start()函数启动HTTPD服务器,参数server指向创建后的HTTPD句柄,config包含HTTPD的配置信息;
+
+3. 注册URI处理函数,通过调用httpd_register_uri_handler()函数将URI处理函数添加到HTTP服务器的URI处理列表中,可以实现不同URI地址的访问和处理;
+
+4. 注册基本认证机制,如果开启了CONFIG_EXAMPLE_BASIC_AUTH宏定义,则通过httpd_register_basic_auth()函数在HTTPD服务器上注册基本的用户名密码验证机制。
+
+
+这里的uri_get、uri_post、都是处理URI请求的处理函数,分别对应不同的URI地址。httpd_handle_t类型是HTTPD服务器的句柄类型,可以用于维护HTTPD服务器的状态等信息.
+
+### 二、注册 URI 处理程序
+
+下面对上面提到的路由处理函数进行分析:
+
+#### get方法
+
+注册get路由:
+
+```c
+/* 注册路由 */
+static const httpd_uri_t hello = {
+ .uri = "/hello",
+ .method = HTTP_GET,
+ .handler = hello_get_handler,
+ /* Let's pass response string in user
+ * context to demonstrate it's usage */
+ .user_ctx = "Hello World!"};
+```
+
+这段代码是一个基于ESP32的HTTP服务器程序中的URI处理函数,对应的URI地址为/hello。具体来说:
+
+1. .uri表示了要处理的URI地址,即当客户端请求该地址时,调用相应的处理函数进行处理;
+2. .method表示了该URI地址所支持的HTTP请求方法,本例中为HTTP_GET,即只支持GET请求;
+3. .handler表示了处理函数,当客户端发起请求时,调用该处理函数进行响应;
+4. .user_ctx是一个指向用户数据的指针,允许在处理函数中使用该指针来存储一些用户自定义的数据,这里将"Hello World!"传递给处理函数作为响应内容。
+
+在这个例子中,当客户端通过GET方法请求URI地址/hello时,会调用hello_get_handler函数进行处理,函数的实现可以根据需求自行编写。
+
+```c
+/* URI 处理函数,在客户端发起 GET /hello 请求时被调用 */
+
+/* An HTTP GET handler */
+static esp_err_t hello_get_handler(httpd_req_t *req)
+{
+ char *buf;
+ size_t buf_len;
+
+ /* Get header value string length and allocate memory for length + 1,
+ * extra byte for null termination */
+ buf_len = httpd_req_get_hdr_value_len(req, "Host") + 1;
+ if (buf_len > 1)
+ {
+ buf = malloc(buf_len);
+ ESP_RETURN_ON_FALSE(buf, ESP_ERR_NO_MEM, TAG, "buffer alloc failed");
+ /* Copy null terminated value string into buffer */
+ if (httpd_req_get_hdr_value_str(req, "Host", buf, buf_len) == ESP_OK)
+ {
+ ESP_LOGI(TAG, "Found header => Host: %s", buf);
+ }
+ free(buf);
+ }
+
+ buf_len = httpd_req_get_hdr_value_len(req, "Test-Header-2") + 1;
+ if (buf_len > 1)
+ {
+ buf = malloc(buf_len);
+ ESP_RETURN_ON_FALSE(buf, ESP_ERR_NO_MEM, TAG, "buffer alloc failed");
+ if (httpd_req_get_hdr_value_str(req, "Test-Header-2", buf, buf_len) == ESP_OK)
+ {
+ ESP_LOGI(TAG, "Found header => Test-Header-2: %s", buf);
+ }
+ free(buf);
+ }
+
+ buf_len = httpd_req_get_hdr_value_len(req, "Test-Header-1") + 1;
+ if (buf_len > 1)
+ {
+ buf = malloc(buf_len);
+ ESP_RETURN_ON_FALSE(buf, ESP_ERR_NO_MEM, TAG, "buffer alloc failed");
+ if (httpd_req_get_hdr_value_str(req, "Test-Header-1", buf, buf_len) == ESP_OK)
+ {
+ ESP_LOGI(TAG, "Found header => Test-Header-1: %s", buf);
+ }
+ free(buf);
+ }
+
+ /* Read URL query string length and allocate memory for length + 1,
+ * extra byte for null termination */
+ buf_len = httpd_req_get_url_query_len(req) + 1;
+ if (buf_len > 1)
+ {
+ buf = malloc(buf_len);
+ ESP_RETURN_ON_FALSE(buf, ESP_ERR_NO_MEM, TAG, "buffer alloc failed");
+ if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK)
+ {
+ ESP_LOGI(TAG, "Found URL query => %s", buf);
+ char param[EXAMPLE_HTTP_QUERY_KEY_MAX_LEN], dec_param[EXAMPLE_HTTP_QUERY_KEY_MAX_LEN] = {0};
+ /* Get value of expected key from query string */
+ if (httpd_query_key_value(buf, "query1", param, sizeof(param)) == ESP_OK)
+ {
+ ESP_LOGI(TAG, "Found URL query parameter => query1=%s", param);
+ example_uri_decode(dec_param, param, strnlen(param, EXAMPLE_HTTP_QUERY_KEY_MAX_LEN));
+ ESP_LOGI(TAG, "Decoded query parameter => %s", dec_param);
+ }
+ if (httpd_query_key_value(buf, "query3", param, sizeof(param)) == ESP_OK)
+ {
+ ESP_LOGI(TAG, "Found URL query parameter => query3=%s", param);
+ example_uri_decode(dec_param, param, strnlen(param, EXAMPLE_HTTP_QUERY_KEY_MAX_LEN));
+ ESP_LOGI(TAG, "Decoded query parameter => %s", dec_param);
+ }
+ if (httpd_query_key_value(buf, "query2", param, sizeof(param)) == ESP_OK)
+ {
+ ESP_LOGI(TAG, "Found URL query parameter => query2=%s", param);
+ example_uri_decode(dec_param, param, strnlen(param, EXAMPLE_HTTP_QUERY_KEY_MAX_LEN));
+ ESP_LOGI(TAG, "Decoded query parameter => %s", dec_param);
+ }
+ }
+ free(buf);
+ }
+
+ /* Set some custom headers */
+ httpd_resp_set_hdr(req, "Custom-Header-1", "Custom-Value-1");
+ httpd_resp_set_hdr(req, "Custom-Header-2", "Custom-Value-2");
+
+ /* Send response with custom headers and body set as the
+ * string passed in user context*/
+ const char *resp_str = (const char *)req->user_ctx;
+ httpd_resp_send(req, resp_str, HTTPD_RESP_USE_STRLEN);
+
+ /* After sending the HTTP response the old HTTP request
+ * headers are lost. Check if HTTP request headers can be read now. */
+ if (httpd_req_get_hdr_value_len(req, "Host") == 0)
+ {
+ ESP_LOGI(TAG, "Request headers lost");
+ }
+ return ESP_OK;
+}
+
+
+```
+上面这段代码是ESP32的HTTP服务器程序中的处理GET请求的函数,对应的URI地址为/hello。具体来说:
+
+1. 首先定义了一个名为hello_get_handler的函数,该函数接收一个指向httpd_req_t类型的指针参数req,表示HTTP请求的结构体实例;
+2. 函数中通过httpd_req_get_hdr_value_len和httpd_req_get_hdr_value_str等函数获取HTTP请求头中特定字段的值,比如Host、Test-Header-1、Test-Header-2等;
+3. 接着通过httpd_req_get_url_query_len和httpd_req_get_url_query_str等函数获取URL查询字符串,并从中解析出特定键值对的值,比如query1、query2、query3等;
+4. 通过httpd_resp_set_hdr设置一些自定义的响应头;
+5. 最后调用httpd_resp_send发送HTTP响应,响应内容为req->user_ctx所指向的字符串,即在URI处理函数中传入的"Hello World!"。
+
+>代码很多,但实际并不复杂运行效果如下:
+
+
+
+
+
+get参数解析:
+
+
+
+
+
+
+我们还可以使用get返回一个网页,用于数据交互:
+
+新增一个HTML网页的GET路由:
+
+```c
+const httpd_uri_t html = {
+ .uri = "/html",
+ .method = HTTP_GET,
+ .handler = html_get_handler,
+ .user_ctx = NULL};
+```
+
+注册:
+```c
+ httpd_register_uri_handler(server, &html);
+```
+
+`html_get_handler`函数:
+
+```c
+//定义HTML页面
+char form_html[] = R"(
+
+
+
+ESP32 Web Server
+
+
+ESP32 Web Server
+
+
+
+)";
+
+static esp_err_t html_get_handler(httpd_req_t *req)
+{
+ // 指向 HTML 页面字符串的指针
+ const char *html_content = form_html;
+
+ // 设置 Content-Type 头
+ httpd_resp_set_type(req, "text/html");
+
+ // 发送 HTML 页面作为 HTTP 响应的正文部分
+ httpd_resp_send(req, html_content, strlen(html_content));
+
+ return ESP_OK;
+}
+
+```
+
+访问效果如图所示:
+
+
+
+#### POST方法
+
+我们接着刚才写好的表单,发起一个psot请求
+
+
+这里的URL的IP需要填写自己ESP32联网分配到的IP。这样我们点击提交表单就可以直接发起POST请求。
+
+注册post路由:
+
+```c
+const httpd_uri_t echo = {
+ .uri = "/echo",
+ .method = HTTP_POST,
+ .handler = echo_post_handler,
+ .user_ctx = NULL};
+```
+
+post处理方法:
+
+```c
+/* An HTTP POST handler */
+static esp_err_t echo_post_handler(httpd_req_t *req)
+{
+ /* 定义 HTTP POST 请求数据的目标缓存区
+ * httpd_req_recv() 只接收 char* 数据,但也可以是
+ * 任意二进制数据(需要类型转换)
+ * 对于字符串数据,null 终止符会被省略,
+ * content_len 会给出字符串的长度 */
+ char buf[100];
+ int ret, remaining = req->content_len;
+
+ while (remaining > 0)
+ {
+ /* Read the data for the request */
+ if ((ret = httpd_req_recv(req, buf,
+ MIN(remaining, sizeof(buf)))) <= 0)
+ {
+ if (ret == HTTPD_SOCK_ERR_TIMEOUT)
+ {
+ /* Retry receiving if timeout occurred */
+ continue;
+ }
+ /* 如果发生了错误,返回 ESP_FAIL 可以确保
+ * 底层套接字被关闭 */
+ return ESP_FAIL;
+ }
+
+ /* Send back the same data */
+ httpd_resp_send_chunk(req, buf, ret);
+ remaining -= ret;
+
+ /* Log data received */
+ ESP_LOGI(TAG, "=========== RECEIVED DATA ==========");
+ ESP_LOGI(TAG, "%.*s", ret, buf);
+ ESP_LOGI(TAG, "====================================");
+ }
+
+ // End response
+ httpd_resp_send_chunk(req, NULL, 0);
+ return ESP_OK;
+}
+```
+
+效果如下:
+
+在表单填写内容:
+
+
+
+提交:
+
+
+
+数据回显,POST请求验证完成。
+
+>PS:如果出现下面的报错:Header fields are too long
+>
+>需要在配置文件里修改:
+>
+
+#### PUT方法
+
+>put用于修改数据
+
+注册路由:
+
+```c
+const httpd_uri_t ctrl = {
+ .uri = "/ctrl",
+ .method = HTTP_PUT,
+ .handler = ctrl_put_handler,
+ .user_ctx = NULL};
+
+```
+
+路由处理:
+
+```c
+esp_err_t ctrl_put_handler(httpd_req_t *req)
+{
+ char buf;
+ int ret;
+
+ if ((ret = httpd_req_recv(req, &buf, 1)) <= 0)
+ {
+ if (ret == HTTPD_SOCK_ERR_TIMEOUT)
+ {
+ httpd_resp_send_408(req);
+ }
+ return ESP_FAIL;
+ }
+
+ if (buf == '0')
+ {
+ /* URI handlers can be unregistered using the uri string */
+ ESP_LOGI(TAG, "Unregistering /hello and /echo URIs");
+ httpd_unregister_uri(req->handle, "/hello");
+ httpd_unregister_uri(req->handle, "/echo");
+ /* Register the custom error handler */
+ httpd_register_err_handler(req->handle, HTTPD_404_NOT_FOUND, http_404_error_handler);
+ }
+ else
+ {
+ ESP_LOGI(TAG, "Registering /hello and /echo URIs");
+ httpd_register_uri_handler(req->handle, &hello);
+ httpd_register_uri_handler(req->handle, &echo);
+ /* Unregister custom error handler */
+ httpd_register_err_handler(req->handle, HTTPD_404_NOT_FOUND, NULL);
+ }
+
+ /* Respond with empty body */
+ httpd_resp_send(req, NULL, 0);
+ return ESP_OK;
+}
+
+
+```
+
+1. 从请求中接收一个字节的数据到 `buf` 中。
+2. 如果接收失败或超时,则返回 HTTP 408 请求超时错误。
+3. 如果接收成功且接收的数据为 '0',则取消注册 "/hello" 和 "/echo" 的 URI 处理器,并注册自定义的 404 错误处理函数 `http_404_error_handler`。
+4. 如果接收的数据不为 '0',则注册 "/hello" 和 "/echo" 的 URI 处理器,并取消注册自定义的 404 错误处理函数。
+5. 最后,回复空的 HTTP 响应体并返回 ESP_OK。
+
+
+测试效果:
+
+使用下面的js代码在浏览器控制台发送PUT请求:
+```js
+var xhr = new XMLHttpRequest();
+xhr.open("PUT","http://192.168.137.55/ctrl",true);
+xhr.send("0");
+```
+
+
+
+
+
+
+这时hello路由就被取消注册了:
+
+
+
+
+发送1,hello被重新注册:
+
+
+
+### 三、停止服务器,释放资源
+
+```c
+/* 停止 Web 服务器的函数 */
+void stop_webserver(httpd_handle_t server)
+{
+ if (server) {
+ /* 停止 httpd server */
+ httpd_stop(server);
+ }
+}
+```
+
+## 三、示例
+
+
+>下面是本节全部代码,效果上面展示过,就不再演示
+
+```c
+#include
+#include
+#include "freertos/FreeRTOS.h"
+#include "freertos/event_groups.h"
+#include "esp_wifi.h"
+#include "esp_log.h"
+#include "esp_event.h"
+#include
+#include "nvs_flash.h"
+#include "esp_mac.h"
+#include "esp_netif.h"
+#include
+#include "esp_tls_crypto.h"
+#include
+#include "esp_tls.h"
+#include "esp_check.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include "esp_netif.h"
+#include "esp_tls_crypto.h"
+#include
+#include "esp_event.h"
+#include "esp_netif.h"
+#include "esp_tls.h"
+#include "esp_check.h"
+
+// 要连接的WIFI
+#define ESP_WIFI_STA_SSID "duruofu_win10"
+#define ESP_WIFI_STA_PASSWD "1234567890"
+
+static const char *TAG = "main";
+
+#define EXAMPLE_HTTP_QUERY_KEY_MAX_LEN (64)
+
+char form_html[] = R"(
+
+
+
+ESP32 Web Server
+
+
+ESP32 Web Server
+
+
+)";
+
+// WIFI回调
+void WIFI_CallBack(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
+{
+ static uint8_t connect_count = 0;
+ // WIFI 启动成功
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_START");
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ // WIFI 连接失败
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED");
+ connect_count++;
+ if (connect_count < 6)
+ {
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ else
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED 10 times");
+ }
+ }
+ // WIFI 连接成功(获取到了IP)
+ if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_GOT_IP");
+ ip_event_got_ip_t *info = (ip_event_got_ip_t *)event_data;
+ ESP_LOGI("WIFI_EVENT", "got ip:" IPSTR "", IP2STR(&info->ip_info.ip));
+ }
+}
+
+// wifi初始化
+static void wifi_sta_init(void)
+{
+ ESP_ERROR_CHECK(esp_netif_init());
+
+ // 注册事件(wifi启动成功)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_START, WIFI_CallBack, NULL, NULL));
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, WIFI_CallBack, NULL, NULL));
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, WIFI_CallBack, NULL, NULL));
+
+ // 初始化STA设备
+ esp_netif_create_default_wifi_sta();
+
+ /*Initialize WiFi */
+ wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+ // WIFI_INIT_CONFIG_DEFAULT 是一个默认配置的宏
+
+ ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+
+ //----------------配置阶段-------------------
+ // 初始化WIFI设备( 为 WiFi 驱动初始化 WiFi 分配资源,如 WiFi 控制结构、RX/TX 缓冲区、WiFi NVS 结构等,这个 WiFi 也启动 WiFi 任务。必须先调用此API,然后才能调用所有其他WiFi API)
+ ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
+
+ // STA详细配置
+ wifi_config_t sta_config = {
+ .sta = {
+ .ssid = ESP_WIFI_STA_SSID,
+ .password = ESP_WIFI_STA_PASSWD,
+ .bssid_set = false,
+ },
+ };
+ ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config));
+
+ //----------------启动阶段-------------------
+ ESP_ERROR_CHECK(esp_wifi_start());
+
+ //----------------配置省电模式-------------------
+ // 不省电(数据传输会更快)
+ ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
+}
+
+
+/*路由处理程序*/
+
+/* An HTTP GET handler */
+static esp_err_t hello_get_handler(httpd_req_t *req)
+{
+ char *buf;
+ size_t buf_len;
+
+ /* Get header value string length and allocate memory for length + 1,
+ * extra byte for null termination */
+ buf_len = httpd_req_get_hdr_value_len(req, "Host") + 1;
+ if (buf_len > 1)
+ {
+ buf = malloc(buf_len);
+ ESP_RETURN_ON_FALSE(buf, ESP_ERR_NO_MEM, TAG, "buffer alloc failed");
+ /* Copy null terminated value string into buffer */
+ if (httpd_req_get_hdr_value_str(req, "Host", buf, buf_len) == ESP_OK)
+ {
+ ESP_LOGI(TAG, "Found header => Host: %s", buf);
+ }
+ free(buf);
+ }
+
+ buf_len = httpd_req_get_hdr_value_len(req, "Test-Header-2") + 1;
+ if (buf_len > 1)
+ {
+ buf = malloc(buf_len);
+ ESP_RETURN_ON_FALSE(buf, ESP_ERR_NO_MEM, TAG, "buffer alloc failed");
+ if (httpd_req_get_hdr_value_str(req, "Test-Header-2", buf, buf_len) == ESP_OK)
+ {
+ ESP_LOGI(TAG, "Found header => Test-Header-2: %s", buf);
+ }
+ free(buf);
+ }
+
+ buf_len = httpd_req_get_hdr_value_len(req, "Test-Header-1") + 1;
+ if (buf_len > 1)
+ {
+ buf = malloc(buf_len);
+ ESP_RETURN_ON_FALSE(buf, ESP_ERR_NO_MEM, TAG, "buffer alloc failed");
+ if (httpd_req_get_hdr_value_str(req, "Test-Header-1", buf, buf_len) == ESP_OK)
+ {
+ ESP_LOGI(TAG, "Found header => Test-Header-1: %s", buf);
+ }
+ free(buf);
+ }
+
+ /* Read URL query string length and allocate memory for length + 1,
+ * extra byte for null termination */
+ buf_len = httpd_req_get_url_query_len(req) + 1;
+ if (buf_len > 1)
+ {
+ buf = malloc(buf_len);
+ ESP_RETURN_ON_FALSE(buf, ESP_ERR_NO_MEM, TAG, "buffer alloc failed");
+ if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK)
+ {
+ ESP_LOGI(TAG, "Found URL query => %s", buf);
+ char param[EXAMPLE_HTTP_QUERY_KEY_MAX_LEN], dec_param[EXAMPLE_HTTP_QUERY_KEY_MAX_LEN] = {0};
+ /* Get value of expected key from query string */
+ if (httpd_query_key_value(buf, "query1", param, sizeof(param)) == ESP_OK)
+ {
+ ESP_LOGI(TAG, "Found URL query parameter => query1=%s", param);
+ //example_uri_decode(dec_param, param, strnlen(param, EXAMPLE_HTTP_QUERY_KEY_MAX_LEN));
+ ESP_LOGI(TAG, "Decoded query parameter => %s", dec_param);
+ }
+ if (httpd_query_key_value(buf, "query2", param, sizeof(param)) == ESP_OK)
+ {
+ ESP_LOGI(TAG, "Found URL query parameter => query2=%s", param);
+ //example_uri_decode(dec_param, param, strnlen(param, EXAMPLE_HTTP_QUERY_KEY_MAX_LEN));
+ ESP_LOGI(TAG, "Decoded query parameter => %s", dec_param);
+ }
+ if (httpd_query_key_value(buf, "query3", param, sizeof(param)) == ESP_OK)
+ {
+ ESP_LOGI(TAG, "Found URL query parameter => query3=%s", param);
+ //example_uri_decode(dec_param, param, strnlen(param, EXAMPLE_HTTP_QUERY_KEY_MAX_LEN));
+ ESP_LOGI(TAG, "Decoded query parameter => %s", dec_param);
+ }
+ }
+ free(buf);
+ }
+
+ /* Set some custom headers */
+ httpd_resp_set_hdr(req, "Custom-Header-1", "Custom-Value-1");
+ httpd_resp_set_hdr(req, "Custom-Header-2", "Custom-Value-2");
+
+ /* Send response with custom headers and body set as the
+ * string passed in user context*/
+ const char *resp_str = (const char *)req->user_ctx;
+ httpd_resp_send(req, resp_str, HTTPD_RESP_USE_STRLEN);
+
+ /* After sending the HTTP response the old HTTP request
+ * headers are lost. Check if HTTP request headers can be read now. */
+ if (httpd_req_get_hdr_value_len(req, "Host") == 0)
+ {
+ ESP_LOGI(TAG, "Request headers lost");
+ }
+ return ESP_OK;
+}
+
+static esp_err_t html_get_handler(httpd_req_t *req)
+{
+ // 指向 HTML 页面字符串的指针
+ const char *html_content = form_html;
+
+ // 设置 Content-Type 头
+ httpd_resp_set_type(req, "text/html");
+
+ // 发送 HTML 页面作为 HTTP 响应的正文部分
+ httpd_resp_send(req, html_content, strlen(html_content));
+
+ return ESP_OK;
+}
+
+/* An HTTP POST handler */
+static esp_err_t echo_post_handler(httpd_req_t *req)
+{
+ /* 定义 HTTP POST 请求数据的目标缓存区
+ * httpd_req_recv() 只接收 char* 数据,但也可以是
+ * 任意二进制数据(需要类型转换)
+ * 对于字符串数据,null 终止符会被省略,
+ * content_len 会给出字符串的长度 */
+ char buf[100];
+ int ret, remaining = req->content_len;
+
+ while (remaining > 0)
+ {
+ /* Read the data for the request */
+ if ((ret = httpd_req_recv(req, buf,
+ MIN(remaining, sizeof(buf)))) <= 0)
+ {
+ if (ret == HTTPD_SOCK_ERR_TIMEOUT)
+ {
+ /* Retry receiving if timeout occurred */
+ continue;
+ }
+ /* 如果发生了错误,返回 ESP_FAIL 可以确保
+ * 底层套接字被关闭 */
+ return ESP_FAIL;
+ }
+
+ /* Send back the same data */
+ httpd_resp_send_chunk(req, buf, ret);
+ remaining -= ret;
+
+ /* Log data received */
+ ESP_LOGI(TAG, "=========== RECEIVED DATA ==========");
+ ESP_LOGI(TAG, "%.*s", ret, buf);
+ ESP_LOGI(TAG, "====================================");
+ }
+
+ // End response
+ httpd_resp_send_chunk(req, NULL, 0);
+ return ESP_OK;
+}
+
+/* 注册路由 */
+const httpd_uri_t hello = {
+ .uri = "/hello",
+ .method = HTTP_GET,
+ .handler = hello_get_handler,
+ .user_ctx = "Hello World!"};
+
+const httpd_uri_t html = {
+ .uri = "/html",
+ .method = HTTP_GET,
+ .handler = html_get_handler,
+ .user_ctx = NULL};
+
+const httpd_uri_t echo = {
+ .uri = "/echo",
+ .method = HTTP_POST,
+ .handler = echo_post_handler,
+ .user_ctx = NULL};
+
+
+/* This handler allows the custom error handling functionality to be
+ * tested from client side. For that, when a PUT request 0 is sent to
+ * URI /ctrl, the /hello and /echo URIs are unregistered and following
+ * custom error handler http_404_error_handler() is registered.
+ * Afterwards, when /hello or /echo is requested, this custom error
+ * handler is invoked which, after sending an error message to client,
+ * either closes the underlying socket (when requested URI is /echo)
+ * or keeps it open (when requested URI is /hello). This allows the
+ * client to infer if the custom error handler is functioning as expected
+ * by observing the socket state.
+ */
+esp_err_t http_404_error_handler(httpd_req_t *req, httpd_err_code_t err)
+{
+ if (strcmp("/hello", req->uri) == 0)
+ {
+ httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "/hello URI is not available");
+ /* Return ESP_OK to keep underlying socket open */
+ return ESP_OK;
+ }
+ else if (strcmp("/echo", req->uri) == 0)
+ {
+ httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "/echo URI is not available");
+ /* Return ESP_FAIL to close underlying socket */
+ return ESP_FAIL;
+ }
+ /* For any other URI send 404 and close socket */
+ httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Some 404 error message");
+ return ESP_FAIL;
+}
+
+/* An HTTP PUT handler. This demonstrates realtime
+ * registration and deregistration of URI handlers
+ */
+esp_err_t ctrl_put_handler(httpd_req_t *req)
+{
+ char buf;
+ int ret;
+
+ if ((ret = httpd_req_recv(req, &buf, 1)) <= 0)
+ {
+ if (ret == HTTPD_SOCK_ERR_TIMEOUT)
+ {
+ httpd_resp_send_408(req);
+ }
+ return ESP_FAIL;
+ }
+
+ if (buf == '0')
+ {
+ /* URI handlers can be unregistered using the uri string */
+ ESP_LOGI(TAG, "Unregistering /hello and /echo URIs");
+ httpd_unregister_uri(req->handle, "/hello");
+ httpd_unregister_uri(req->handle, "/echo");
+ /* Register the custom error handler */
+ httpd_register_err_handler(req->handle, HTTPD_404_NOT_FOUND, http_404_error_handler);
+ }
+ else
+ {
+ ESP_LOGI(TAG, "Registering /hello and /echo URIs");
+ httpd_register_uri_handler(req->handle, &hello);
+ httpd_register_uri_handler(req->handle, &echo);
+ /* Unregister custom error handler */
+ httpd_register_err_handler(req->handle, HTTPD_404_NOT_FOUND, NULL);
+ }
+
+ /* Respond with empty body */
+ httpd_resp_send(req, NULL, 0);
+ return ESP_OK;
+}
+
+
+static const httpd_uri_t ctrl = {
+ .uri = "/ctrl",
+ .method = HTTP_PUT,
+ .handler = ctrl_put_handler,
+ .user_ctx = NULL};
+
+/* 启动 Web 服务器的函数 */
+httpd_handle_t start_webserver(void)
+{
+ /* 生成默认的配置参数 */
+ httpd_config_t config = HTTPD_DEFAULT_CONFIG();
+
+ /* 置空 esp_http_server 的实例句柄 */
+ httpd_handle_t server = NULL;
+
+ ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
+ /* 启动 httpd server */
+ if (httpd_start(&server, &config) == ESP_OK)
+ {
+ /* 注册 URI 处理程序 */
+ ESP_LOGI(TAG, "Registering URI handlers");
+ httpd_register_uri_handler(server, &hello);
+ httpd_register_uri_handler(server, &html);
+ httpd_register_uri_handler(server, &echo);
+ httpd_register_uri_handler(server, &ctrl);
+ }
+ /* 如果服务器启动失败,返回的句柄是 NULL */
+ return server;
+}
+
+
+void app_main(void)
+{
+ static httpd_handle_t server = NULL;
+
+ // Initialize 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());
+ ret = nvs_flash_init();
+ }
+ ESP_ERROR_CHECK(ret);
+
+ // 创建默认事件循环
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+
+ // 配置启动WIFI
+ wifi_sta_init();
+
+ vTaskDelay(3000 / portTICK_PERIOD_MS);
+
+ /* Start the server for the first time */
+ server = start_webserver();
+
+ while (server)
+ {
+ sleep(5);
+ }
+}
+
+
+
+```
+
+
+# 参考链接
+
+1. https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/protocols/esp_http_server.html
+2. https://space.bilibili.com/1338335828
\ No newline at end of file
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240325174743.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240325174743.png
new file mode 100644
index 0000000..9fdb78f
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240325174743.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240325174900.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240325174900.png
new file mode 100644
index 0000000..fcb2dd6
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240325174900.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240325175024.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240325175024.png
new file mode 100644
index 0000000..1528ec8
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240325175024.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240325175043.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240325175043.png
new file mode 100644
index 0000000..eaa0013
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240325175043.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326085636.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326085636.png
new file mode 100644
index 0000000..593fcc8
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326085636.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326090740.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326090740.png
new file mode 100644
index 0000000..23b99ee
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326090740.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326091157.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326091157.png
new file mode 100644
index 0000000..89c7c39
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326091157.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326091216.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326091216.png
new file mode 100644
index 0000000..9e860a0
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326091216.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326091539.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326091539.png
new file mode 100644
index 0000000..e65c873
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326091539.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326091652.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326091652.png
new file mode 100644
index 0000000..6ec5bee
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326091652.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326094235.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326094235.png
new file mode 100644
index 0000000..47c3558
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326094235.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326094304.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326094304.png
new file mode 100644
index 0000000..deaa325
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326094304.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326094552.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326094552.png
new file mode 100644
index 0000000..833219f
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326094552.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326094621.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326094621.png
new file mode 100644
index 0000000..c6883d3
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.3-HTTP协议/HTTP协议(服务端)/attachments/20240326094621.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/WebSocket协议.md b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/WebSocket协议.md
index e69de29..221e8fd 100644
--- a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/WebSocket协议.md
+++ b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/WebSocket协议.md
@@ -0,0 +1,1040 @@
+
+# ESP32网络入门-WebSocket协议
+
+## 一、介绍
+
+### 1.1 WebSocket介绍
+
+> 详细介绍可以参考: [万字长文,一篇吃透WebSocket:概念、原理、易错常识、动手实践](https://cloud.tencent.com/developer/article/1887095)
+
+WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它的主要特点是可以实现实时双向通信,并且可以在客户端和服务器之间传输任意数据,而不受 HTTP 语义限制。
+
+
+
+与传统的 HTTP 请求-响应模式不同,WebSocket 采用了类似握手的方法来建立连接。客户端向服务器发送一个 HTTP 请求头,其中包含 Upgrade、Connection、Sec-WebSocket-Key 等字段,通知服务器要进行连接升级。服务器返回一个 HTTP 响应头,其中也包含 Upgrade、Connection 和 Sec-WebSocket-Accept 等字段,表示已同意升级连接。之后,客户端和服务器之间的数据就可以以帧为单位进行传输,每个帧包含一个信息片段,可以是文本也可以是二进制。
+
+
+
+WebSocket 协议的优点是支持实时通信,具有较低的延迟,可减少网络传输的开销,有助于提高应用程序的性能。常见的应用场景包括聊天应用、在线游戏、实时数据监控等。
+
+### webSocket数据帧格式
+
+WebSocket 数据帧是 WebSocket 协议中传输数据的基本单位,每个帧可以包含一个或多个信息片段(Message Fragment),可以是文本也可以是二进制,由 Header 和 Payload 两部分组成。
+
+
+
+Header 一般包括了以下几个字段:
+1. FIN:1 位,表示当前帧是否为消息的最后一帧,值为 1 表示是,值为 0 表示不是。
+2. RSV1、RSV2、RSV3:各占 1 位,用于扩展协议。通常情况下,这三位都被置为 0。
+3. Opcode:4 位,用于表示消息类型,如 0x1 表示文本消息,0x2 表示二进制消息,0x8 表示关闭连接等。
+4. Mask:1 位,表示负载数据是否被掩码处理过,值为 1 表示被掩码处理,值为 0 表示未被掩码处理。
+5. Payload length:7 位或 7+16 位或 7+64 位,表示负载数据的长度。当值为 0~126 时,表示负载数据的长度就是该值;当值为 127 时,额外有两个字节表示长度,当值大于等于 2^16 时,额外有 4 个字节表示长度。
+如果 Mask 位被设置为 1,则 Header 中还要包含一个 4 字节的掩码码值(Masking Key),用于对负载数据进行反掩码操作。
+Payload 是实际的数据内容,长度由 Header 中的 Payload length 字段表示,可能是文本也可能是二进制。
+在整个 WebSocket 连接过程中,客户端和服务器会相互发送一些数据帧,包括握手请求、握手响应、消息片段等,根据不同的功能要求分别使用不同的 Opcode 来表示。
+
+## 二、使用
+
+### 2.1 服务端
+
+下面我们参考:[ Websocket echo server](https://github.com/espressif/esp-idf/tree/be06a6f5ffe36f9554cfc91fe2036e0fc85fea60/examples/protocols/http_server/ws_echo_server)这个历程来学习WebSocket服务端程序
+
+#### 2.1.1 创建服务实例
+
+```c
+static httpd_handle_t start_webserver(void)
+{
+ httpd_handle_t server = NULL;
+ httpd_config_t config = HTTPD_DEFAULT_CONFIG();
+
+ // Start the httpd server
+ ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
+ if (httpd_start(&server, &config) == ESP_OK)
+ {
+ // Registering the ws handler
+ ESP_LOGI(TAG, "Registering URI handlers");
+ httpd_register_uri_handler(server, &ws);
+ return server;
+ }
+
+ ESP_LOGI(TAG, "Error starting server!");
+ return NULL;
+}
+```
+
+这段代码用于启动 HTTP 服务器。它首先创建一个 HTTP 服务器实例,并使用默认配置进行初始化。然后尝试启动 HTTP 服务器,如果启动成功则打印服务器监听的端口号,并注册 WebSocket 处理器(ws handler)。
+#### 2.1.2 注册路由
+
+```c
+static const httpd_uri_t ws = {
+ .uri = "/ws",
+ .method = HTTP_GET,
+ .handler = echo_handler,
+ .user_ctx = NULL,
+ .is_websocket = true};
+
+```
+#### 2.1.3 路由处理
+
+```c
+/*
+ * This handler echos back the received ws data
+ * and triggers an async send if certain message received
+ */
+static esp_err_t echo_handler(httpd_req_t *req)
+{
+ if (req->method == HTTP_GET)
+ {
+ ESP_LOGI(TAG, "Handshake done, the new connection was opened");
+ return ESP_OK;
+ }
+ httpd_ws_frame_t ws_pkt;
+ uint8_t *buf = NULL;
+ memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
+ ws_pkt.type = HTTPD_WS_TYPE_TEXT;
+ /* Set max_len = 0 to get the frame len */
+ // 读取数据
+ esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0);
+ if (ret != ESP_OK)
+ {
+ ESP_LOGE(TAG, "httpd_ws_recv_frame failed to get frame len with %d", ret);
+ return ret;
+ }
+ ESP_LOGI(TAG, "frame len is %d", ws_pkt.len);
+ if (ws_pkt.len)
+ {
+ /* ws_pkt.len + 1 is for NULL termination as we are expecting a string */
+ 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;
+ /* Set max_len = ws_pkt.len to get the frame payload */
+ 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, "Packet type: %d", ws_pkt.type);
+ if (ws_pkt.type == HTTPD_WS_TYPE_TEXT &&
+ strcmp((char *)ws_pkt.payload, "Trigger async") == 0)
+ {
+ free(buf);
+ return trigger_async_send(req->handle, req);
+ }
+
+ ret = httpd_ws_send_frame(req, &ws_pkt);
+ if (ret != ESP_OK)
+ {
+ ESP_LOGE(TAG, "httpd_ws_send_frame failed with %d", ret);
+ }
+ free(buf);
+ return ret;
+}
+```
+
+这段代码用于处理通过 WebSocket 接收到的数据并作出相应的响应。
+
+1. 当接收到 HTTP GET 请求时,打印日志并返回 ESP_OK,表示握手成功,连接已打开。
+2. 当接收到 WebSocket 数据帧时,首先获取帧的长度,并根据长度分配内存。
+3. 再次接收 WebSocket 数据帧,获取帧的有效载荷(payload),并将其打印出来。
+4. 根据接收到的数据帧类型,如果是文本类型且内容为 "Trigger async",则调用 `trigger_async_send` 函数进行异步发送处理,并释放内存。
+5. 否则,将接收到的数据帧原样返回给客户端,并释放内存。
+
+#### 2.1.4 服务器发送数据
+
+
+```c
+/*
+ * Structure holding server handle
+ * and internal socket fd in order
+ * to use out of request send
+ */
+struct async_resp_arg
+{
+ httpd_handle_t hd;
+ int fd;
+};
+
+/*
+ * async send function, which we put into the httpd work queue
+ */
+static void ws_async_send(void *arg)
+{
+ static const char *data = "Async data";
+ struct async_resp_arg *resp_arg = arg;
+ httpd_handle_t hd = resp_arg->hd;
+ int fd = resp_arg->fd;
+ httpd_ws_frame_t ws_pkt;
+ memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
+ ws_pkt.payload = (uint8_t *)data;
+ ws_pkt.len = strlen(data);
+ ws_pkt.type = HTTPD_WS_TYPE_TEXT;
+
+ httpd_ws_send_frame_async(hd, fd, &ws_pkt);
+ free(resp_arg);
+}
+
+static esp_err_t trigger_async_send(httpd_handle_t handle, httpd_req_t *req)
+{
+ struct async_resp_arg *resp_arg = malloc(sizeof(struct async_resp_arg));
+ if (resp_arg == NULL)
+ {
+ return ESP_ERR_NO_MEM;
+ }
+ resp_arg->hd = req->handle;
+ resp_arg->fd = httpd_req_to_sockfd(req);
+ esp_err_t ret = httpd_queue_work(handle, ws_async_send, resp_arg);
+ if (ret != ESP_OK)
+ {
+ free(resp_arg);
+ }
+ return ret;
+}
+```
+
+异步发送
+```c
+trigger_async_send(req->handle, req);
+```
+
+同步发送:
+```
+httpd_ws_send_frame(req, &ws_pkt);
+```
+
+这两个函数都调用`esp_err_t httpd_ws_send_frame_async(httpd_handle_t hd, int fd, httpd_ws_frame_t *frame)`
+
+`httpd_ws_send_frame_async`的内容如下:
+
+ >了解即可:
+
+```c
+esp_err_t httpd_ws_send_frame_async(httpd_handle_t hd, int fd, httpd_ws_frame_t *frame)
+{
+ if (!frame) {
+ ESP_LOGW(TAG, LOG_FMT("Argument is invalid"));
+ return ESP_ERR_INVALID_ARG;
+ }
+ /* Prepare Tx buffer - maximum length is 14, which includes 2 bytes header, 8 bytes length, 4 bytes mask key */
+ uint8_t tx_len = 0;
+ uint8_t header_buf[10] = {0 };
+ /* Set the `FIN` bit by default if message is not fragmented. Else, set it as per the `final` field */
+ header_buf[0] |= (!frame->fragmented) ? HTTPD_WS_FIN_BIT : (frame->final? HTTPD_WS_FIN_BIT: HTTPD_WS_CONTINUE);
+ header_buf[0] |= frame->type; /* Type (opcode): 4 bits */
+ if (frame->len <= 125) {
+ header_buf[1] = frame->len & 0x7fU; /* Length for 7 bits */
+ tx_len = 2;
+ } else if (frame->len > 125 && frame->len < UINT16_MAX) {
+ header_buf[1] = 126; /* Length for 16 bits */
+ header_buf[2] = (frame->len >> 8U) & 0xffU;
+ header_buf[3] = frame->len & 0xffU;
+ tx_len = 4;
+ } else {
+ header_buf[1] = 127; /* Length for 64 bits */
+ uint8_t shift_idx = sizeof(uint64_t) - 1; /* Shift index starts at 7 */
+ uint64_t len64 = frame->len; /* Raise variable size to make sure we won't shift by more bits
+ * than the length has (to avoid undefined behaviour) */
+ for (int8_t idx = 2; idx <= 9; idx++) {
+ /* Now do shifting (be careful of endianness, i.e. when buffer index is 2, frame length shift index is 7) */
+ header_buf[idx] = (len64 >> (shift_idx * 8)) & 0xffU;
+ shift_idx--;
+ }
+ tx_len = 10;
+ }
+ /* WebSocket server does not required to mask response payload, so leave the MASK bit as 0. */
+ header_buf[1] &= (~HTTPD_WS_MASK_BIT);
+ struct sock_db *sess = httpd_sess_get(hd, fd);
+ if (!sess) {
+ return ESP_ERR_INVALID_ARG;
+ }
+ /* Send off header */
+ if (sess->send_fn(hd, fd, (const char *)header_buf, tx_len, 0) < 0) {
+ ESP_LOGW(TAG, LOG_FMT("Failed to send WS header"));
+ return ESP_FAIL;
+ }
+ /* Send off payload */
+ if(frame->len > 0 && frame->payload != NULL) {
+ if (sess->send_fn(hd, fd, (const char *)frame->payload, frame->len, 0) < 0) {
+ ESP_LOGW(TAG, LOG_FMT("Failed to send WS payload"));
+ return ESP_FAIL;
+ }
+ }
+ return ESP_OK;
+}
+```
+
+
+这是一段 ESP32 上通过 WebSocket 发送帧的代码,该函数会向指定的 WebSocket 连接发送加密后的帧数据。主要实现如下:
+
+1. 首先对 frame 参数进行校验,如果为空则返回 ESP_ERR_INVALID_ARG 错误。
+2. 根据 WebSocket 数据帧格式,设置 Header 的 FIN、Opcode 以及 Payload length 字段。同时根据负载长度的大小决定 Header 的长度,最多为 10 个字节,包括了 2 个字节的 Header,2 个字节的 Payload length,4 个字节的 Masking key(在此代码中未使用,因为 WebSocket Server 不需要对 Payload 进行掩码处理)。
+3. 通过 httpd_sess_get() 函数获取对应 WebSocket 连接的套接字描述符(socket descriptor),若获取失败则返回 ESP_ERR_INVALID_ARG 错误。
+4. 调用 socket 的 send_fn() 函数,将 Header 和 Payload 发送给客户端。
+5. 函数返回 ESP_OK 表示发送数据成功,返回 ESP_FAIL 表示发送数据失败。
+
+### 2.2 客户端
+
+> 5.0以上的IDF里,客户端程序以扩展组件包的形式存在,需要使用组件管理器
+
+使用下面的命令将客户端组件添加到工程:
+
+```
+idf.py add-dependency 'espressif/esp_websocket_client^1.2.2'
+```
+
+文档连接:[ ESP WebSocket 客户端](https://docs.espressif.com/projects/esp-protocols/esp_websocket_client/docs/latest/index.html#esp-websocket-client "Permalink to this headline")
+
+
+```c
+static void log_error_if_nonzero(const char *message, int error_code)
+{
+ if (error_code != 0)
+ {
+ ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code);
+ }
+}
+
+static void shutdown_signaler(TimerHandle_t xTimer)
+{
+ ESP_LOGI(TAG, "No data received for %d seconds, signaling shutdown", NO_DATA_TIMEOUT_SEC);
+ xSemaphoreGive(shutdown_sema);
+}
+
+// WebSocket客户端事件处理
+static void websocket_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
+{
+ esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
+ switch (event_id)
+ {
+ // 连接成功
+ case WEBSOCKET_EVENT_CONNECTED:
+ ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED");
+ break;
+ // 连接断开
+ case WEBSOCKET_EVENT_DISCONNECTED:
+ ESP_LOGI(TAG, "WEBSOCKET_EVENT_DISCONNECTED");
+ log_error_if_nonzero("HTTP status code", data->error_handle.esp_ws_handshake_status_code);
+ if (data->error_handle.error_type == WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT)
+ {
+ log_error_if_nonzero("reported from esp-tls", data->error_handle.esp_tls_last_esp_err);
+ log_error_if_nonzero("reported from tls stack", data->error_handle.esp_tls_stack_err);
+ log_error_if_nonzero("captured as transport's socket errno", data->error_handle.esp_transport_sock_errno);
+ }
+ break;
+ // 收到数据
+ case WEBSOCKET_EVENT_DATA:
+ ESP_LOGI(TAG, "WEBSOCKET_EVENT_DATA");
+ ESP_LOGI(TAG, "Received opcode=%d", data->op_code);
+ if (data->op_code == 0x08 && data->data_len == 2)
+ {
+ ESP_LOGW(TAG, "Received closed message with code=%d", 256 * data->data_ptr[0] + data->data_ptr[1]);
+ }
+ else
+ {
+ ESP_LOGW(TAG, "Received=%.*s\n\n", data->data_len, (char *)data->data_ptr);
+ }
+
+ // If received data contains json structure it succeed to parse
+ cJSON *root = cJSON_Parse(data->data_ptr);
+ if (root)
+ {
+ for (int i = 0; i < cJSON_GetArraySize(root); i++)
+ {
+ cJSON *elem = cJSON_GetArrayItem(root, i);
+ cJSON *id = cJSON_GetObjectItem(elem, "id");
+ cJSON *name = cJSON_GetObjectItem(elem, "name");
+ ESP_LOGW(TAG, "Json={'id': '%s', 'name': '%s'}", id->valuestring, name->valuestring);
+ }
+ cJSON_Delete(root);
+ }
+
+ ESP_LOGW(TAG, "Total payload length=%d, data_len=%d, current payload offset=%d\r\n", data->payload_len, data->data_len, data->payload_offset);
+
+ // 定时器复位
+ xTimerReset(shutdown_signal_timer, portMAX_DELAY);
+ break;
+ // 错误
+ case WEBSOCKET_EVENT_ERROR:
+ ESP_LOGI(TAG, "WEBSOCKET_EVENT_ERROR");
+ log_error_if_nonzero("HTTP status code", data->error_handle.esp_ws_handshake_status_code);
+ if (data->error_handle.error_type == WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT)
+ {
+ log_error_if_nonzero("reported from esp-tls", data->error_handle.esp_tls_last_esp_err);
+ log_error_if_nonzero("reported from tls stack", data->error_handle.esp_tls_stack_err);
+ log_error_if_nonzero("captured as transport's socket errno", data->error_handle.esp_transport_sock_errno);
+ }
+ break;
+ }
+}
+
+// WebSocket客户端
+static void websocket_app_start(void)
+{
+ esp_websocket_client_config_t websocket_cfg = {};
+
+ // 创建定时器
+ shutdown_signal_timer = xTimerCreate("Websocket shutdown timer", NO_DATA_TIMEOUT_SEC * 1000 / portTICK_PERIOD_MS,
+ pdFALSE, NULL, shutdown_signaler);
+ // 创建信号量
+ shutdown_sema = xSemaphoreCreateBinary();
+
+ // 配置目标服务器
+ websocket_cfg.uri = CONFIG_WEBSOCKET_URI;
+
+ ESP_LOGI(TAG, "Connecting to %s...", websocket_cfg.uri);
+
+ // 创建WebSocket客户端
+ esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg);
+ // 注册事件
+ esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, websocket_event_handler, (void *)client);
+ // 启动WebSocket客户端
+ esp_websocket_client_start(client);
+ xTimerStart(shutdown_signal_timer, portMAX_DELAY);
+ char data[32];
+ int i = 0;
+ // 发送5次数据
+ while (i < 5)
+ {
+ if (esp_websocket_client_is_connected(client))
+ {
+ int len = sprintf(data, "hello %04d", i++);
+ ESP_LOGI(TAG, "Sending %s", data);
+ // 发送文本数据
+ esp_websocket_client_send_text(client, data, len, portMAX_DELAY);
+ }
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ }
+
+ ESP_LOGI(TAG, "Sending fragmented message");
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ memset(data, 'a', sizeof(data));
+ esp_websocket_client_send_text_partial(client, data, sizeof(data), portMAX_DELAY);
+ memset(data, 'b', sizeof(data));
+ esp_websocket_client_send_cont_msg(client, data, sizeof(data), portMAX_DELAY);
+ esp_websocket_client_send_fin(client, portMAX_DELAY);
+
+ // 等待信号量
+ xSemaphoreTake(shutdown_sema, portMAX_DELAY);
+ // 关闭WebSocket客户端
+ esp_websocket_client_close(client, portMAX_DELAY);
+ ESP_LOGI(TAG, "Websocket Stopped");
+ // 销毁WebSocket客户端
+ esp_websocket_client_destroy(client);
+}
+
+```
+
+- `websocket_app_start`函数初始化WebSocket客户端配置,并设置要连接的WebSocket服务器的URI。然后使用`esp_websocket_client_init`初始化WebSocket客户端,并使用`esp_websocket_register_events`注册事件处理程序。最后,启动WebSocket客户端。
+- `websocket_event_handler`函数处理各种WebSocket事件,如连接、断开连接、接收数据和错误。它为每个事件记录相关信息,并在接收数据时重置一个定时器以防止由于不活动而关闭连接。
+
+程序流程可以总结为下图(图源:Michael_ee):
+
+
+
+
+## 三、示例
+
+### 3.1 服务端
+```c
+#include
+#include
+#include "freertos/FreeRTOS.h"
+#include "freertos/event_groups.h"
+#include "esp_wifi.h"
+#include "esp_log.h"
+#include "esp_event.h"
+#include "nvs_flash.h"
+#include "esp_mac.h"
+#include "esp_netif.h"
+#include
+#include
+#include "esp_eth.h"
+
+// 要连接的WIFI
+#define ESP_WIFI_STA_SSID "duruofu_win10"
+#define ESP_WIFI_STA_PASSWD "1234567890"
+
+static const char *TAG = "main";
+
+void WIFI_CallBack(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
+{
+ static uint8_t connect_count = 0;
+ // WIFI 启动成功
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_START");
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ // WIFI 连接失败
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED");
+ connect_count++;
+ if (connect_count < 6)
+ {
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ else
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED 10 times");
+ }
+ }
+ // WIFI 连接成功(获取到了IP)
+ if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_GOT_IP");
+ ip_event_got_ip_t *info = (ip_event_got_ip_t *)event_data;
+ ESP_LOGI("WIFI_EVENT", "got ip:" IPSTR "", IP2STR(&info->ip_info.ip));
+ }
+}
+
+// wifi初始化
+static void wifi_sta_init(void)
+{
+ ESP_ERROR_CHECK(esp_netif_init());
+
+ // 注册事件(wifi启动成功)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_START, WIFI_CallBack, NULL, NULL));
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, WIFI_CallBack, NULL, NULL));
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, WIFI_CallBack, NULL, NULL));
+
+ // 初始化STA设备
+ esp_netif_create_default_wifi_sta();
+
+ /*Initialize WiFi */
+ wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+ // WIFI_INIT_CONFIG_DEFAULT 是一个默认配置的宏
+
+ ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+
+ //----------------配置阶段-------------------
+ // 初始化WIFI设备( 为 WiFi 驱动初始化 WiFi 分配资源,如 WiFi 控制结构、RX/TX 缓冲区、WiFi NVS 结构等,这个 WiFi 也启动 WiFi 任务。必须先调用此API,然后才能调用所有其他WiFi API)
+ ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
+
+ // STA详细配置
+ wifi_config_t sta_config = {
+ .sta = {
+ .ssid = ESP_WIFI_STA_SSID,
+ .password = ESP_WIFI_STA_PASSWD,
+ .bssid_set = false,
+ },
+ };
+ ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config));
+
+ //----------------启动阶段-------------------
+ ESP_ERROR_CHECK(esp_wifi_start());
+
+ //----------------配置省电模式-------------------
+ // 不省电(数据传输会更快)
+ ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
+}
+
+/*
+ * Structure holding server handle
+ * and internal socket fd in order
+ * to use out of request send
+ */
+struct async_resp_arg
+{
+ httpd_handle_t hd;
+ int fd;
+};
+
+/*
+ * async send function, which we put into the httpd work queue
+ */
+static void ws_async_send(void *arg)
+{
+ static const char *data = "Async data";
+ struct async_resp_arg *resp_arg = arg;
+ httpd_handle_t hd = resp_arg->hd;
+ int fd = resp_arg->fd;
+ httpd_ws_frame_t ws_pkt;
+ memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
+ ws_pkt.payload = (uint8_t *)data;
+ ws_pkt.len = strlen(data);
+ ws_pkt.type = HTTPD_WS_TYPE_TEXT;
+
+ httpd_ws_send_frame_async(hd, fd, &ws_pkt);
+ free(resp_arg);
+}
+
+static esp_err_t trigger_async_send(httpd_handle_t handle, httpd_req_t *req)
+{
+ struct async_resp_arg *resp_arg = malloc(sizeof(struct async_resp_arg));
+ if (resp_arg == NULL)
+ {
+ return ESP_ERR_NO_MEM;
+ }
+ resp_arg->hd = req->handle;
+ resp_arg->fd = httpd_req_to_sockfd(req);
+ esp_err_t ret = httpd_queue_work(handle, ws_async_send, resp_arg);
+ if (ret != ESP_OK)
+ {
+ free(resp_arg);
+ }
+ return ret;
+}
+
+/*
+ * This handler echos back the received ws data
+ * and triggers an async send if certain message received
+ */
+static esp_err_t echo_handler(httpd_req_t *req)
+{
+ if (req->method == HTTP_GET)
+ {
+ ESP_LOGI(TAG, "Handshake done, the new connection was opened");
+ return ESP_OK;
+ }
+ httpd_ws_frame_t ws_pkt;
+ uint8_t *buf = NULL;
+ memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
+ ws_pkt.type = HTTPD_WS_TYPE_TEXT;
+ /* Set max_len = 0 to get the frame len */
+ // 读取数据
+ esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0);
+ if (ret != ESP_OK)
+ {
+ ESP_LOGE(TAG, "httpd_ws_recv_frame failed to get frame len with %d", ret);
+ return ret;
+ }
+ ESP_LOGI(TAG, "frame len is %d", ws_pkt.len);
+ if (ws_pkt.len)
+ {
+ /* ws_pkt.len + 1 is for NULL termination as we are expecting a string */
+ 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;
+ /* Set max_len = ws_pkt.len to get the frame payload */
+ 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, "Packet type: %d", ws_pkt.type);
+ // 发送数据
+ if (ws_pkt.type == HTTPD_WS_TYPE_TEXT &&
+ strcmp((char *)ws_pkt.payload, "Trigger async") == 0)
+ {
+ free(buf);
+ return trigger_async_send(req->handle, req);
+ }
+
+ ret = httpd_ws_send_frame(req, &ws_pkt);
+ if (ret != ESP_OK)
+ {
+ ESP_LOGE(TAG, "httpd_ws_send_frame failed with %d", ret);
+ }
+ free(buf);
+ return ret;
+}
+
+static const httpd_uri_t ws = {
+ .uri = "/ws",
+ .method = HTTP_GET,
+ .handler = echo_handler,
+ .user_ctx = NULL,
+ .is_websocket = true};
+
+static httpd_handle_t start_webserver(void)
+{
+ httpd_handle_t server = NULL;
+ httpd_config_t config = HTTPD_DEFAULT_CONFIG();
+
+ // Start the httpd server
+ ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
+ if (httpd_start(&server, &config) == ESP_OK)
+ {
+ // Registering the ws handler
+ ESP_LOGI(TAG, "Registering URI handlers");
+ httpd_register_uri_handler(server, &ws);
+ return server;
+ }
+
+ ESP_LOGI(TAG, "Error starting server!");
+ return NULL;
+}
+
+static esp_err_t stop_webserver(httpd_handle_t server)
+{
+ // Stop the httpd server
+ return httpd_stop(server);
+}
+
+void app_main(void)
+{
+ static httpd_handle_t server = NULL;
+
+ // Initialize 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());
+ ret = nvs_flash_init();
+ }
+ ESP_ERROR_CHECK(ret);
+
+ // 创建默认事件循环
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+
+ // 配置启动WIFI
+ wifi_sta_init();
+
+ // 等待wifi连接成功(暂时这样处理)
+ vTaskDelay(5000 / portTICK_PERIOD_MS);
+
+ // 创建WebSocket服务器
+ server = start_webserver();
+}
+
+```
+
+> 注意需要设置项目HTTP组件的下面两项配置
+
+
+
+然后随便打开一个WebSocket测试网站:http://www.jsons.cn/websocket/
+
+
+
+服务器程序正常。
+
+### 3.2 客户端
+
+
+我们使用python编写一个简单的回声服务器程序:
+
+```python
+from flask import Flask
+from flask_sock import Sock
+
+app = Flask(__name__)
+sock = Sock(app)
+
+@sock.route('/')
+def echo(ws):
+ while True:
+ data = ws.receive()
+ ws.send(data)
+
+if __name__ == '__main__':
+ # To run your Flask + WebSocket server in production you can use Gunicorn:
+ # gunicorn -b 0.0.0.0:5000 --workers 4 --threads 100 module:app
+ app.run(host="0.0.0.0", debug=True)
+```
+
+下载依赖:
+```
+pip install flask-sock
+```
+
+运行程序:
+
+
+
+在代码里定义目标服务器(写自己电脑的ip):
+
+```c
+// 目标服务器
+#define CONFIG_WEBSOCKET_URI "ws://192.168.137.1:5000"
+```
+
+```c
+#include
+#include
+#include "freertos/FreeRTOS.h"
+#include "freertos/event_groups.h"
+#include "esp_wifi.h"
+#include "esp_log.h"
+#include "esp_event.h"
+#include "nvs_flash.h"
+#include "esp_mac.h"
+#include "esp_netif.h"
+#include
+#include "esp_eth.h"
+#include "esp_websocket_client.h"
+#include
+
+// 要连接的WIFI
+#define ESP_WIFI_STA_SSID "duruofu_win10"
+#define ESP_WIFI_STA_PASSWD "1234567890"
+
+// 目标服务器
+#define CONFIG_WEBSOCKET_URI "ws://demo.piesocket.com/v3/channel_123?api_key=VCXCEuvhGcBDP7XhiJJUDvR1e1D3eiVjgZ9VRiaV¬ify_self"
+
+#define NO_DATA_TIMEOUT_SEC 5
+
+static const char *TAG = "main";
+
+static TimerHandle_t shutdown_signal_timer;
+static SemaphoreHandle_t shutdown_sema;
+
+void WIFI_CallBack(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
+{
+ static uint8_t connect_count = 0;
+ // WIFI 启动成功
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_START");
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ // WIFI 连接失败
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED");
+ connect_count++;
+ if (connect_count < 6)
+ {
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ else
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED 10 times");
+ }
+ }
+ // WIFI 连接成功(获取到了IP)
+ if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_GOT_IP");
+ ip_event_got_ip_t *info = (ip_event_got_ip_t *)event_data;
+ ESP_LOGI("WIFI_EVENT", "got ip:" IPSTR "", IP2STR(&info->ip_info.ip));
+ }
+}
+
+// wifi初始化
+static void wifi_sta_init(void)
+{
+ ESP_ERROR_CHECK(esp_netif_init());
+
+ // 注册事件(wifi启动成功)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_START, WIFI_CallBack, NULL, NULL));
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, WIFI_CallBack, NULL, NULL));
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, WIFI_CallBack, NULL, NULL));
+
+ // 初始化STA设备
+ esp_netif_create_default_wifi_sta();
+
+ /*Initialize WiFi */
+ wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+ // WIFI_INIT_CONFIG_DEFAULT 是一个默认配置的宏
+
+ ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+
+ //----------------配置阶段-------------------
+ // 初始化WIFI设备( 为 WiFi 驱动初始化 WiFi 分配资源,如 WiFi 控制结构、RX/TX 缓冲区、WiFi NVS 结构等,这个 WiFi 也启动 WiFi 任务。必须先调用此API,然后才能调用所有其他WiFi API)
+ ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
+
+ // STA详细配置
+ wifi_config_t sta_config = {
+ .sta = {
+ .ssid = ESP_WIFI_STA_SSID,
+ .password = ESP_WIFI_STA_PASSWD,
+ .bssid_set = false,
+ },
+ };
+ ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config));
+
+ //----------------启动阶段-------------------
+ ESP_ERROR_CHECK(esp_wifi_start());
+
+ //----------------配置省电模式-------------------
+ // 不省电(数据传输会更快)
+ ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
+}
+
+static void log_error_if_nonzero(const char *message, int error_code)
+{
+ if (error_code != 0)
+ {
+ ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code);
+ }
+}
+
+static void shutdown_signaler(TimerHandle_t xTimer)
+{
+ ESP_LOGI(TAG, "No data received for %d seconds, signaling shutdown", NO_DATA_TIMEOUT_SEC);
+ xSemaphoreGive(shutdown_sema);
+}
+
+// WebSocket客户端事件处理
+static void websocket_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
+{
+ esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
+ switch (event_id)
+ {
+ // 连接成功
+ case WEBSOCKET_EVENT_CONNECTED:
+ ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED");
+ break;
+ // 连接断开
+ case WEBSOCKET_EVENT_DISCONNECTED:
+ ESP_LOGI(TAG, "WEBSOCKET_EVENT_DISCONNECTED");
+ log_error_if_nonzero("HTTP status code", data->error_handle.esp_ws_handshake_status_code);
+ if (data->error_handle.error_type == WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT)
+ {
+ log_error_if_nonzero("reported from esp-tls", data->error_handle.esp_tls_last_esp_err);
+ log_error_if_nonzero("reported from tls stack", data->error_handle.esp_tls_stack_err);
+ log_error_if_nonzero("captured as transport's socket errno", data->error_handle.esp_transport_sock_errno);
+ }
+ break;
+ // 收到数据
+ case WEBSOCKET_EVENT_DATA:
+ ESP_LOGI(TAG, "WEBSOCKET_EVENT_DATA");
+ ESP_LOGI(TAG, "Received opcode=%d", data->op_code);
+ if (data->op_code == 0x08 && data->data_len == 2)
+ {
+ ESP_LOGW(TAG, "Received closed message with code=%d", 256 * data->data_ptr[0] + data->data_ptr[1]);
+ }
+ else
+ {
+ ESP_LOGW(TAG, "Received=%.*s\n\n", data->data_len, (char *)data->data_ptr);
+ }
+
+ // If received data contains json structure it succeed to parse
+ cJSON *root = cJSON_Parse(data->data_ptr);
+ if (root)
+ {
+ for (int i = 0; i < cJSON_GetArraySize(root); i++)
+ {
+ cJSON *elem = cJSON_GetArrayItem(root, i);
+ cJSON *id = cJSON_GetObjectItem(elem, "id");
+ cJSON *name = cJSON_GetObjectItem(elem, "name");
+ ESP_LOGW(TAG, "Json={'id': '%s', 'name': '%s'}", id->valuestring, name->valuestring);
+ }
+ cJSON_Delete(root);
+ }
+
+ ESP_LOGW(TAG, "Total payload length=%d, data_len=%d, current payload offset=%d\r\n", data->payload_len, data->data_len, data->payload_offset);
+
+ // 定时器复位
+ xTimerReset(shutdown_signal_timer, portMAX_DELAY);
+ break;
+ // 错误
+ case WEBSOCKET_EVENT_ERROR:
+ ESP_LOGI(TAG, "WEBSOCKET_EVENT_ERROR");
+ log_error_if_nonzero("HTTP status code", data->error_handle.esp_ws_handshake_status_code);
+ if (data->error_handle.error_type == WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT)
+ {
+ log_error_if_nonzero("reported from esp-tls", data->error_handle.esp_tls_last_esp_err);
+ log_error_if_nonzero("reported from tls stack", data->error_handle.esp_tls_stack_err);
+ log_error_if_nonzero("captured as transport's socket errno", data->error_handle.esp_transport_sock_errno);
+ }
+ break;
+ }
+}
+
+// WebSocket客户端
+static void websocket_app_start(void)
+{
+ esp_websocket_client_config_t websocket_cfg = {};
+
+ // 创建定时器
+ shutdown_signal_timer = xTimerCreate("Websocket shutdown timer", NO_DATA_TIMEOUT_SEC * 1000 / portTICK_PERIOD_MS,
+ pdFALSE, NULL, shutdown_signaler);
+ // 创建信号量
+ shutdown_sema = xSemaphoreCreateBinary();
+
+ // 配置目标服务器
+ websocket_cfg.uri = CONFIG_WEBSOCKET_URI;
+
+ ESP_LOGI(TAG, "Connecting to %s...", websocket_cfg.uri);
+
+ // 创建WebSocket客户端
+ esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg);
+ // 注册事件
+ esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, websocket_event_handler, (void *)client);
+ // 启动WebSocket客户端
+ esp_websocket_client_start(client);
+ xTimerStart(shutdown_signal_timer, portMAX_DELAY);
+ char data[32];
+ int i = 0;
+ // 发送5次数据
+ while (i < 5)
+ {
+ if (esp_websocket_client_is_connected(client))
+ {
+ int len = sprintf(data, "hello %04d", i++);
+ ESP_LOGI(TAG, "Sending %s", data);
+ // 发送文本数据
+ esp_websocket_client_send_text(client, data, len, portMAX_DELAY);
+ }
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ }
+
+ ESP_LOGI(TAG, "Sending fragmented message");
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ memset(data, 'a', sizeof(data));
+ esp_websocket_client_send_text_partial(client, data, sizeof(data), portMAX_DELAY);
+ memset(data, 'b', sizeof(data));
+ esp_websocket_client_send_cont_msg(client, data, sizeof(data), portMAX_DELAY);
+ esp_websocket_client_send_fin(client, portMAX_DELAY);
+
+ // 等待信号量
+ xSemaphoreTake(shutdown_sema, portMAX_DELAY);
+ // 关闭WebSocket客户端
+ esp_websocket_client_close(client, portMAX_DELAY);
+ ESP_LOGI(TAG, "Websocket Stopped");
+ // 销毁WebSocket客户端
+ esp_websocket_client_destroy(client);
+}
+
+void app_main(void)
+{
+
+ // Initialize 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());
+ ret = nvs_flash_init();
+ }
+ ESP_ERROR_CHECK(ret);
+
+ // 创建默认事件循环
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+
+ // 配置启动WIFI
+ wifi_sta_init();
+
+ // 等待wifi连接成功(暂时这样处理)
+ vTaskDelay(5000 / portTICK_PERIOD_MS);
+
+ // 创建WebSocket客户端
+ websocket_app_start();
+}
+
+```
+
+效果:
+
+
+
+成功收到回显数据,客户端程序测试完成
+
+# 参考链接
+
+1. https://space.bilibili.com/1338335828
+2. https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/protocols/esp_http_server.html?highlight=websocket
+3. https://docs.espressif.com/projects/esp-protocols/esp_websocket_client/docs/latest/index.html#
\ No newline at end of file
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240326100058.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240326100058.png
new file mode 100644
index 0000000..2d171fd
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240326100058.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240326100128.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240326100128.png
new file mode 100644
index 0000000..7276868
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240326100128.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240326100219.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240326100219.png
new file mode 100644
index 0000000..baa6678
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240326100219.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240327095321.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240327095321.png
new file mode 100644
index 0000000..872c9ba
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240327095321.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240327095439.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240327095439.png
new file mode 100644
index 0000000..2ef3d1c
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240327095439.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240327102246.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240327102246.png
new file mode 100644
index 0000000..b38f56f
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240327102246.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240327111836.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240327111836.png
new file mode 100644
index 0000000..fc21dc5
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240327111836.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240327112254.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240327112254.png
new file mode 100644
index 0000000..df794b9
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.4-WebSocket协议/attachments/20240327112254.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.5-MQTT协议/MQTT协议应用.md b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.5-MQTT协议/MQTT协议应用.md
index e69de29..838fc9e 100644
--- a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.5-MQTT协议/MQTT协议应用.md
+++ b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.5-MQTT协议/MQTT协议应用.md
@@ -0,0 +1,450 @@
+
+
+# ESP32网络入门-MQTT协议
+
+
+## 一、介绍
+
+> MQTT 与 HTTP 一样,MQTT 运行在传输控制协议/互联网协议 (TCP/IP) 堆栈之上。
+### 1.1 MQTT介绍
+
+> [MQTT官网](https://mqtt.org/)
+> [MQTT 协议入门](https://www.emqx.com/zh/blog/the-easiest-guide-to-getting-started-with-mqtt)
+
+MQTT(Message Queuing Telemetry Transport)是一种轻量级的、基于发布-订阅模式的通信协议,主要应用于物联网(IoT)领域中设备间的通信。
+
+
+
+MQTT 协议采用了简单的二进制编码格式,使得它适用于网络带宽较小、延迟较高、网络不稳定的环境下进行通信。同时,MQTT 提供了 QoS(Quality of Service)服务质量保证机制,支持三种不同的 QoS 等级:
+
+- QoS 0:最多发送一次消息,不提供可靠性保证。
+- QoS 1:至少发送一次消息,确保消息能到达接收方,但可能会重复。
+- QoS 2:恰好发送一次消息,确保消息能到达接收方且只接收一次。
+
+MQTT 协议的特点包括:可扩展性好、开销小、易于实现、开源、支持多种编程语言等。
+在 MQTT 协议中,存在两个主要的参与者:发布者和订阅者。发布者将消息发布到一个或多个主题(Topic)中,订阅者可以订阅感兴趣的主题,从而接收到发布者发送的消息。主题可以看作是某一个特定类型的消息的分类标准。
+MQTT 协议的使用场景包括但不限于:智能家居、智能灯光、智能安防、农业物联网、工业物联网等。
+
+##### 优点
+
+1. 轻巧高效: MQTT 客户端非常小,需要的资源最少,因此可以在小型微控制器上使用。MQTT 消息头很小,可以优化网络带宽。
+2. 双向通信:MQTT 允许在设备到云和云到设备之间进行消息传递。这使得向事物组广播消息变得容易。
+3. 扩展到数百万个事物:MQTT 可以扩展以连接数百万个物联网设备。
+4. 可靠的消息传递:消息传递的可靠性对于许多物联网用例都很重要。这就是为什么 MQTT 有 3 个定义的服务质量级别:0 - 最多一次,1 - 至少一次,2 - 恰好一次
+5. 支持不可靠的网络:许多物联网设备通过不可靠的蜂窝网络连接。MQTT 对持久会话的支持减少了将客户端与代理重新连接的时间。
+6. 已启用安全性 :MQTT 使使用 TLS 加密消息和使用现代身份验证协议(如 OAuth)对客户端进行身份验证变得容易。
+
+### 1.2 MQTT 数据包结构
+
+- `固定头(Fixed header)`,存在于所有`MQTT`数据包中,表示数据包类型及数据包的分组类标识;
+- `可变头(Variable header)`,存在于部分`MQTT`数据包中,数据包类型决定了可变头是否存在及其具体内容;
+- `消息体(Payload)`,存在于部分`MQTT`数据包中,表示客户端收到的具体内容;
+
+ 
+
+### 1.3 MQTT 其他概念
+
+**一、订阅(Subscription)**
+
+订阅包含主题筛选器(Topic Filter)和最大服务质量(QoS)。订阅会与一个会话(Session)关联。一个会话可以包含多个订阅。每一个会话中的每个订阅都有一个不同的主题筛选器。
+
+**二、会话(Session)**
+
+每个客户端与服务器建立连接后就是一个会话,客户端和服务器之间有状态交互。会话存在于一个网络之间,也可能在客户端和服务器之间跨越多个连续的网络连接。
+
+**三、主题名(Topic Name)**
+
+连接到一个应用程序消息的标签,该标签与服务器的订阅相匹配。服务器会将消息发送给订阅所匹配标签的每个客户端。
+
+**四、主题筛选器(Topic Filter)**
+
+一个对主题名通配符筛选器,在订阅表达式中使用,表示订阅所匹配到的多个主题。
+
+**五、负载(Payload)**
+
+消息订阅者所具体接收的内容。
+
+
+详细内容可以查看MQTT协议文档:[MQTT Version 5.0](extension://bfdogplmndidlpjfhoijckpakkdjkkil/pdf/viewer.html?file=https%3A%2F%2Fdocs.oasis-open.org%2Fmqtt%2Fmqtt%2Fv5.0%2Fmqtt-v5.0.pdf)
+
+### 1.4 ESP32的MQTT支持
+
+>https://docs.espressif.com/projects/esp-idf/zh_CN/v5.1.3/esp32/api-reference/protocols/mqtt.html
+
+- 支持基于 TCP 的 MQTT、基于 Mbed TLS 的 SSL、基于 WebSocket 的 MQTT 以及基于 WebSocket Secure 的 MQTT
+- 通过 URI 简化配置流程
+- 多个实例(一个应用程序中有多个客户端)
+- 支持订阅、发布、认证、遗嘱消息、保持连接心跳机制以及 3 个服务质量 (QoS) 级别(组成全功能客户端)
+
+
+## 二、使用
+
+>示例代码参考:https://github.com/espressif/esp-idf/tree/master/examples/protocols/mqtt/tcp
+
+#### 2.1 创建一个简单的MQTT服务器:
+
+```c
+// MQTT客户端
+static void mqtt_app_start(void)
+{
+ esp_mqtt_client_config_t mqtt_cfg = {
+ .broker.address.uri = CONFIG_BROKER_URL,
+ };
+
+ esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
+ /* The last argument may be used to pass data to the event handler, in this example mqtt_event_handler */
+ // 注册事件处理函数
+ esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
+ // 启动MQTT客户端
+ esp_mqtt_client_start(client);
+}
+```
+
+代码很简单这里就不展开说了。
+
+#### 2.2 事件处理
+
+事件处理部分是重点:
+
+代码:
+```c
+// MQTT客户端事件处理
+/*
+ * @brief Event handler registered to receive MQTT events
+ *
+ * This function is called by the MQTT client event loop.
+ *
+ * @param handler_args user data registered to the event.
+ * @param base Event base for the handler(always MQTT Base in this example).
+ * @param event_id The id for the received event.
+ * @param event_data The data for the event, esp_mqtt_event_handle_t.
+ */
+static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
+{
+ ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%" PRIi32 "", base, event_id);
+ esp_mqtt_event_handle_t event = event_data;
+ esp_mqtt_client_handle_t client = event->client;
+ int msg_id;
+ switch ((esp_mqtt_event_id_t)event_id)
+ {
+ // MQTT连接成功
+ case MQTT_EVENT_CONNECTED:
+ ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
+ // 发布消息
+ msg_id = esp_mqtt_client_publish(client, "/topic/qos1", "data_3", 0, 1, 0);
+ ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
+ // 订阅消息
+ msg_id = esp_mqtt_client_subscribe(client, "/topic/qos0", 0);
+ ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
+ msg_id = esp_mqtt_client_subscribe(client, "/topic/qos1", 1);
+ ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
+ // 取消订阅消息
+ msg_id = esp_mqtt_client_unsubscribe(client, "/topic/qos1");
+ ESP_LOGI(TAG, "sent unsubscribe successful, msg_id=%d", msg_id);
+ break;
+ // MQTT连接断开
+ case MQTT_EVENT_DISCONNECTED:
+ ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
+ break;
+ // MQTT订阅成功
+ case MQTT_EVENT_SUBSCRIBED:
+ ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
+ // 发布消息
+ msg_id = esp_mqtt_client_publish(client, "/topic/qos0", "data", 0, 0, 0);
+ ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
+ break;
+ // MQTT取消订阅成功
+ case MQTT_EVENT_UNSUBSCRIBED:
+ ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
+ break;
+ // MQTT发布成功
+ case MQTT_EVENT_PUBLISHED:
+ ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
+ break;
+ // MQTT收到数据
+ case MQTT_EVENT_DATA:
+ ESP_LOGI(TAG, "MQTT_EVENT_DATA");
+ printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
+ printf("DATA=%.*s\r\n", event->data_len, event->data);
+ break;
+ // MQTT错误
+ case MQTT_EVENT_ERROR:
+ ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
+ if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT)
+ {
+ log_error_if_nonzero("reported from esp-tls", event->error_handle->esp_tls_last_esp_err);
+ log_error_if_nonzero("reported from tls stack", event->error_handle->esp_tls_stack_err);
+ log_error_if_nonzero("captured as transport's socket errno", event->error_handle->esp_transport_sock_errno);
+ ESP_LOGI(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno));
+ }
+ break;
+ default:
+ ESP_LOGI(TAG, "Other event id:%d", event->event_id);
+ break;
+ }
+}
+```
+
+- `MQTT_EVENT_CONNECTED`: 处理MQTT客户端成功连接到代理时的事件。在此情况下,它展示了如何发布、订阅和取消订阅MQTT主题。
+- `MQTT_EVENT_DISCONNECTED`: 处理MQTT客户端与代理断开连接时的事件。
+- `MQTT_EVENT_SUBSCRIBED`: 处理MQTT客户端成功订阅主题时的事件。
+- `MQTT_EVENT_UNSUBSCRIBED`: 处理MQTT客户端成功取消订阅主题时的事件。
+- `MQTT_EVENT_PUBLISHED`: 处理MQTT客户端成功发布消息时的事件。
+- `MQTT_EVENT_DATA`: 处理收到来自代理的数据时的事件。在此情况下,它打印了主题和数据。
+- `MQTT_EVENT_ERROR`: 处理MQTT客户端发生错误时的事件。
+
+## 三、实例
+
+
+这里使用我自己的MQTT服务器测试:`mqtt://www.duruofu.com:1883`
+
+```c
+#include
+#include
+#include
+#include
+#include "freertos/FreeRTOS.h"
+#include "freertos/event_groups.h"
+#include "esp_wifi.h"
+#include "esp_log.h"
+#include "esp_event.h"
+#include "nvs_flash.h"
+#include "esp_mac.h"
+#include "esp_netif.h"
+#include
+#include "esp_eth.h"
+#include "lwip/sockets.h"
+#include "lwip/dns.h"
+#include "lwip/netdb.h"
+#include "mqtt_client.h"
+
+
+// 要连接的WIFI
+#define ESP_WIFI_STA_SSID "duruofu_win10"
+#define ESP_WIFI_STA_PASSWD "1234567890"
+
+static const char *TAG = "main";
+
+
+
+void WIFI_CallBack(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
+{
+ static uint8_t connect_count = 0;
+ // WIFI 启动成功
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_START");
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ // WIFI 连接失败
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED");
+ connect_count++;
+ if (connect_count < 6)
+ {
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ else
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED 10 times");
+ }
+ }
+ // WIFI 连接成功(获取到了IP)
+ if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_GOT_IP");
+ ip_event_got_ip_t *info = (ip_event_got_ip_t *)event_data;
+ ESP_LOGI("WIFI_EVENT", "got ip:" IPSTR "", IP2STR(&info->ip_info.ip));
+ }
+}
+
+// wifi初始化
+static void wifi_sta_init(void)
+{
+ ESP_ERROR_CHECK(esp_netif_init());
+
+ // 注册事件(wifi启动成功)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_START, WIFI_CallBack, NULL, NULL));
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, WIFI_CallBack, NULL, NULL));
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, WIFI_CallBack, NULL, NULL));
+
+ // 初始化STA设备
+ esp_netif_create_default_wifi_sta();
+
+ /*Initialize WiFi */
+ wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+ // WIFI_INIT_CONFIG_DEFAULT 是一个默认配置的宏
+
+ ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+
+ //----------------配置阶段-------------------
+ // 初始化WIFI设备( 为 WiFi 驱动初始化 WiFi 分配资源,如 WiFi 控制结构、RX/TX 缓冲区、WiFi NVS 结构等,这个 WiFi 也启动 WiFi 任务。必须先调用此API,然后才能调用所有其他WiFi API)
+ ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
+
+ // STA详细配置
+ wifi_config_t sta_config = {
+ .sta = {
+ .ssid = ESP_WIFI_STA_SSID,
+ .password = ESP_WIFI_STA_PASSWD,
+ .bssid_set = false,
+ },
+ };
+ ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config));
+
+ //----------------启动阶段-------------------
+ ESP_ERROR_CHECK(esp_wifi_start());
+
+ //----------------配置省电模式-------------------
+ // 不省电(数据传输会更快)
+ ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
+}
+
+static void log_error_if_nonzero(const char *message, int error_code)
+{
+ if (error_code != 0)
+ {
+ ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code);
+ }
+}
+
+
+// MQTT客户端事件处理
+/*
+ * @brief Event handler registered to receive MQTT events
+ *
+ * This function is called by the MQTT client event loop.
+ *
+ * @param handler_args user data registered to the event.
+ * @param base Event base for the handler(always MQTT Base in this example).
+ * @param event_id The id for the received event.
+ * @param event_data The data for the event, esp_mqtt_event_handle_t.
+ */
+static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
+{
+ ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%" PRIi32 "", base, event_id);
+ esp_mqtt_event_handle_t event = event_data;
+ esp_mqtt_client_handle_t client = event->client;
+ int msg_id;
+ switch ((esp_mqtt_event_id_t)event_id)
+ {
+ // MQTT连接成功
+ case MQTT_EVENT_CONNECTED:
+ ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
+ // 发布消息
+ msg_id = esp_mqtt_client_publish(client, "/topic/qos1", "data_3", 0, 1, 0);
+ ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
+ // 订阅消息
+ msg_id = esp_mqtt_client_subscribe(client, "/topic/qos0", 0);
+ ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
+ msg_id = esp_mqtt_client_subscribe(client, "/topic/qos1", 1);
+ ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
+ // 取消订阅消息
+ msg_id = esp_mqtt_client_unsubscribe(client, "/topic/qos1");
+ ESP_LOGI(TAG, "sent unsubscribe successful, msg_id=%d", msg_id);
+ break;
+ // MQTT连接断开
+ case MQTT_EVENT_DISCONNECTED:
+ ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
+ break;
+ // MQTT订阅成功
+ case MQTT_EVENT_SUBSCRIBED:
+ ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
+ // 发布消息
+ msg_id = esp_mqtt_client_publish(client, "/topic/qos0", "data", 0, 0, 0);
+ ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
+ break;
+ // MQTT取消订阅成功
+ case MQTT_EVENT_UNSUBSCRIBED:
+ ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
+ break;
+ // MQTT发布成功
+ case MQTT_EVENT_PUBLISHED:
+ ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
+ break;
+ // MQTT收到数据
+ case MQTT_EVENT_DATA:
+ ESP_LOGI(TAG, "MQTT_EVENT_DATA");
+ printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
+ printf("DATA=%.*s\r\n", event->data_len, event->data);
+ break;
+ // MQTT错误
+ case MQTT_EVENT_ERROR:
+ ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
+ if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT)
+ {
+ log_error_if_nonzero("reported from esp-tls", event->error_handle->esp_tls_last_esp_err);
+ log_error_if_nonzero("reported from tls stack", event->error_handle->esp_tls_stack_err);
+ log_error_if_nonzero("captured as transport's socket errno", event->error_handle->esp_transport_sock_errno);
+ ESP_LOGI(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno));
+ }
+ break;
+ default:
+ ESP_LOGI(TAG, "Other event id:%d", event->event_id);
+ break;
+ }
+}
+
+// MQTT客户端
+static void mqtt_app_start(void)
+{
+ esp_mqtt_client_config_t mqtt_cfg = {
+ .broker.address.uri = "mqtt://www.duruofu.top:1883",
+ };
+
+ esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
+ /* The last argument may be used to pass data to the event handler, in this example mqtt_event_handler */
+ // 注册事件处理函数
+ esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
+ // 启动MQTT客户端
+ esp_mqtt_client_start(client);
+}
+
+void app_main(void)
+{
+
+ // Initialize 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());
+ ret = nvs_flash_init();
+ }
+ ESP_ERROR_CHECK(ret);
+
+ // 创建默认事件循环
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+
+ // 配置启动WIFI
+ wifi_sta_init();
+
+ // 等待wifi连接成功(暂时这样处理)
+ vTaskDelay(5000 / portTICK_PERIOD_MS);
+
+ // 创建MQTT客户端
+ mqtt_app_start();
+}
+
+```
+
+使用MQTTX来充当另一个客户端:
+
+
+
+成功收到消息,如上图
+
+尝试发送数据,ESP会将数据返回:
+
+
+
+
+
+
+这样就完成了简单的MQTT客户端程序。
+# 参考链接
+
+1. https://docs.espressif.com/projects/esp-idf/zh_CN/v5.1.3/esp32/api-reference/protocols/mqtt.html
+2. https://github.com/espressif/esp-idf/blob/master/examples/protocols/mqtt/tcp/main/app_main.c
\ No newline at end of file
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.5-MQTT协议/attachments/20240327113720.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.5-MQTT协议/attachments/20240327113720.png
new file mode 100644
index 0000000..6fe4b7d
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.5-MQTT协议/attachments/20240327113720.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.5-MQTT协议/attachments/20240327114242.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.5-MQTT协议/attachments/20240327114242.png
new file mode 100644
index 0000000..900a9fb
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.5-MQTT协议/attachments/20240327114242.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.5-MQTT协议/attachments/20240327123152.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.5-MQTT协议/attachments/20240327123152.png
new file mode 100644
index 0000000..08ed791
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.5-MQTT协议/attachments/20240327123152.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.5-MQTT协议/attachments/20240327123309.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.5-MQTT协议/attachments/20240327123309.png
new file mode 100644
index 0000000..e863c79
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.5-MQTT协议/attachments/20240327123309.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.5-MQTT协议/attachments/20240327123341.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.5-MQTT协议/attachments/20240327123341.png
new file mode 100644
index 0000000..726fb25
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.5-MQTT协议/attachments/20240327123341.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.6-ESP-NOW协议/ESP-NOW协议应用.md b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.6-ESP-NOW协议/ESP-NOW协议应用.md
index e69de29..b20de55 100644
--- a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.6-ESP-NOW协议/ESP-NOW协议应用.md
+++ b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.6-ESP-NOW协议/ESP-NOW协议应用.md
@@ -0,0 +1,291 @@
+
+# ESP32网络入门-ESP-NOW协议
+
+## 一、概述
+
+
+### 1.1 ESP-NOW介绍
+
+>视频介绍:https://www.espressif.com/sites/default/files/esp-now-zh.mp4
+
+ESP-NOW 是一种由乐鑫公司定义的无连接 Wi-Fi 通信协议。在 ESP-NOW 中,应用程序数据被封装在各个供应商的动作帧中,然后在无连接的情况下,从一个 Wi-Fi 设备传输到另一个 Wi-Fi 设备。 CTR 与 CBC-MAC 协议 (CCMP) 可用来保护动作帧的安全。ESP-NOW 广泛应用于智能照明、远程控制、传感器等领域。
+
+ESP-NOW 是基于数据链路层的无线通信协议,它将五层 OSI 上层协议精简为一层,数据传输时无需依次经过网络层、传输层、会话层、表示层、应用层等复杂的层级,也无需层层增加包头和解包,大大缓解了网络拥挤时因为丢包而导致的卡顿和延迟,拥有更高的响应速度。
+
+
+
+
+### 1.2 ESP-NOW 数据帧格式
+
+> 搬运自官方教程
+
+ESP-NOW 使用各个供应商的动作帧传输数据,默认比特率为 1 Mbps。各个供应商的动作帧格式为:
+
+| MAC 报头 | 分类代码 | 组织标识符 | 随机值 | 供应商特定内容 | FCS |
+| ------ | ---- | ----- | ---- | -------- | ---- |
+| 24 字节 | 1 字节 | 3 字节 | 4 字节 | 7~257 字节 | 4 字节 |
+
+- 分类代码:分类代码字段可用于指示各个供应商的类别(比如 127)。
+- 组织标识符:组织标识符包含一个唯一标识符 (比如 0x18fe34),为乐鑫指定的 MAC 地址的前三个字节。
+- 随机值:防止重放攻击。
+- 供应商特定内容:供应商特定内容包含供应商特定字段,如下所示:
+
+| 元素 ID | 长度 | 组织标识符 | 类型 | 版本 | 正文 |
+| ------- | ---- | ---------- | ---- | ---- | ---- |
+| 1 字节 | 1 字节 | 3 字节 | 1 字节 | 1 字节 | 0~250 字节 |
+
+- 元素 ID:元素 ID 字段可用于指示特定于供应商的元素。
+- 长度:长度是组织标识符、类型、版本和正文的总长度。
+- 组织标识符:组织标识符包含一个唯一标识符 (比如 0x18fe34),为乐鑫指定的 MAC 地址的前三个字节。
+- 类型:类型字段设置为 4,代表 ESP-NOW。
+- 版本:版本字段设置为 ESP-NOW 的版本。
+- 正文:正文包含 ESP-NOW 数据。
+
+由于 ESP-NOW 是无连接的,因此 MAC 报头与标准帧略有不同。FrameControl 字段的 FromDS 和 ToDS 位均为 0。第一个地址字段用于配置目标地址。第二个地址字段用于配置源地址。第三个地址字段用于配置广播地址 (0xff:0xff:0xff:0xff:0xff:0xff)。
+
+## 二、使用
+
+>使用ESP-NOW的基本条件是要知道ESP32的MAC地址
+
+可以使用[base_mac_address](https://github.com/espressif/esp-idf/tree/v5.1.3/examples/system/base_mac_address)历程获取芯片MAC地址:
+
+
+
+### 2.1 初始化
+
+调用 [`esp_now_init()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32c3/api-reference/network/esp_now.html#_CPPv412esp_now_initv "esp_now_init") 初始化 ESP-NOW,调用 [`esp_now_deinit()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32c3/api-reference/network/esp_now.html#_CPPv414esp_now_deinitv "esp_now_deinit") 反初始化 ESP-NOW。ESP-NOW 数据必须在 Wi-Fi 启动后传输,因此建议在初始化 ESP-NOW 之前启动 Wi-Fi,并在反初始化 ESP-NOW 之后停止 Wi-Fi。
+
+当调用 [`esp_now_deinit()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32c3/api-reference/network/esp_now.html#_CPPv414esp_now_deinitv "esp_now_deinit") 时,配对设备的所有信息都将被删除。
+### 2.2 设备配对
+
+在将数据发送到其他设备之前,请先调用 [`esp_now_add_peer()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32c3/api-reference/network/esp_now.html#_CPPv416esp_now_add_peerPK19esp_now_peer_info_t "esp_now_add_peer") 将其添加到配对设备列表中。如果启用了加密,则必须设置 LMK。ESP-NOW 数据可以从 Station 或 SoftAP 接口发送。确保在发送 ESP-NOW 数据之前已启用该接口。
+配对设备的最大数量是 20,其中加密设备的数量不超过 17,默认值是 7。
+### 2.3 发送数据
+
+调用 [`esp_now_send()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32c3/api-reference/network/esp_now.html#_CPPv412esp_now_sendPK7uint8_tPK7uint8_t6size_t "esp_now_send") 发送 ESP-NOW 数据,调用 [`esp_now_register_send_cb()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32c3/api-reference/network/esp_now.html#_CPPv424esp_now_register_send_cb17esp_now_send_cb_t "esp_now_register_send_cb") 注册发送回调函数。如果 MAC 层成功接收到数据,则该函数将返回 ESP_NOW_SEND_SUCCESS 事件。否则,它将返回 ESP_NOW_SEND_FAIL。
+
+如果有大量 ESP-NOW 数据要发送,调用 `esp_now_send()` 时需注意单次发送的数据不能超过 250 字节。请注意,两个 ESP-NOW 数据包的发送间隔太短可能导致回调函数返回混乱。因此,建议在等到上一次回调函数返回 ACK 后再发送下一个 ESP-NOW 数据。
+### 2.4 接收数据
+
+调用 [`esp_now_register_recv_cb()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32c3/api-reference/network/esp_now.html#_CPPv424esp_now_register_recv_cb17esp_now_recv_cb_t "esp_now_register_recv_cb") 注册接收回调函数。当接收 ESP-NOW 数据时,需要调用接收回调函数。接收回调函数也在 Wi-Fi 任务任务中运行。因此,不要在回调函数中执行冗长的操作。 相反,将必要的数据发布到队列,并交给优先级较低的任务处理。
+
+## 三、示例
+
+### 3.1 发送
+
+> 注意:发送需要接收方的MAC地址
+
+```c
+#include
+#include
+#include
+#include
+#include "freertos/FreeRTOS.h"
+#include "freertos/semphr.h"
+#include "freertos/timers.h"
+#include "nvs_flash.h"
+#include "esp_random.h"
+#include "esp_event.h"
+#include "esp_netif.h"
+#include "esp_wifi.h"
+#include "esp_log.h"
+#include "esp_mac.h"
+#include "esp_now.h"
+#include "esp_crc.h"
+
+// 接收方地址
+//static uint8_t s_broadcast_mac[ESP_NOW_ETH_ALEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+static uint8_t s_broadcast_mac[ESP_NOW_ETH_ALEN] = {0x24, 0xdc, 0xc3, 0x9c, 0xeb, 0x18};
+
+static const char *TAG = "espnow";
+
+/* WiFi should start before using ESPNOW */
+static void wifi_init(void)
+{
+ ESP_ERROR_CHECK(esp_netif_init());
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+ wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+ ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+ ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
+ ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
+ ESP_ERROR_CHECK(esp_wifi_start());
+
+}
+
+static void example_espnow_send_cb(const uint8_t *mac_addr, esp_now_send_status_t status)
+{
+ if (status != ESP_NOW_SEND_SUCCESS)
+ {
+ ESP_LOGE(TAG, "Send error");
+ }
+ else
+ {
+ ESP_LOGI(TAG, "Send success");
+ }
+}
+
+// ESPNOW发送任务
+static void espnow_task(void *pvParameter)
+{
+ char send_msg[] = "ESPNOW test!";
+ ESP_LOGI(TAG, "Start sending broadcast data");
+ while (1)
+ {
+ // 发送数据
+ if (esp_now_send(s_broadcast_mac, (uint8_t *)send_msg, sizeof(send_msg)) != ESP_OK)
+ {
+ ESP_LOGE(TAG, "Send error");
+ }
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ }
+}
+
+static esp_err_t espnow_init(void)
+{
+
+ /* Initialize ESPNOW and register sending and receiving callback function. */
+ ESP_ERROR_CHECK(esp_now_init());
+ // 注册发送回调函数
+ ESP_ERROR_CHECK(esp_now_register_send_cb(example_espnow_send_cb));
+ //ESP_ERROR_CHECK(esp_now_register_recv_cb(example_espnow_recv_cb));
+
+ // 将广播对等方信息添加到对等方列表
+ esp_now_peer_info_t *peer = malloc(sizeof(esp_now_peer_info_t));
+ if (peer == NULL)
+ {
+ ESP_LOGE(TAG, "Malloc peer information fail");
+ esp_now_deinit();
+ return ESP_FAIL;
+ }
+ // 初始化对等方信息
+ memset(peer, 0, sizeof(esp_now_peer_info_t));
+ peer->channel = 1;
+ peer->ifidx = ESP_IF_WIFI_STA;
+ peer->encrypt = false;
+ memcpy(peer->peer_addr, s_broadcast_mac, ESP_NOW_ETH_ALEN);
+ // 添加对等方
+ ESP_ERROR_CHECK(esp_now_add_peer(peer));
+ free(peer);
+
+ xTaskCreate(espnow_task, "espnow_task", 2048, NULL, 4, NULL);
+
+ return ESP_OK;
+}
+
+void app_main(void)
+{
+ // Initialize 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());
+ ret = nvs_flash_init();
+ }
+ ESP_ERROR_CHECK(ret);
+
+ // Initialize WiFi
+ wifi_init();
+
+ // Initialize ESPNOW
+ espnow_init();
+}
+
+```
+
+### 3.2 接收
+
+```c
+#include
+#include
+#include
+#include
+#include "freertos/FreeRTOS.h"
+#include "freertos/semphr.h"
+#include "freertos/timers.h"
+#include "nvs_flash.h"
+#include "esp_random.h"
+#include "esp_event.h"
+#include "esp_netif.h"
+#include "esp_wifi.h"
+#include "esp_log.h"
+#include "esp_mac.h"
+#include "esp_now.h"
+#include "esp_crc.h"
+
+// 接收方地址
+// static uint8_t s_broadcast_mac[ESP_NOW_ETH_ALEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+static uint8_t s_broadcast_mac[ESP_NOW_ETH_ALEN] = {0x24, 0xdc, 0xc3, 0x9c, 0xeb, 0x18};
+
+static const char *TAG = "espnow";
+
+/* WiFi should start before using ESPNOW */
+static void wifi_init(void)
+{
+ ESP_ERROR_CHECK(esp_netif_init());
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+ wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+ ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+ ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
+ ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
+ ESP_ERROR_CHECK(esp_wifi_start());
+}
+
+// ESPNOW接收回调函数
+static void example_espnow_recv_cb(const esp_now_recv_info_t *recv_info, const uint8_t *data, int len)
+{
+ ESP_LOGI(TAG, "Receive data from: " MACSTR ", len: %d", MAC2STR(recv_info->src_addr), len);
+ ESP_LOGI(TAG, "Data: %s", data);
+}
+
+// ESPNOW接受数据任务
+static void espnow_task(void *pvParameter)
+{
+ ESP_LOGI(TAG, "Start receiving data");
+ while (1)
+ {
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ }
+}
+
+static esp_err_t espnow_init(void)
+{
+
+ /* Initialize ESPNOW and register sending and receiving callback function. */
+ ESP_ERROR_CHECK(esp_now_init());
+ // 注册接受回调函数
+ //ESP_ERROR_CHECK(esp_now_register_send_cb(example_espnow_send_cb));
+ ESP_ERROR_CHECK(esp_now_register_recv_cb(example_espnow_recv_cb));
+
+ xTaskCreate(espnow_task, "espnow_task", 2048, NULL, 4, NULL);
+
+ return ESP_OK;
+}
+
+void app_main(void)
+{
+ // Initialize 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());
+ ret = nvs_flash_init();
+ }
+ ESP_ERROR_CHECK(ret);
+
+ // Initialize WiFi
+ wifi_init();
+
+ // Initialize ESPNOW
+ espnow_init();
+}
+
+```
+
+效果:
+
+
+
+# 参考链接
+
+1. https://github.com/espressif/esp-idf/blob/v5.1.3/examples/wifi/espnow/main/espnow_example.h
+2. https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32c3/api-reference/network/esp_now.html
\ No newline at end of file
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.6-ESP-NOW协议/attachments/20240327133912.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.6-ESP-NOW协议/attachments/20240327133912.png
new file mode 100644
index 0000000..39ccf0d
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.6-ESP-NOW协议/attachments/20240327133912.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.6-ESP-NOW协议/attachments/20240327141511.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.6-ESP-NOW协议/attachments/20240327141511.png
new file mode 100644
index 0000000..edf72bf
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.6-ESP-NOW协议/attachments/20240327141511.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.6-ESP-NOW协议/attachments/20240327152252.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.6-ESP-NOW协议/attachments/20240327152252.png
new file mode 100644
index 0000000..3f5cf38
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.6-ESP-NOW协议/attachments/20240327152252.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.7-SNTP校时/SNTP校时.md b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.7-SNTP校时/SNTP校时.md
new file mode 100644
index 0000000..af945a7
--- /dev/null
+++ b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.7-SNTP校时/SNTP校时.md
@@ -0,0 +1,260 @@
+
+# ESP32网络入门-SNTP校时
+
+## 一、介绍
+
+SNTP 指 **简单网络时间协议**(Simple Network Time Protocol),一个合格的物联网设备,少不了一个准确的钟。通过SNTP,可以使ESP32设备通过网络校准本地时间。使用起来也非常简单。
+
+## 二、使用
+
+#### 2.1 初始化特定的 SNTP 服务器
+
+要初始化特定的 SNTP 服务器并启动 SNTP 服务,只需创建有特定服务器名称的默认 SNTP 服务器配置,然后调用 [`esp_netif_sntp_init()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32s3/api-reference/network/esp_netif.html#_CPPv419esp_netif_sntp_initPK17esp_sntp_config_t "esp_netif_sntp_init") 注册该服务器并启动 SNTP 服务。
+
+```c
+esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG("pool.ntp.org");
+esp_netif_sntp_init(&config);
+```
+
+#### 2.1 启动 SNTP 服务
+
+```c
+ // Start SNTP
+ esp_netif_sntp_start();
+```
+
+#### 2.3 同步时间
+
+```c
+ // wait for time to be set
+ time_t now = 0;
+ struct tm timeinfo = {0};
+ int retry = 0;
+ const int retry_count = 15;
+ while (esp_netif_sntp_sync_wait(2000 / portTICK_PERIOD_MS) == ESP_ERR_TIMEOUT && ++retry < retry_count)
+ {
+ ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count);
+ }
+ time(&now);
+ localtime_r(&now, &timeinfo);
+
+```
+
+#### 2.4 其他
+
+设置时区:
+
+1. 调用 `setenv()`,将 `TZ` 环境变量根据设备位置设置为正确的值。时间字符串的格式与 [GNU libc 文档](https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html) 中描述的相同(但实现方式不同)。
+2. 调用 `tzset()`,为新的时区更新 C 库的运行数据。
+
+例:
+```c
+// Set timezone to Beijing time
+setenv("TZ", "CST-8", 1); // CST-8 represents China Standard Time (UTC+8)
+tzset();
+```
+
+## 三、示例
+
+```c
+#include
+#include
+#include "freertos/FreeRTOS.h"
+#include "freertos/event_groups.h"
+#include "esp_wifi.h"
+#include "esp_log.h"
+#include "esp_event.h"
+#include "nvs_flash.h"
+#include "esp_mac.h"
+#include "esp_netif.h"
+#include
+#include "esp_netif_sntp.h"
+#include "lwip/ip_addr.h"
+#include "esp_sntp.h"
+
+// 要连接的WIFI
+#define ESP_WIFI_STA_SSID "duruofu"
+#define ESP_WIFI_STA_PASSWD "1234567890"
+
+
+static const char *TAG = "main";
+
+void time_sync_notification_cb(struct timeval *tv)
+{
+ ESP_LOGI(TAG, "Notification of a time synchronization event");
+}
+
+static void print_servers(void)
+{
+ ESP_LOGI(TAG, "List of configured NTP servers:");
+
+ for (uint8_t i = 0; i < SNTP_MAX_SERVERS; ++i)
+ {
+ if (esp_sntp_getservername(i))
+ {
+ ESP_LOGI(TAG, "server %d: %s", i, esp_sntp_getservername(i));
+ }
+ else
+ {
+ // we have either IPv4 or IPv6 address, let's print it
+ char buff[INET6_ADDRSTRLEN];
+ ip_addr_t const *ip = esp_sntp_getserver(i);
+ if (ipaddr_ntoa_r(ip, buff, INET6_ADDRSTRLEN) != NULL)
+ ESP_LOGI(TAG, "server %d: %s", i, buff);
+ }
+ }
+}
+
+static void obtain_time(void)
+{
+ // Set timezone to Beijing time
+ setenv("TZ", "CST-8", 1); // CST-8 represents China Standard Time (UTC+8)
+ tzset();
+
+ ESP_LOGI(TAG, "Initializing SNTP");
+ esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG("cn.pool.ntp.org");
+ config.sync_cb = time_sync_notification_cb;
+ config.renew_servers_after_new_IP = true,
+
+ // Initialize SNTP with the provided configuration
+ esp_netif_sntp_init(&config);
+
+ // Start SNTP
+ esp_netif_sntp_start();
+
+ ESP_LOGI(TAG, "Initializing and starting SNTP");
+
+ print_servers();
+
+ // wait for time to be set
+ time_t now = 0;
+ struct tm timeinfo = {0};
+ int retry = 0;
+ const int retry_count = 15;
+ while (esp_netif_sntp_sync_wait(2000 / portTICK_PERIOD_MS) == ESP_ERR_TIMEOUT && ++retry < retry_count)
+ {
+ ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count);
+ }
+ time(&now);
+ localtime_r(&now, &timeinfo);
+
+
+
+ // Print current time
+ char strftime_buf[64];
+ strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
+ ESP_LOGI(TAG, "Current time: %s", strftime_buf);
+}
+
+
+void WIFI_CallBack(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
+{
+ static uint8_t connect_count = 0;
+ // WIFI 启动成功
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_START");
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ // WIFI 连接失败
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED");
+ connect_count++;
+ if (connect_count < 6)
+ {
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ }
+ else
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED 10 times");
+ }
+ }
+ // WIFI 连接成功(获取到了IP)
+ if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
+ {
+ ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_GOT_IP");
+ ip_event_got_ip_t *info = (ip_event_got_ip_t *)event_data;
+ ESP_LOGI("WIFI_EVENT", "got ip:" IPSTR "", IP2STR(&info->ip_info.ip));
+ // 更新时间
+ obtain_time();
+ }
+}
+
+// wifi初始化
+static void wifi_sta_init(void)
+{
+ ESP_ERROR_CHECK(esp_netif_init());
+
+ // 注册事件(wifi启动成功)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_START, WIFI_CallBack, NULL, NULL));
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, WIFI_CallBack, NULL, NULL));
+ // 注册事件(wifi连接失败)
+ ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, WIFI_CallBack, NULL, NULL));
+
+ // 初始化STA设备
+ esp_netif_create_default_wifi_sta();
+
+ /*Initialize WiFi */
+ wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+ // WIFI_INIT_CONFIG_DEFAULT 是一个默认配置的宏
+
+ ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+
+ //----------------配置阶段-------------------
+ // 初始化WIFI设备( 为 WiFi 驱动初始化 WiFi 分配资源,如 WiFi 控制结构、RX/TX 缓冲区、WiFi NVS 结构等,这个 WiFi 也启动 WiFi 任务。必须先调用此API,然后才能调用所有其他WiFi API)
+ ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
+
+ // STA详细配置
+ wifi_config_t sta_config = {
+ .sta = {
+ .ssid = ESP_WIFI_STA_SSID,
+ .password = ESP_WIFI_STA_PASSWD,
+ .bssid_set = false,
+ },
+ };
+ ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config));
+
+ //----------------启动阶段-------------------
+ ESP_ERROR_CHECK(esp_wifi_start());
+
+ //----------------配置省电模式-------------------
+ // 不省电(数据传输会更快)
+ ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
+}
+
+
+void app_main(void)
+{
+ // Initialize 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());
+ ret = nvs_flash_init();
+ }
+ ESP_ERROR_CHECK(ret);
+
+ // 创建默认事件循环
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+
+ // 配置启动WIFI
+ wifi_sta_init();
+
+ // 使用SNTP校准时间
+
+}
+
+
+```
+
+效果:
+
+
+
+# 参考链接
+
+1. https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32s3/api-reference/system/system_time.html?highlight=sntp
+2. https://github.com/espressif/esp-idf/blob/5a40bb8746633477c07ff9a3e90016c37fa0dc0c/examples/protocols/sntp/main/sntp_example_main.c
\ No newline at end of file
diff --git a/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.7-SNTP校时/attachments/20240412152658.png b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.7-SNTP校时/attachments/20240412152658.png
new file mode 100644
index 0000000..2f2ab24
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.2-网络协议应用/6.2.7-SNTP校时/attachments/20240412152658.png differ
diff --git a/docs/06.Wi-Fi功能与相关协议/6.3-Wi-Fi配网技术/6.3.1-BluFi配网/ESP32网络入门-BluFi配网.md b/docs/06.Wi-Fi功能与相关协议/6.3-Wi-Fi配网技术/6.3.1-BluFi配网/ESP32网络入门-BluFi配网.md
new file mode 100644
index 0000000..4adf790
--- /dev/null
+++ b/docs/06.Wi-Fi功能与相关协议/6.3-Wi-Fi配网技术/6.3.1-BluFi配网/ESP32网络入门-BluFi配网.md
@@ -0,0 +1,59 @@
+
+### 说明:
+
+1. 本文档由DuRuofu撰写,由DuRuofu负责解释及执行。
+2. 本文档记录如何使用BluFi完成ESP32的WiFi配网程序。
+
+### 修订历史:
+
+| 文档名称 | 版本 | 作者 | 时间 | 备注 |
+| ----------------- | ------ | ------- | ---------- | ---- |
+| ESP32网络入门-BluFi配网 | v1.0.0 | DuRuofu | 2024-04-03 | 首次建立 |
+
+
+
+# ESP32网络入门-BluFi配网
+
+## 一、介绍
+
+### 1.1 BluFi是什么
+
+BluFi 是一项基于蓝牙通道的 Wi-Fi 网络配置功能,适用于 ESP32。它通过安全协议将 Wi-Fi 的 SSID、密码等配置信息传输到 ESP32。基于这些信息,ESP32 可进而连接到 AP 或建立 SoftAP。
+
+BluFi 流程的关键部分包括数据的分片、加密以及校验和验证。
+
+用户可按需自定义用于对称加密、非对称加密以及校验的算法。此处,我们采用 DH 算法进行密钥协商,128-AES 算法用于数据加密,CRC16 算法用于校验和验证。
+
+
+### 1.2 流程
+
+BluFi 配网流程包含配置 SoftAP 和配置 Station 两部分。
+
+
+
+1. ESP32 开启 GATT Server 模式,发送带有特定 _advertising data_ 的广播。该广播不属于 BluFi Profile,可以按需对其进行自定义。
+2. 使用手机应用程序搜索到该广播后,手机将作为 GATT Client 连接 ESP32。该步骤对具体使用哪款手机应用程序并无特殊要求。
+3. 成功建立 GATT 连接后,手机会向 ESP32 发送数据帧进行密钥协商(详见 [BluFi 中定义的帧格式](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-guides/blufi.html#frame-formats) )。
+4. ESP32 收到密钥协商的数据帧后,会按照自定义的协商方法进行解析。
+5. 手机与 ESP32 进行密钥协商。协商过程可使用 DH/RSA/ECC 等加密算法。
+6. 协商结束后,手机端向 ESP32 发送控制帧,用于设置安全模式。
+7. ESP32 收到控制帧后,使用共享密钥以及安全配置对通信数据进行加密和解密。
+8. 手机向 ESP32 发送 [BluFi 中定义的帧格式](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-guides/blufi.html#frame-formats) 中定义的数据帧,包括 SSID、密码等 Wi-Fi 配置信息。
+9. 手机向 ESP32 发送 Wi-Fi 连接请求的控制帧。ESP32 收到控制帧后,即默认手机已完成必要信息的传输,准备连接 Wi-Fi。
+10. 连接到 Wi-Fi 后,ESP32 发送 Wi-Fi 连接状态报告的控制帧到手机。至此,配网结束。
+
+>1. ESP32 收到安全模式配置的控制帧后,会根据定义的安全模式进行相关操作。
+>2. 进行对称加密和解密时,加密和解密前后的数据长度必须一致。支持原地加密和解密。
+
+
+
+
+
+## 二、使用
+
+
+
+## 三、示例
+
+
+# 参考链接
\ No newline at end of file
diff --git a/docs/06.Wi-Fi功能与相关协议/6.3-Wi-Fi配网技术/6.3.1-BluFi配网/attachments/Pasted image 20240403181235.png b/docs/06.Wi-Fi功能与相关协议/6.3-Wi-Fi配网技术/6.3.1-BluFi配网/attachments/Pasted image 20240403181235.png
new file mode 100644
index 0000000..13f8330
Binary files /dev/null and b/docs/06.Wi-Fi功能与相关协议/6.3-Wi-Fi配网技术/6.3.1-BluFi配网/attachments/Pasted image 20240403181235.png differ