From 077f1c4eccb97cec5bf0553ccbcf07570e6f69f6 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Mon, 7 Jul 2025 17:12:29 +0200 Subject: [PATCH 1/2] fix(modem): Add support for ESP-AT based tcp-client example fix(modem): Fixup tcp-client example with esp-at fix(modem): Use nicer way to override modules fix(modem): Make tcp-client example support multiple connections fix(modem): Add link id to tcp API fix(modem): Use link-id from responder class fix(modem): WIP for multiple conn fix(modem): Support for TCP-TRANSPORT mode fix(modem): Cleanup CI fix(modem): Fix build step fix(modem): Make ssl connection working with esp-at fix(modem): Add support for BG96 and SIM7600 --- .../main/command/sock_dce.cpp | 80 +++++++-- .../main/command/sock_dce.hpp | 21 +++ .../main/generate/sock_dce.cpp | 80 +++++++-- .../main/generate/sock_dce.hpp | 21 +++ .../modem_tcp_client/main/modem_client.cpp | 73 ++++++-- .../main/sock_commands_bg96.cpp | 49 ++++-- .../main/sock_commands_espat.cpp | 156 +++++++++++------- .../main/sock_commands_sim7600.cpp | 47 +++--- 8 files changed, 392 insertions(+), 135 deletions(-) diff --git a/components/esp_modem/examples/modem_tcp_client/main/command/sock_dce.cpp b/components/esp_modem/examples/modem_tcp_client/main/command/sock_dce.cpp index d6d597b1c9..19375a28b7 100644 --- a/components/esp_modem/examples/modem_tcp_client/main/command/sock_dce.cpp +++ b/components/esp_modem/examples/modem_tcp_client/main/command/sock_dce.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include #include #include #include "esp_vfs.h" @@ -15,6 +16,28 @@ namespace sock_dce { constexpr auto const *TAG = "sock_dce"; +// Definition of the static member variables +std::vector DCE::dce_list{}; +bool DCE::network_init = false; +int Responder::s_link_id = 0; +SemaphoreHandle_t Responder::s_dte_mutex{}; + +// Constructor - add this DCE instance to the static list +DCE::DCE(std::shared_ptr dte_arg, const esp_modem_dce_config *config) + : Module(std::move(dte_arg), config) +{ + dce_list.push_back(this); +} + +// Destructor - remove this DCE instance from the static list +DCE::~DCE() +{ + auto it = std::find(dce_list.begin(), dce_list.end(), this); + if (it != dce_list.end()) { + dce_list.erase(it); + } +} + bool DCE::perform_sock() { @@ -61,13 +84,24 @@ bool DCE::perform_sock() void DCE::perform_at(uint8_t *data, size_t len) { - ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_VERBOSE); + if (state != status::RECEIVING) { + std::string_view resp_sv((char *)data, len); + at.check_urc(state, resp_sv); + if (state == status::IDLE) { + return; + } + } + ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_INFO); switch (at.process_data(state, data, len)) { case Responder::ret::OK: + ESP_LOGW(TAG, "GIVE data %d", at.link_id); + xSemaphoreGive(at.s_dte_mutex); state = status::IDLE; signal.set(IDLE); return; case Responder::ret::FAIL: + ESP_LOGW(TAG, "GIVE data %d", at.link_id); + xSemaphoreGive(at.s_dte_mutex); state = status::FAILED; signal.set(IDLE); return; @@ -82,10 +116,14 @@ void DCE::perform_at(uint8_t *data, size_t len) std::string_view response((char *)data, len); switch (at.check_async_replies(state, response)) { case Responder::ret::OK: + ESP_LOGW(TAG, "GIVE command %d", at.link_id); + xSemaphoreGive(at.s_dte_mutex); state = status::IDLE; signal.set(IDLE); return; case Responder::ret::FAIL: + ESP_LOGW(TAG, "GIVE command %d", at.link_id); + xSemaphoreGive(at.s_dte_mutex); state = status::FAILED; signal.set(IDLE); return; @@ -131,6 +169,9 @@ bool DCE::at_to_sock() close_sock(); return false; } + ESP_LOGI(TAG, "TAKE RECV %d", at.link_id); + xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); + ESP_LOGE(TAG, "TAKE RECV %d", at.link_id); state = status::RECEIVING; at.start_receiving(at.get_buf_len()); return true; @@ -139,8 +180,8 @@ bool DCE::at_to_sock() bool DCE::sock_to_at() { ESP_LOGD(TAG, "socket read: data available"); - if (!signal.wait(IDLE, 1000)) { - ESP_LOGE(TAG, "Failed to get idle"); + if (!signal.wait(IDLE, 5000)) { + ESP_LOGE(TAG, "Failed to get idle 2"); close_sock(); return false; } @@ -149,6 +190,9 @@ bool DCE::sock_to_at() close_sock(); return false; } + ESP_LOGI(TAG, "TAKE SEND %d", at.link_id); + xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); + ESP_LOGE(TAG, "TAKE SEND %d", at.link_id); state = status::SENDING; int len = ::recv(sock, at.get_buf(), at.get_buf_len(), 0); if (len < 0) { @@ -224,12 +268,17 @@ void DCE::start_listening(int port) bool DCE::connect(std::string host, int port) { - dte->on_read(nullptr); - tcp_close(); - dte->on_read([this](uint8_t *data, size_t len) { - this->perform_at(data, len); - return esp_modem::command_result::TIMEOUT; - }); + data_ready_fd = eventfd(0, EFD_SUPPORT_ISR); + assert(data_ready_fd > 0); + // dte->on_read(nullptr); + // tcp_close(); + // dte->on_read([](uint8_t *data, size_t len) { + // read_callback(data, len); + // return esp_modem::command_result::TIMEOUT; + // }); + ESP_LOGI(TAG, "TAKE CONNECT %d", at.link_id); + xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); + ESP_LOGE(TAG, "TAKE CONNECT %d", at.link_id); if (!at.start_connecting(host, port)) { ESP_LOGE(TAG, "Unable to start connecting"); dte->on_read(nullptr); @@ -241,12 +290,15 @@ bool DCE::connect(std::string host, int port) bool DCE::init() { + if (network_init) { + return true; + } + network_init = true; + Responder::s_dte_mutex = xSemaphoreCreateBinary(); + xSemaphoreGive(at.s_dte_mutex); esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT(); esp_vfs_eventfd_register(&config); - data_ready_fd = eventfd(0, EFD_SUPPORT_ISR); - assert(data_ready_fd > 0); - dte->on_read(nullptr); const int retries = 5; int i = 0; @@ -287,6 +339,10 @@ bool DCE::init() esp_modem::Task::Delay(5000); } ESP_LOGI(TAG, "Got IP %s", ip_addr.c_str()); + dte->on_read([](uint8_t *data, size_t len) { + read_callback(data, len); + return esp_modem::command_result::TIMEOUT; + }); return true; } diff --git a/components/esp_modem/examples/modem_tcp_client/main/command/sock_dce.hpp b/components/esp_modem/examples/modem_tcp_client/main/command/sock_dce.hpp index 5add603f0f..47c10329f9 100644 --- a/components/esp_modem/examples/modem_tcp_client/main/command/sock_dce.hpp +++ b/components/esp_modem/examples/modem_tcp_client/main/command/sock_dce.hpp @@ -34,6 +34,7 @@ class Responder { sock(s), data_ready_fd(ready_fd), dte(dte_arg) {} ret process_data(status state, uint8_t *data, size_t len); ret check_async_replies(status state, std::string_view &response); + ret check_urc(status state, std::string_view &response); void start_sending(size_t len); void start_receiving(size_t len); @@ -63,13 +64,17 @@ class Responder { return total_len; } + int link_id{s_link_id++}; + static SemaphoreHandle_t s_dte_mutex; private: + static int s_link_id; static constexpr size_t buffer_size = 512; bool on_read(char *data, size_t len) { #ifndef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT ::send(sock, data, len, 0); + printf("sending %d\n", len); #else ::memcpy(&buffer[actual_read], data, len); actual_read += len; @@ -101,6 +106,8 @@ class Responder { class DCE : public Module { using Module::Module; public: + DCE(std::shared_ptr dte_arg, const esp_modem_dce_config *config); + ~DCE(); /** * @brief Opens network in AT command mode @@ -163,6 +170,9 @@ class DCE : public Module { return 0; } at.clear_offsets(); + ESP_LOGI("TAG", "TAKE RECV %d", at.link_id); + xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); + ESP_LOGE("TAG", "TAKE RECV %d", at.link_id); state = status::RECEIVING; uint64_t data; read(data_ready_fd, &data, sizeof(data)); @@ -184,6 +194,9 @@ class DCE : public Module { if (!wait_to_idle(timeout_ms)) { return -1; } + ESP_LOGI("TAG", "TAKE SEND %d", at.link_id); + xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); + ESP_LOGE("TAG", "TAKE SEND %d", at.link_id); state = status::SENDING; memcpy(at.get_buf(), buffer, len_to_send); ESP_LOG_BUFFER_HEXDUMP("dce", at.get_buf(), len, ESP_LOG_VERBOSE); @@ -224,6 +237,14 @@ class DCE : public Module { } return -1; } + static std::vector dce_list; + static bool network_init; + static void read_callback(uint8_t *data, size_t len) + { + for (auto dce : dce_list) { + dce->perform_at(data, len); + } + } private: esp_modem::SignalGroup signal; void close_sock(); diff --git a/components/esp_modem/examples/modem_tcp_client/main/generate/sock_dce.cpp b/components/esp_modem/examples/modem_tcp_client/main/generate/sock_dce.cpp index 3b4bec8055..f82bbb560f 100644 --- a/components/esp_modem/examples/modem_tcp_client/main/generate/sock_dce.cpp +++ b/components/esp_modem/examples/modem_tcp_client/main/generate/sock_dce.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include #include #include #include "esp_vfs.h" @@ -15,6 +16,28 @@ namespace sock_dce { constexpr auto const *TAG = "sock_dce"; +// Definition of the static member variables +std::vector DCE::dce_list{}; +bool DCE::network_init = false; +int Responder::s_link_id = 0; +SemaphoreHandle_t Responder::s_dte_mutex{}; + +// Constructor - add this DCE instance to the static list +DCE::DCE(std::shared_ptr dte_arg, const esp_modem_dce_config *config) + : Module(std::move(dte_arg), config) +{ + dce_list.push_back(this); +} + +// Destructor - remove this DCE instance from the static list +DCE::~DCE() +{ + auto it = std::find(dce_list.begin(), dce_list.end(), this); + if (it != dce_list.end()) { + dce_list.erase(it); + } +} + bool DCE::perform_sock() { @@ -61,13 +84,24 @@ bool DCE::perform_sock() void DCE::perform_at(uint8_t *data, size_t len) { - ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_VERBOSE); + if (state != status::RECEIVING) { + std::string_view resp_sv((char *)data, len); + at.check_urc(state, resp_sv); + if (state == status::IDLE) { + return; + } + } + ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_INFO); switch (at.process_data(state, data, len)) { case Responder::ret::OK: + ESP_LOGW(TAG, "GIVE data %d", at.link_id); + xSemaphoreGive(at.s_dte_mutex); state = status::IDLE; signal.set(IDLE); return; case Responder::ret::FAIL: + ESP_LOGW(TAG, "GIVE data %d", at.link_id); + xSemaphoreGive(at.s_dte_mutex); state = status::FAILED; signal.set(IDLE); return; @@ -82,10 +116,14 @@ void DCE::perform_at(uint8_t *data, size_t len) std::string_view response((char *)data, len); switch (at.check_async_replies(state, response)) { case Responder::ret::OK: + ESP_LOGW(TAG, "GIVE command %d", at.link_id); + xSemaphoreGive(at.s_dte_mutex); state = status::IDLE; signal.set(IDLE); return; case Responder::ret::FAIL: + ESP_LOGW(TAG, "GIVE command %d", at.link_id); + xSemaphoreGive(at.s_dte_mutex); state = status::FAILED; signal.set(IDLE); return; @@ -131,6 +169,9 @@ bool DCE::at_to_sock() close_sock(); return false; } + ESP_LOGI(TAG, "TAKE RECV %d", at.link_id); + xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); + ESP_LOGE(TAG, "TAKE RECV %d", at.link_id); state = status::RECEIVING; at.start_receiving(at.get_buf_len()); return true; @@ -139,8 +180,8 @@ bool DCE::at_to_sock() bool DCE::sock_to_at() { ESP_LOGD(TAG, "socket read: data available"); - if (!signal.wait(IDLE, 1000)) { - ESP_LOGE(TAG, "Failed to get idle"); + if (!signal.wait(IDLE, 5000)) { + ESP_LOGE(TAG, "Failed to get idle 2"); close_sock(); return false; } @@ -149,6 +190,9 @@ bool DCE::sock_to_at() close_sock(); return false; } + ESP_LOGI(TAG, "TAKE SEND %d", at.link_id); + xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); + ESP_LOGE(TAG, "TAKE SEND %d", at.link_id); state = status::SENDING; int len = ::recv(sock, at.get_buf(), at.get_buf_len(), 0); if (len < 0) { @@ -224,12 +268,17 @@ void DCE::start_listening(int port) bool DCE::connect(std::string host, int port) { - dte->on_read(nullptr); - tcp_close(); - dte->on_read([this](uint8_t *data, size_t len) { - this->perform_at(data, len); - return esp_modem::command_result::TIMEOUT; - }); + data_ready_fd = eventfd(0, EFD_SUPPORT_ISR); + assert(data_ready_fd > 0); + // dte->on_read(nullptr); + // tcp_close(); + // dte->on_read([](uint8_t *data, size_t len) { + // read_callback(data, len); + // return esp_modem::command_result::TIMEOUT; + // }); + ESP_LOGI(TAG, "TAKE CONNECT %d", at.link_id); + xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); + ESP_LOGE(TAG, "TAKE CONNECT %d", at.link_id); if (!at.start_connecting(host, port)) { ESP_LOGE(TAG, "Unable to start connecting"); dte->on_read(nullptr); @@ -241,12 +290,15 @@ bool DCE::connect(std::string host, int port) bool DCE::init() { + if (network_init) { + return true; + } + network_init = true; + Responder::s_dte_mutex = xSemaphoreCreateBinary(); + xSemaphoreGive(at.s_dte_mutex); esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT(); esp_vfs_eventfd_register(&config); - data_ready_fd = eventfd(0, EFD_SUPPORT_ISR); - assert(data_ready_fd > 0); - dte->on_read(nullptr); const int retries = 5; int i = 0; @@ -287,6 +339,10 @@ bool DCE::init() esp_modem::Task::Delay(5000); } ESP_LOGI(TAG, "Got IP %s", ip_addr.c_str()); + dte->on_read([](uint8_t *data, size_t len) { + read_callback(data, len); + return esp_modem::command_result::TIMEOUT; + }); return true; } diff --git a/components/esp_modem/examples/modem_tcp_client/main/generate/sock_dce.hpp b/components/esp_modem/examples/modem_tcp_client/main/generate/sock_dce.hpp index 92e14013f6..246e9c3d28 100644 --- a/components/esp_modem/examples/modem_tcp_client/main/generate/sock_dce.hpp +++ b/components/esp_modem/examples/modem_tcp_client/main/generate/sock_dce.hpp @@ -34,6 +34,7 @@ class Responder { sock(s), data_ready_fd(ready_fd), dte(dte_arg) {} ret process_data(status state, uint8_t *data, size_t len); ret check_async_replies(status state, std::string_view &response); + ret check_urc(status state, std::string_view &response); void start_sending(size_t len); void start_receiving(size_t len); @@ -63,13 +64,17 @@ class Responder { return total_len; } + int link_id{s_link_id++}; + static SemaphoreHandle_t s_dte_mutex; private: + static int s_link_id; static constexpr size_t buffer_size = 512; bool on_read(char *data, size_t len) { #ifndef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT ::send(sock, data, len, 0); + printf("sending %d\n", len); #else ::memcpy(&buffer[actual_read], data, len); actual_read += len; @@ -101,6 +106,8 @@ class Responder { class DCE : public Module { using Module::Module; public: + DCE(std::shared_ptr dte_arg, const esp_modem_dce_config *config); + ~DCE(); // --- ESP-MODEM command module starts here --- #include "esp_modem_command_declare_helper.inc" @@ -141,6 +148,9 @@ esp_modem::return_type name(ESP_MODEM_COMMAND_PARAMS(__VA_ARGS__)); return 0; } at.clear_offsets(); + ESP_LOGI("TAG", "TAKE RECV %d", at.link_id); + xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); + ESP_LOGE("TAG", "TAKE RECV %d", at.link_id); state = status::RECEIVING; uint64_t data; read(data_ready_fd, &data, sizeof(data)); @@ -163,6 +173,9 @@ esp_modem::return_type name(ESP_MODEM_COMMAND_PARAMS(__VA_ARGS__)); if (!wait_to_idle(timeout_ms)) { return -1; } + ESP_LOGI("TAG", "TAKE SEND %d", at.link_id); + xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); + ESP_LOGE("TAG", "TAKE SEND %d", at.link_id); state = status::SENDING; memcpy(at.get_buf(), buffer, len_to_send); ESP_LOG_BUFFER_HEXDUMP("dce", at.get_buf(), len, ESP_LOG_VERBOSE); @@ -204,6 +217,14 @@ esp_modem::return_type name(ESP_MODEM_COMMAND_PARAMS(__VA_ARGS__)); } return -1; } + static std::vector dce_list; + static bool network_init; + static void read_callback(uint8_t *data, size_t len) + { + for (auto dce : dce_list) { + dce->perform_at(data, len); + } + } private: esp_modem::SignalGroup signal; diff --git a/components/esp_modem/examples/modem_tcp_client/main/modem_client.cpp b/components/esp_modem/examples/modem_tcp_client/main/modem_client.cpp index 092ea16a25..f7fd57940e 100644 --- a/components/esp_modem/examples/modem_tcp_client/main/modem_client.cpp +++ b/components/esp_modem/examples/modem_tcp_client/main/modem_client.cpp @@ -23,14 +23,25 @@ #include "tcp_transport_mbedtls.h" #include "tcp_transport_at.h" -#define BROKER_URL "test.mosquitto.org" +#define USE_TLS 0 + +#define BROKER_HOST "test.mosquitto.org" +#if USE_TLS +#define BROKER_SCHEME "mqtts" +#define BROKER_PORT 8883 +#else +#define BROKER_SCHEME "mqtt" #define BROKER_PORT 1883 +#endif +#define BROKER_URL BROKER_SCHEME "://" BROKER_HOST static const char *TAG = "modem_client"; static EventGroupHandle_t event_group = NULL; static const int CONNECT_BIT = BIT0; static const int GOT_DATA_BIT = BIT2; +static const int DCE0_DONE = BIT3; +static const int DCE1_DONE = BIT4; static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) { @@ -73,13 +84,14 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_ } } +static void perform(void* ctx); extern "C" void app_main(void) { - /* Init and register system/core components */ ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); + esp_log_level_set("*", ESP_LOG_INFO); event_group = xEventGroupCreate(); @@ -104,30 +116,62 @@ extern "C" void app_main(void) esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(CONFIG_EXAMPLE_MODEM_APN); /* create the DCE and initialize network manually (using AT commands) */ - auto dce = sock_dce::create(&dce_config, std::move(dte)); + auto dce = sock_dce::create(&dce_config, dte); if (!dce->init()) { ESP_LOGE(TAG, "Failed to setup network"); return; } + xTaskCreate(perform, "perform", 4096, dce.get(), 4, nullptr); + + // vTaskDelay(pdMS_TO_TICKS(5000)); + // vTaskDelay(portMAX_DELAY); + /* create another DCE to serve a new connection */ + auto dce1 = sock_dce::create(&dce_config, dte); + if (!dce1->init()) { + ESP_LOGE(TAG, "Failed to setup network"); + return; + } + xTaskCreate(perform, "perform", 4096, dce1.get(), 4, nullptr); + + xEventGroupWaitBits(event_group, DCE0_DONE | DCE1_DONE, pdFALSE, pdTRUE, portMAX_DELAY); +#ifdef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT + // this example does never keeps both DCEs running and in tcp-transport option + // we don't need a task to run so we exit main and keep DCE's "running" + vTaskDelay(portMAX_DELAY); +#endif +} + +static void perform(void* ctx) +{ + auto dce = static_cast(ctx); + char mqtt_client_id[] = "MQTT_CLIENT_0"; + static int counter = 0; + const int id = counter++; + mqtt_client_id[sizeof(mqtt_client_id) - 2] += id; // assumes a different client id per each thread esp_mqtt_client_config_t mqtt_config = {}; - mqtt_config.broker.address.port = BROKER_PORT; mqtt_config.session.message_retransmit_timeout = 10000; + mqtt_config.credentials.client_id = mqtt_client_id; #ifndef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT - mqtt_config.broker.address.uri = "mqtt://127.0.0.1"; - dce->start_listening(BROKER_PORT); + mqtt_config.broker.address.port = BROKER_PORT + id; + mqtt_config.broker.address.uri = BROKER_SCHEME "://127.0.0.1"; + dce->start_listening(BROKER_PORT + id); #else - mqtt_config.broker.address.uri = "mqtt://" BROKER_URL; - esp_transport_handle_t at = esp_transport_at_init(dce.get()); + mqtt_config.broker.address.port = BROKER_PORT; + mqtt_config.broker.address.uri = BROKER_URL; + esp_transport_handle_t at = esp_transport_at_init(dce); +#if USE_TLS esp_transport_handle_t ssl = esp_transport_tls_init(at); - mqtt_config.network.transport = ssl; -#endif +#else + mqtt_config.network.transport = at; +#endif // USE_TLS +#endif // CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT esp_mqtt_client_handle_t mqtt_client = esp_mqtt_client_init(&mqtt_config); esp_mqtt_client_register_event(mqtt_client, static_cast(ESP_EVENT_ANY_ID), mqtt_event_handler, nullptr); esp_mqtt_client_start(mqtt_client); #ifndef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT - if (!dce->connect(BROKER_URL, BROKER_PORT)) { + if (!dce->connect(BROKER_HOST, BROKER_PORT)) { ESP_LOGE(TAG, "Failed to start DCE"); return; } @@ -141,12 +185,11 @@ extern "C" void app_main(void) ESP_LOGE(TAG, "Failed to reinit network"); return; } - if (!dce->connect(BROKER_URL, BROKER_PORT)) { + if (!dce->connect(BROKER_HOST, BROKER_PORT)) { ESP_LOGI(TAG, "Network reinitialized, retrying"); } } -#else - vTaskDelay(portMAX_DELAY); #endif - + xEventGroupSetBits(event_group, id ? DCE0_DONE : DCE1_DONE); + vTaskDelete(nullptr); } diff --git a/components/esp_modem/examples/modem_tcp_client/main/sock_commands_bg96.cpp b/components/esp_modem/examples/modem_tcp_client/main/sock_commands_bg96.cpp index d5a0dcd782..1916b64553 100644 --- a/components/esp_modem/examples/modem_tcp_client/main/sock_commands_bg96.cpp +++ b/components/esp_modem/examples/modem_tcp_client/main/sock_commands_bg96.cpp @@ -109,17 +109,17 @@ void Responder::start_sending(size_t len) { data_to_send = len; send_stat = 0; - send_cmd("AT+QISEND=0," + std::to_string(len) + "\r"); + send_cmd("AT+QISEND=" + std::to_string(link_id) + "," + std::to_string(len) + "\r"); } void Responder::start_receiving(size_t len) { - send_cmd("AT+QIRD=0," + std::to_string(len) + "\r"); + send_cmd("AT+QIRD=" + std::to_string(link_id) + "," + std::to_string(len) + "\r"); } bool Responder::start_connecting(std::string host, int port) { - send_cmd(R"(AT+QIOPEN=1,0,"TCP",")" + host + "\"," + std::to_string(port) + "\r"); + send_cmd(std::string("AT+QIOPEN=1,") + std::to_string(link_id) + R"(,"TCP",")" + host + "\"," + std::to_string(port) + "\r"); return true; } @@ -130,16 +130,24 @@ Responder::ret Responder::recv(uint8_t *data, size_t len) auto *recv_data = (char *)data; if (data_to_recv == 0) { const std::string_view head = "+QIRD: "; +again: const std::string_view recv_data_view = std::string_view(recv_data, len); auto head_pos_found = recv_data_view.find(head); if (head_pos_found == std::string_view::npos) { - return ret::FAIL; + return ret::IN_PROGRESS; } auto *head_pos = recv_data + head_pos_found; auto next_nl = (char *)memchr(head_pos + head.size(), '\n', MIN_MESSAGE); if (next_nl == nullptr) { + if (head_pos + head.size() + 1 < recv_data + len) { + // might be that we misinterpreted the URC +QIRD: <>,<>,<> (notification) with the +QIRD: <> (read data) + // so we try to find the next +QIRD: + recv_data = head_pos + head.size() + 1; + goto again; + } + ESP_LOGD(TAG, "no new line found"); return ret::FAIL; } @@ -151,7 +159,9 @@ Responder::ret Responder::recv(uint8_t *data, size_t len) ESP_LOGD(TAG, "Received: actual len=%d", actual_len); if (actual_len == 0) { ESP_LOGD(TAG, "no data received"); - return ret::FAIL; + data_to_recv = 0; + // return OK here, as BG96 would keep unacked data and notifies us with +QIRD: 0 + return ret::OK; } if (actual_len > buffer_size) { @@ -182,6 +192,7 @@ Responder::ret Responder::recv(uint8_t *data, size_t len) last_pos = (char *)memchr(recv_data + 1 + actual_len, 'O', MIN_MESSAGE); if (last_pos == nullptr || last_pos[1] != 'K') { data_to_recv = 0; + ESP_LOGE(TAG, "no OK after data"); return ret::FAIL; } } @@ -222,7 +233,7 @@ Responder::ret Responder::send(std::string_view response) { if (send_stat == 3) { if (response.find("SEND OK") != std::string::npos) { - send_cmd("AT+QISEND=0,0\r"); + send_cmd(std::string("AT+QISEND=") + std::to_string(link_id) + ",0\r"); send_stat++; return ret::IN_PROGRESS; } else if (response.find("SEND FAIL") != std::string::npos) { @@ -284,7 +295,8 @@ Responder::ret Responder::send(std::string_view response) Responder::ret Responder::connect(std::string_view response) { - if (response.find("+QIOPEN: 0,0") != std::string::npos) { + std::string open_response = "+QIOPEN: " + std::to_string(link_id) + ",0"; + if (response.find(open_response) != std::string::npos) { ESP_LOGI(TAG, "Connected!"); return ret::OK; } @@ -295,10 +307,9 @@ Responder::ret Responder::connect(std::string_view response) return Responder::ret::IN_PROGRESS; } -Responder::ret Responder::check_async_replies(status state, std::string_view &response) +Responder::ret Responder::check_urc(status state, std::string_view &response) { - ESP_LOGD(TAG, "response %.*s", static_cast(response.size()), response.data()); - if (response.find("+QIURC: \"recv\",0") != std::string::npos) { + if (response.find(std::string("+QIURC: \"recv\",") + std::to_string(link_id)) != std::string::npos) { uint64_t data_ready = 1; write(data_ready_fd, &data_ready, sizeof(data_ready)); ESP_LOGD(TAG, "Got data on modem!"); @@ -309,6 +320,9 @@ Responder::ret Responder::check_async_replies(status state, std::string_view &re response = response.substr(head_pos + head.size()); int next_cr = response.find('\r'); if (next_cr != std::string::npos) { + if (next_cr < 2) { + return ret::IN_PROGRESS; + } response = response.substr(next_cr - 2, next_cr); if (response.find(",0") != std::string::npos) { ESP_LOGV(TAG, "Receiving done"); @@ -318,12 +332,21 @@ Responder::ret Responder::check_async_replies(status state, std::string_view &re ESP_LOGD(TAG, "Got data on modem!"); } } - } else if (response.find("+QIURC: \"closed\",0") != std::string::npos) { + } else if (response.find(std::string("+QIURC: \"closed\",") + std::to_string(link_id)) != std::string::npos) { + ESP_LOGE(TAG, "Connection closed"); return ret::FAIL; } + return ret::IN_PROGRESS; +} + + +Responder::ret Responder::check_async_replies(status state, std::string_view &response) +{ + ESP_LOGD(TAG, "response %.*s", static_cast(response.size()), response.data()); if (state == status::SENDING) { return send(response); - } else if (state == status::CONNECTING) { + } + if (state == status::CONNECTING) { return connect(response); } return ret::IN_PROGRESS; @@ -342,7 +365,7 @@ Responder::ret Responder::process_data(status state, uint8_t *data, size_t len) status Responder::pending() { - send_cmd("AT+QISEND=0,0\r"); + send_cmd(std::string("AT+QISEND=") + std::to_string(link_id) + ",0\r"); return status::SENDING; } diff --git a/components/esp_modem/examples/modem_tcp_client/main/sock_commands_espat.cpp b/components/esp_modem/examples/modem_tcp_client/main/sock_commands_espat.cpp index a293a3f6f6..98e3376016 100644 --- a/components/esp_modem/examples/modem_tcp_client/main/sock_commands_espat.cpp +++ b/components/esp_modem/examples/modem_tcp_client/main/sock_commands_espat.cpp @@ -58,12 +58,27 @@ command_result net_open(CommandableIf *t) } ESP_LOGI(TAG, "WiFi connected successfully"); - // Set passive receive mode (1) for better control - ret = set_rx_mode(t, 1); + // Enable multiple connections mode + ret = dce_commands::generic_command(t, "AT+CIPMUX=1\r\n", "OK", "ERROR", 1000); if (ret != command_result::OK) { - ESP_LOGE(TAG, "Failed to set preferred Rx mode"); + ESP_LOGE(TAG, "Failed to enable multiple connections mode"); return ret; } + ESP_LOGI(TAG, "Multiple connections mode enabled"); + + // Set passive receive mode (1) for better control + for (int i = 0; i < 2; i++) { + std::string cmd = "AT+CIPRECVTYPE=" + std::to_string(i) + ",1\r\n"; + dce_commands::generic_command(t, cmd, "OK", "ERROR", 1000); + } + // std::string cmd = "AT+CIPRECVTYPE=" + std::to_string(link_id) + "," + std::to_string(mode) + "\r\n"; + // return dce_commands::generic_command(t, cmd, "OK", "ERROR", 1000); + // + // ret = set_rx_mode(t, 1); + // if (ret != command_result::OK) { + // ESP_LOGE(TAG, "Failed to set preferred Rx mode"); + // return ret; + // } return command_result::OK; } @@ -78,50 +93,44 @@ command_result net_close(CommandableIf *t) return command_result::OK; } -command_result tcp_open(CommandableIf *t, const std::string &host, int port, int timeout) -{ - ESP_LOGV(TAG, "%s", __func__); - - // Set single connection mode (just in case) - auto ret = dce_commands::generic_command(t, "AT+CIPMUX=0\r\n", "OK", "ERROR", 1000); - if (ret != command_result::OK) { - ESP_LOGW(TAG, "Failed to set single connection mode"); - } - - // Establish TCP connection - std::string tcp_cmd = "AT+CIPSTART=\"TCP\",\"" + host + "\"," + std::to_string(port) + "\r\n"; - ret = dce_commands::generic_command(t, tcp_cmd, "CONNECT", "ERROR", timeout); - if (ret != command_result::OK) { - ESP_LOGE(TAG, "Failed to establish TCP connection to %s:%d", host.c_str(), port); - return ret; - } - - ESP_LOGI(TAG, "TCP connection established to %s:%d", host.c_str(), port); - return command_result::OK; -} +// command_result tcp_open(CommandableIf *t, const std::string &host, int port, int timeout) +// { +// ESP_LOGV(TAG, "%s", __func__); +// +// // Use link ID 0 for now (can be extended to support multiple concurrent connections) +// const int link_id = 0; +// +// // Establish TCP connection with link ID for multiple connections mode +// std::string tcp_cmd = "AT+CIPSTART=" + std::to_string(link_id) + ",\"TCP\",\"" + host + "\"," + std::to_string(port) + "\r\n"; +// +// // In multiple connections mode, response format is: ,CONNECT +// std::string expected_response = std::to_string(link_id) + ",CONNECT"; +// +// auto ret = dce_commands::generic_command(t, tcp_cmd, expected_response, "ERROR", timeout); +// if (ret != command_result::OK) { +// ESP_LOGE(TAG, "Failed to establish TCP connection to %s:%d on link %d", host.c_str(), port, link_id); +// return ret; +// } +// +// ESP_LOGI(TAG, "TCP connection established to %s:%d on link %d", host.c_str(), port, link_id); +// return command_result::OK; +// } command_result tcp_close(CommandableIf *t) { + return command_result::OK; ESP_LOGV(TAG, "%s", __func__); - return dce_commands::generic_command(t, "AT+CIPCLOSE\r\n", "CLOSED", "ERROR", 5000); -} + // Use link ID 0 for closing connection + const int link_id = 0; + std::string close_cmd = "AT+CIPCLOSE=" + std::to_string(link_id) + "\r\n"; -command_result tcp_send(CommandableIf *t, uint8_t *data, size_t len) -{ - ESP_LOGV(TAG, "%s", __func__); - // This function is not used in the current implementation - // Data sending is handled by the DCE responder - return command_result::FAIL; -} + // In multiple connections mode, response format is: ,CLOSED + std::string expected_response = std::to_string(link_id) + ",CLOSED"; -command_result tcp_recv(CommandableIf *t, uint8_t *data, size_t len, size_t &out_len) -{ - ESP_LOGV(TAG, "%s", __func__); - // This function is not used in the current implementation - // Data receiving is handled by the DCE responder - return command_result::FAIL; + return dce_commands::generic_command(t, close_cmd, expected_response, "ERROR", 5000); } + command_result get_ip(CommandableIf *t, std::string &ip) { ESP_LOGV(TAG, "%s", __func__); @@ -150,9 +159,11 @@ command_result get_ip(CommandableIf *t, std::string &ip) command_result set_rx_mode(CommandableIf *t, int mode) { - ESP_LOGE(TAG, "%s", __func__); + ESP_LOGV(TAG, "%s", __func__); + // For multiple connections mode, set receive mode for link ID 0 + const int link_id = 0; // Active mode (0) sends data automatically, Passive mode (1) notifies about data for reading - std::string cmd = "AT+CIPRECVTYPE=" + std::to_string(mode) + "\r\n"; + std::string cmd = "AT+CIPRECVTYPE=" + std::to_string(link_id) + "," + std::to_string(mode) + "\r\n"; return dce_commands::generic_command(t, cmd, "OK", "ERROR", 1000); } @@ -164,17 +175,20 @@ void Responder::start_sending(size_t len) { data_to_send = len; send_stat = 0; - send_cmd("AT+CIPSEND=" + std::to_string(len) + "\r\n"); + // For multiple connections mode, include link ID + send_cmd("AT+CIPSEND=" + std::to_string(link_id) + "," + std::to_string(len) + "\r\n"); } void Responder::start_receiving(size_t len) { - send_cmd("AT+CIPRECVDATA=" + std::to_string(len) + "\r\n"); + // For multiple connections mode, include link ID + send_cmd("AT+CIPRECVDATA=" + std::to_string(link_id) + "," + std::to_string(len) + "\r\n"); } bool Responder::start_connecting(std::string host, int port) { - std::string cmd = "AT+CIPSTART=\"TCP\",\"" + host + "\"," + std::to_string(port) + "\r\n"; + // For multiple connections mode, include link ID + std::string cmd = "AT+CIPSTART=" + std::to_string(link_id) + ",\"TCP\",\"" + host + "\"," + std::to_string(port) + "\r\n"; send_cmd(cmd); return true; } @@ -187,16 +201,12 @@ Responder::ret Responder::recv(uint8_t *data, size_t len) if (data_to_recv == 0) { const std::string_view head = "+CIPRECVDATA:"; - - // Find the response header - auto head_pos = std::search(recv_data, recv_data + len, head.data(), head.data() + head.size(), [](char a, char b) { - return a == b; - }); - - if (head_pos == recv_data + len) { - return ret::FAIL; + const std::string_view recv_data_view(recv_data, len); + const auto head_pos_found = recv_data_view.find(head); + if (head_pos_found == std::string_view::npos) { + return ret::IN_PROGRESS; } - + const auto *head_pos = recv_data + head_pos_found; // Find the end of the length field auto next_comma = (char *)memchr(head_pos + head.size(), ',', MIN_MESSAGE); if (next_comma == nullptr) { @@ -245,12 +255,25 @@ Responder::ret Responder::recv(uint8_t *data, size_t len) char *ok_pos = nullptr; if (actual_len + 1 + 2 /* OK */ <= len) { ok_pos = (char *)memchr(recv_data + actual_len + 1, 'O', MIN_MESSAGE); - if (ok_pos == nullptr || ok_pos[1] != 'K') { + if (ok_pos == nullptr) { // || ok_pos[1] != 'K') { data_to_recv = 0; + ESP_LOGE(TAG, "Missed 'OK' marker"); + return ret::OK; + return ret::FAIL; + } + if (ok_pos + 1 < recv_data + len && ok_pos[1] != 'K') { + // we ignore the condition when receiving 'O' as the last character in the last batch, + // don't wait for the 'K' in the next run, assume the data are valid and let higher layers deal with it. + data_to_recv = 0; + ESP_LOGE(TAG, "Missed 'OK' marker2"); return ret::FAIL; } } - + if (ok_pos != nullptr && (char *)data + len - ok_pos - 2 > MIN_MESSAGE) { + // check for async replies after the Recv header + std::string_view response((char *)ok_pos + 2 /* OK */, (char *)data + len - ok_pos); + check_urc(status::RECEIVING, response); + } // Reset and prepare for next receive data_to_recv = 0; return ret::OK; @@ -299,7 +322,8 @@ Responder::ret Responder::send(std::string_view response) Responder::ret Responder::connect(std::string_view response) { - if (response.find("CONNECT") != std::string::npos) { + // In multiple connections mode, response format is: ,CONNECT + if (response.find(",CONNECT") != std::string::npos || response.find("CONNECT") != std::string::npos) { ESP_LOGI(TAG, "TCP connected!"); return ret::OK; } @@ -309,6 +333,17 @@ Responder::ret Responder::connect(std::string_view response) } return ret::IN_PROGRESS; } +Responder::ret Responder::check_urc(status state, std::string_view &response) +{ + // Handle data notifications - in multiple connections mode, format is +IPD,, + std::string expected_urc = "+IPD," + std::to_string(link_id); + if (response.find(expected_urc) != std::string::npos) { + uint64_t data_ready = 1; + write(data_ready_fd, &data_ready, sizeof(data_ready)); + ESP_LOGD(TAG, "Data available notification"); + } + return ret::IN_PROGRESS; +} Responder::ret Responder::check_async_replies(status state, std::string_view &response) { @@ -321,7 +356,7 @@ Responder::ret Responder::check_async_replies(status state, std::string_view &re ESP_LOGW(TAG, "WiFi disconnected"); } - // Handle TCP status messages + // Handle TCP status messages (multiple connections format: ,CONNECT or ,CLOSED) if (response.find("CONNECT") != std::string::npos && state == status::CONNECTING) { return connect(response); } else if (response.find("CLOSED") != std::string::npos) { @@ -329,13 +364,6 @@ Responder::ret Responder::check_async_replies(status state, std::string_view &re return ret::FAIL; } - // Handle data notifications in active mode (if we switch to it later) - if (response.find("+IPD,") != std::string::npos) { - uint64_t data_ready = 1; - write(data_ready_fd, &data_ready, sizeof(data_ready)); - ESP_LOGD(TAG, "Data available notification"); - } - if (state == status::SENDING) { return send(response); } else if (state == status::CONNECTING) { diff --git a/components/esp_modem/examples/modem_tcp_client/main/sock_commands_sim7600.cpp b/components/esp_modem/examples/modem_tcp_client/main/sock_commands_sim7600.cpp index 5e1343ceb0..619adacf66 100644 --- a/components/esp_modem/examples/modem_tcp_client/main/sock_commands_sim7600.cpp +++ b/components/esp_modem/examples/modem_tcp_client/main/sock_commands_sim7600.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -18,13 +18,13 @@ using namespace esp_modem; command_result net_open(CommandableIf *term) { - ESP_LOGV(TAG, "%s", __func__ ); + ESP_LOGV(TAG, "%s", __func__); std::string response; auto ret = dce_commands::generic_get_string(term, "AT+NETOPEN?\r", response, 1000); if (ret != command_result::OK) { return ret; } - ESP_LOGV(TAG, "%s", response.data() ); + ESP_LOGV(TAG, "%s", response.data()); if (response.find("+NETOPEN: 1") != std::string::npos) { ESP_LOGD(TAG, "Already there"); ret = command_result::OK; @@ -42,23 +42,23 @@ command_result net_open(CommandableIf *term) command_result net_close(CommandableIf *term) { - ESP_LOGV(TAG, "%s", __func__ ); + ESP_LOGV(TAG, "%s", __func__); return dce_commands::generic_command(term, "AT+NETCLOSE\r", "+NETCLOSE:", "ERROR", 30000); } command_result tcp_open(CommandableIf *term, const std::string &host, int port, int timeout) { - ESP_LOGV(TAG, "%s", __func__ ); + ESP_LOGV(TAG, "%s", __func__); auto ret = dce_commands::generic_command(term, "AT+CIPRXGET=1\r", "OK", "ERROR", 50000); if (ret != command_result::OK) { ESP_LOGE(TAG, "Setting Rx mode failed!"); return ret; } - ESP_LOGV(TAG, "%s", __func__ ); + ESP_LOGV(TAG, "%s", __func__); std::string ip_open = R"(AT+CIPOPEN=0,"TCP",")" + host + "\"," + std::to_string(port) + "\r"; ret = dce_commands::generic_command(term, ip_open, "+CIPOPEN: 0,0", "ERROR", timeout); if (ret != command_result::OK) { - ESP_LOGE(TAG, "%s Failed", __func__ ); + ESP_LOGE(TAG, "%s Failed", __func__); return ret; } return command_result::OK; @@ -66,13 +66,13 @@ command_result tcp_open(CommandableIf *term, const std::string &host, int port, command_result tcp_close(CommandableIf *term) { - ESP_LOGV(TAG, "%s", __func__ ); + ESP_LOGV(TAG, "%s", __func__); return dce_commands::generic_command(term, "AT+CIPCLOSE=0\r", "+CIPCLOSE:", "ERROR", 10000); } command_result tcp_send(CommandableIf *term, uint8_t *data, size_t len) { - ESP_LOGV(TAG, "%s", __func__ ); + ESP_LOGV(TAG, "%s", __func__); std::string send = "AT+CIPSEND=0," + std::to_string(len) + "\r"; auto ret = term->command(send, [&](uint8_t *data, size_t len) { std::string_view response((char *)data, len); @@ -107,7 +107,7 @@ command_result tcp_send(CommandableIf *term, uint8_t *data, size_t len) uint8_t ctrl_z = '\x1A'; term->write(&ctrl_z, 1); int count = 0; - while (ret == command_result::TIMEOUT && count++ < 1000 ) { + while (ret == command_result::TIMEOUT && count++ < 1000) { vTaskDelay(pdMS_TO_TICKS(1000)); } term->on_read(nullptr); @@ -116,7 +116,7 @@ command_result tcp_send(CommandableIf *term, uint8_t *data, size_t len) command_result tcp_recv(CommandableIf *term, uint8_t *data, size_t len, size_t &out_len) { - ESP_LOGV(TAG, "%s", __func__ ); + ESP_LOGV(TAG, "%s", __func__); std::string out; auto ret = dce_commands::generic_get_string(term, "AT+CIPRXGET=4,0\r", out); if (ret != command_result::OK) { @@ -195,17 +195,17 @@ void Responder::start_sending(size_t len) { data_to_send = len; send_stat = 0; - send_cmd("AT+CIPSEND=0," + std::to_string(len) + "\r"); + send_cmd("AT+CIPSEND=" + std::to_string(link_id) + "," + std::to_string(len) + "\r"); } void Responder::start_receiving(size_t len) { - send_cmd("AT+CIPRXGET=2,0," + std::to_string(len) + "\r"); + send_cmd("AT+CIPRXGET=2," + std::to_string(link_id) + "," + std::to_string(len) + "\r"); } bool Responder::start_connecting(std::string host, int port) { - send_cmd(R"(AT+CIPOPEN=0,"TCP",")" + host + "\"," + std::to_string(port) + "\r"); + send_cmd(std::string("AT+CIPOPEN=") + std::to_string(link_id) + R"(,"TCP",")" + host + "\"," + std::to_string(port) + "\r"); return true; } @@ -215,7 +215,7 @@ Responder::ret Responder::recv(uint8_t *data, size_t len) size_t actual_len = 0; auto *recv_data = (char *)data; if (data_to_recv == 0) { - static constexpr std::string_view head = "+CIPRXGET: 2,0,"; + const std::string head = std::string("+CIPRXGET: 2,") + std::to_string(link_id) + ","; auto head_pos = std::search(recv_data, recv_data + len, head.begin(), head.end()); if (head_pos == recv_data + len) { return ret::FAIL; @@ -329,7 +329,8 @@ Responder::ret Responder::send(std::string_view response) Responder::ret Responder::connect(std::string_view response) { - if (response.find("+CIPOPEN: 0,0") != std::string::npos) { + std::string open_response = "+CIPOPEN: " + std::to_string(link_id) + ",0"; + if (response.find(open_response) != std::string::npos) { ESP_LOGI(TAG, "Connected!"); return ret::OK; } @@ -340,14 +341,22 @@ Responder::ret Responder::connect(std::string_view response) return Responder::ret::IN_PROGRESS; } -Responder::ret Responder::check_async_replies(status state, std::string_view &response) +Responder::ret Responder::check_urc(status state, std::string_view &response) { - ESP_LOGD(TAG, "response %.*s", static_cast(response.size()), response.data()); - if (response.find("+CIPRXGET: 1") != std::string::npos) { + // 1. When is set to 1 and the 2-4 mode will take effect. + // 2. If AT+CIPRXGET=1, it will report +CIPRXGET: 1,(multi client) when + const std::string expected = std::string("+CIPRXGET: 1,") + std::to_string(link_id); + if (response.find(expected) != std::string::npos) { uint64_t data_ready = 1; write(data_ready_fd, &data_ready, sizeof(data_ready)); ESP_LOGD(TAG, "Got data on modem!"); } + return ret::IN_PROGRESS; +} + +Responder::ret Responder::check_async_replies(status state, std::string_view &response) +{ + ESP_LOGD(TAG, "response %.*s", static_cast(response.size()), response.data()); if (state == status::SENDING) { return send(response); } else if (state == status::CONNECTING) { From 171c30fd94a40b5f33aac24f103622f92a1269fc Mon Sep 17 00:00:00 2001 From: David Cermak Date: Thu, 4 Sep 2025 09:13:08 +0200 Subject: [PATCH 2/2] fix(modem): Cleanup tcp-client code --- .../examples/modem_tcp_client/README.md | 15 ++++++++ .../main/command/sock_dce.cpp | 37 +++++++++---------- .../main/command/sock_dce.hpp | 12 ++++-- .../main/generate/sock_dce.cpp | 37 +++++++++---------- .../main/generate/sock_dce.hpp | 12 ++++-- .../modem_tcp_client/main/modem_client.cpp | 5 +-- .../main/sock_commands_bg96.cpp | 4 +- .../main/sock_commands_espat.cpp | 37 ++----------------- .../main/sock_commands_sim7600.cpp | 6 +-- 9 files changed, 77 insertions(+), 88 deletions(-) diff --git a/components/esp_modem/examples/modem_tcp_client/README.md b/components/esp_modem/examples/modem_tcp_client/README.md index a54f9dcec1..f619b2c85d 100644 --- a/components/esp_modem/examples/modem_tcp_client/README.md +++ b/components/esp_modem/examples/modem_tcp_client/README.md @@ -22,3 +22,18 @@ To enable this mode, please set `EXAMPLE_CUSTOM_TCP_TRANSPORT=y` This configuration could be used with any network library, which is connecting to a localhost endpoint instead of remote one. This example creates a localhost listener which basically mimics the remote endpoint by forwarding the traffic between the library and the TCP/socket layer of the modem (which is already secure if the TLS is used in the network library) ![with localhost listener](at_client_localhost.png) + +### Multi-connection support + +This example supports opening multiple TCP connections concurrently when the modem firmware allows it. + +- ESP-AT: Multi-connection mode is enabled via `AT+CIPMUX=1`. The example assigns a unique link ID per DCE instance and includes the link ID in `CIPSTART/CIPSEND/CIPRECVDATA` commands. +- BG96/SIM7600: The example uses module-specific multi-connection syntax (for example `QIOPEN/CIPOPEN` with a connection ID) and tracks link IDs internally. + +How it works: +- The `sock_dce` layer creates multiple DCE instances over a shared DTE. A lightweight mutex coordinates access to the UART so only one DCE issues AT commands at a time. +- Asynchronous URCs (for example `+IPD`, `+QIURC`, `+CIPRXGET: 1,`) wake the corresponding DCE which then performs receive operations for its link. + +Usage: +- `app_main` starts two DCE tasks to demonstrate concurrent connections. Adjust the number of DCE instances as needed. +- For ESP-AT, ensure your firmware supports `CIPMUX=1` and passive receive (`CIPRECVTYPE`). diff --git a/components/esp_modem/examples/modem_tcp_client/main/command/sock_dce.cpp b/components/esp_modem/examples/modem_tcp_client/main/command/sock_dce.cpp index 19375a28b7..ce5ca785f1 100644 --- a/components/esp_modem/examples/modem_tcp_client/main/command/sock_dce.cpp +++ b/components/esp_modem/examples/modem_tcp_client/main/command/sock_dce.cpp @@ -91,16 +91,18 @@ void DCE::perform_at(uint8_t *data, size_t len) return; } } - ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_INFO); + // Trace incoming AT bytes when handling a response; use DEBUG level + ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_DEBUG); switch (at.process_data(state, data, len)) { case Responder::ret::OK: - ESP_LOGW(TAG, "GIVE data %d", at.link_id); + // Release DTE access for this link after processing data + ESP_LOGD(TAG, "GIVE data %d", at.link_id); xSemaphoreGive(at.s_dte_mutex); state = status::IDLE; signal.set(IDLE); return; case Responder::ret::FAIL: - ESP_LOGW(TAG, "GIVE data %d", at.link_id); + ESP_LOGD(TAG, "GIVE data %d", at.link_id); xSemaphoreGive(at.s_dte_mutex); state = status::FAILED; signal.set(IDLE); @@ -116,13 +118,13 @@ void DCE::perform_at(uint8_t *data, size_t len) std::string_view response((char *)data, len); switch (at.check_async_replies(state, response)) { case Responder::ret::OK: - ESP_LOGW(TAG, "GIVE command %d", at.link_id); + ESP_LOGD(TAG, "GIVE command %d", at.link_id); xSemaphoreGive(at.s_dte_mutex); state = status::IDLE; signal.set(IDLE); return; case Responder::ret::FAIL: - ESP_LOGW(TAG, "GIVE command %d", at.link_id); + ESP_LOGD(TAG, "GIVE command %d", at.link_id); xSemaphoreGive(at.s_dte_mutex); state = status::FAILED; signal.set(IDLE); @@ -169,9 +171,10 @@ bool DCE::at_to_sock() close_sock(); return false; } - ESP_LOGI(TAG, "TAKE RECV %d", at.link_id); + // Take DTE mutex before issuing receive on this link + ESP_LOGD(TAG, "TAKE RECV %d", at.link_id); xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); - ESP_LOGE(TAG, "TAKE RECV %d", at.link_id); + ESP_LOGD(TAG, "TAKEN RECV %d", at.link_id); state = status::RECEIVING; at.start_receiving(at.get_buf_len()); return true; @@ -190,9 +193,10 @@ bool DCE::sock_to_at() close_sock(); return false; } - ESP_LOGI(TAG, "TAKE SEND %d", at.link_id); + // Take DTE mutex before issuing send on this link + ESP_LOGD(TAG, "TAKE SEND %d", at.link_id); xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); - ESP_LOGE(TAG, "TAKE SEND %d", at.link_id); + ESP_LOGD(TAG, "TAKEN SEND %d", at.link_id); state = status::SENDING; int len = ::recv(sock, at.get_buf(), at.get_buf_len(), 0); if (len < 0) { @@ -245,7 +249,7 @@ void DCE::start_listening(int port) } int opt = 1; setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); - ESP_LOGI(TAG, "Socket created"); + ESP_LOGD(TAG, "Socket created"); struct sockaddr_in addr = { }; addr.sin_family = AF_INET; addr.sin_port = htons(port); @@ -257,7 +261,7 @@ void DCE::start_listening(int port) ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno); return; } - ESP_LOGI(TAG, "Socket bound, port %d", 1883); + ESP_LOGD(TAG, "Socket bound, port %d", 1883); err = listen(listen_sock, 1); if (err != 0) { ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno); @@ -270,15 +274,10 @@ bool DCE::connect(std::string host, int port) { data_ready_fd = eventfd(0, EFD_SUPPORT_ISR); assert(data_ready_fd > 0); - // dte->on_read(nullptr); - // tcp_close(); - // dte->on_read([](uint8_t *data, size_t len) { - // read_callback(data, len); - // return esp_modem::command_result::TIMEOUT; - // }); - ESP_LOGI(TAG, "TAKE CONNECT %d", at.link_id); + // Take DTE mutex before starting connect for this link + ESP_LOGD(TAG, "TAKE CONNECT %d", at.link_id); xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); - ESP_LOGE(TAG, "TAKE CONNECT %d", at.link_id); + ESP_LOGD(TAG, "TAKEN CONNECT %d", at.link_id); if (!at.start_connecting(host, port)) { ESP_LOGE(TAG, "Unable to start connecting"); dte->on_read(nullptr); diff --git a/components/esp_modem/examples/modem_tcp_client/main/command/sock_dce.hpp b/components/esp_modem/examples/modem_tcp_client/main/command/sock_dce.hpp index 47c10329f9..1d99996f3b 100644 --- a/components/esp_modem/examples/modem_tcp_client/main/command/sock_dce.hpp +++ b/components/esp_modem/examples/modem_tcp_client/main/command/sock_dce.hpp @@ -64,7 +64,9 @@ class Responder { return total_len; } + // Unique link identifier used to target multi-connection AT commands int link_id{s_link_id++}; + // Shared mutex guarding DTE access across concurrent DCE instances static SemaphoreHandle_t s_dte_mutex; private: static int s_link_id; @@ -170,9 +172,10 @@ class DCE : public Module { return 0; } at.clear_offsets(); - ESP_LOGI("TAG", "TAKE RECV %d", at.link_id); + // Take DTE mutex before issuing receive on this link + ESP_LOGD("TAG", "TAKE RECV %d", at.link_id); xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); - ESP_LOGE("TAG", "TAKE RECV %d", at.link_id); + ESP_LOGD("TAG", "TAKEN RECV %d", at.link_id); state = status::RECEIVING; uint64_t data; read(data_ready_fd, &data, sizeof(data)); @@ -194,9 +197,10 @@ class DCE : public Module { if (!wait_to_idle(timeout_ms)) { return -1; } - ESP_LOGI("TAG", "TAKE SEND %d", at.link_id); + // Take DTE mutex before issuing send on this link + ESP_LOGD("TAG", "TAKE SEND %d", at.link_id); xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); - ESP_LOGE("TAG", "TAKE SEND %d", at.link_id); + ESP_LOGD("TAG", "TAKEN SEND %d", at.link_id); state = status::SENDING; memcpy(at.get_buf(), buffer, len_to_send); ESP_LOG_BUFFER_HEXDUMP("dce", at.get_buf(), len, ESP_LOG_VERBOSE); diff --git a/components/esp_modem/examples/modem_tcp_client/main/generate/sock_dce.cpp b/components/esp_modem/examples/modem_tcp_client/main/generate/sock_dce.cpp index f82bbb560f..48f917baf5 100644 --- a/components/esp_modem/examples/modem_tcp_client/main/generate/sock_dce.cpp +++ b/components/esp_modem/examples/modem_tcp_client/main/generate/sock_dce.cpp @@ -91,16 +91,18 @@ void DCE::perform_at(uint8_t *data, size_t len) return; } } - ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_INFO); + // Trace incoming AT bytes when handling a response; use DEBUG level + ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_DEBUG); switch (at.process_data(state, data, len)) { case Responder::ret::OK: - ESP_LOGW(TAG, "GIVE data %d", at.link_id); + // Release DTE access for this link after processing data + ESP_LOGD(TAG, "GIVE data %d", at.link_id); xSemaphoreGive(at.s_dte_mutex); state = status::IDLE; signal.set(IDLE); return; case Responder::ret::FAIL: - ESP_LOGW(TAG, "GIVE data %d", at.link_id); + ESP_LOGD(TAG, "GIVE data %d", at.link_id); xSemaphoreGive(at.s_dte_mutex); state = status::FAILED; signal.set(IDLE); @@ -116,13 +118,13 @@ void DCE::perform_at(uint8_t *data, size_t len) std::string_view response((char *)data, len); switch (at.check_async_replies(state, response)) { case Responder::ret::OK: - ESP_LOGW(TAG, "GIVE command %d", at.link_id); + ESP_LOGD(TAG, "GIVE command %d", at.link_id); xSemaphoreGive(at.s_dte_mutex); state = status::IDLE; signal.set(IDLE); return; case Responder::ret::FAIL: - ESP_LOGW(TAG, "GIVE command %d", at.link_id); + ESP_LOGD(TAG, "GIVE command %d", at.link_id); xSemaphoreGive(at.s_dte_mutex); state = status::FAILED; signal.set(IDLE); @@ -169,9 +171,10 @@ bool DCE::at_to_sock() close_sock(); return false; } - ESP_LOGI(TAG, "TAKE RECV %d", at.link_id); + // Take DTE mutex before issuing receive on this link + ESP_LOGD(TAG, "TAKE RECV %d", at.link_id); xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); - ESP_LOGE(TAG, "TAKE RECV %d", at.link_id); + ESP_LOGD(TAG, "TAKEN RECV %d", at.link_id); state = status::RECEIVING; at.start_receiving(at.get_buf_len()); return true; @@ -190,9 +193,10 @@ bool DCE::sock_to_at() close_sock(); return false; } - ESP_LOGI(TAG, "TAKE SEND %d", at.link_id); + // Take DTE mutex before issuing send on this link + ESP_LOGD(TAG, "TAKE SEND %d", at.link_id); xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); - ESP_LOGE(TAG, "TAKE SEND %d", at.link_id); + ESP_LOGD(TAG, "TAKEN SEND %d", at.link_id); state = status::SENDING; int len = ::recv(sock, at.get_buf(), at.get_buf_len(), 0); if (len < 0) { @@ -245,7 +249,7 @@ void DCE::start_listening(int port) } int opt = 1; setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); - ESP_LOGI(TAG, "Socket created"); + ESP_LOGD(TAG, "Socket created"); struct sockaddr_in addr = { }; addr.sin_family = AF_INET; addr.sin_port = htons(port); @@ -257,7 +261,7 @@ void DCE::start_listening(int port) ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno); return; } - ESP_LOGI(TAG, "Socket bound, port %d", 1883); + ESP_LOGD(TAG, "Socket bound, port %d", 1883); err = listen(listen_sock, 1); if (err != 0) { ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno); @@ -270,15 +274,10 @@ bool DCE::connect(std::string host, int port) { data_ready_fd = eventfd(0, EFD_SUPPORT_ISR); assert(data_ready_fd > 0); - // dte->on_read(nullptr); - // tcp_close(); - // dte->on_read([](uint8_t *data, size_t len) { - // read_callback(data, len); - // return esp_modem::command_result::TIMEOUT; - // }); - ESP_LOGI(TAG, "TAKE CONNECT %d", at.link_id); + // Take DTE mutex before starting connect for this link + ESP_LOGD(TAG, "TAKE CONNECT %d", at.link_id); xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); - ESP_LOGE(TAG, "TAKE CONNECT %d", at.link_id); + ESP_LOGD(TAG, "TAKEN CONNECT %d", at.link_id); if (!at.start_connecting(host, port)) { ESP_LOGE(TAG, "Unable to start connecting"); dte->on_read(nullptr); diff --git a/components/esp_modem/examples/modem_tcp_client/main/generate/sock_dce.hpp b/components/esp_modem/examples/modem_tcp_client/main/generate/sock_dce.hpp index 246e9c3d28..4c6d979f28 100644 --- a/components/esp_modem/examples/modem_tcp_client/main/generate/sock_dce.hpp +++ b/components/esp_modem/examples/modem_tcp_client/main/generate/sock_dce.hpp @@ -64,7 +64,9 @@ class Responder { return total_len; } + // Unique link identifier used to target multi-connection AT commands int link_id{s_link_id++}; + // Shared mutex guarding DTE access across concurrent DCE instances static SemaphoreHandle_t s_dte_mutex; private: static int s_link_id; @@ -148,9 +150,10 @@ esp_modem::return_type name(ESP_MODEM_COMMAND_PARAMS(__VA_ARGS__)); return 0; } at.clear_offsets(); - ESP_LOGI("TAG", "TAKE RECV %d", at.link_id); + // Take DTE mutex before issuing receive on this link + ESP_LOGD("TAG", "TAKE RECV %d", at.link_id); xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); - ESP_LOGE("TAG", "TAKE RECV %d", at.link_id); + ESP_LOGD("TAG", "TAKEN RECV %d", at.link_id); state = status::RECEIVING; uint64_t data; read(data_ready_fd, &data, sizeof(data)); @@ -173,9 +176,10 @@ esp_modem::return_type name(ESP_MODEM_COMMAND_PARAMS(__VA_ARGS__)); if (!wait_to_idle(timeout_ms)) { return -1; } - ESP_LOGI("TAG", "TAKE SEND %d", at.link_id); + // Take DTE mutex before issuing send on this link + ESP_LOGD("TAG", "TAKE SEND %d", at.link_id); xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); - ESP_LOGE("TAG", "TAKE SEND %d", at.link_id); + ESP_LOGD("TAG", "TAKEN SEND %d", at.link_id); state = status::SENDING; memcpy(at.get_buf(), buffer, len_to_send); ESP_LOG_BUFFER_HEXDUMP("dce", at.get_buf(), len, ESP_LOG_VERBOSE); diff --git a/components/esp_modem/examples/modem_tcp_client/main/modem_client.cpp b/components/esp_modem/examples/modem_tcp_client/main/modem_client.cpp index f7fd57940e..3c27335542 100644 --- a/components/esp_modem/examples/modem_tcp_client/main/modem_client.cpp +++ b/components/esp_modem/examples/modem_tcp_client/main/modem_client.cpp @@ -91,6 +91,7 @@ extern "C" void app_main(void) /* Init and register system/core components */ ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); + // Default to INFO; individual modules use DEBUG for verbose tracing esp_log_level_set("*", ESP_LOG_INFO); event_group = xEventGroupCreate(); @@ -124,8 +125,6 @@ extern "C" void app_main(void) xTaskCreate(perform, "perform", 4096, dce.get(), 4, nullptr); - // vTaskDelay(pdMS_TO_TICKS(5000)); - // vTaskDelay(portMAX_DELAY); /* create another DCE to serve a new connection */ auto dce1 = sock_dce::create(&dce_config, dte); if (!dce1->init()) { @@ -179,7 +178,7 @@ static void perform(void* ctx) while (dce->perform_sock()) { ESP_LOGV(TAG, "...performing"); } - ESP_LOGE(TAG, "Loop exit.. retrying"); + ESP_LOGD(TAG, "Loop exit.. retrying"); // handle disconnections errors if (!dce->init()) { ESP_LOGE(TAG, "Failed to reinit network"); diff --git a/components/esp_modem/examples/modem_tcp_client/main/sock_commands_bg96.cpp b/components/esp_modem/examples/modem_tcp_client/main/sock_commands_bg96.cpp index 1916b64553..80f37d6f47 100644 --- a/components/esp_modem/examples/modem_tcp_client/main/sock_commands_bg96.cpp +++ b/components/esp_modem/examples/modem_tcp_client/main/sock_commands_bg96.cpp @@ -192,7 +192,7 @@ Responder::ret Responder::recv(uint8_t *data, size_t len) last_pos = (char *)memchr(recv_data + 1 + actual_len, 'O', MIN_MESSAGE); if (last_pos == nullptr || last_pos[1] != 'K') { data_to_recv = 0; - ESP_LOGE(TAG, "no OK after data"); + ESP_LOGD(TAG, "no OK after data"); return ret::FAIL; } } @@ -278,7 +278,7 @@ Responder::ret Responder::send(std::string_view response) if (ack < total) { ESP_LOGD(TAG, "all sending data are not ack (missing %d bytes acked)", (total - ack)); if (total - ack > 64) { - ESP_LOGW(TAG, "Need a pause: missing %d bytes acked", (total - ack)); + ESP_LOGD(TAG, "Need a pause: missing %d bytes acked", (total - ack)); return ret::NEED_MORE_TIME; } } diff --git a/components/esp_modem/examples/modem_tcp_client/main/sock_commands_espat.cpp b/components/esp_modem/examples/modem_tcp_client/main/sock_commands_espat.cpp index 98e3376016..223dc516b0 100644 --- a/components/esp_modem/examples/modem_tcp_client/main/sock_commands_espat.cpp +++ b/components/esp_modem/examples/modem_tcp_client/main/sock_commands_espat.cpp @@ -64,21 +64,13 @@ command_result net_open(CommandableIf *t) ESP_LOGE(TAG, "Failed to enable multiple connections mode"); return ret; } - ESP_LOGI(TAG, "Multiple connections mode enabled"); + ESP_LOGD(TAG, "Multiple connections mode enabled"); // Set passive receive mode (1) for better control for (int i = 0; i < 2; i++) { std::string cmd = "AT+CIPRECVTYPE=" + std::to_string(i) + ",1\r\n"; dce_commands::generic_command(t, cmd, "OK", "ERROR", 1000); } - // std::string cmd = "AT+CIPRECVTYPE=" + std::to_string(link_id) + "," + std::to_string(mode) + "\r\n"; - // return dce_commands::generic_command(t, cmd, "OK", "ERROR", 1000); - // - // ret = set_rx_mode(t, 1); - // if (ret != command_result::OK) { - // ESP_LOGE(TAG, "Failed to set preferred Rx mode"); - // return ret; - // } return command_result::OK; } @@ -93,29 +85,6 @@ command_result net_close(CommandableIf *t) return command_result::OK; } -// command_result tcp_open(CommandableIf *t, const std::string &host, int port, int timeout) -// { -// ESP_LOGV(TAG, "%s", __func__); -// -// // Use link ID 0 for now (can be extended to support multiple concurrent connections) -// const int link_id = 0; -// -// // Establish TCP connection with link ID for multiple connections mode -// std::string tcp_cmd = "AT+CIPSTART=" + std::to_string(link_id) + ",\"TCP\",\"" + host + "\"," + std::to_string(port) + "\r\n"; -// -// // In multiple connections mode, response format is: ,CONNECT -// std::string expected_response = std::to_string(link_id) + ",CONNECT"; -// -// auto ret = dce_commands::generic_command(t, tcp_cmd, expected_response, "ERROR", timeout); -// if (ret != command_result::OK) { -// ESP_LOGE(TAG, "Failed to establish TCP connection to %s:%d on link %d", host.c_str(), port, link_id); -// return ret; -// } -// -// ESP_LOGI(TAG, "TCP connection established to %s:%d on link %d", host.c_str(), port, link_id); -// return command_result::OK; -// } - command_result tcp_close(CommandableIf *t) { return command_result::OK; @@ -353,14 +322,14 @@ Responder::ret Responder::check_async_replies(status state, std::string_view &re if (response.find("WIFI CONNECTED") != std::string::npos) { ESP_LOGI(TAG, "WiFi connected"); } else if (response.find("WIFI DISCONNECTED") != std::string::npos) { - ESP_LOGW(TAG, "WiFi disconnected"); + ESP_LOGD(TAG, "WiFi disconnected"); } // Handle TCP status messages (multiple connections format: ,CONNECT or ,CLOSED) if (response.find("CONNECT") != std::string::npos && state == status::CONNECTING) { return connect(response); } else if (response.find("CLOSED") != std::string::npos) { - ESP_LOGW(TAG, "TCP connection closed"); + ESP_LOGD(TAG, "TCP connection closed"); return ret::FAIL; } diff --git a/components/esp_modem/examples/modem_tcp_client/main/sock_commands_sim7600.cpp b/components/esp_modem/examples/modem_tcp_client/main/sock_commands_sim7600.cpp index 619adacf66..0c86310fa4 100644 --- a/components/esp_modem/examples/modem_tcp_client/main/sock_commands_sim7600.cpp +++ b/components/esp_modem/examples/modem_tcp_client/main/sock_commands_sim7600.cpp @@ -86,10 +86,10 @@ command_result tcp_send(CommandableIf *term, uint8_t *data, size_t len) return ret; } ret = command_result::TIMEOUT; - ESP_LOGW(TAG, "Before setting..."); + ESP_LOGD(TAG, "Before setting..."); term->on_read([&ret](uint8_t *cmd_data, size_t cmd_len) { std::string_view response((char *)cmd_data, cmd_len); - ESP_LOGW(TAG, "CIPSEND response %.*s", static_cast(response.size()), response.data()); + ESP_LOGD(TAG, "CIPSEND response %.*s", static_cast(response.size()), response.data()); if (response.find("+CIPSEND:") != std::string::npos) { ret = command_result::OK; @@ -98,7 +98,7 @@ command_result tcp_send(CommandableIf *term, uint8_t *data, size_t len) } return ret; }); - ESP_LOGW(TAG, "Before writing..."); + ESP_LOGD(TAG, "Before writing..."); auto written = term->write(data, len); if (written != len) { ESP_LOGE(TAG, "written %d (%d)...", written, len);