add some code
This commit is contained in:
207
managed_components/78__esp-ml307/src/at_modem.cc
Normal file
207
managed_components/78__esp-ml307/src/at_modem.cc
Normal file
@@ -0,0 +1,207 @@
|
||||
#include "at_modem.h"
|
||||
#include "ml307/ml307_at_modem.h"
|
||||
#include "ec801e/ec801e_at_modem.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <cstring>
|
||||
|
||||
static const char* TAG = "AtModem";
|
||||
|
||||
std::unique_ptr<AtModem> AtModem::Detect(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin, int baud_rate) {
|
||||
// 创建AtUart进行检测
|
||||
auto uart = std::make_shared<AtUart>(tx_pin, rx_pin, dtr_pin);
|
||||
uart->Initialize();
|
||||
|
||||
// 设置波特率
|
||||
if (!uart->SetBaudRate(baud_rate)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 发送AT+CGMR(或ATI)命令获取模组型号
|
||||
if (!uart->SendCommand("AT+CGMR", 3000)) {
|
||||
ESP_LOGE(TAG, "Failed to send AT+CGMR command");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string response = uart->GetResponse();
|
||||
ESP_LOGI(TAG, "Detected modem: %s", response.c_str());
|
||||
|
||||
// 检查响应中的模组型号
|
||||
if (response.find("EC801E") == 0) {
|
||||
return std::make_unique<Ec801EAtModem>(uart);
|
||||
} else if (response.find("NT26K") == 0) {
|
||||
return std::make_unique<Ec801EAtModem>(uart);
|
||||
} else if (response.find("ML307") == 0) {
|
||||
return std::make_unique<Ml307AtModem>(uart);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unrecognized modem type: %s, use ML307 AtModem as default", response.c_str());
|
||||
return std::make_unique<Ml307AtModem>(uart);
|
||||
}
|
||||
}
|
||||
|
||||
AtModem::AtModem(std::shared_ptr<AtUart> at_uart) : at_uart_(at_uart) {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
HandleUrc(command, arguments);
|
||||
});
|
||||
}
|
||||
|
||||
AtModem::~AtModem() {
|
||||
if (event_group_handle_) {
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
void AtModem::OnNetworkStateChanged(std::function<void(bool network_ready)> callback) {
|
||||
on_network_state_changed_ = callback;
|
||||
}
|
||||
|
||||
void AtModem::Reboot() {
|
||||
}
|
||||
|
||||
void AtModem::SetFlightMode(bool enable) {
|
||||
if (enable) {
|
||||
at_uart_->SendCommand("AT+CFUN=4"); // flight mode
|
||||
at_uart_->SetDtrPin(enable);
|
||||
network_ready_ = false;
|
||||
} else {
|
||||
at_uart_->SetDtrPin(enable);
|
||||
at_uart_->SendCommand("AT+CFUN=1"); // normal mode
|
||||
}
|
||||
}
|
||||
|
||||
bool AtModem::SetSleepMode(bool enable, int delay_seconds) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NetworkStatus AtModem::WaitForNetworkReady(int timeout_ms) {
|
||||
ESP_LOGI(TAG, "Waiting for network ready...");
|
||||
network_ready_ = false;
|
||||
cereg_state_ = CeregState{};
|
||||
xEventGroupClearBits(event_group_handle_, AT_EVENT_NETWORK_READY | AT_EVENT_NETWORK_ERROR);
|
||||
|
||||
// 检查 SIM 卡是否准备好
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (at_uart_->SendCommand("AT+CPIN?")) {
|
||||
pin_ready_ = true;
|
||||
break;
|
||||
}
|
||||
if (at_uart_->GetCmeErrorCode() == 10) {
|
||||
pin_ready_ = false;
|
||||
return NetworkStatus::ErrorInsertPin;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
|
||||
// 检查网络注册状态
|
||||
if (!at_uart_->SendCommand("AT+CEREG=2")) {
|
||||
return NetworkStatus::Error;
|
||||
}
|
||||
if (!at_uart_->SendCommand("AT+CEREG?")) {
|
||||
return NetworkStatus::Error;
|
||||
}
|
||||
|
||||
TickType_t timeout = portMAX_DELAY;
|
||||
if (timeout_ms > 0) {
|
||||
timeout = pdMS_TO_TICKS(timeout_ms);
|
||||
}
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, AT_EVENT_NETWORK_READY | AT_EVENT_NETWORK_ERROR, pdTRUE, pdFALSE, timeout);
|
||||
if (bits & AT_EVENT_NETWORK_READY) {
|
||||
return NetworkStatus::Ready;
|
||||
} else if (bits & AT_EVENT_NETWORK_ERROR) {
|
||||
if (cereg_state_.stat == 3) {
|
||||
return NetworkStatus::ErrorRegistrationDenied;
|
||||
} else if (!pin_ready_) {
|
||||
return NetworkStatus::ErrorInsertPin;
|
||||
} else {
|
||||
return NetworkStatus::Error;
|
||||
}
|
||||
}
|
||||
return NetworkStatus::ErrorTimeout;
|
||||
}
|
||||
|
||||
std::string AtModem::GetImei() {
|
||||
if (!imei_.empty()) {
|
||||
return imei_;
|
||||
}
|
||||
at_uart_->SendCommand("AT+CGSN=1");
|
||||
return imei_;
|
||||
}
|
||||
|
||||
std::string AtModem::GetIccid() {
|
||||
at_uart_->SendCommand("AT+ICCID");
|
||||
return iccid_;
|
||||
}
|
||||
|
||||
std::string AtModem::GetModuleRevision() {
|
||||
if (!module_revision_.empty()) {
|
||||
return module_revision_;
|
||||
}
|
||||
if (at_uart_->SendCommand("AT+CGMR")) {
|
||||
module_revision_ = at_uart_->GetResponse();
|
||||
}
|
||||
return module_revision_;
|
||||
}
|
||||
|
||||
std::string AtModem::GetCarrierName() {
|
||||
at_uart_->SendCommand("AT+COPS?");
|
||||
return carrier_name_;
|
||||
}
|
||||
|
||||
int AtModem::GetCsq() {
|
||||
at_uart_->SendCommand("AT+CSQ", 10);
|
||||
return csq_;
|
||||
}
|
||||
|
||||
CeregState AtModem::GetRegistrationState() {
|
||||
at_uart_->SendCommand("AT+CEREG?");
|
||||
return cereg_state_;
|
||||
}
|
||||
|
||||
void AtModem::HandleUrc(const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
if (command == "CGSN" && arguments.size() >= 1) {
|
||||
imei_ = arguments[0].string_value;
|
||||
} else if (command == "ICCID" && arguments.size() >= 1) {
|
||||
iccid_ = arguments[0].string_value;
|
||||
} else if (command == "COPS" && arguments.size() >= 4) {
|
||||
carrier_name_ = arguments[2].string_value;
|
||||
} else if (command == "CSQ" && arguments.size() >= 1) {
|
||||
csq_ = arguments[0].int_value;
|
||||
} else if (command == "CEREG" && arguments.size() >= 1) {
|
||||
cereg_state_ = CeregState{};
|
||||
if (arguments.size() == 1) {
|
||||
cereg_state_.stat = 0;
|
||||
} else if (arguments.size() >= 2) {
|
||||
int state_index = arguments[1].type == AtArgumentValue::Type::Int ? 1 : 0;
|
||||
cereg_state_.stat = arguments[state_index].int_value;
|
||||
if (arguments.size() >= state_index + 2) {
|
||||
cereg_state_.tac = arguments[state_index + 1].string_value;
|
||||
cereg_state_.ci = arguments[state_index + 2].string_value;
|
||||
if (arguments.size() >= state_index + 4) {
|
||||
cereg_state_.AcT = arguments[state_index + 3].int_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool new_network_ready = cereg_state_.stat == 1 || cereg_state_.stat == 5;
|
||||
if (new_network_ready != network_ready_) {
|
||||
network_ready_ = new_network_ready;
|
||||
if (on_network_state_changed_) {
|
||||
on_network_state_changed_(new_network_ready);
|
||||
}
|
||||
}
|
||||
if (new_network_ready) {
|
||||
xEventGroupSetBits(event_group_handle_, AT_EVENT_NETWORK_READY);
|
||||
} else if (cereg_state_.stat == 3) {
|
||||
xEventGroupSetBits(event_group_handle_, AT_EVENT_NETWORK_ERROR);
|
||||
}
|
||||
} else if (command == "CPIN" && arguments.size() >= 1) {
|
||||
if (arguments[0].string_value == "READY") {
|
||||
pin_ready_ = true;
|
||||
} else {
|
||||
pin_ready_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
395
managed_components/78__esp-ml307/src/at_uart.cc
Normal file
395
managed_components/78__esp-ml307/src/at_uart.cc
Normal file
@@ -0,0 +1,395 @@
|
||||
#include "at_uart.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
|
||||
#define TAG "AtUart"
|
||||
|
||||
|
||||
// AtUart 构造函数实现
|
||||
AtUart::AtUart(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin)
|
||||
: tx_pin_(tx_pin), rx_pin_(rx_pin), dtr_pin_(dtr_pin), uart_num_(UART_NUM),
|
||||
baud_rate_(115200), initialized_(false),
|
||||
event_task_handle_(nullptr), event_queue_handle_(nullptr), event_group_handle_(nullptr) {
|
||||
}
|
||||
|
||||
AtUart::~AtUart() {
|
||||
if (event_task_handle_) {
|
||||
vTaskDelete(event_task_handle_);
|
||||
}
|
||||
if (event_group_handle_) {
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
if (initialized_) {
|
||||
uart_driver_delete(uart_num_);
|
||||
}
|
||||
}
|
||||
|
||||
void AtUart::Initialize() {
|
||||
if (initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
if (!event_group_handle_) {
|
||||
ESP_LOGE(TAG, "创建事件组失败");
|
||||
return;
|
||||
}
|
||||
|
||||
uart_config_t uart_config = {};
|
||||
uart_config.baud_rate = baud_rate_;
|
||||
uart_config.data_bits = UART_DATA_8_BITS;
|
||||
uart_config.parity = UART_PARITY_DISABLE;
|
||||
uart_config.stop_bits = UART_STOP_BITS_1;
|
||||
uart_config.source_clk = UART_SCLK_DEFAULT;
|
||||
|
||||
ESP_ERROR_CHECK(uart_driver_install(uart_num_, 8192, 0, 100, &event_queue_handle_, ESP_INTR_FLAG_IRAM));
|
||||
ESP_ERROR_CHECK(uart_param_config(uart_num_, &uart_config));
|
||||
ESP_ERROR_CHECK(uart_set_pin(uart_num_, tx_pin_, rx_pin_, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
|
||||
|
||||
if (dtr_pin_ != GPIO_NUM_NC) {
|
||||
gpio_config_t config = {};
|
||||
config.pin_bit_mask = (1ULL << dtr_pin_);
|
||||
config.mode = GPIO_MODE_OUTPUT;
|
||||
config.pull_up_en = GPIO_PULLUP_DISABLE;
|
||||
config.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
config.intr_type = GPIO_INTR_DISABLE;
|
||||
gpio_config(&config);
|
||||
gpio_set_level(dtr_pin_, 0);
|
||||
}
|
||||
|
||||
xTaskCreate([](void* arg) {
|
||||
auto ml307_at_modem = (AtUart*)arg;
|
||||
ml307_at_modem->EventTask();
|
||||
vTaskDelete(NULL);
|
||||
}, "modem_event", 4096, this, 15, &event_task_handle_);
|
||||
|
||||
xTaskCreate([](void* arg) {
|
||||
auto ml307_at_modem = (AtUart*)arg;
|
||||
ml307_at_modem->ReceiveTask();
|
||||
vTaskDelete(NULL);
|
||||
}, "modem_receive", 4096 * 2, this, 15, &receive_task_handle_);
|
||||
initialized_ = true;
|
||||
}
|
||||
|
||||
void AtUart::EventTaskWrapper(void* arg) {
|
||||
auto uart = static_cast<AtUart*>(arg);
|
||||
uart->EventTask();
|
||||
vTaskDelete(nullptr);
|
||||
}
|
||||
|
||||
void AtUart::EventTask() {
|
||||
uart_event_t event;
|
||||
while (true) {
|
||||
if (xQueueReceive(event_queue_handle_, &event, portMAX_DELAY) == pdTRUE) {
|
||||
switch (event.type)
|
||||
{
|
||||
case UART_DATA:
|
||||
xEventGroupSetBits(event_group_handle_, AT_EVENT_DATA_AVAILABLE);
|
||||
break;
|
||||
case UART_BREAK:
|
||||
ESP_LOGI(TAG, "break");
|
||||
break;
|
||||
case UART_BUFFER_FULL:
|
||||
ESP_LOGE(TAG, "buffer full");
|
||||
break;
|
||||
case UART_FIFO_OVF:
|
||||
ESP_LOGE(TAG, "FIFO overflow");
|
||||
HandleUrc("FIFO_OVERFLOW", {});
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "unknown event type: %d", event.type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AtUart::ReceiveTask() {
|
||||
while (true) {
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, AT_EVENT_DATA_AVAILABLE, pdTRUE, pdFALSE, portMAX_DELAY);
|
||||
if (bits & AT_EVENT_DATA_AVAILABLE) {
|
||||
size_t available;
|
||||
uart_get_buffered_data_len(uart_num_, &available);
|
||||
if (available > 0) {
|
||||
// Extend rx_buffer_ and read into buffer
|
||||
rx_buffer_.resize(rx_buffer_.size() + available);
|
||||
char* rx_buffer_ptr = &rx_buffer_[rx_buffer_.size() - available];
|
||||
uart_read_bytes(uart_num_, rx_buffer_ptr, available, portMAX_DELAY);
|
||||
while (ParseResponse()) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_number(const std::string& s) {
|
||||
return !s.empty() && std::all_of(s.begin(), s.end(), ::isdigit) && s.length() < 10;
|
||||
}
|
||||
|
||||
bool AtUart::ParseResponse() {
|
||||
if (wait_for_response_ && rx_buffer_[0] == '>') {
|
||||
rx_buffer_.erase(0, 1);
|
||||
xEventGroupSetBits(event_group_handle_, AT_EVENT_COMMAND_DONE);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto end_pos = rx_buffer_.find("\r\n");
|
||||
if (end_pos == std::string::npos) {
|
||||
// FIXME: for +MHTTPURC: "ind", missing newline
|
||||
if (rx_buffer_.size() >= 16 && memcmp(rx_buffer_.c_str(), "+MHTTPURC: \"ind\"", 16) == 0) {
|
||||
// Find the end of this line and add \r\n if missing
|
||||
auto next_plus = rx_buffer_.find("+", 1);
|
||||
if (next_plus != std::string::npos) {
|
||||
// Insert \r\n before the next + command
|
||||
rx_buffer_.insert(next_plus, "\r\n");
|
||||
} else {
|
||||
// Append \r\n at the end
|
||||
rx_buffer_.append("\r\n");
|
||||
}
|
||||
end_pos = rx_buffer_.find("\r\n");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore empty lines
|
||||
if (end_pos == 0) {
|
||||
rx_buffer_.erase(0, 2);
|
||||
return true;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "<< %.64s (%u bytes)", rx_buffer_.substr(0, end_pos).c_str(), end_pos);
|
||||
// print last 64 bytes before end_pos if available
|
||||
// if (end_pos > 64) {
|
||||
// ESP_LOGI(TAG, "<< LAST: %.64s", rx_buffer_.c_str() + end_pos - 64);
|
||||
// }
|
||||
|
||||
// Parse "+CME ERROR: 123,456,789"
|
||||
if (rx_buffer_[0] == '+') {
|
||||
std::string command, values;
|
||||
auto pos = rx_buffer_.find(": ");
|
||||
if (pos == std::string::npos || pos > end_pos) {
|
||||
command = rx_buffer_.substr(1, end_pos - 1);
|
||||
} else {
|
||||
command = rx_buffer_.substr(1, pos - 1);
|
||||
values = rx_buffer_.substr(pos + 2, end_pos - pos - 2);
|
||||
}
|
||||
rx_buffer_.erase(0, end_pos + 2);
|
||||
|
||||
// Parse "string", int, int, ... into AtArgumentValue
|
||||
std::vector<AtArgumentValue> arguments;
|
||||
std::istringstream iss(values);
|
||||
std::string item;
|
||||
while (std::getline(iss, item, ',')) {
|
||||
AtArgumentValue argument;
|
||||
if (item.front() == '"') {
|
||||
argument.type = AtArgumentValue::Type::String;
|
||||
argument.string_value = item.substr(1, item.size() - 2);
|
||||
} else if (item.find(".") != std::string::npos) {
|
||||
argument.type = AtArgumentValue::Type::Double;
|
||||
argument.double_value = std::stod(item);
|
||||
} else if (is_number(item)) {
|
||||
argument.type = AtArgumentValue::Type::Int;
|
||||
argument.int_value = std::stoi(item);
|
||||
argument.string_value = std::move(item);
|
||||
} else {
|
||||
argument.type = AtArgumentValue::Type::String;
|
||||
argument.string_value = std::move(item);
|
||||
}
|
||||
arguments.push_back(argument);
|
||||
}
|
||||
|
||||
HandleUrc(command, arguments);
|
||||
return true;
|
||||
} else if (rx_buffer_.size() >= 4 && rx_buffer_[0] == 'O' && rx_buffer_[1] == 'K' && rx_buffer_[2] == '\r' && rx_buffer_[3] == '\n') {
|
||||
rx_buffer_.erase(0, 4);
|
||||
xEventGroupSetBits(event_group_handle_, AT_EVENT_COMMAND_DONE);
|
||||
return true;
|
||||
} else if (rx_buffer_.size() >= 7 && rx_buffer_[0] == 'E' && rx_buffer_[1] == 'R' && rx_buffer_[2] == 'R' && rx_buffer_[3] == 'O' && rx_buffer_[4] == 'R' && rx_buffer_[5] == '\r' && rx_buffer_[6] == '\n') {
|
||||
rx_buffer_.erase(0, 7);
|
||||
xEventGroupSetBits(event_group_handle_, AT_EVENT_COMMAND_ERROR);
|
||||
return true;
|
||||
} else {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
response_ = rx_buffer_.substr(0, end_pos);
|
||||
rx_buffer_.erase(0, end_pos + 2);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AtUart::HandleCommand(const char* command) {
|
||||
// 这个函数现在主要用于向后兼容,大部分处理逻辑已经移到 ParseLine 中
|
||||
if (wait_for_response_) {
|
||||
response_.append(command);
|
||||
response_.append("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
void AtUart::HandleUrc(const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
if (command == "CME ERROR") {
|
||||
cme_error_code_ = arguments[0].int_value;
|
||||
xEventGroupSetBits(event_group_handle_, AT_EVENT_COMMAND_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
for (auto& callback : urc_callbacks_) {
|
||||
callback(command, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
bool AtUart::DetectBaudRate() {
|
||||
int baud_rates[] = {115200, 921600, 460800, 230400, 57600, 38400, 19200, 9600};
|
||||
while (true) {
|
||||
ESP_LOGI(TAG, "Detecting baud rate...");
|
||||
for (size_t i = 0; i < sizeof(baud_rates) / sizeof(baud_rates[0]); i++) {
|
||||
int rate = baud_rates[i];
|
||||
uart_set_baudrate(uart_num_, rate);
|
||||
if (SendCommand("AT", 20)) {
|
||||
ESP_LOGI(TAG, "Detected baud rate: %d", rate);
|
||||
baud_rate_ = rate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AtUart::SetBaudRate(int new_baud_rate) {
|
||||
if (!DetectBaudRate()) {
|
||||
ESP_LOGE(TAG, "Failed to detect baud rate");
|
||||
return false;
|
||||
}
|
||||
if (new_baud_rate == baud_rate_) {
|
||||
return true;
|
||||
}
|
||||
// Set new baud rate
|
||||
if (!SendCommand(std::string("AT+IPR=") + std::to_string(new_baud_rate))) {
|
||||
ESP_LOGI(TAG, "Failed to set baud rate to %d", new_baud_rate);
|
||||
return false;
|
||||
}
|
||||
uart_set_baudrate(uart_num_, new_baud_rate);
|
||||
baud_rate_ = new_baud_rate;
|
||||
ESP_LOGI(TAG, "Set baud rate to %d", new_baud_rate);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AtUart::SendData(const char* data, size_t length) {
|
||||
if (!initialized_) {
|
||||
ESP_LOGE(TAG, "UART未初始化");
|
||||
return false;
|
||||
}
|
||||
|
||||
int ret = uart_write_bytes(uart_num_, data, length);
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "uart_write_bytes failed: %d", ret);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AtUart::SendCommandWithData(const std::string& command, size_t timeout_ms, bool add_crlf, const char* data, size_t data_length) {
|
||||
std::lock_guard<std::mutex> lock(command_mutex_);
|
||||
ESP_LOGD(TAG, ">> %.64s (%u bytes)", command.data(), command.length());
|
||||
|
||||
xEventGroupClearBits(event_group_handle_, AT_EVENT_COMMAND_DONE | AT_EVENT_COMMAND_ERROR);
|
||||
wait_for_response_ = true;
|
||||
cme_error_code_ = 0;
|
||||
response_.clear();
|
||||
|
||||
if (add_crlf) {
|
||||
if (!SendData((command + "\r\n").data(), command.length() + 2)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!SendData(command.data(), command.length())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (timeout_ms > 0) {
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, AT_EVENT_COMMAND_DONE | AT_EVENT_COMMAND_ERROR, pdTRUE, pdFALSE, pdMS_TO_TICKS(timeout_ms));
|
||||
wait_for_response_ = false;
|
||||
if (!(bits & AT_EVENT_COMMAND_DONE)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
wait_for_response_ = false;
|
||||
}
|
||||
|
||||
if (data && data_length > 0) {
|
||||
wait_for_response_ = true;
|
||||
if (!SendData(data, data_length)) {
|
||||
return false;
|
||||
}
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, AT_EVENT_COMMAND_DONE | AT_EVENT_COMMAND_ERROR, pdTRUE, pdFALSE, pdMS_TO_TICKS(timeout_ms));
|
||||
wait_for_response_ = false;
|
||||
if (!(bits & AT_EVENT_COMMAND_DONE)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AtUart::SendCommand(const std::string& command, size_t timeout_ms, bool add_crlf) {
|
||||
return SendCommandWithData(command, timeout_ms, add_crlf, nullptr, 0);
|
||||
}
|
||||
|
||||
std::list<UrcCallback>::iterator AtUart::RegisterUrcCallback(UrcCallback callback) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return urc_callbacks_.insert(urc_callbacks_.end(), callback);
|
||||
}
|
||||
|
||||
void AtUart::UnregisterUrcCallback(std::list<UrcCallback>::iterator iterator) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
urc_callbacks_.erase(iterator);
|
||||
}
|
||||
|
||||
void AtUart::SetDtrPin(bool high) {
|
||||
if (dtr_pin_ != GPIO_NUM_NC) {
|
||||
ESP_LOGD(TAG, "Set DTR pin %d to %d", dtr_pin_, high ? 1 : 0);
|
||||
gpio_set_level(dtr_pin_, high ? 1 : 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(20));
|
||||
}
|
||||
}
|
||||
|
||||
static const char hex_chars[] = "0123456789ABCDEF";
|
||||
// 辅助函数,将单个十六进制字符转换为对应的数值
|
||||
inline uint8_t CharToHex(char c) {
|
||||
if (c >= '0' && c <= '9') return c - '0';
|
||||
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||
return 0; // 对于无效输入,返回0
|
||||
}
|
||||
|
||||
void AtUart::EncodeHexAppend(std::string& dest, const char* data, size_t length) {
|
||||
dest.reserve(dest.size() + length * 2 + 4); // 预分配空间,多分配4个字节用于\r\n\0
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
dest.push_back(hex_chars[(data[i] & 0xF0) >> 4]);
|
||||
dest.push_back(hex_chars[data[i] & 0x0F]);
|
||||
}
|
||||
}
|
||||
|
||||
void AtUart::DecodeHexAppend(std::string& dest, const char* data, size_t length) {
|
||||
dest.reserve(dest.size() + length / 2 + 4); // 预分配空间,多分配4个字节用于\r\n\0
|
||||
for (size_t i = 0; i < length; i += 2) {
|
||||
char byte = (CharToHex(data[i]) << 4) | CharToHex(data[i + 1]);
|
||||
dest.push_back(byte);
|
||||
}
|
||||
}
|
||||
|
||||
std::string AtUart::EncodeHex(const std::string& data) {
|
||||
std::string encoded;
|
||||
EncodeHexAppend(encoded, data.c_str(), data.size());
|
||||
return encoded;
|
||||
}
|
||||
|
||||
std::string AtUart::DecodeHex(const std::string& data) {
|
||||
std::string decoded;
|
||||
DecodeHexAppend(decoded, data.c_str(), data.size());
|
||||
return decoded;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
#include "ec801e_at_modem.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
#include <cassert>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <cstring>
|
||||
#include "ec801e_ssl.h"
|
||||
#include "ec801e_tcp.h"
|
||||
#include "ec801e_udp.h"
|
||||
#include "ec801e_mqtt.h"
|
||||
#include "http_client.h"
|
||||
#include "web_socket.h"
|
||||
|
||||
#define TAG "Ec801EAtModem"
|
||||
|
||||
|
||||
Ec801EAtModem::Ec801EAtModem(std::shared_ptr<AtUart> at_uart) : AtModem(at_uart) {
|
||||
// 子类特定的初始化在这里
|
||||
// ATE0 关闭 echo
|
||||
at_uart_->SendCommand("ATE0");
|
||||
// 设置 URC 端口为 UART1
|
||||
at_uart_->SendCommand("AT+QURCCFG=\"urcport\",\"uart1\"");
|
||||
}
|
||||
|
||||
void Ec801EAtModem::HandleUrc(const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
// Handle Common URC
|
||||
AtModem::HandleUrc(command, arguments);
|
||||
}
|
||||
|
||||
bool Ec801EAtModem::SetSleepMode(bool enable, int delay_seconds) {
|
||||
if (enable) {
|
||||
if (delay_seconds > 0) {
|
||||
at_uart_->SendCommand("AT+QSCLKEX=1," + std::to_string(delay_seconds) + ",30");
|
||||
}
|
||||
return at_uart_->SendCommand("AT+QSCLK=1");
|
||||
} else {
|
||||
return at_uart_->SendCommand("AT+QSCLK=0");
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Http> Ec801EAtModem::CreateHttp(int connect_id) {
|
||||
assert(connect_id >= 0);
|
||||
return std::make_unique<HttpClient>(this, connect_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<Tcp> Ec801EAtModem::CreateTcp(int connect_id) {
|
||||
assert(connect_id >= 0);
|
||||
return std::make_unique<Ec801ETcp>(at_uart_, connect_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<Tcp> Ec801EAtModem::CreateSsl(int connect_id) {
|
||||
assert(connect_id >= 0);
|
||||
return std::make_unique<Ec801ESsl>(at_uart_, connect_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<Udp> Ec801EAtModem::CreateUdp(int connect_id) {
|
||||
assert(connect_id >= 0);
|
||||
return std::make_unique<Ec801EUdp>(at_uart_, connect_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<Mqtt> Ec801EAtModem::CreateMqtt(int connect_id) {
|
||||
assert(connect_id >= 0);
|
||||
return std::make_unique<Ec801EMqtt>(at_uart_, connect_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<WebSocket> Ec801EAtModem::CreateWebSocket(int connect_id) {
|
||||
assert(connect_id >= 0);
|
||||
return std::make_unique<WebSocket>(this, connect_id);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#ifndef _EC801E_AT_MODEM_H_
|
||||
#define _EC801E_AT_MODEM_H_
|
||||
|
||||
#include "at_modem.h"
|
||||
|
||||
class Ec801EAtModem : public AtModem {
|
||||
public:
|
||||
Ec801EAtModem(std::shared_ptr<AtUart> at_uart);
|
||||
~Ec801EAtModem() override = default;
|
||||
|
||||
bool SetSleepMode(bool enable, int delay_seconds=0) override;
|
||||
|
||||
// 实现基类的纯虚函数
|
||||
std::unique_ptr<Http> CreateHttp(int connect_id) override;
|
||||
std::unique_ptr<Tcp> CreateTcp(int connect_id) override;
|
||||
std::unique_ptr<Tcp> CreateSsl(int connect_id) override;
|
||||
std::unique_ptr<Udp> CreateUdp(int connect_id) override;
|
||||
std::unique_ptr<Mqtt> CreateMqtt(int connect_id) override;
|
||||
std::unique_ptr<WebSocket> CreateWebSocket(int connect_id) override;
|
||||
|
||||
protected:
|
||||
void HandleUrc(const std::string& command, const std::vector<AtArgumentValue>& arguments) override;
|
||||
};
|
||||
|
||||
|
||||
#endif // _EC801E_AT_MODEM_H_
|
||||
244
managed_components/78__esp-ml307/src/ec801e/ec801e_mqtt.cc
Normal file
244
managed_components/78__esp-ml307/src/ec801e/ec801e_mqtt.cc
Normal file
@@ -0,0 +1,244 @@
|
||||
#include "ec801e_mqtt.h"
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "Ec801EMqtt"
|
||||
|
||||
Ec801EMqtt::Ec801EMqtt(std::shared_ptr<AtUart> at_uart, int mqtt_id) : at_uart_(at_uart), mqtt_id_(mqtt_id) {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
|
||||
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
if (command == "QMTRECV" && arguments.size() >= 4) {
|
||||
if (arguments[0].int_value == mqtt_id_) {
|
||||
auto topic = arguments[2].string_value;
|
||||
if (on_message_callback_) {
|
||||
on_message_callback_(topic, at_uart_->DecodeHex(arguments[3].string_value));
|
||||
}
|
||||
}
|
||||
} else if (command == "QMTSTAT" && arguments.size() == 2) {
|
||||
if (arguments[0].int_value == mqtt_id_) {
|
||||
auto error_code = arguments[1].int_value;
|
||||
if (error_code != 0) {
|
||||
auto error_message = ErrorToString(error_code);
|
||||
ESP_LOGE(TAG, "MQTT error occurred: %s", error_message.c_str());
|
||||
if (on_error_callback_) {
|
||||
on_error_callback_(error_message);
|
||||
}
|
||||
if (connected_) {
|
||||
connected_ = false;
|
||||
if (on_disconnected_callback_) {
|
||||
on_disconnected_callback_();
|
||||
}
|
||||
}
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_MQTT_DISCONNECTED_EVENT);
|
||||
}
|
||||
}
|
||||
} else if (command == "QMTCONN" && arguments.size() == 3) {
|
||||
if (arguments[0].int_value == mqtt_id_) {
|
||||
error_code_ = arguments[2].int_value;
|
||||
if (error_code_ == 0) {
|
||||
if (!connected_) {
|
||||
connected_ = true;
|
||||
if (on_connected_callback_) {
|
||||
on_connected_callback_();
|
||||
}
|
||||
}
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_MQTT_CONNECTED_EVENT);
|
||||
} else {
|
||||
if (connected_) {
|
||||
connected_ = false;
|
||||
if (on_disconnected_callback_) {
|
||||
on_disconnected_callback_();
|
||||
}
|
||||
}
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_MQTT_DISCONNECTED_EVENT);
|
||||
}
|
||||
}
|
||||
} else if (command == "QMTOPEN" && arguments.size() == 2) {
|
||||
if (arguments[0].int_value == mqtt_id_) {
|
||||
error_code_ = arguments[1].int_value;
|
||||
if (error_code_ == 0) {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_MQTT_OPEN_COMPLETE);
|
||||
} else {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_MQTT_OPEN_FAILED);
|
||||
}
|
||||
}
|
||||
} else if (command == "QMTDISC" && arguments.size() == 2) {
|
||||
if (arguments[0].int_value == mqtt_id_) {
|
||||
if (arguments[1].int_value == 0) {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_MQTT_DISCONNECTED_EVENT);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to disconnect from MQTT broker");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ec801EMqtt::~Ec801EMqtt() {
|
||||
at_uart_->UnregisterUrcCallback(urc_callback_it_);
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
|
||||
bool Ec801EMqtt::Connect(const std::string broker_address, int broker_port, const std::string client_id, const std::string username, const std::string password) {
|
||||
EventBits_t bits;
|
||||
|
||||
if (broker_port == 8883) {
|
||||
// Config SSL Context
|
||||
at_uart_->SendCommand("AT+QSSLCFG=\"sslversion\",2,4;+QSSLCFG=\"ciphersuite\",2,0xFFFF;+QSSLCFG=\"seclevel\",2,0");
|
||||
if (!at_uart_->SendCommand(std::string("AT+QMTCFG=\"ssl\",") + std::to_string(mqtt_id_) + ",1,2")) {
|
||||
ESP_LOGE(TAG, "Failed to set MQTT to use SSL");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Set version
|
||||
if (!at_uart_->SendCommand(std::string("AT+QMTCFG=\"version\",") + std::to_string(mqtt_id_) + ",4")) {
|
||||
ESP_LOGE(TAG, "Failed to set MQTT version to 3.1.1");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set clean session
|
||||
if (!at_uart_->SendCommand(std::string("AT+QMTCFG=\"session\",") + std::to_string(mqtt_id_) + ",1")) {
|
||||
ESP_LOGE(TAG, "Failed to set MQTT clean session");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set keep alive
|
||||
if (!at_uart_->SendCommand(std::string("AT+QMTCFG=\"keepalive\",") + std::to_string(mqtt_id_) + "," + std::to_string(keep_alive_seconds_))) {
|
||||
ESP_LOGE(TAG, "Failed to set MQTT keep alive");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set HEX encoding (ASCII for sending, HEX for receiving)
|
||||
if (!at_uart_->SendCommand("AT+QMTCFG=\"dataformat\"," + std::to_string(mqtt_id_) + ",0,1")) {
|
||||
ESP_LOGE(TAG, "Failed to set MQTT to use HEX encoding");
|
||||
return false;
|
||||
}
|
||||
|
||||
xEventGroupClearBits(event_group_handle_, EC801E_MQTT_OPEN_COMPLETE | EC801E_MQTT_OPEN_FAILED);
|
||||
std::string command = "AT+QMTOPEN=" + std::to_string(mqtt_id_) + ",\"" + broker_address + "\"," + std::to_string(broker_port);
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to open MQTT connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
bits = xEventGroupWaitBits(event_group_handle_, EC801E_MQTT_OPEN_COMPLETE | EC801E_MQTT_OPEN_FAILED, pdTRUE, pdFALSE, pdMS_TO_TICKS(EC801E_MQTT_CONNECT_TIMEOUT_MS));
|
||||
if (bits & EC801E_MQTT_OPEN_FAILED) {
|
||||
const char* error_code_str[] = {
|
||||
"Connected",
|
||||
"Parameter error",
|
||||
"MQTT identifier occupied",
|
||||
"PDP activation failed",
|
||||
"Domain name resolution failed",
|
||||
"Server disconnected"
|
||||
};
|
||||
const char* message = error_code_ < 6 ? error_code_str[error_code_] : "Unknown error";
|
||||
ESP_LOGE(TAG, "Failed to open MQTT connection: %s", message);
|
||||
|
||||
if (error_code_ == 2) { // MQTT 标识符被占用
|
||||
at_uart_->SendCommand(std::string("AT+QMTDISC=") + std::to_string(mqtt_id_));
|
||||
bits = xEventGroupWaitBits(event_group_handle_, EC801E_MQTT_DISCONNECTED_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(EC801E_MQTT_CONNECT_TIMEOUT_MS));
|
||||
if (!(bits & EC801E_MQTT_DISCONNECTED_EVENT)) {
|
||||
ESP_LOGE(TAG, "Failed to disconnect from previous connection");
|
||||
return false;
|
||||
}
|
||||
return Connect(broker_address, broker_port, client_id, username, password);
|
||||
}
|
||||
return false;
|
||||
} else if (!(bits & EC801E_MQTT_OPEN_COMPLETE)) {
|
||||
ESP_LOGE(TAG, "MQTT connection timeout");
|
||||
return false;
|
||||
}
|
||||
|
||||
xEventGroupClearBits(event_group_handle_, EC801E_MQTT_CONNECTED_EVENT | EC801E_MQTT_DISCONNECTED_EVENT);
|
||||
command = "AT+QMTCONN=" + std::to_string(mqtt_id_) + ",\"" + client_id + "\",\"" + username + "\",\"" + password + "\"";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to connect to MQTT broker");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 等待连接完成
|
||||
bits = xEventGroupWaitBits(event_group_handle_, EC801E_MQTT_CONNECTED_EVENT | EC801E_MQTT_DISCONNECTED_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(EC801E_MQTT_CONNECT_TIMEOUT_MS));
|
||||
if (bits & EC801E_MQTT_DISCONNECTED_EVENT) {
|
||||
const char* error_code_str[] = {
|
||||
"Accepted",
|
||||
"Rejected: Unacceptable protocol version",
|
||||
"Rejected: Identifier rejected",
|
||||
"Rejected: Server unavailable",
|
||||
"Rejected: Wrong username or password",
|
||||
"Rejected: Unauthorized"
|
||||
};
|
||||
const char* message = error_code_ < 6 ? error_code_str[error_code_] : "Unknown error";
|
||||
ESP_LOGE(TAG, "Failed to connect to MQTT broker: %s", message);
|
||||
return false;
|
||||
} else if (!(bits & EC801E_MQTT_CONNECTED_EVENT)) {
|
||||
ESP_LOGE(TAG, "MQTT connection timeout");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Ec801EMqtt::IsConnected() {
|
||||
return connected_;
|
||||
}
|
||||
|
||||
void Ec801EMqtt::Disconnect() {
|
||||
if (!connected_) {
|
||||
return;
|
||||
}
|
||||
at_uart_->SendCommand(std::string("AT+QMTDISC=") + std::to_string(mqtt_id_));
|
||||
}
|
||||
|
||||
bool Ec801EMqtt::Publish(const std::string topic, const std::string payload, int qos) {
|
||||
if (!connected_) {
|
||||
return false;
|
||||
}
|
||||
// If payload size is larger than 64KB, a CME ERROR 601 will be returned.
|
||||
std::string command = "AT+QMTPUBEX=" + std::to_string(mqtt_id_) + ",0,0,0,\"" + topic + "\",";
|
||||
command += std::to_string(payload.size());
|
||||
if (!at_uart_->SendCommandWithData(command, 1000, true, payload.data(), payload.size())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Ec801EMqtt::Subscribe(const std::string topic, int qos) {
|
||||
if (!connected_) {
|
||||
return false;
|
||||
}
|
||||
std::string command = "AT+QMTSUB=" + std::to_string(mqtt_id_) + ",0,\"" + topic + "\"," + std::to_string(qos);
|
||||
return at_uart_->SendCommand(command);
|
||||
}
|
||||
|
||||
bool Ec801EMqtt::Unsubscribe(const std::string topic) {
|
||||
if (!connected_) {
|
||||
return false;
|
||||
}
|
||||
std::string command = "AT+QMTUNS=" + std::to_string(mqtt_id_) + ",0,\"" + topic + "\"";
|
||||
return at_uart_->SendCommand(command);
|
||||
}
|
||||
|
||||
std::string Ec801EMqtt::ErrorToString(int error_code) {
|
||||
switch (error_code) {
|
||||
case 0:
|
||||
return "Connected";
|
||||
case 1:
|
||||
return "Server disconnected or reset";
|
||||
case 2:
|
||||
return "Ping timeout or failed";
|
||||
case 3:
|
||||
return "Connect timeout or failed";
|
||||
case 4:
|
||||
return "Receive CONNACK timeout or failed";
|
||||
case 5:
|
||||
return "Client sends DISCONNECT packet, but server actively disconnects MQTT connection";
|
||||
case 6:
|
||||
return "Client actively disconnects MQTT connection because sending data packets always fails";
|
||||
case 7:
|
||||
return "Link does not work or server is unavailable";
|
||||
case 8:
|
||||
return "Client actively disconnects MQTT connection";
|
||||
default:
|
||||
return "Unknown error";
|
||||
}
|
||||
}
|
||||
45
managed_components/78__esp-ml307/src/ec801e/ec801e_mqtt.h
Normal file
45
managed_components/78__esp-ml307/src/ec801e/ec801e_mqtt.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef EC801E_MQTT_H
|
||||
#define EC801E_MQTT_H
|
||||
|
||||
#include "mqtt.h"
|
||||
|
||||
#include "at_uart.h"
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
#define EC801E_MQTT_CONNECT_TIMEOUT_MS 10000
|
||||
|
||||
#define EC801E_MQTT_CONNECTED_EVENT BIT1
|
||||
#define EC801E_MQTT_DISCONNECTED_EVENT BIT2
|
||||
#define EC801E_MQTT_OPEN_COMPLETE BIT5
|
||||
#define EC801E_MQTT_OPEN_FAILED BIT6
|
||||
|
||||
class Ec801EMqtt : public Mqtt {
|
||||
public:
|
||||
Ec801EMqtt(std::shared_ptr<AtUart> at_uart, int mqtt_id);
|
||||
~Ec801EMqtt();
|
||||
|
||||
bool Connect(const std::string broker_address, int broker_port, const std::string client_id, const std::string username, const std::string password);
|
||||
void Disconnect();
|
||||
bool Publish(const std::string topic, const std::string payload, int qos = 0);
|
||||
bool Subscribe(const std::string topic, int qos = 0);
|
||||
bool Unsubscribe(const std::string topic);
|
||||
bool IsConnected();
|
||||
|
||||
private:
|
||||
std::shared_ptr<AtUart> at_uart_;
|
||||
int mqtt_id_;
|
||||
bool connected_ = false;
|
||||
int error_code_ = 0;
|
||||
EventGroupHandle_t event_group_handle_;
|
||||
std::string message_payload_;
|
||||
|
||||
std::list<UrcCallback>::iterator urc_callback_it_;
|
||||
|
||||
std::string ErrorToString(int error_code);
|
||||
};
|
||||
|
||||
#endif
|
||||
160
managed_components/78__esp-ml307/src/ec801e/ec801e_ssl.cc
Normal file
160
managed_components/78__esp-ml307/src/ec801e/ec801e_ssl.cc
Normal file
@@ -0,0 +1,160 @@
|
||||
#include "ec801e_ssl.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "Ec801ESsl"
|
||||
|
||||
|
||||
Ec801ESsl::Ec801ESsl(std::shared_ptr<AtUart> at_uart, int ssl_id) : at_uart_(at_uart), ssl_id_(ssl_id) {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
|
||||
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
if (command == "QSSLOPEN" && arguments.size() == 2) {
|
||||
if (arguments[0].int_value == ssl_id_ && !instance_active_) {
|
||||
if (arguments[1].int_value == 0) {
|
||||
connected_ = true;
|
||||
instance_active_ = true;
|
||||
xEventGroupClearBits(event_group_handle_, EC801E_SSL_DISCONNECTED | EC801E_SSL_ERROR);
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_SSL_CONNECTED);
|
||||
} else {
|
||||
connected_ = false;
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_SSL_ERROR);
|
||||
}
|
||||
}
|
||||
} else if (command == "QSSLCLOSE" && arguments.size() == 1) {
|
||||
if (arguments[0].int_value == ssl_id_) {
|
||||
instance_active_ = false;
|
||||
}
|
||||
} else if (command == "QISEND" && arguments.size() == 3) {
|
||||
if (arguments[0].int_value == ssl_id_) {
|
||||
if (arguments[1].int_value == 0) {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_SSL_SEND_COMPLETE);
|
||||
} else {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_SSL_ERROR);
|
||||
}
|
||||
}
|
||||
} else if (command == "QSSLURC" && arguments.size() >= 2) {
|
||||
if (arguments[1].int_value == ssl_id_) {
|
||||
if (arguments[0].string_value == "recv" && arguments.size() >= 4) {
|
||||
if (stream_callback_) {
|
||||
stream_callback_(at_uart_->DecodeHex(arguments[3].string_value));
|
||||
}
|
||||
} else if (arguments[0].string_value == "closed") {
|
||||
if (connected_) {
|
||||
connected_ = false;
|
||||
// instance_active_ 保持 true,需要发送 QICLOSE 清理
|
||||
if (disconnect_callback_) {
|
||||
disconnect_callback_();
|
||||
}
|
||||
}
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_SSL_DISCONNECTED);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unknown QIURC command: %s", arguments[0].string_value.c_str());
|
||||
}
|
||||
}
|
||||
} else if (command == "QSSLSTATE" && arguments.size() > 5) {
|
||||
if (arguments[0].int_value == ssl_id_) {
|
||||
connected_ = arguments[5].int_value == 2;
|
||||
instance_active_ = true;
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_SSL_INITIALIZED);
|
||||
}
|
||||
} else if (command == "FIFO_OVERFLOW") {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_SSL_ERROR);
|
||||
Disconnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ec801ESsl::~Ec801ESsl() {
|
||||
Disconnect();
|
||||
at_uart_->UnregisterUrcCallback(urc_callback_it_);
|
||||
}
|
||||
|
||||
bool Ec801ESsl::Connect(const std::string& host, int port) {
|
||||
// Clear bits
|
||||
xEventGroupClearBits(event_group_handle_, EC801E_SSL_CONNECTED | EC801E_SSL_DISCONNECTED | EC801E_SSL_ERROR);
|
||||
|
||||
// Keep data in one line; Use HEX encoding in response
|
||||
at_uart_->SendCommand("AT+QICFG=\"close/mode\",1;+QICFG=\"viewmode\",1;+QICFG=\"sendinfo\",1;+QICFG=\"dataformat\",0,1");
|
||||
|
||||
// Config SSL Context
|
||||
at_uart_->SendCommand("AT+QSSLCFG=\"sslversion\",1,4;+QSSLCFG=\"ciphersuite\",1,0xFFFF;+QSSLCFG=\"seclevel\",1,0");
|
||||
// at_uart_->SendCommand("AT+QSSLCFG=\"cacert\",1,\"UFS:cacert.pem\"");
|
||||
|
||||
// 检查这个 id 是否已经连接
|
||||
std::string command = "AT+QSSLSTATE=1," + std::to_string(ssl_id_);
|
||||
at_uart_->SendCommand(command);
|
||||
|
||||
// 断开之前的连接(不触发回调事件)
|
||||
if (instance_active_) {
|
||||
at_uart_->SendCommand("AT+QSSLCLOSE=" + std::to_string(ssl_id_));
|
||||
xEventGroupWaitBits(event_group_handle_, EC801E_SSL_DISCONNECTED, pdTRUE, pdFALSE, SSL_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||
instance_active_ = false;
|
||||
}
|
||||
|
||||
// 打开 TCP 连接
|
||||
command = "AT+QSSLOPEN=1,1," + std::to_string(ssl_id_) + ",\"" + host + "\"," + std::to_string(port) + ",1";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to open TCP connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 等待连接完成
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, EC801E_SSL_CONNECTED | EC801E_SSL_ERROR, pdTRUE, pdFALSE, SSL_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||
if (bits & EC801E_SSL_ERROR) {
|
||||
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void Ec801ESsl::Disconnect() {
|
||||
if (!instance_active_) {
|
||||
return;
|
||||
}
|
||||
|
||||
at_uart_->SendCommand("AT+QSSLCLOSE=" + std::to_string(ssl_id_));
|
||||
|
||||
if (connected_) {
|
||||
connected_ = false;
|
||||
if (disconnect_callback_) {
|
||||
disconnect_callback_();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Ec801ESsl::Send(const std::string& data) {
|
||||
const size_t MAX_PACKET_SIZE = 1460;
|
||||
size_t total_sent = 0;
|
||||
|
||||
if (!connected_) {
|
||||
ESP_LOGE(TAG, "Not connected");
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (total_sent < data.size()) {
|
||||
size_t chunk_size = std::min(data.size() - total_sent, MAX_PACKET_SIZE);
|
||||
|
||||
std::string command = "AT+QSSLSEND=" + std::to_string(ssl_id_) + "," + std::to_string(chunk_size);
|
||||
|
||||
if (!at_uart_->SendCommandWithData(command, 1000, true, data.data() + total_sent, chunk_size)) {
|
||||
ESP_LOGE(TAG, "Send command failed");
|
||||
Disconnect();
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, EC801E_SSL_SEND_COMPLETE | EC801E_SSL_SEND_FAILED, pdTRUE, pdFALSE, pdMS_TO_TICKS(SSL_CONNECT_TIMEOUT_MS));
|
||||
if (bits & EC801E_SSL_SEND_FAILED) {
|
||||
ESP_LOGE(TAG, "Send failed, retry later");
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
continue;
|
||||
} else if (!(bits & EC801E_SSL_SEND_COMPLETE)) {
|
||||
ESP_LOGE(TAG, "Send timeout");
|
||||
return -1;
|
||||
}
|
||||
|
||||
total_sent += chunk_size;
|
||||
}
|
||||
return data.size();
|
||||
}
|
||||
36
managed_components/78__esp-ml307/src/ec801e/ec801e_ssl.h
Normal file
36
managed_components/78__esp-ml307/src/ec801e/ec801e_ssl.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef EC801E_SSL_H
|
||||
#define EC801E_SSL_H
|
||||
|
||||
#include "tcp.h"
|
||||
#include "at_uart.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
|
||||
#define EC801E_SSL_CONNECTED BIT0
|
||||
#define EC801E_SSL_DISCONNECTED BIT1
|
||||
#define EC801E_SSL_ERROR BIT2
|
||||
#define EC801E_SSL_SEND_COMPLETE BIT3
|
||||
#define EC801E_SSL_SEND_FAILED BIT4
|
||||
#define EC801E_SSL_INITIALIZED BIT5
|
||||
|
||||
#define SSL_CONNECT_TIMEOUT_MS 10000
|
||||
|
||||
class Ec801ESsl : public Tcp {
|
||||
public:
|
||||
Ec801ESsl(std::shared_ptr<AtUart> at_uart, int ssl_id);
|
||||
~Ec801ESsl();
|
||||
|
||||
bool Connect(const std::string& host, int port) override;
|
||||
void Disconnect() override;
|
||||
int Send(const std::string& data) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<AtUart> at_uart_;
|
||||
int ssl_id_;
|
||||
bool instance_active_ = false;
|
||||
EventGroupHandle_t event_group_handle_;
|
||||
std::list<UrcCallback>::iterator urc_callback_it_;
|
||||
};
|
||||
|
||||
#endif // EC801E_SSL_H
|
||||
159
managed_components/78__esp-ml307/src/ec801e/ec801e_tcp.cc
Normal file
159
managed_components/78__esp-ml307/src/ec801e/ec801e_tcp.cc
Normal file
@@ -0,0 +1,159 @@
|
||||
#include "ec801e_tcp.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "Ec801ETcp"
|
||||
|
||||
|
||||
Ec801ETcp::Ec801ETcp(std::shared_ptr<AtUart> at_uart, int tcp_id) : at_uart_(at_uart), tcp_id_(tcp_id) {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
|
||||
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
if (command == "QIOPEN" && arguments.size() == 2) {
|
||||
if (arguments[0].int_value == tcp_id_) {
|
||||
if (arguments[1].int_value == 0) {
|
||||
connected_ = true;
|
||||
instance_active_ = true;
|
||||
xEventGroupClearBits(event_group_handle_, EC801E_TCP_DISCONNECTED | EC801E_TCP_ERROR);
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_TCP_CONNECTED);
|
||||
} else {
|
||||
connected_ = false;
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_TCP_ERROR);
|
||||
if (disconnect_callback_) {
|
||||
disconnect_callback_();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (command == "QISEND" && arguments.size() == 3) {
|
||||
if (arguments[0].int_value == tcp_id_) {
|
||||
if (arguments[1].int_value == 0) {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_TCP_SEND_COMPLETE);
|
||||
} else {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_TCP_SEND_FAILED);
|
||||
}
|
||||
}
|
||||
} else if (command == "QIURC" && arguments.size() >= 2) {
|
||||
if (arguments[1].int_value == tcp_id_) {
|
||||
if (arguments[0].string_value == "recv" && arguments.size() >= 4) {
|
||||
if (connected_ && stream_callback_) {
|
||||
stream_callback_(at_uart_->DecodeHex(arguments[3].string_value));
|
||||
}
|
||||
} else if (arguments[0].string_value == "closed") {
|
||||
if (connected_) {
|
||||
connected_ = false;
|
||||
// instance_active_ 保持 true,需要发送 QICLOSE 清理
|
||||
if (disconnect_callback_) {
|
||||
disconnect_callback_();
|
||||
}
|
||||
}
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_TCP_DISCONNECTED);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unknown QIURC command: %s", arguments[0].string_value.c_str());
|
||||
}
|
||||
}
|
||||
} else if (command == "QISTATE" && arguments.size() > 5) {
|
||||
if (arguments[0].int_value == tcp_id_) {
|
||||
connected_ = arguments[5].int_value == 2;
|
||||
instance_active_ = true;
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_TCP_INITIALIZED);
|
||||
}
|
||||
} else if (command == "FIFO_OVERFLOW") {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_TCP_ERROR);
|
||||
Disconnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ec801ETcp::~Ec801ETcp() {
|
||||
Disconnect();
|
||||
at_uart_->UnregisterUrcCallback(urc_callback_it_);
|
||||
if (event_group_handle_) {
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool Ec801ETcp::Connect(const std::string& host, int port) {
|
||||
// Clear bits
|
||||
xEventGroupClearBits(event_group_handle_, EC801E_TCP_CONNECTED | EC801E_TCP_DISCONNECTED | EC801E_TCP_ERROR);
|
||||
|
||||
// Keep data in one line; Use HEX encoding in response
|
||||
at_uart_->SendCommand("AT+QICFG=\"close/mode\",1;+QICFG=\"viewmode\",1;+QICFG=\"sendinfo\",1;+QICFG=\"dataformat\",0,1");
|
||||
|
||||
// 检查这个 id 是否已经连接
|
||||
std::string command = "AT+QISTATE=1," + std::to_string(tcp_id_);
|
||||
at_uart_->SendCommand(command);
|
||||
|
||||
// 断开之前的连接(不触发回调事件)
|
||||
if (instance_active_) {
|
||||
at_uart_->SendCommand("AT+QICLOSE=" + std::to_string(tcp_id_));
|
||||
xEventGroupWaitBits(event_group_handle_, EC801E_TCP_DISCONNECTED, pdTRUE, pdFALSE, TCP_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||
instance_active_ = false;
|
||||
}
|
||||
|
||||
// 打开 TCP 连接
|
||||
command = "AT+QIOPEN=1," + std::to_string(tcp_id_) + ",\"TCP\",\"" + host + "\"," + std::to_string(port) + ",0,1";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to open TCP connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 等待连接完成
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, EC801E_TCP_CONNECTED | EC801E_TCP_ERROR, pdTRUE, pdFALSE, TCP_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||
if (bits & EC801E_TCP_ERROR) {
|
||||
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Ec801ETcp::Disconnect() {
|
||||
if (!instance_active_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (at_uart_->SendCommand("AT+QICLOSE=" + std::to_string(tcp_id_))) {
|
||||
instance_active_ = false;
|
||||
}
|
||||
|
||||
if (connected_) {
|
||||
connected_ = false;
|
||||
if (disconnect_callback_) {
|
||||
disconnect_callback_();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Ec801ETcp::Send(const std::string& data) {
|
||||
const size_t MAX_PACKET_SIZE = 1460;
|
||||
size_t total_sent = 0;
|
||||
|
||||
if (!connected_) {
|
||||
ESP_LOGE(TAG, "Not connected");
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (total_sent < data.size()) {
|
||||
size_t chunk_size = std::min(data.size() - total_sent, MAX_PACKET_SIZE);
|
||||
|
||||
std::string command = "AT+QISEND=" + std::to_string(tcp_id_) + "," + std::to_string(chunk_size);
|
||||
|
||||
if (!at_uart_->SendCommandWithData(command, 1000, true, data.data() + total_sent, chunk_size)) {
|
||||
ESP_LOGE(TAG, "Send command failed");
|
||||
Disconnect();
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, EC801E_TCP_SEND_COMPLETE | EC801E_TCP_SEND_FAILED, pdTRUE, pdFALSE, pdMS_TO_TICKS(TCP_CONNECT_TIMEOUT_MS));
|
||||
if (bits & EC801E_TCP_SEND_FAILED) {
|
||||
ESP_LOGE(TAG, "Send failed, retry later");
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
continue;
|
||||
} else if (!(bits & EC801E_TCP_SEND_COMPLETE)) {
|
||||
ESP_LOGE(TAG, "Send timeout");
|
||||
return -1;
|
||||
}
|
||||
|
||||
total_sent += chunk_size;
|
||||
}
|
||||
return data.size();
|
||||
}
|
||||
37
managed_components/78__esp-ml307/src/ec801e/ec801e_tcp.h
Normal file
37
managed_components/78__esp-ml307/src/ec801e/ec801e_tcp.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#ifndef EC801E_TCP_H
|
||||
#define EC801E_TCP_H
|
||||
|
||||
#include "tcp.h"
|
||||
#include "at_uart.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <string>
|
||||
|
||||
#define EC801E_TCP_CONNECTED BIT0
|
||||
#define EC801E_TCP_DISCONNECTED BIT1
|
||||
#define EC801E_TCP_ERROR BIT2
|
||||
#define EC801E_TCP_SEND_COMPLETE BIT3
|
||||
#define EC801E_TCP_SEND_FAILED BIT4
|
||||
#define EC801E_TCP_INITIALIZED BIT5
|
||||
|
||||
#define TCP_CONNECT_TIMEOUT_MS 10000
|
||||
|
||||
class Ec801ETcp : public Tcp {
|
||||
public:
|
||||
Ec801ETcp(std::shared_ptr<AtUart> at_uart, int tcp_id);
|
||||
~Ec801ETcp();
|
||||
|
||||
bool Connect(const std::string& host, int port) override;
|
||||
void Disconnect() override;
|
||||
int Send(const std::string& data) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<AtUart> at_uart_;
|
||||
int tcp_id_;
|
||||
bool instance_active_ = false;
|
||||
EventGroupHandle_t event_group_handle_;
|
||||
std::list<UrcCallback>::iterator urc_callback_it_;
|
||||
};
|
||||
|
||||
#endif // EC801E_TCP_H
|
||||
141
managed_components/78__esp-ml307/src/ec801e/ec801e_udp.cc
Normal file
141
managed_components/78__esp-ml307/src/ec801e/ec801e_udp.cc
Normal file
@@ -0,0 +1,141 @@
|
||||
#include "ec801e_udp.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "Ec801EUdp"
|
||||
|
||||
|
||||
Ec801EUdp::Ec801EUdp(std::shared_ptr<AtUart> at_uart, int udp_id) : at_uart_(at_uart), udp_id_(udp_id) {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
|
||||
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
if (command == "QIOPEN" && arguments.size() == 2) {
|
||||
if (arguments[0].int_value == udp_id_) {
|
||||
connected_ = arguments[1].int_value == 0;
|
||||
if (connected_) {
|
||||
instance_active_ = true;
|
||||
xEventGroupClearBits(event_group_handle_, EC801E_UDP_DISCONNECTED | EC801E_UDP_ERROR);
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_UDP_CONNECTED);
|
||||
} else {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_UDP_ERROR);
|
||||
}
|
||||
}
|
||||
} else if (command == "QISEND" && arguments.size() == 3) {
|
||||
if (arguments[0].int_value == udp_id_) {
|
||||
if (arguments[1].int_value == 0) {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_UDP_SEND_COMPLETE);
|
||||
} else {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_UDP_SEND_FAILED);
|
||||
}
|
||||
}
|
||||
} else if (command == "QIURC" && arguments.size() >= 2) {
|
||||
if (arguments[1].int_value == udp_id_) {
|
||||
if (arguments[0].string_value == "recv" && arguments.size() >= 4) {
|
||||
if (connected_ && message_callback_) {
|
||||
message_callback_(at_uart_->DecodeHex(arguments[3].string_value));
|
||||
}
|
||||
} else if (arguments[0].string_value == "closed") {
|
||||
connected_ = false;
|
||||
instance_active_ = false;
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_UDP_DISCONNECTED);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unknown QIURC command: %s", arguments[0].string_value.c_str());
|
||||
}
|
||||
}
|
||||
} else if (command == "QISTATE" && arguments.size() > 5) {
|
||||
if (arguments[0].int_value == udp_id_) {
|
||||
connected_ = arguments[5].int_value == 2;
|
||||
instance_active_ = true;
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_UDP_INITIALIZED);
|
||||
}
|
||||
} else if (command == "FIFO_OVERFLOW") {
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_UDP_ERROR);
|
||||
Disconnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ec801EUdp::~Ec801EUdp() {
|
||||
Disconnect();
|
||||
at_uart_->UnregisterUrcCallback(urc_callback_it_);
|
||||
if (event_group_handle_) {
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool Ec801EUdp::Connect(const std::string& host, int port) {
|
||||
// Clear bits
|
||||
xEventGroupClearBits(event_group_handle_, EC801E_UDP_CONNECTED | EC801E_UDP_DISCONNECTED | EC801E_UDP_ERROR);
|
||||
|
||||
// Keep data in one line; Use HEX encoding in response
|
||||
at_uart_->SendCommand("AT+QICFG=\"close/mode\",1;+QICFG=\"viewmode\",1;+QICFG=\"sendinfo\",1;+QICFG=\"dataformat\",0,1");
|
||||
|
||||
// 检查这个 id 是否已经连接
|
||||
std::string command = "AT+QISTATE=1," + std::to_string(udp_id_);
|
||||
at_uart_->SendCommand(command);
|
||||
|
||||
// 断开之前的连接(不触发回调事件)
|
||||
if (instance_active_) {
|
||||
at_uart_->SendCommand("AT+QICLOSE=" + std::to_string(udp_id_));
|
||||
xEventGroupWaitBits(event_group_handle_, EC801E_UDP_DISCONNECTED, pdTRUE, pdFALSE, UDP_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||
instance_active_ = false;
|
||||
}
|
||||
|
||||
// 打开 UDP 连接
|
||||
command = "AT+QIOPEN=1," + std::to_string(udp_id_) + ",\"UDP\",\"" + host + "\"," + std::to_string(port) + ",0,1";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to open UDP connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 等待连接完成
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, EC801E_UDP_CONNECTED | EC801E_UDP_ERROR, pdTRUE, pdFALSE, UDP_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||
if (bits & EC801E_UDP_ERROR) {
|
||||
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void Ec801EUdp::Disconnect() {
|
||||
if (!instance_active_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (at_uart_->SendCommand("AT+QICLOSE=" + std::to_string(udp_id_))) {
|
||||
instance_active_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
int Ec801EUdp::Send(const std::string& data) {
|
||||
const size_t MAX_PACKET_SIZE = 1460;
|
||||
|
||||
if (!connected_) {
|
||||
ESP_LOGE(TAG, "Not connected");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (data.size() > MAX_PACKET_SIZE) {
|
||||
ESP_LOGE(TAG, "Data block exceeds maximum limit");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 在循环外预先分配command
|
||||
std::string command = "AT+QISEND=" + std::to_string(udp_id_) + "," + std::to_string(data.size());
|
||||
if (!at_uart_->SendCommandWithData(command, 1000, true, data.data(), data.size())) {
|
||||
ESP_LOGE(TAG, "Failed to send command");
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, EC801E_UDP_SEND_COMPLETE | EC801E_UDP_SEND_FAILED, pdTRUE, pdFALSE, pdMS_TO_TICKS(UDP_CONNECT_TIMEOUT_MS));
|
||||
if (bits & EC801E_UDP_SEND_FAILED) {
|
||||
ESP_LOGE(TAG, "Failed to send data");
|
||||
return -1;
|
||||
} else if (!(bits & EC801E_UDP_SEND_COMPLETE)) {
|
||||
ESP_LOGE(TAG, "Send timeout");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return data.size();
|
||||
}
|
||||
36
managed_components/78__esp-ml307/src/ec801e/ec801e_udp.h
Normal file
36
managed_components/78__esp-ml307/src/ec801e/ec801e_udp.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef EC801E_UDP_H
|
||||
#define EC801E_UDP_H
|
||||
|
||||
#include "udp.h"
|
||||
#include "at_uart.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
|
||||
#define EC801E_UDP_CONNECTED BIT0
|
||||
#define EC801E_UDP_DISCONNECTED BIT1
|
||||
#define EC801E_UDP_ERROR BIT2
|
||||
#define EC801E_UDP_SEND_COMPLETE BIT3
|
||||
#define EC801E_UDP_SEND_FAILED BIT4
|
||||
#define EC801E_UDP_INITIALIZED BIT5
|
||||
|
||||
#define UDP_CONNECT_TIMEOUT_MS 10000
|
||||
|
||||
class Ec801EUdp : public Udp {
|
||||
public:
|
||||
Ec801EUdp(std::shared_ptr<AtUart> at_uart, int udp_id);
|
||||
~Ec801EUdp();
|
||||
|
||||
bool Connect(const std::string& host, int port) override;
|
||||
void Disconnect() override;
|
||||
int Send(const std::string& data) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<AtUart> at_uart_;
|
||||
int udp_id_;
|
||||
bool instance_active_ = false;
|
||||
EventGroupHandle_t event_group_handle_;
|
||||
std::list<UrcCallback>::iterator urc_callback_it_;
|
||||
};
|
||||
|
||||
#endif // EC801E_UDP_H
|
||||
137
managed_components/78__esp-ml307/src/esp/esp_mqtt.cc
Normal file
137
managed_components/78__esp-ml307/src/esp/esp_mqtt.cc
Normal file
@@ -0,0 +1,137 @@
|
||||
#include "esp_mqtt.h"
|
||||
#include <esp_crt_bundle.h>
|
||||
#include <esp_log.h>
|
||||
|
||||
static const char *TAG = "esp_mqtt";
|
||||
|
||||
EspMqtt::EspMqtt() {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
}
|
||||
|
||||
EspMqtt::~EspMqtt() {
|
||||
Disconnect();
|
||||
if (event_group_handle_ != nullptr) {
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool EspMqtt::Connect(const std::string broker_address, int broker_port, const std::string client_id, const std::string username, const std::string password) {
|
||||
if (mqtt_client_handle_ != nullptr) {
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
esp_mqtt_client_config_t mqtt_config = {};
|
||||
mqtt_config.broker.address.hostname = broker_address.c_str();
|
||||
mqtt_config.broker.address.port = broker_port;
|
||||
if (broker_port == 8883) {
|
||||
mqtt_config.broker.address.transport = MQTT_TRANSPORT_OVER_SSL;
|
||||
mqtt_config.broker.verification.crt_bundle_attach = esp_crt_bundle_attach;
|
||||
} else {
|
||||
mqtt_config.broker.address.transport = MQTT_TRANSPORT_OVER_TCP;
|
||||
}
|
||||
mqtt_config.credentials.client_id = client_id.c_str();
|
||||
mqtt_config.credentials.username = username.c_str();
|
||||
mqtt_config.credentials.authentication.password = password.c_str();
|
||||
mqtt_config.session.keepalive = keep_alive_seconds_;
|
||||
|
||||
mqtt_client_handle_ = esp_mqtt_client_init(&mqtt_config);
|
||||
esp_mqtt_client_register_event(mqtt_client_handle_, MQTT_EVENT_ANY, [](void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
|
||||
((EspMqtt*)handler_args)->MqttEventCallback(base, event_id, event_data);
|
||||
}, this);
|
||||
esp_mqtt_client_start(mqtt_client_handle_);
|
||||
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, MQTT_CONNECTED_EVENT | MQTT_DISCONNECTED_EVENT | MQTT_ERROR_EVENT,
|
||||
pdTRUE, pdFALSE, pdMS_TO_TICKS(MQTT_CONNECT_TIMEOUT_MS));
|
||||
return bits & MQTT_CONNECTED_EVENT;
|
||||
}
|
||||
|
||||
void EspMqtt::MqttEventCallback(esp_event_base_t base, int32_t event_id, void *event_data) {
|
||||
auto event = (esp_mqtt_event_t*)event_data;
|
||||
switch (event_id) {
|
||||
case MQTT_EVENT_CONNECTED:
|
||||
if (!connected_) {
|
||||
connected_ = true;
|
||||
if (on_connected_callback_) {
|
||||
on_connected_callback_();
|
||||
}
|
||||
}
|
||||
xEventGroupSetBits(event_group_handle_, MQTT_CONNECTED_EVENT);
|
||||
break;
|
||||
case MQTT_EVENT_DISCONNECTED:
|
||||
if (connected_) {
|
||||
connected_ = false;
|
||||
if (on_disconnected_callback_) {
|
||||
on_disconnected_callback_();
|
||||
}
|
||||
}
|
||||
xEventGroupSetBits(event_group_handle_, MQTT_DISCONNECTED_EVENT);
|
||||
break;
|
||||
case MQTT_EVENT_DATA: {
|
||||
auto topic = std::string(event->topic, event->topic_len);
|
||||
auto payload = std::string(event->data, event->data_len);
|
||||
if (event->data_len == event->total_data_len) {
|
||||
if (on_message_callback_) {
|
||||
on_message_callback_(topic, payload);
|
||||
}
|
||||
} else {
|
||||
message_payload_.append(payload);
|
||||
if (message_payload_.size() >= event->total_data_len && on_message_callback_) {
|
||||
on_message_callback_(topic, message_payload_);
|
||||
message_payload_.clear();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MQTT_EVENT_BEFORE_CONNECT:
|
||||
break;
|
||||
case MQTT_EVENT_SUBSCRIBED:
|
||||
break;
|
||||
case MQTT_EVENT_ERROR: {
|
||||
xEventGroupSetBits(event_group_handle_, MQTT_ERROR_EVENT);
|
||||
const char* error_name = esp_err_to_name(event->error_handle->esp_tls_last_esp_err);
|
||||
ESP_LOGI(TAG, "MQTT error occurred: %s", error_name);
|
||||
if (on_error_callback_) {
|
||||
on_error_callback_(error_name ? error_name : "MQTT error");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGI(TAG, "Unhandled event id %ld", event_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void EspMqtt::Disconnect() {
|
||||
if (mqtt_client_handle_ != nullptr) {
|
||||
esp_mqtt_client_stop(mqtt_client_handle_);
|
||||
esp_mqtt_client_destroy(mqtt_client_handle_);
|
||||
mqtt_client_handle_ = nullptr;
|
||||
}
|
||||
connected_ = false;
|
||||
xEventGroupClearBits(event_group_handle_, MQTT_CONNECTED_EVENT | MQTT_DISCONNECTED_EVENT | MQTT_ERROR_EVENT);
|
||||
}
|
||||
|
||||
bool EspMqtt::Publish(const std::string topic, const std::string payload, int qos) {
|
||||
if (!connected_) {
|
||||
return false;
|
||||
}
|
||||
return esp_mqtt_client_publish(mqtt_client_handle_, topic.c_str(), payload.data(), payload.size(), qos, 0) == 0;
|
||||
}
|
||||
|
||||
bool EspMqtt::Subscribe(const std::string topic, int qos) {
|
||||
if (!connected_) {
|
||||
return false;
|
||||
}
|
||||
return esp_mqtt_client_subscribe_single(mqtt_client_handle_, topic.c_str(), qos) == 0;
|
||||
}
|
||||
|
||||
bool EspMqtt::Unsubscribe(const std::string topic) {
|
||||
if (!connected_) {
|
||||
return false;
|
||||
}
|
||||
return esp_mqtt_client_unsubscribe(mqtt_client_handle_, topic.c_str()) == 0;
|
||||
}
|
||||
|
||||
bool EspMqtt::IsConnected() {
|
||||
return connected_;
|
||||
}
|
||||
41
managed_components/78__esp-ml307/src/esp/esp_mqtt.h
Normal file
41
managed_components/78__esp-ml307/src/esp/esp_mqtt.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#ifndef ESP_MQTT_H
|
||||
#define ESP_MQTT_H
|
||||
|
||||
#include "mqtt.h"
|
||||
|
||||
#include <mqtt_client.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
#define MQTT_CONNECT_TIMEOUT_MS 10000
|
||||
|
||||
#define MQTT_INITIALIZED_EVENT BIT0
|
||||
#define MQTT_CONNECTED_EVENT BIT1
|
||||
#define MQTT_DISCONNECTED_EVENT BIT2
|
||||
#define MQTT_ERROR_EVENT BIT3
|
||||
|
||||
class EspMqtt : public Mqtt {
|
||||
public:
|
||||
EspMqtt();
|
||||
~EspMqtt();
|
||||
|
||||
bool Connect(const std::string broker_address, int broker_port, const std::string client_id, const std::string username, const std::string password);
|
||||
void Disconnect();
|
||||
bool Publish(const std::string topic, const std::string payload, int qos = 0);
|
||||
bool Subscribe(const std::string topic, int qos = 0);
|
||||
bool Unsubscribe(const std::string topic);
|
||||
bool IsConnected();
|
||||
|
||||
private:
|
||||
bool connected_ = false;
|
||||
EventGroupHandle_t event_group_handle_;
|
||||
std::string message_payload_;
|
||||
esp_mqtt_client_handle_t mqtt_client_handle_ = nullptr;
|
||||
|
||||
void MqttEventCallback(esp_event_base_t base, int32_t event_id, void *event_data);
|
||||
};
|
||||
|
||||
#endif
|
||||
41
managed_components/78__esp-ml307/src/esp/esp_network.cc
Normal file
41
managed_components/78__esp-ml307/src/esp/esp_network.cc
Normal file
@@ -0,0 +1,41 @@
|
||||
#include "esp_network.h"
|
||||
|
||||
#include "esp_tcp.h"
|
||||
#include "esp_ssl.h"
|
||||
#include "esp_udp.h"
|
||||
#include "esp_mqtt.h"
|
||||
#include "http_client.h"
|
||||
#include "web_socket.h"
|
||||
|
||||
|
||||
EspNetwork::EspNetwork() {
|
||||
|
||||
}
|
||||
|
||||
EspNetwork::~EspNetwork() {
|
||||
|
||||
}
|
||||
|
||||
std::unique_ptr<Http> EspNetwork::CreateHttp(int connect_id) {
|
||||
return std::make_unique<HttpClient>(this, connect_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<Tcp> EspNetwork::CreateTcp(int connect_id) {
|
||||
return std::make_unique<EspTcp>();
|
||||
}
|
||||
|
||||
std::unique_ptr<Tcp> EspNetwork::CreateSsl(int connect_id) {
|
||||
return std::make_unique<EspSsl>();
|
||||
}
|
||||
|
||||
std::unique_ptr<Udp> EspNetwork::CreateUdp(int connect_id) {
|
||||
return std::make_unique<EspUdp>();
|
||||
}
|
||||
|
||||
std::unique_ptr<Mqtt> EspNetwork::CreateMqtt(int connect_id) {
|
||||
return std::make_unique<EspMqtt>();
|
||||
}
|
||||
|
||||
std::unique_ptr<WebSocket> EspNetwork::CreateWebSocket(int connect_id) {
|
||||
return std::make_unique<WebSocket>(this, connect_id);
|
||||
}
|
||||
136
managed_components/78__esp-ml307/src/esp/esp_ssl.cc
Normal file
136
managed_components/78__esp-ml307/src/esp/esp_ssl.cc
Normal file
@@ -0,0 +1,136 @@
|
||||
#include "esp_ssl.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_crt_bundle.h>
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
|
||||
static const char *TAG = "EspSsl";
|
||||
|
||||
EspSsl::EspSsl() {
|
||||
event_group_ = xEventGroupCreate();
|
||||
}
|
||||
|
||||
EspSsl::~EspSsl() {
|
||||
Disconnect();
|
||||
|
||||
if (event_group_ != nullptr) {
|
||||
vEventGroupDelete(event_group_);
|
||||
event_group_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool EspSsl::Connect(const std::string& host, int port) {
|
||||
if (tls_client_ != nullptr) {
|
||||
ESP_LOGE(TAG, "tls client has been initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
tls_client_ = esp_tls_init();
|
||||
if (tls_client_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to initialize TLS");
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_tls_cfg_t cfg = {};
|
||||
cfg.crt_bundle_attach = esp_crt_bundle_attach;
|
||||
|
||||
int ret = esp_tls_conn_new_sync(host.c_str(), host.length(), port, &cfg, tls_client_);
|
||||
if (ret != 1) {
|
||||
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
|
||||
esp_tls_conn_destroy(tls_client_);
|
||||
tls_client_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
connected_ = true;
|
||||
|
||||
xEventGroupClearBits(event_group_, ESP_SSL_EVENT_RECEIVE_TASK_EXIT);
|
||||
xTaskCreate([](void* arg) {
|
||||
EspSsl* ssl = (EspSsl*)arg;
|
||||
ssl->ReceiveTask();
|
||||
xEventGroupSetBits(ssl->event_group_, ESP_SSL_EVENT_RECEIVE_TASK_EXIT);
|
||||
vTaskDelete(NULL);
|
||||
}, "ssl_receive", 4096, this, 1, &receive_task_handle_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void EspSsl::Disconnect() {
|
||||
connected_ = false;
|
||||
|
||||
// Close socket if it is open
|
||||
if (tls_client_ != nullptr) {
|
||||
int sockfd;
|
||||
ESP_ERROR_CHECK(esp_tls_get_conn_sockfd(tls_client_, &sockfd));
|
||||
if (sockfd >= 0) {
|
||||
close(sockfd);
|
||||
}
|
||||
|
||||
auto bits = xEventGroupWaitBits(event_group_, ESP_SSL_EVENT_RECEIVE_TASK_EXIT, pdFALSE, pdFALSE, pdMS_TO_TICKS(10000));
|
||||
if (!(bits & ESP_SSL_EVENT_RECEIVE_TASK_EXIT)) {
|
||||
ESP_LOGE(TAG, "Failed to wait for receive task exit");
|
||||
}
|
||||
|
||||
esp_tls_conn_destroy(tls_client_);
|
||||
tls_client_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/* CONFIG_MBEDTLS_SSL_RENEGOTIATION should be disabled in sdkconfig.
|
||||
* Otherwise, invalid memory access may be triggered.
|
||||
*/
|
||||
int EspSsl::Send(const std::string& data) {
|
||||
if (!connected_) {
|
||||
ESP_LOGE(TAG, "Not connected");
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t total_sent = 0;
|
||||
size_t data_size = data.size();
|
||||
const char* data_ptr = data.data();
|
||||
|
||||
while (total_sent < data_size) {
|
||||
int ret = esp_tls_conn_write(tls_client_, data_ptr + total_sent, data_size - total_sent);
|
||||
|
||||
if (ret == ESP_TLS_ERR_SSL_WANT_WRITE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ret <= 0) {
|
||||
ESP_LOGE(TAG, "SSL send failed: ret=%d, errno=%d", ret, errno);
|
||||
return ret;
|
||||
}
|
||||
|
||||
total_sent += ret;
|
||||
}
|
||||
|
||||
return total_sent;
|
||||
}
|
||||
|
||||
void EspSsl::ReceiveTask() {
|
||||
std::string data;
|
||||
while (connected_) {
|
||||
data.resize(1500);
|
||||
int ret = esp_tls_conn_read(tls_client_, data.data(), data.size());
|
||||
|
||||
if (ret == ESP_TLS_ERR_SSL_WANT_READ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ret <= 0) {
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "SSL receive failed: %d", ret);
|
||||
}
|
||||
connected_ = false;
|
||||
// 接收失败或连接断开时调用断连回调
|
||||
if (disconnect_callback_) {
|
||||
disconnect_callback_();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (stream_callback_) {
|
||||
data.resize(ret);
|
||||
stream_callback_(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
30
managed_components/78__esp-ml307/src/esp/esp_ssl.h
Normal file
30
managed_components/78__esp-ml307/src/esp/esp_ssl.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef _ESP_SSL_H_
|
||||
#define _ESP_SSL_H_
|
||||
|
||||
#include "tcp.h"
|
||||
#include <esp_tls.h>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#define ESP_SSL_EVENT_RECEIVE_TASK_EXIT 1
|
||||
|
||||
class EspSsl : public Tcp {
|
||||
public:
|
||||
EspSsl();
|
||||
~EspSsl();
|
||||
|
||||
bool Connect(const std::string& host, int port) override;
|
||||
void Disconnect() override;
|
||||
int Send(const std::string& data) override;
|
||||
|
||||
private:
|
||||
esp_tls_t* tls_client_ = nullptr;
|
||||
EventGroupHandle_t event_group_ = nullptr;
|
||||
TaskHandle_t receive_task_handle_ = nullptr;
|
||||
|
||||
void ReceiveTask();
|
||||
};
|
||||
|
||||
#endif // _ESP_SSL_H_
|
||||
130
managed_components/78__esp-ml307/src/esp/esp_tcp.cc
Normal file
130
managed_components/78__esp-ml307/src/esp/esp_tcp.cc
Normal file
@@ -0,0 +1,130 @@
|
||||
#include "esp_tcp.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <unistd.h>
|
||||
#include <cstring>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <errno.h>
|
||||
|
||||
static const char *TAG = "EspTcp";
|
||||
|
||||
EspTcp::EspTcp() {
|
||||
event_group_ = xEventGroupCreate();
|
||||
}
|
||||
|
||||
EspTcp::~EspTcp() {
|
||||
Disconnect();
|
||||
|
||||
if (event_group_ != nullptr) {
|
||||
vEventGroupDelete(event_group_);
|
||||
event_group_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool EspTcp::Connect(const std::string& host, int port) {
|
||||
// 确保先断开已有连接
|
||||
if (connected_) {
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
struct sockaddr_in server_addr;
|
||||
bzero(&server_addr, sizeof(server_addr));
|
||||
server_addr.sin_family = AF_INET;
|
||||
server_addr.sin_port = htons(port);
|
||||
// host is domain
|
||||
struct hostent *server = gethostbyname(host.c_str());
|
||||
if (server == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to get host by name");
|
||||
return false;
|
||||
}
|
||||
memcpy(&server_addr.sin_addr, server->h_addr, server->h_length);
|
||||
|
||||
tcp_fd_ = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (tcp_fd_ < 0) {
|
||||
ESP_LOGE(TAG, "Failed to create socket");
|
||||
return false;
|
||||
}
|
||||
|
||||
int ret = connect(tcp_fd_, (struct sockaddr*)&server_addr, sizeof(server_addr));
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
|
||||
close(tcp_fd_);
|
||||
tcp_fd_ = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
connected_ = true;
|
||||
|
||||
xEventGroupClearBits(event_group_, ESP_TCP_EVENT_RECEIVE_TASK_EXIT);
|
||||
xTaskCreate([](void* arg) {
|
||||
EspTcp* tcp = (EspTcp*)arg;
|
||||
tcp->ReceiveTask();
|
||||
xEventGroupSetBits(tcp->event_group_, ESP_TCP_EVENT_RECEIVE_TASK_EXIT);
|
||||
vTaskDelete(NULL);
|
||||
}, "tcp_receive", 4096, this, 1, &receive_task_handle_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void EspTcp::Disconnect() {
|
||||
connected_ = false;
|
||||
|
||||
if (tcp_fd_ != -1) {
|
||||
close(tcp_fd_);
|
||||
tcp_fd_ = -1;
|
||||
|
||||
auto bits = xEventGroupWaitBits(event_group_, ESP_TCP_EVENT_RECEIVE_TASK_EXIT, pdFALSE, pdFALSE, pdMS_TO_TICKS(10000));
|
||||
if (!(bits & ESP_TCP_EVENT_RECEIVE_TASK_EXIT)) {
|
||||
ESP_LOGE(TAG, "Failed to wait for receive task exit");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int EspTcp::Send(const std::string& data) {
|
||||
if (!connected_) {
|
||||
ESP_LOGE(TAG, "Not connected");
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t total_sent = 0;
|
||||
size_t data_size = data.size();
|
||||
const char* data_ptr = data.data();
|
||||
|
||||
while (total_sent < data_size) {
|
||||
int ret = send(tcp_fd_, data_ptr + total_sent, data_size - total_sent, 0);
|
||||
|
||||
if (ret <= 0) {
|
||||
ESP_LOGE(TAG, "Send failed: ret=%d, errno=%d", ret, errno);
|
||||
return ret;
|
||||
}
|
||||
|
||||
total_sent += ret;
|
||||
}
|
||||
|
||||
return total_sent;
|
||||
}
|
||||
|
||||
void EspTcp::ReceiveTask() {
|
||||
std::string data;
|
||||
while (connected_) {
|
||||
data.resize(1500);
|
||||
int ret = recv(tcp_fd_, data.data(), data.size(), 0);
|
||||
if (ret <= 0) {
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "TCP receive failed: %d", ret);
|
||||
}
|
||||
connected_ = false;
|
||||
// 接收失败或连接断开时调用断连回调
|
||||
if (disconnect_callback_) {
|
||||
disconnect_callback_();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (stream_callback_) {
|
||||
data.resize(ret);
|
||||
stream_callback_(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
managed_components/78__esp-ml307/src/esp/esp_tcp.h
Normal file
29
managed_components/78__esp-ml307/src/esp/esp_tcp.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#ifndef _ESP_TCP_H_
|
||||
#define _ESP_TCP_H_
|
||||
|
||||
#include "tcp.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#define ESP_TCP_EVENT_RECEIVE_TASK_EXIT 1
|
||||
|
||||
class EspTcp : public Tcp {
|
||||
public:
|
||||
EspTcp();
|
||||
~EspTcp();
|
||||
|
||||
bool Connect(const std::string& host, int port) override;
|
||||
void Disconnect() override;
|
||||
int Send(const std::string& data) override;
|
||||
|
||||
private:
|
||||
int tcp_fd_ = -1;
|
||||
EventGroupHandle_t event_group_ = nullptr;
|
||||
TaskHandle_t receive_task_handle_ = nullptr;
|
||||
|
||||
void ReceiveTask();
|
||||
};
|
||||
|
||||
#endif // _ESP_TCP_H_
|
||||
111
managed_components/78__esp-ml307/src/esp/esp_udp.cc
Normal file
111
managed_components/78__esp-ml307/src/esp/esp_udp.cc
Normal file
@@ -0,0 +1,111 @@
|
||||
#include "esp_udp.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <unistd.h>
|
||||
#include <cstring>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
|
||||
static const char *TAG = "EspUdp";
|
||||
|
||||
EspUdp::EspUdp() : udp_fd_(-1) {
|
||||
event_group_ = xEventGroupCreate();
|
||||
}
|
||||
|
||||
EspUdp::~EspUdp() {
|
||||
Disconnect();
|
||||
|
||||
if (event_group_ != nullptr) {
|
||||
vEventGroupDelete(event_group_);
|
||||
event_group_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool EspUdp::Connect(const std::string& host, int port) {
|
||||
// 确保先断开已有连接
|
||||
if (connected_) {
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
struct sockaddr_in server_addr;
|
||||
bzero(&server_addr, sizeof(server_addr));
|
||||
server_addr.sin_family = AF_INET;
|
||||
server_addr.sin_port = htons(port);
|
||||
// host is domain
|
||||
struct hostent *server = gethostbyname(host.c_str());
|
||||
if (server == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to get host by name");
|
||||
return false;
|
||||
}
|
||||
memcpy(&server_addr.sin_addr, server->h_addr, server->h_length);
|
||||
|
||||
udp_fd_ = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (udp_fd_ < 0) {
|
||||
ESP_LOGE(TAG, "Failed to create socket");
|
||||
return false;
|
||||
}
|
||||
|
||||
int ret = connect(udp_fd_, (struct sockaddr*)&server_addr, sizeof(server_addr));
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
|
||||
close(udp_fd_);
|
||||
udp_fd_ = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
connected_ = true;
|
||||
|
||||
xEventGroupClearBits(event_group_, ESP_UDP_EVENT_RECEIVE_TASK_EXIT);
|
||||
xTaskCreate([](void* arg) {
|
||||
EspUdp* udp = (EspUdp*)arg;
|
||||
udp->ReceiveTask();
|
||||
xEventGroupSetBits(udp->event_group_, ESP_UDP_EVENT_RECEIVE_TASK_EXIT);
|
||||
vTaskDelete(NULL);
|
||||
}, "udp_receive", 4096, this, 1, &receive_task_handle_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void EspUdp::Disconnect() {
|
||||
connected_ = false;
|
||||
|
||||
if (udp_fd_ != -1) {
|
||||
close(udp_fd_);
|
||||
udp_fd_ = -1;
|
||||
|
||||
auto bits = xEventGroupWaitBits(event_group_, ESP_UDP_EVENT_RECEIVE_TASK_EXIT, pdFALSE, pdFALSE, pdMS_TO_TICKS(10000));
|
||||
if (!(bits & ESP_UDP_EVENT_RECEIVE_TASK_EXIT)) {
|
||||
ESP_LOGE(TAG, "Failed to wait for receive task exit");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int EspUdp::Send(const std::string& data) {
|
||||
if (!connected_) {
|
||||
ESP_LOGE(TAG, "Not connected");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ret = send(udp_fd_, data.data(), data.size(), 0);
|
||||
if (ret <= 0) {
|
||||
ESP_LOGE(TAG, "Send failed: ret=%d, errno=%d", ret, errno);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void EspUdp::ReceiveTask() {
|
||||
std::string data;
|
||||
while (connected_) {
|
||||
data.resize(1500);
|
||||
int ret = recv(udp_fd_, data.data(), data.size(), 0);
|
||||
if (ret <= 0) {
|
||||
connected_ = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (message_callback_) {
|
||||
data.resize(ret);
|
||||
message_callback_(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
managed_components/78__esp-ml307/src/esp/esp_udp.h
Normal file
29
managed_components/78__esp-ml307/src/esp/esp_udp.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#ifndef ESP_UDP_H
|
||||
#define ESP_UDP_H
|
||||
|
||||
#include "udp.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#define ESP_UDP_EVENT_RECEIVE_TASK_EXIT 1
|
||||
|
||||
class EspUdp : public Udp {
|
||||
public:
|
||||
EspUdp();
|
||||
~EspUdp();
|
||||
|
||||
bool Connect(const std::string& host, int port) override;
|
||||
void Disconnect() override;
|
||||
int Send(const std::string& data) override;
|
||||
|
||||
private:
|
||||
int udp_fd_;
|
||||
EventGroupHandle_t event_group_ = nullptr;
|
||||
TaskHandle_t receive_task_handle_ = nullptr;
|
||||
|
||||
void ReceiveTask();
|
||||
};
|
||||
|
||||
#endif // ESP_UDP_H
|
||||
724
managed_components/78__esp-ml307/src/http_client.cc
Normal file
724
managed_components/78__esp-ml307/src/http_client.cc
Normal file
@@ -0,0 +1,724 @@
|
||||
#include "http_client.h"
|
||||
#include "network_interface.h"
|
||||
#include <esp_log.h>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
static const char *TAG = "HttpClient";
|
||||
|
||||
HttpClient::HttpClient(NetworkInterface* network, int connect_id) : network_(network), connect_id_(connect_id) {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
}
|
||||
|
||||
HttpClient::~HttpClient() {
|
||||
if (connected_) {
|
||||
Close();
|
||||
}
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
|
||||
void HttpClient::SetTimeout(int timeout_ms) {
|
||||
timeout_ms_ = timeout_ms;
|
||||
}
|
||||
|
||||
void HttpClient::SetHeader(const std::string& key, const std::string& value) {
|
||||
// 转换key为小写用于存储和查找,但保留原始key用于输出
|
||||
std::string lower_key = key;
|
||||
std::transform(lower_key.begin(), lower_key.end(), lower_key.begin(), ::tolower);
|
||||
headers_[lower_key] = HeaderEntry(key, value);
|
||||
}
|
||||
|
||||
void HttpClient::SetContent(std::string&& content) {
|
||||
content_ = std::move(content);
|
||||
}
|
||||
|
||||
bool HttpClient::ParseUrl(const std::string& url) {
|
||||
// 解析 URL: protocol://host:port/path
|
||||
size_t protocol_end = url.find("://");
|
||||
if (protocol_end == std::string::npos) {
|
||||
ESP_LOGE(TAG, "Invalid URL format: %s", url.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
protocol_ = url.substr(0, protocol_end);
|
||||
std::transform(protocol_.begin(), protocol_.end(), protocol_.begin(), ::tolower);
|
||||
|
||||
size_t host_start = protocol_end + 3;
|
||||
size_t path_start = url.find("/", host_start);
|
||||
size_t port_start = url.find(":", host_start);
|
||||
|
||||
// 默认端口
|
||||
if (protocol_ == "https") {
|
||||
port_ = 443;
|
||||
} else {
|
||||
port_ = 80;
|
||||
}
|
||||
|
||||
if (path_start == std::string::npos) {
|
||||
path_ = "/";
|
||||
if (port_start != std::string::npos) {
|
||||
host_ = url.substr(host_start, port_start - host_start);
|
||||
std::string port_str = url.substr(port_start + 1);
|
||||
char* endptr;
|
||||
long port = strtol(port_str.c_str(), &endptr, 10);
|
||||
if (endptr != port_str.c_str() && *endptr == '\0' && port > 0 && port <= 65535) {
|
||||
port_ = static_cast<int>(port);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Invalid port: %s", port_str.c_str());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
host_ = url.substr(host_start);
|
||||
}
|
||||
} else {
|
||||
path_ = url.substr(path_start);
|
||||
if (port_start != std::string::npos && port_start < path_start) {
|
||||
host_ = url.substr(host_start, port_start - host_start);
|
||||
std::string port_str = url.substr(port_start + 1, path_start - port_start - 1);
|
||||
char* endptr;
|
||||
long port = strtol(port_str.c_str(), &endptr, 10);
|
||||
if (endptr != port_str.c_str() && *endptr == '\0' && port > 0 && port <= 65535) {
|
||||
port_ = static_cast<int>(port);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Invalid port: %s", port_str.c_str());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
host_ = url.substr(host_start, path_start - host_start);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Parsed URL: protocol=%s, host=%s, port=%d, path=%s",
|
||||
protocol_.c_str(), host_.c_str(), port_, path_.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string HttpClient::BuildHttpRequest() {
|
||||
std::ostringstream request;
|
||||
|
||||
// 请求行
|
||||
request << method_ << " " << path_ << " HTTP/1.1\r\n";
|
||||
|
||||
// Host 头
|
||||
request << "Host: " << host_;
|
||||
if ((protocol_ == "http" && port_ != 80) || (protocol_ == "https" && port_ != 443)) {
|
||||
request << ":" << port_;
|
||||
}
|
||||
request << "\r\n";
|
||||
|
||||
// 用户自定义头部(使用原始key输出)
|
||||
for (const auto& [lower_key, header_entry] : headers_) {
|
||||
request << header_entry.original_key << ": " << header_entry.value << "\r\n";
|
||||
}
|
||||
|
||||
// 内容相关头部(仅在用户未设置时添加)
|
||||
bool user_set_content_length = headers_.find("content-length") != headers_.end();
|
||||
bool user_set_transfer_encoding = headers_.find("transfer-encoding") != headers_.end();
|
||||
bool has_content = content_.has_value() && !content_->empty();
|
||||
if (has_content && !user_set_content_length) {
|
||||
request << "Content-Length: " << content_->size() << "\r\n";
|
||||
} else if ((method_ == "POST" || method_ == "PUT") && !user_set_content_length && !user_set_transfer_encoding) {
|
||||
if (request_chunked_) {
|
||||
request << "Transfer-Encoding: chunked\r\n";
|
||||
} else {
|
||||
request << "Content-Length: 0\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
// 连接控制(仅在用户未设置时添加)
|
||||
if (headers_.find("connection") == headers_.end()) {
|
||||
request << "Connection: close\r\n";
|
||||
}
|
||||
|
||||
// 结束头部
|
||||
request << "\r\n";
|
||||
ESP_LOGD(TAG, "HTTP request headers:\n%s", request.str().c_str());
|
||||
|
||||
// 请求体
|
||||
if (has_content) {
|
||||
request << *content_;
|
||||
}
|
||||
|
||||
return request.str();
|
||||
}
|
||||
|
||||
bool HttpClient::Open(const std::string& method, const std::string& url) {
|
||||
method_ = method;
|
||||
url_ = url;
|
||||
|
||||
// 重置状态
|
||||
status_code_ = -1;
|
||||
response_headers_.clear();
|
||||
{
|
||||
std::lock_guard<std::mutex> read_lock(read_mutex_);
|
||||
body_chunks_.clear();
|
||||
}
|
||||
body_offset_ = 0;
|
||||
content_length_ = 0;
|
||||
total_body_received_ = 0;
|
||||
eof_ = false;
|
||||
headers_received_ = false;
|
||||
response_chunked_ = false;
|
||||
connection_error_ = false; // 重置连接错误状态
|
||||
parse_state_ = ParseState::STATUS_LINE;
|
||||
chunk_size_ = 0;
|
||||
chunk_received_ = 0;
|
||||
rx_buffer_.clear();
|
||||
|
||||
xEventGroupClearBits(event_group_handle_,
|
||||
EC801E_HTTP_EVENT_HEADERS_RECEIVED |
|
||||
EC801E_HTTP_EVENT_BODY_RECEIVED |
|
||||
EC801E_HTTP_EVENT_ERROR |
|
||||
EC801E_HTTP_EVENT_COMPLETE);
|
||||
|
||||
// 解析 URL
|
||||
if (!ParseUrl(url)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 建立 TCP 连接
|
||||
if (protocol_ == "https") {
|
||||
tcp_ = network_->CreateSsl(connect_id_);
|
||||
} else {
|
||||
tcp_ = network_->CreateTcp(connect_id_);
|
||||
}
|
||||
|
||||
// 设置 TCP 数据接收回调
|
||||
tcp_->OnStream([this](const std::string& data) {
|
||||
OnTcpData(data);
|
||||
});
|
||||
|
||||
// 设置 TCP 断开连接回调
|
||||
tcp_->OnDisconnected([this]() {
|
||||
OnTcpDisconnected();
|
||||
});
|
||||
if (!tcp_->Connect(host_, port_)) {
|
||||
ESP_LOGE(TAG, "TCP connection failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
connected_ = true;
|
||||
request_chunked_ = (method_ == "POST" || method_ == "PUT") && !content_.has_value();
|
||||
|
||||
// 构建并发送 HTTP 请求
|
||||
std::string http_request = BuildHttpRequest();
|
||||
if (tcp_->Send(http_request) <= 0) {
|
||||
ESP_LOGE(TAG, "Send HTTP request failed");
|
||||
tcp_->Disconnect();
|
||||
connected_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void HttpClient::Close() {
|
||||
if (!connected_) {
|
||||
return;
|
||||
}
|
||||
|
||||
connected_ = false;
|
||||
write_cv_.notify_all();
|
||||
tcp_->Disconnect();
|
||||
|
||||
eof_ = true;
|
||||
cv_.notify_all();
|
||||
ESP_LOGD(TAG, "HTTP connection closed");
|
||||
}
|
||||
|
||||
void HttpClient::OnTcpData(const std::string& data) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
// 检查 body_chunks_ 大小,如果超过 8KB 且 heap 小于 32KB 则阻塞
|
||||
{
|
||||
std::unique_lock<std::mutex> read_lock(read_mutex_);
|
||||
write_cv_.wait(read_lock, [this, size=data.size()] {
|
||||
size_t total_size = size;
|
||||
for (const auto& chunk : body_chunks_) {
|
||||
total_size += chunk.data.size();
|
||||
}
|
||||
size_t free_heap = esp_get_free_heap_size();
|
||||
return total_size < MAX_BODY_CHUNKS_SIZE || !connected_ || free_heap >= 32768;
|
||||
});
|
||||
}
|
||||
|
||||
rx_buffer_.append(data);
|
||||
ProcessReceivedData();
|
||||
cv_.notify_one();
|
||||
}
|
||||
|
||||
void HttpClient::OnTcpDisconnected() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
connected_ = false;
|
||||
|
||||
// 检查数据是否完整接收
|
||||
if (headers_received_ && !IsDataComplete()) {
|
||||
// 如果已接收头部但数据不完整,标记为连接错误
|
||||
connection_error_ = true;
|
||||
ESP_LOGE(TAG, "Connection closed prematurely, expected %u bytes but only received %u bytes",
|
||||
content_length_, total_body_received_);
|
||||
} else {
|
||||
// 数据完整或还未开始接收响应体,正常结束
|
||||
eof_ = true;
|
||||
}
|
||||
|
||||
cv_.notify_all(); // 通知所有等待的读取操作
|
||||
}
|
||||
|
||||
void HttpClient::ProcessReceivedData() {
|
||||
while (!rx_buffer_.empty() && parse_state_ != ParseState::COMPLETE) {
|
||||
switch (parse_state_) {
|
||||
case ParseState::STATUS_LINE: {
|
||||
if (!HasCompleteLine(rx_buffer_)) return; // 需要更多数据
|
||||
|
||||
std::string line = GetNextLine(rx_buffer_);
|
||||
|
||||
if (ParseStatusLine(line)) {
|
||||
parse_state_ = ParseState::HEADERS;
|
||||
} else {
|
||||
SetError();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ParseState::HEADERS: {
|
||||
if (!HasCompleteLine(rx_buffer_)) return; // 需要更多数据
|
||||
|
||||
std::string line = GetNextLine(rx_buffer_);
|
||||
|
||||
// 检查是否为空行(头部结束标记)
|
||||
// GetNextLine 已经移除了 \r,所以空行就是 empty()
|
||||
if (line.empty()) {
|
||||
// 检查是否为 chunked 编码
|
||||
auto it = response_headers_.find("transfer-encoding");
|
||||
if (it != response_headers_.end() &&
|
||||
it->second.value.find("chunked") != std::string::npos) {
|
||||
response_chunked_ = true;
|
||||
parse_state_ = ParseState::CHUNK_SIZE;
|
||||
} else {
|
||||
parse_state_ = ParseState::BODY;
|
||||
auto cl_it = response_headers_.find("content-length");
|
||||
if (cl_it != response_headers_.end()) {
|
||||
char* endptr;
|
||||
unsigned long length = strtoul(cl_it->second.value.c_str(), &endptr, 10);
|
||||
if (endptr != cl_it->second.value.c_str() && *endptr == '\0') {
|
||||
content_length_ = static_cast<size_t>(length);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Invalid Content-Length: %s", cl_it->second.value.c_str());
|
||||
content_length_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 头部结束
|
||||
headers_received_ = true;
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_HTTP_EVENT_HEADERS_RECEIVED);
|
||||
} else {
|
||||
if (!ParseHeaderLine(line)) {
|
||||
SetError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ParseState::BODY: {
|
||||
if (response_chunked_) {
|
||||
ParseChunkedBody(rx_buffer_);
|
||||
} else {
|
||||
ParseRegularBody(rx_buffer_);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ParseState::CHUNK_SIZE: {
|
||||
if (!HasCompleteLine(rx_buffer_)) return; // 需要更多数据
|
||||
|
||||
std::string line = GetNextLine(rx_buffer_);
|
||||
|
||||
chunk_size_ = ParseChunkSize(line);
|
||||
chunk_received_ = 0;
|
||||
|
||||
if (chunk_size_ == 0) {
|
||||
parse_state_ = ParseState::CHUNK_TRAILER;
|
||||
} else {
|
||||
parse_state_ = ParseState::CHUNK_DATA;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ParseState::CHUNK_DATA: {
|
||||
size_t available = std::min(rx_buffer_.size(), chunk_size_ - chunk_received_);
|
||||
if (available > 0) {
|
||||
std::string chunk_data = rx_buffer_.substr(0, available);
|
||||
AddBodyData(std::move(chunk_data));
|
||||
total_body_received_ += available;
|
||||
rx_buffer_.erase(0, available);
|
||||
chunk_received_ += available;
|
||||
|
||||
if (chunk_received_ == chunk_size_) {
|
||||
// 跳过 chunk 后的 CRLF
|
||||
if (rx_buffer_.size() >= 2 && rx_buffer_.substr(0, 2) == "\r\n") {
|
||||
rx_buffer_.erase(0, 2);
|
||||
}
|
||||
parse_state_ = ParseState::CHUNK_SIZE;
|
||||
}
|
||||
}
|
||||
if (available == 0) return; // 需要更多数据
|
||||
break;
|
||||
}
|
||||
|
||||
case ParseState::CHUNK_TRAILER: {
|
||||
if (!HasCompleteLine(rx_buffer_)) return; // 需要更多数据
|
||||
|
||||
std::string line = GetNextLine(rx_buffer_);
|
||||
|
||||
if (line.empty()) {
|
||||
parse_state_ = ParseState::COMPLETE;
|
||||
eof_ = true;
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_HTTP_EVENT_COMPLETE);
|
||||
}
|
||||
// 忽略 trailer 头部
|
||||
break;
|
||||
}
|
||||
|
||||
case ParseState::COMPLETE:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否完成(非 chunked 模式)
|
||||
if (parse_state_ == ParseState::BODY && !response_chunked_ &&
|
||||
content_length_ > 0 && total_body_received_ >= content_length_) {
|
||||
parse_state_ = ParseState::COMPLETE;
|
||||
eof_ = true;
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_HTTP_EVENT_COMPLETE);
|
||||
ESP_LOGD(TAG, "HTTP response body received: %u/%u bytes", total_body_received_, content_length_);
|
||||
}
|
||||
}
|
||||
|
||||
bool HttpClient::ParseStatusLine(const std::string& line) {
|
||||
// HTTP/1.1 200 OK
|
||||
std::istringstream iss(line);
|
||||
std::string version, status_str, reason;
|
||||
|
||||
if (!(iss >> version >> status_str)) {
|
||||
ESP_LOGE(TAG, "Invalid status line: %s", line.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::getline(iss, reason); // 获取剩余部分作为原因短语
|
||||
|
||||
// 安全地解析状态码
|
||||
char* endptr;
|
||||
long status = strtol(status_str.c_str(), &endptr, 10);
|
||||
|
||||
if (endptr == status_str.c_str() || *endptr != '\0' || status < 100 || status > 999) {
|
||||
ESP_LOGE(TAG, "Parse status code failed: %s", status_str.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
status_code_ = static_cast<int>(status);
|
||||
ESP_LOGD(TAG, "HTTP status code: %d", status_code_);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HttpClient::ParseHeaderLine(const std::string& line) {
|
||||
size_t colon_pos = line.find(':');
|
||||
if (colon_pos == std::string::npos) {
|
||||
ESP_LOGE(TAG, "Invalid header line: %s", line.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string key = line.substr(0, colon_pos);
|
||||
std::string value = line.substr(colon_pos + 1);
|
||||
|
||||
// 去除前后空格
|
||||
key.erase(0, key.find_first_not_of(" \t"));
|
||||
key.erase(key.find_last_not_of(" \t") + 1);
|
||||
value.erase(0, value.find_first_not_of(" \t"));
|
||||
value.erase(value.find_last_not_of(" \t\r\n") + 1);
|
||||
|
||||
// 转换为小写键名用于存储和查找,同时保存原始key
|
||||
std::string lower_key = key;
|
||||
std::transform(lower_key.begin(), lower_key.end(), lower_key.begin(), ::tolower);
|
||||
|
||||
response_headers_[lower_key] = HeaderEntry(key, value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void HttpClient::ParseChunkedBody(const std::string& data) {
|
||||
// Chunked body 在 ProcessReceivedData 中的状态机中处理
|
||||
}
|
||||
|
||||
void HttpClient::ParseRegularBody(const std::string& data) {
|
||||
if (!data.empty()) {
|
||||
AddBodyData(data); // 使用新的方法添加数据
|
||||
total_body_received_ += data.size(); // 累加接收的字节数
|
||||
rx_buffer_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
size_t HttpClient::ParseChunkSize(const std::string& chunk_size_line) {
|
||||
// 解析 chunk size(十六进制)
|
||||
std::string size_str = chunk_size_line;
|
||||
|
||||
// 移除 CRLF 和任何扩展
|
||||
size_t semi_pos = size_str.find(';');
|
||||
if (semi_pos != std::string::npos) {
|
||||
size_str = size_str.substr(0, semi_pos);
|
||||
}
|
||||
|
||||
size_str.erase(size_str.find_last_not_of(" \t\r\n") + 1);
|
||||
|
||||
// 安全地解析十六进制 chunk 大小
|
||||
char* endptr;
|
||||
unsigned long chunk_size = strtoul(size_str.c_str(), &endptr, 16);
|
||||
|
||||
if (endptr == size_str.c_str() || *endptr != '\0') {
|
||||
ESP_LOGE(TAG, "Parse chunk size failed: %s", size_str.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
return static_cast<size_t>(chunk_size);
|
||||
}
|
||||
|
||||
std::string HttpClient::GetNextLine(std::string& buffer) {
|
||||
size_t pos = buffer.find('\n');
|
||||
if (pos == std::string::npos) {
|
||||
return ""; // 没有完整的行
|
||||
}
|
||||
|
||||
std::string line = buffer.substr(0, pos);
|
||||
buffer.erase(0, pos + 1);
|
||||
|
||||
// 移除 CR
|
||||
if (!line.empty() && line.back() == '\r') {
|
||||
line.pop_back();
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
bool HttpClient::HasCompleteLine(const std::string& buffer) {
|
||||
return buffer.find('\n') != std::string::npos;
|
||||
}
|
||||
|
||||
void HttpClient::SetError() {
|
||||
ESP_LOGE(TAG, "HTTP parse error");
|
||||
xEventGroupSetBits(event_group_handle_, EC801E_HTTP_EVENT_ERROR);
|
||||
}
|
||||
|
||||
int HttpClient::Read(char* buffer, size_t buffer_size) {
|
||||
std::unique_lock<std::mutex> read_lock(read_mutex_);
|
||||
|
||||
// 如果连接异常断开,返回错误
|
||||
if (connection_error_) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 如果已经到达文件末尾且没有更多数据,返回0
|
||||
if (eof_ && body_chunks_.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 如果有数据可读,直接返回
|
||||
while (!body_chunks_.empty()) {
|
||||
auto& front_chunk = body_chunks_.front();
|
||||
size_t bytes_read = front_chunk.read(buffer, buffer_size);
|
||||
|
||||
if (bytes_read > 0) {
|
||||
// 如果当前chunk已读完,移除它
|
||||
if (front_chunk.empty()) {
|
||||
body_chunks_.pop_front();
|
||||
}
|
||||
// 通知等待的写入操作
|
||||
write_cv_.notify_one();
|
||||
return static_cast<int>(bytes_read);
|
||||
}
|
||||
|
||||
// 如果chunk为空,移除它并继续下一个
|
||||
body_chunks_.pop_front();
|
||||
}
|
||||
|
||||
// 如果连接已断开,检查是否有错误
|
||||
if (!connected_) {
|
||||
if (connection_error_) {
|
||||
return -1; // 连接异常断开
|
||||
}
|
||||
return 0; // 正常结束
|
||||
}
|
||||
|
||||
// 等待数据或连接关闭
|
||||
auto timeout = std::chrono::milliseconds(timeout_ms_);
|
||||
bool received = cv_.wait_for(read_lock, timeout, [this] {
|
||||
return !body_chunks_.empty() || eof_ || !connected_ || connection_error_;
|
||||
});
|
||||
|
||||
if (!received) {
|
||||
ESP_LOGE(TAG, "Wait for HTTP content receive timeout");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 再次检查连接错误状态
|
||||
if (connection_error_) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 再次检查是否有数据可读
|
||||
while (!body_chunks_.empty()) {
|
||||
auto& front_chunk = body_chunks_.front();
|
||||
size_t bytes_read = front_chunk.read(buffer, buffer_size);
|
||||
|
||||
if (bytes_read > 0) {
|
||||
// 如果当前chunk已读完,移除它
|
||||
if (front_chunk.empty()) {
|
||||
body_chunks_.pop_front();
|
||||
}
|
||||
// 通知等待的写入操作
|
||||
write_cv_.notify_one();
|
||||
return static_cast<int>(bytes_read);
|
||||
}
|
||||
|
||||
// 如果chunk为空,移除它并继续下一个
|
||||
body_chunks_.pop_front();
|
||||
}
|
||||
|
||||
// 连接已关闭或到达EOF,返回0
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HttpClient::Write(const char* buffer, size_t buffer_size) {
|
||||
if (!connected_ || !request_chunked_) {
|
||||
ESP_LOGE(TAG, "Cannot write: connection closed or not chunked mode");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (buffer_size == 0) {
|
||||
// 发送结束 chunk
|
||||
std::string end_chunk = "0\r\n\r\n";
|
||||
return tcp_->Send(end_chunk);
|
||||
}
|
||||
|
||||
// 发送 chunk
|
||||
std::ostringstream chunk;
|
||||
chunk << std::hex << buffer_size << "\r\n";
|
||||
chunk.write(buffer, buffer_size);
|
||||
chunk << "\r\n";
|
||||
|
||||
std::string chunk_data = chunk.str();
|
||||
return tcp_->Send(chunk_data);
|
||||
}
|
||||
|
||||
int HttpClient::GetStatusCode() {
|
||||
if (!headers_received_) {
|
||||
// 等待头部接收
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_,
|
||||
EC801E_HTTP_EVENT_HEADERS_RECEIVED | EC801E_HTTP_EVENT_ERROR,
|
||||
pdFALSE, pdFALSE,
|
||||
pdMS_TO_TICKS(timeout_ms_));
|
||||
|
||||
if (bits & EC801E_HTTP_EVENT_ERROR) {
|
||||
return -1;
|
||||
}
|
||||
if (!(bits & EC801E_HTTP_EVENT_HEADERS_RECEIVED)) {
|
||||
ESP_LOGE(TAG, "Wait for HTTP headers receive timeout");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return status_code_;
|
||||
}
|
||||
|
||||
std::string HttpClient::GetResponseHeader(const std::string& key) const {
|
||||
// 转换为小写进行查找
|
||||
std::string lower_key = key;
|
||||
std::transform(lower_key.begin(), lower_key.end(), lower_key.begin(), ::tolower);
|
||||
|
||||
auto it = response_headers_.find(lower_key);
|
||||
if (it != response_headers_.end()) {
|
||||
return it->second.value;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
size_t HttpClient::GetBodyLength() {
|
||||
if (!headers_received_) {
|
||||
GetStatusCode(); // 这会等待头部接收
|
||||
}
|
||||
|
||||
if (response_chunked_) {
|
||||
return 0; // Chunked 模式下长度未知
|
||||
}
|
||||
|
||||
return content_length_;
|
||||
}
|
||||
|
||||
void HttpClient::AddBodyData(const std::string& data) {
|
||||
if (data.empty()) return;
|
||||
|
||||
std::lock_guard<std::mutex> read_lock(read_mutex_);
|
||||
body_chunks_.emplace_back(data); // 使用构造函数,避免额外的拷贝
|
||||
cv_.notify_one(); // 通知有新数据
|
||||
write_cv_.notify_one(); // 通知写入操作
|
||||
}
|
||||
|
||||
void HttpClient::AddBodyData(std::string&& data) {
|
||||
if (data.empty()) return;
|
||||
|
||||
std::lock_guard<std::mutex> read_lock(read_mutex_);
|
||||
body_chunks_.emplace_back(std::move(data)); // 使用移动语义,避免拷贝
|
||||
cv_.notify_one(); // 通知有新数据
|
||||
write_cv_.notify_one(); // 通知写入操作
|
||||
}
|
||||
|
||||
std::string HttpClient::ReadAll() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
|
||||
// 等待完成或出错
|
||||
auto timeout = std::chrono::milliseconds(timeout_ms_);
|
||||
bool completed = cv_.wait_for(lock, timeout, [this] {
|
||||
return eof_ || connection_error_;
|
||||
});
|
||||
|
||||
if (!completed) {
|
||||
ESP_LOGE(TAG, "Wait for HTTP content receive complete timeout");
|
||||
return ""; // 超时返回空字符串
|
||||
}
|
||||
|
||||
// 如果连接异常断开,返回空字符串并记录错误
|
||||
if (connection_error_) {
|
||||
ESP_LOGE(TAG, "Cannot read all data: connection closed prematurely");
|
||||
return "";
|
||||
}
|
||||
|
||||
// 收集所有数据
|
||||
std::string result;
|
||||
std::lock_guard<std::mutex> read_lock(read_mutex_);
|
||||
for (const auto& chunk : body_chunks_) {
|
||||
result.append(chunk.data);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool HttpClient::IsDataComplete() const {
|
||||
// 对于chunked编码,如果parse_state_是COMPLETE,说明接收完整
|
||||
if (response_chunked_) {
|
||||
return parse_state_ == ParseState::COMPLETE;
|
||||
}
|
||||
|
||||
// 对于固定长度,检查是否接收了完整的content-length
|
||||
if (content_length_ > 0) {
|
||||
return total_body_received_ >= content_length_;
|
||||
}
|
||||
|
||||
// 如果没有content-length且不是chunked,当连接关闭时认为完整
|
||||
// 这种情况通常用于HTTP/1.0或者content-length为0的响应
|
||||
return true;
|
||||
}
|
||||
112
managed_components/78__esp-ml307/src/ml307/ml307_at_modem.cc
Normal file
112
managed_components/78__esp-ml307/src/ml307/ml307_at_modem.cc
Normal file
@@ -0,0 +1,112 @@
|
||||
#include "ml307_at_modem.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
#include <cassert>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <cstring>
|
||||
#include "ml307_tcp.h"
|
||||
#include "ml307_ssl.h"
|
||||
#include "ml307_udp.h"
|
||||
#include "ml307_mqtt.h"
|
||||
#include "ml307_http.h"
|
||||
#include "web_socket.h"
|
||||
|
||||
#define TAG "Ml307AtModem"
|
||||
|
||||
|
||||
Ml307AtModem::Ml307AtModem(std::shared_ptr<AtUart> at_uart) : AtModem(at_uart) {
|
||||
// 子类特定的初始化在这里
|
||||
// Reset HTTP instances
|
||||
ResetConnections();
|
||||
}
|
||||
|
||||
void Ml307AtModem::ResetConnections() {
|
||||
at_uart_->SendCommand("AT+MHTTPDEL=0");
|
||||
at_uart_->SendCommand("AT+MHTTPDEL=1");
|
||||
at_uart_->SendCommand("AT+MHTTPDEL=2");
|
||||
at_uart_->SendCommand("AT+MHTTPDEL=3");
|
||||
}
|
||||
|
||||
void Ml307AtModem::HandleUrc(const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
// Handle Common URC
|
||||
AtModem::HandleUrc(command, arguments);
|
||||
// Handle ML307 URC
|
||||
if (command == "MIPCALL" && arguments.size() >= 3) {
|
||||
if (arguments[1].int_value == 1) {
|
||||
auto ip = arguments[2].string_value;
|
||||
ESP_LOGI(TAG, "PDP Context %d IP: %s", arguments[0].int_value, ip.c_str());
|
||||
network_ready_ = true;
|
||||
xEventGroupSetBits(event_group_handle_, AT_EVENT_NETWORK_READY);
|
||||
}
|
||||
} else if (command == "MATREADY") {
|
||||
if (network_ready_) {
|
||||
network_ready_ = false;
|
||||
if (on_network_state_changed_) {
|
||||
on_network_state_changed_(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Ml307AtModem::Reboot() {
|
||||
at_uart_->SendCommand("AT+MREBOOT=0");
|
||||
}
|
||||
|
||||
bool Ml307AtModem::SetSleepMode(bool enable, int delay_seconds) {
|
||||
if (enable) {
|
||||
if (delay_seconds > 0) {
|
||||
at_uart_->SendCommand("AT+MLPMCFG=\"delaysleep\"," + std::to_string(delay_seconds));
|
||||
}
|
||||
return at_uart_->SendCommand("AT+MLPMCFG=\"sleepmode\",2,0");
|
||||
} else {
|
||||
return at_uart_->SendCommand("AT+MLPMCFG=\"sleepmode\",0,0");
|
||||
}
|
||||
}
|
||||
|
||||
NetworkStatus Ml307AtModem::WaitForNetworkReady(int timeout_ms) {
|
||||
NetworkStatus status = AtModem::WaitForNetworkReady(timeout_ms);
|
||||
if (status == NetworkStatus::Ready) {
|
||||
// Wait for IP address, maximum total wait time is 4270ms
|
||||
int delay_ms = 10;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
at_uart_->SendCommand("AT+MIPCALL?");
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, AT_EVENT_NETWORK_READY, pdFALSE, pdTRUE, pdMS_TO_TICKS(delay_ms));
|
||||
if (bits & AT_EVENT_NETWORK_READY) {
|
||||
return NetworkStatus::Ready;
|
||||
}
|
||||
delay_ms = std::min(delay_ms * 2, 1000);
|
||||
}
|
||||
ESP_LOGE(TAG, "Network ready but no IP address");
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
std::unique_ptr<Http> Ml307AtModem::CreateHttp(int connect_id) {
|
||||
return std::make_unique<Ml307Http>(at_uart_);
|
||||
}
|
||||
|
||||
std::unique_ptr<Tcp> Ml307AtModem::CreateTcp(int connect_id) {
|
||||
assert(connect_id >= 0);
|
||||
return std::make_unique<Ml307Tcp>(at_uart_, connect_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<Tcp> Ml307AtModem::CreateSsl(int connect_id) {
|
||||
assert(connect_id >= 0);
|
||||
return std::make_unique<Ml307Ssl>(at_uart_, connect_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<Udp> Ml307AtModem::CreateUdp(int connect_id) {
|
||||
assert(connect_id >= 0);
|
||||
return std::make_unique<Ml307Udp>(at_uart_, connect_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<Mqtt> Ml307AtModem::CreateMqtt(int connect_id) {
|
||||
assert(connect_id >= 0);
|
||||
return std::make_unique<Ml307Mqtt>(at_uart_, connect_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<WebSocket> Ml307AtModem::CreateWebSocket(int connect_id) {
|
||||
assert(connect_id >= 0);
|
||||
return std::make_unique<WebSocket>(this, connect_id);
|
||||
}
|
||||
34
managed_components/78__esp-ml307/src/ml307/ml307_at_modem.h
Normal file
34
managed_components/78__esp-ml307/src/ml307/ml307_at_modem.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#ifndef _ML307_AT_MODEM_H_
|
||||
#define _ML307_AT_MODEM_H_
|
||||
|
||||
#include "at_modem.h"
|
||||
#include "tcp.h"
|
||||
#include "udp.h"
|
||||
#include "http.h"
|
||||
#include "mqtt.h"
|
||||
#include "web_socket.h"
|
||||
|
||||
class Ml307AtModem : public AtModem {
|
||||
public:
|
||||
Ml307AtModem(std::shared_ptr<AtUart> at_uart);
|
||||
~Ml307AtModem() override = default;
|
||||
|
||||
void Reboot() override;
|
||||
bool SetSleepMode(bool enable, int delay_seconds=0) override;
|
||||
NetworkStatus WaitForNetworkReady(int timeout_ms=-1) override;
|
||||
|
||||
// 实现基类的纯虚函数
|
||||
std::unique_ptr<Http> CreateHttp(int connect_id) override;
|
||||
std::unique_ptr<Tcp> CreateTcp(int connect_id) override;
|
||||
std::unique_ptr<Tcp> CreateSsl(int connect_id) override;
|
||||
std::unique_ptr<Udp> CreateUdp(int connect_id) override;
|
||||
std::unique_ptr<Mqtt> CreateMqtt(int connect_id) override;
|
||||
std::unique_ptr<WebSocket> CreateWebSocket(int connect_id) override;
|
||||
|
||||
protected:
|
||||
void HandleUrc(const std::string& command, const std::vector<AtArgumentValue>& arguments) override;
|
||||
void ResetConnections();
|
||||
};
|
||||
|
||||
|
||||
#endif // _ML307_AT_MODEM_H_
|
||||
353
managed_components/78__esp-ml307/src/ml307/ml307_http.cc
Normal file
353
managed_components/78__esp-ml307/src/ml307/ml307_http.cc
Normal file
@@ -0,0 +1,353 @@
|
||||
#include "ml307_http.h"
|
||||
#include <esp_log.h>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include <chrono>
|
||||
|
||||
static const char *TAG = "Ml307Http";
|
||||
|
||||
Ml307Http::Ml307Http(std::shared_ptr<AtUart> at_uart) : at_uart_(at_uart) {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
|
||||
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
if (command == "MHTTPURC") {
|
||||
if (arguments[1].int_value == http_id_) {
|
||||
auto& type = arguments[0].string_value;
|
||||
if (type == "header") {
|
||||
eof_ = false;
|
||||
body_offset_ = 0;
|
||||
body_.clear();
|
||||
status_code_ = arguments[2].int_value;
|
||||
if (arguments.size() >= 5) {
|
||||
ParseResponseHeaders(at_uart_->DecodeHex(arguments[4].string_value));
|
||||
} else {
|
||||
// FIXME: <header> 被分包发送
|
||||
ESP_LOGE(TAG, "Missing header");
|
||||
}
|
||||
xEventGroupSetBits(event_group_handle_, ML307_HTTP_EVENT_HEADERS_RECEIVED);
|
||||
} else if (type == "content") {
|
||||
// +MHTTPURC: "content",<httpid>,<content_len>,<sum_len>,<cur_len>,<data>
|
||||
std::string decoded_data;
|
||||
if (arguments.size() >= 6) {
|
||||
at_uart_->DecodeHexAppend(decoded_data, arguments[5].string_value.c_str(), arguments[5].string_value.length());
|
||||
} else {
|
||||
// FIXME: <data> 被分包发送
|
||||
ESP_LOGE(TAG, "Missing content");
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
body_.append(decoded_data);
|
||||
|
||||
// chunked传输时,EOF由cur_len == 0判断,非 chunked传输时,EOF由content_len判断
|
||||
if (response_chunked_) {
|
||||
eof_ = arguments[4].int_value == 0;
|
||||
} else {
|
||||
eof_ = arguments[3].int_value >= arguments[2].int_value;
|
||||
}
|
||||
|
||||
body_offset_ += arguments[4].int_value;
|
||||
if (arguments[3].int_value > body_offset_) {
|
||||
ESP_LOGE(TAG, "body_offset_: %u, arguments[3].int_value: %d", body_offset_, arguments[3].int_value);
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
cv_.notify_one(); // 使用条件变量通知
|
||||
} else if (type == "err") {
|
||||
error_code_ = arguments[2].int_value;
|
||||
xEventGroupSetBits(event_group_handle_, ML307_HTTP_EVENT_ERROR);
|
||||
} else if (type == "ind") {
|
||||
xEventGroupSetBits(event_group_handle_, ML307_HTTP_EVENT_IND);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unknown HTTP event: %s", type.c_str());
|
||||
}
|
||||
}
|
||||
} else if (command == "MHTTPCREATE") {
|
||||
http_id_ = arguments[0].int_value;
|
||||
instance_active_ = true;
|
||||
xEventGroupSetBits(event_group_handle_, ML307_HTTP_EVENT_INITIALIZED);
|
||||
} else if (command == "FIFO_OVERFLOW") {
|
||||
xEventGroupSetBits(event_group_handle_, ML307_HTTP_EVENT_ERROR);
|
||||
Close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int Ml307Http::Read(char* buffer, size_t buffer_size) {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
|
||||
if (eof_ && body_.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 使用条件变量等待数据
|
||||
auto timeout = std::chrono::milliseconds(timeout_ms_);
|
||||
bool received = cv_.wait_for(lock, timeout, [this] {
|
||||
return !body_.empty() || eof_;
|
||||
});
|
||||
|
||||
if (!received) {
|
||||
ESP_LOGE(TAG, "Timeout waiting for HTTP content to be received");
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t bytes_to_read = std::min(body_.size(), buffer_size);
|
||||
std::memcpy(buffer, body_.data(), bytes_to_read);
|
||||
body_.erase(0, bytes_to_read);
|
||||
|
||||
return bytes_to_read;
|
||||
}
|
||||
|
||||
int Ml307Http::Write(const char* buffer, size_t buffer_size) {
|
||||
if (buffer_size == 0) { // FIXME: 模组好像不支持发送空数据
|
||||
std::string command = "AT+MHTTPCONTENT=" + std::to_string(http_id_) + ",0,2,\"0D0A\"";
|
||||
at_uart_->SendCommand(command);
|
||||
return 0;
|
||||
}
|
||||
std::string command = "AT+MHTTPCONTENT=" + std::to_string(http_id_) + ",1," + std::to_string(buffer_size);
|
||||
at_uart_->SendCommand(command);
|
||||
at_uart_->SendCommand(std::string(buffer, buffer_size));
|
||||
return buffer_size;
|
||||
}
|
||||
|
||||
Ml307Http::~Ml307Http() {
|
||||
if (instance_active_) {
|
||||
Close();
|
||||
}
|
||||
|
||||
at_uart_->UnregisterUrcCallback(urc_callback_it_);
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
|
||||
void Ml307Http::SetHeader(const std::string& key, const std::string& value) {
|
||||
headers_[key] = value;
|
||||
}
|
||||
|
||||
void Ml307Http::SetContent(std::string&& content) {
|
||||
content_ = std::make_optional(std::move(content));
|
||||
}
|
||||
|
||||
void Ml307Http::SetTimeout(int timeout_ms) {
|
||||
timeout_ms_ = timeout_ms;
|
||||
}
|
||||
|
||||
void Ml307Http::ParseResponseHeaders(const std::string& headers) {
|
||||
std::istringstream iss(headers);
|
||||
std::string line;
|
||||
while (std::getline(iss, line)) {
|
||||
std::istringstream line_iss(line);
|
||||
std::string key, value;
|
||||
std::getline(line_iss, key, ':');
|
||||
std::getline(line_iss, value);
|
||||
|
||||
// 去除前后空格
|
||||
key.erase(0, key.find_first_not_of(" \t"));
|
||||
key.erase(key.find_last_not_of(" \t") + 1);
|
||||
value.erase(0, value.find_first_not_of(" \t"));
|
||||
value.erase(value.find_last_not_of(" \t\r\n") + 1);
|
||||
|
||||
response_headers_[key] = value;
|
||||
|
||||
// 检查是否为chunked传输编码
|
||||
if (key == "Transfer-Encoding" && value.find("chunked") != std::string::npos) {
|
||||
response_chunked_ = true;
|
||||
ESP_LOGI(TAG, "Found chunked transfer encoding");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Ml307Http::Open(const std::string& method, const std::string& url) {
|
||||
method_ = method;
|
||||
url_ = url;
|
||||
|
||||
// 判断是否为需要发送内容的HTTP方法
|
||||
bool method_supports_content = (method_ == "POST" || method_ == "PUT");
|
||||
|
||||
// 解析URL
|
||||
size_t protocol_end = url.find("://");
|
||||
if (protocol_end != std::string::npos) {
|
||||
protocol_ = url.substr(0, protocol_end);
|
||||
size_t host_start = protocol_end + 3;
|
||||
size_t path_start = url.find("/", host_start);
|
||||
if (path_start != std::string::npos) {
|
||||
host_ = url.substr(host_start, path_start - host_start);
|
||||
path_ = url.substr(path_start);
|
||||
} else {
|
||||
host_ = url.substr(host_start);
|
||||
path_ = "/";
|
||||
}
|
||||
} else {
|
||||
// URL格式不正确
|
||||
ESP_LOGE(TAG, "Invalid URL format");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建HTTP连接
|
||||
std::string command = "AT+MHTTPCREATE=\"" + protocol_ + "://" + host_ + "\"";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to create HTTP connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, ML307_HTTP_EVENT_INITIALIZED, pdTRUE, pdFALSE, pdMS_TO_TICKS(timeout_ms_));
|
||||
if (!(bits & ML307_HTTP_EVENT_INITIALIZED)) {
|
||||
ESP_LOGE(TAG, "Timeout waiting for HTTP connection to be created");
|
||||
return false;
|
||||
}
|
||||
request_chunked_ = method_supports_content && !content_.has_value();
|
||||
ESP_LOGI(TAG, "HTTP connection created, ID: %d, protocol: %s, host: %s", http_id_, protocol_.c_str(), host_.c_str());
|
||||
|
||||
if (protocol_ == "https") {
|
||||
command = "AT+MHTTPCFG=\"ssl\"," + std::to_string(http_id_) + ",1,0";
|
||||
at_uart_->SendCommand(command);
|
||||
}
|
||||
|
||||
if (request_chunked_) {
|
||||
command = "AT+MHTTPCFG=\"chunked\"," + std::to_string(http_id_) + ",1";
|
||||
at_uart_->SendCommand(command);
|
||||
}
|
||||
|
||||
// Set HEX encoding OFF
|
||||
command = "AT+MHTTPCFG=\"encoding\"," + std::to_string(http_id_) + ",0,0";
|
||||
at_uart_->SendCommand(command);
|
||||
|
||||
// Set timeout (seconds): connect timeout, response timeout, input timeout
|
||||
// sprintf(command, "AT+MHTTPCFG=\"timeout\",%d,%d,%d,%d", http_id_, timeout_ms_ / 1000, timeout_ms_ / 1000, timeout_ms_ / 1000);
|
||||
// modem_.Command(command);
|
||||
|
||||
// Set headers
|
||||
for (auto it = headers_.begin(); it != headers_.end(); it++) {
|
||||
auto line = it->first + ": " + it->second;
|
||||
bool is_last = std::next(it) == headers_.end();
|
||||
command = "AT+MHTTPHEADER=" + std::to_string(http_id_) + "," + std::to_string(is_last ? 0 : 1) + "," + std::to_string(line.size()) + ",\"" + line + "\"";
|
||||
at_uart_->SendCommand(command);
|
||||
}
|
||||
|
||||
if (method_supports_content && content_.has_value()) {
|
||||
command = "AT+MHTTPCONTENT=" + std::to_string(http_id_) + ",0," + std::to_string(content_.value().size());
|
||||
at_uart_->SendCommand(command);
|
||||
at_uart_->SendCommand(content_.value());
|
||||
content_ = std::nullopt;
|
||||
}
|
||||
|
||||
// Set HEX encoding ON
|
||||
command = "AT+MHTTPCFG=\"encoding\"," + std::to_string(http_id_) + ",1,1";
|
||||
at_uart_->SendCommand(command);
|
||||
|
||||
// Send request
|
||||
// method to value: 1. GET 2. POST 3. PUT 4. DELETE 5. HEAD
|
||||
const char* methods[6] = {"UNKNOWN", "GET", "POST", "PUT", "DELETE", "HEAD"};
|
||||
int method_value = 1;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (strcmp(methods[i], method_.c_str()) == 0) {
|
||||
method_value = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
command = "AT+MHTTPREQUEST=" + std::to_string(http_id_) + "," + std::to_string(method_value) + ",0,";
|
||||
if (!at_uart_->SendCommand(command + at_uart_->EncodeHex(path_))) {
|
||||
ESP_LOGE(TAG, "Failed to send HTTP request");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request_chunked_) {
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, ML307_HTTP_EVENT_IND, pdTRUE, pdFALSE, pdMS_TO_TICKS(timeout_ms_));
|
||||
if (!(bits & ML307_HTTP_EVENT_IND)) {
|
||||
ESP_LOGE(TAG, "Timeout waiting for HTTP IND");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Ml307Http::FetchHeaders() {
|
||||
// Wait for headers
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, ML307_HTTP_EVENT_HEADERS_RECEIVED | ML307_HTTP_EVENT_ERROR, pdTRUE, pdFALSE, pdMS_TO_TICKS(timeout_ms_));
|
||||
if (bits & ML307_HTTP_EVENT_ERROR) {
|
||||
ESP_LOGE(TAG, "HTTP request error: %s", ErrorCodeToString(error_code_).c_str());
|
||||
return false;
|
||||
}
|
||||
if (!(bits & ML307_HTTP_EVENT_HEADERS_RECEIVED)) {
|
||||
ESP_LOGE(TAG, "Timeout waiting for HTTP headers to be received");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto it = response_headers_.find("Content-Length");
|
||||
if (it != response_headers_.end()) {
|
||||
content_length_ = std::stoul(it->second);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "HTTP request successful, status code: %d", status_code_);
|
||||
return true;
|
||||
}
|
||||
|
||||
int Ml307Http::GetStatusCode() {
|
||||
if (status_code_ == -1) {
|
||||
if (!FetchHeaders()) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return status_code_;
|
||||
}
|
||||
|
||||
size_t Ml307Http::GetBodyLength() {
|
||||
if (status_code_ == -1) {
|
||||
if (!FetchHeaders()) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return content_length_;
|
||||
}
|
||||
|
||||
std::string Ml307Http::ReadAll() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
|
||||
auto timeout = std::chrono::milliseconds(timeout_ms_);
|
||||
bool received = cv_.wait_for(lock, timeout, [this] {
|
||||
return eof_;
|
||||
});
|
||||
|
||||
if (!received) {
|
||||
ESP_LOGE(TAG, "Timeout waiting for HTTP content to be received");
|
||||
return body_;
|
||||
}
|
||||
|
||||
return body_;
|
||||
}
|
||||
|
||||
void Ml307Http::Close() {
|
||||
if (!instance_active_) {
|
||||
return;
|
||||
}
|
||||
std::string command = "AT+MHTTPDEL=" + std::to_string(http_id_);
|
||||
at_uart_->SendCommand(command);
|
||||
|
||||
instance_active_ = false;
|
||||
eof_ = true;
|
||||
cv_.notify_one();
|
||||
ESP_LOGI(TAG, "HTTP connection closed, ID: %d", http_id_);
|
||||
}
|
||||
|
||||
std::string Ml307Http::ErrorCodeToString(int error_code) {
|
||||
switch (error_code) {
|
||||
case 1: return "Domain name resolution failed";
|
||||
case 2: return "Connection to server failed";
|
||||
case 3: return "Connection to server timeout";
|
||||
case 4: return "SSL handshake failed";
|
||||
case 5: return "Connection abnormal disconnection";
|
||||
case 6: return "Request response timeout";
|
||||
case 7: return "Data reception parsing failed";
|
||||
case 8: return "Cache space insufficient";
|
||||
case 9: return "Data packet loss";
|
||||
case 10: return "File write failed";
|
||||
case 255: return "Unknown error";
|
||||
default: return "Undefined error";
|
||||
}
|
||||
}
|
||||
|
||||
std::string Ml307Http::GetResponseHeader(const std::string& key) const {
|
||||
auto it = response_headers_.find(key);
|
||||
if (it != response_headers_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
72
managed_components/78__esp-ml307/src/ml307/ml307_http.h
Normal file
72
managed_components/78__esp-ml307/src/ml307/ml307_http.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#ifndef ML307_HTTP_TRANSPORT_H
|
||||
#define ML307_HTTP_TRANSPORT_H
|
||||
|
||||
#include "at_uart.h"
|
||||
#include "http.h"
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <optional>
|
||||
|
||||
#define ML307_HTTP_EVENT_INITIALIZED (1 << 0)
|
||||
#define ML307_HTTP_EVENT_ERROR (1 << 2)
|
||||
#define ML307_HTTP_EVENT_HEADERS_RECEIVED (1 << 3)
|
||||
#define ML307_HTTP_EVENT_IND (1 << 4)
|
||||
|
||||
class Ml307Http : public Http {
|
||||
public:
|
||||
Ml307Http(std::shared_ptr<AtUart> at_uart);
|
||||
~Ml307Http();
|
||||
|
||||
void SetTimeout(int timeout_ms) override;
|
||||
void SetHeader(const std::string& key, const std::string& value) override;
|
||||
void SetContent(std::string&& content) override;
|
||||
bool Open(const std::string& method, const std::string& url) override;
|
||||
void Close() override;
|
||||
int Read(char* buffer, size_t buffer_size) override;
|
||||
int Write(const char* buffer, size_t buffer_size) override;
|
||||
|
||||
int GetStatusCode() override;
|
||||
std::string GetResponseHeader(const std::string& key) const override;
|
||||
size_t GetBodyLength() override;
|
||||
std::string ReadAll() override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<AtUart> at_uart_;
|
||||
EventGroupHandle_t event_group_handle_;
|
||||
std::mutex mutex_;
|
||||
std::condition_variable cv_;
|
||||
|
||||
int http_id_ = -1;
|
||||
int status_code_ = -1;
|
||||
int error_code_ = -1;
|
||||
int timeout_ms_ = 30000;
|
||||
std::string rx_buffer_;
|
||||
std::list<UrcCallback>::iterator urc_callback_it_;
|
||||
std::map<std::string, std::string> headers_;
|
||||
std::string url_;
|
||||
std::string method_;
|
||||
std::string protocol_;
|
||||
std::string host_;
|
||||
std::string path_;
|
||||
std::optional<std::string> content_ = std::nullopt;
|
||||
std::map<std::string, std::string> response_headers_;
|
||||
std::string body_;
|
||||
size_t body_offset_ = 0;
|
||||
size_t content_length_ = 0;
|
||||
bool eof_ = false;
|
||||
bool instance_active_ = false;
|
||||
bool request_chunked_ = false;
|
||||
bool response_chunked_ = false;
|
||||
|
||||
bool FetchHeaders();
|
||||
void ParseResponseHeaders(const std::string& headers);
|
||||
std::string ErrorCodeToString(int error_code);
|
||||
};
|
||||
|
||||
#endif
|
||||
196
managed_components/78__esp-ml307/src/ml307/ml307_mqtt.cc
Normal file
196
managed_components/78__esp-ml307/src/ml307/ml307_mqtt.cc
Normal file
@@ -0,0 +1,196 @@
|
||||
#include "ml307_mqtt.h"
|
||||
#include <esp_log.h>
|
||||
|
||||
static const char *TAG = "Ml307Mqtt";
|
||||
|
||||
Ml307Mqtt::Ml307Mqtt(std::shared_ptr<AtUart> at_uart, int mqtt_id) : at_uart_(at_uart), mqtt_id_(mqtt_id) {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
|
||||
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
if (command == "MQTTURC" && arguments.size() >= 2) {
|
||||
if (arguments[1].int_value == mqtt_id_) {
|
||||
auto type = arguments[0].string_value;
|
||||
if (type == "conn") {
|
||||
int error_code = arguments[2].int_value;
|
||||
if (error_code == 0) {
|
||||
if (!connected_) {
|
||||
connected_ = true;
|
||||
if (on_connected_callback_) {
|
||||
on_connected_callback_();
|
||||
}
|
||||
}
|
||||
xEventGroupSetBits(event_group_handle_, MQTT_CONNECTED_EVENT);
|
||||
} else {
|
||||
if (connected_) {
|
||||
connected_ = false;
|
||||
if (on_disconnected_callback_) {
|
||||
on_disconnected_callback_();
|
||||
}
|
||||
}
|
||||
xEventGroupSetBits(event_group_handle_, MQTT_DISCONNECTED_EVENT);
|
||||
}
|
||||
if (error_code == 5 || error_code == 6) {
|
||||
auto error_message = ErrorToString(error_code);
|
||||
ESP_LOGW(TAG, "MQTT error occurred: %s", error_message.c_str());
|
||||
if (on_error_callback_) {
|
||||
on_error_callback_(error_message);
|
||||
}
|
||||
}
|
||||
} else if (type == "suback") {
|
||||
} else if (type == "publish" && arguments.size() >= 7) {
|
||||
auto topic = arguments[3].string_value;
|
||||
if (arguments[4].int_value == arguments[5].int_value) {
|
||||
if (on_message_callback_) {
|
||||
on_message_callback_(topic, at_uart_->DecodeHex(arguments[6].string_value));
|
||||
}
|
||||
} else {
|
||||
message_payload_.append(at_uart_->DecodeHex(arguments[6].string_value));
|
||||
if (message_payload_.size() >= arguments[4].int_value && on_message_callback_) {
|
||||
on_message_callback_(topic, message_payload_);
|
||||
message_payload_.clear();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGI(TAG, "unhandled MQTT event: %s", type.c_str());
|
||||
}
|
||||
}
|
||||
} else if (command == "MQTTSTATE" && arguments.size() == 1) {
|
||||
connected_ = arguments[0].int_value != 3;
|
||||
xEventGroupSetBits(event_group_handle_, MQTT_INITIALIZED_EVENT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ml307Mqtt::~Ml307Mqtt() {
|
||||
at_uart_->UnregisterUrcCallback(urc_callback_it_);
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
|
||||
bool Ml307Mqtt::Connect(const std::string broker_address, int broker_port, const std::string client_id, const std::string username, const std::string password) {
|
||||
EventBits_t bits;
|
||||
if (IsConnected()) {
|
||||
// 断开之前的连接
|
||||
Disconnect();
|
||||
bits = xEventGroupWaitBits(event_group_handle_, MQTT_DISCONNECTED_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(MQTT_CONNECT_TIMEOUT_MS));
|
||||
if (!(bits & MQTT_DISCONNECTED_EVENT)) {
|
||||
ESP_LOGE(TAG, "Failed to disconnect from previous connection");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (broker_port == 8883) {
|
||||
if (!at_uart_->SendCommand(std::string("AT+MQTTCFG=\"ssl\",") + std::to_string(mqtt_id_) + ",1")) {
|
||||
ESP_LOGE(TAG, "Failed to set MQTT to use SSL");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Set clean session
|
||||
if (!at_uart_->SendCommand(std::string("AT+MQTTCFG=\"clean\",") + std::to_string(mqtt_id_) + ",1")) {
|
||||
ESP_LOGE(TAG, "Failed to set MQTT clean session");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set keep alive and ping interval both to the same value
|
||||
if (!at_uart_->SendCommand(std::string("AT+MQTTCFG=\"keepalive\",") + std::to_string(mqtt_id_) + "," + std::to_string(keep_alive_seconds_))) {
|
||||
ESP_LOGE(TAG, "Failed to set MQTT keepalive interval");
|
||||
return false;
|
||||
}
|
||||
if (!at_uart_->SendCommand(std::string("AT+MQTTCFG=\"pingreq\",") + std::to_string(mqtt_id_) + "," + std::to_string(keep_alive_seconds_))) {
|
||||
ESP_LOGE(TAG, "Failed to set MQTT ping interval");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set HEX encoding (ASCII for sending, HEX for receiving)
|
||||
if (!at_uart_->SendCommand("AT+MQTTCFG=\"encoding\"," + std::to_string(mqtt_id_) + ",0,1")) {
|
||||
ESP_LOGE(TAG, "Failed to set MQTT to use HEX encoding");
|
||||
return false;
|
||||
}
|
||||
|
||||
xEventGroupClearBits(event_group_handle_, MQTT_CONNECTED_EVENT | MQTT_DISCONNECTED_EVENT);
|
||||
// 创建MQTT连接
|
||||
std::string command = "AT+MQTTCONN=" + std::to_string(mqtt_id_) + ",\"" + broker_address + "\"," + std::to_string(broker_port) + ",\"" + client_id + "\",\"" + username + "\",\"" + password + "\"";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to create MQTT connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 等待连接完成
|
||||
bits = xEventGroupWaitBits(event_group_handle_, MQTT_CONNECTED_EVENT | MQTT_DISCONNECTED_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(MQTT_CONNECT_TIMEOUT_MS));
|
||||
if (!(bits & MQTT_CONNECTED_EVENT)) {
|
||||
ESP_LOGE(TAG, "Failed to connect to MQTT broker");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Ml307Mqtt::IsConnected() {
|
||||
// 检查这个 id 是否已经连接
|
||||
at_uart_->SendCommand(std::string("AT+MQTTSTATE=") + std::to_string(mqtt_id_));
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, MQTT_INITIALIZED_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(MQTT_CONNECT_TIMEOUT_MS));
|
||||
if (!(bits & MQTT_INITIALIZED_EVENT)) {
|
||||
ESP_LOGE(TAG, "Failed to initialize MQTT connection");
|
||||
return false;
|
||||
}
|
||||
return connected_;
|
||||
}
|
||||
|
||||
void Ml307Mqtt::Disconnect() {
|
||||
if (!connected_) {
|
||||
return;
|
||||
}
|
||||
at_uart_->SendCommand(std::string("AT+MQTTDISC=") + std::to_string(mqtt_id_));
|
||||
}
|
||||
|
||||
bool Ml307Mqtt::Publish(const std::string topic, const std::string payload, int qos) {
|
||||
if (!connected_) {
|
||||
return false;
|
||||
}
|
||||
// If payload size is larger than 64KB, a CME ERROR 601 will be returned.
|
||||
std::string command = "AT+MQTTPUB=" + std::to_string(mqtt_id_) + ",\"" + topic + "\",";
|
||||
command += std::to_string(qos) + ",0,0,";
|
||||
command += std::to_string(payload.size());
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
return false;
|
||||
}
|
||||
return at_uart_->SendCommand(payload);
|
||||
}
|
||||
|
||||
bool Ml307Mqtt::Subscribe(const std::string topic, int qos) {
|
||||
if (!connected_) {
|
||||
return false;
|
||||
}
|
||||
std::string command = "AT+MQTTSUB=" + std::to_string(mqtt_id_) + ",\"" + topic + "\"," + std::to_string(qos);
|
||||
return at_uart_->SendCommand(command);
|
||||
}
|
||||
|
||||
bool Ml307Mqtt::Unsubscribe(const std::string topic) {
|
||||
if (!connected_) {
|
||||
return false;
|
||||
}
|
||||
std::string command = "AT+MQTTUNSUB=" + std::to_string(mqtt_id_) + ",\"" + topic + "\"";
|
||||
return at_uart_->SendCommand(command);
|
||||
}
|
||||
|
||||
std::string Ml307Mqtt::ErrorToString(int error_code) {
|
||||
switch (error_code) {
|
||||
case 0:
|
||||
return "Connected";
|
||||
case 1:
|
||||
return "Reconnecting";
|
||||
case 2:
|
||||
return "Disconnected: User initiated";
|
||||
case 3:
|
||||
return "Disconnected: Rejected (protocol version, identifier, username or password error)";
|
||||
case 4:
|
||||
return "Disconnected: Server disconnected";
|
||||
case 5:
|
||||
return "Disconnected: Ping timeout";
|
||||
case 6:
|
||||
return "Disconnected: Network error";
|
||||
case 255:
|
||||
return "Disconnected: Unknown error";
|
||||
default:
|
||||
return "Unknown error";
|
||||
}
|
||||
}
|
||||
43
managed_components/78__esp-ml307/src/ml307/ml307_mqtt.h
Normal file
43
managed_components/78__esp-ml307/src/ml307/ml307_mqtt.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef ML307_MQTT_H
|
||||
#define ML307_MQTT_H
|
||||
|
||||
#include "mqtt.h"
|
||||
|
||||
#include "at_uart.h"
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
#define MQTT_CONNECT_TIMEOUT_MS 10000
|
||||
|
||||
#define MQTT_INITIALIZED_EVENT BIT0
|
||||
#define MQTT_CONNECTED_EVENT BIT1
|
||||
#define MQTT_DISCONNECTED_EVENT BIT2
|
||||
|
||||
class Ml307Mqtt : public Mqtt {
|
||||
public:
|
||||
Ml307Mqtt(std::shared_ptr<AtUart> at_uart, int mqtt_id);
|
||||
~Ml307Mqtt();
|
||||
|
||||
bool Connect(const std::string broker_address, int broker_port, const std::string client_id, const std::string username, const std::string password);
|
||||
void Disconnect();
|
||||
bool Publish(const std::string topic, const std::string payload, int qos = 0);
|
||||
bool Subscribe(const std::string topic, int qos = 0);
|
||||
bool Unsubscribe(const std::string topic);
|
||||
bool IsConnected();
|
||||
|
||||
private:
|
||||
std::shared_ptr<AtUart> at_uart_;
|
||||
int mqtt_id_;
|
||||
bool connected_ = false;
|
||||
EventGroupHandle_t event_group_handle_;
|
||||
std::string message_payload_;
|
||||
|
||||
std::list<UrcCallback>::iterator urc_callback_it_;
|
||||
|
||||
std::string ErrorToString(int error_code);
|
||||
};
|
||||
|
||||
#endif
|
||||
25
managed_components/78__esp-ml307/src/ml307/ml307_ssl.cc
Normal file
25
managed_components/78__esp-ml307/src/ml307/ml307_ssl.cc
Normal file
@@ -0,0 +1,25 @@
|
||||
#include "ml307_ssl.h"
|
||||
#include <esp_log.h>
|
||||
|
||||
static const char *TAG = "Ml307Ssl";
|
||||
|
||||
Ml307Ssl::Ml307Ssl(std::shared_ptr<AtUart> at_uart, int tcp_id) : Ml307Tcp(at_uart, tcp_id) {
|
||||
}
|
||||
|
||||
bool Ml307Ssl::ConfigureSsl(int port) {
|
||||
// 设置 SSL 配置
|
||||
std::string command = "AT+MSSLCFG=\"auth\",0,0";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to set SSL configuration");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 强制启用 SSL
|
||||
command = "AT+MIPCFG=\"ssl\"," + std::to_string(tcp_id_) + ",1,0";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to set SSL configuration");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
16
managed_components/78__esp-ml307/src/ml307/ml307_ssl.h
Normal file
16
managed_components/78__esp-ml307/src/ml307/ml307_ssl.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef ML307_SSL_H
|
||||
#define ML307_SSL_H
|
||||
|
||||
#include "ml307_tcp.h"
|
||||
|
||||
class Ml307Ssl : public Ml307Tcp {
|
||||
public:
|
||||
Ml307Ssl(std::shared_ptr<AtUart> at_uart, int tcp_id);
|
||||
~Ml307Ssl() = default;
|
||||
|
||||
protected:
|
||||
// 重写SSL配置方法
|
||||
bool ConfigureSsl(int port) override;
|
||||
};
|
||||
|
||||
#endif // ML307_SSL_H
|
||||
192
managed_components/78__esp-ml307/src/ml307/ml307_tcp.cc
Normal file
192
managed_components/78__esp-ml307/src/ml307/ml307_tcp.cc
Normal file
@@ -0,0 +1,192 @@
|
||||
#include "ml307_tcp.h"
|
||||
#include <esp_log.h>
|
||||
#include <cstring>
|
||||
|
||||
#define TAG "Ml307Tcp"
|
||||
|
||||
Ml307Tcp::Ml307Tcp(std::shared_ptr<AtUart> at_uart, int tcp_id) : at_uart_(at_uart), tcp_id_(tcp_id) {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
|
||||
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
if (command == "MIPOPEN" && arguments.size() == 2) {
|
||||
if (arguments[0].int_value == tcp_id_) {
|
||||
connected_ = arguments[1].int_value == 0;
|
||||
if (connected_) {
|
||||
instance_active_ = true;
|
||||
xEventGroupClearBits(event_group_handle_, ML307_TCP_DISCONNECTED | ML307_TCP_ERROR);
|
||||
xEventGroupSetBits(event_group_handle_, ML307_TCP_CONNECTED);
|
||||
} else {
|
||||
xEventGroupSetBits(event_group_handle_, ML307_TCP_ERROR);
|
||||
}
|
||||
}
|
||||
} else if (command == "MIPCLOSE" && arguments.size() == 1) {
|
||||
if (arguments[0].int_value == tcp_id_) {
|
||||
instance_active_ = false;
|
||||
xEventGroupSetBits(event_group_handle_, ML307_TCP_DISCONNECTED);
|
||||
}
|
||||
} else if (command == "MIPSEND" && arguments.size() == 2) {
|
||||
if (arguments[0].int_value == tcp_id_) {
|
||||
xEventGroupSetBits(event_group_handle_, ML307_TCP_SEND_COMPLETE);
|
||||
}
|
||||
} else if (command == "MIPURC" && arguments.size() >= 3) {
|
||||
if (arguments[1].int_value == tcp_id_) {
|
||||
if (arguments[0].string_value == "rtcp") {
|
||||
if (connected_ && stream_callback_) {
|
||||
stream_callback_(at_uart_->DecodeHex(arguments[3].string_value));
|
||||
}
|
||||
} else if (arguments[0].string_value == "disconn") {
|
||||
if (connected_) {
|
||||
connected_ = false;
|
||||
if (disconnect_callback_) {
|
||||
disconnect_callback_();
|
||||
}
|
||||
}
|
||||
instance_active_ = false;
|
||||
xEventGroupSetBits(event_group_handle_, ML307_TCP_DISCONNECTED);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unknown MIPURC command: %s", arguments[0].string_value.c_str());
|
||||
}
|
||||
}
|
||||
} else if (command == "MIPSTATE" && arguments.size() >= 5) {
|
||||
if (arguments[0].int_value == tcp_id_) {
|
||||
connected_ = arguments[4].string_value == "CONNECTED";
|
||||
instance_active_ = arguments[4].string_value != "INITIAL";
|
||||
xEventGroupSetBits(event_group_handle_, ML307_TCP_INITIALIZED);
|
||||
}
|
||||
} else if (command == "FIFO_OVERFLOW") {
|
||||
xEventGroupSetBits(event_group_handle_, ML307_TCP_ERROR);
|
||||
Disconnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ml307Tcp::~Ml307Tcp() {
|
||||
Disconnect();
|
||||
at_uart_->UnregisterUrcCallback(urc_callback_it_);
|
||||
if (event_group_handle_) {
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool Ml307Tcp::Connect(const std::string& host, int port) {
|
||||
// Clear bits
|
||||
xEventGroupClearBits(event_group_handle_, ML307_TCP_CONNECTED | ML307_TCP_DISCONNECTED | ML307_TCP_ERROR);
|
||||
|
||||
// 检查这个 id 是否已经连接
|
||||
std::string command = "AT+MIPSTATE=" + std::to_string(tcp_id_);
|
||||
at_uart_->SendCommand(command);
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, ML307_TCP_INITIALIZED, pdTRUE, pdFALSE, pdMS_TO_TICKS(TCP_CONNECT_TIMEOUT_MS));
|
||||
if (!(bits & ML307_TCP_INITIALIZED)) {
|
||||
ESP_LOGE(TAG, "Failed to initialize TCP connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 断开之前的连接
|
||||
if (instance_active_) {
|
||||
command = "AT+MIPCLOSE=" + std::to_string(tcp_id_);
|
||||
if (at_uart_->SendCommand(command)) {
|
||||
// 等待断开完成
|
||||
xEventGroupWaitBits(event_group_handle_, ML307_TCP_DISCONNECTED, pdTRUE, pdFALSE, pdMS_TO_TICKS(TCP_CONNECT_TIMEOUT_MS));
|
||||
}
|
||||
}
|
||||
|
||||
// 配置SSL(子类可以重写)
|
||||
if (!ConfigureSsl(port)) {
|
||||
ESP_LOGE(TAG, "Failed to configure SSL");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用 HEX 编码
|
||||
command = "AT+MIPCFG=\"encoding\"," + std::to_string(tcp_id_) + ",1,1";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to set HEX encoding");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 打开 TCP 连接
|
||||
command = "AT+MIPOPEN=" + std::to_string(tcp_id_) + ",\"TCP\",\"" + host + "\"," + std::to_string(port) + ",,0";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to open TCP connection, error=%d", at_uart_->GetCmeErrorCode());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 等待连接完成
|
||||
bits = xEventGroupWaitBits(event_group_handle_, ML307_TCP_CONNECTED | ML307_TCP_ERROR, pdTRUE, pdFALSE, TCP_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||
if (bits & ML307_TCP_ERROR) {
|
||||
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Ml307Tcp::Disconnect() {
|
||||
if (!instance_active_) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string command = "AT+MIPCLOSE=" + std::to_string(tcp_id_);
|
||||
if (at_uart_->SendCommand(command)) {
|
||||
xEventGroupWaitBits(event_group_handle_, ML307_TCP_DISCONNECTED, pdTRUE, pdFALSE, pdMS_TO_TICKS(TCP_CONNECT_TIMEOUT_MS));
|
||||
}
|
||||
|
||||
if (connected_) {
|
||||
connected_ = false;
|
||||
if (disconnect_callback_) {
|
||||
disconnect_callback_();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Ml307Tcp::ConfigureSsl(int port) {
|
||||
std::string command = "AT+MIPCFG=\"ssl\"," + std::to_string(tcp_id_) + ",0,0";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to set SSL configuration");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int Ml307Tcp::Send(const std::string& data) {
|
||||
const size_t MAX_PACKET_SIZE = 1460 / 2;
|
||||
size_t total_sent = 0;
|
||||
|
||||
if (!connected_) {
|
||||
ESP_LOGE(TAG, "Not connected");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 在循环外预先分配command
|
||||
std::string command;
|
||||
command.reserve(32 + MAX_PACKET_SIZE * 2); // 预分配最大可能需要的空间
|
||||
|
||||
while (total_sent < data.size()) {
|
||||
size_t chunk_size = std::min(data.size() - total_sent, MAX_PACKET_SIZE);
|
||||
|
||||
// 重置command并构建新的命令,利用预分配的容量
|
||||
command.clear();
|
||||
command += "AT+MIPSEND=";
|
||||
command += std::to_string(tcp_id_);
|
||||
command += ",";
|
||||
command += std::to_string(chunk_size);
|
||||
command += ",";
|
||||
|
||||
// 直接在command字符串上进行十六进制编码
|
||||
at_uart_->EncodeHexAppend(command, data.data() + total_sent, chunk_size);
|
||||
command += "\r\n";
|
||||
|
||||
if (!at_uart_->SendCommand(command, 100, false)) {
|
||||
ESP_LOGE(TAG, "Failed to send data chunk");
|
||||
Disconnect();
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, ML307_TCP_SEND_COMPLETE, pdTRUE, pdFALSE, pdMS_TO_TICKS(TCP_CONNECT_TIMEOUT_MS));
|
||||
if (!(bits & ML307_TCP_SEND_COMPLETE)) {
|
||||
ESP_LOGE(TAG, "No send confirmation received");
|
||||
return -1;
|
||||
}
|
||||
|
||||
total_sent += chunk_size;
|
||||
}
|
||||
return data.size();
|
||||
}
|
||||
39
managed_components/78__esp-ml307/src/ml307/ml307_tcp.h
Normal file
39
managed_components/78__esp-ml307/src/ml307/ml307_tcp.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#ifndef ML307_TCP_H
|
||||
#define ML307_TCP_H
|
||||
|
||||
#include "tcp.h"
|
||||
#include "at_uart.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <string>
|
||||
|
||||
#define ML307_TCP_CONNECTED BIT0
|
||||
#define ML307_TCP_DISCONNECTED BIT1
|
||||
#define ML307_TCP_ERROR BIT2
|
||||
#define ML307_TCP_SEND_COMPLETE BIT4
|
||||
#define ML307_TCP_INITIALIZED BIT5
|
||||
|
||||
#define TCP_CONNECT_TIMEOUT_MS 10000
|
||||
|
||||
class Ml307Tcp : public Tcp {
|
||||
public:
|
||||
Ml307Tcp(std::shared_ptr<AtUart> at_uart, int tcp_id);
|
||||
virtual ~Ml307Tcp();
|
||||
|
||||
bool Connect(const std::string& host, int port) override;
|
||||
void Disconnect() override;
|
||||
int Send(const std::string& data) override;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<AtUart> at_uart_;
|
||||
int tcp_id_;
|
||||
bool instance_active_ = false;
|
||||
EventGroupHandle_t event_group_handle_;
|
||||
std::list<UrcCallback>::iterator urc_callback_it_;
|
||||
|
||||
// 虚函数允许子类自定义SSL配置
|
||||
virtual bool ConfigureSsl(int port);
|
||||
};
|
||||
|
||||
#endif // ML307_TCP_H
|
||||
152
managed_components/78__esp-ml307/src/ml307/ml307_udp.cc
Normal file
152
managed_components/78__esp-ml307/src/ml307/ml307_udp.cc
Normal file
@@ -0,0 +1,152 @@
|
||||
#include "ml307_udp.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "Ml307Udp"
|
||||
|
||||
|
||||
Ml307Udp::Ml307Udp(std::shared_ptr<AtUart> at_uart, int udp_id) : at_uart_(at_uart), udp_id_(udp_id) {
|
||||
event_group_handle_ = xEventGroupCreate();
|
||||
|
||||
urc_callback_it_ = at_uart_->RegisterUrcCallback([this](const std::string& command, const std::vector<AtArgumentValue>& arguments) {
|
||||
if (command == "MIPOPEN" && arguments.size() == 2) {
|
||||
if (arguments[0].int_value == udp_id_) {
|
||||
connected_ = arguments[1].int_value == 0;
|
||||
if (connected_) {
|
||||
instance_active_ = true;
|
||||
xEventGroupClearBits(event_group_handle_, ML307_UDP_DISCONNECTED | ML307_UDP_ERROR);
|
||||
xEventGroupSetBits(event_group_handle_, ML307_UDP_CONNECTED);
|
||||
} else {
|
||||
xEventGroupSetBits(event_group_handle_, ML307_UDP_ERROR);
|
||||
}
|
||||
}
|
||||
} else if (command == "MIPCLOSE" && arguments.size() == 1) {
|
||||
if (arguments[0].int_value == udp_id_) {
|
||||
instance_active_ = false;
|
||||
xEventGroupSetBits(event_group_handle_, ML307_UDP_DISCONNECTED);
|
||||
}
|
||||
} else if (command == "MIPSEND" && arguments.size() == 2) {
|
||||
if (arguments[0].int_value == udp_id_) {
|
||||
xEventGroupSetBits(event_group_handle_, ML307_UDP_SEND_COMPLETE);
|
||||
}
|
||||
} else if (command == "MIPURC" && arguments.size() == 4) {
|
||||
if (arguments[1].int_value == udp_id_) {
|
||||
if (arguments[0].string_value == "rudp") {
|
||||
if (connected_ && message_callback_) {
|
||||
message_callback_(at_uart_->DecodeHex(arguments[3].string_value));
|
||||
}
|
||||
} else if (arguments[0].string_value == "disconn") {
|
||||
connected_ = false;
|
||||
instance_active_ = false;
|
||||
xEventGroupSetBits(event_group_handle_, ML307_UDP_DISCONNECTED);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unknown MIPURC command: %s", arguments[0].string_value.c_str());
|
||||
}
|
||||
}
|
||||
} else if (command == "MIPSTATE" && arguments.size() == 5) {
|
||||
if (arguments[0].int_value == udp_id_) {
|
||||
connected_ = arguments[4].string_value == "CONNECTED";
|
||||
instance_active_ = arguments[4].string_value != "INITIAL";
|
||||
xEventGroupSetBits(event_group_handle_, ML307_UDP_INITIALIZED);
|
||||
}
|
||||
} else if (command == "FIFO_OVERFLOW") {
|
||||
xEventGroupSetBits(event_group_handle_, ML307_UDP_ERROR);
|
||||
Disconnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ml307Udp::~Ml307Udp() {
|
||||
Disconnect();
|
||||
at_uart_->UnregisterUrcCallback(urc_callback_it_);
|
||||
if (event_group_handle_) {
|
||||
vEventGroupDelete(event_group_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool Ml307Udp::Connect(const std::string& host, int port) {
|
||||
// Clear bits
|
||||
xEventGroupClearBits(event_group_handle_, ML307_UDP_CONNECTED | ML307_UDP_DISCONNECTED | ML307_UDP_ERROR);
|
||||
|
||||
// 检查这个 id 是否已经连接
|
||||
std::string command = "AT+MIPSTATE=" + std::to_string(udp_id_);
|
||||
at_uart_->SendCommand(command);
|
||||
auto bits = xEventGroupWaitBits(event_group_handle_, ML307_UDP_INITIALIZED, pdTRUE, pdFALSE, pdMS_TO_TICKS(UDP_CONNECT_TIMEOUT_MS));
|
||||
if (!(bits & ML307_UDP_INITIALIZED)) {
|
||||
ESP_LOGE(TAG, "Failed to initialize TCP connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 断开之前的连接
|
||||
if (instance_active_) {
|
||||
command = "AT+MIPCLOSE=" + std::to_string(udp_id_);
|
||||
if (at_uart_->SendCommand(command)) {
|
||||
// 等待断开完成
|
||||
xEventGroupWaitBits(event_group_handle_, ML307_UDP_DISCONNECTED, pdTRUE, pdFALSE, pdMS_TO_TICKS(UDP_CONNECT_TIMEOUT_MS));
|
||||
}
|
||||
}
|
||||
|
||||
// 使用 HEX 编码
|
||||
command = "AT+MIPCFG=\"encoding\"," + std::to_string(udp_id_) + ",1,1";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to set HEX encoding");
|
||||
return false;
|
||||
}
|
||||
command = "AT+MIPCFG=\"ssl\"," + std::to_string(udp_id_) + ",0,0";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to set SSL configuration");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 打开 UDP 连接
|
||||
command = "AT+MIPOPEN=" + std::to_string(udp_id_) + ",\"UDP\",\"" + host + "\"," + std::to_string(port) + ",,0";
|
||||
if (!at_uart_->SendCommand(command)) {
|
||||
ESP_LOGE(TAG, "Failed to open UDP connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 等待连接完成
|
||||
bits = xEventGroupWaitBits(event_group_handle_, ML307_UDP_CONNECTED | ML307_UDP_ERROR, pdTRUE, pdFALSE, UDP_CONNECT_TIMEOUT_MS / portTICK_PERIOD_MS);
|
||||
if (bits & ML307_UDP_ERROR) {
|
||||
ESP_LOGE(TAG, "Failed to connect to %s:%d", host.c_str(), port);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void Ml307Udp::Disconnect() {
|
||||
if (!instance_active_) {
|
||||
return;
|
||||
}
|
||||
|
||||
at_uart_->SendCommand("AT+MIPCLOSE=" + std::to_string(udp_id_));
|
||||
connected_ = false;
|
||||
}
|
||||
|
||||
int Ml307Udp::Send(const std::string& data) {
|
||||
const size_t MAX_PACKET_SIZE = 1460 / 2;
|
||||
|
||||
if (!connected_) {
|
||||
ESP_LOGE(TAG, "Not connected");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (data.size() > MAX_PACKET_SIZE) {
|
||||
ESP_LOGE(TAG, "Data chunk exceeds maximum limit");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 在循环外预先分配command
|
||||
std::string command = "AT+MIPSEND=" + std::to_string(udp_id_) + "," + std::to_string(data.size()) + ",";
|
||||
|
||||
// 直接在command字符串上进行十六进制编码
|
||||
at_uart_->EncodeHexAppend(command, data.data(), data.size());
|
||||
command += "\r\n";
|
||||
|
||||
if (!at_uart_->SendCommand(command, 100, false)) {
|
||||
ESP_LOGE(TAG, "Failed to send data chunk");
|
||||
return -1;
|
||||
}
|
||||
return data.size();
|
||||
}
|
||||
36
managed_components/78__esp-ml307/src/ml307/ml307_udp.h
Normal file
36
managed_components/78__esp-ml307/src/ml307/ml307_udp.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef ML307_UDP_H
|
||||
#define ML307_UDP_H
|
||||
|
||||
#include "udp.h"
|
||||
#include "at_uart.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
|
||||
#define ML307_UDP_CONNECTED BIT0
|
||||
#define ML307_UDP_DISCONNECTED BIT1
|
||||
#define ML307_UDP_ERROR BIT2
|
||||
#define ML307_UDP_RECEIVE BIT3
|
||||
#define ML307_UDP_SEND_COMPLETE BIT4
|
||||
#define ML307_UDP_INITIALIZED BIT5
|
||||
|
||||
#define UDP_CONNECT_TIMEOUT_MS 10000
|
||||
|
||||
class Ml307Udp : public Udp {
|
||||
public:
|
||||
Ml307Udp(std::shared_ptr<AtUart> at_uart, int udp_id);
|
||||
~Ml307Udp();
|
||||
|
||||
bool Connect(const std::string& host, int port) override;
|
||||
void Disconnect() override;
|
||||
int Send(const std::string& data) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<AtUart> at_uart_;
|
||||
int udp_id_;
|
||||
bool instance_active_ = false;
|
||||
EventGroupHandle_t event_group_handle_;
|
||||
std::list<UrcCallback>::iterator urc_callback_it_;
|
||||
};
|
||||
|
||||
#endif // ML307_UDP_H
|
||||
436
managed_components/78__esp-ml307/src/web_socket.cc
Normal file
436
managed_components/78__esp-ml307/src/web_socket.cc
Normal file
@@ -0,0 +1,436 @@
|
||||
#include "web_socket.h"
|
||||
#include "network_interface.h"
|
||||
#include <esp_log.h>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <esp_pthread.h>
|
||||
|
||||
|
||||
#define TAG "WebSocket"
|
||||
|
||||
static std::string base64_encode(const unsigned char *data, size_t len) {
|
||||
const char *base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
std::string encoded;
|
||||
unsigned char char_array_3[3];
|
||||
unsigned char char_array_4[4];
|
||||
|
||||
size_t i = 0;
|
||||
while (i < len) {
|
||||
size_t chunk_size = std::min((size_t)3, len - i);
|
||||
|
||||
for (size_t j = 0; j < 3; j++) {
|
||||
char_array_3[j] = (j < chunk_size) ? data[i + j] : 0;
|
||||
}
|
||||
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
|
||||
for (size_t j = 0; j < 4; j++) {
|
||||
if (j <= chunk_size) {
|
||||
encoded.push_back(base64_chars[char_array_4[j]]);
|
||||
} else {
|
||||
encoded.push_back('=');
|
||||
}
|
||||
}
|
||||
|
||||
i += chunk_size;
|
||||
}
|
||||
return encoded;
|
||||
}
|
||||
|
||||
|
||||
WebSocket::WebSocket(NetworkInterface* network, int connect_id) : network_(network), connect_id_(connect_id) {
|
||||
handshake_event_group_ = xEventGroupCreate();
|
||||
}
|
||||
|
||||
WebSocket::~WebSocket() {
|
||||
if (connected_) {
|
||||
tcp_->Disconnect();
|
||||
}
|
||||
if (handshake_event_group_) {
|
||||
vEventGroupDelete(handshake_event_group_);
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocket::SetHeader(const char* key, const char* value) {
|
||||
headers_[key] = value;
|
||||
}
|
||||
|
||||
void WebSocket::SetReceiveBufferSize(size_t size) {
|
||||
receive_buffer_size_ = size;
|
||||
}
|
||||
|
||||
bool WebSocket::IsConnected() const {
|
||||
return connected_;
|
||||
}
|
||||
|
||||
bool WebSocket::Connect(const char* uri) {
|
||||
std::string uri_str(uri);
|
||||
std::string protocol, host, port, path;
|
||||
size_t pos = 0;
|
||||
size_t next_pos = 0;
|
||||
|
||||
// 解析协议
|
||||
next_pos = uri_str.find("://");
|
||||
if (next_pos == std::string::npos) {
|
||||
ESP_LOGE(TAG, "Invalid URI format");
|
||||
return false;
|
||||
}
|
||||
protocol = uri_str.substr(0, next_pos);
|
||||
pos = next_pos + 3;
|
||||
|
||||
// 解析主机
|
||||
next_pos = uri_str.find(':', pos);
|
||||
if (next_pos == std::string::npos) {
|
||||
next_pos = uri_str.find('/', pos);
|
||||
if (next_pos == std::string::npos) {
|
||||
host = uri_str.substr(pos);
|
||||
path = "/";
|
||||
} else {
|
||||
host = uri_str.substr(pos, next_pos - pos);
|
||||
path = uri_str.substr(next_pos);
|
||||
}
|
||||
port = (protocol == "wss") ? "443" : "80";
|
||||
} else {
|
||||
host = uri_str.substr(pos, next_pos - pos);
|
||||
pos = next_pos + 1;
|
||||
|
||||
// 解析端口
|
||||
next_pos = uri_str.find('/', pos);
|
||||
if (next_pos == std::string::npos) {
|
||||
port = uri_str.substr(pos);
|
||||
path = "/";
|
||||
} else {
|
||||
port = uri_str.substr(pos, next_pos - pos);
|
||||
path = uri_str.substr(next_pos);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Connecting to %s://%s:%s%s", protocol.c_str(), host.c_str(), port.c_str(), path.c_str());
|
||||
|
||||
// 设置 WebSocket 特定的头部
|
||||
SetHeader("Upgrade", "websocket");
|
||||
SetHeader("Connection", "Upgrade");
|
||||
SetHeader("Sec-WebSocket-Version", "13");
|
||||
|
||||
// 生成随机的 Sec-WebSocket-Key
|
||||
char key[25];
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
key[i] = rand() % 256;
|
||||
}
|
||||
std::string base64_key = base64_encode(reinterpret_cast<const unsigned char*>(key), 16);
|
||||
SetHeader("Sec-WebSocket-Key", base64_key.c_str());
|
||||
|
||||
if (protocol == "wss" || protocol == "https") {
|
||||
tcp_ = network_->CreateSsl(connect_id_);
|
||||
} else {
|
||||
tcp_ = network_->CreateTcp(connect_id_);
|
||||
}
|
||||
|
||||
connected_ = false;
|
||||
// 使用 tcp 建立连接
|
||||
if (!tcp_->Connect(host, std::stoi(port))) {
|
||||
ESP_LOGE(TAG, "Failed to connect to server");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 发送 WebSocket 握手请求
|
||||
std::string request = "GET " + path + " HTTP/1.1\r\n";
|
||||
if (headers_.find("Host") == headers_.end()) {
|
||||
request += "Host: " + host + "\r\n";
|
||||
}
|
||||
for (const auto& header : headers_) {
|
||||
request += header.first + ": " + header.second + "\r\n";
|
||||
}
|
||||
request += "\r\n";
|
||||
|
||||
if (tcp_->Send(request) < 0) {
|
||||
ESP_LOGE(TAG, "Failed to send WebSocket handshake request");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 清除事件位
|
||||
xEventGroupClearBits(handshake_event_group_, HANDSHAKE_SUCCESS_BIT | HANDSHAKE_FAILED_BIT);
|
||||
|
||||
// 设置数据接收回调来处理握手和后续的WebSocket帧
|
||||
tcp_->OnStream([this](const std::string& data) {
|
||||
this->OnTcpData(data);
|
||||
});
|
||||
|
||||
// 设置断开连接回调
|
||||
tcp_->OnDisconnected([this]() {
|
||||
if (connected_) {
|
||||
connected_ = false;
|
||||
if (on_disconnected_) {
|
||||
on_disconnected_();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 等待握手完成,超时时间10秒
|
||||
EventBits_t bits = xEventGroupWaitBits(
|
||||
handshake_event_group_,
|
||||
HANDSHAKE_SUCCESS_BIT | HANDSHAKE_FAILED_BIT,
|
||||
pdFALSE, // 不清除事件位
|
||||
pdFALSE, // 等待任意一个事件位
|
||||
pdMS_TO_TICKS(10000) // 10秒超时
|
||||
);
|
||||
|
||||
if (bits & HANDSHAKE_SUCCESS_BIT) {
|
||||
connected_ = true;
|
||||
if (on_connected_) {
|
||||
on_connected_();
|
||||
}
|
||||
return true;
|
||||
} else if (bits & HANDSHAKE_FAILED_BIT) {
|
||||
ESP_LOGE(TAG, "WebSocket handshake failed");
|
||||
if (on_error_) {
|
||||
on_error_(-1);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "WebSocket handshake timeout");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool WebSocket::Send(const std::string& data) {
|
||||
return Send(data.data(), data.size(), false);
|
||||
}
|
||||
|
||||
bool WebSocket::Send(const void* data, size_t len, bool binary, bool fin) {
|
||||
if (len > 65535) {
|
||||
ESP_LOGE(TAG, "Data too large, maximum supported size is 65535 bytes");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string frame;
|
||||
frame.reserve(len + 8); // 最大可能的帧大小(2字节帧头 + 2字节长度 + 4字节mask)
|
||||
|
||||
// 第一个字节:FIN 位 + 操作码
|
||||
uint8_t first_byte = (fin ? 0x80 : 0x00);
|
||||
if (binary) {
|
||||
first_byte |= 0x02; // 二进制帧
|
||||
} else if (!continuation_) {
|
||||
first_byte |= 0x01; // 文本帧
|
||||
} // 否则,操作码为0(延续帧)
|
||||
|
||||
frame.push_back(static_cast<char>(first_byte));
|
||||
|
||||
// 第二个字节:MASK 位 + 有效载荷长度
|
||||
if (len < 126) {
|
||||
frame.push_back(static_cast<char>(0x80 | len)); // 设置MASK位
|
||||
} else {
|
||||
frame.push_back(static_cast<char>(0x80 | 126)); // 设置MASK位
|
||||
frame.push_back(static_cast<char>((len >> 8) & 0xFF));
|
||||
frame.push_back(static_cast<char>(len & 0xFF));
|
||||
}
|
||||
|
||||
// 生成随机的4字节mask
|
||||
uint8_t mask[4];
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
mask[i] = rand() & 0xFF;
|
||||
}
|
||||
frame.append(reinterpret_cast<const char*>(mask), 4);
|
||||
|
||||
// 添加并mask处理有效载荷
|
||||
const uint8_t* payload = static_cast<const uint8_t*>(data);
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
frame.push_back(static_cast<char>(payload[i] ^ mask[i % 4]));
|
||||
}
|
||||
|
||||
// 更新continuation_状态
|
||||
continuation_ = !fin;
|
||||
|
||||
// 发送帧
|
||||
return tcp_->Send(frame) >= 0;
|
||||
}
|
||||
|
||||
void WebSocket::Ping() {
|
||||
SendControlFrame(0x9, nullptr, 0);
|
||||
}
|
||||
|
||||
void WebSocket::Close() {
|
||||
if (connected_) {
|
||||
SendControlFrame(0x8, nullptr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocket::OnConnected(std::function<void()> callback) {
|
||||
on_connected_ = callback;
|
||||
}
|
||||
|
||||
void WebSocket::OnDisconnected(std::function<void()> callback) {
|
||||
on_disconnected_ = callback;
|
||||
}
|
||||
|
||||
void WebSocket::OnData(std::function<void(const char*, size_t, bool binary)> callback) {
|
||||
on_data_ = callback;
|
||||
}
|
||||
|
||||
void WebSocket::OnError(std::function<void(int)> callback) {
|
||||
on_error_ = callback;
|
||||
}
|
||||
|
||||
void WebSocket::OnTcpData(const std::string& data) {
|
||||
// 将新数据追加到接收缓冲区
|
||||
receive_buffer_.append(data);
|
||||
|
||||
if (!handshake_completed_) {
|
||||
// 检查握手响应
|
||||
size_t pos = receive_buffer_.find("\r\n\r\n");
|
||||
if (pos != std::string::npos) {
|
||||
std::string handshake_response = receive_buffer_.substr(0, pos + 4);
|
||||
receive_buffer_ = receive_buffer_.substr(pos + 4);
|
||||
|
||||
if (handshake_response.find("HTTP/1.1 101") != std::string::npos) {
|
||||
handshake_completed_ = true;
|
||||
// 设置握手成功事件
|
||||
xEventGroupSetBits(handshake_event_group_, HANDSHAKE_SUCCESS_BIT);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "WebSocket handshake failed");
|
||||
// 设置握手失败事件
|
||||
xEventGroupSetBits(handshake_event_group_, HANDSHAKE_FAILED_BIT);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// 握手响应未完整接收
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理WebSocket帧
|
||||
static std::vector<char> current_message;
|
||||
static bool is_fragmented = false;
|
||||
static bool is_binary = false;
|
||||
|
||||
size_t buffer_offset = 0;
|
||||
const char* buffer = receive_buffer_.c_str();
|
||||
size_t buffer_size = receive_buffer_.size();
|
||||
|
||||
while (buffer_offset < buffer_size) {
|
||||
if (buffer_size - buffer_offset < 2) break; // 需要更多数据
|
||||
|
||||
uint8_t opcode = buffer[buffer_offset] & 0x0F;
|
||||
bool fin = (buffer[buffer_offset] & 0x80) != 0;
|
||||
uint8_t mask = buffer[buffer_offset + 1] & 0x80;
|
||||
uint64_t payload_length = buffer[buffer_offset + 1] & 0x7F;
|
||||
|
||||
size_t header_length = 2;
|
||||
if (payload_length == 126) {
|
||||
if (buffer_size - buffer_offset < 4) break; // 需要更多数据
|
||||
payload_length = (buffer[buffer_offset + 2] << 8) | buffer[buffer_offset + 3];
|
||||
header_length += 2;
|
||||
} else if (payload_length == 127) {
|
||||
if (buffer_size - buffer_offset < 10) break; // 需要更多数据
|
||||
payload_length = 0;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
payload_length = (payload_length << 8) | buffer[buffer_offset + 2 + i];
|
||||
}
|
||||
header_length += 8;
|
||||
}
|
||||
|
||||
uint8_t mask_key[4] = {0};
|
||||
if (mask) {
|
||||
if (buffer_size - buffer_offset < header_length + 4) break; // 需要更多数据
|
||||
memcpy(mask_key, buffer + buffer_offset + header_length, 4);
|
||||
header_length += 4;
|
||||
}
|
||||
|
||||
if (buffer_size - buffer_offset < header_length + payload_length) break; // 需要更多数据
|
||||
|
||||
// 解码有效载荷
|
||||
std::vector<char> payload(payload_length);
|
||||
memcpy(payload.data(), buffer + buffer_offset + header_length, payload_length);
|
||||
if (mask) {
|
||||
for (size_t i = 0; i < payload_length; ++i) {
|
||||
payload[i] ^= mask_key[i % 4];
|
||||
}
|
||||
}
|
||||
|
||||
// 处理帧
|
||||
switch (opcode) {
|
||||
case 0x0: // 延续帧
|
||||
case 0x1: // 文本帧
|
||||
case 0x2: // 二进制帧
|
||||
if (opcode != 0x0 && is_fragmented) {
|
||||
ESP_LOGE(TAG, "Received new message frame while still fragmenting");
|
||||
break;
|
||||
}
|
||||
if (opcode != 0x0) {
|
||||
is_fragmented = !fin;
|
||||
is_binary = (opcode == 0x2);
|
||||
current_message.clear();
|
||||
}
|
||||
current_message.insert(current_message.end(), payload.begin(), payload.end());
|
||||
if (fin) {
|
||||
if (on_data_) {
|
||||
on_data_(current_message.data(), current_message.size(), is_binary);
|
||||
}
|
||||
current_message.clear();
|
||||
is_fragmented = false;
|
||||
}
|
||||
break;
|
||||
case 0x8: // 关闭帧
|
||||
connected_ = false;
|
||||
if (on_disconnected_) {
|
||||
on_disconnected_();
|
||||
}
|
||||
break;
|
||||
case 0x9: // Ping
|
||||
// 发送 Pong
|
||||
SendControlFrame(0xA, payload.data(), payload_length);
|
||||
break;
|
||||
case 0xA: // Pong
|
||||
// 可以在这里处理 Pong
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Unknown opcode: %d", opcode);
|
||||
break;
|
||||
}
|
||||
|
||||
buffer_offset += header_length + payload_length;
|
||||
}
|
||||
|
||||
// 保留未处理的数据
|
||||
if (buffer_offset > 0) {
|
||||
receive_buffer_ = receive_buffer_.substr(buffer_offset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool WebSocket::SendControlFrame(uint8_t opcode, const void* data, size_t len) {
|
||||
if (len > 125) {
|
||||
ESP_LOGE(TAG, "控制帧有效载荷过大");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string frame;
|
||||
frame.reserve(len + 6); // 帧头 + 掩码 + 有效载荷
|
||||
|
||||
// 第一个字节:FIN 位 + 操作码
|
||||
frame.push_back(static_cast<char>(0x80 | opcode));
|
||||
|
||||
// 第二个字节:MASK 位 + 有效载荷长度
|
||||
frame.push_back(static_cast<char>(0x80 | len));
|
||||
|
||||
// 生成随机的4字节掩码
|
||||
uint8_t mask[4];
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
mask[i] = rand() & 0xFF;
|
||||
}
|
||||
frame.append(reinterpret_cast<const char*>(mask), 4);
|
||||
|
||||
// 添加并掩码处理有效载荷
|
||||
const uint8_t* payload = static_cast<const uint8_t*>(data);
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
frame.push_back(static_cast<char>(payload[i] ^ mask[i % 4]));
|
||||
}
|
||||
|
||||
// 发送帧
|
||||
return tcp_->Send(frame) >= 0;
|
||||
}
|
||||
Reference in New Issue
Block a user