add some code

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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