add some code

This commit is contained in:
2025-09-05 13:25:11 +08:00
parent 9ff0a99e7a
commit 3cf1229a85
8911 changed files with 2535396 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
dist/
.component_hash
.idea

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,33 @@
idf_component_register(
SRCS
"src/at_uart.cc"
"src/at_modem.cc"
"src/ec801e/ec801e_at_modem.cc"
"src/ec801e/ec801e_tcp.cc"
"src/ec801e/ec801e_ssl.cc"
"src/ec801e/ec801e_udp.cc"
"src/ec801e/ec801e_mqtt.cc"
"src/ml307/ml307_at_modem.cc"
"src/ml307/ml307_tcp.cc"
"src/ml307/ml307_ssl.cc"
"src/ml307/ml307_mqtt.cc"
"src/ml307/ml307_udp.cc"
"src/ml307/ml307_http.cc"
"src/esp/esp_network.cc"
"src/esp/esp_ssl.cc"
"src/esp/esp_tcp.cc"
"src/esp/esp_mqtt.cc"
"src/esp/esp_udp.cc"
"src/web_socket.cc"
"src/http_client.cc"
INCLUDE_DIRS
"include"
PRIV_INCLUDE_DIRS
"."
REQUIRES
"esp_driver_gpio"
"esp_driver_uart"
"esp-tls"
"pthread"
"mqtt"
)

View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,408 @@
# ML307 / Quectel-E Series Cat.1 AT Modem (v3.0)
这是一个适用于 ML307R / EC801E / NT26K LTE Cat.1 模组的组件。
本项目最初为 https://github.com/78/xiaozhi-esp32 项目创建。
## 🆕 版本 3.0 新特性
- **自动模组检测**: 自动识别 ML307 和 EC801E 模组
- **统一接口**: 通过 `NetworkInterface` 基类提供一致的API
- **智能内存管理**: 使用 `std::unique_ptr` 确保内存安全
- **简化的API**: 更加直观和易用的接口设计
## 功能特性
- AT 命令
- MQTT / MQTTS
- HTTP / HTTPS
- TCP / SSL TCP
- UDP
- WebSocket
- 自动模组检测和初始化
## 支持的模组
- ML307R
- ML307A
- EC801E \*
- NT26K \*
\* 需要在购买时咨询是否已烧录支持 SSL TCP 的固件
## 快速开始
### 基础用法
```cpp
#include "esp_log.h"
#include "at_modem.h"
static const char *TAG = "ML307_DEMO";
extern "C" void app_main(void) {
// 自动检测并初始化模组
auto modem = AtModem::Detect(GPIO_NUM_13, GPIO_NUM_14, GPIO_NUM_15, 921600);
if (!modem) {
ESP_LOGE(TAG, "模组检测失败");
return;
}
// 设置网络状态回调
modem->OnNetworkStateChanged([](bool ready) {
ESP_LOGI(TAG, "网络状态: %s", ready ? "已连接" : "已断开");
});
// 等待网络就绪
NetworkStatus status = modem->WaitForNetworkReady(30000);
if (status != NetworkStatus::Ready) {
ESP_LOGE(TAG, "网络连接失败");
return;
}
// 打印模组信息
ESP_LOGI(TAG, "模组版本: %s", modem->GetModuleRevision().c_str());
ESP_LOGI(TAG, "IMEI: %s", modem->GetImei().c_str());
ESP_LOGI(TAG, "ICCID: %s", modem->GetIccid().c_str());
ESP_LOGI(TAG, "运营商: %s", modem->GetCarrierName().c_str());
ESP_LOGI(TAG, "信号强度: %d", modem->GetCsq());
}
```
### HTTP 客户端
```cpp
void TestHttp(std::unique_ptr<AtModem>& modem) {
ESP_LOGI(TAG, "开始 HTTP 测试");
// 创建 HTTP 客户端
auto http = modem->CreateHttp(0);
// 设置请求头
http->SetHeader("User-Agent", "Xiaozhi/3.0.0");
http->SetTimeout(10000);
// 发送 GET 请求
if (http->Open("GET", "https://httpbin.org/json")) {
ESP_LOGI(TAG, "HTTP 状态码: %d", http->GetStatusCode());
ESP_LOGI(TAG, "响应内容长度: %zu bytes", http->GetBodyLength());
// 读取响应内容
std::string response = http->ReadAll();
ESP_LOGI(TAG, "响应内容: %s", response.c_str());
http->Close();
} else {
ESP_LOGE(TAG, "HTTP 请求失败");
}
// unique_ptr 会自动释放内存,无需手动 delete
}
```
### MQTT 客户端
```cpp
void TestMqtt(std::unique_ptr<AtModem>& modem) {
ESP_LOGI(TAG, "开始 MQTT 测试");
// 创建 MQTT 客户端
auto mqtt = modem->CreateMqtt(0);
// 设置回调函数
mqtt->OnConnected([]() {
ESP_LOGI(TAG, "MQTT 连接成功");
});
mqtt->OnDisconnected([]() {
ESP_LOGI(TAG, "MQTT 连接断开");
});
mqtt->OnMessage([](const std::string& topic, const std::string& payload) {
ESP_LOGI(TAG, "收到消息 [%s]: %s", topic.c_str(), payload.c_str());
});
// 连接到 MQTT 代理
if (mqtt->Connect("broker.emqx.io", 1883, "esp32_client", "", "")) {
// 订阅主题
mqtt->Subscribe("test/esp32/message");
// 发布消息
mqtt->Publish("test/esp32/hello", "Hello from ESP32!");
// 等待一段时间接收消息
vTaskDelay(pdMS_TO_TICKS(5000));
mqtt->Disconnect();
} else {
ESP_LOGE(TAG, "MQTT 连接失败");
}
// unique_ptr 会自动释放内存,无需手动 delete
}
```
### WebSocket 客户端
```cpp
void TestWebSocket(std::unique_ptr<AtModem>& modem) {
ESP_LOGI(TAG, "开始 WebSocket 测试");
// 创建 WebSocket 客户端
auto ws = modem->CreateWebSocket(0);
// 设置请求头
ws->SetHeader("Protocol-Version", "3");
// 设置回调函数
ws->OnConnected([]() {
ESP_LOGI(TAG, "WebSocket 连接成功");
});
ws->OnData([](const char* data, size_t length, bool binary) {
ESP_LOGI(TAG, "收到数据: %.*s", (int)length, data);
});
ws->OnDisconnected([]() {
ESP_LOGI(TAG, "WebSocket 连接断开");
});
ws->OnError([](int error) {
ESP_LOGE(TAG, "WebSocket 错误: %d", error);
});
// 连接到 WebSocket 服务器
if (ws->Connect("wss://echo.websocket.org/")) {
// 发送消息
for (int i = 0; i < 5; i++) {
std::string message = "{\"type\": \"ping\", \"id\": " + std::to_string(i) + "}";
ws->Send(message);
vTaskDelay(pdMS_TO_TICKS(1000));
}
ws->Close();
} else {
ESP_LOGE(TAG, "WebSocket 连接失败");
}
// unique_ptr 会自动释放内存,无需手动 delete
}
```
### TCP 客户端
```cpp
void TestTcp(std::unique_ptr<AtModem>& modem) {
ESP_LOGI(TAG, "开始 TCP 测试");
// 创建 TCP 客户端
auto tcp = modem->CreateTcp(0);
// 设置数据接收回调
tcp->OnStream([](const std::string& data) {
ESP_LOGI(TAG, "TCP 接收数据: %s", data.c_str());
});
// 设置断开连接回调
tcp->OnDisconnected([]() {
ESP_LOGI(TAG, "TCP 连接已断开");
});
if (tcp->Connect("httpbin.org", 80)) {
// 发送 HTTP 请求
std::string request = "GET /ip HTTP/1.1\r\nHost: httpbin.org\r\nConnection: close\r\n\r\n";
int sent = tcp->Send(request);
ESP_LOGI(TAG, "TCP 发送了 %d 字节", sent);
// 等待接收响应(通过回调处理)
vTaskDelay(pdMS_TO_TICKS(3000));
tcp->Disconnect();
} else {
ESP_LOGE(TAG, "TCP 连接失败");
}
// unique_ptr 会自动释放内存,无需手动 delete
}
```
### UDP 客户端
```cpp
void TestUdp(std::unique_ptr<AtModem>& modem) {
ESP_LOGI(TAG, "开始 UDP 测试");
// 创建 UDP 客户端
auto udp = modem->CreateUdp(0);
// 设置数据接收回调
udp->OnMessage([](const std::string& data) {
ESP_LOGI(TAG, "UDP 接收数据: %s", data.c_str());
});
// 连接到 UDP 服务器
if (udp->Connect("8.8.8.8", 53)) {
// 发送简单的测试数据
std::string test_data = "Hello UDP Server!";
int sent = udp->Send(test_data);
ESP_LOGI(TAG, "UDP 发送了 %d 字节", sent);
// 等待接收响应(通过回调处理)
vTaskDelay(pdMS_TO_TICKS(2000));
udp->Disconnect();
} else {
ESP_LOGE(TAG, "UDP 连接失败");
}
// unique_ptr 会自动释放内存,无需手动 delete
}
```
## 高级用法
### 直接访问 AtUart
```cpp
void DirectAtCommand(std::unique_ptr<AtModem>& modem) {
// 获取共享的 AtUart 实例
auto uart = modem->GetAtUart();
// 发送自定义 AT 命令
if (uart->SendCommand("AT+CSQ", 1000)) {
std::string response = uart->GetResponse();
ESP_LOGI(TAG, "信号强度查询结果: %s", response.c_str());
}
// 可以在多个地方安全地持有 uart 引用
std::shared_ptr<AtUart> my_uart = modem->GetAtUart();
// my_uart 可以在其他线程或对象中安全使用
}
```
### 网络状态监控
```cpp
void MonitorNetwork(std::unique_ptr<AtModem>& modem) {
// 监控网络状态变化
modem->OnNetworkStateChanged([&modem](bool ready) {
if (ready) {
ESP_LOGI(TAG, "网络已就绪");
ESP_LOGI(TAG, "信号强度: %d", modem->GetCsq());
auto reg_state = modem->GetRegistrationState();
ESP_LOGI(TAG, "注册状态: %s", reg_state.ToString().c_str());
} else {
ESP_LOGE(TAG, "网络连接丢失");
}
});
// 检查网络状态
if (modem->network_ready()) {
ESP_LOGI(TAG, "当前网络状态: 已连接");
} else {
ESP_LOGI(TAG, "当前网络状态: 未连接");
}
}
```
### 提前释放网络对象
```cpp
void EarlyReleaseExample(std::unique_ptr<AtModem>& modem) {
// 创建 HTTP 客户端
auto http = modem->CreateHttp(0);
// 使用完毕后提前释放
http->Close();
http.reset(); // 显式释放内存
// 或者让 unique_ptr 在作用域结束时自动释放
{
auto tcp = modem->CreateTcp(0);
tcp->Connect("example.com", 80);
// 作用域结束时 tcp 自动释放
}
// 此时 tcp 已经自动释放,可以创建新的连接
auto udp = modem->CreateUdp(0);
// ...
}
```
## 错误处理
```cpp
void HandleErrors(std::unique_ptr<AtModem>& modem) {
// 等待网络就绪,处理各种错误情况
NetworkStatus status = modem->WaitForNetworkReady(30000);
switch (status) {
case NetworkStatus::Ready:
ESP_LOGI(TAG, "网络连接成功");
break;
case NetworkStatus::ErrorInsertPin:
ESP_LOGE(TAG, "SIM 卡未插入或 PIN 码错误");
break;
case NetworkStatus::ErrorRegistrationDenied:
ESP_LOGE(TAG, "网络注册被拒绝");
break;
case NetworkStatus::ErrorTimeout:
ESP_LOGE(TAG, "网络连接超时");
break;
default:
ESP_LOGE(TAG, "未知网络错误");
break;
}
}
```
## 迁移指南 (v2.x → v3.0)
### 旧版本 (v2.x)
```cpp
// 旧方式需要明确指定模组类型和GPIO引脚
Ml307AtModem modem(GPIO_NUM_13, GPIO_NUM_14, GPIO_NUM_15);
NetworkStatus status = modem.WaitForNetworkReady();
Ml307Http http(modem);
http.Open("GET", "https://example.com");
```
### 新版本 (v3.0)
```cpp
// 新方式:自动检测模组类型,使用智能指针管理内存
auto modem = AtModem::Detect(GPIO_NUM_13, GPIO_NUM_14, GPIO_NUM_15);
NetworkStatus status = modem->WaitForNetworkReady();
auto http = modem->CreateHttp(0);
http->Open("GET", "https://example.com");
// 无需手动 deleteunique_ptr 自动管理内存
```
## 架构优势
1. **自动化**: 无需手动指定模组类型,提高代码通用性
2. **统一接口**: 不同模组使用相同的API
3. **代码复用**: 避免重复实现相同功能
4. **易于维护**: 公共逻辑集中管理
5. **扩展性**: 便于添加新的模组类型支持
6. **内存安全**: `std::unique_ptr` 提供自动内存管理,避免内存泄漏
7. **线程安全**: 支持多线程安全访问
8. **RAII 原则**: 资源获取即初始化,作用域结束时自动释放
## 注意事项
1. 构造函数已变化,现在使用 `AtModem::Detect()` 方法
2. 协议客户端需要通过 `CreateXxx()` 方法创建,返回 `std::unique_ptr`
3. **无需手动 delete**`std::unique_ptr` 会自动管理内存
4. 网络状态通过回调函数异步通知
5. `GetAtUart()` 返回 `shared_ptr<AtUart>`,支持安全共享
6. 如果需要提前释放网络对象,可以调用 `.reset()` 方法
7. 所有网络接口方法现在都有默认参数 `connect_id = -1`
## 作者
- 虾哥 Terrence (terrence@tenclass.com)

View File

@@ -0,0 +1,11 @@
dependencies:
idf: '>=5.3'
description: ESP32 ML307 / EC801E / NT26K Cat.1 Cellular Module
files:
exclude:
- .git
- dist
license: MIT
repository: https://github.com/78/esp-ml307
url: https://github.com/78/esp-ml307
version: 3.3.0

View File

@@ -0,0 +1,99 @@
#ifndef _AT_MODEM_H_
#define _AT_MODEM_H_
#include <cstddef>
#include <string>
#include <vector>
#include <list>
#include <functional>
#include <mutex>
#include <memory>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#include <freertos/event_groups.h>
#include <driver/gpio.h>
#include <driver/uart.h>
#include "at_uart.h"
#include "network_interface.h"
#define AT_EVENT_PIN_ERROR BIT2
#define AT_EVENT_NETWORK_ERROR BIT3
#define AT_EVENT_NETWORK_READY BIT4
enum class NetworkStatus {
ErrorInsertPin = -1,
ErrorRegistrationDenied = -2,
ErrorTimeout = -3,
Ready = 0,
Error = 1,
};
struct CeregState {
int stat = 0; // <stat>
std::string tac; // <tac>
std::string ci; // <ci>
int AcT = -1; // <AcT>
std::string ToString() const {
std::string json = "{";
json += "\"stat\":" + std::to_string(stat);
if (!tac.empty()) json += ",\"tac\":\"" + tac + "\"";
if (!ci.empty()) json += ",\"ci\":\"" + ci + "\"";
if (AcT >= 0) json += ",\"AcT\":" + std::to_string(AcT);
json += "}";
return json;
}
};
class AtModem : public NetworkInterface {
public:
// 静态检测方法
static std::unique_ptr<AtModem> Detect(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin = GPIO_NUM_NC, int baud_rate = 115200);
// 构造函数和析构函数
AtModem(std::shared_ptr<AtUart> at_uart);
virtual ~AtModem();
std::shared_ptr<AtUart> GetAtUart() { return at_uart_; }
void OnNetworkStateChanged(std::function<void(bool network_ready)> callback);
// 网络状态管理
virtual void Reboot();
virtual NetworkStatus WaitForNetworkReady(int timeout_ms = -1);
virtual bool SetSleepMode(bool enable, int delay_seconds=0);
virtual void SetFlightMode(bool enable);
// 模组信息获取
std::string GetImei();
std::string GetIccid();
std::string GetModuleRevision();
CeregState GetRegistrationState();
std::string GetCarrierName();
int GetCsq();
// 状态查询
bool pin_ready() const { return pin_ready_; }
bool network_ready() const { return network_ready_; }
protected:
std::shared_ptr<AtUart> at_uart_;
std::mutex mutex_;
std::string iccid_;
std::string imei_;
std::string carrier_name_;
std::string module_revision_;
int csq_ = -1;
bool pin_ready_ = true;
bool network_ready_ = false;
gpio_num_t dtr_pin_;
EventGroupHandle_t event_group_handle_ = nullptr;
CeregState cereg_state_;
virtual void HandleUrc(const std::string& command, const std::vector<AtArgumentValue>& arguments);
std::function<void(bool network_state)> on_network_state_changed_;
};
#endif // _AT_MODEM_H_

View File

@@ -0,0 +1,123 @@
#ifndef _AT_UART_H_
#define _AT_UART_H_
#include <string>
#include <vector>
#include <functional>
#include <mutex>
#include <list>
#include <cstdlib>
#include <memory>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#include <freertos/event_groups.h>
#include <driver/gpio.h>
#include <driver/uart.h>
// UART事件定义
#define AT_EVENT_DATA_AVAILABLE BIT1
#define AT_EVENT_COMMAND_DONE BIT2
#define AT_EVENT_COMMAND_ERROR BIT3
// 默认配置
#define UART_NUM UART_NUM_1
// AT命令参数值结构
struct AtArgumentValue {
enum class Type { String, Int, Double };
Type type;
std::string string_value;
int int_value;
double double_value;
std::string ToString() const {
switch (type) {
case Type::String:
return "\"" + string_value + "\"";
case Type::Int:
return std::to_string(int_value);
case Type::Double:
return std::to_string(double_value);
default:
return "";
}
}
};
// 数据接收回调函数类型
typedef std::function<void(const std::string& command, const std::vector<AtArgumentValue>& arguments)> UrcCallback;
class AtUart {
public:
// 构造函数
AtUart(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin = GPIO_NUM_NC);
~AtUart();
// 初始化和配置
void Initialize();
// 波特率管理
bool SetBaudRate(int new_baud_rate);
int GetBaudRate() const { return baud_rate_; }
// 数据发送
bool SendCommand(const std::string& command, size_t timeout_ms = 1000, bool add_crlf = true);
bool SendCommandWithData(const std::string& command, size_t timeout_ms = 1000, bool add_crlf = true, const char* data = nullptr, size_t data_length = 0);
const std::string& GetResponse() const { return response_; }
int GetCmeErrorCode() const { return cme_error_code_; }
// 回调管理
std::list<UrcCallback>::iterator RegisterUrcCallback(UrcCallback callback);
void UnregisterUrcCallback(std::list<UrcCallback>::iterator iterator);
// 控制接口
void SetDtrPin(bool high);
bool IsInitialized() const { return initialized_; }
std::string EncodeHex(const std::string& data);
std::string DecodeHex(const std::string& data);
void EncodeHexAppend(std::string& dest, const char* data, size_t length);
void DecodeHexAppend(std::string& dest, const char* data, size_t length);
private:
// 配置参数
gpio_num_t tx_pin_;
gpio_num_t rx_pin_;
gpio_num_t dtr_pin_;
uart_port_t uart_num_;
int baud_rate_;
bool initialized_;
int cme_error_code_ = 0;
std::string response_;
bool wait_for_response_ = false;
std::mutex command_mutex_;
std::mutex mutex_;
// FreeRTOS 对象
TaskHandle_t event_task_handle_ = nullptr;
TaskHandle_t receive_task_handle_ = nullptr;
QueueHandle_t event_queue_handle_;
EventGroupHandle_t event_group_handle_;
std::string rx_buffer_;
// 回调函数
std::list<UrcCallback> urc_callbacks_;
// 内部方法
void EventTask();
void ReceiveTask();
bool ParseResponse();
bool DetectBaudRate();
// 处理 AT 命令
void HandleCommand(const char* command);
// 处理 URC
void HandleUrc(const std::string& command, const std::vector<AtArgumentValue>& arguments);
bool SendData(const char* data, size_t length);
// 静态任务函数
static void EventTaskWrapper(void* arg);
};
#endif // _AT_UART_H_

View File

@@ -0,0 +1,19 @@
#ifndef ESP_NETWORK_H
#define ESP_NETWORK_H
#include "network_interface.h"
class EspNetwork : public NetworkInterface {
public:
EspNetwork();
~EspNetwork();
std::unique_ptr<Http> CreateHttp(int connect_id = -1) override;
std::unique_ptr<Tcp> CreateTcp(int connect_id = -1) override;
std::unique_ptr<Tcp> CreateSsl(int connect_id = -1) override;
std::unique_ptr<Udp> CreateUdp(int connect_id = -1) override;
std::unique_ptr<Mqtt> CreateMqtt(int connect_id = -1) override;
std::unique_ptr<WebSocket> CreateWebSocket(int connect_id = -1) override;
};
#endif // ESP_NETWORK_H

View File

@@ -0,0 +1,46 @@
#ifndef HTTP_H
#define HTTP_H
#include <string>
#include <map>
#include <functional>
class Http {
public:
virtual ~Http() = default;
// Set timeout
virtual void SetTimeout(int timeout_ms) = 0;
// 设置 HTTP 请求头
virtual void SetHeader(const std::string& key, const std::string& value) = 0;
// 设置 HTTP Content
virtual void SetContent(std::string&& content) = 0;
// 打开 HTTP 连接并发送请求
virtual bool Open(const std::string& method, const std::string& url) = 0;
// 关闭 HTTP 连接
virtual void Close() = 0;
// 读取 HTTP 响应数据
virtual int Read(char* buffer, size_t buffer_size) = 0;
// 写入 HTTP 请求数据
virtual int Write(const char* buffer, size_t buffer_size) = 0;
// 获取 HTTP 响应状态码
virtual int GetStatusCode() = 0;
// 获取指定 key 的 HTTP 响应头
virtual std::string GetResponseHeader(const std::string& key) const = 0;
// 获取 HTTP 响应体长度
virtual size_t GetBodyLength() = 0;
// 获取 HTTP 响应体
virtual std::string ReadAll() = 0;
};
#endif // HTTP_H

View File

