add some code
This commit is contained in:
3
managed_components/78__esp-ml307/.gitignore
vendored
Normal file
3
managed_components/78__esp-ml307/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
dist/
|
||||
.component_hash
|
||||
.idea
|
||||
1
managed_components/78__esp-ml307/CHECKSUMS.json
Normal file
1
managed_components/78__esp-ml307/CHECKSUMS.json
Normal file
File diff suppressed because one or more lines are too long
33
managed_components/78__esp-ml307/CMakeLists.txt
Normal file
33
managed_components/78__esp-ml307/CMakeLists.txt
Normal 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"
|
||||
)
|
||||
201
managed_components/78__esp-ml307/LICENSE
Normal file
201
managed_components/78__esp-ml307/LICENSE
Normal 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.
|
||||
408
managed_components/78__esp-ml307/README.md
Normal file
408
managed_components/78__esp-ml307/README.md
Normal 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");
|
||||
// 无需手动 delete,unique_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)
|
||||
11
managed_components/78__esp-ml307/idf_component.yml
Normal file
11
managed_components/78__esp-ml307/idf_component.yml
Normal 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
|
||||
99
managed_components/78__esp-ml307/include/at_modem.h
Normal file
99
managed_components/78__esp-ml307/include/at_modem.h
Normal 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_
|
||||
123
managed_components/78__esp-ml307/include/at_uart.h
Normal file
123
managed_components/78__esp-ml307/include/at_uart.h
Normal 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_
|
||||
19
managed_components/78__esp-ml307/include/esp_network.h
Normal file
19
managed_components/78__esp-ml307/include/esp_network.h
Normal 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
|
||||
46
managed_components/78__esp-ml307/include/http.h
Normal file
46
managed_components/78__esp-ml307/include/http.h
Normal 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
|
||||
156
managed_components/78__esp-ml307/include/http_client.h
Normal file
156
managed_components/78__esp-ml307/include/http_client.h
Normal 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
|
||||
32
managed_components/78__esp-ml307/include/mqtt.h
Normal file
32
managed_components/78__esp-ml307/include/mqtt.h
Normal 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
|
||||
24
managed_components/78__esp-ml307/include/network_interface.h
Normal file
24
managed_components/78__esp-ml307/include/network_interface.h
Normal 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
|
||||
34
managed_components/78__esp-ml307/include/tcp.h
Normal file
34
managed_components/78__esp-ml307/include/tcp.h
Normal 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
|
||||
25
managed_components/78__esp-ml307/include/udp.h
Normal file
25
managed_components/78__esp-ml307/include/udp.h
Normal 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
|
||||
59
managed_components/78__esp-ml307/include/web_socket.h
Normal file
59
managed_components/78__esp-ml307/include/web_socket.h
Normal 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
|
||||
207
managed_components/78__esp-ml307/src/at_modem.cc
Normal file
207
managed_components/78__esp-ml307/src/at_modem.cc
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
395
managed_components/78__esp-ml307/src/at_uart.cc
Normal file
395
managed_components/78__esp-ml307/src/at_uart.cc
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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_
|
||||
244
managed_components/78__esp-ml307/src/ec801e/ec801e_mqtt.cc
Normal file
244
managed_components/78__esp-ml307/src/ec801e/ec801e_mqtt.cc
Normal 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";
|
||||
}
|
||||
}
|
||||
45
managed_components/78__esp-ml307/src/ec801e/ec801e_mqtt.h
Normal file
45
managed_components/78__esp-ml307/src/ec801e/ec801e_mqtt.h
Normal 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
|
||||
160
managed_components/78__esp-ml307/src/ec801e/ec801e_ssl.cc
Normal file
160
managed_components/78__esp-ml307/src/ec801e/ec801e_ssl.cc
Normal 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();
|
||||
}
|
||||
36
managed_components/78__esp-ml307/src/ec801e/ec801e_ssl.h
Normal file
36
managed_components/78__esp-ml307/src/ec801e/ec801e_ssl.h
Normal 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
|
||||
159
managed_components/78__esp-ml307/src/ec801e/ec801e_tcp.cc
Normal file
159
managed_components/78__esp-ml307/src/ec801e/ec801e_tcp.cc
Normal 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();
|
||||
}
|
||||
37
managed_components/78__esp-ml307/src/ec801e/ec801e_tcp.h
Normal file
37
managed_components/78__esp-ml307/src/ec801e/ec801e_tcp.h
Normal 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
|
||||
141
managed_components/78__esp-ml307/src/ec801e/ec801e_udp.cc
Normal file
141
managed_components/78__esp-ml307/src/ec801e/ec801e_udp.cc
Normal 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();
|
||||
}
|
||||
36
managed_components/78__esp-ml307/src/ec801e/ec801e_udp.h
Normal file
36
managed_components/78__esp-ml307/src/ec801e/ec801e_udp.h
Normal 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
|
||||
137
managed_components/78__esp-ml307/src/esp/esp_mqtt.cc
Normal file
137
managed_components/78__esp-ml307/src/esp/esp_mqtt.cc
Normal 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_;
|
||||
}
|
||||
41
managed_components/78__esp-ml307/src/esp/esp_mqtt.h
Normal file
41
managed_components/78__esp-ml307/src/esp/esp_mqtt.h
Normal 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
|
||||
41
managed_components/78__esp-ml307/src/esp/esp_network.cc
Normal file
41
managed_components/78__esp-ml307/src/esp/esp_network.cc
Normal 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);
|
||||
}
|
||||
136
managed_components/78__esp-ml307/src/esp/esp_ssl.cc
Normal file
136
managed_components/78__esp-ml307/src/esp/esp_ssl.cc
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
30
managed_components/78__esp-ml307/src/esp/esp_ssl.h
Normal file
30
managed_components/78__esp-ml307/src/esp/esp_ssl.h
Normal 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_
|
||||
130
managed_components/78__esp-ml307/src/esp/esp_tcp.cc
Normal file
130
managed_components/78__esp-ml307/src/esp/esp_tcp.cc
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
managed_components/78__esp-ml307/src/esp/esp_tcp.h
Normal file
29
managed_components/78__esp-ml307/src/esp/esp_tcp.h
Normal 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_
|
||||
111
managed_components/78__esp-ml307/src/esp/esp_udp.cc
Normal file
111
managed_components/78__esp-ml307/src/esp/esp_udp.cc
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
managed_components/78__esp-ml307/src/esp/esp_udp.h
Normal file
29
managed_components/78__esp-ml307/src/esp/esp_udp.h
Normal 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
|
||||
724
managed_components/78__esp-ml307/src/http_client.cc
Normal file
724
managed_components/78__esp-ml307/src/http_client.cc
Normal 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;
|
||||
}
|
||||
112
managed_components/78__esp-ml307/src/ml307/ml307_at_modem.cc
Normal file
112
managed_components/78__esp-ml307/src/ml307/ml307_at_modem.cc
Normal 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);
|
||||
}
|
||||
34
managed_components/78__esp-ml307/src/ml307/ml307_at_modem.h
Normal file
34
managed_components/78__esp-ml307/src/ml307/ml307_at_modem.h
Normal 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_
|
||||
353
managed_components/78__esp-ml307/src/ml307/ml307_http.cc
Normal file
353
managed_components/78__esp-ml307/src/ml307/ml307_http.cc
Normal 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 "";
|
||||
}
|
||||
72
managed_components/78__esp-ml307/src/ml307/ml307_http.h
Normal file
72
managed_components/78__esp-ml307/src/ml307/ml307_http.h
Normal 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
|
||||
196
managed_components/78__esp-ml307/src/ml307/ml307_mqtt.cc
Normal file
196
managed_components/78__esp-ml307/src/ml307/ml307_mqtt.cc
Normal 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";
|
||||
}
|
||||
}
|
||||
43
managed_components/78__esp-ml307/src/ml307/ml307_mqtt.h
Normal file
43
managed_components/78__esp-ml307/src/ml307/ml307_mqtt.h
Normal 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
|
||||
25
managed_components/78__esp-ml307/src/ml307/ml307_ssl.cc
Normal file
25
managed_components/78__esp-ml307/src/ml307/ml307_ssl.cc
Normal 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;
|
||||
}
|
||||
16
managed_components/78__esp-ml307/src/ml307/ml307_ssl.h
Normal file
16
managed_components/78__esp-ml307/src/ml307/ml307_ssl.h
Normal 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
|
||||
192
managed_components/78__esp-ml307/src/ml307/ml307_tcp.cc
Normal file
192
managed_components/78__esp-ml307/src/ml307/ml307_tcp.cc
Normal 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();
|
||||
}
|
||||
39
managed_components/78__esp-ml307/src/ml307/ml307_tcp.h
Normal file
39
managed_components/78__esp-ml307/src/ml307/ml307_tcp.h
Normal 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
|
||||
152
managed_components/78__esp-ml307/src/ml307/ml307_udp.cc
Normal file
152
managed_components/78__esp-ml307/src/ml307/ml307_udp.cc
Normal 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();
|
||||
}
|
||||
36
managed_components/78__esp-ml307/src/ml307/ml307_udp.h
Normal file
36
managed_components/78__esp-ml307/src/ml307/ml307_udp.h
Normal 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
|
||||
436
managed_components/78__esp-ml307/src/web_socket.cc
Normal file
436
managed_components/78__esp-ml307/src/web_socket.cc
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user