From 5ad7367e70f7a478789660f66ea83ecac5bd0c81 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Mon, 7 Jul 2025 17:12:29 +0200 Subject: [PATCH 01/12] fix(modem): Add support for ESP-AT based tcp-client example --- .../modem_tcp_client/main/CMakeLists.txt | 2 + .../modem_tcp_client/main/Kconfig.projbuild | 18 + .../main/sock_commands_espat.cpp | 339 ++++++++++++++++++ 3 files changed, 359 insertions(+) create mode 100644 components/esp_modem/examples/modem_tcp_client/main/sock_commands_espat.cpp diff --git a/components/esp_modem/examples/modem_tcp_client/main/CMakeLists.txt b/components/esp_modem/examples/modem_tcp_client/main/CMakeLists.txt index 3788c86081..974b597541 100644 --- a/components/esp_modem/examples/modem_tcp_client/main/CMakeLists.txt +++ b/components/esp_modem/examples/modem_tcp_client/main/CMakeLists.txt @@ -2,6 +2,8 @@ if (CONFIG_EXAMPLE_MODEM_DEVICE_BG96) set(device_srcs sock_commands_bg96.cpp) elseif(CONFIG_EXAMPLE_MODEM_DEVICE_SIM7600) set(device_srcs sock_commands_sim7600.cpp) +elseif(CONFIG_EXAMPLE_MODEM_DEVICE_ESPAT) + set(device_srcs sock_commands_espat.cpp) endif() if(CONFIG_ESP_MODEM_ENABLE_DEVELOPMENT_MODE) diff --git a/components/esp_modem/examples/modem_tcp_client/main/Kconfig.projbuild b/components/esp_modem/examples/modem_tcp_client/main/Kconfig.projbuild index 988cade41a..5911579f5c 100644 --- a/components/esp_modem/examples/modem_tcp_client/main/Kconfig.projbuild +++ b/components/esp_modem/examples/modem_tcp_client/main/Kconfig.projbuild @@ -18,8 +18,26 @@ menu "Example Configuration" bool "SIM7600" help SIM7600 is Multi-Band LTE-TDD/LTE-FDD/HSPA+ and GSM/GPRS/EDGE module + config EXAMPLE_MODEM_DEVICE_ESPAT + bool "ESP-AT" + help + ESP-AT firmware for ESP32 modules with WiFi connectivity endchoice + if EXAMPLE_MODEM_DEVICE_ESPAT + config EXAMPLE_WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) to connect to. + + config EXAMPLE_WIFI_PASSWORD + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2). + endif + config EXAMPLE_MODEM_APN string "Set MODEM APN" default "internet" 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 new file mode 100644 index 0000000000..5573e45e9b --- /dev/null +++ b/components/esp_modem/examples/modem_tcp_client/main/sock_commands_espat.cpp @@ -0,0 +1,339 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "sock_commands.hpp" +#include "cxx_include/esp_modem_command_library_utils.hpp" +#include "sock_dce.hpp" + +static const char *TAG = "sock_commands_espat"; + +namespace sock_commands { + +using namespace esp_modem; + +command_result net_open(CommandableIf *t) +{ + ESP_LOGV(TAG, "%s", __func__); + + // Set WiFi mode to station + auto ret = dce_commands::generic_command(t, "AT+CWMODE=1\r", "OK", "ERROR", 5000); + if (ret != command_result::OK) { + ESP_LOGE(TAG, "Failed to set WiFi mode"); + return ret; + } + + // Connect to WiFi network + std::string wifi_cmd = "AT+CWJAP=\"" CONFIG_EXAMPLE_WIFI_SSID "\",\"" CONFIG_EXAMPLE_WIFI_PASSWORD "\"\r"; + ret = dce_commands::generic_command(t, wifi_cmd, "OK", "ERROR", 15000); + if (ret != command_result::OK) { + ESP_LOGE(TAG, "Failed to connect to WiFi"); + return ret; + } + + ESP_LOGI(TAG, "WiFi connected successfully"); + return command_result::OK; +} + +command_result net_close(CommandableIf *t) +{ + ESP_LOGV(TAG, "%s", __func__); + // Disconnect from WiFi + auto ret = dce_commands::generic_command(t, "AT+CWQAP\r", "OK", "ERROR", 5000); + if (ret != command_result::OK) { + ESP_LOGW(TAG, "Failed to disconnect WiFi (may already be disconnected)"); + } + 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", "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"; + 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_close(CommandableIf *t) +{ + ESP_LOGV(TAG, "%s", __func__); + return dce_commands::generic_command(t, "AT+CIPCLOSE\r", "CLOSED", "ERROR", 5000); +} + +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; +} + +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; +} + +command_result get_ip(CommandableIf *t, std::string &ip) +{ + ESP_LOGV(TAG, "%s", __func__); + std::string out; + auto ret = dce_commands::generic_get_string(t, "AT+CIFSR\r", out, 5000); + if (ret != command_result::OK) { + return ret; + } + + // Parse station IP from response + // Expected format: +CIFSR:STAIP,"192.168.1.100" + auto pos = out.find("+CIFSR:STAIP,\""); + if (pos != std::string::npos) { + pos += 14; // Move past "+CIFSR:STAIP,\"" + auto end_pos = out.find("\"", pos); + if (end_pos != std::string::npos) { + ip = out.substr(pos, end_pos - pos); + ESP_LOGI(TAG, "Got IP address: %s", ip.c_str()); + return command_result::OK; + } + } + + ESP_LOGE(TAG, "Failed to parse IP address from response"); + return command_result::FAIL; +} + +command_result set_rx_mode(CommandableIf *t, int mode) +{ + ESP_LOGV(TAG, "%s", __func__); + // Set passive receive mode (1) for better control + // Active mode (0) would send +IPD automatically + std::string cmd = "AT+CIPRECVTYPE=" + std::to_string(mode) + "\r"; + return dce_commands::generic_command(t, cmd, "OK", "ERROR", 1000); +} + +} // sock_commands + +namespace sock_dce { + +void Responder::start_sending(size_t len) +{ + data_to_send = len; + send_stat = 0; + send_cmd("AT+CIPSEND=" + std::to_string(len) + "\r"); +} + +void Responder::start_receiving(size_t len) +{ + send_cmd("AT+CIPRECVDATA=" + std::to_string(len) + "\r"); +} + +bool Responder::start_connecting(std::string host, int port) +{ + std::string cmd = "AT+CIPSTART=\"TCP\",\"" + host + "\"," + std::to_string(port) + "\r"; + send_cmd(cmd); + return true; +} + +Responder::ret Responder::recv(uint8_t *data, size_t len) +{ + const int MIN_MESSAGE = 6; + size_t actual_len = 0; + auto *recv_data = (char *)data; + + 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; + } + + // Find the end of the length field + auto next_comma = (char *)memchr(head_pos + head.size(), ',', MIN_MESSAGE); + if (next_comma == nullptr) { + return ret::FAIL; + } + + // Parse the actual length + if (std::from_chars(head_pos + head.size(), next_comma, actual_len).ec == std::errc::invalid_argument) { + ESP_LOGE(TAG, "Cannot convert length"); + return ret::FAIL; + } + + ESP_LOGD(TAG, "Received: actual len=%zu", actual_len); + if (actual_len == 0) { + ESP_LOGD(TAG, "No data received"); + return ret::FAIL; + } + + if (actual_len > buffer_size) { + ESP_LOGE(TAG, "Data too large: %zu > %zu", actual_len, buffer_size); + return ret::FAIL; + } + + // Move to the actual data after the comma + recv_data = next_comma + 1; + auto first_data_len = len - (recv_data - (char *)data); + + if (actual_len > first_data_len) { + on_read(recv_data, first_data_len); + data_to_recv = actual_len - first_data_len; + return ret::NEED_MORE_DATA; + } + on_read(recv_data, actual_len); + + } else if (data_to_recv > len) { // Continue receiving + on_read(recv_data, len); + data_to_recv -= len; + return ret::NEED_MORE_DATA; + + } else if (data_to_recv <= len) { // Last chunk + on_read(recv_data, data_to_recv); + actual_len = data_to_recv; + } + + // Look for "OK" marker after the data + 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') { + data_to_recv = 0; + return ret::FAIL; + } + } + + // Reset and prepare for next receive + data_to_recv = 0; + return ret::OK; +} + +Responder::ret Responder::send(uint8_t *data, size_t len) +{ + if (send_stat < 3) { + // Look for the '>' prompt + if (memchr(data, '>', len) == NULL) { + if (send_stat++ < 2) { + return ret::NEED_MORE_DATA; + } + ESP_LOGE(TAG, "Missed '>' prompt"); + return ret::FAIL; + } + + // Send the actual data + auto written = dte->write(&buffer[0], data_to_send); + if (written != data_to_send) { + ESP_LOGE(TAG, "Failed to write data: %d/%zu", written, data_to_send); + return ret::FAIL; + } + data_to_send = 0; + send_stat = 3; + } + return ret::IN_PROGRESS; +} + +Responder::ret Responder::send(std::string_view response) +{ + if (send_stat == 3) { + if (response.find("SEND OK") != std::string::npos) { + send_stat = 0; + return ret::OK; + } else if (response.find("SEND FAIL") != std::string::npos) { + ESP_LOGE(TAG, "Send failed"); + return ret::FAIL; + } else if (response.find("ERROR") != std::string::npos) { + ESP_LOGE(TAG, "Send error"); + return ret::FAIL; + } + } + return ret::IN_PROGRESS; +} + +Responder::ret Responder::connect(std::string_view response) +{ + if (response.find("CONNECT") != std::string::npos) { + ESP_LOGI(TAG, "TCP connected!"); + return ret::OK; + } + if (response.find("ERROR") != std::string::npos) { + ESP_LOGE(TAG, "Failed to connect"); + 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()); + + // Handle WiFi status messages + 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"); + } + + // Handle TCP status messages + 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"); + 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) { + return connect(response); + } + + return ret::IN_PROGRESS; +} + +Responder::ret Responder::process_data(status state, uint8_t *data, size_t len) +{ + if (state == status::SENDING) { + return send(data, len); + } + if (state == status::RECEIVING) { + return recv(data, len); + } + return ret::IN_PROGRESS; +} + +status Responder::pending() +{ + // For ESP-AT, we don't need a pending check like BG96 + // Just return current status + return status::SENDING; +} + +} // sock_dce From 26b75f9df074262df8421cdc840e9263e0a36c12 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Mon, 14 Jul 2025 15:52:26 +0200 Subject: [PATCH 02/12] fix(modem): Fixup tcp-client example with esp-at --- .../main/command/sock_dce.cpp | 17 ++++++++++++- .../main/command/sock_dce.hpp | 3 +++ .../modem_tcp_client/main/modem_client.cpp | 9 +++---- .../main/sock_commands_espat.cpp | 24 +++++++++---------- 4 files changed, 36 insertions(+), 17 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..cff9196b9e 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 @@ -8,8 +8,8 @@ #include #include "esp_vfs.h" #include "esp_vfs_eventfd.h" - #include "sock_dce.hpp" +#include "cxx_include/esp_modem_command_library_utils.hpp" namespace sock_dce { @@ -305,6 +305,21 @@ std::unique_ptr create(const esp_modem::dce_config *config, std::shared_ptr } +esp_modem::command_result DCE::sync() +{ + return esp_modem::dce_commands::generic_command_common(dte.get(), "AT\r\n"); +} + +esp_modem::command_result DCE::set_echo(bool on) +{ + return esp_modem::dce_commands::generic_command_common(dte.get(), "ATE0\r\n"); +} + +esp_modem::command_result DCE::set_pdp_context(esp_modem::PdpContext &pdp) +{ + return esp_modem::command_result::OK; +} + /** * @brief Opens network in AT command mode * @return OK, FAIL or TIMEOUT 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 e73478d5f1..0515f0b7d5 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 @@ -101,6 +101,9 @@ class DCE : public ::esp_modem::GenericModule { using esp_modem::GenericModule::GenericModule; public: + esp_modem::command_result sync() override; + esp_modem::command_result set_echo(bool on) override; + esp_modem::command_result set_pdp_context(esp_modem::PdpContext &pdp) override; /** * @brief Opens network in AT command mode * @return OK, FAIL or TIMEOUT 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 558ba5316e..bf377e611f 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 @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -23,8 +23,8 @@ #include "tcp_transport_mbedtls.h" #include "tcp_transport_at.h" -#define BROKER_URL "mqtt.eclipseprojects.io" -#define BROKER_PORT 8883 +#define BROKER_URL "test.mosquitto.org" +#define BROKER_PORT 1883 static const char *TAG = "modem_client"; @@ -114,7 +114,7 @@ extern "C" void app_main(void) mqtt_config.broker.address.port = BROKER_PORT; mqtt_config.session.message_retransmit_timeout = 10000; #ifndef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT - mqtt_config.broker.address.uri = "mqtts://127.0.0.1"; + mqtt_config.broker.address.uri = "mqtt://127.0.0.1"; dce->start_listening(BROKER_PORT); #else mqtt_config.broker.address.uri = "mqtt://" BROKER_URL; @@ -127,6 +127,7 @@ extern "C" void app_main(void) esp_mqtt_client_register_event(mqtt_client, static_cast(ESP_EVENT_ANY_ID), mqtt_event_handler, NULL); esp_mqtt_client_start(mqtt_client); #ifndef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT + dce->set_rx_mode(1); if (!dce->connect(BROKER_URL, BROKER_PORT)) { ESP_LOGE(TAG, "Failed to start DCE"); return; 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 5573e45e9b..b4d9536826 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 @@ -21,14 +21,14 @@ command_result net_open(CommandableIf *t) ESP_LOGV(TAG, "%s", __func__); // Set WiFi mode to station - auto ret = dce_commands::generic_command(t, "AT+CWMODE=1\r", "OK", "ERROR", 5000); + auto ret = dce_commands::generic_command(t, "AT+CWMODE=1\r\n", "OK", "ERROR", 5000); if (ret != command_result::OK) { ESP_LOGE(TAG, "Failed to set WiFi mode"); return ret; } // Connect to WiFi network - std::string wifi_cmd = "AT+CWJAP=\"" CONFIG_EXAMPLE_WIFI_SSID "\",\"" CONFIG_EXAMPLE_WIFI_PASSWORD "\"\r"; + std::string wifi_cmd = "AT+CWJAP=\"" CONFIG_EXAMPLE_WIFI_SSID "\",\"" CONFIG_EXAMPLE_WIFI_PASSWORD "\"\r\n"; ret = dce_commands::generic_command(t, wifi_cmd, "OK", "ERROR", 15000); if (ret != command_result::OK) { ESP_LOGE(TAG, "Failed to connect to WiFi"); @@ -43,7 +43,7 @@ command_result net_close(CommandableIf *t) { ESP_LOGV(TAG, "%s", __func__); // Disconnect from WiFi - auto ret = dce_commands::generic_command(t, "AT+CWQAP\r", "OK", "ERROR", 5000); + auto ret = dce_commands::generic_command(t, "AT+CWQAP\r\n", "OK", "ERROR", 5000); if (ret != command_result::OK) { ESP_LOGW(TAG, "Failed to disconnect WiFi (may already be disconnected)"); } @@ -55,13 +55,13 @@ command_result tcp_open(CommandableIf *t, const std::string &host, int port, int ESP_LOGV(TAG, "%s", __func__); // Set single connection mode (just in case) - auto ret = dce_commands::generic_command(t, "AT+CIPMUX=0\r", "OK", "ERROR", 1000); + 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"; + 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); @@ -75,7 +75,7 @@ command_result tcp_open(CommandableIf *t, const std::string &host, int port, int command_result tcp_close(CommandableIf *t) { ESP_LOGV(TAG, "%s", __func__); - return dce_commands::generic_command(t, "AT+CIPCLOSE\r", "CLOSED", "ERROR", 5000); + return dce_commands::generic_command(t, "AT+CIPCLOSE\r\n", "CLOSED", "ERROR", 5000); } command_result tcp_send(CommandableIf *t, uint8_t *data, size_t len) @@ -98,7 +98,7 @@ command_result get_ip(CommandableIf *t, std::string &ip) { ESP_LOGV(TAG, "%s", __func__); std::string out; - auto ret = dce_commands::generic_get_string(t, "AT+CIFSR\r", out, 5000); + auto ret = dce_commands::at_raw(t, "AT+CIFSR\r\n", out, "OK", "ERROR", 5000); if (ret != command_result::OK) { return ret; } @@ -122,10 +122,10 @@ command_result get_ip(CommandableIf *t, std::string &ip) command_result set_rx_mode(CommandableIf *t, int mode) { - ESP_LOGV(TAG, "%s", __func__); + ESP_LOGE(TAG, "%s", __func__); // Set passive receive mode (1) for better control // Active mode (0) would send +IPD automatically - std::string cmd = "AT+CIPRECVTYPE=" + std::to_string(mode) + "\r"; + std::string cmd = "AT+CIPRECVTYPE=" + std::to_string(mode) + "\r\n"; return dce_commands::generic_command(t, cmd, "OK", "ERROR", 1000); } @@ -137,17 +137,17 @@ void Responder::start_sending(size_t len) { data_to_send = len; send_stat = 0; - send_cmd("AT+CIPSEND=" + std::to_string(len) + "\r"); + send_cmd("AT+CIPSEND=" + std::to_string(len) + "\r\n"); } void Responder::start_receiving(size_t len) { - send_cmd("AT+CIPRECVDATA=" + std::to_string(len) + "\r"); + send_cmd("AT+CIPRECVDATA=" + 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"; + std::string cmd = "AT+CIPSTART=\"TCP\",\"" + host + "\"," + std::to_string(port) + "\r\n"; send_cmd(cmd); return true; } From 4e901ebf0565c71312e77c18dffef1c53573c107 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Tue, 15 Jul 2025 16:24:39 +0200 Subject: [PATCH 03/12] fix(modem): Use nicer way to override modules --- .../modem_tcp_client/main/CMakeLists.txt | 4 ++- .../main/command/sock_dce.cpp | 17 +--------- .../main/command/sock_dce.hpp | 8 ++--- .../main/espat_module/sock_module.hpp | 24 ++++++++++++++ .../main/generate/sock_dce.hpp | 5 +-- .../main/generic_module/sock_module.hpp | 16 +++++++++ .../modem_tcp_client/main/modem_client.cpp | 5 ++- .../main/sock_commands_espat.cpp | 33 +++++++++++++++++-- .../modem_tcp_client/sdkconfig.ci.default | 0 .../modem_tcp_client/sdkconfig.ci.espat | 2 ++ 10 files changed, 84 insertions(+), 30 deletions(-) create mode 100644 components/esp_modem/examples/modem_tcp_client/main/espat_module/sock_module.hpp create mode 100644 components/esp_modem/examples/modem_tcp_client/main/generic_module/sock_module.hpp create mode 100644 components/esp_modem/examples/modem_tcp_client/sdkconfig.ci.default create mode 100644 components/esp_modem/examples/modem_tcp_client/sdkconfig.ci.espat diff --git a/components/esp_modem/examples/modem_tcp_client/main/CMakeLists.txt b/components/esp_modem/examples/modem_tcp_client/main/CMakeLists.txt index 974b597541..2594fe424d 100644 --- a/components/esp_modem/examples/modem_tcp_client/main/CMakeLists.txt +++ b/components/esp_modem/examples/modem_tcp_client/main/CMakeLists.txt @@ -1,9 +1,11 @@ +set(module_dir "generic_module") if (CONFIG_EXAMPLE_MODEM_DEVICE_BG96) set(device_srcs sock_commands_bg96.cpp) elseif(CONFIG_EXAMPLE_MODEM_DEVICE_SIM7600) set(device_srcs sock_commands_sim7600.cpp) elseif(CONFIG_EXAMPLE_MODEM_DEVICE_ESPAT) set(device_srcs sock_commands_espat.cpp) + set(module_dir "espat_module") endif() if(CONFIG_ESP_MODEM_ENABLE_DEVELOPMENT_MODE) @@ -16,4 +18,4 @@ idf_component_register(SRCS "modem_client.cpp" "${command_dir}/sock_dce.cpp" "tcp_transport_at.cpp" "${device_srcs}" - INCLUDE_DIRS "." "${command_dir}") + INCLUDE_DIRS "." "${command_dir}" "${module_dir}") 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 cff9196b9e..d6d597b1c9 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 @@ -8,8 +8,8 @@ #include #include "esp_vfs.h" #include "esp_vfs_eventfd.h" + #include "sock_dce.hpp" -#include "cxx_include/esp_modem_command_library_utils.hpp" namespace sock_dce { @@ -305,21 +305,6 @@ std::unique_ptr create(const esp_modem::dce_config *config, std::shared_ptr } -esp_modem::command_result DCE::sync() -{ - return esp_modem::dce_commands::generic_command_common(dte.get(), "AT\r\n"); -} - -esp_modem::command_result DCE::set_echo(bool on) -{ - return esp_modem::dce_commands::generic_command_common(dte.get(), "ATE0\r\n"); -} - -esp_modem::command_result DCE::set_pdp_context(esp_modem::PdpContext &pdp) -{ - return esp_modem::command_result::OK; -} - /** * @brief Opens network in AT command mode * @return OK, FAIL or TIMEOUT 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 0515f0b7d5..5add603f0f 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 @@ -8,6 +8,7 @@ #include "cxx_include/esp_modem_api.hpp" #include #include "sock_commands.hpp" +#include "sock_module.hpp" #include #pragma once @@ -97,13 +98,10 @@ class Responder { std::shared_ptr &dte; }; -class DCE : public ::esp_modem::GenericModule { - using esp_modem::GenericModule::GenericModule; +class DCE : public Module { + using Module::Module; public: - esp_modem::command_result sync() override; - esp_modem::command_result set_echo(bool on) override; - esp_modem::command_result set_pdp_context(esp_modem::PdpContext &pdp) override; /** * @brief Opens network in AT command mode * @return OK, FAIL or TIMEOUT diff --git a/components/esp_modem/examples/modem_tcp_client/main/espat_module/sock_module.hpp b/components/esp_modem/examples/modem_tcp_client/main/espat_module/sock_module.hpp new file mode 100644 index 0000000000..f7184ea166 --- /dev/null +++ b/components/esp_modem/examples/modem_tcp_client/main/espat_module/sock_module.hpp @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_modem_config.h" +#include "cxx_include/esp_modem_api.hpp" + +#pragma once + +namespace sock_dce { + +class Module: public esp_modem::GenericModule { + using esp_modem::GenericModule::GenericModule; +public: + + esp_modem::command_result sync() override; + esp_modem::command_result set_echo(bool on) override; + esp_modem::command_result set_pdp_context(esp_modem::PdpContext &pdp) override; + +}; + +} 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 d64fceecf3..92e14013f6 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 @@ -8,6 +8,7 @@ #include "cxx_include/esp_modem_api.hpp" #include #include "sock_commands.hpp" +#include "sock_module.hpp" #include #pragma once @@ -97,8 +98,8 @@ class Responder { std::shared_ptr &dte; }; -class DCE : public ::esp_modem::GenericModule { - using esp_modem::GenericModule::GenericModule; +class DCE : public Module { + using Module::Module; public: // --- ESP-MODEM command module starts here --- diff --git a/components/esp_modem/examples/modem_tcp_client/main/generic_module/sock_module.hpp b/components/esp_modem/examples/modem_tcp_client/main/generic_module/sock_module.hpp new file mode 100644 index 0000000000..6d198278af --- /dev/null +++ b/components/esp_modem/examples/modem_tcp_client/main/generic_module/sock_module.hpp @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_modem_config.h" +#include "cxx_include/esp_modem_api.hpp" + +#pragma once + +namespace sock_dce { + +using Module = esp_modem::GenericModule; + +} 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 bf377e611f..092ea16a25 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 @@ -124,15 +124,14 @@ extern "C" void app_main(void) mqtt_config.network.transport = ssl; #endif 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, NULL); + 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 - dce->set_rx_mode(1); if (!dce->connect(BROKER_URL, BROKER_PORT)) { ESP_LOGE(TAG, "Failed to start DCE"); return; } - while (1) { + while (true) { while (dce->perform_sock()) { ESP_LOGV(TAG, "...performing"); } 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 b4d9536826..a293a3f6f6 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 @@ -9,9 +9,31 @@ #include "sock_commands.hpp" #include "cxx_include/esp_modem_command_library_utils.hpp" #include "sock_dce.hpp" +#include "sock_module.hpp" static const char *TAG = "sock_commands_espat"; +namespace sock_dce { + +using namespace esp_modem; + +command_result Module::sync() +{ + return dce_commands::generic_command_common(dte.get(), "AT\r\n"); +} + +command_result Module::set_echo(bool on) +{ + return dce_commands::generic_command_common(dte.get(), "ATE0\r\n"); +} + +command_result Module::set_pdp_context(PdpContext &pdp) +{ + return command_result::OK; +} + +} + namespace sock_commands { using namespace esp_modem; @@ -34,8 +56,14 @@ command_result net_open(CommandableIf *t) ESP_LOGE(TAG, "Failed to connect to WiFi"); return ret; } - ESP_LOGI(TAG, "WiFi connected successfully"); + + // Set passive receive mode (1) for better control + 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; } @@ -123,8 +151,7 @@ command_result get_ip(CommandableIf *t, std::string &ip) command_result set_rx_mode(CommandableIf *t, int mode) { ESP_LOGE(TAG, "%s", __func__); - // Set passive receive mode (1) for better control - // Active mode (0) would send +IPD automatically + // 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"; return dce_commands::generic_command(t, cmd, "OK", "ERROR", 1000); } diff --git a/components/esp_modem/examples/modem_tcp_client/sdkconfig.ci.default b/components/esp_modem/examples/modem_tcp_client/sdkconfig.ci.default new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/esp_modem/examples/modem_tcp_client/sdkconfig.ci.espat b/components/esp_modem/examples/modem_tcp_client/sdkconfig.ci.espat new file mode 100644 index 0000000000..1533b8934b --- /dev/null +++ b/components/esp_modem/examples/modem_tcp_client/sdkconfig.ci.espat @@ -0,0 +1,2 @@ +CONFIG_IDF_TARGET="esp32" +CONFIG_EXAMPLE_MODEM_DEVICE_ESPAT=y From cf7ca4f2086b19a99be136f2876d3699d5a1cdae Mon Sep 17 00:00:00 2001 From: David Cermak Date: Wed, 16 Jul 2025 15:34:45 +0200 Subject: [PATCH 04/12] fix(modem): Make tcp-client example support multiple connections --- .../main/command/sock_dce.cpp | 33 ++++++++++++++++--- .../main/command/sock_dce.hpp | 17 +++++++++- .../modem_tcp_client/main/modem_client.cpp | 3 +- 3 files changed, 47 insertions(+), 6 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..0caeea185f 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 @@ -6,6 +6,7 @@ #include #include +#include // for std::find #include "esp_vfs.h" #include "esp_vfs_eventfd.h" @@ -15,6 +16,25 @@ 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; + +// 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() { @@ -104,6 +124,7 @@ void DCE::close_sock() close(sock); sock = -1; } + close(data_ready_fd); dte->on_read(nullptr); const int retries = 5; int i = 0; @@ -224,15 +245,18 @@ void DCE::start_listening(int port) 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([this](uint8_t *data, size_t len) { - this->perform_at(data, len); + read_callback(data, len); return esp_modem::command_result::TIMEOUT; }); if (!at.start_connecting(host, port)) { ESP_LOGE(TAG, "Unable to start connecting"); dte->on_read(nullptr); + close(data_ready_fd); return false; } state = status::CONNECTING; @@ -241,12 +265,13 @@ bool DCE::connect(std::string host, int port) bool DCE::init() { + if (network_init) { + return true; + } + network_init = true; 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; 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..09425e64c7 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 @@ -99,8 +99,14 @@ class Responder { }; class DCE : public Module { - using Module::Module; public: + // Constructor and destructor for managing dce_list + // explicit GenericModule(std::shared_ptr dte, std::unique_ptr pdp): + // dte(std::move(dte)), pdp(std::move(pdp)) {} + // explicit GenericModule(std::shared_ptr dte, ); + // + DCE(std::shared_ptr dte_arg, const esp_modem_dce_config *config); + ~DCE(); /** * @brief Opens network in AT command mode @@ -237,6 +243,15 @@ class DCE : public Module { int sock {-1}; int listen_sock {-1}; int data_ready_fd {-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); + } + } + }; std::unique_ptr create(const esp_modem::dce_config *config, std::shared_ptr dte); } 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..9cc62dbae3 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 @@ -73,6 +73,7 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_ } } +// sock_dce::DCE::dce_list{}; extern "C" void app_main(void) { @@ -104,7 +105,7 @@ 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; From d2feaf3fe53387703ddc491cc1b12ebd1171a425 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Wed, 16 Jul 2025 15:56:56 +0200 Subject: [PATCH 05/12] fix(modem): Add link id to tcp API --- .../main/sock_commands_espat.cpp | 63 +++++++++++++------ 1 file changed, 44 insertions(+), 19 deletions(-) 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..0d376829c0 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,6 +58,14 @@ command_result net_open(CommandableIf *t) } ESP_LOGI(TAG, "WiFi connected successfully"); + // 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 enable multiple connections mode"); + return ret; + } + ESP_LOGI(TAG, "Multiple connections mode enabled"); + // Set passive receive mode (1) for better control ret = set_rx_mode(t, 1); if (ret != command_result::OK) { @@ -82,28 +90,36 @@ command_result tcp_open(CommandableIf *t, const std::string &host, int port, int { 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"); - } + // Use link ID 0 for now (can be extended to support multiple concurrent connections) + const int link_id = 0; - // 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); + // 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", host.c_str(), port); + 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", host.c_str(), port); + 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) { 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"; + + // In multiple connections mode, response format is: ,CLOSED + std::string expected_response = std::to_string(link_id) + ",CLOSED"; + + return dce_commands::generic_command(t, close_cmd, expected_response, "ERROR", 5000); } command_result tcp_send(CommandableIf *t, uint8_t *data, size_t len) @@ -150,9 +166,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 +182,23 @@ 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 + const int link_id = 0; + 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 + const int link_id = 0; + 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 + const int link_id = 0; + std::string cmd = "AT+CIPSTART=" + std::to_string(link_id) + ",\"TCP\",\"" + host + "\"," + std::to_string(port) + "\r\n"; send_cmd(cmd); return true; } @@ -299,7 +323,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; } @@ -321,7 +346,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,7 +354,7 @@ 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) + // Handle data notifications - in multiple connections mode, format is +IPD,, if (response.find("+IPD,") != std::string::npos) { uint64_t data_ready = 1; write(data_ready_fd, &data_ready, sizeof(data_ready)); From 1cf0041b455730625c22a18119a6fbf74895315d Mon Sep 17 00:00:00 2001 From: David Cermak Date: Wed, 16 Jul 2025 16:48:49 +0200 Subject: [PATCH 06/12] fix(modem): Use link-id from responder class --- .../main/generate/sock_dce.cpp | 32 ++++++++-- .../main/generate/sock_dce.hpp | 13 +++- .../main/sock_commands_espat.cpp | 62 +++++++------------ 3 files changed, 62 insertions(+), 45 deletions(-) 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..b2e07fec3f 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 @@ -15,6 +15,27 @@ 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; + +// 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() { @@ -224,10 +245,12 @@ void DCE::start_listening(int port) 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([this](uint8_t *data, size_t len) { - this->perform_at(data, len); + read_callback(data, len); return esp_modem::command_result::TIMEOUT; }); if (!at.start_connecting(host, port)) { @@ -241,12 +264,13 @@ bool DCE::connect(std::string host, int port) bool DCE::init() { + if (network_init) { + return true; + } + network_init = true; 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; 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..f62000b23a 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 @@ -63,7 +63,9 @@ class Responder { return total_len; } + int link_id{s_link_id++}; private: + static int s_link_id; static constexpr size_t buffer_size = 512; bool on_read(char *data, size_t len) @@ -99,8 +101,9 @@ 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" @@ -204,6 +207,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/sock_commands_espat.cpp b/components/esp_modem/examples/modem_tcp_client/main/sock_commands_espat.cpp index 0d376829c0..c0e53d3e30 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 @@ -86,28 +86,28 @@ 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_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) { @@ -122,21 +122,6 @@ command_result tcp_close(CommandableIf *t) return dce_commands::generic_command(t, close_cmd, expected_response, "ERROR", 5000); } -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; -} - -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; -} command_result get_ip(CommandableIf *t, std::string &ip) { @@ -183,21 +168,18 @@ void Responder::start_sending(size_t len) data_to_send = len; send_stat = 0; // For multiple connections mode, include link ID - const int link_id = 0; send_cmd("AT+CIPSEND=" + std::to_string(link_id) + "," + std::to_string(len) + "\r\n"); } void Responder::start_receiving(size_t len) { // For multiple connections mode, include link ID - const int link_id = 0; send_cmd("AT+CIPRECVDATA=" + std::to_string(link_id) + "," + std::to_string(len) + "\r\n"); } bool Responder::start_connecting(std::string host, int port) { // For multiple connections mode, include link ID - const int link_id = 0; std::string cmd = "AT+CIPSTART=" + std::to_string(link_id) + ",\"TCP\",\"" + host + "\"," + std::to_string(port) + "\r\n"; send_cmd(cmd); return true; From 740479249d2a0e6c0fb1d68491d62e1940a55545 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Thu, 17 Jul 2025 12:42:16 +0200 Subject: [PATCH 07/12] fix(modem): WIP for multiple conn --- .../main/generate/sock_dce.cpp | 47 +++++++++++++++---- .../main/generate/sock_dce.hpp | 5 ++ .../modem_tcp_client/main/modem_client.cpp | 38 ++++++++++++--- .../main/sock_commands_espat.cpp | 42 ++++++++++++----- 4 files changed, 104 insertions(+), 28 deletions(-) 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 b2e07fec3f..9f56dc0851 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 @@ -19,6 +19,7 @@ constexpr auto const *TAG = "sock_dce"; 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) @@ -82,13 +83,22 @@ bool DCE::perform_sock() void DCE::perform_at(uint8_t *data, size_t len) { - ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_VERBOSE); + 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; @@ -103,10 +113,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; @@ -152,6 +166,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; @@ -160,8 +177,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; } @@ -170,6 +187,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) { @@ -247,12 +267,15 @@ 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([this](uint8_t *data, size_t len) { - read_callback(data, len); - return esp_modem::command_result::TIMEOUT; - }); + // 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); @@ -268,6 +291,8 @@ bool DCE::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); @@ -311,6 +336,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 f62000b23a..843e12c5e1 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); @@ -64,6 +65,7 @@ class Responder { } 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; @@ -72,6 +74,7 @@ class Responder { { #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; @@ -144,6 +147,7 @@ esp_modem::return_type name(ESP_MODEM_COMMAND_PARAMS(__VA_ARGS__)); return 0; } at.clear_offsets(); + // TODO: MUTEX state = status::RECEIVING; uint64_t data; read(data_ready_fd, &data, sizeof(data)); @@ -166,6 +170,7 @@ esp_modem::return_type name(ESP_MODEM_COMMAND_PARAMS(__VA_ARGS__)); if (!wait_to_idle(timeout_ms)) { return -1; } + // TODO: MUTEX 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 9cc62dbae3..4dafe31573 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,7 +23,8 @@ #include "tcp_transport_mbedtls.h" #include "tcp_transport_at.h" -#define BROKER_URL "test.mosquitto.org" +// #define BROKER_URL "test.mosquitto.org" +#define BROKER_URL "192.168.0.39" #define BROKER_PORT 1883 @@ -31,6 +32,8 @@ 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) { @@ -75,9 +78,10 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_ // sock_dce::DCE::dce_list{}; +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()); @@ -111,12 +115,34 @@ extern "C" void app_main(void) return; } + xTaskCreate(perform, "perform", 4096, dce.get(), 4, nullptr); + + // vTaskDelay(pdMS_TO_TICKS(15000)); + /* 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); +} + +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[12] += id; // assumes different client id per each thread esp_mqtt_client_config_t mqtt_config = {}; - mqtt_config.broker.address.port = BROKER_PORT; + mqtt_config.broker.address.port = BROKER_PORT + id; 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); + 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()); @@ -146,8 +172,6 @@ extern "C" void app_main(void) ESP_LOGI(TAG, "Network reinitialized, retrying"); } } -#else - vTaskDelay(portMAX_DELAY); #endif - + xEventGroupSetBits(event_group, id ? DCE0_DONE : DCE1_DONE); } 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 c0e53d3e30..13d817e295 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 @@ -67,11 +67,18 @@ command_result net_open(CommandableIf *t) ESP_LOGI(TAG, "Multiple connections mode enabled"); // Set passive receive mode (1) for better control - ret = set_rx_mode(t, 1); - if (ret != command_result::OK) { - ESP_LOGE(TAG, "Failed to set preferred Rx mode"); - return ret; + 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; } @@ -111,6 +118,7 @@ command_result net_close(CommandableIf *t) command_result tcp_close(CommandableIf *t) { + return command_result::OK; ESP_LOGV(TAG, "%s", __func__); // Use link ID 0 for closing connection const int link_id = 0; @@ -192,6 +200,12 @@ Responder::ret Responder::recv(uint8_t *data, size_t len) auto *recv_data = (char *)data; if (data_to_recv == 0) { + // const std::string_view error_str = "ERROR"; + // const std::string_view data_sv(recv_data, len); + // if (data_sv.find(error_str) == std::string_view::npos) { + // // no data, + // return ret::OK; + // } const std::string_view head = "+CIPRECVDATA:"; // Find the response header @@ -200,7 +214,7 @@ Responder::ret Responder::recv(uint8_t *data, size_t len) }); if (head_pos == recv_data + len) { - return ret::FAIL; + return ret::IN_PROGRESS; } // Find the end of the length field @@ -316,6 +330,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) { @@ -336,13 +361,6 @@ Responder::ret Responder::check_async_replies(status state, std::string_view &re return ret::FAIL; } - // Handle data notifications - in multiple connections mode, format is +IPD,, - 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) { From 1886fb4862a7dfccd9d9ed86f29ff2e0571752de Mon Sep 17 00:00:00 2001 From: David Cermak Date: Fri, 18 Jul 2025 19:16:30 +0200 Subject: [PATCH 08/12] fix(modem): Support for TCP-TRANSPORT mode --- .../main/command/sock_dce.cpp | 54 ++++++++++++++----- .../main/command/sock_dce.hpp | 33 +++++++----- .../main/generate/sock_dce.hpp | 8 ++- .../modem_tcp_client/main/modem_client.cpp | 23 +++++--- 4 files changed, 81 insertions(+), 37 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 0caeea185f..0320606dbc 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 @@ -6,7 +6,6 @@ #include #include -#include // for std::find #include "esp_vfs.h" #include "esp_vfs_eventfd.h" @@ -17,8 +16,10 @@ namespace sock_dce { constexpr auto const *TAG = "sock_dce"; // Definition of the static member variables -std::vector DCE::dce_list{}; +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) @@ -36,6 +37,7 @@ DCE::~DCE() } } + bool DCE::perform_sock() { if (listen_sock == -1) { @@ -81,13 +83,22 @@ bool DCE::perform_sock() void DCE::perform_at(uint8_t *data, size_t len) { - ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_VERBOSE); + 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; @@ -102,10 +113,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; @@ -124,7 +139,6 @@ void DCE::close_sock() close(sock); sock = -1; } - close(data_ready_fd); dte->on_read(nullptr); const int retries = 5; int i = 0; @@ -152,6 +166,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; @@ -160,8 +177,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; } @@ -170,6 +187,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) { @@ -247,16 +267,18 @@ 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([this](uint8_t *data, size_t len) { - read_callback(data, len); - return esp_modem::command_result::TIMEOUT; - }); + // 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); - close(data_ready_fd); return false; } state = status::CONNECTING; @@ -269,6 +291,8 @@ bool DCE::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); @@ -312,6 +336,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 09425e64c7..b8ec870174 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; @@ -100,11 +105,6 @@ class Responder { class DCE : public Module { public: - // Constructor and destructor for managing dce_list - // explicit GenericModule(std::shared_ptr dte, std::unique_ptr pdp): - // dte(std::move(dte)), pdp(std::move(pdp)) {} - // explicit GenericModule(std::shared_ptr dte, ); - // DCE(std::shared_ptr dte_arg, const esp_modem_dce_config *config); ~DCE(); @@ -169,6 +169,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)); @@ -190,6 +193,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); @@ -230,6 +236,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(); @@ -243,15 +257,6 @@ class DCE : public Module { int sock {-1}; int listen_sock {-1}; int data_ready_fd {-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); - } - } - }; std::unique_ptr create(const esp_modem::dce_config *config, std::shared_ptr dte); } 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 843e12c5e1..01675e4065 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 @@ -147,7 +147,9 @@ esp_modem::return_type name(ESP_MODEM_COMMAND_PARAMS(__VA_ARGS__)); return 0; } at.clear_offsets(); - // TODO: MUTEX + 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)); @@ -170,7 +172,9 @@ esp_modem::return_type name(ESP_MODEM_COMMAND_PARAMS(__VA_ARGS__)); if (!wait_to_idle(timeout_ms)) { return -1; } - // TODO: MUTEX + 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); 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 4dafe31573..0c9ff22591 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 @@ -24,7 +24,7 @@ #include "tcp_transport_at.h" // #define BROKER_URL "test.mosquitto.org" -#define BROKER_URL "192.168.0.39" +#define BROKER_URL "broker.emqx.io" #define BROKER_PORT 1883 @@ -76,8 +76,6 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_ } } -// sock_dce::DCE::dce_list{}; - static void perform(void* ctx); extern "C" void app_main(void) @@ -117,7 +115,8 @@ extern "C" void app_main(void) xTaskCreate(perform, "perform", 4096, dce.get(), 4, nullptr); - // vTaskDelay(pdMS_TO_TICKS(15000)); + // 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()) { @@ -127,6 +126,13 @@ extern "C" void app_main(void) 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 + // we release smart pointers, as this example does never exit + // and in tcp-transport option we don't need a task to run + // so we exit main and keep DCE's "running" + dce.release(); + dce1.release(); +#endif } static void perform(void* ctx) @@ -137,7 +143,7 @@ static void perform(void* ctx) const int id = counter++; mqtt_client_id[12] += id; // assumes different client id per each thread esp_mqtt_client_config_t mqtt_config = {}; - mqtt_config.broker.address.port = BROKER_PORT + id; + mqtt_config.broker.address.port = BROKER_PORT; // + id; mqtt_config.session.message_retransmit_timeout = 10000; mqtt_config.credentials.client_id = mqtt_client_id; #ifndef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT @@ -145,10 +151,10 @@ static void perform(void* ctx) 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()); - esp_transport_handle_t ssl = esp_transport_tls_init(at); + esp_transport_handle_t at = esp_transport_at_init(dce); + // esp_transport_handle_t ssl = esp_transport_tls_init(at); - mqtt_config.network.transport = ssl; + mqtt_config.network.transport = at; #endif 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); @@ -174,4 +180,5 @@ static void perform(void* ctx) } #endif xEventGroupSetBits(event_group, id ? DCE0_DONE : DCE1_DONE); + vTaskDelete(nullptr); } From fcff07ee6a7e29fd0e3292f7d44c394f3711efba Mon Sep 17 00:00:00 2001 From: David Cermak Date: Mon, 21 Jul 2025 10:16:12 +0200 Subject: [PATCH 09/12] fix(modem): Cleanup CI --- .../main/sock_commands_bg96.cpp | 14 +++++-- .../main/sock_commands_sim7600.cpp | 41 +++++++++++-------- 2 files changed, 34 insertions(+), 21 deletions(-) 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..abb1a25418 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 @@ -295,9 +295,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()); + // Handle data notifications - in multiple connections mode, format is +IPD,, if (response.find("+QIURC: \"recv\",0") != std::string::npos) { uint64_t data_ready = 1; write(data_ready_fd, &data_ready, sizeof(data_ready)); @@ -321,9 +321,17 @@ Responder::ret Responder::check_async_replies(status state, std::string_view &re } else if (response.find("+QIURC: \"closed\",0") != std::string::npos) { 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; 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..78fbf53ee1 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) { @@ -340,21 +340,26 @@ Responder::ret Responder::connect(std::string_view response) return Responder::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) { - ESP_LOGD(TAG, "response %.*s", static_cast(response.size()), response.data()); if (response.find("+CIPRXGET: 1") != std::string::npos) { uint64_t data_ready = 1; write(data_ready_fd, &data_ready, sizeof(data_ready)); ESP_LOGD(TAG, "Got data on modem!"); } - if (state == status::SENDING) { - return send(response); - } else if (state == status::CONNECTING) { - return connect(response); - } return ret::IN_PROGRESS; - } Responder::ret Responder::process_data(status state, uint8_t *data, size_t len) From de6500ee2a93e05c517479ec14dae7b8deec1167 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Mon, 21 Jul 2025 15:37:41 +0200 Subject: [PATCH 10/12] fix(modem): Fix build step --- .../main/command/sock_dce.cpp | 1 + .../main/generate/sock_dce.cpp | 1 + .../modem_tcp_client/main/modem_client.cpp | 17 +++++----- .../main/sock_commands_bg96.cpp | 2 +- .../main/sock_commands_espat.cpp | 32 +++++++++---------- .../main/sock_commands_sim7600.cpp | 15 +++++---- 6 files changed, 36 insertions(+), 32 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 0320606dbc..fa24db332f 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" 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 9f56dc0851..fd4e97ad1d 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" 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 0c9ff22591..f8814784f5 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 @@ -24,8 +24,9 @@ #include "tcp_transport_at.h" // #define BROKER_URL "test.mosquitto.org" -#define BROKER_URL "broker.emqx.io" -#define BROKER_PORT 1883 +// #define BROKER_URL "broker.emqx.io" +#define BROKER_URL "192.168.0.18" +#define BROKER_PORT 8883 static const char *TAG = "modem_client"; @@ -141,20 +142,20 @@ static void perform(void* ctx) char mqtt_client_id[] = "MQTT_CLIENT_0"; static int counter = 0; const int id = counter++; - mqtt_client_id[12] += id; // assumes different client id per each thread + mqtt_client_id[12] += id; // assumes a different client id per each thread esp_mqtt_client_config_t mqtt_config = {}; - mqtt_config.broker.address.port = BROKER_PORT; // + id; + mqtt_config.broker.address.port = BROKER_PORT + id; 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"; + mqtt_config.broker.address.uri = "mqtts://127.0.0.1"; dce->start_listening(BROKER_PORT + id); #else - mqtt_config.broker.address.uri = "mqtt://" BROKER_URL; + mqtt_config.broker.address.uri = "mqtts://" BROKER_URL; esp_transport_handle_t at = esp_transport_at_init(dce); - // esp_transport_handle_t ssl = esp_transport_tls_init(at); + esp_transport_handle_t ssl = esp_transport_tls_init(at); - mqtt_config.network.transport = at; + mqtt_config.network.transport = ssl; #endif 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); 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 abb1a25418..d16ce2add3 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 @@ -133,7 +133,7 @@ Responder::ret Responder::recv(uint8_t *data, size_t len) 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; 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 13d817e295..670e8be0b5 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 @@ -200,23 +200,13 @@ Responder::ret Responder::recv(uint8_t *data, size_t len) auto *recv_data = (char *)data; if (data_to_recv == 0) { - // const std::string_view error_str = "ERROR"; - // const std::string_view data_sv(recv_data, len); - // if (data_sv.find(error_str) == std::string_view::npos) { - // // no data, - // return ret::OK; - // } 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) { + 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) { @@ -265,12 +255,22 @@ 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; + 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; 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; 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 78fbf53ee1..2e7da01abd 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 @@ -343,23 +343,24 @@ Responder::ret Responder::connect(std::string_view response) 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) { + if (response.find("+CIPRXGET: 1") != std::string::npos) { uint64_t data_ready = 1; write(data_ready_fd, &data_ready, sizeof(data_ready)); - ESP_LOGD(TAG, "Data available notification"); + ESP_LOGD(TAG, "Got data on modem!"); } return ret::IN_PROGRESS; } Responder::ret Responder::check_async_replies(status state, std::string_view &response) { - if (response.find("+CIPRXGET: 1") != std::string::npos) { - uint64_t data_ready = 1; - write(data_ready_fd, &data_ready, sizeof(data_ready)); - ESP_LOGD(TAG, "Got data on modem!"); + ESP_LOGD(TAG, "response %.*s", static_cast(response.size()), response.data()); + if (state == status::SENDING) { + return send(response); + } else if (state == status::CONNECTING) { + return connect(response); } return ret::IN_PROGRESS; + } Responder::ret Responder::process_data(status state, uint8_t *data, size_t len) From ecf41607b10abe188ab4ba1b386e5086eadc82d2 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Wed, 13 Aug 2025 14:11:58 +0200 Subject: [PATCH 11/12] fix(modem): Make ssl connection working with esp-at --- .../modem_tcp_client/main/command/sock_dce.cpp | 10 ++++++---- .../modem_tcp_client/main/generate/sock_dce.cpp | 10 ++++++---- .../examples/modem_tcp_client/main/modem_client.cpp | 12 +++++++----- .../modem_tcp_client/main/sock_commands_espat.cpp | 3 +++ 4 files changed, 22 insertions(+), 13 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 fa24db332f..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 @@ -84,10 +84,12 @@ bool DCE::perform_sock() void DCE::perform_at(uint8_t *data, size_t len) { - std::string_view resp_sv((char *)data, len); - at.check_urc(state, resp_sv); - if (state == status::IDLE) { - return; + 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)) { 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 fd4e97ad1d..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 @@ -84,10 +84,12 @@ bool DCE::perform_sock() void DCE::perform_at(uint8_t *data, size_t len) { - std::string_view resp_sv((char *)data, len); - at.check_urc(state, resp_sv); - if (state == status::IDLE) { - return; + 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)) { 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 f8814784f5..7131455f89 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,10 +23,11 @@ #include "tcp_transport_mbedtls.h" #include "tcp_transport_at.h" -// #define BROKER_URL "test.mosquitto.org" +#define BROKER_URL "test.mosquitto.org" // #define BROKER_URL "broker.emqx.io" -#define BROKER_URL "192.168.0.18" +// #define BROKER_URL "192.168.0.18" #define BROKER_PORT 8883 +// #define BROKER_PORT 1883 static const char *TAG = "modem_client"; @@ -142,19 +143,19 @@ static void perform(void* ctx) char mqtt_client_id[] = "MQTT_CLIENT_0"; static int counter = 0; const int id = counter++; - mqtt_client_id[12] += id; // assumes a different client id per each thread + 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 + id; 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.port = BROKER_PORT + id; mqtt_config.broker.address.uri = "mqtts://127.0.0.1"; dce->start_listening(BROKER_PORT + id); #else + mqtt_config.broker.address.port = BROKER_PORT; mqtt_config.broker.address.uri = "mqtts://" BROKER_URL; esp_transport_handle_t at = esp_transport_at_init(dce); esp_transport_handle_t ssl = esp_transport_tls_init(at); - mqtt_config.network.transport = ssl; #endif esp_mqtt_client_handle_t mqtt_client = esp_mqtt_client_init(&mqtt_config); @@ -180,6 +181,7 @@ static void perform(void* ctx) } } #endif + vTaskDelay(portMAX_DELAY); xEventGroupSetBits(event_group, id ? DCE0_DONE : DCE1_DONE); vTaskDelete(nullptr); } 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 670e8be0b5..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 @@ -257,12 +257,15 @@ Responder::ret Responder::recv(uint8_t *data, size_t len) ok_pos = (char *)memchr(recv_data + actual_len + 1, 'O', MIN_MESSAGE); 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; } } From f3ab0d5a2d690b104ecdb93478a5e13e922654b8 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Thu, 14 Aug 2025 10:40:39 +0200 Subject: [PATCH 12/12] fix(modem): Add support for BG96 and SIM7600 --- .../modem_tcp_client/main/modem_client.cpp | 38 +++++++++++-------- .../main/sock_commands_bg96.cpp | 35 ++++++++++++----- .../main/sock_commands_sim7600.cpp | 17 +++++---- 3 files changed, 58 insertions(+), 32 deletions(-) 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 7131455f89..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,11 +23,17 @@ #include "tcp_transport_mbedtls.h" #include "tcp_transport_at.h" -#define BROKER_URL "test.mosquitto.org" -// #define BROKER_URL "broker.emqx.io" -// #define BROKER_URL "192.168.0.18" +#define USE_TLS 0 + +#define BROKER_HOST "test.mosquitto.org" +#if USE_TLS +#define BROKER_SCHEME "mqtts" #define BROKER_PORT 8883 -// #define BROKER_PORT 1883 +#else +#define BROKER_SCHEME "mqtt" +#define BROKER_PORT 1883 +#endif +#define BROKER_URL BROKER_SCHEME "://" BROKER_HOST static const char *TAG = "modem_client"; @@ -85,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()); + esp_log_level_set("*", ESP_LOG_INFO); event_group = xEventGroupCreate(); @@ -129,11 +136,9 @@ extern "C" void app_main(void) xEventGroupWaitBits(event_group, DCE0_DONE | DCE1_DONE, pdFALSE, pdTRUE, portMAX_DELAY); #ifdef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT - // we release smart pointers, as this example does never exit - // and in tcp-transport option we don't need a task to run - // so we exit main and keep DCE's "running" - dce.release(); - dce1.release(); + // 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 } @@ -149,20 +154,24 @@ static void perform(void* ctx) mqtt_config.credentials.client_id = mqtt_client_id; #ifndef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT mqtt_config.broker.address.port = BROKER_PORT + id; - mqtt_config.broker.address.uri = "mqtts://127.0.0.1"; + mqtt_config.broker.address.uri = BROKER_SCHEME "://127.0.0.1"; dce->start_listening(BROKER_PORT + id); #else mqtt_config.broker.address.port = BROKER_PORT; - mqtt_config.broker.address.uri = "mqtts://" BROKER_URL; + 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; } @@ -176,12 +185,11 @@ static void perform(void* ctx) 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"); } } #endif - vTaskDelay(portMAX_DELAY); 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 d16ce2add3..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,6 +130,7 @@ 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) { @@ -140,6 +141,13 @@ Responder::ret Responder::recv(uint8_t *data, size_t len) 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; } @@ -297,8 +309,7 @@ Responder::ret Responder::connect(std::string_view response) Responder::ret Responder::check_urc(status state, std::string_view &response) { - // Handle data notifications - in multiple connections mode, format is +IPD,, - 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_urc(status state, std::string_view &response) 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,7 +332,8 @@ Responder::ret Responder::check_urc(status state, std::string_view &response) 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; @@ -350,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_sim7600.cpp b/components/esp_modem/examples/modem_tcp_client/main/sock_commands_sim7600.cpp index 2e7da01abd..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 @@ -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; } @@ -342,8 +343,10 @@ Responder::ret Responder::connect(std::string_view response) Responder::ret Responder::check_urc(status state, std::string_view &response) { - // Handle data notifications - in multiple connections mode, format is +IPD,, - 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!");