@@ -0,0 +1,156 @@
#ifndef HTTP_CLIENT_H
#define HTTP_CLIENT_H
#include "http.h"
#include "tcp.h"
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <map>
#include <string>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <optional>
#include <memory>
#include <deque>
#include <cstring>
#define EC801E_HTTP_EVENT_HEADERS_RECEIVED (1 << 0)
#define EC801E_HTTP_EVENT_BODY_RECEIVED (1 << 1)
#define EC801E_HTTP_EVENT_ERROR (1 << 2)
#define EC801E_HTTP_EVENT_COMPLETE (1 << 3)
class NetworkInterface;
class HttpClient : public Http {
public:
HttpClient(NetworkInterface* network, int connect_id = 0);
~HttpClient();
void SetTimeout(int timeout_ms) override;
void SetHeader(const std::string& key, const std::string& value) override;
void SetContent(std::string&& content) override;
bool Open(const std::string& method, const std::string& url) override;
void Close() override;
int Read(char* buffer, size_t buffer_size) override;
int Write(const char* buffer, size_t buffer_size) override;
int GetStatusCode() override;
std::string GetResponseHeader(const std::string& key) const override;
size_t GetBodyLength() override;
std::string ReadAll() override;
private:
// 数据块结构,用于队列缓冲
struct DataChunk {
std::string data;
size_t offset = 0; // 当前读取偏移
// 使用移动构造函数避免拷贝
DataChunk(std::string&& d) : data(std::move(d)), offset(0) {}
DataChunk(const std::string& d) : data(d), offset(0) {}
size_t available() const {
return data.size() - offset;
}
size_t read(char* buffer, size_t size) {
size_t bytes_to_read = std::min(size, available());
if (bytes_to_read > 0) {
memcpy(buffer, data.data() + offset, bytes_to_read);
offset += bytes_to_read;
}
return bytes_to_read;
}
bool empty() const {
return offset >= data.size();
}
};
// 头部条目结构体,用于高效存储和查找
struct HeaderEntry {
std::string original_key; // 保留原始大小写的key用于输出HTTP头部
std::string value; // 头部值
HeaderEntry() = default;
HeaderEntry(const std::string& key, const std::string& val)
: original_key(key), value(val) {}
};
NetworkInterface* network_;
int connect_id_;
std::unique_ptr<Tcp> tcp_;
EventGroupHandle_t event_group_handle_;
std::mutex mutex_;
std::condition_variable cv_;
// 用于读取操作的专门锁和缓冲区队列
std::mutex read_mutex_;
std::deque<DataChunk> body_chunks_;
std::condition_variable write_cv_;
const size_t MAX_BODY_CHUNKS_SIZE = 8192;
int status_code_ = -1;
int timeout_ms_ = 30000;
std::string rx_buffer_;
std::map<std::string, HeaderEntry> headers_; // key为小写用于快速查找
std::string url_;
std::string method_;
std::string protocol_;
std::string host_;
std::string path_;
int port_ = 80;
std::optional<std::string> content_ = std::nullopt;
std::map<std::string, HeaderEntry> response_headers_; // key为小写用于快速查找
// 移除原来的 body_ 变量,现在使用 body_chunks_ 队列
size_t body_offset_ = 0;
size_t content_length_ = 0;
size_t total_body_received_ = 0; // 总共接收的响应体字节数
bool eof_ = false;
bool connected_ = false;
bool headers_received_ = false;
bool request_chunked_ = false;
bool response_chunked_ = false;
bool connection_error_ = false; // 新增:标记连接是否异常断开
// HTTP 协议解析状态
enum class ParseState {
STATUS_LINE,
HEADERS,
BODY,
CHUNK_SIZE,
CHUNK_DATA,
CHUNK_TRAILER,
COMPLETE
};
ParseState parse_state_ = ParseState::STATUS_LINE;
size_t chunk_size_ = 0;
size_t chunk_received_ = 0;
// 私有方法
bool ParseUrl(const std::string& url);
std::string BuildHttpRequest();
void OnTcpData(const std::string& data);
void OnTcpDisconnected();
void ProcessReceivedData();
bool ParseStatusLine(const std::string& line);
bool ParseHeaderLine(const std::string& line);
void ParseChunkedBody(const std::string& data);
void ParseRegularBody(const std::string& data);
size_t ParseChunkSize(const std::string& chunk_size_line);
std::string GetNextLine(std::string& buffer);
bool HasCompleteLine(const std::string& buffer);
void SetError();
// 新增:向读取队列添加数据的方法
void AddBodyData(const std::string& data);
void AddBodyData(std::string&& data); // 移动版本
// 新增:检查数据是否完整接收
bool IsDataComplete() const;
};
#endif

View File

@@ -0,0 +1,32 @@
#ifndef MQTT_INTERFACE_H
#define MQTT_INTERFACE_H
#include <string>
#include <functional>
class Mqtt {
public:
virtual ~Mqtt() {}
void SetKeepAlive(int keep_alive_seconds) { keep_alive_seconds_ = keep_alive_seconds; }
virtual bool Connect(const std::string broker_address, int broker_port, const std::string client_id, const std::string username, const std::string password) = 0;
virtual void Disconnect() = 0;
virtual bool Publish(const std::string topic, const std::string payload, int qos = 0) = 0;
virtual bool Subscribe(const std::string topic, int qos = 0) = 0;
virtual bool Unsubscribe(const std::string topic) = 0;
virtual bool IsConnected() = 0;
virtual void OnConnected(std::function<void()> callback) { on_connected_callback_ = std::move(callback); }
virtual void OnDisconnected(std::function<void()> callback) { on_disconnected_callback_ = std::move(callback); }
virtual void OnMessage(std::function<void(const std::string& topic, const std::string& payload)> callback) { on_message_callback_ = std::move(callback); }
virtual void OnError(std::function<void(const std::string& error)> callback) { on_error_callback_ = std::move(callback); }
protected:
int keep_alive_seconds_ = 120;
std::function<void(const std::string& topic, const std::string& payload)> on_message_callback_;
std::function<void()> on_connected_callback_;
std::function<void()> on_disconnected_callback_;
std::function<void(const std::string& error)> on_error_callback_;
};
#endif // MQTT_INTERFACE_H

View File

@@ -0,0 +1,24 @@
#ifndef NETWORK_INTERFACE_H
#define NETWORK_INTERFACE_H
#include <memory>
#include "http.h"
#include "tcp.h"
#include "udp.h"
#include "mqtt.h"
#include "web_socket.h"
class NetworkInterface {
public:
virtual ~NetworkInterface() = default;
// 连接创建接口(纯虚函数,由子类实现)
virtual std::unique_ptr<Http> CreateHttp(int connect_id = -1) = 0;
virtual std::unique_ptr<Tcp> CreateTcp(int connect_id = -1) = 0;
virtual std::unique_ptr<Tcp> CreateSsl(int connect_id = -1) = 0;
virtual std::unique_ptr<Udp> CreateUdp(int connect_id = -1) = 0;
virtual std::unique_ptr<Mqtt> CreateMqtt(int connect_id = -1) = 0;
virtual std::unique_ptr<WebSocket> CreateWebSocket(int connect_id = -1) = 0;
};
#endif // NETWORK_INTERFACE_H

View File

@@ -0,0 +1,34 @@
#ifndef TCP_H
#define TCP_H
#include <string>
#include <functional>
class Tcp {
public:
virtual ~Tcp() = default;
virtual bool Connect(const std::string& host, int port) = 0;
virtual void Disconnect() = 0;
virtual int Send(const std::string& data) = 0;
virtual void OnStream(std::function<void(const std::string& data)> callback) {
stream_callback_ = callback;
}
virtual void OnDisconnected(std::function<void()> callback) {
disconnect_callback_ = callback;
}
// 连接状态查询
bool connected() const { return connected_; }
protected:
std::function<void(const std::string& data)> stream_callback_;
std::function<void()> disconnect_callback_;
// 连接状态管理
bool connected_ = false; // 是否可以正常读写数据
};
#endif // TCP_H

View File

@@ -0,0 +1,25 @@
#ifndef UDP_H
#define UDP_H
#include <string>
#include <functional>
class Udp {
public:
virtual ~Udp() = default;
virtual bool Connect(const std::string& host, int port) = 0;
virtual void Disconnect() = 0;
virtual int Send(const std::string& data) = 0;
virtual void OnMessage(std::function<void(const std::string& data)> callback) {
message_callback_ = std::move(callback);
}
bool connected() const { return connected_; }
protected:
std::function<void(const std::string& data)> message_callback_;
bool connected_ = false;
};
#endif // UDP_H

View File

@@ -0,0 +1,59 @@
#ifndef WEBSOCKET_H
#define WEBSOCKET_H
#include <functional>
#include <string>
#include <map>
#include <thread>
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include "tcp.h"
class NetworkInterface;
class WebSocket {
public:
WebSocket(NetworkInterface* network, int connect_id);
~WebSocket();
void SetHeader(const char* key, const char* value);
void SetReceiveBufferSize(size_t size);
bool IsConnected() const;
bool Connect(const char* uri);
bool Send(const std::string& data);
bool Send(const void* data, size_t len, bool binary = false, bool fin = true);
void Ping();
void Close();
void OnConnected(std::function<void()> callback);
void OnDisconnected(std::function<void()> callback);
void OnData(std::function<void(const char*, size_t, bool binary)> callback);
void OnError(std::function<void(int)> callback);
private:
NetworkInterface* network_;
int connect_id_;
std::unique_ptr<Tcp> tcp_;
bool continuation_ = false;
size_t receive_buffer_size_ = 2048;
std::string receive_buffer_;
bool handshake_completed_ = false;
bool connected_ = false;
// FreeRTOS 事件组用于同步握手
EventGroupHandle_t handshake_event_group_;
static const EventBits_t HANDSHAKE_SUCCESS_BIT = BIT0;
static const EventBits_t HANDSHAKE_FAILED_BIT = BIT1;
std::map<std::string, std::string> headers_;
std::function<void(const char*, size_t, bool binary)> on_data_;
std::function<void(int)> on_error_;
std::function<void()> on_connected_;
std::function<void()> on_disconnected_;
void OnTcpData(const std::string& data);
bool SendControlFrame(uint8_t opcode, const void* data, size_t len);
};
#endif // WEBSOCKET_H

View File

@@ -0,0 +1,207 @@
#include "at_modem.h"
#include "ml307/ml307_at_modem.h"
#include "ec801e/ec801e_at_modem.h"
#include <esp_log.h>
#include <esp_err.h>
#include <sstream>
#include <iomanip>
#include <cstring>
static const char* TAG = "AtModem";
std::unique_ptr<AtModem> AtModem::Detect(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin, int baud_rate) {
// 创建AtUart进行检测
auto uart = std::make_shared<AtUart>(tx_pin, rx_pin, dtr_pin);
uart->Initialize();
// 设置波特率
if (!uart->SetBaudRate(baud_rate)) {
return nullptr;
}
// 发送AT+CGMR或ATI命令获取模组型号
if (!uart->SendCommand("AT+CGMR", 3000)) {
ESP_LOGE(TAG, "Failed to send AT+CGMR command");
return nullptr;
}
std::string response = uart->GetResponse();
ESP_LOGI(TAG, "Detected modem: %s", response.c_str());
// 检查响应中的模组型号
if (response.find("EC801E") == 0) {
return std::make_unique<Ec801EAtModem>(uart);
} else if (response.find("NT26K") == 0) {
return std::make_unique<Ec801EAtModem>(uart);
} else if (response.find("ML307") == 0) {
return std::make_unique<Ml307AtModem>(uart);
} else {
ESP_LOGE(TAG, "Unrecognized modem type: %s, use ML307 AtModem as default", response.c_str());
return std::make_unique<Ml307AtModem>(uart);
}
}
AtModem::AtModem(std::shared_ptr<AtUart> at_uart) : at_uart_(at_uart) {
event_group_handle_ = xEventGroupCreate();
at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
HandleUrc(command, arguments);
});
}
AtModem::~AtModem() {
if (event_group_handle_) {
vEventGroupDelete(event_group_handle_);
}
}
void AtModem::OnNetworkStateChanged(std::function<void(bool network_ready)> callback) {
on_network_state_changed_ = callback;
}
void AtModem::Reboot() {
}
void AtModem::SetFlightMode(bool enable) {
if (enable) {
at_uart_->SendCommand("AT+CFUN=4"); // flight mode
at_uart_->SetDtrPin(enable);
network_ready_ = false;
} else {
at_uart_->SetDtrPin(enable);
at_uart_->SendCommand("AT+CFUN=1"); // normal mode
}
}
bool AtModem::SetSleepMode(bool enable, int delay_seconds) {
return false;
}
NetworkStatus AtModem::WaitForNetworkReady(int timeout_ms) {
ESP_LOGI(TAG, "Waiting for network ready...");
network_ready_ = false;
cereg_state_ = CeregState{};
xEventGroupClearBits(event_group_handle_, AT_EVENT_NETWORK_READY | AT_EVENT_NETWORK_ERROR);
// 检查 SIM 卡是否准备好
for (int i = 0; i < 10; i++) {
if (at_uart_->SendCommand("AT+CPIN?")) {
pin_ready_ = true;
break;
}
if (at_uart_->GetCmeErrorCode() == 10) {
pin_ready_ = false;
return NetworkStatus::ErrorInsertPin;
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
// 检查网络注册状态
if (!at_uart_->SendCommand("AT+CEREG=2")) {
return NetworkStatus::Error;
}
if (!at_uart_->SendCommand("AT+CEREG?")) {
return NetworkStatus::Error;
}
TickType_t timeout = portMAX_DELAY;
if (timeout_ms > 0) {
timeout = pdMS_TO_TICKS(timeout_ms);
}
auto bits = xEventGroupWaitBits(event_group_handle_, AT_EVENT_NETWORK_READY | AT_EVENT_NETWORK_ERROR, pdTRUE, pdFALSE, timeout);
if (bits & AT_EVENT_NETWORK_READY) {
return NetworkStatus::Ready;
} else if (bits & AT_EVENT_NETWORK_ERROR) {
if (cereg_state_.stat == 3) {
return NetworkStatus::ErrorRegistrationDenied;
} else if (!pin_ready_) {
return NetworkStatus::ErrorInsertPin;
} else {
return NetworkStatus::Error;
}
}
return NetworkStatus::ErrorTimeout;
}
std::string AtModem::GetImei() {
if (!imei_.empty()) {
return imei_;
}
at_uart_->SendCommand("AT+CGSN=1");
return imei_;
}
std::string AtModem::GetIccid() {
at_uart_->SendCommand("AT+ICCID");
return iccid_;
}
std::string AtModem::GetModuleRevision() {
if (!module_revision_.empty()) {
return module_revision_;
}
if (at_uart_->SendCommand("AT+CGMR")) {
module_revision_ = at_uart_->GetResponse();
}
return module_revision_;
}
std::string AtModem::GetCarrierName() {
at_uart_->SendCommand("AT+COPS?");
return carrier_name_;
}
int AtModem::GetCsq() {
at_uart_->SendCommand("AT+CSQ", 10);
return csq_;
}
CeregState AtModem::GetRegistrationState() {
at_uart_->SendCommand("AT+CEREG?");
return cereg_state_;
}
void AtModem::HandleUrc(const std::string& command, const std::vector<AtArgumentValue>& arguments) {
if (command == "CGSN" && arguments.size() >= 1) {
imei_ = arguments[0].string_value;
} else if (command == "ICCID" && arguments.size() >= 1) {
iccid_ = arguments[0].string_value;
} else if (command == "COPS" && arguments.size() >= 4) {
carrier_name_ = arguments[2].string_value;
} else if (command == "CSQ" && arguments.size() >= 1) {
csq_ = arguments[0].int_value;
} else if (command == "CEREG" && arguments.size() >= 1) {
cereg_state_ = CeregState{};
if (arguments.size() == 1) {
cereg_state_.stat = 0;
} else if (arguments.size() >= 2) {
int state_index = arguments[1].type == AtArgumentValue::Type::Int ? 1 : 0;
cereg_state_.stat = arguments[state_index].int_value;
if (arguments.size() >= state_index + 2) {
cereg_state_.tac = arguments[state_index + 1].string_value;
cereg_state_.ci = arguments[state_index + 2].string_value;
if (arguments.size() >= state_index + 4) {
cereg_state_.AcT = arguments[state_index + 3].int_value;
}
}
}
bool new_network_ready = cereg_state_.stat == 1 || cereg_state_.stat == 5;
if (new_network_ready != network_ready_) {
network_ready_ = new_network_ready;
if (on_network_state_changed_) {
on_network_state_changed_(new_network_ready);
}
}
if (new_network_ready) {
xEventGroupSetBits(event_group_handle_, AT_EVENT_NETWORK_READY);
} else if (cereg_state_.stat == 3) {
xEventGroupSetBits(event_group_handle_, AT_EVENT_NETWORK_ERROR);
}
} else if (command == "CPIN" && arguments.size() >= 1) {
if (arguments[0].string_value == "READY") {
pin_ready_ = true;
} else {
pin_ready_ = false;
}
}
}

View File

@@ -0,0 +1,395 @@
#include "at_uart.h"
#include <esp_log.h>
#include <esp_err.h>
#include <algorithm>
#include <cstring>
#include <cstdlib>
#include <sstream>
#define TAG "AtUart"
// AtUart 构造函数实现
AtUart::AtUart(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin)
: tx_pin_(tx_pin), rx_pin_(rx_pin), dtr_pin_(dtr_pin), uart_num_(UART_NUM),
baud_rate_(115200), initialized_(false),
event_task_handle_(nullptr), event_queue_handle_(nullptr), event_group_handle_(nullptr) {
}
AtUart::~AtUart() {
if (event_task_handle_) {
vTaskDelete(event_task_handle_);
}
if (event_group_handle_) {
vEventGroupDelete(event_group_handle_);
}
if (initialized_) {
uart_driver_delete(uart_num_);
}
}
void AtUart::Initialize() {
if (initialized_) {
return;
}
event_group_handle_ = xEventGroupCreate();
if (!event_group_handle_) {
ESP_LOGE(TAG, "创建事件组失败");
return;
}
uart_config_t uart_config = {};
uart_config.baud_rate = baud_rate_;
uart_config.data_bits = UART_DATA_8_BITS;
uart_config.parity = UART_PARITY_DISABLE;
uart_config.stop_bits = UART_STOP_BITS_1;
uart_config.source_clk = UART_SCLK_DEFAULT;
ESP_ERROR_CHECK(uart_driver_install(uart_num_, 8192, 0, 100, &event_queue_handle_, ESP_INTR_FLAG_IRAM));
ESP_ERROR_CHECK(uart_param_config(uart_num_, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(uart_num_, tx_pin_, rx_pin_, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
if (dtr_pin_ != GPIO_NUM_NC) {
gpio_config_t config = {};
config.pin_bit_mask = (1ULL << dtr_pin_);
config.mode = GPIO_MODE_OUTPUT;
config.pull_up_en = GPIO_PULLUP_DISABLE;
config.pull_down_en = GPIO_PULLDOWN_DISABLE;
config.intr_type = GPIO_INTR_DISABLE;
gpio_config(&config);
gpio_set_level(dtr_pin_, 0);
}
xTaskCreate([](void* arg) {
auto ml307_at_modem = (AtUart*)arg;
ml307_at_modem->EventTask();
vTaskDelete(NULL);
}, "modem_event", 4096, this, 15, &event_task_handle_);
xTaskCreate([](void* arg) {
auto ml307_at_modem = (AtUart*)arg;
ml307_at_modem->ReceiveTask();
vTaskDelete(NULL);
}, "modem_receive", 4096 * 2, this, 15, &receive_task_handle_);
initialized_ = true;
}
void AtUart::EventTaskWrapper(void* arg) {
auto uart = static_cast<AtUart*>(arg);
uart->EventTask();
vTaskDelete(nullptr);
}
void AtUart::EventTask() {
uart_event_t event;
while (true) {
if (xQueueReceive(event_queue_handle_, &event, portMAX_DELAY) == pdTRUE) {
switch (event.type)
{
case UART_DATA:
xEventGroupSetBits(event_group_handle_, AT_EVENT_DATA_AVAILABLE);
break;
case UART_BREAK:
ESP_LOGI(TAG, "break");
break;
case UART_BUFFER_FULL:
ESP_LOGE(TAG, "buffer full");
break;
case UART_FIFO_OVF:
ESP_LOGE(TAG, "FIFO overflow");
HandleUrc("FIFO_OVERFLOW", {});
break;
default:
ESP_LOGE(TAG, "unknown event type: %d", event.type);
break;
}
}
}
}
void AtUart::ReceiveTask() {
while (true) {
auto bits = xEventGroupWaitBits(event_group_handle_, AT_EVENT_DATA_AVAILABLE, pdTRUE, pdFALSE, portMAX_DELAY);
if (bits & AT_EVENT_DATA_AVAILABLE) {
size_t available;
uart_get_buffered_data_len(uart_num_, &available);
if (available > 0) {
// Extend rx_buffer_ and read into buffer
rx_buffer_.resize(rx_buffer_.size() + available);
char* rx_buffer_ptr = &rx_buffer_[rx_buffer_.size() - available];
uart_read_bytes(uart_num_, rx_buffer_ptr, available, portMAX_DELAY);
while (ParseResponse()) {}
}
}
}
}
static bool is_number(const std::string& s) {
return !s.empty() && std::all_of(s.begin(), s.end(), ::isdigit) && s.length() < 10;
}
bool AtUart::ParseResponse() {
if (wait_for_response_ && rx_buffer_[0] == '>') {
rx_buffer_.erase(0, 1);
xEventGroupSetBits(event_group_handle_, AT_EVENT_COMMAND_DONE);
return true;
}
auto end_pos = rx_buffer_.find("\r\n");
if (end_pos == std::string::npos) {
// FIXME: for +MHTTPURC: "ind", missing newline
if (rx_buffer_.size() >= 16 && memcmp(rx_buffer_.c_str(), "+MHTTPURC: \"ind\"", 16) == 0) {
// Find the end of this line and add \r\n if missing
auto next_plus = rx_buffer_.find("+", 1);
if (next_plus != std::string::npos) {
// Insert \r\n before the next + command
rx_buffer_.insert(next_plus, "\r\n");
} else {
// Append \r\n at the end
rx_buffer_.append("\r\n");
}
end_pos = rx_buffer_.find("\r\n");
} else {
return false;
}
}
// Ignore empty lines
if (end_pos == 0) {
rx_buffer_.erase(0, 2);
return true;
}
ESP_LOGD(TAG, "<< %.64s (%u bytes)", rx_buffer_.substr(0, end_pos).c_str(), end_pos);
// print last 64 bytes before end_pos if available
// if (end_pos > 64) {
// ESP_LOGI(TAG, "<< LAST: %.64s", rx_buffer_.c_str() + end_pos - 64);
// }
// Parse "+CME ERROR: 123,456,789"
if (rx_buffer_[0] == '+') {
std::string command, values;
auto pos = rx_buffer_.find(": ");
if (pos == std::string::npos || pos > end_pos) {
command = rx_buffer_.substr(1, end_pos - 1);
} else {
command = rx_buffer_.substr(1, pos - 1);
values = rx_buffer_.substr(pos + 2, end_pos - pos - 2);
}
rx_buffer_.erase(0, end_pos + 2);
// Parse "string", int, int, ... into AtArgumentValue
std::vector<AtArgumentValue> arguments;
std::istringstream iss(values);
std::string item;
while (std::getline(iss, item, ',')) {
AtArgumentValue argument;
if (item.front() == '"') {
argument.type = AtArgumentValue::Type::String;
argument.string_value = item.substr(1, item.size() - 2);
} else if (item.find(".") != std::string::npos) {
argument.type = AtArgumentValue::Type::Double;
argument.double_value = std::stod(item);
} else if (is_number(item)) {
argument.type = AtArgumentValue::Type::Int;
argument.int_value = std::stoi(item);
argument.string_value = std::move(item);
} else {
argument.type = AtArgumentValue::Type::String;
argument.string_value = std::move(item);
}
arguments.push_back(argument);
}
HandleUrc(command, arguments);
return true;
} else if (rx_buffer_.size() >= 4 && rx_buffer_[0] == 'O' && rx_buffer_[1] == 'K' && rx_buffer_[2] == '\r' && rx_buffer_[3] == '\n') {
rx_buffer_.erase(0, 4);
xEventGroupSetBits(event_group_handle_, AT_EVENT_COMMAND_DONE);
return true;
} else if (rx_buffer_.size() >= 7 && rx_buffer_[0] == 'E' && rx_buffer_[1] == 'R' && rx_buffer_[2] == 'R' && rx_buffer_[3] == 'O' && rx_buffer_[4] == 'R' && rx_buffer_[5] == '\r' && rx_buffer_[6] == '\n') {
rx_buffer_.erase(0, 7);
xEventGroupSetBits(event_group_handle_, AT_EVENT_COMMAND_ERROR);
return true;
} else {
std::lock_guard<std::mutex> lock(mutex_);
response_ = rx_buffer_.substr(0, end_pos);
rx_buffer_.erase(0, end_pos + 2);
return true;
}
return false;
}
void AtUart::HandleCommand(const char* command) {
// 这个函数现在主要用于向后兼容,大部分处理逻辑已经移到 ParseLine 中
if (wait_for_response_) {
response_.append(command);
response_.append("\r\n");
}
}
void AtUart::HandleUrc(const std::string& command, const std::vector<AtArgumentValue>& arguments) {
if (command == "CME ERROR") {
cme_error_code_ = arguments[0].int_value;
xEventGroupSetBits(event_group_handle_, AT_EVENT_COMMAND_ERROR);
return;
}
std::lock_guard<std::mutex> lock(mutex_);
for (auto& callback : urc_callbacks_) {
callback(command, arguments);
}
}
bool AtUart::DetectBaudRate() {
int baud_rates[] = {115200, 921600, 460800, 230400, 57600, 38400, 19200, 9600};
while (true) {
ESP_LOGI(TAG, "Detecting baud rate...");
for (size_t i = 0; i < sizeof(baud_rates) / sizeof(baud_rates[0]); i++) {
int rate = baud_rates[i];
uart_set_baudrate(uart_num_, rate);
if (SendCommand("AT", 20)) {
ESP_LOGI(TAG, "Detected baud rate: %d", rate);
baud_rate_ = rate;
return true;
}
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
return false;
}
bool AtUart::SetBaudRate(int new_baud_rate) {
if (!DetectBaudRate()) {
ESP_LOGE(TAG, "Failed to detect baud rate");
return false;
}
if (new_baud_rate == baud_rate_) {
return true;
}
// Set new baud rate
if (!SendCommand(std::string("AT+IPR=") + std::to_string(new_baud_rate))) {
ESP_LOGI(TAG, "Failed to set baud rate to %d", new_baud_rate);
return false;
}
uart_set_baudrate(uart_num_, new_baud_rate);
baud_rate_ = new_baud_rate;
ESP_LOGI(TAG, "Set baud rate to %d", new_baud_rate);
return true;
}
bool AtUart::SendData(const char* data, size_t length) {
if (!initialized_) {
ESP_LOGE(TAG, "UART未初始化");
return false;
}
int ret = uart_write_bytes(uart_num_, data, length);
if (ret < 0) {
ESP_LOGE(TAG, "uart_write_bytes failed: %d", ret);
return false;
}
return true;
}
bool AtUart::SendCommandWithData(const std::string& command, size_t timeout_ms, bool add_crlf, const char* data, size_t data_length) {
std::lock_guard<std::mutex> lock(command_mutex_);
ESP_LOGD(TAG, ">> %.64s (%u bytes)", command.data(), command.length());
xEventGroupClearBits(event_group_handle_, AT_EVENT_COMMAND_DONE | AT_EVENT_COMMAND_ERROR);
wait_for_response_ = true;
cme_error_code_ = 0;
response_.clear();
if (add_crlf) {
if (!SendData((command + "\r\n").data(), command.length() + 2)) {
return false;
}
} else {
if (!SendData(command.data(), command.length())) {
return false;
}
}
if (timeout_ms > 0) {
auto bits = xEventGroupWaitBits(event_group_handle_, AT_EVENT_COMMAND_DONE | AT_EVENT_COMMAND_ERROR, pdTRUE, pdFALSE, pdMS_TO_TICKS(timeout_ms));
wait_for_response_ = false;
if (!(bits & AT_EVENT_COMMAND_DONE)) {
return false;
}
} else {
wait_for_response_ = false;
}
if (data && data_length > 0) {
wait_for_response_ = true;
if (!SendData(data, data_length)) {
return false;
}
auto bits = xEventGroupWaitBits(event_group_handle_, AT_EVENT_COMMAND_DONE | AT_EVENT_COMMAND_ERROR, pdTRUE, pdFALSE, pdMS_TO_TICKS(timeout_ms));
wait_for_response_ = false;
if (!(bits & AT_EVENT_COMMAND_DONE)) {
return false;
}
}
return true;
}
bool AtUart::SendCommand(const std::string& command, size_t timeout_ms, bool add_crlf) {
return SendCommandWithData(command, timeout_ms, add_crlf, nullptr, 0);
}
std::list<UrcCallback>::iterator AtUart::RegisterUrcCallback(UrcCallback callback) {
std::lock_guard<std::mutex> lock(mutex_);
return urc_callbacks_.insert(urc_callbacks_.end(), callback);
}
void AtUart::UnregisterUrcCallback(std::list<UrcCallback>::iterator iterator) {
std::lock_guard<std::mutex> lock(mutex_);
urc_callbacks_.erase(iterator);
}
void AtUart::SetDtrPin(bool high) {
if (dtr_pin_ != GPIO_NUM_NC) {
ESP_LOGD(TAG, "Set DTR pin %d to %d", dtr_pin_, high ? 1 : 0);
gpio_set_level(dtr_pin_, high ? 1 : 0);
vTaskDelay(pdMS_TO_TICKS(20));
}
}
static const char hex_chars[] = "0123456789ABCDEF";
// 辅助函数,将单个十六进制字符转换为对应的数值
inline uint8_t CharToHex(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
return 0; // 对于无效输入返回0
}
void AtUart::EncodeHexAppend(std::string& dest, const char* data, size_t length) {
dest.reserve(dest.size() + length * 2 + 4); // 预分配空间多分配4个字节用于\r\n\0
for (size_t i = 0; i < length; i++) {
dest.push_back(hex_chars[(data[i] & 0xF0) >> 4]);
dest.push_back(hex_chars[data[i] & 0x0F]);
}
}
void AtUart::DecodeHexAppend(std::string& dest, const char* data, size_t length) {
dest.reserve(dest.size() + length / 2 + 4); // 预分配空间多分配4个字节用于\r\n\0
for (size_t i = 0; i < length; i += 2) {
char byte = (CharToHex(data[i]) << 4) | CharToHex(data[i + 1]);
dest.push_back(byte);
}
}
std::string AtUart::EncodeHex(const std::string& data) {
std::string encoded;
EncodeHexAppend(encoded, data.c_str(), data.size());
return encoded;
}
std::string AtUart::DecodeHex(const std::string& data) {
std::string decoded;
DecodeHexAppend(decoded, data.c_str(), data.size());
return decoded;
}

View File

@@ -0,0 +1,70 @@
#include "ec801e_at_modem.h"
#include <esp_log.h>
#include <esp_err.h>
#include <cassert>
#include <sstream>
#include <iomanip>
#include <cstring>
#include "ec801e_ssl.h"
#include "ec801e_tcp.h"
#include "ec801e_udp.h"
#include "ec801e_mqtt.h"
#include "http_client.h"
#include "web_socket.h"
#define TAG "Ec801EAtModem"
Ec801EAtModem::Ec801EAtModem(std::shared_ptr<AtUart> at_uart) : AtModem(at_uart) {
// 子类特定的初始化在这里
// ATE0 关闭 echo
at_uart_->SendCommand("ATE0");
// 设置 URC 端口为 UART1
at_uart_->SendCommand("AT+QURCCFG=\"urcport\",\"uart1\"");
}
void Ec801EAtModem::HandleUrc(const std::string& command, const std::vector<AtArgumentValue>& arguments) {
// Handle Common URC
AtModem::HandleUrc(command, arguments);
}
bool Ec801EAtModem::SetSleepMode(bool enable, int delay_seconds) {
if (enable) {
if (delay_seconds > 0) {
at_uart_->SendCommand("AT+QSCLKEX=1," + std::to_string(delay_seconds) + ",30");
}
return at_uart_->SendCommand("AT+QSCLK=1");
} else {
return at_uart_->SendCommand("AT+QSCLK=0");
}
}
std::unique_ptr<Http> Ec801EAtModem::CreateHttp(int connect_id) {
assert(connect_id >= 0);
return std::make_unique<HttpClient>(this, connect_id);
}
std::unique_ptr<Tcp> Ec801EAtModem::CreateTcp(int connect_id) {
assert(connect_id >= 0);
return std::make_unique<Ec801ETcp>(at_uart_, connect_id);
}
std::unique_ptr<Tcp> Ec801EAtModem::CreateSsl(int connect_id) {
assert(connect_id >= 0);
return std::make_unique<Ec801ESsl>(at_uart_, connect_id);
}
std::unique_ptr<Udp> Ec801EAtModem::CreateUdp(int connect_id) {
assert(connect_id >= 0);
return std::make_unique<Ec801EUdp>(at_uart_, connect_id);
}
std::unique_ptr<Mqtt> Ec801EAtModem::CreateMqtt(int connect_id) {
assert(connect_id >= 0);
return std::make_unique<Ec801EMqtt>(at_uart_, connect_id);
}
std::unique_ptr<WebSocket> Ec801EAtModem::CreateWebSocket(int connect_id) {
assert(connect_id >= 0);
return std::make_unique<WebSocket>(this, connect_id);
}

View File

@@ -0,0 +1,26 @@
#ifndef _EC801E_AT_MODEM_H_
#define _EC801E_AT_MODEM_H_
#include "at_modem.h"
class Ec801EAtModem : public AtModem {
public:
Ec801EAtModem(std::shared_ptr<AtUart> at_uart);
~Ec801EAtModem() override = default;
bool SetSleepMode(bool enable, int delay_seconds=0) override;
// 实现基类的纯虚函数
std::unique_ptr<Http> CreateHttp(int connect_id) override;
std::unique_ptr<Tcp> CreateTcp(int connect_id) override;
std::unique_ptr<Tcp> CreateSsl(int connect_id) override;
std::unique_ptr<Udp> CreateUdp(int connect_id) override;
std::unique_ptr<Mqtt> CreateMqtt(int connect_id) override;
std::unique_ptr<WebSocket> CreateWebSocket(int connect_id) override;
protected:
void HandleUrc(const std::string& command, const std::vector<AtArgumentValue>& arguments) override;
};
#endif // _EC801E_AT_MODEM_H_

View File

@@ -0,0 +1,244 @@
#include "ec801e_mqtt.h"
#include <esp_log.h>
#define TAG "Ec801EMqtt"
Ec801EMqtt::Ec801EMqtt(std::shared_ptr<AtUart> at_uart, int mqtt_id) : at_uart_(at_uart), mqtt_id_(mqtt_id) {
event_group_handle_ = xEventGroupCreate();
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
if (command == "QMTRECV" && arguments.size() >= 4) {
if (arguments[0].int_value == mqtt_id_) {
auto topic = arguments[2].string_value;
if (on_message_callback_) {
on_message_callback_(topic, at_uart_->DecodeHex(arguments[3].string_value));
}
}
} else if (command == "QMTSTAT" && arguments.size() == 2) {
if (arguments[0].int_value == mqtt_id_) {
auto error_code = arguments[1].int_value;
if (error_code != 0) {
auto error_message = ErrorToString(error_code);
ESP_LOGE(TAG, "MQTT error occurred: %s", error_message.c_str());
if (on_error_callback_) {
on_error_callback_(error_message);
}
if (connected_) {
connected_ = false;
if (on_disconnected_callback_) {
on_disconnected_callback_();
}
}
xEventGroupSetBits(event_group_handle_, EC801E_MQTT_DISCONNECTED_EVENT);
}
}
} else if (command == "QMTCONN" && arguments.size() == 3) {
if (arguments[0].int_value == mqtt_id_) {
error_code_ = arguments[2].int_value;
if (error_code_ == 0) {
if (!connected_) {
connected_ = true;
if (on_connected_callback_) {
on_connected_callback_();
}
}
xEventGroupSetBits(event_group_handle_, EC801E_MQTT_CONNECTED_EVENT);
} else {
if (connected_) {
connected_ = false;
if (on_disconnected_callback_) {
on_disconnected_callback_();
}
}
xEventGroupSetBits(event_group_handle_, EC801E_MQTT_DISCONNECTED_EVENT);
}
}
} else if (command == "QMTOPEN" && arguments.size() == 2) {
if (arguments[0].int_value == mqtt_id_) {
error_code_ = arguments[1].int_value;
if (error_code_ == 0) {
xEventGroupSetBits(event_group_handle_, EC801E_MQTT_OPEN_COMPLETE);
} else {
xEventGroupSetBits(event_group_handle_, EC801E_MQTT_OPEN_FAILED);
}
}
} else if (command == "QMTDISC" && arguments.size() == 2) {
if (arguments[0].int_value == mqtt_id_) {
if (arguments[1].int_value == 0) {
xEventGroupSetBits(event_group_handle_, EC801E_MQTT_DISCONNECTED_EVENT);
} else {
ESP_LOGE(TAG, "Failed to disconnect from MQTT broker");
}
}
}
});
}
Ec801EMqtt::~Ec801EMqtt() {
at_uart_->UnregisterUrcCallback(urc_callback_it_);
vEventGroupDelete(event_group_handle_);
}
bool Ec801EMqtt::Connect(const std::string broker_address, int broker_port, const std::string client_id, const std::string username, const std::string password) {
EventBits_t bits;
if (broker_port == 8883) {
// Config SSL Context
at_uart_->SendCommand("AT+QSSLCFG=\"sslversion\",2,4;+QSSLCFG=\"ciphersuite\",2,0xFFFF;+QSSLCFG=\"seclevel\",2,0");
if (!at_uart_->SendCommand(std::string("AT+QMTCFG=\"ssl\",") + std::to_string(mqtt_id_) + ",1,2")) {
ESP_LOGE(TAG, "Failed to set MQTT to use SSL");
return false;
}
}
// Set version
if (!at_uart_->SendCommand(std::string("AT+QMTCFG=\"version\",") + std::to_string(mqtt_id_) + ",4")) {
ESP_LOGE(TAG, "Failed to set MQTT version to 3.1.1");
return false;
}
// Set clean session
if (!at_uart_->SendCommand(std::string("AT+QMTCFG=\"session\",") + std::to_string(mqtt_id_) + ",1")) {
ESP_LOGE(TAG, "Failed to set MQTT clean session");
return false;
}
// Set keep alive
if (!at_uart_->SendCommand(std::string("AT+QMTCFG=\"keepalive\",") + std::to_string(mqtt_id_) + "," + std::to_string(keep_alive_seconds_))) {
ESP_LOGE(TAG, "Failed to set MQTT keep alive");
return false;
}
// Set HEX encoding (ASCII for sending, HEX for receiving)
if (!at_uart_->SendCommand("AT+QMTCFG=\"dataformat\"," + std::to_string(mqtt_id_) + ",0,1")) {
ESP_LOGE(TAG, "Failed to set MQTT to use HEX encoding");
return false;
}
xEventGroupClearBits(event_group_handle_, EC801E_MQTT_OPEN_COMPLETE | EC801E_MQTT_OPEN_FAILED);
std::string command = "AT+QMTOPEN=" + std::to_string(mqtt_id_) + ",\"" + broker_address + "\"," + std::to_string(broker_port);
if (!at_uart_->SendCommand(command)) {
ESP_LOGE(TAG, "Failed to open MQTT connection");
return false;
}
bits = xEventGroupWaitBits(event_group_handle_, EC801E_MQTT_OPEN_COMPLETE | EC801E_MQTT_OPEN_FAILED, pdTRUE, pdFALSE, pdMS_TO_TICKS(EC801E_MQTT_CONNECT_TIMEOUT_MS));
if (bits & EC801E_MQTT_OPEN_FAILED) {
const char* error_code_str[] = {
"Connected",
"Parameter error",
"MQTT identifier occupied",
"PDP activation failed",
"Domain name resolution failed",
"Server disconnected"
};
const char* message = error_code_ < 6 ? error_code_str[error_code_] : "Unknown error";
ESP_LOGE(TAG, "Failed to open MQTT connection: %s", message);
if (error_code_ == 2) { // MQTT 标识符被占用
at_uart_->SendCommand(std::string("AT+QMTDISC=") + std::to_string(mqtt_id_));
bits = xEventGroupWaitBits(event_group_handle_, EC801E_MQTT_DISCONNECTED_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(EC801E_MQTT_CONNECT_TIMEOUT_MS));
if (!(bits & EC801E_MQTT_DISCONNECTED_EVENT)) {
ESP_LOGE(TAG, "Failed to disconnect from previous connection");
return false;
}
return Connect(broker_address, broker_port, client_id, username, password);
}
return false;
} else if (!(bits & EC801E_MQTT_OPEN_COMPLETE)) {
ESP_LOGE(TAG, "MQTT connection timeout");
return false;
}
xEventGroupClearBits(event_group_handle_, EC801E_MQTT_CONNECTED_EVENT | EC801E_MQTT_DISCONNECTED_EVENT);
command = "AT+QMTCONN=" + std::to_string(mqtt_id_) + ",\"" + client_id + "\",\"" + username + "\",\"" + password + "\"";
if (!at_uart_->SendCommand(command)) {
ESP_LOGE(TAG, "Failed to connect to MQTT broker");
return false;
}
// 等待连接完成
bits = xEventGroupWaitBits(event_group_handle_, EC801E_MQTT_CONNECTED_EVENT | EC801E_MQTT_DISCONNECTED_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(EC801E_MQTT_CONNECT_TIMEOUT_MS));
if (bits & EC801E_MQTT_DISCONNECTED_EVENT) {
const char* error_code_str[] = {
"Accepted",
"Rejected: Unacceptable protocol version",
"Rejected: Identifier rejected",
"Rejected: Server unavailable",
"Rejected: Wrong username or password",
"Rejected: Unauthorized"
};
const char* message = error_code_ < 6 ? error_code_str[error_code_] : "Unknown error";
ESP_LOGE(TAG, "Failed to connect to MQTT broker: %s", message);
return false;
} else if (!(bits & EC801E_MQTT_CONNECTED_EVENT)) {
ESP_LOGE(TAG, "MQTT connection timeout");
return false;
}
return true;
}
bool Ec801EMqtt::IsConnected() {
return connected_;
}
void Ec801EMqtt::Disconnect() {
if (!connected_) {
return;
}
at_uart_->SendCommand(std::string("AT+QMTDISC=") + std::to_string(mqtt_id_));
}
bool Ec801EMqtt::Publish(const std::string topic, const std::string payload, int qos) {
if (!connected_) {
return false;
}
// If payload size is larger than 64KB, a CME ERROR 601 will be returned.
std::string command = "AT+QMTPUBEX=" + std::to_string(mqtt_id_) + ",0,0,0,\"" + topic + "\",";
command += std::to_string(payload.size());
if (!at_uart_->SendCommandWithData(command, 1000, true, payload.data(), payload.size())) {
return false;
}
return true;
}
bool Ec801EMqtt::Subscribe(const std::string topic, int qos) {
if (!connected_) {
return false;
}
std::string command = "AT+QMTSUB=" + std::to_string(mqtt_id_) + ",0,\"" + topic + "\"," + std::to_string(qos);
return at_uart_->SendCommand(command);
}
bool Ec801EMqtt::Unsubscribe(const std::string topic) {
if (!connected_) {
return false;
}
std::string command = "AT+QMTUNS=" + std::to_string(mqtt_id_) + ",0,\"" + topic + "\"";
return at_uart_->SendCommand(command);
}
std::string Ec801EMqtt::ErrorToString(int error_code) {
switch (error_code) {
case 0:
return "Connected";
case 1:
return "Server disconnected or reset";
case 2:
return "Ping timeout or failed";
case 3:
return "Connect timeout or failed";
case 4:
return "Receive CONNACK timeout or failed";
case 5:
return "Client sends DISCONNECT packet, but server actively disconnects MQTT connection";
case 6:
return "Client actively disconnects MQTT connection because sending data packets always fails";
case 7:
return "Link does not work or server is unavailable";
case 8:
return "Client actively disconnects MQTT connection";
default:
return "Unknown error";
}
}

View File

@@ -0,0 +1,45 @@
#ifndef EC801E_MQTT_H
#define EC801E_MQTT_H
#include "mqtt.h"
#include "at_uart.h"
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/event_groups.h>
#include <string>
#include <functional>
#define EC801E_MQTT_CONNECT_TIMEOUT_MS 10000
#define EC801E_MQTT_CONNECTED_EVENT BIT1
#define EC801E_MQTT_DISCONNECTED_EVENT BIT2
#define EC801E_MQTT_OPEN_COMPLETE BIT5
#define EC801E_MQTT_OPEN_FAILED BIT6
class Ec801EMqtt : public Mqtt {
public:
Ec801EMqtt(std::shared_ptr<AtUart> at_uart, int mqtt_id);
~Ec801EMqtt();
bool Connect(const std::string broker_address, int broker_port, const std::string client_id, const std::string username, const std::string password);
void Disconnect();
bool Publish(const std::string topic, const std::string payload, int qos = 0);
bool Subscribe(const std::string topic, int qos = 0);
bool Unsubscribe(const std::string topic);
bool IsConnected();
private:
std::shared_ptr<AtUart> at_uart_;
int mqtt_id_;
bool connected_ = false;
int error_code_ = 0;
EventGroupHandle_t event_group_handle_;
std::string message_payload_;
std::list<UrcCallback>::iterator urc_callback_it_;
std::string ErrorToString(int error_code);
};
#endif

View File

@@ -0,0 +1,160 @@
#include "ec801e_ssl.h"
#include <esp_log.h>
#define TAG "Ec801ESsl"
Ec801ESsl::Ec801ESsl(std::shared_ptr<AtUart> at_uart, int ssl_id) : at_uart_(at_uart), ssl_id_(ssl_id) {
event_group_handle_ = xEventGroupCreate();
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
if (command == "QSSLOPEN" && arguments.size() == 2) {
if (arguments[0].int_value == ssl_id_ && !instance_active_) {
if (arguments[1].int_value == 0) {
connected_ = true;
instance_active_ = true;
xEventGroupClearBits(event_group_handle_, EC801E_SSL_DISCONNECTED | EC801E_SSL_ERROR);
xEventGroupSetBits(event_group_handle_, EC801E_SSL_CONNECTED);
} else {
connected_ = false;
xEventGroupSetBits(event_group_handle_, EC801E_SSL_ERROR);
}
}
} else if (command == "QSSLCLOSE" && arguments.size() == 1) {
if (arguments[0].int_value == ssl_id_) {
instance_active_ = false;
}
} else if (command == "QISEND" && arguments.size() == 3) {
if (arguments[0].int_value == ssl_id_) {
if (arguments[1].int_value == 0) {
xEventGroupSetBits(event_group_handle_, EC801E_SSL_SEND_COMPLETE);
} else {
xEventGroupSetBits(event_group_handle_, EC801E_SSL_ERROR);
}
}
} else if (command == "QSSLURC" && arguments.size() >= 2) {
if (arguments[1].int_value == ssl_id_) {
if (arguments[0].string_value == "recv" && arguments.size() >= 4) {
if (stream_callback_) {
stream_callback_(at_uart_->DecodeHex(arguments[3].string_value));
}
} else if (arguments[0].string_value == "closed") {
if (connected_) {
connected_ = false;
// instance_active_ 保持 true需要发送 QICLOSE 清理
if (disconnect_callback_) {
disconnect_callback_();
}
}
xEventGroupSetBits(event_group_handle_, EC801E_SSL_DISCONNECTED);
} else {
ESP_LOGE(TAG, "Unknown QIURC command: %s", arguments[0].string_value.c_str());
}
}
} else if (command == "QSSLSTATE" && arguments.size() > 5) {
if (arguments[0].int_value == ssl_id_) {
connected_ = arguments[5].int_value == 2;
instance_active_ = true;
xEventGroupSetBits(event_group_handle_, EC801E_SSL_INITIALIZED);
}
} else if (command == "FIFO_OVERFLOW") {
xEventGroupSetBits(event_group_handle_, EC801E_SSL_ERROR);
Disconnect();
}
});
}
Ec801ESsl::~Ec801ESsl() {
Disconnect();
at_uart_->UnregisterUrcCallback(urc_callback_it_);
}
bool Ec801ESsl::Connect(const std::string& host, int port) {
// Clear bits
xEventGroupClearBits(event_group_handle_, EC801E_SSL_CONNECTED | EC801E_SSL_DISCONNECTED | EC801E_SSL_ERROR);
// Keep data in one line; Use HEX encoding in response
at_uart_->SendCommand("AT+QICFG=\"close/mode\",1;+QICFG=\"viewmode\",1;+QICFG=\"sendinfo\",1;+QICFG=\"dataformat\",0,1");
// Config SSL Context
at_uart_->SendCommand("AT+QSSLCFG=\"sslversion\",1,4;+QSSLCFG=\"ciphersuite\",1,0xFFFF;+QSSLCFG=\"seclevel\",1,0");
// at_uart_->SendCommand("AT+QSSLCFG=\"cacert\",1,\"UFS:cacert.pem\"");
// 检查这个 id 是否已经连接
std::string command = "AT+QSSLSTATE=1," + std::to_string(ssl_id_);
at_uart_->SendCommand(command);
// 断开之前的连接(不触发回调事件)
if (instance_active_) {
at_uart_->SendCommand("AT+QSSLCLOSE=" + std::to_string(ssl_id_));
xEventGroupWaitBits(event_group_handle_, EC801E_SSL_DISCONNECTED, pdTRUE, pdFALSE, SSL_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
instance_active_ = false;
}
// 打开 TCP 连接
command = "AT+QSSLOPEN=1,1," + std::to_string(ssl_id_) + ",\"" + host + "\"," + std::to_string(port) + ",1";
if (!at_uart_->SendCommand(command)) {
ESP_LOGE(TAG, "Failed to open TCP connection");
return false;
}
// 等待连接完成
auto bits = xEventGroupWaitBits(event_group_handle_, EC801E_SSL_CONNECTED | EC801E_SSL_ERROR, pdTRUE, pdFALSE, SSL_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
if (bits & EC801E_SSL_ERROR) {
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
return false;
}
return true;
}
void Ec801ESsl::Disconnect() {
if (!instance_active_) {
return;
}
at_uart_->SendCommand("AT+QSSLCLOSE=" + std::to_string(ssl_id_));
if (connected_) {
connected_ = false;
if (disconnect_callback_) {
disconnect_callback_();
}
}
}
int Ec801ESsl::Send(const std::string& data) {
const size_t MAX_PACKET_SIZE = 1460;
size_t total_sent = 0;
if (!connected_) {
ESP_LOGE(TAG, "Not connected");
return -1;
}
while (total_sent < data.size()) {
size_t chunk_size = std::min(data.size() - total_sent, MAX_PACKET_SIZE);
std::string command = "AT+QSSLSEND=" + std::to_string(ssl_id_) + "," + std::to_string(chunk_size);
if (!at_uart_->SendCommandWithData(command, 1000, true, data.data() + total_sent, chunk_size)) {
ESP_LOGE(TAG, "Send command failed");
Disconnect();
return -1;
}
auto bits = xEventGroupWaitBits(event_group_handle_, EC801E_SSL_SEND_COMPLETE | EC801E_SSL_SEND_FAILED, pdTRUE, pdFALSE, pdMS_TO_TICKS(SSL_CONNECT_TIMEOUT_MS));
if (bits & EC801E_SSL_SEND_FAILED) {
ESP_LOGE(TAG, "Send failed, retry later");
vTaskDelay(pdMS_TO_TICKS(100));
continue;
} else if (!(bits & EC801E_SSL_SEND_COMPLETE)) {
ESP_LOGE(TAG, "Send timeout");
return -1;
}
total_sent += chunk_size;
}
return data.size();
}

View File

@@ -0,0 +1,36 @@
#ifndef EC801E_SSL_H
#define EC801E_SSL_H
#include "tcp.h"
#include "at_uart.h"
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#define EC801E_SSL_CONNECTED BIT0
#define EC801E_SSL_DISCONNECTED BIT1
#define EC801E_SSL_ERROR BIT2
#define EC801E_SSL_SEND_COMPLETE BIT3
#define EC801E_SSL_SEND_FAILED BIT4
#define EC801E_SSL_INITIALIZED BIT5
#define SSL_CONNECT_TIMEOUT_MS 10000
class Ec801ESsl : public Tcp {
public:
Ec801ESsl(std::shared_ptr<AtUart> at_uart, int ssl_id);
~Ec801ESsl();
bool Connect(const std::string& host, int port) override;
void Disconnect() override;
int Send(const std::string& data) override;
private:
std::shared_ptr<AtUart> at_uart_;
int ssl_id_;
bool instance_active_ = false;
EventGroupHandle_t event_group_handle_;
std::list<UrcCallback>::iterator urc_callback_it_;
};
#endif // EC801E_SSL_H

View File

@@ -0,0 +1,159 @@
#include "ec801e_tcp.h"
#include <esp_log.h>
#define TAG "Ec801ETcp"
Ec801ETcp::Ec801ETcp(std::shared_ptr<AtUart> at_uart, int tcp_id) : at_uart_(at_uart), tcp_id_(tcp_id) {
event_group_handle_ = xEventGroupCreate();
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
if (command == "QIOPEN" && arguments.size() == 2) {
if (arguments[0].int_value == tcp_id_) {
if (arguments[1].int_value == 0) {
connected_ = true;
instance_active_ = true;
xEventGroupClearBits(event_group_handle_, EC801E_TCP_DISCONNECTED | EC801E_TCP_ERROR);
xEventGroupSetBits(event_group_handle_, EC801E_TCP_CONNECTED);
} else {
connected_ = false;
xEventGroupSetBits(event_group_handle_, EC801E_TCP_ERROR);
if (disconnect_callback_) {
disconnect_callback_();
}
}
}
} else if (command == "QISEND" && arguments.size() == 3) {
if (arguments[0].int_value == tcp_id_) {
if (arguments[1].int_value == 0) {
xEventGroupSetBits(event_group_handle_, EC801E_TCP_SEND_COMPLETE);
} else {
xEventGroupSetBits(event_group_handle_, EC801E_TCP_SEND_FAILED);
}
}
} else if (command == "QIURC" && arguments.size() >= 2) {
if (arguments[1].int_value == tcp_id_) {
if (arguments[0].string_value == "recv" && arguments.size() >= 4) {
if (connected_ && stream_callback_) {
stream_callback_(at_uart_->DecodeHex(arguments[3].string_value));
}
} else if (arguments[0].string_value == "closed") {
if (connected_) {
connected_ = false;
// instance_active_ 保持 true需要发送 QICLOSE 清理
if (disconnect_callback_) {
disconnect_callback_();
}
}
xEventGroupSetBits(event_group_handle_, EC801E_TCP_DISCONNECTED);
} else {
ESP_LOGE(TAG, "Unknown QIURC command: %s", arguments[0].string_value.c_str());
}
}
} else if (command == "QISTATE" && arguments.size() > 5) {
if (arguments[0].int_value == tcp_id_) {
connected_ = arguments[5].int_value == 2;
instance_active_ = true;
xEventGroupSetBits(event_group_handle_, EC801E_TCP_INITIALIZED);
}
} else if (command == "FIFO_OVERFLOW") {
xEventGroupSetBits(event_group_handle_, EC801E_TCP_ERROR);
Disconnect();
}
});
}
Ec801ETcp::~Ec801ETcp() {
Disconnect();
at_uart_->UnregisterUrcCallback(urc_callback_it_);
if (event_group_handle_) {
vEventGroupDelete(event_group_handle_);
}
}
bool Ec801ETcp::Connect(const std::string& host, int port) {
// Clear bits
xEventGroupClearBits(event_group_handle_, EC801E_TCP_CONNECTED | EC801E_TCP_DISCONNECTED | EC801E_TCP_ERROR);
// Keep data in one line; Use HEX encoding in response
at_uart_->SendCommand("AT+QICFG=\"close/mode\",1;+QICFG=\"viewmode\",1;+QICFG=\"sendinfo\",1;+QICFG=\"dataformat\",0,1");
// 检查这个 id 是否已经连接
std::string command = "AT+QISTATE=1," + std::to_string(tcp_id_);
at_uart_->SendCommand(command);
// 断开之前的连接(不触发回调事件)
if (instance_active_) {
at_uart_->SendCommand("AT+QICLOSE=" + std::to_string(tcp_id_));
xEventGroupWaitBits(event_group_handle_, EC801E_TCP_DISCONNECTED, pdTRUE, pdFALSE, TCP_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
instance_active_ = false;
}
// 打开 TCP 连接
command = "AT+QIOPEN=1," + std::to_string(tcp_id_) + ",\"TCP\",\"" + host + "\"," + std::to_string(port) + ",0,1";
if (!at_uart_->SendCommand(command)) {
ESP_LOGE(TAG, "Failed to open TCP connection");
return false;
}
// 等待连接完成
auto bits = xEventGroupWaitBits(event_group_handle_, EC801E_TCP_CONNECTED | EC801E_TCP_ERROR, pdTRUE, pdFALSE, TCP_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
if (bits & EC801E_TCP_ERROR) {
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
return false;
}
return true;
}
void Ec801ETcp::Disconnect() {
if (!instance_active_) {
return;
}
if (at_uart_->SendCommand("AT+QICLOSE=" + std::to_string(tcp_id_))) {
instance_active_ = false;
}
if (connected_) {
connected_ = false;
if (disconnect_callback_) {
disconnect_callback_();
}
}
}
int Ec801ETcp::Send(const std::string& data) {
const size_t MAX_PACKET_SIZE = 1460;
size_t total_sent = 0;
if (!connected_) {
ESP_LOGE(TAG, "Not connected");
return -1;
}
while (total_sent < data.size()) {
size_t chunk_size = std::min(data.size() - total_sent, MAX_PACKET_SIZE);
std::string command = "AT+QISEND=" + std::to_string(tcp_id_) + "," + std::to_string(chunk_size);
if (!at_uart_->SendCommandWithData(command, 1000, true, data.data() + total_sent, chunk_size)) {
ESP_LOGE(TAG, "Send command failed");
Disconnect();
return -1;
}
auto bits = xEventGroupWaitBits(event_group_handle_, EC801E_TCP_SEND_COMPLETE | EC801E_TCP_SEND_FAILED, pdTRUE, pdFALSE, pdMS_TO_TICKS(TCP_CONNECT_TIMEOUT_MS));
if (bits & EC801E_TCP_SEND_FAILED) {
ESP_LOGE(TAG, "Send failed, retry later");
vTaskDelay(pdMS_TO_TICKS(100));
continue;
} else if (!(bits & EC801E_TCP_SEND_COMPLETE)) {
ESP_LOGE(TAG, "Send timeout");
return -1;
}
total_sent += chunk_size;
}
return data.size();
}

View File

@@ -0,0 +1,37 @@
#ifndef EC801E_TCP_H
#define EC801E_TCP_H
#include "tcp.h"
#include "at_uart.h"
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <string>
#define EC801E_TCP_CONNECTED BIT0
#define EC801E_TCP_DISCONNECTED BIT1
#define EC801E_TCP_ERROR BIT2
#define EC801E_TCP_SEND_COMPLETE BIT3
#define EC801E_TCP_SEND_FAILED BIT4
#define EC801E_TCP_INITIALIZED BIT5
#define TCP_CONNECT_TIMEOUT_MS 10000
class Ec801ETcp : public Tcp {
public:
Ec801ETcp(std::shared_ptr<AtUart> at_uart, int tcp_id);
~Ec801ETcp();
bool Connect(const std::string& host, int port) override;
void Disconnect() override;
int Send(const std::string& data) override;
private:
std::shared_ptr<AtUart> at_uart_;
int tcp_id_;
bool instance_active_ = false;
EventGroupHandle_t event_group_handle_;
std::list<UrcCallback>::iterator urc_callback_it_;
};
#endif // EC801E_TCP_H

View File

@@ -0,0 +1,141 @@
#include "ec801e_udp.h"
#include <esp_log.h>
#define TAG "Ec801EUdp"
Ec801EUdp::Ec801EUdp(std::shared_ptr<AtUart> at_uart, int udp_id) : at_uart_(at_uart), udp_id_(udp_id) {
event_group_handle_ = xEventGroupCreate();
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
if (command == "QIOPEN" && arguments.size() == 2) {
if (arguments[0].int_value == udp_id_) {
connected_ = arguments[1].int_value == 0;
if (connected_) {
instance_active_ = true;
xEventGroupClearBits(event_group_handle_, EC801E_UDP_DISCONNECTED | EC801E_UDP_ERROR);
xEventGroupSetBits(event_group_handle_, EC801E_UDP_CONNECTED);
} else {
xEventGroupSetBits(event_group_handle_, EC801E_UDP_ERROR);
}
}
} else if (command == "QISEND" && arguments.size() == 3) {
if (arguments[0].int_value == udp_id_) {
if (arguments[1].int_value == 0) {
xEventGroupSetBits(event_group_handle_, EC801E_UDP_SEND_COMPLETE);
} else {
xEventGroupSetBits(event_group_handle_, EC801E_UDP_SEND_FAILED);
}
}
} else if (command == "QIURC" && arguments.size() >= 2) {
if (arguments[1].int_value == udp_id_) {
if (arguments[0].string_value == "recv" && arguments.size() >= 4) {
if (connected_ && message_callback_) {
message_callback_(at_uart_->DecodeHex(arguments[3].string_value));
}
} else if (arguments[0].string_value == "closed") {
connected_ = false;
instance_active_ = false;
xEventGroupSetBits(event_group_handle_, EC801E_UDP_DISCONNECTED);
} else {
ESP_LOGE(TAG, "Unknown QIURC command: %s", arguments[0].string_value.c_str());
}
}
} else if (command == "QISTATE" && arguments.size() > 5) {
if (arguments[0].int_value == udp_id_) {
connected_ = arguments[5].int_value == 2;
instance_active_ = true;
xEventGroupSetBits(event_group_handle_, EC801E_UDP_INITIALIZED);
}
} else if (command == "FIFO_OVERFLOW") {
xEventGroupSetBits(event_group_handle_, EC801E_UDP_ERROR);
Disconnect();
}
});
}
Ec801EUdp::~Ec801EUdp() {
Disconnect();
at_uart_->UnregisterUrcCallback(urc_callback_it_);
if (event_group_handle_) {
vEventGroupDelete(event_group_handle_);
}
}
bool Ec801EUdp::Connect(const std::string& host, int port) {
// Clear bits
xEventGroupClearBits(event_group_handle_, EC801E_UDP_CONNECTED | EC801E_UDP_DISCONNECTED | EC801E_UDP_ERROR);
// Keep data in one line; Use HEX encoding in response
at_uart_->SendCommand("AT+QICFG=\"close/mode\",1;+QICFG=\"viewmode\",1;+QICFG=\"sendinfo\",1;+QICFG=\"dataformat\",0,1");
// 检查这个 id 是否已经连接
std::string command = "AT+QISTATE=1," + std::to_string(udp_id_);
at_uart_->SendCommand(command);
// 断开之前的连接(不触发回调事件)
if (instance_active_) {
at_uart_->SendCommand("AT+QICLOSE=" + std::to_string(udp_id_));
xEventGroupWaitBits(event_group_handle_, EC801E_UDP_DISCONNECTED, pdTRUE, pdFALSE, UDP_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
instance_active_ = false;
}
// 打开 UDP 连接
command = "AT+QIOPEN=1," + std::to_string(udp_id_) + ",\"UDP\",\"" + host + "\"," + std::to_string(port) + ",0,1";
if (!at_uart_->SendCommand(command)) {
ESP_LOGE(TAG, "Failed to open UDP connection");
return false;
}
// 等待连接完成
auto bits = xEventGroupWaitBits(event_group_handle_, EC801E_UDP_CONNECTED | EC801E_UDP_ERROR, pdTRUE, pdFALSE, UDP_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
if (bits & EC801E_UDP_ERROR) {
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
return false;
}
return true;
}
void Ec801EUdp::Disconnect() {
if (!instance_active_) {
return;
}
if (at_uart_->SendCommand("AT+QICLOSE=" + std::to_string(udp_id_))) {
instance_active_ = false;
}
}
int Ec801EUdp::Send(const std::string& data) {
const size_t MAX_PACKET_SIZE = 1460;
if (!connected_) {
ESP_LOGE(TAG, "Not connected");
return -1;
}
if (data.size() > MAX_PACKET_SIZE) {
ESP_LOGE(TAG, "Data block exceeds maximum limit");
return -1;
}
// 在循环外预先分配command
std::string command = "AT+QISEND=" + std::to_string(udp_id_) + "," + std::to_string(data.size());
if (!at_uart_->SendCommandWithData(command, 1000, true, data.data(), data.size())) {
ESP_LOGE(TAG, "Failed to send command");
return -1;
}
auto bits = xEventGroupWaitBits(event_group_handle_, EC801E_UDP_SEND_COMPLETE | EC801E_UDP_SEND_FAILED, pdTRUE, pdFALSE, pdMS_TO_TICKS(UDP_CONNECT_TIMEOUT_MS));
if (bits & EC801E_UDP_SEND_FAILED) {
ESP_LOGE(TAG, "Failed to send data");
return -1;
} else if (!(bits & EC801E_UDP_SEND_COMPLETE)) {
ESP_LOGE(TAG, "Send timeout");
return -1;
}
return data.size();
}

View File

@@ -0,0 +1,36 @@
#ifndef EC801E_UDP_H
#define EC801E_UDP_H
#include "udp.h"
#include "at_uart.h"
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#define EC801E_UDP_CONNECTED BIT0
#define EC801E_UDP_DISCONNECTED BIT1
#define EC801E_UDP_ERROR BIT2
#define EC801E_UDP_SEND_COMPLETE BIT3
#define EC801E_UDP_SEND_FAILED BIT4
#define EC801E_UDP_INITIALIZED BIT5
#define UDP_CONNECT_TIMEOUT_MS 10000
class Ec801EUdp : public Udp {
public:
Ec801EUdp(std::shared_ptr<AtUart> at_uart, int udp_id);
~Ec801EUdp();
bool Connect(const std::string& host, int port) override;
void Disconnect() override;
int Send(const std::string& data) override;
private:
std::shared_ptr<AtUart> at_uart_;
int udp_id_;
bool instance_active_ = false;
EventGroupHandle_t event_group_handle_;
std::list<UrcCallback>::iterator urc_callback_it_;
};
#endif // EC801E_UDP_H

View File

@@ -0,0 +1,137 @@
#include "esp_mqtt.h"
#include <esp_crt_bundle.h>
#include <esp_log.h>
static const char *TAG = "esp_mqtt";
EspMqtt::EspMqtt() {
event_group_handle_ = xEventGroupCreate();
}
EspMqtt::~EspMqtt() {
Disconnect();
if (event_group_handle_ != nullptr) {
vEventGroupDelete(event_group_handle_);
}
}
bool EspMqtt::Connect(const std::string broker_address, int broker_port, const std::string client_id, const std::string username, const std::string password) {
if (mqtt_client_handle_ != nullptr) {
Disconnect();
}
esp_mqtt_client_config_t mqtt_config = {};
mqtt_config.broker.address.hostname = broker_address.c_str();
mqtt_config.broker.address.port = broker_port;
if (broker_port == 8883) {
mqtt_config.broker.address.transport = MQTT_TRANSPORT_OVER_SSL;
mqtt_config.broker.verification.crt_bundle_attach = esp_crt_bundle_attach;
} else {
mqtt_config.broker.address.transport = MQTT_TRANSPORT_OVER_TCP;
}
mqtt_config.credentials.client_id = client_id.c_str();
mqtt_config.credentials.username = username.c_str();
mqtt_config.credentials.authentication.password = password.c_str();
mqtt_config.session.keepalive = keep_alive_seconds_;
mqtt_client_handle_ = esp_mqtt_client_init(&mqtt_config);
esp_mqtt_client_register_event(mqtt_client_handle_, MQTT_EVENT_ANY, [](void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
((EspMqtt*)handler_args)->MqttEventCallback(base, event_id, event_data);
}, this);
esp_mqtt_client_start(mqtt_client_handle_);
auto bits = xEventGroupWaitBits(event_group_handle_, MQTT_CONNECTED_EVENT | MQTT_DISCONNECTED_EVENT | MQTT_ERROR_EVENT,
pdTRUE, pdFALSE, pdMS_TO_TICKS(MQTT_CONNECT_TIMEOUT_MS));
return bits & MQTT_CONNECTED_EVENT;
}
void EspMqtt::MqttEventCallback(esp_event_base_t base, int32_t event_id, void *event_data) {
auto event = (esp_mqtt_event_t*)event_data;
switch (event_id) {
case MQTT_EVENT_CONNECTED:
if (!connected_) {
connected_ = true;
if (on_connected_callback_) {
on_connected_callback_();
}
}
xEventGroupSetBits(event_group_handle_, MQTT_CONNECTED_EVENT);
break;
case MQTT_EVENT_DISCONNECTED:
if (connected_) {
connected_ = false;
if (on_disconnected_callback_) {
on_disconnected_callback_();
}
}
xEventGroupSetBits(event_group_handle_, MQTT_DISCONNECTED_EVENT);
break;
case MQTT_EVENT_DATA: {
auto topic = std::string(event->topic, event->topic_len);
auto payload = std::string(event->data, event->data_len);
if (event->data_len == event->total_data_len) {
if (on_message_callback_) {
on_message_callback_(topic, payload);
}
} else {
message_payload_.append(payload);
if (message_payload_.size() >= event->total_data_len && on_message_callback_) {
on_message_callback_(topic, message_payload_);
message_payload_.clear();
}
}
break;
}
case MQTT_EVENT_BEFORE_CONNECT:
break;
case MQTT_EVENT_SUBSCRIBED:
break;
case MQTT_EVENT_ERROR: {
xEventGroupSetBits(event_group_handle_, MQTT_ERROR_EVENT);
const char* error_name = esp_err_to_name(event->error_handle->esp_tls_last_esp_err);
ESP_LOGI(TAG, "MQTT error occurred: %s", error_name);
if (on_error_callback_) {
on_error_callback_(error_name ? error_name : "MQTT error");
}
break;
}
default:
ESP_LOGI(TAG, "Unhandled event id %ld", event_id);
break;
}
}
void EspMqtt::Disconnect() {
if (mqtt_client_handle_ != nullptr) {
esp_mqtt_client_stop(mqtt_client_handle_);
esp_mqtt_client_destroy(mqtt_client_handle_);
mqtt_client_handle_ = nullptr;
}
connected_ = false;
xEventGroupClearBits(event_group_handle_, MQTT_CONNECTED_EVENT | MQTT_DISCONNECTED_EVENT | MQTT_ERROR_EVENT);
}
bool EspMqtt::Publish(const std::string topic, const std::string payload, int qos) {
if (!connected_) {
return false;
}
return esp_mqtt_client_publish(mqtt_client_handle_, topic.c_str(), payload.data(), payload.size(), qos, 0) == 0;
}
bool EspMqtt::Subscribe(const std::string topic, int qos) {
if (!connected_) {
return false;
}
return esp_mqtt_client_subscribe_single(mqtt_client_handle_, topic.c_str(), qos) == 0;
}
bool EspMqtt::Unsubscribe(const std::string topic) {
if (!connected_) {
return false;
}
return esp_mqtt_client_unsubscribe(mqtt_client_handle_, topic.c_str()) == 0;
}
bool EspMqtt::IsConnected() {
return connected_;
}

View File

@@ -0,0 +1,41 @@
#ifndef ESP_MQTT_H
#define ESP_MQTT_H
#include "mqtt.h"
#include <mqtt_client.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/event_groups.h>
#include <string>
#include <functional>
#define MQTT_CONNECT_TIMEOUT_MS 10000
#define MQTT_INITIALIZED_EVENT BIT0
#define MQTT_CONNECTED_EVENT BIT1
#define MQTT_DISCONNECTED_EVENT BIT2
#define MQTT_ERROR_EVENT BIT3
class EspMqtt : public Mqtt {
public:
EspMqtt();
~EspMqtt();
bool Connect(const std::string broker_address, int broker_port, const std::string client_id, const std::string username, const std::string password);
void Disconnect();
bool Publish(const std::string topic, const std::string payload, int qos = 0);
bool Subscribe(const std::string topic, int qos = 0);
bool Unsubscribe(const std::string topic);
bool IsConnected();
private:
bool connected_ = false;
EventGroupHandle_t event_group_handle_;
std::string message_payload_;
esp_mqtt_client_handle_t mqtt_client_handle_ = nullptr;
void MqttEventCallback(esp_event_base_t base, int32_t event_id, void *event_data);
};
#endif

View File

@@ -0,0 +1,41 @@
#include "esp_network.h"
#include "esp_tcp.h"
#include "esp_ssl.h"
#include "esp_udp.h"
#include "esp_mqtt.h"
#include "http_client.h"
#include "web_socket.h"
EspNetwork::EspNetwork() {
}
EspNetwork::~EspNetwork() {
}
std::unique_ptr<Http> EspNetwork::CreateHttp(int connect_id) {
return std::make_unique<HttpClient>(this, connect_id);
}
std::unique_ptr<Tcp> EspNetwork::CreateTcp(int connect_id) {
return std::make_unique<EspTcp>();
}
std::unique_ptr<Tcp> EspNetwork::CreateSsl(int connect_id) {
return std::make_unique<EspSsl>();
}
std::unique_ptr<Udp> EspNetwork::CreateUdp(int connect_id) {
return std::make_unique<EspUdp>();
}
std::unique_ptr<Mqtt> EspNetwork::CreateMqtt(int connect_id) {
return std::make_unique<EspMqtt>();
}
std::unique_ptr<WebSocket> EspNetwork::CreateWebSocket(int connect_id) {
return std::make_unique<WebSocket>(this, connect_id);
}

View File

@@ -0,0 +1,136 @@
#include "esp_ssl.h"
#include <esp_log.h>
#include <esp_crt_bundle.h>
#include <cstring>
#include <unistd.h>
static const char *TAG = "EspSsl";
EspSsl::EspSsl() {
event_group_ = xEventGroupCreate();
}
EspSsl::~EspSsl() {
Disconnect();
if (event_group_ != nullptr) {
vEventGroupDelete(event_group_);
event_group_ = nullptr;
}
}
bool EspSsl::Connect(const std::string& host, int port) {
if (tls_client_ != nullptr) {
ESP_LOGE(TAG, "tls client has been initialized");
return false;
}
tls_client_ = esp_tls_init();
if (tls_client_ == nullptr) {
ESP_LOGE(TAG, "Failed to initialize TLS");
return false;
}
esp_tls_cfg_t cfg = {};
cfg.crt_bundle_attach = esp_crt_bundle_attach;
int ret = esp_tls_conn_new_sync(host.c_str(), host.length(), port, &cfg, tls_client_);
if (ret != 1) {
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
esp_tls_conn_destroy(tls_client_);
tls_client_ = nullptr;
return false;
}
connected_ = true;
xEventGroupClearBits(event_group_, ESP_SSL_EVENT_RECEIVE_TASK_EXIT);
xTaskCreate([](void* arg) {
EspSsl* ssl = (EspSsl*)arg;
ssl->ReceiveTask();
xEventGroupSetBits(ssl->event_group_, ESP_SSL_EVENT_RECEIVE_TASK_EXIT);
vTaskDelete(NULL);
}, "ssl_receive", 4096, this, 1, &receive_task_handle_);
return true;
}
void EspSsl::Disconnect() {
connected_ = false;
// Close socket if it is open
if (tls_client_ != nullptr) {
int sockfd;
ESP_ERROR_CHECK(esp_tls_get_conn_sockfd(tls_client_, &sockfd));
if (sockfd >= 0) {
close(sockfd);
}
auto bits = xEventGroupWaitBits(event_group_, ESP_SSL_EVENT_RECEIVE_TASK_EXIT, pdFALSE, pdFALSE, pdMS_TO_TICKS(10000));
if (!(bits & ESP_SSL_EVENT_RECEIVE_TASK_EXIT)) {
ESP_LOGE(TAG, "Failed to wait for receive task exit");
}
esp_tls_conn_destroy(tls_client_);
tls_client_ = nullptr;
}
}
/* CONFIG_MBEDTLS_SSL_RENEGOTIATION should be disabled in sdkconfig.
* Otherwise, invalid memory access may be triggered.
*/
int EspSsl::Send(const std::string& data) {
if (!connected_) {
ESP_LOGE(TAG, "Not connected");
return -1;
}
size_t total_sent = 0;
size_t data_size = data.size();
const char* data_ptr = data.data();
while (total_sent < data_size) {
int ret = esp_tls_conn_write(tls_client_, data_ptr + total_sent, data_size - total_sent);
if (ret == ESP_TLS_ERR_SSL_WANT_WRITE) {
continue;
}
if (ret <= 0) {
ESP_LOGE(TAG, "SSL send failed: ret=%d, errno=%d", ret, errno);
return ret;
}
total_sent += ret;
}
return total_sent;
}
void EspSsl::ReceiveTask() {
std::string data;
while (connected_) {
data.resize(1500);
int ret = esp_tls_conn_read(tls_client_, data.data(), data.size());
if (ret == ESP_TLS_ERR_SSL_WANT_READ) {
continue;
}
if (ret <= 0) {
if (ret < 0) {
ESP_LOGE(TAG, "SSL receive failed: %d", ret);
}
connected_ = false;
// 接收失败或连接断开时调用断连回调
if (disconnect_callback_) {
disconnect_callback_();
}
break;
}
if (stream_callback_) {
data.resize(ret);
stream_callback_(data);
}
}
}

View File

@@ -0,0 +1,30 @@
#ifndef _ESP_SSL_H_
#define _ESP_SSL_H_
#include "tcp.h"
#include <esp_tls.h>
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <freertos/task.h>
#define ESP_SSL_EVENT_RECEIVE_TASK_EXIT 1
class EspSsl : public Tcp {
public:
EspSsl();
~EspSsl();
bool Connect(const std::string& host, int port) override;
void Disconnect() override;
int Send(const std::string& data) override;
private:
esp_tls_t* tls_client_ = nullptr;
EventGroupHandle_t event_group_ = nullptr;
TaskHandle_t receive_task_handle_ = nullptr;
void ReceiveTask();
};
#endif // _ESP_SSL_H_

View File

@@ -0,0 +1,130 @@
#include "esp_tcp.h"
#include <esp_log.h>
#include <unistd.h>
#include <cstring>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
#include <errno.h>
static const char *TAG = "EspTcp";
EspTcp::EspTcp() {
event_group_ = xEventGroupCreate();
}
EspTcp::~EspTcp() {
Disconnect();
if (event_group_ != nullptr) {
vEventGroupDelete(event_group_);
event_group_ = nullptr;
}
}
bool EspTcp::Connect(const std::string& host, int port) {
// 确保先断开已有连接
if (connected_) {
Disconnect();
}
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
// host is domain
struct hostent *server = gethostbyname(host.c_str());
if (server == NULL) {
ESP_LOGE(TAG, "Failed to get host by name");
return false;
}
memcpy(&server_addr.sin_addr, server->h_addr, server->h_length);
tcp_fd_ = socket(AF_INET, SOCK_STREAM, 0);
if (tcp_fd_ < 0) {
ESP_LOGE(TAG, "Failed to create socket");
return false;
}
int ret = connect(tcp_fd_, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (ret < 0) {
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
close(tcp_fd_);
tcp_fd_ = -1;
return false;
}
connected_ = true;
xEventGroupClearBits(event_group_, ESP_TCP_EVENT_RECEIVE_TASK_EXIT);
xTaskCreate([](void* arg) {
EspTcp* tcp = (EspTcp*)arg;
tcp->ReceiveTask();
xEventGroupSetBits(tcp->event_group_, ESP_TCP_EVENT_RECEIVE_TASK_EXIT);
vTaskDelete(NULL);
}, "tcp_receive", 4096, this, 1, &receive_task_handle_);
return true;
}
void EspTcp::Disconnect() {
connected_ = false;
if (tcp_fd_ != -1) {
close(tcp_fd_);
tcp_fd_ = -1;
auto bits = xEventGroupWaitBits(event_group_, ESP_TCP_EVENT_RECEIVE_TASK_EXIT, pdFALSE, pdFALSE, pdMS_TO_TICKS(10000));
if (!(bits & ESP_TCP_EVENT_RECEIVE_TASK_EXIT)) {
ESP_LOGE(TAG, "Failed to wait for receive task exit");
}
}
}
int EspTcp::Send(const std::string& data) {
if (!connected_) {
ESP_LOGE(TAG, "Not connected");
return -1;
}
size_t total_sent = 0;
size_t data_size = data.size();
const char* data_ptr = data.data();
while (total_sent < data_size) {
int ret = send(tcp_fd_, data_ptr + total_sent, data_size - total_sent, 0);
if (ret <= 0) {
ESP_LOGE(TAG, "Send failed: ret=%d, errno=%d", ret, errno);
return ret;
}
total_sent += ret;
}
return total_sent;
}
void EspTcp::ReceiveTask() {
std::string data;
while (connected_) {
data.resize(1500);
int ret = recv(tcp_fd_, data.data(), data.size(), 0);
if (ret <= 0) {
if (ret < 0) {
ESP_LOGE(TAG, "TCP receive failed: %d", ret);
}
connected_ = false;
// 接收失败或连接断开时调用断连回调
if (disconnect_callback_) {
disconnect_callback_();
}
break;
}
if (stream_callback_) {
data.resize(ret);
stream_callback_(data);
}
}
}

View File

@@ -0,0 +1,29 @@
#ifndef _ESP_TCP_H_
#define _ESP_TCP_H_
#include "tcp.h"
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <freertos/task.h>
#define ESP_TCP_EVENT_RECEIVE_TASK_EXIT 1
class EspTcp : public Tcp {
public:
EspTcp();
~EspTcp();
bool Connect(const std::string& host, int port) override;
void Disconnect() override;
int Send(const std::string& data) override;
private:
int tcp_fd_ = -1;
EventGroupHandle_t event_group_ = nullptr;
TaskHandle_t receive_task_handle_ = nullptr;
void ReceiveTask();
};
#endif // _ESP_TCP_H_

View File

@@ -0,0 +1,111 @@
#include "esp_udp.h"
#include <esp_log.h>
#include <unistd.h>
#include <cstring>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
static const char *TAG = "EspUdp";
EspUdp::EspUdp() : udp_fd_(-1) {
event_group_ = xEventGroupCreate();
}
EspUdp::~EspUdp() {
Disconnect();
if (event_group_ != nullptr) {
vEventGroupDelete(event_group_);
event_group_ = nullptr;
}
}
bool EspUdp::Connect(const std::string& host, int port) {
// 确保先断开已有连接
if (connected_) {
Disconnect();
}
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
// host is domain
struct hostent *server = gethostbyname(host.c_str());
if (server == NULL) {
ESP_LOGE(TAG, "Failed to get host by name");
return false;
}
memcpy(&server_addr.sin_addr, server->h_addr, server->h_length);
udp_fd_ = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_fd_ < 0) {
ESP_LOGE(TAG, "Failed to create socket");
return false;
}
int ret = connect(udp_fd_, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (ret < 0) {
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
close(udp_fd_);
udp_fd_ = -1;
return false;
}
connected_ = true;
xEventGroupClearBits(event_group_, ESP_UDP_EVENT_RECEIVE_TASK_EXIT);
xTaskCreate([](void* arg) {
EspUdp* udp = (EspUdp*)arg;
udp->ReceiveTask();
xEventGroupSetBits(udp->event_group_, ESP_UDP_EVENT_RECEIVE_TASK_EXIT);
vTaskDelete(NULL);
}, "udp_receive", 4096, this, 1, &receive_task_handle_);
return true;
}
void EspUdp::Disconnect() {
connected_ = false;
if (udp_fd_ != -1) {
close(udp_fd_);
udp_fd_ = -1;
auto bits = xEventGroupWaitBits(event_group_, ESP_UDP_EVENT_RECEIVE_TASK_EXIT, pdFALSE, pdFALSE, pdMS_TO_TICKS(10000));
if (!(bits & ESP_UDP_EVENT_RECEIVE_TASK_EXIT)) {
ESP_LOGE(TAG, "Failed to wait for receive task exit");
}
}
}
int EspUdp::Send(const std::string& data) {
if (!connected_) {
ESP_LOGE(TAG, "Not connected");
return -1;
}
int ret = send(udp_fd_, data.data(), data.size(), 0);
if (ret <= 0) {
ESP_LOGE(TAG, "Send failed: ret=%d, errno=%d", ret, errno);
}
return ret;
}
void EspUdp::ReceiveTask() {
std::string data;
while (connected_) {
data.resize(1500);
int ret = recv(udp_fd_, data.data(), data.size(), 0);
if (ret <= 0) {
connected_ = false;
break;
}
if (message_callback_) {
data.resize(ret);
message_callback_(data);
}
}
}

View File

@@ -0,0 +1,29 @@
#ifndef ESP_UDP_H
#define ESP_UDP_H
#include "udp.h"
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <freertos/task.h>
#define ESP_UDP_EVENT_RECEIVE_TASK_EXIT 1
class EspUdp : public Udp {
public:
EspUdp();
~EspUdp();
bool Connect(const std::string& host, int port) override;
void Disconnect() override;
int Send(const std::string& data) override;
private:
int udp_fd_;
EventGroupHandle_t event_group_ = nullptr;
TaskHandle_t receive_task_handle_ = nullptr;
void ReceiveTask();
};
#endif // ESP_UDP_H

View File

@@ -0,0 +1,724 @@
#include "http_client.h"
#include "network_interface.h"
#include <esp_log.h>
#include <cstring>
#include <cstdlib>
#include <sstream>
#include <chrono>
#include <algorithm>
#include <cctype>
static const char *TAG = "HttpClient";
HttpClient::HttpClient(NetworkInterface* network, int connect_id) : network_(network), connect_id_(connect_id) {
event_group_handle_ = xEventGroupCreate();
}
HttpClient::~HttpClient() {
if (connected_) {
Close();
}
vEventGroupDelete(event_group_handle_);
}
void HttpClient::SetTimeout(int timeout_ms) {
timeout_ms_ = timeout_ms;
}
void HttpClient::SetHeader(const std::string& key, const std::string& value) {
// 转换key为小写用于存储和查找但保留原始key用于输出
std::string lower_key = key;
std::transform(lower_key.begin(), lower_key.end(), lower_key.begin(), ::tolower);
headers_[lower_key] = HeaderEntry(key, value);
}
void HttpClient::SetContent(std::string&& content) {
content_ = std::move(content);
}
bool HttpClient::ParseUrl(const std::string& url) {
// 解析 URL: protocol://host:port/path
size_t protocol_end = url.find("://");
if (protocol_end == std::string::npos) {
ESP_LOGE(TAG, "Invalid URL format: %s", url.c_str());
return false;
}
protocol_ = url.substr(0, protocol_end);
std::transform(protocol_.begin(), protocol_.end(), protocol_.begin(), ::tolower);
size_t host_start = protocol_end + 3;
size_t path_start = url.find("/", host_start);
size_t port_start = url.find(":", host_start);
// 默认端口
if (protocol_ == "https") {
port_ = 443;
} else {
port_ = 80;
}
if (path_start == std::string::npos) {
path_ = "/";
if (port_start != std::string::npos) {
host_ = url.substr(host_start, port_start - host_start);
std::string port_str = url.substr(port_start + 1);
char* endptr;
long port = strtol(port_str.c_str(), &endptr, 10);
if (endptr != port_str.c_str() && *endptr == '\0' && port > 0 && port <= 65535) {
port_ = static_cast<int>(port);
} else {
ESP_LOGE(TAG, "Invalid port: %s", port_str.c_str());
return false;
}
} else {
host_ = url.substr(host_start);
}
} else {
path_ = url.substr(path_start);
if (port_start != std::string::npos && port_start < path_start) {
host_ = url.substr(host_start, port_start - host_start);
std::string port_str = url.substr(port_start + 1, path_start - port_start - 1);
char* endptr;
long port = strtol(port_str.c_str(), &endptr, 10);
if (endptr != port_str.c_str() && *endptr == '\0' && port > 0 && port <= 65535) {
port_ = static_cast<int>(port);
} else {
ESP_LOGE(TAG, "Invalid port: %s", port_str.c_str());
return false;
}
} else {
host_ = url.substr(host_start, path_start - host_start);
}
}
ESP_LOGD(TAG, "Parsed URL: protocol=%s, host=%s, port=%d, path=%s",
protocol_.c_str(), host_.c_str(), port_, path_.c_str());
return true;
}
std::string HttpClient::BuildHttpRequest() {
std::ostringstream request;
// 请求行
request << method_ << " " << path_ << " HTTP/1.1\r\n";
// Host 头
request << "Host: " << host_;
if ((protocol_ == "http" && port_ != 80) || (protocol_ == "https" && port_ != 443)) {
request << ":" << port_;
}
request << "\r\n";
// 用户自定义头部使用原始key输出
for (const auto& [lower_key, header_entry] : headers_) {
request << header_entry.original_key << ": " << header_entry.value << "\r\n";
}
// 内容相关头部(仅在用户未设置时添加)
bool user_set_content_length = headers_.find("content-length") != headers_.end();
bool user_set_transfer_encoding = headers_.find("transfer-encoding") != headers_.end();
bool has_content = content_.has_value() && !content_->empty();
if (has_content && !user_set_content_length) {
request << "Content-Length: " << content_->size() << "\r\n";
} else if ((method_ == "POST" || method_ == "PUT") && !user_set_content_length && !user_set_transfer_encoding) {
if (request_chunked_) {
request << "Transfer-Encoding: chunked\r\n";
} else {
request << "Content-Length: 0\r\n";
}
}
// 连接控制(仅在用户未设置时添加)
if (headers_.find("connection") == headers_.end()) {
request << "Connection: close\r\n";
}
// 结束头部
request << "\r\n";
ESP_LOGD(TAG, "HTTP request headers:\n%s", request.str().c_str());
// 请求体
if (has_content) {
request << *content_;
}
return request.str();
}
bool HttpClient::Open(const std::string& method, const std::string& url) {
method_ = method;
url_ = url;
// 重置状态
status_code_ = -1;
response_headers_.clear();
{
std::lock_guard<std::mutex> read_lock(read_mutex_);
body_chunks_.clear();
}
body_offset_ = 0;
content_length_ = 0;
total_body_received_ = 0;
eof_ = false;
headers_received_ = false;
response_chunked_ = false;
connection_error_ = false; // 重置连接错误状态
parse_state_ = ParseState::STATUS_LINE;
chunk_size_ = 0;
chunk_received_ = 0;
rx_buffer_.clear();
xEventGroupClearBits(event_group_handle_,
EC801E_HTTP_EVENT_HEADERS_RECEIVED |
EC801E_HTTP_EVENT_BODY_RECEIVED |
EC801E_HTTP_EVENT_ERROR |
EC801E_HTTP_EVENT_COMPLETE);
// 解析 URL
if (!ParseUrl(url)) {
return false;
}
// 建立 TCP 连接
if (protocol_ == "https") {
tcp_ = network_->CreateSsl(connect_id_);
} else {
tcp_ = network_->CreateTcp(connect_id_);
}
// 设置 TCP 数据接收回调
tcp_->OnStream([this](const std::string& data) {
OnTcpData(data);
});
// 设置 TCP 断开连接回调
tcp_->OnDisconnected([this]() {
OnTcpDisconnected();
});
if (!tcp_->Connect(host_, port_)) {
ESP_LOGE(TAG, "TCP connection failed");
return false;
}
connected_ = true;
request_chunked_ = (method_ == "POST" || method_ == "PUT") && !content_.has_value();
// 构建并发送 HTTP 请求
std::string http_request = BuildHttpRequest();
if (tcp_->Send(http_request) <= 0) {
ESP_LOGE(TAG, "Send HTTP request failed");
tcp_->Disconnect();
connected_ = false;
return false;
}
return true;
}
void HttpClient::Close() {
if (!connected_) {
return;
}
connected_ = false;
write_cv_.notify_all();
tcp_->Disconnect();
eof_ = true;
cv_.notify_all();
ESP_LOGD(TAG, "HTTP connection closed");
}
void HttpClient::OnTcpData(const std::string& data) {
std::lock_guard<std::mutex> lock(mutex_);
// 检查 body_chunks_ 大小,如果超过 8KB 且 heap 小于 32KB 则阻塞
{
std::unique_lock<std::mutex> read_lock(read_mutex_);
write_cv_.wait(read_lock, [this, size=data.size()] {
size_t total_size = size;
for (const auto& chunk : body_chunks_) {
total_size += chunk.data.size();
}
size_t free_heap = esp_get_free_heap_size();
return total_size < MAX_BODY_CHUNKS_SIZE || !connected_ || free_heap >= 32768;
});
}
rx_buffer_.append(data);
ProcessReceivedData();
cv_.notify_one();
}
void HttpClient::OnTcpDisconnected() {
std::lock_guard<std::mutex> lock(mutex_);
connected_ = false;
// 检查数据是否完整接收
if (headers_received_ && !IsDataComplete()) {
// 如果已接收头部但数据不完整,标记为连接错误
connection_error_ = true;
ESP_LOGE(TAG, "Connection closed prematurely, expected %u bytes but only received %u bytes",
content_length_, total_body_received_);
} else {
// 数据完整或还未开始接收响应体,正常结束
eof_ = true;
}
cv_.notify_all(); // 通知所有等待的读取操作
}
void HttpClient::ProcessReceivedData() {
while (!rx_buffer_.empty() && parse_state_ != ParseState::COMPLETE) {
switch (parse_state_) {
case ParseState::STATUS_LINE: {
if (!HasCompleteLine(rx_buffer_)) return; // 需要更多数据
std::string line = GetNextLine(rx_buffer_);
if (ParseStatusLine(line)) {
parse_state_ = ParseState::HEADERS;
} else {
SetError();
return;
}
break;
}
case ParseState::HEADERS: {
if (!HasCompleteLine(rx_buffer_)) return; // 需要更多数据
std::string line = GetNextLine(rx_buffer_);
// 检查是否为空行(头部结束标记)
// GetNextLine 已经移除了 \r所以空行就是 empty()
if (line.empty()) {
// 检查是否为 chunked 编码
auto it = response_headers_.find("transfer-encoding");
if (it != response_headers_.end() &&
it->second.value.find("chunked") != std::string::npos) {
response_chunked_ = true;
parse_state_ = ParseState::CHUNK_SIZE;
} else {
parse_state_ = ParseState::BODY;
auto cl_it = response_headers_.find("content-length");
if (cl_it != response_headers_.end()) {
char* endptr;
unsigned long length = strtoul(cl_it->second.value.c_str(), &endptr, 10);
if (endptr != cl_it->second.value.c_str() && *endptr == '\0') {
content_length_ = static_cast<size_t>(length);
} else {
ESP_LOGE(TAG, "Invalid Content-Length: %s", cl_it->second.value.c_str());
content_length_ = 0;
}
}
}
// 头部结束
headers_received_ = true;
xEventGroupSetBits(event_group_handle_, EC801E_HTTP_EVENT_HEADERS_RECEIVED);
} else {
if (!ParseHeaderLine(line)) {
SetError();
return;
}
}
break;
}
case ParseState::BODY: {
if (response_chunked_) {
ParseChunkedBody(rx_buffer_);
} else {
ParseRegularBody(rx_buffer_);
}
break;
}
case ParseState::CHUNK_SIZE: {
if (!HasCompleteLine(rx_buffer_)) return; // 需要更多数据
std::string line = GetNextLine(rx_buffer_);
chunk_size_ = ParseChunkSize(line);
chunk_received_ = 0;
if (chunk_size_ == 0) {
parse_state_ = ParseState::CHUNK_TRAILER;
} else {
parse_state_ = ParseState::CHUNK_DATA;
}
break;
}
case ParseState::CHUNK_DATA: {
size_t available = std::min(rx_buffer_.size(), chunk_size_ - chunk_received_);
if (available > 0) {
std::string chunk_data = rx_buffer_.substr(0, available);
AddBodyData(std::move(chunk_data));
total_body_received_ += available;
rx_buffer_.erase(0, available);
chunk_received_ += available;
if (chunk_received_ == chunk_size_) {
// 跳过 chunk 后的 CRLF
if (rx_buffer_.size() >= 2 && rx_buffer_.substr(0, 2) == "\r\n") {
rx_buffer_.erase(0, 2);
}
parse_state_ = ParseState::CHUNK_SIZE;
}
}
if (available == 0) return; // 需要更多数据
break;
}
case ParseState::CHUNK_TRAILER: {
if (!HasCompleteLine(rx_buffer_)) return; // 需要更多数据
std::string line = GetNextLine(rx_buffer_);
if (line.empty()) {
parse_state_ = ParseState::COMPLETE;
eof_ = true;
xEventGroupSetBits(event_group_handle_, EC801E_HTTP_EVENT_COMPLETE);
}
// 忽略 trailer 头部
break;
}
case ParseState::COMPLETE:
return;
}
}
// 检查是否完成(非 chunked 模式)
if (parse_state_ == ParseState::BODY && !response_chunked_ &&
content_length_ > 0 && total_body_received_ >= content_length_) {
parse_state_ = ParseState::COMPLETE;
eof_ = true;
xEventGroupSetBits(event_group_handle_, EC801E_HTTP_EVENT_COMPLETE);
ESP_LOGD(TAG, "HTTP response body received: %u/%u bytes", total_body_received_, content_length_);
}
}
bool HttpClient::ParseStatusLine(const std::string& line) {
// HTTP/1.1 200 OK
std::istringstream iss(line);
std::string version, status_str, reason;
if (!(iss >> version >> status_str)) {
ESP_LOGE(TAG, "Invalid status line: %s", line.c_str());
return false;
}
std::getline(iss, reason); // 获取剩余部分作为原因短语
// 安全地解析状态码
char* endptr;
long status = strtol(status_str.c_str(), &endptr, 10);
if (endptr == status_str.c_str() || *endptr != '\0' || status < 100 || status > 999) {
ESP_LOGE(TAG, "Parse status code failed: %s", status_str.c_str());
return false;
}
status_code_ = static_cast<int>(status);
ESP_LOGD(TAG, "HTTP status code: %d", status_code_);
return true;
}
bool HttpClient::ParseHeaderLine(const std::string& line) {
size_t colon_pos = line.find(':');
if (colon_pos == std::string::npos) {
ESP_LOGE(TAG, "Invalid header line: %s", line.c_str());
return false;
}
std::string key = line.substr(0, colon_pos);
std::string value = line.substr(colon_pos + 1);
// 去除前后空格
key.erase(0, key.find_first_not_of(" \t"));
key.erase(key.find_last_not_of(" \t") + 1);
value.erase(0, value.find_first_not_of(" \t"));
value.erase(value.find_last_not_of(" \t\r\n") + 1);
// 转换为小写键名用于存储和查找同时保存原始key
std::string lower_key = key;
std::transform(lower_key.begin(), lower_key.end(), lower_key.begin(), ::tolower);
response_headers_[lower_key] = HeaderEntry(key, value);
return true;
}
void HttpClient::ParseChunkedBody(const std::string& data) {
// Chunked body 在 ProcessReceivedData 中的状态机中处理
}
void HttpClient::ParseRegularBody(const std::string& data) {
if (!data.empty()) {
AddBodyData(data); // 使用新的方法添加数据
total_body_received_ += data.size(); // 累加接收的字节数
rx_buffer_.clear();
}
}
size_t HttpClient::ParseChunkSize(const std::string& chunk_size_line) {
// 解析 chunk size十六进制
std::string size_str = chunk_size_line;
// 移除 CRLF 和任何扩展
size_t semi_pos = size_str.find(';');
if (semi_pos != std::string::npos) {
size_str = size_str.substr(0, semi_pos);
}
size_str.erase(size_str.find_last_not_of(" \t\r\n") + 1);
// 安全地解析十六进制 chunk 大小
char* endptr;
unsigned long chunk_size = strtoul(size_str.c_str(), &endptr, 16);
if (endptr == size_str.c_str() || *endptr != '\0') {
ESP_LOGE(TAG, "Parse chunk size failed: %s", size_str.c_str());
return 0;
}
return static_cast<size_t>(chunk_size);
}
std::string HttpClient::GetNextLine(std::string& buffer) {
size_t pos = buffer.find('\n');
if (pos == std::string::npos) {
return ""; // 没有完整的行
}
std::string line = buffer.substr(0, pos);
buffer.erase(0, pos + 1);
// 移除 CR
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
return line;
}
bool HttpClient::HasCompleteLine(const std::string& buffer) {
return buffer.find('\n') != std::string::npos;
}
void HttpClient::SetError() {
ESP_LOGE(TAG, "HTTP parse error");
xEventGroupSetBits(event_group_handle_, EC801E_HTTP_EVENT_ERROR);
}
int HttpClient::Read(char* buffer, size_t buffer_size) {
std::unique_lock<std::mutex> read_lock(read_mutex_);
// 如果连接异常断开,返回错误
if (connection_error_) {
return -1;
}
// 如果已经到达文件末尾且没有更多数据返回0
if (eof_ && body_chunks_.empty()) {
return 0;
}
// 如果有数据可读,直接返回
while (!body_chunks_.empty()) {
auto& front_chunk = body_chunks_.front();
size_t bytes_read = front_chunk.read(buffer, buffer_size);
if (bytes_read > 0) {
// 如果当前chunk已读完移除它
if (front_chunk.empty()) {
body_chunks_.pop_front();
}
// 通知等待的写入操作
write_cv_.notify_one();
return static_cast<int>(bytes_read);
}
// 如果chunk为空移除它并继续下一个
body_chunks_.pop_front();
}
// 如果连接已断开,检查是否有错误
if (!connected_) {
if (connection_error_) {
return -1; // 连接异常断开
}
return 0; // 正常结束
}
// 等待数据或连接关闭
auto timeout = std::chrono::milliseconds(timeout_ms_);
bool received = cv_.wait_for(read_lock, timeout, [this] {
return !body_chunks_.empty() || eof_ || !connected_ || connection_error_;
});
if (!received) {
ESP_LOGE(TAG, "Wait for HTTP content receive timeout");
return -1;
}
// 再次检查连接错误状态
if (connection_error_) {
return -1;
}
// 再次检查是否有数据可读
while (!body_chunks_.empty()) {
auto& front_chunk = body_chunks_.front();
size_t bytes_read = front_chunk.read(buffer, buffer_size);
if (bytes_read > 0) {
// 如果当前chunk已读完移除它
if (front_chunk.empty()) {
body_chunks_.pop_front();
}
// 通知等待的写入操作
write_cv_.notify_one();
return static_cast<int>(bytes_read);
}
// 如果chunk为空移除它并继续下一个
body_chunks_.pop_front();
}
// 连接已关闭或到达EOF返回0
return 0;
}
int HttpClient::Write(const char* buffer, size_t buffer_size) {
if (!connected_ || !request_chunked_) {
ESP_LOGE(TAG, "Cannot write: connection closed or not chunked mode");
return -1;
}
if (buffer_size == 0) {
// 发送结束 chunk
std::string end_chunk = "0\r\n\r\n";
return tcp_->Send(end_chunk);
}
// 发送 chunk
std::ostringstream chunk;
chunk << std::hex << buffer_size << "\r\n";
chunk.write(buffer, buffer_size);
chunk << "\r\n";
std::string chunk_data = chunk.str();
return tcp_->Send(chunk_data);
}
int HttpClient::GetStatusCode() {
if (!headers_received_) {
// 等待头部接收
auto bits = xEventGroupWaitBits(event_group_handle_,
EC801E_HTTP_EVENT_HEADERS_RECEIVED | EC801E_HTTP_EVENT_ERROR,
pdFALSE, pdFALSE,
pdMS_TO_TICKS(timeout_ms_));
if (bits & EC801E_HTTP_EVENT_ERROR) {
return -1;
}
if (!(bits & EC801E_HTTP_EVENT_HEADERS_RECEIVED)) {
ESP_LOGE(TAG, "Wait for HTTP headers receive timeout");
return -1;
}
}
return status_code_;
}
std::string HttpClient::GetResponseHeader(const std::string& key) const {
// 转换为小写进行查找
std::string lower_key = key;
std::transform(lower_key.begin(), lower_key.end(), lower_key.begin(), ::tolower);
auto it = response_headers_.find(lower_key);
if (it != response_headers_.end()) {
return it->second.value;
}
return "";
}
size_t HttpClient::GetBodyLength() {
if (!headers_received_) {
GetStatusCode(); // 这会等待头部接收
}
if (response_chunked_) {
return 0; // Chunked 模式下长度未知
}
return content_length_;
}
void HttpClient::AddBodyData(const std::string& data) {
if (data.empty()) return;
std::lock_guard<std::mutex> read_lock(read_mutex_);
body_chunks_.emplace_back(data); // 使用构造函数,避免额外的拷贝
cv_.notify_one(); // 通知有新数据
write_cv_.notify_one(); // 通知写入操作
}
void HttpClient::AddBodyData(std::string&& data) {
if (data.empty()) return;
std::lock_guard<std::mutex> read_lock(read_mutex_);
body_chunks_.emplace_back(std::move(data)); // 使用移动语义,避免拷贝
cv_.notify_one(); // 通知有新数据
write_cv_.notify_one(); // 通知写入操作
}
std::string HttpClient::ReadAll() {
std::unique_lock<std::mutex> lock(mutex_);
// 等待完成或出错
auto timeout = std::chrono::milliseconds(timeout_ms_);
bool completed = cv_.wait_for(lock, timeout, [this] {
return eof_ || connection_error_;
});
if (!completed) {
ESP_LOGE(TAG, "Wait for HTTP content receive complete timeout");
return ""; // 超时返回空字符串
}
// 如果连接异常断开,返回空字符串并记录错误
if (connection_error_) {
ESP_LOGE(TAG, "Cannot read all data: connection closed prematurely");
return "";
}
// 收集所有数据
std::string result;
std::lock_guard<std::mutex> read_lock(read_mutex_);
for (const auto& chunk : body_chunks_) {
result.append(chunk.data);
}
return result;
}
bool HttpClient::IsDataComplete() const {
// 对于chunked编码如果parse_state_是COMPLETE说明接收完整
if (response_chunked_) {
return parse_state_ == ParseState::COMPLETE;
}
// 对于固定长度检查是否接收了完整的content-length
if (content_length_ > 0) {
return total_body_received_ >= content_length_;
}
// 如果没有content-length且不是chunked当连接关闭时认为完整
// 这种情况通常用于HTTP/1.0或者content-length为0的响应
return true;
}

View File

@@ -0,0 +1,112 @@
#include "ml307_at_modem.h"
#include <esp_log.h>
#include <esp_err.h>
#include <cassert>
#include <sstream>
#include <iomanip>
#include <cstring>
#include "ml307_tcp.h"
#include "ml307_ssl.h"
#include "ml307_udp.h"
#include "ml307_mqtt.h"
#include "ml307_http.h"
#include "web_socket.h"
#define TAG "Ml307AtModem"
Ml307AtModem::Ml307AtModem(std::shared_ptr<AtUart> at_uart) : AtModem(at_uart) {
// 子类特定的初始化在这里
// Reset HTTP instances
ResetConnections();
}
void Ml307AtModem::ResetConnections() {
at_uart_->SendCommand("AT+MHTTPDEL=0");
at_uart_->SendCommand("AT+MHTTPDEL=1");
at_uart_->SendCommand("AT+MHTTPDEL=2");
at_uart_->SendCommand("AT+MHTTPDEL=3");
}
void Ml307AtModem::HandleUrc(const std::string& command, const std::vector<AtArgumentValue>& arguments) {
// Handle Common URC
AtModem::HandleUrc(command, arguments);
// Handle ML307 URC
if (command == "MIPCALL" && arguments.size() >= 3) {
if (arguments[1].int_value == 1) {
auto ip = arguments[2].string_value;
ESP_LOGI(TAG, "PDP Context %d IP: %s", arguments[0].int_value, ip.c_str());
network_ready_ = true;
xEventGroupSetBits(event_group_handle_, AT_EVENT_NETWORK_READY);
}
} else if (command == "MATREADY") {
if (network_ready_) {
network_ready_ = false;
if (on_network_state_changed_) {
on_network_state_changed_(false);
}
}
}
}
void Ml307AtModem::Reboot() {
at_uart_->SendCommand("AT+MREBOOT=0");
}
bool Ml307AtModem::SetSleepMode(bool enable, int delay_seconds) {
if (enable) {
if (delay_seconds > 0) {
at_uart_->SendCommand("AT+MLPMCFG=\"delaysleep\"," + std::to_string(delay_seconds));
}
return at_uart_->SendCommand("AT+MLPMCFG=\"sleepmode\",2,0");
} else {
return at_uart_->SendCommand("AT+MLPMCFG=\"sleepmode\",0,0");
}
}
NetworkStatus Ml307AtModem::WaitForNetworkReady(int timeout_ms) {
NetworkStatus status = AtModem::WaitForNetworkReady(timeout_ms);
if (status == NetworkStatus::Ready) {
// Wait for IP address, maximum total wait time is 4270ms
int delay_ms = 10;
for (int i = 0; i < 10; i++) {
at_uart_->SendCommand("AT+MIPCALL?");
auto bits = xEventGroupWaitBits(event_group_handle_, AT_EVENT_NETWORK_READY, pdFALSE, pdTRUE, pdMS_TO_TICKS(delay_ms));
if (bits & AT_EVENT_NETWORK_READY) {
return NetworkStatus::Ready;
}
delay_ms = std::min(delay_ms * 2, 1000);
}
ESP_LOGE(TAG, "Network ready but no IP address");
}
return status;
}
std::unique_ptr<Http> Ml307AtModem::CreateHttp(int connect_id) {
return std::make_unique<Ml307Http>(at_uart_);
}
std::unique_ptr<Tcp> Ml307AtModem::CreateTcp(int connect_id) {
assert(connect_id >= 0);
return std::make_unique<Ml307Tcp>(at_uart_, connect_id);
}
std::unique_ptr<Tcp> Ml307AtModem::CreateSsl(int connect_id) {
assert(connect_id >= 0);
return std::make_unique<Ml307Ssl>(at_uart_, connect_id);
}
std::unique_ptr<Udp> Ml307AtModem::CreateUdp(int connect_id) {
assert(connect_id >= 0);
return std::make_unique<Ml307Udp>(at_uart_, connect_id);
}
std::unique_ptr<Mqtt> Ml307AtModem::CreateMqtt(int connect_id) {
assert(connect_id >= 0);
return std::make_unique<Ml307Mqtt>(at_uart_, connect_id);
}
std::unique_ptr<WebSocket> Ml307AtModem::CreateWebSocket(int connect_id) {
assert(connect_id >= 0);
return std::make_unique<WebSocket>(this, connect_id);
}

View File

@@ -0,0 +1,34 @@
#ifndef _ML307_AT_MODEM_H_
#define _ML307_AT_MODEM_H_
#include "at_modem.h"
#include "tcp.h"
#include "udp.h"
#include "http.h"
#include "mqtt.h"
#include "web_socket.h"
class Ml307AtModem : public AtModem {
public:
Ml307AtModem(std::shared_ptr<AtUart> at_uart);
~Ml307AtModem() override = default;
void Reboot() override;
bool SetSleepMode(bool enable, int delay_seconds=0) override;
NetworkStatus WaitForNetworkReady(int timeout_ms=-1) override;
// 实现基类的纯虚函数
std::unique_ptr<Http> CreateHttp(int connect_id) override;
std::unique_ptr<Tcp> CreateTcp(int connect_id) override;
std::unique_ptr<Tcp> CreateSsl(int connect_id) override;
std::unique_ptr<Udp> CreateUdp(int connect_id) override;
std::unique_ptr<Mqtt> CreateMqtt(int connect_id) override;
std::unique_ptr<WebSocket> CreateWebSocket(int connect_id) override;
protected:
void HandleUrc(const std::string& command, const std::vector<AtArgumentValue>& arguments) override;
void ResetConnections();
};
#endif // _ML307_AT_MODEM_H_

View File

@@ -0,0 +1,353 @@
#include "ml307_http.h"
#include <esp_log.h>
#include <cstring>
#include <sstream>
#include <chrono>
static const char *TAG = "Ml307Http";
Ml307Http::Ml307Http(std::shared_ptr<AtUart> at_uart) : at_uart_(at_uart) {
event_group_handle_ = xEventGroupCreate();
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
if (command == "MHTTPURC") {
if (arguments[1].int_value == http_id_) {
auto& type = arguments[0].string_value;
if (type == "header") {
eof_ = false;
body_offset_ = 0;
body_.clear();
status_code_ = arguments[2].int_value;
if (arguments.size() >= 5) {
ParseResponseHeaders(at_uart_->DecodeHex(arguments[4].string_value));
} else {
// FIXME: <header> 被分包发送
ESP_LOGE(TAG, "Missing header");
}
xEventGroupSetBits(event_group_handle_, ML307_HTTP_EVENT_HEADERS_RECEIVED);
} else if (type == "content") {
// +MHTTPURC: "content",<httpid>,<content_len>,<sum_len>,<cur_len>,<data>
std::string decoded_data;
if (arguments.size() >= 6) {
at_uart_->DecodeHexAppend(decoded_data, arguments[5].string_value.c_str(), arguments[5].string_value.length());
} else {
// FIXME: <data> 被分包发送
ESP_LOGE(TAG, "Missing content");
}
std::lock_guard<std::mutex> lock(mutex_);
body_.append(decoded_data);
// chunked传输时EOF由cur_len == 0判断非 chunked传输时EOF由content_len判断
if (response_chunked_) {
eof_ = arguments[4].int_value == 0;
} else {
eof_ = arguments[3].int_value >= arguments[2].int_value;
}
body_offset_ += arguments[4].int_value;
if (arguments[3].int_value > body_offset_) {
ESP_LOGE(TAG, "body_offset_: %u, arguments[3].int_value: %d", body_offset_, arguments[3].int_value);
Close();
return;
}
cv_.notify_one(); // 使用条件变量通知
} else if (type == "err") {
error_code_ = arguments[2].int_value;
xEventGroupSetBits(event_group_handle_, ML307_HTTP_EVENT_ERROR);
} else if (type == "ind") {
xEventGroupSetBits(event_group_handle_, ML307_HTTP_EVENT_IND);
} else {
ESP_LOGE(TAG, "Unknown HTTP event: %s", type.c_str());
}
}
} else if (command == "MHTTPCREATE") {
http_id_ = arguments[0].int_value;
instance_active_ = true;
xEventGroupSetBits(event_group_handle_, ML307_HTTP_EVENT_INITIALIZED);
} else if (command == "FIFO_OVERFLOW") {
xEventGroupSetBits(event_group_handle_, ML307_HTTP_EVENT_ERROR);
Close();
}
});
}
int Ml307Http::Read(char* buffer, size_t buffer_size) {
std::unique_lock<std::mutex> lock(mutex_);
if (eof_ && body_.empty()) {
return 0;
}
// 使用条件变量等待数据
auto timeout = std::chrono::milliseconds(timeout_ms_);
bool received = cv_.wait_for(lock, timeout, [this] {
return !body_.empty() || eof_;
});
if (!received) {
ESP_LOGE(TAG, "Timeout waiting for HTTP content to be received");
return -1;
}
size_t bytes_to_read = std::min(body_.size(), buffer_size);
std::memcpy(buffer, body_.data(), bytes_to_read);
body_.erase(0, bytes_to_read);
return bytes_to_read;
}
int Ml307Http::Write(const char* buffer, size_t buffer_size) {
if (buffer_size == 0) { // FIXME: 模组好像不支持发送空数据
std::string command = "AT+MHTTPCONTENT=" + std::to_string(http_id_) + ",0,2,\"0D0A\"";
at_uart_->SendCommand(command);
return 0;
}
std::string command = "AT+MHTTPCONTENT=" + std::to_string(http_id_) + ",1," + std::to_string(buffer_size);
at_uart_->SendCommand(command);
at_uart_->SendCommand(std::string(buffer, buffer_size));
return buffer_size;
}
Ml307Http::~Ml307Http() {
if (instance_active_) {
Close();
}
at_uart_->UnregisterUrcCallback(urc_callback_it_);
vEventGroupDelete(event_group_handle_);
}
void Ml307Http::SetHeader(const std::string& key, const std::string& value) {
headers_[key] = value;
}
void Ml307Http::SetContent(std::string&& content) {
content_ = std::make_optional(std::move(content));
}
void Ml307Http::SetTimeout(int timeout_ms) {
timeout_ms_ = timeout_ms;
}
void Ml307Http::ParseResponseHeaders(const std::string& headers) {
std::istringstream iss(headers);
std::string line;
while (std::getline(iss, line)) {
std::istringstream line_iss(line);
std::string key, value;
std::getline(line_iss, key, ':');
std::getline(line_iss, value);
// 去除前后空格
key.erase(0, key.find_first_not_of(" \t"));
key.erase(key.find_last_not_of(" \t") + 1);
value.erase(0, value.find_first_not_of(" \t"));
value.erase(value.find_last_not_of(" \t\r\n") + 1);
response_headers_[key] = value;
// 检查是否为chunked传输编码
if (key == "Transfer-Encoding" && value.find("chunked") != std::string::npos) {
response_chunked_ = true;
ESP_LOGI(TAG, "Found chunked transfer encoding");
}
}
}
bool Ml307Http::Open(const std::string& method, const std::string& url) {
method_ = method;
url_ = url;
// 判断是否为需要发送内容的HTTP方法
bool method_supports_content = (method_ == "POST" || method_ == "PUT");
// 解析URL
size_t protocol_end = url.find("://");
if (protocol_end != std::string::npos) {
protocol_ = url.substr(0, protocol_end);
size_t host_start = protocol_end + 3;
size_t path_start = url.find("/", host_start);
if (path_start != std::string::npos) {
host_ = url.substr(host_start, path_start - host_start);
path_ = url.substr(path_start);
} else {
host_ = url.substr(host_start);
path_ = "/";
}
} else {
// URL格式不正确
ESP_LOGE(TAG, "Invalid URL format");
return false;
}
// 创建HTTP连接
std::string command = "AT+MHTTPCREATE=\"" + protocol_ + "://" + host_ + "\"";
if (!at_uart_->SendCommand(command)) {
ESP_LOGE(TAG, "Failed to create HTTP connection");
return false;
}
auto bits = xEventGroupWaitBits(event_group_handle_, ML307_HTTP_EVENT_INITIALIZED, pdTRUE, pdFALSE, pdMS_TO_TICKS(timeout_ms_));
if (!(bits & ML307_HTTP_EVENT_INITIALIZED)) {
ESP_LOGE(TAG, "Timeout waiting for HTTP connection to be created");
return false;
}
request_chunked_ = method_supports_content && !content_.has_value();
ESP_LOGI(TAG, "HTTP connection created, ID: %d, protocol: %s, host: %s", http_id_, protocol_.c_str(), host_.c_str());
if (protocol_ == "https") {
command = "AT+MHTTPCFG=\"ssl\"," + std::to_string(http_id_) + ",1,0";
at_uart_->SendCommand(command);
}
if (request_chunked_) {
command = "AT+MHTTPCFG=\"chunked\"," + std::to_string(http_id_) + ",1";
at_uart_->SendCommand(command);
}
// Set HEX encoding OFF
command = "AT+MHTTPCFG=\"encoding\"," + std::to_string(http_id_) + ",0,0";
at_uart_->SendCommand(command);
// Set timeout (seconds): connect timeout, response timeout, input timeout
// sprintf(command, "AT+MHTTPCFG=\"timeout\",%d,%d,%d,%d", http_id_, timeout_ms_ / 1000, timeout_ms_ / 1000, timeout_ms_ / 1000);
// modem_.Command(command);
// Set headers
for (auto it = headers_.begin(); it != headers_.end(); it++) {
auto line = it->first + ": " + it->second;
bool is_last = std::next(it) == headers_.end();
command = "AT+MHTTPHEADER=" + std::to_string(http_id_) + "," + std::to_string(is_last ? 0 : 1) + "," + std::to_string(line.size()) + ",\"" + line + "\"";
at_uart_->SendCommand(command);
}
if (method_supports_content && content_.has_value()) {
command = "AT+MHTTPCONTENT=" + std::to_string(http_id_) + ",0," + std::to_string(content_.value().size());
at_uart_->SendCommand(command);
at_uart_->SendCommand(content_.value());
content_ = std::nullopt;
}
// Set HEX encoding ON
command = "AT+MHTTPCFG=\"encoding\"," + std::to_string(http_id_) + ",1,1";
at_uart_->SendCommand(command);
// Send request
// method to value: 1. GET 2. POST 3. PUT 4. DELETE 5. HEAD
const char* methods[6] = {"UNKNOWN", "GET", "POST", "PUT", "DELETE", "HEAD"};
int method_value = 1;
for (int i = 0; i < 6; i++) {
if (strcmp(methods[i], method_.c_str()) == 0) {
method_value = i;
break;
}
}
command = "AT+MHTTPREQUEST=" + std::to_string(http_id_) + "," + std::to_string(method_value) + ",0,";
if (!at_uart_->SendCommand(command + at_uart_->EncodeHex(path_))) {
ESP_LOGE(TAG, "Failed to send HTTP request");
return false;
}
if (request_chunked_) {
auto bits = xEventGroupWaitBits(event_group_handle_, ML307_HTTP_EVENT_IND, pdTRUE, pdFALSE, pdMS_TO_TICKS(timeout_ms_));
if (!(bits & ML307_HTTP_EVENT_IND)) {
ESP_LOGE(TAG, "Timeout waiting for HTTP IND");
return false;
}
}
return true;
}
bool Ml307Http::FetchHeaders() {
// Wait for headers
auto bits = xEventGroupWaitBits(event_group_handle_, ML307_HTTP_EVENT_HEADERS_RECEIVED | ML307_HTTP_EVENT_ERROR, pdTRUE, pdFALSE, pdMS_TO_TICKS(timeout_ms_));
if (bits & ML307_HTTP_EVENT_ERROR) {
ESP_LOGE(TAG, "HTTP request error: %s", ErrorCodeToString(error_code_).c_str());
return false;
}
if (!(bits & ML307_HTTP_EVENT_HEADERS_RECEIVED)) {
ESP_LOGE(TAG, "Timeout waiting for HTTP headers to be received");
return false;
}
auto it = response_headers_.find("Content-Length");
if (it != response_headers_.end()) {
content_length_ = std::stoul(it->second);
}
ESP_LOGI(TAG, "HTTP request successful, status code: %d", status_code_);
return true;
}
int Ml307Http::GetStatusCode() {
if (status_code_ == -1) {
if (!FetchHeaders()) {
return -1;
}
}
return status_code_;
}
size_t Ml307Http::GetBodyLength() {
if (status_code_ == -1) {
if (!FetchHeaders()) {
return 0;
}
}
return content_length_;
}
std::string Ml307Http::ReadAll() {
std::unique_lock<std::mutex> lock(mutex_);
auto timeout = std::chrono::milliseconds(timeout_ms_);
bool received = cv_.wait_for(lock, timeout, [this] {
return eof_;
});
if (!received) {
ESP_LOGE(TAG, "Timeout waiting for HTTP content to be received");
return body_;
}
return body_;
}
void Ml307Http::Close() {
if (!instance_active_) {
return;
}
std::string command = "AT+MHTTPDEL=" + std::to_string(http_id_);
at_uart_->SendCommand(command);
instance_active_ = false;
eof_ = true;
cv_.notify_one();
ESP_LOGI(TAG, "HTTP connection closed, ID: %d", http_id_);
}
std::string Ml307Http::ErrorCodeToString(int error_code) {
switch (error_code) {
case 1: return "Domain name resolution failed";
case 2: return "Connection to server failed";
case 3: return "Connection to server timeout";
case 4: return "SSL handshake failed";
case 5: return "Connection abnormal disconnection";
case 6: return "Request response timeout";
case 7: return "Data reception parsing failed";
case 8: return "Cache space insufficient";
case 9: return "Data packet loss";
case 10: return "File write failed";
case 255: return "Unknown error";
default: return "Undefined error";
}
}
std::string Ml307Http::GetResponseHeader(const std::string& key) const {
auto it = response_headers_.find(key);
if (it != response_headers_.end()) {
return it->second;
}
return "";
}

View File

@@ -0,0 +1,72 @@
#ifndef ML307_HTTP_TRANSPORT_H
#define ML307_HTTP_TRANSPORT_H
#include "at_uart.h"
#include "http.h"
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <map>
#include <string>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <optional>
#define ML307_HTTP_EVENT_INITIALIZED (1 << 0)
#define ML307_HTTP_EVENT_ERROR (1 << 2)
#define ML307_HTTP_EVENT_HEADERS_RECEIVED (1 << 3)
#define ML307_HTTP_EVENT_IND (1 << 4)
class Ml307Http : public Http {
public:
Ml307Http(std::shared_ptr<AtUart> at_uart);
~Ml307Http();
void SetTimeout(int timeout_ms) override;
void SetHeader(const std::string& key, const std::string& value) override;
void SetContent(std::string&& content) override;
bool Open(const std::string& method, const std::string& url) override;
void Close() override;
int Read(char* buffer, size_t buffer_size) override;
int Write(const char* buffer, size_t buffer_size) override;
int GetStatusCode() override;
std::string GetResponseHeader(const std::string& key) const override;
size_t GetBodyLength() override;
std::string ReadAll() override;
private:
std::shared_ptr<AtUart> at_uart_;
EventGroupHandle_t event_group_handle_;
std::mutex mutex_;
std::condition_variable cv_;
int http_id_ = -1;
int status_code_ = -1;
int error_code_ = -1;
int timeout_ms_ = 30000;
std::string rx_buffer_;
std::list<UrcCallback>::iterator urc_callback_it_;
std::map<std::string, std::string> headers_;
std::string url_;
std::string method_;
std::string protocol_;
std::string host_;
std::string path_;
std::optional<std::string> content_ = std::nullopt;
std::map<std::string, std::string> response_headers_;
std::string body_;
size_t body_offset_ = 0;
size_t content_length_ = 0;
bool eof_ = false;
bool instance_active_ = false;
bool request_chunked_ = false;
bool response_chunked_ = false;
bool FetchHeaders();
void ParseResponseHeaders(const std::string& headers);
std::string ErrorCodeToString(int error_code);
};
#endif

View File

@@ -0,0 +1,196 @@
#include "ml307_mqtt.h"
#include <esp_log.h>
static const char *TAG = "Ml307Mqtt";
Ml307Mqtt::Ml307Mqtt(std::shared_ptr<AtUart> at_uart, int mqtt_id) : at_uart_(at_uart), mqtt_id_(mqtt_id) {
event_group_handle_ = xEventGroupCreate();
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
if (command == "MQTTURC" && arguments.size() >= 2) {
if (arguments[1].int_value == mqtt_id_) {
auto type = arguments[0].string_value;
if (type == "conn") {
int error_code = arguments[2].int_value;
if (error_code == 0) {
if (!connected_) {
connected_ = true;
if (on_connected_callback_) {
on_connected_callback_();
}
}
xEventGroupSetBits(event_group_handle_, MQTT_CONNECTED_EVENT);
} else {
if (connected_) {
connected_ = false;
if (on_disconnected_callback_) {
on_disconnected_callback_();
}
}
xEventGroupSetBits(event_group_handle_, MQTT_DISCONNECTED_EVENT);
}
if (error_code == 5 || error_code == 6) {
auto error_message = ErrorToString(error_code);
ESP_LOGW(TAG, "MQTT error occurred: %s", error_message.c_str());
if (on_error_callback_) {
on_error_callback_(error_message);
}
}
} else if (type == "suback") {
} else if (type == "publish" && arguments.size() >= 7) {
auto topic = arguments[3].string_value;
if (arguments[4].int_value == arguments[5].int_value) {
if (on_message_callback_) {
on_message_callback_(topic, at_uart_->DecodeHex(arguments[6].string_value));
}
} else {
message_payload_.append(at_uart_->DecodeHex(arguments[6].string_value));
if (message_payload_.size() >= arguments[4].int_value && on_message_callback_) {
on_message_callback_(topic, message_payload_);
message_payload_.clear();
}
}
} else {
ESP_LOGI(TAG, "unhandled MQTT event: %s", type.c_str());
}
}
} else if (command == "MQTTSTATE" && arguments.size() == 1) {
connected_ = arguments[0].int_value != 3;
xEventGroupSetBits(event_group_handle_, MQTT_INITIALIZED_EVENT);
}
});
}
Ml307Mqtt::~Ml307Mqtt() {
at_uart_->UnregisterUrcCallback(urc_callback_it_);
vEventGroupDelete(event_group_handle_);
}
bool Ml307Mqtt::Connect(const std::string broker_address, int broker_port, const std::string client_id, const std::string username, const std::string password) {
EventBits_t bits;
if (IsConnected()) {
// 断开之前的连接
Disconnect();
bits = xEventGroupWaitBits(event_group_handle_, MQTT_DISCONNECTED_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(MQTT_CONNECT_TIMEOUT_MS));
if (!(bits & MQTT_DISCONNECTED_EVENT)) {
ESP_LOGE(TAG, "Failed to disconnect from previous connection");
return false;
}
}
if (broker_port == 8883) {
if (!at_uart_->SendCommand(std::string("AT+MQTTCFG=\"ssl\",") + std::to_string(mqtt_id_) + ",1")) {
ESP_LOGE(TAG, "Failed to set MQTT to use SSL");
return false;
}
}
// Set clean session
if (!at_uart_->SendCommand(std::string("AT+MQTTCFG=\"clean\",") + std::to_string(mqtt_id_) + ",1")) {
ESP_LOGE(TAG, "Failed to set MQTT clean session");
return false;
}
// Set keep alive and ping interval both to the same value
if (!at_uart_->SendCommand(std::string("AT+MQTTCFG=\"keepalive\",") + std::to_string(mqtt_id_) + "," + std::to_string(keep_alive_seconds_))) {
ESP_LOGE(TAG, "Failed to set MQTT keepalive interval");
return false;
}
if (!at_uart_->SendCommand(std::string("AT+MQTTCFG=\"pingreq\",") + std::to_string(mqtt_id_) + "," + std::to_string(keep_alive_seconds_))) {
ESP_LOGE(TAG, "Failed to set MQTT ping interval");
return false;
}
// Set HEX encoding (ASCII for sending, HEX for receiving)
if (!at_uart_->SendCommand("AT+MQTTCFG=\"encoding\"," + std::to_string(mqtt_id_) + ",0,1")) {
ESP_LOGE(TAG, "Failed to set MQTT to use HEX encoding");
return false;
}
xEventGroupClearBits(event_group_handle_, MQTT_CONNECTED_EVENT | MQTT_DISCONNECTED_EVENT);
// 创建MQTT连接
std::string command = "AT+MQTTCONN=" + std::to_string(mqtt_id_) + ",\"" + broker_address + "\"," + std::to_string(broker_port) + ",\"" + client_id + "\",\"" + username + "\",\"" + password + "\"";
if (!at_uart_->SendCommand(command)) {
ESP_LOGE(TAG, "Failed to create MQTT connection");
return false;
}
// 等待连接完成
bits = xEventGroupWaitBits(event_group_handle_, MQTT_CONNECTED_EVENT | MQTT_DISCONNECTED_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(MQTT_CONNECT_TIMEOUT_MS));
if (!(bits & MQTT_CONNECTED_EVENT)) {
ESP_LOGE(TAG, "Failed to connect to MQTT broker");
return false;
}
return true;
}
bool Ml307Mqtt::IsConnected() {
// 检查这个 id 是否已经连接
at_uart_->SendCommand(std::string("AT+MQTTSTATE=") + std::to_string(mqtt_id_));
auto bits = xEventGroupWaitBits(event_group_handle_, MQTT_INITIALIZED_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(MQTT_CONNECT_TIMEOUT_MS));
if (!(bits & MQTT_INITIALIZED_EVENT)) {
ESP_LOGE(TAG, "Failed to initialize MQTT connection");
return false;
}
return connected_;
}
void Ml307Mqtt::Disconnect() {
if (!connected_) {
return;
}
at_uart_->SendCommand(std::string("AT+MQTTDISC=") + std::to_string(mqtt_id_));
}
bool Ml307Mqtt::Publish(const std::string topic, const std::string payload, int qos) {
if (!connected_) {
return false;
}
// If payload size is larger than 64KB, a CME ERROR 601 will be returned.
std::string command = "AT+MQTTPUB=" + std::to_string(mqtt_id_) + ",\"" + topic + "\",";
command += std::to_string(qos) + ",0,0,";
command += std::to_string(payload.size());
if (!at_uart_->SendCommand(command)) {
return false;
}
return at_uart_->SendCommand(payload);
}
bool Ml307Mqtt::Subscribe(const std::string topic, int qos) {
if (!connected_) {
return false;
}
std::string command = "AT+MQTTSUB=" + std::to_string(mqtt_id_) + ",\"" + topic + "\"," + std::to_string(qos);
return at_uart_->SendCommand(command);
}
bool Ml307Mqtt::Unsubscribe(const std::string topic) {
if (!connected_) {
return false;
}
std::string command = "AT+MQTTUNSUB=" + std::to_string(mqtt_id_) + ",\"" + topic + "\"";
return at_uart_->SendCommand(command);
}
std::string Ml307Mqtt::ErrorToString(int error_code) {
switch (error_code) {
case 0:
return "Connected";
case 1:
return "Reconnecting";
case 2:
return "Disconnected: User initiated";
case 3:
return "Disconnected: Rejected (protocol version, identifier, username or password error)";
case 4:
return "Disconnected: Server disconnected";
case 5:
return "Disconnected: Ping timeout";
case 6:
return "Disconnected: Network error";
case 255:
return "Disconnected: Unknown error";
default:
return "Unknown error";
}
}

View File

@@ -0,0 +1,43 @@
#ifndef ML307_MQTT_H
#define ML307_MQTT_H
#include "mqtt.h"
#include "at_uart.h"
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/event_groups.h>
#include <string>
#include <functional>
#define MQTT_CONNECT_TIMEOUT_MS 10000
#define MQTT_INITIALIZED_EVENT BIT0
#define MQTT_CONNECTED_EVENT BIT1
#define MQTT_DISCONNECTED_EVENT BIT2
class Ml307Mqtt : public Mqtt {
public:
Ml307Mqtt(std::shared_ptr<AtUart> at_uart, int mqtt_id);
~Ml307Mqtt();
bool Connect(const std::string broker_address, int broker_port, const std::string client_id, const std::string username, const std::string password);
void Disconnect();
bool Publish(const std::string topic, const std::string payload, int qos = 0);
bool Subscribe(const std::string topic, int qos = 0);
bool Unsubscribe(const std::string topic);
bool IsConnected();
private:
std::shared_ptr<AtUart> at_uart_;
int mqtt_id_;
bool connected_ = false;
EventGroupHandle_t event_group_handle_;
std::string message_payload_;
std::list<UrcCallback>::iterator urc_callback_it_;
std::string ErrorToString(int error_code);
};
#endif

View File

@@ -0,0 +1,25 @@
#include "ml307_ssl.h"
#include <esp_log.h>
static const char *TAG = "Ml307Ssl";
Ml307Ssl::Ml307Ssl(std::shared_ptr<AtUart> at_uart, int tcp_id) : Ml307Tcp(at_uart, tcp_id) {
}
bool Ml307Ssl::ConfigureSsl(int port) {
// 设置 SSL 配置
std::string command = "AT+MSSLCFG=\"auth\",0,0";
if (!at_uart_->SendCommand(command)) {
ESP_LOGE(TAG, "Failed to set SSL configuration");
return false;
}
// 强制启用 SSL
command = "AT+MIPCFG=\"ssl\"," + std::to_string(tcp_id_) + ",1,0";
if (!at_uart_->SendCommand(command)) {
ESP_LOGE(TAG, "Failed to set SSL configuration");
return false;
}
return true;
}

View File

@@ -0,0 +1,16 @@
#ifndef ML307_SSL_H
#define ML307_SSL_H
#include "ml307_tcp.h"
class Ml307Ssl : public Ml307Tcp {
public:
Ml307Ssl(std::shared_ptr<AtUart> at_uart, int tcp_id);
~Ml307Ssl() = default;
protected:
// 重写SSL配置方法
bool ConfigureSsl(int port) override;
};
#endif // ML307_SSL_H

View File

@@ -0,0 +1,192 @@
#include "ml307_tcp.h"
#include <esp_log.h>
#include <cstring>
#define TAG "Ml307Tcp"
Ml307Tcp::Ml307Tcp(std::shared_ptr<AtUart> at_uart, int tcp_id) : at_uart_(at_uart), tcp_id_(tcp_id) {
event_group_handle_ = xEventGroupCreate();
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
if (command == "MIPOPEN" && arguments.size() == 2) {
if (arguments[0].int_value == tcp_id_) {
connected_ = arguments[1].int_value == 0;
if (connected_) {
instance_active_ = true;
xEventGroupClearBits(event_group_handle_, ML307_TCP_DISCONNECTED | ML307_TCP_ERROR);
xEventGroupSetBits(event_group_handle_, ML307_TCP_CONNECTED);
} else {
xEventGroupSetBits(event_group_handle_, ML307_TCP_ERROR);
}
}
} else if (command == "MIPCLOSE" && arguments.size() == 1) {
if (arguments[0].int_value == tcp_id_) {
instance_active_ = false;
xEventGroupSetBits(event_group_handle_, ML307_TCP_DISCONNECTED);
}
} else if (command == "MIPSEND" && arguments.size() == 2) {
if (arguments[0].int_value == tcp_id_) {
xEventGroupSetBits(event_group_handle_, ML307_TCP_SEND_COMPLETE);
}
} else if (command == "MIPURC" && arguments.size() >= 3) {
if (arguments[1].int_value == tcp_id_) {
if (arguments[0].string_value == "rtcp") {
if (connected_ && stream_callback_) {
stream_callback_(at_uart_->DecodeHex(arguments[3].string_value));
}
} else if (arguments[0].string_value == "disconn") {
if (connected_) {
connected_ = false;
if (disconnect_callback_) {
disconnect_callback_();
}
}
instance_active_ = false;
xEventGroupSetBits(event_group_handle_, ML307_TCP_DISCONNECTED);
} else {
ESP_LOGE(TAG, "Unknown MIPURC command: %s", arguments[0].string_value.c_str());
}
}
} else if (command == "MIPSTATE" && arguments.size() >= 5) {
if (arguments[0].int_value == tcp_id_) {
connected_ = arguments[4].string_value == "CONNECTED";
instance_active_ = arguments[4].string_value != "INITIAL";
xEventGroupSetBits(event_group_handle_, ML307_TCP_INITIALIZED);
}
} else if (command == "FIFO_OVERFLOW") {
xEventGroupSetBits(event_group_handle_, ML307_TCP_ERROR);
Disconnect();
}
});
}
Ml307Tcp::~Ml307Tcp() {
Disconnect();
at_uart_->UnregisterUrcCallback(urc_callback_it_);
if (event_group_handle_) {
vEventGroupDelete(event_group_handle_);
}
}
bool Ml307Tcp::Connect(const std::string& host, int port) {
// Clear bits
xEventGroupClearBits(event_group_handle_, ML307_TCP_CONNECTED | ML307_TCP_DISCONNECTED | ML307_TCP_ERROR);
// 检查这个 id 是否已经连接
std::string command = "AT+MIPSTATE=" + std::to_string(tcp_id_);
at_uart_->SendCommand(command);
auto bits = xEventGroupWaitBits(event_group_handle_, ML307_TCP_INITIALIZED, pdTRUE, pdFALSE, pdMS_TO_TICKS(TCP_CONNECT_TIMEOUT_MS));
if (!(bits & ML307_TCP_INITIALIZED)) {
ESP_LOGE(TAG, "Failed to initialize TCP connection");
return false;
}
// 断开之前的连接
if (instance_active_) {
command = "AT+MIPCLOSE=" + std::to_string(tcp_id_);
if (at_uart_->SendCommand(command)) {
// 等待断开完成
xEventGroupWaitBits(event_group_handle_, ML307_TCP_DISCONNECTED, pdTRUE, pdFALSE, pdMS_TO_TICKS(TCP_CONNECT_TIMEOUT_MS));
}
}
// 配置SSL子类可以重写
if (!ConfigureSsl(port)) {
ESP_LOGE(TAG, "Failed to configure SSL");
return false;
}
// 使用 HEX 编码
command = "AT+MIPCFG=\"encoding\"," + std::to_string(tcp_id_) + ",1,1";
if (!at_uart_->SendCommand(command)) {
ESP_LOGE(TAG, "Failed to set HEX encoding");
return false;
}
// 打开 TCP 连接
command = "AT+MIPOPEN=" + std::to_string(tcp_id_) + ",\"TCP\",\"" + host + "\"," + std::to_string(port) + ",,0";
if (!at_uart_->SendCommand(command)) {
ESP_LOGE(TAG, "Failed to open TCP connection, error=%d", at_uart_->GetCmeErrorCode());
return false;
}
// 等待连接完成
bits = xEventGroupWaitBits(event_group_handle_, ML307_TCP_CONNECTED | ML307_TCP_ERROR, pdTRUE, pdFALSE, TCP_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
if (bits & ML307_TCP_ERROR) {
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
return false;
}
return true;
}
void Ml307Tcp::Disconnect() {
if (!instance_active_) {
return;
}
std::string command = "AT+MIPCLOSE=" + std::to_string(tcp_id_);
if (at_uart_->SendCommand(command)) {
xEventGroupWaitBits(event_group_handle_, ML307_TCP_DISCONNECTED, pdTRUE, pdFALSE, pdMS_TO_TICKS(TCP_CONNECT_TIMEOUT_MS));
}
if (connected_) {
connected_ = false;
if (disconnect_callback_) {
disconnect_callback_();
}
}
}
bool Ml307Tcp::ConfigureSsl(int port) {
std::string command = "AT+MIPCFG=\"ssl\"," + std::to_string(tcp_id_) + ",0,0";
if (!at_uart_->SendCommand(command)) {
ESP_LOGE(TAG, "Failed to set SSL configuration");
return false;
}
return true;
}
int Ml307Tcp::Send(const std::string& data) {
const size_t MAX_PACKET_SIZE = 1460 / 2;
size_t total_sent = 0;
if (!connected_) {
ESP_LOGE(TAG, "Not connected");
return -1;
}
// 在循环外预先分配command
std::string command;
command.reserve(32 + MAX_PACKET_SIZE * 2); // 预分配最大可能需要的空间
while (total_sent < data.size()) {
size_t chunk_size = std::min(data.size() - total_sent, MAX_PACKET_SIZE);
// 重置command并构建新的命令利用预分配的容量
command.clear();
command += "AT+MIPSEND=";
command += std::to_string(tcp_id_);
command += ",";
command += std::to_string(chunk_size);
command += ",";
// 直接在command字符串上进行十六进制编码
at_uart_->EncodeHexAppend(command, data.data() + total_sent, chunk_size);
command += "\r\n";
if (!at_uart_->SendCommand(command, 100, false)) {
ESP_LOGE(TAG, "Failed to send data chunk");
Disconnect();
return -1;
}
auto bits = xEventGroupWaitBits(event_group_handle_, ML307_TCP_SEND_COMPLETE, pdTRUE, pdFALSE, pdMS_TO_TICKS(TCP_CONNECT_TIMEOUT_MS));
if (!(bits & ML307_TCP_SEND_COMPLETE)) {
ESP_LOGE(TAG, "No send confirmation received");
return -1;
}
total_sent += chunk_size;
}
return data.size();
}

View File

@@ -0,0 +1,39 @@
#ifndef ML307_TCP_H
#define ML307_TCP_H
#include "tcp.h"
#include "at_uart.h"
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <string>
#define ML307_TCP_CONNECTED BIT0
#define ML307_TCP_DISCONNECTED BIT1
#define ML307_TCP_ERROR BIT2
#define ML307_TCP_SEND_COMPLETE BIT4
#define ML307_TCP_INITIALIZED BIT5
#define TCP_CONNECT_TIMEOUT_MS 10000
class Ml307Tcp : public Tcp {
public:
Ml307Tcp(std::shared_ptr<AtUart> at_uart, int tcp_id);
virtual ~Ml307Tcp();
bool Connect(const std::string& host, int port) override;
void Disconnect() override;
int Send(const std::string& data) override;
protected:
std::shared_ptr<AtUart> at_uart_;
int tcp_id_;
bool instance_active_ = false;
EventGroupHandle_t event_group_handle_;
std::list<UrcCallback>::iterator urc_callback_it_;
// 虚函数允许子类自定义SSL配置
virtual bool ConfigureSsl(int port);
};
#endif // ML307_TCP_H

View File

@@ -0,0 +1,152 @@
#include "ml307_udp.h"
#include <esp_log.h>
#define TAG "Ml307Udp"
Ml307Udp::Ml307Udp(std::shared_ptr<AtUart> at_uart, int udp_id) : at_uart_(at_uart), udp_id_(udp_id) {
event_group_handle_ = xEventGroupCreate();
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
if (command == "MIPOPEN" && arguments.size() == 2) {
if (arguments[0].int_value == udp_id_) {
connected_ = arguments[1].int_value == 0;
if (connected_) {
instance_active_ = true;
xEventGroupClearBits(event_group_handle_, ML307_UDP_DISCONNECTED | ML307_UDP_ERROR);
xEventGroupSetBits(event_group_handle_, ML307_UDP_CONNECTED);
} else {
xEventGroupSetBits(event_group_handle_, ML307_UDP_ERROR);
}
}
} else if (command == "MIPCLOSE" && arguments.size() == 1) {
if (arguments[0].int_value == udp_id_) {
instance_active_ = false;
xEventGroupSetBits(event_group_handle_, ML307_UDP_DISCONNECTED);
}
} else if (command == "MIPSEND" && arguments.size() == 2) {
if (arguments[0].int_value == udp_id_) {
xEventGroupSetBits(event_group_handle_, ML307_UDP_SEND_COMPLETE);
}
} else if (command == "MIPURC" && arguments.size() == 4) {
if (arguments[1].int_value == udp_id_) {
if (arguments[0].string_value == "rudp") {
if (connected_ && message_callback_) {
message_callback_(at_uart_->DecodeHex(arguments[3].string_value));
}
} else if (arguments[0].string_value == "disconn") {
connected_ = false;
instance_active_ = false;
xEventGroupSetBits(event_group_handle_, ML307_UDP_DISCONNECTED);
} else {
ESP_LOGE(TAG, "Unknown MIPURC command: %s", arguments[0].string_value.c_str());
}
}
} else if (command == "MIPSTATE" && arguments.size() == 5) {
if (arguments[0].int_value == udp_id_) {
connected_ = arguments[4].string_value == "CONNECTED";
instance_active_ = arguments[4].string_value != "INITIAL";
xEventGroupSetBits(event_group_handle_, ML307_UDP_INITIALIZED);
}
} else if (command == "FIFO_OVERFLOW") {
xEventGroupSetBits(event_group_handle_, ML307_UDP_ERROR);
Disconnect();
}
});
}
Ml307Udp::~Ml307Udp() {
Disconnect();
at_uart_->UnregisterUrcCallback(urc_callback_it_);
if (event_group_handle_) {
vEventGroupDelete(event_group_handle_);
}
}
bool Ml307Udp::Connect(const std::string& host, int port) {
// Clear bits
xEventGroupClearBits(event_group_handle_, ML307_UDP_CONNECTED | ML307_UDP_DISCONNECTED | ML307_UDP_ERROR);
// 检查这个 id 是否已经连接
std::string command = "AT+MIPSTATE=" + std::to_string(udp_id_);
at_uart_->SendCommand(command);
auto bits = xEventGroupWaitBits(event_group_handle_, ML307_UDP_INITIALIZED, pdTRUE, pdFALSE, pdMS_TO_TICKS(UDP_CONNECT_TIMEOUT_MS));
if (!(bits & ML307_UDP_INITIALIZED)) {
ESP_LOGE(TAG, "Failed to initialize TCP connection");
return false;
}
// 断开之前的连接
if (instance_active_) {
command = "AT+MIPCLOSE=" + std::to_string(udp_id_);
if (at_uart_->SendCommand(command)) {
// 等待断开完成
xEventGroupWaitBits(event_group_handle_, ML307_UDP_DISCONNECTED, pdTRUE, pdFALSE, pdMS_TO_TICKS(UDP_CONNECT_TIMEOUT_MS));
}
}
// 使用 HEX 编码
command = "AT+MIPCFG=\"encoding\"," + std::to_string(udp_id_) + ",1,1";
if (!at_uart_->SendCommand(command)) {
ESP_LOGE(TAG, "Failed to set HEX encoding");
return false;
}
command = "AT+MIPCFG=\"ssl\"," + std::to_string(udp_id_) + ",0,0";
if (!at_uart_->SendCommand(command)) {
ESP_LOGE(TAG, "Failed to set SSL configuration");
return false;
}
// 打开 UDP 连接
command = "AT+MIPOPEN=" + std::to_string(udp_id_) + ",\"UDP\",\"" + host + "\"," + std::to_string(port) + ",,0";
if (!at_uart_->SendCommand(command)) {
ESP_LOGE(TAG, "Failed to open UDP connection");
return false;
}
// 等待连接完成
bits = xEventGroupWaitBits(event_group_handle_, ML307_UDP_CONNECTED | ML307_UDP_ERROR, pdTRUE, pdFALSE, UDP_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
if (bits & ML307_UDP_ERROR) {
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
return false;
}
return true;
}
void Ml307Udp::Disconnect() {
if (!instance_active_) {
return;
}
at_uart_->SendCommand("AT+MIPCLOSE=" + std::to_string(udp_id_));
connected_ = false;
}
int Ml307Udp::Send(const std::string& data) {
const size_t MAX_PACKET_SIZE = 1460 / 2;
if (!connected_) {
ESP_LOGE(TAG, "Not connected");
return -1;
}
if (data.size() > MAX_PACKET_SIZE) {
ESP_LOGE(TAG, "Data chunk exceeds maximum limit");
return -1;
}
// 在循环外预先分配command
std::string command = "AT+MIPSEND=" + std::to_string(udp_id_) + "," + std::to_string(data.size()) + ",";
// 直接在command字符串上进行十六进制编码
at_uart_->EncodeHexAppend(command, data.data(), data.size());
command += "\r\n";
if (!at_uart_->SendCommand(command, 100, false)) {
ESP_LOGE(TAG, "Failed to send data chunk");
return -1;
}
return data.size();
}

View File

@@ -0,0 +1,36 @@
#ifndef ML307_UDP_H
#define ML307_UDP_H
#include "udp.h"
#include "at_uart.h"
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#define ML307_UDP_CONNECTED BIT0
#define ML307_UDP_DISCONNECTED BIT1
#define ML307_UDP_ERROR BIT2
#define ML307_UDP_RECEIVE BIT3
#define ML307_UDP_SEND_COMPLETE BIT4
#define ML307_UDP_INITIALIZED BIT5
#define UDP_CONNECT_TIMEOUT_MS 10000
class Ml307Udp : public Udp {
public:
Ml307Udp(std::shared_ptr<AtUart> at_uart, int udp_id);
~Ml307Udp();
bool Connect(const std::string& host, int port) override;
void Disconnect() override;
int Send(const std::string& data) override;
private:
std::shared_ptr<AtUart> at_uart_;
int udp_id_;
bool instance_active_ = false;
EventGroupHandle_t event_group_handle_;
std::list<UrcCallback>::iterator urc_callback_it_;
};
#endif // ML307_UDP_H

View File

@@ -0,0 +1,436 @@
#include "web_socket.h"
#include "network_interface.h"
#include <esp_log.h>
#include <cstdlib>
#include <cstring>
#include <esp_pthread.h>
#define TAG "WebSocket"
static std::string base64_encode(const unsigned char *data, size_t len) {
const char *base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
std::string encoded;
unsigned char char_array_3[3];
unsigned char char_array_4[4];
size_t i = 0;
while (i < len) {
size_t chunk_size = std::min((size_t)3, len - i);
for (size_t j = 0; j < 3; j++) {
char_array_3[j] = (j < chunk_size) ? data[i + j] : 0;
}
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (size_t j = 0; j < 4; j++) {
if (j <= chunk_size) {
encoded.push_back(base64_chars[char_array_4[j]]);
} else {
encoded.push_back('=');
}
}
i += chunk_size;
}
return encoded;
}
WebSocket::WebSocket(NetworkInterface* network, int connect_id) : network_(network), connect_id_(connect_id) {
handshake_event_group_ = xEventGroupCreate();
}
WebSocket::~WebSocket() {
if (connected_) {
tcp_->Disconnect();
}
if (handshake_event_group_) {
vEventGroupDelete(handshake_event_group_);
}
}
void WebSocket::SetHeader(const char* key, const char* value) {
headers_[key] = value;
}
void WebSocket::SetReceiveBufferSize(size_t size) {
receive_buffer_size_ = size;
}
bool WebSocket::IsConnected() const {
return connected_;
}
bool WebSocket::Connect(const char* uri) {
std::string uri_str(uri);
std::string protocol, host, port, path;
size_t pos = 0;
size_t next_pos = 0;
// 解析协议
next_pos = uri_str.find("://");
if (next_pos == std::string::npos) {
ESP_LOGE(TAG, "Invalid URI format");
return false;
}
protocol = uri_str.substr(0, next_pos);
pos = next_pos + 3;
// 解析主机
next_pos = uri_str.find(':', pos);
if (next_pos == std::string::npos) {
next_pos = uri_str.find('/', pos);
if (next_pos == std::string::npos) {
host = uri_str.substr(pos);
path = "/";
} else {
host = uri_str.substr(pos, next_pos - pos);
path = uri_str.substr(next_pos);
}
port = (protocol == "wss") ? "443" : "80";
} else {
host = uri_str.substr(pos, next_pos - pos);
pos = next_pos + 1;
// 解析端口
next_pos = uri_str.find('/', pos);
if (next_pos == std::string::npos) {
port = uri_str.substr(pos);
path = "/";
} else {
port = uri_str.substr(pos, next_pos - pos);
path = uri_str.substr(next_pos);
}
}
ESP_LOGD(TAG, "Connecting to %s://%s:%s%s", protocol.c_str(), host.c_str(), port.c_str(), path.c_str());
// 设置 WebSocket 特定的头部
SetHeader("Upgrade", "websocket");
SetHeader("Connection", "Upgrade");
SetHeader("Sec-WebSocket-Version", "13");
// 生成随机的 Sec-WebSocket-Key
char key[25];
for (int i = 0; i < 16; ++i) {
key[i] = rand() % 256;
}
std::string base64_key = base64_encode(reinterpret_cast<const unsigned char*>(key), 16);
SetHeader("Sec-WebSocket-Key", base64_key.c_str());
if (protocol == "wss" || protocol == "https") {
tcp_ = network_->CreateSsl(connect_id_);
} else {
tcp_ = network_->CreateTcp(connect_id_);
}
connected_ = false;
// 使用 tcp 建立连接
if (!tcp_->Connect(host, std::stoi(port))) {
ESP_LOGE(TAG, "Failed to connect to server");
return false;
}
// 发送 WebSocket 握手请求
std::string request = "GET " + path + " HTTP/1.1\r\n";
if (headers_.find("Host") == headers_.end()) {
request += "Host: " + host + "\r\n";
}
for (const auto& header : headers_) {
request += header.first + ": " + header.second + "\r\n";
}
request += "\r\n";
if (tcp_->Send(request) < 0) {
ESP_LOGE(TAG, "Failed to send WebSocket handshake request");
return false;
}
// 清除事件位
xEventGroupClearBits(handshake_event_group_, HANDSHAKE_SUCCESS_BIT | HANDSHAKE_FAILED_BIT);
// 设置数据接收回调来处理握手和后续的WebSocket帧
tcp_->OnStream([this](const std::string& data) {
this->OnTcpData(data);
});
// 设置断开连接回调
tcp_->OnDisconnected([this]() {
if (connected_) {
connected_ = false;
if (on_disconnected_) {
on_disconnected_();
}
}
});
// 等待握手完成超时时间10秒
EventBits_t bits = xEventGroupWaitBits(
handshake_event_group_,
HANDSHAKE_SUCCESS_BIT | HANDSHAKE_FAILED_BIT,
pdFALSE, // 不清除事件位
pdFALSE, // 等待任意一个事件位
pdMS_TO_TICKS(10000) // 10秒超时
);
if (bits & HANDSHAKE_SUCCESS_BIT) {
connected_ = true;
if (on_connected_) {
on_connected_();
}
return true;
} else if (bits & HANDSHAKE_FAILED_BIT) {
ESP_LOGE(TAG, "WebSocket handshake failed");
if (on_error_) {
on_error_(-1);
}
return false;
} else {
ESP_LOGE(TAG, "WebSocket handshake timeout");
return false;
}
}
bool WebSocket::Send(const std::string& data) {
return Send(data.data(), data.size(), false);
}
bool WebSocket::Send(const void* data, size_t len, bool binary, bool fin) {
if (len > 65535) {
ESP_LOGE(TAG, "Data too large, maximum supported size is 65535 bytes");
return false;
}
std::string frame;
frame.reserve(len + 8); // 最大可能的帧大小2字节帧头 + 2字节长度 + 4字节mask
// 第一个字节FIN 位 + 操作码
uint8_t first_byte = (fin ? 0x80 : 0x00);
if (binary) {
first_byte |= 0x02; // 二进制帧
} else if (!continuation_) {
first_byte |= 0x01; // 文本帧
} // 否则操作码为0延续帧
frame.push_back(static_cast<char>(first_byte));
// 第二个字节MASK 位 + 有效载荷长度
if (len < 126) {
frame.push_back(static_cast<char>(0x80 | len)); // 设置MASK位
} else {
frame.push_back(static_cast<char>(0x80 | 126)); // 设置MASK位
frame.push_back(static_cast<char>((len >> 8) & 0xFF));
frame.push_back(static_cast<char>(len & 0xFF));
}
// 生成随机的4字节mask
uint8_t mask[4];
for (int i = 0; i < 4; ++i) {
mask[i] = rand() & 0xFF;
}
frame.append(reinterpret_cast<const char*>(mask), 4);
// 添加并mask处理有效载荷
const uint8_t* payload = static_cast<const uint8_t*>(data);
for (size_t i = 0; i < len; ++i) {
frame.push_back(static_cast<char>(payload[i] ^ mask[i % 4]));
}
// 更新continuation_状态
continuation_ = !fin;
// 发送帧
return tcp_->Send(frame) >= 0;
}
void WebSocket::Ping() {
SendControlFrame(0x9, nullptr, 0);
}
void WebSocket::Close() {
if (connected_) {
SendControlFrame(0x8, nullptr, 0);
}
}
void WebSocket::OnConnected(std::function<void()> callback) {
on_connected_ = callback;
}
void WebSocket::OnDisconnected(std::function<void()> callback) {
on_disconnected_ = callback;
}
void WebSocket::OnData(std::function<void(const char*, size_t, bool binary)> callback) {
on_data_ = callback;
}
void WebSocket::OnError(std::function<void(int)> callback) {
on_error_ = callback;
}
void WebSocket::OnTcpData(const std::string& data) {
// 将新数据追加到接收缓冲区
receive_buffer_.append(data);
if (!handshake_completed_) {
// 检查握手响应
size_t pos = receive_buffer_.find("\r\n\r\n");
if (pos != std::string::npos) {
std::string handshake_response = receive_buffer_.substr(0, pos + 4);
receive_buffer_ = receive_buffer_.substr(pos + 4);
if (handshake_response.find("HTTP/1.1 101") != std::string::npos) {
handshake_completed_ = true;
// 设置握手成功事件
xEventGroupSetBits(handshake_event_group_, HANDSHAKE_SUCCESS_BIT);
} else {
ESP_LOGE(TAG, "WebSocket handshake failed");
// 设置握手失败事件
xEventGroupSetBits(handshake_event_group_, HANDSHAKE_FAILED_BIT);
return;
}
} else {
// 握手响应未完整接收
return;
}
}
// 处理WebSocket帧
static std::vector<char> current_message;
static bool is_fragmented = false;
static bool is_binary = false;
size_t buffer_offset = 0;
const char* buffer = receive_buffer_.c_str();
size_t buffer_size = receive_buffer_.size();
while (buffer_offset < buffer_size) {
if (buffer_size - buffer_offset < 2) break; // 需要更多数据
uint8_t opcode = buffer[buffer_offset] & 0x0F;
bool fin = (buffer[buffer_offset] & 0x80) != 0;
uint8_t mask = buffer[buffer_offset + 1] & 0x80;
uint64_t payload_length = buffer[buffer_offset + 1] & 0x7F;
size_t header_length = 2;
if (payload_length == 126) {
if (buffer_size - buffer_offset < 4) break; // 需要更多数据
payload_length = (buffer[buffer_offset + 2] << 8) | buffer[buffer_offset + 3];
header_length += 2;
} else if (payload_length == 127) {
if (buffer_size - buffer_offset < 10) break; // 需要更多数据
payload_length = 0;
for (int i = 0; i < 8; ++i) {
payload_length = (payload_length << 8) | buffer[buffer_offset + 2 + i];
}
header_length += 8;
}
uint8_t mask_key[4] = {0};
if (mask) {
if (buffer_size - buffer_offset < header_length + 4) break; // 需要更多数据
memcpy(mask_key, buffer + buffer_offset + header_length, 4);
header_length += 4;
}
if (buffer_size - buffer_offset < header_length + payload_length) break; // 需要更多数据
// 解码有效载荷
std::vector<char> payload(payload_length);
memcpy(payload.data(), buffer + buffer_offset + header_length, payload_length);
if (mask) {
for (size_t i = 0; i < payload_length; ++i) {
payload[i] ^= mask_key[i % 4];
}
}
// 处理帧
switch (opcode) {
case 0x0: // 延续帧
case 0x1: // 文本帧
case 0x2: // 二进制帧
if (opcode != 0x0 && is_fragmented) {
ESP_LOGE(TAG, "Received new message frame while still fragmenting");
break;
}
if (opcode != 0x0) {
is_fragmented = !fin;
is_binary = (opcode == 0x2);
current_message.clear();
}
current_message.insert(current_message.end(), payload.begin(), payload.end());
if (fin) {
if (on_data_) {
on_data_(current_message.data(), current_message.size(), is_binary);
}
current_message.clear();
is_fragmented = false;
}
break;
case 0x8: // 关闭帧
connected_ = false;
if (on_disconnected_) {
on_disconnected_();
}
break;
case 0x9: // Ping
// 发送 Pong
SendControlFrame(0xA, payload.data(), payload_length);
break;
case 0xA: // Pong
// 可以在这里处理 Pong
break;
default:
ESP_LOGE(TAG, "Unknown opcode: %d", opcode);
break;
}
buffer_offset += header_length + payload_length;
}
// 保留未处理的数据
if (buffer_offset > 0) {
receive_buffer_ = receive_buffer_.substr(buffer_offset);
}
}
bool WebSocket::SendControlFrame(uint8_t opcode, const void* data, size_t len) {
if (len > 125) {
ESP_LOGE(TAG, "控制帧有效载荷过大");
return false;
}
std::string frame;
frame.reserve(len + 6); // 帧头 + 掩码 + 有效载荷
// 第一个字节FIN 位 + 操作码
frame.push_back(static_cast<char>(0x80 | opcode));
// 第二个字节MASK 位 + 有效载荷长度
frame.push_back(static_cast<char>(0x80 | len));
// 生成随机的4字节掩码
uint8_t mask[4];
for (int i = 0; i < 4; ++i) {
mask[i] = rand() & 0xFF;
}
frame.append(reinterpret_cast<const char*>(mask), 4);
// 添加并掩码处理有效载荷
const uint8_t* payload = static_cast<const uint8_t*>(data);
for (size_t i = 0; i < len; ++i) {
frame.push_back(static_cast<char>(payload[i] ^ mask[i % 4]));
}
// 发送帧
return tcp_->Send(frame) >= 0;
}