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..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,7 +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) @@ -14,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/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/command/sock_dce.cpp b/components/esp_modem/examples/modem_tcp_client/main/command/sock_dce.cpp index d6d597b1c9..19375a28b7 100644 --- a/components/esp_modem/examples/modem_tcp_client/main/command/sock_dce.cpp +++ b/components/esp_modem/examples/modem_tcp_client/main/command/sock_dce.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include #include #include #include "esp_vfs.h" @@ -15,6 +16,28 @@ namespace sock_dce { constexpr auto const *TAG = "sock_dce"; +// Definition of the static member variables +std::vector DCE::dce_list{}; +bool DCE::network_init = false; +int Responder::s_link_id = 0; +SemaphoreHandle_t Responder::s_dte_mutex{}; + +// Constructor - add this DCE instance to the static list +DCE::DCE(std::shared_ptr dte_arg, const esp_modem_dce_config *config) + : Module(std::move(dte_arg), config) +{ + dce_list.push_back(this); +} + +// Destructor - remove this DCE instance from the static list +DCE::~DCE() +{ + auto it = std::find(dce_list.begin(), dce_list.end(), this); + if (it != dce_list.end()) { + dce_list.erase(it); + } +} + bool DCE::perform_sock() { @@ -61,13 +84,24 @@ bool DCE::perform_sock() void DCE::perform_at(uint8_t *data, size_t len) { - ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_VERBOSE); + if (state != status::RECEIVING) { + std::string_view resp_sv((char *)data, len); + at.check_urc(state, resp_sv); + if (state == status::IDLE) { + return; + } + } + ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_INFO); switch (at.process_data(state, data, len)) { case Responder::ret::OK: + ESP_LOGW(TAG, "GIVE data %d", at.link_id); + xSemaphoreGive(at.s_dte_mutex); state = status::IDLE; signal.set(IDLE); return; case Responder::ret::FAIL: + ESP_LOGW(TAG, "GIVE data %d", at.link_id); + xSemaphoreGive(at.s_dte_mutex); state = status::FAILED; signal.set(IDLE); return; @@ -82,10 +116,14 @@ void DCE::perform_at(uint8_t *data, size_t len) std::string_view response((char *)data, len); switch (at.check_async_replies(state, response)) { case Responder::ret::OK: + ESP_LOGW(TAG, "GIVE command %d", at.link_id); + xSemaphoreGive(at.s_dte_mutex); state = status::IDLE; signal.set(IDLE); return; case Responder::ret::FAIL: + ESP_LOGW(TAG, "GIVE command %d", at.link_id); + xSemaphoreGive(at.s_dte_mutex); state = status::FAILED; signal.set(IDLE); return; @@ -131,6 +169,9 @@ bool DCE::at_to_sock() close_sock(); return false; } + ESP_LOGI(TAG, "TAKE RECV %d", at.link_id); + xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); + ESP_LOGE(TAG, "TAKE RECV %d", at.link_id); state = status::RECEIVING; at.start_receiving(at.get_buf_len()); return true; @@ -139,8 +180,8 @@ bool DCE::at_to_sock() bool DCE::sock_to_at() { ESP_LOGD(TAG, "socket read: data available"); - if (!signal.wait(IDLE, 1000)) { - ESP_LOGE(TAG, "Failed to get idle"); + if (!signal.wait(IDLE, 5000)) { + ESP_LOGE(TAG, "Failed to get idle 2"); close_sock(); return false; } @@ -149,6 +190,9 @@ bool DCE::sock_to_at() close_sock(); return false; } + ESP_LOGI(TAG, "TAKE SEND %d", at.link_id); + xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); + ESP_LOGE(TAG, "TAKE SEND %d", at.link_id); state = status::SENDING; int len = ::recv(sock, at.get_buf(), at.get_buf_len(), 0); if (len < 0) { @@ -224,12 +268,17 @@ void DCE::start_listening(int port) bool DCE::connect(std::string host, int port) { - dte->on_read(nullptr); - tcp_close(); - dte->on_read([this](uint8_t *data, size_t len) { - this->perform_at(data, len); - return esp_modem::command_result::TIMEOUT; - }); + data_ready_fd = eventfd(0, EFD_SUPPORT_ISR); + assert(data_ready_fd > 0); + // dte->on_read(nullptr); + // tcp_close(); + // dte->on_read([](uint8_t *data, size_t len) { + // read_callback(data, len); + // return esp_modem::command_result::TIMEOUT; + // }); + ESP_LOGI(TAG, "TAKE CONNECT %d", at.link_id); + xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); + ESP_LOGE(TAG, "TAKE CONNECT %d", at.link_id); if (!at.start_connecting(host, port)) { ESP_LOGE(TAG, "Unable to start connecting"); dte->on_read(nullptr); @@ -241,12 +290,15 @@ bool DCE::connect(std::string host, int port) bool DCE::init() { + if (network_init) { + return true; + } + network_init = true; + Responder::s_dte_mutex = xSemaphoreCreateBinary(); + xSemaphoreGive(at.s_dte_mutex); esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT(); esp_vfs_eventfd_register(&config); - data_ready_fd = eventfd(0, EFD_SUPPORT_ISR); - assert(data_ready_fd > 0); - dte->on_read(nullptr); const int retries = 5; int i = 0; @@ -287,6 +339,10 @@ bool DCE::init() esp_modem::Task::Delay(5000); } ESP_LOGI(TAG, "Got IP %s", ip_addr.c_str()); + dte->on_read([](uint8_t *data, size_t len) { + read_callback(data, len); + return esp_modem::command_result::TIMEOUT; + }); return true; } diff --git a/components/esp_modem/examples/modem_tcp_client/main/command/sock_dce.hpp b/components/esp_modem/examples/modem_tcp_client/main/command/sock_dce.hpp index e73478d5f1..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 @@ -8,6 +8,7 @@ #include "cxx_include/esp_modem_api.hpp" #include #include "sock_commands.hpp" +#include "sock_module.hpp" #include #pragma once @@ -33,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); @@ -62,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; @@ -97,9 +103,10 @@ class Responder { std::shared_ptr &dte; }; -class DCE : public ::esp_modem::GenericModule { - using esp_modem::GenericModule::GenericModule; +class DCE : public Module { public: + DCE(std::shared_ptr dte_arg, const esp_modem_dce_config *config); + ~DCE(); /** * @brief Opens network in AT command mode @@ -162,6 +169,9 @@ class DCE : public ::esp_modem::GenericModule { 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)); @@ -183,6 +193,9 @@ class DCE : public ::esp_modem::GenericModule { 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); @@ -223,6 +236,14 @@ class DCE : public ::esp_modem::GenericModule { } return -1; } + static std::vector dce_list; + static bool network_init; + static void read_callback(uint8_t *data, size_t len) + { + for (auto dce : dce_list) { + dce->perform_at(data, len); + } + } private: esp_modem::SignalGroup signal; void close_sock(); diff --git a/components/esp_modem/examples/modem_tcp_client/main/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.cpp b/components/esp_modem/examples/modem_tcp_client/main/generate/sock_dce.cpp index 3b4bec8055..f82bbb560f 100644 --- a/components/esp_modem/examples/modem_tcp_client/main/generate/sock_dce.cpp +++ b/components/esp_modem/examples/modem_tcp_client/main/generate/sock_dce.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include #include #include #include "esp_vfs.h" @@ -15,6 +16,28 @@ namespace sock_dce { constexpr auto const *TAG = "sock_dce"; +// Definition of the static member variables +std::vector DCE::dce_list{}; +bool DCE::network_init = false; +int Responder::s_link_id = 0; +SemaphoreHandle_t Responder::s_dte_mutex{}; + +// Constructor - add this DCE instance to the static list +DCE::DCE(std::shared_ptr dte_arg, const esp_modem_dce_config *config) + : Module(std::move(dte_arg), config) +{ + dce_list.push_back(this); +} + +// Destructor - remove this DCE instance from the static list +DCE::~DCE() +{ + auto it = std::find(dce_list.begin(), dce_list.end(), this); + if (it != dce_list.end()) { + dce_list.erase(it); + } +} + bool DCE::perform_sock() { @@ -61,13 +84,24 @@ bool DCE::perform_sock() void DCE::perform_at(uint8_t *data, size_t len) { - ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_VERBOSE); + if (state != status::RECEIVING) { + std::string_view resp_sv((char *)data, len); + at.check_urc(state, resp_sv); + if (state == status::IDLE) { + return; + } + } + ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_INFO); switch (at.process_data(state, data, len)) { case Responder::ret::OK: + ESP_LOGW(TAG, "GIVE data %d", at.link_id); + xSemaphoreGive(at.s_dte_mutex); state = status::IDLE; signal.set(IDLE); return; case Responder::ret::FAIL: + ESP_LOGW(TAG, "GIVE data %d", at.link_id); + xSemaphoreGive(at.s_dte_mutex); state = status::FAILED; signal.set(IDLE); return; @@ -82,10 +116,14 @@ void DCE::perform_at(uint8_t *data, size_t len) std::string_view response((char *)data, len); switch (at.check_async_replies(state, response)) { case Responder::ret::OK: + ESP_LOGW(TAG, "GIVE command %d", at.link_id); + xSemaphoreGive(at.s_dte_mutex); state = status::IDLE; signal.set(IDLE); return; case Responder::ret::FAIL: + ESP_LOGW(TAG, "GIVE command %d", at.link_id); + xSemaphoreGive(at.s_dte_mutex); state = status::FAILED; signal.set(IDLE); return; @@ -131,6 +169,9 @@ bool DCE::at_to_sock() close_sock(); return false; } + ESP_LOGI(TAG, "TAKE RECV %d", at.link_id); + xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); + ESP_LOGE(TAG, "TAKE RECV %d", at.link_id); state = status::RECEIVING; at.start_receiving(at.get_buf_len()); return true; @@ -139,8 +180,8 @@ bool DCE::at_to_sock() bool DCE::sock_to_at() { ESP_LOGD(TAG, "socket read: data available"); - if (!signal.wait(IDLE, 1000)) { - ESP_LOGE(TAG, "Failed to get idle"); + if (!signal.wait(IDLE, 5000)) { + ESP_LOGE(TAG, "Failed to get idle 2"); close_sock(); return false; } @@ -149,6 +190,9 @@ bool DCE::sock_to_at() close_sock(); return false; } + ESP_LOGI(TAG, "TAKE SEND %d", at.link_id); + xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); + ESP_LOGE(TAG, "TAKE SEND %d", at.link_id); state = status::SENDING; int len = ::recv(sock, at.get_buf(), at.get_buf_len(), 0); if (len < 0) { @@ -224,12 +268,17 @@ void DCE::start_listening(int port) bool DCE::connect(std::string host, int port) { - dte->on_read(nullptr); - tcp_close(); - dte->on_read([this](uint8_t *data, size_t len) { - this->perform_at(data, len); - return esp_modem::command_result::TIMEOUT; - }); + data_ready_fd = eventfd(0, EFD_SUPPORT_ISR); + assert(data_ready_fd > 0); + // dte->on_read(nullptr); + // tcp_close(); + // dte->on_read([](uint8_t *data, size_t len) { + // read_callback(data, len); + // return esp_modem::command_result::TIMEOUT; + // }); + ESP_LOGI(TAG, "TAKE CONNECT %d", at.link_id); + xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); + ESP_LOGE(TAG, "TAKE CONNECT %d", at.link_id); if (!at.start_connecting(host, port)) { ESP_LOGE(TAG, "Unable to start connecting"); dte->on_read(nullptr); @@ -241,12 +290,15 @@ bool DCE::connect(std::string host, int port) bool DCE::init() { + if (network_init) { + return true; + } + network_init = true; + Responder::s_dte_mutex = xSemaphoreCreateBinary(); + xSemaphoreGive(at.s_dte_mutex); esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT(); esp_vfs_eventfd_register(&config); - data_ready_fd = eventfd(0, EFD_SUPPORT_ISR); - assert(data_ready_fd > 0); - dte->on_read(nullptr); const int retries = 5; int i = 0; @@ -287,6 +339,10 @@ bool DCE::init() esp_modem::Task::Delay(5000); } ESP_LOGI(TAG, "Got IP %s", ip_addr.c_str()); + dte->on_read([](uint8_t *data, size_t len) { + read_callback(data, len); + return esp_modem::command_result::TIMEOUT; + }); return true; } diff --git a/components/esp_modem/examples/modem_tcp_client/main/generate/sock_dce.hpp b/components/esp_modem/examples/modem_tcp_client/main/generate/sock_dce.hpp index d64fceecf3..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 @@ -8,6 +8,7 @@ #include "cxx_include/esp_modem_api.hpp" #include #include "sock_commands.hpp" +#include "sock_module.hpp" #include #pragma once @@ -33,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); @@ -62,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; @@ -97,9 +103,10 @@ class Responder { std::shared_ptr &dte; }; -class DCE : public ::esp_modem::GenericModule { - using esp_modem::GenericModule::GenericModule; +class DCE : public 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" @@ -140,6 +147,9 @@ esp_modem::return_type name(ESP_MODEM_COMMAND_PARAMS(__VA_ARGS__)); return 0; } at.clear_offsets(); + ESP_LOGI("TAG", "TAKE RECV %d", at.link_id); + xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); + ESP_LOGE("TAG", "TAKE RECV %d", at.link_id); state = status::RECEIVING; uint64_t data; read(data_ready_fd, &data, sizeof(data)); @@ -162,6 +172,9 @@ esp_modem::return_type name(ESP_MODEM_COMMAND_PARAMS(__VA_ARGS__)); if (!wait_to_idle(timeout_ms)) { return -1; } + ESP_LOGI("TAG", "TAKE SEND %d", at.link_id); + xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY); + ESP_LOGE("TAG", "TAKE SEND %d", at.link_id); state = status::SENDING; memcpy(at.get_buf(), buffer, len_to_send); ESP_LOG_BUFFER_HEXDUMP("dce", at.get_buf(), len, ESP_LOG_VERBOSE); @@ -203,6 +216,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/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 558ba5316e..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 @@ -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,14 +23,25 @@ #include "tcp_transport_mbedtls.h" #include "tcp_transport_at.h" -#define BROKER_URL "mqtt.eclipseprojects.io" +#define USE_TLS 0 + +#define BROKER_HOST "test.mosquitto.org" +#if USE_TLS +#define BROKER_SCHEME "mqtts" #define BROKER_PORT 8883 +#else +#define BROKER_SCHEME "mqtt" +#define BROKER_PORT 1883 +#endif +#define BROKER_URL BROKER_SCHEME "://" BROKER_HOST static const char *TAG = "modem_client"; static EventGroupHandle_t event_group = NULL; static const int CONNECT_BIT = BIT0; static const int GOT_DATA_BIT = BIT2; +static const int DCE0_DONE = BIT3; +static const int DCE1_DONE = BIT4; static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) { @@ -73,13 +84,14 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_ } } +static void perform(void* ctx); extern "C" void app_main(void) { - /* Init and register system/core components */ ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); + esp_log_level_set("*", ESP_LOG_INFO); event_group = xEventGroupCreate(); @@ -104,34 +116,66 @@ extern "C" void app_main(void) esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(CONFIG_EXAMPLE_MODEM_APN); /* create the DCE and initialize network manually (using AT commands) */ - auto dce = sock_dce::create(&dce_config, std::move(dte)); + auto dce = sock_dce::create(&dce_config, dte); if (!dce->init()) { ESP_LOGE(TAG, "Failed to setup network"); return; } + xTaskCreate(perform, "perform", 4096, dce.get(), 4, nullptr); + + // vTaskDelay(pdMS_TO_TICKS(5000)); + // vTaskDelay(portMAX_DELAY); + /* create another DCE to serve a new connection */ + auto dce1 = sock_dce::create(&dce_config, dte); + if (!dce1->init()) { + ESP_LOGE(TAG, "Failed to setup network"); + return; + } + xTaskCreate(perform, "perform", 4096, dce1.get(), 4, nullptr); + + xEventGroupWaitBits(event_group, DCE0_DONE | DCE1_DONE, pdFALSE, pdTRUE, portMAX_DELAY); +#ifdef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT + // this example does never keeps both DCEs running and in tcp-transport option + // we don't need a task to run so we exit main and keep DCE's "running" + vTaskDelay(portMAX_DELAY); +#endif +} + +static void perform(void* ctx) +{ + auto dce = static_cast(ctx); + char mqtt_client_id[] = "MQTT_CLIENT_0"; + static int counter = 0; + const int id = counter++; + mqtt_client_id[sizeof(mqtt_client_id) - 2] += id; // assumes a different client id per each thread esp_mqtt_client_config_t mqtt_config = {}; - mqtt_config.broker.address.port = BROKER_PORT; mqtt_config.session.message_retransmit_timeout = 10000; + mqtt_config.credentials.client_id = mqtt_client_id; #ifndef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT - mqtt_config.broker.address.uri = "mqtts://127.0.0.1"; - dce->start_listening(BROKER_PORT); + mqtt_config.broker.address.port = BROKER_PORT + id; + mqtt_config.broker.address.uri = BROKER_SCHEME "://127.0.0.1"; + dce->start_listening(BROKER_PORT + id); #else - mqtt_config.broker.address.uri = "mqtt://" BROKER_URL; - esp_transport_handle_t at = esp_transport_at_init(dce.get()); + mqtt_config.broker.address.port = BROKER_PORT; + mqtt_config.broker.address.uri = BROKER_URL; + esp_transport_handle_t at = esp_transport_at_init(dce); +#if USE_TLS esp_transport_handle_t ssl = esp_transport_tls_init(at); - mqtt_config.network.transport = ssl; -#endif +#else + mqtt_config.network.transport = at; +#endif // USE_TLS +#endif // CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT esp_mqtt_client_handle_t mqtt_client = esp_mqtt_client_init(&mqtt_config); - esp_mqtt_client_register_event(mqtt_client, static_cast(ESP_EVENT_ANY_ID), mqtt_event_handler, 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 - if (!dce->connect(BROKER_URL, BROKER_PORT)) { + if (!dce->connect(BROKER_HOST, BROKER_PORT)) { ESP_LOGE(TAG, "Failed to start DCE"); return; } - while (1) { + while (true) { while (dce->perform_sock()) { ESP_LOGV(TAG, "...performing"); } @@ -141,12 +185,11 @@ extern "C" void app_main(void) ESP_LOGE(TAG, "Failed to reinit network"); return; } - if (!dce->connect(BROKER_URL, BROKER_PORT)) { + if (!dce->connect(BROKER_HOST, BROKER_PORT)) { ESP_LOGI(TAG, "Network reinitialized, retrying"); } } -#else - vTaskDelay(portMAX_DELAY); #endif - + xEventGroupSetBits(event_group, id ? DCE0_DONE : DCE1_DONE); + vTaskDelete(nullptr); } diff --git a/components/esp_modem/examples/modem_tcp_client/main/sock_commands_bg96.cpp b/components/esp_modem/examples/modem_tcp_client/main/sock_commands_bg96.cpp index d5a0dcd782..1916b64553 100644 --- a/components/esp_modem/examples/modem_tcp_client/main/sock_commands_bg96.cpp +++ b/components/esp_modem/examples/modem_tcp_client/main/sock_commands_bg96.cpp @@ -109,17 +109,17 @@ void Responder::start_sending(size_t len) { data_to_send = len; send_stat = 0; - send_cmd("AT+QISEND=0," + std::to_string(len) + "\r"); + send_cmd("AT+QISEND=" + std::to_string(link_id) + "," + std::to_string(len) + "\r"); } void Responder::start_receiving(size_t len) { - send_cmd("AT+QIRD=0," + std::to_string(len) + "\r"); + send_cmd("AT+QIRD=" + std::to_string(link_id) + "," + std::to_string(len) + "\r"); } bool Responder::start_connecting(std::string host, int port) { - send_cmd(R"(AT+QIOPEN=1,0,"TCP",")" + host + "\"," + std::to_string(port) + "\r"); + send_cmd(std::string("AT+QIOPEN=1,") + std::to_string(link_id) + R"(,"TCP",")" + host + "\"," + std::to_string(port) + "\r"); return true; } @@ -130,16 +130,24 @@ Responder::ret Responder::recv(uint8_t *data, size_t len) auto *recv_data = (char *)data; if (data_to_recv == 0) { const std::string_view head = "+QIRD: "; +again: const std::string_view recv_data_view = std::string_view(recv_data, len); auto head_pos_found = recv_data_view.find(head); if (head_pos_found == std::string_view::npos) { - return ret::FAIL; + return ret::IN_PROGRESS; } auto *head_pos = recv_data + head_pos_found; auto next_nl = (char *)memchr(head_pos + head.size(), '\n', MIN_MESSAGE); if (next_nl == nullptr) { + if (head_pos + head.size() + 1 < recv_data + len) { + // might be that we misinterpreted the URC +QIRD: <>,<>,<> (notification) with the +QIRD: <> (read data) + // so we try to find the next +QIRD: + recv_data = head_pos + head.size() + 1; + goto again; + } + ESP_LOGD(TAG, "no new line found"); return ret::FAIL; } @@ -151,7 +159,9 @@ Responder::ret Responder::recv(uint8_t *data, size_t len) ESP_LOGD(TAG, "Received: actual len=%d", actual_len); if (actual_len == 0) { ESP_LOGD(TAG, "no data received"); - return ret::FAIL; + data_to_recv = 0; + // return OK here, as BG96 would keep unacked data and notifies us with +QIRD: 0 + return ret::OK; } if (actual_len > buffer_size) { @@ -182,6 +192,7 @@ Responder::ret Responder::recv(uint8_t *data, size_t len) last_pos = (char *)memchr(recv_data + 1 + actual_len, 'O', MIN_MESSAGE); if (last_pos == nullptr || last_pos[1] != 'K') { data_to_recv = 0; + ESP_LOGE(TAG, "no OK after data"); return ret::FAIL; } } @@ -222,7 +233,7 @@ Responder::ret Responder::send(std::string_view response) { if (send_stat == 3) { if (response.find("SEND OK") != std::string::npos) { - send_cmd("AT+QISEND=0,0\r"); + send_cmd(std::string("AT+QISEND=") + std::to_string(link_id) + ",0\r"); send_stat++; return ret::IN_PROGRESS; } else if (response.find("SEND FAIL") != std::string::npos) { @@ -284,7 +295,8 @@ Responder::ret Responder::send(std::string_view response) Responder::ret Responder::connect(std::string_view response) { - if (response.find("+QIOPEN: 0,0") != std::string::npos) { + std::string open_response = "+QIOPEN: " + std::to_string(link_id) + ",0"; + if (response.find(open_response) != std::string::npos) { ESP_LOGI(TAG, "Connected!"); return ret::OK; } @@ -295,10 +307,9 @@ Responder::ret Responder::connect(std::string_view response) return Responder::ret::IN_PROGRESS; } -Responder::ret Responder::check_async_replies(status state, std::string_view &response) +Responder::ret Responder::check_urc(status state, std::string_view &response) { - ESP_LOGD(TAG, "response %.*s", static_cast(response.size()), response.data()); - if (response.find("+QIURC: \"recv\",0") != std::string::npos) { + if (response.find(std::string("+QIURC: \"recv\",") + std::to_string(link_id)) != std::string::npos) { uint64_t data_ready = 1; write(data_ready_fd, &data_ready, sizeof(data_ready)); ESP_LOGD(TAG, "Got data on modem!"); @@ -309,6 +320,9 @@ Responder::ret Responder::check_async_replies(status state, std::string_view &re response = response.substr(head_pos + head.size()); int next_cr = response.find('\r'); if (next_cr != std::string::npos) { + if (next_cr < 2) { + return ret::IN_PROGRESS; + } response = response.substr(next_cr - 2, next_cr); if (response.find(",0") != std::string::npos) { ESP_LOGV(TAG, "Receiving done"); @@ -318,12 +332,21 @@ Responder::ret Responder::check_async_replies(status state, std::string_view &re ESP_LOGD(TAG, "Got data on modem!"); } } - } else if (response.find("+QIURC: \"closed\",0") != std::string::npos) { + } else if (response.find(std::string("+QIURC: \"closed\",") + std::to_string(link_id)) != std::string::npos) { + ESP_LOGE(TAG, "Connection closed"); return ret::FAIL; } + return ret::IN_PROGRESS; +} + + +Responder::ret Responder::check_async_replies(status state, std::string_view &response) +{ + ESP_LOGD(TAG, "response %.*s", static_cast(response.size()), response.data()); if (state == status::SENDING) { return send(response); - } else if (state == status::CONNECTING) { + } + if (state == status::CONNECTING) { return connect(response); } return ret::IN_PROGRESS; @@ -342,7 +365,7 @@ Responder::ret Responder::process_data(status state, uint8_t *data, size_t len) status Responder::pending() { - send_cmd("AT+QISEND=0,0\r"); + send_cmd(std::string("AT+QISEND=") + std::to_string(link_id) + ",0\r"); return status::SENDING; } diff --git a/components/esp_modem/examples/modem_tcp_client/main/sock_commands_espat.cpp b/components/esp_modem/examples/modem_tcp_client/main/sock_commands_espat.cpp new file mode 100644 index 0000000000..98e3376016 --- /dev/null +++ b/components/esp_modem/examples/modem_tcp_client/main/sock_commands_espat.cpp @@ -0,0 +1,394 @@ +/* + * 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" +#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; + +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\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\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"); + return ret; + } + 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 + 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; +} + +command_result net_close(CommandableIf *t) +{ + ESP_LOGV(TAG, "%s", __func__); + // Disconnect from WiFi + 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)"); + } + return command_result::OK; +} + +// command_result tcp_open(CommandableIf *t, const std::string &host, int port, int timeout) +// { +// ESP_LOGV(TAG, "%s", __func__); +// +// // Use link ID 0 for now (can be extended to support multiple concurrent connections) +// const int link_id = 0; +// +// // Establish TCP connection with link ID for multiple connections mode +// std::string tcp_cmd = "AT+CIPSTART=" + std::to_string(link_id) + ",\"TCP\",\"" + host + "\"," + std::to_string(port) + "\r\n"; +// +// // In multiple connections mode, response format is: ,CONNECT +// std::string expected_response = std::to_string(link_id) + ",CONNECT"; +// +// auto ret = dce_commands::generic_command(t, tcp_cmd, expected_response, "ERROR", timeout); +// if (ret != command_result::OK) { +// ESP_LOGE(TAG, "Failed to establish TCP connection to %s:%d on link %d", host.c_str(), port, link_id); +// return ret; +// } +// +// ESP_LOGI(TAG, "TCP connection established to %s:%d on link %d", host.c_str(), port, link_id); +// return command_result::OK; +// } + +command_result tcp_close(CommandableIf *t) +{ + return command_result::OK; + ESP_LOGV(TAG, "%s", __func__); + // 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 get_ip(CommandableIf *t, std::string &ip) +{ + ESP_LOGV(TAG, "%s", __func__); + std::string out; + auto ret = dce_commands::at_raw(t, "AT+CIFSR\r\n", out, "OK", "ERROR", 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__); + // 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(link_id) + "," + std::to_string(mode) + "\r\n"; + 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; + // For multiple connections mode, include link ID + send_cmd("AT+CIPSEND=" + std::to_string(link_id) + "," + std::to_string(len) + "\r\n"); +} + +void Responder::start_receiving(size_t len) +{ + // For multiple connections mode, include link ID + send_cmd("AT+CIPRECVDATA=" + std::to_string(link_id) + "," + std::to_string(len) + "\r\n"); +} + +bool Responder::start_connecting(std::string host, int port) +{ + // For multiple connections mode, include link ID + std::string cmd = "AT+CIPSTART=" + std::to_string(link_id) + ",\"TCP\",\"" + host + "\"," + std::to_string(port) + "\r\n"; + send_cmd(cmd); + return true; +} + +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:"; + 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) { + 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; + ESP_LOGE(TAG, "Missed 'OK' marker"); + return ret::OK; + return ret::FAIL; + } + if (ok_pos + 1 < recv_data + len && ok_pos[1] != 'K') { + // we ignore the condition when receiving 'O' as the last character in the last batch, + // don't wait for the 'K' in the next run, assume the data are valid and let higher layers deal with it. + data_to_recv = 0; + ESP_LOGE(TAG, "Missed 'OK' marker2"); + return ret::FAIL; + } + } + if (ok_pos != nullptr && (char *)data + len - ok_pos - 2 > MIN_MESSAGE) { + // check for async replies after the Recv header + std::string_view response((char *)ok_pos + 2 /* OK */, (char *)data + len - ok_pos); + check_urc(status::RECEIVING, response); + } + // Reset and prepare for next receive + data_to_recv = 0; + return ret::OK; +} + +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) +{ + // 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; + } + if (response.find("ERROR") != std::string::npos) { + ESP_LOGE(TAG, "Failed to connect"); + return ret::FAIL; + } + 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) +{ + 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 (multiple connections format: ,CONNECT or ,CLOSED) + if (response.find("CONNECT") != std::string::npos && state == status::CONNECTING) { + return connect(response); + } else if (response.find("CLOSED") != std::string::npos) { + ESP_LOGW(TAG, "TCP connection closed"); + return ret::FAIL; + } + + 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 diff --git a/components/esp_modem/examples/modem_tcp_client/main/sock_commands_sim7600.cpp b/components/esp_modem/examples/modem_tcp_client/main/sock_commands_sim7600.cpp index 5e1343ceb0..619adacf66 100644 --- a/components/esp_modem/examples/modem_tcp_client/main/sock_commands_sim7600.cpp +++ b/components/esp_modem/examples/modem_tcp_client/main/sock_commands_sim7600.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -18,13 +18,13 @@ using namespace esp_modem; command_result net_open(CommandableIf *term) { - ESP_LOGV(TAG, "%s", __func__ ); + ESP_LOGV(TAG, "%s", __func__); std::string response; auto ret = dce_commands::generic_get_string(term, "AT+NETOPEN?\r", response, 1000); if (ret != command_result::OK) { return ret; } - ESP_LOGV(TAG, "%s", response.data() ); + ESP_LOGV(TAG, "%s", response.data()); if (response.find("+NETOPEN: 1") != std::string::npos) { ESP_LOGD(TAG, "Already there"); ret = command_result::OK; @@ -42,23 +42,23 @@ command_result net_open(CommandableIf *term) command_result net_close(CommandableIf *term) { - ESP_LOGV(TAG, "%s", __func__ ); + ESP_LOGV(TAG, "%s", __func__); return dce_commands::generic_command(term, "AT+NETCLOSE\r", "+NETCLOSE:", "ERROR", 30000); } command_result tcp_open(CommandableIf *term, const std::string &host, int port, int timeout) { - ESP_LOGV(TAG, "%s", __func__ ); + ESP_LOGV(TAG, "%s", __func__); auto ret = dce_commands::generic_command(term, "AT+CIPRXGET=1\r", "OK", "ERROR", 50000); if (ret != command_result::OK) { ESP_LOGE(TAG, "Setting Rx mode failed!"); return ret; } - ESP_LOGV(TAG, "%s", __func__ ); + ESP_LOGV(TAG, "%s", __func__); std::string ip_open = R"(AT+CIPOPEN=0,"TCP",")" + host + "\"," + std::to_string(port) + "\r"; ret = dce_commands::generic_command(term, ip_open, "+CIPOPEN: 0,0", "ERROR", timeout); if (ret != command_result::OK) { - ESP_LOGE(TAG, "%s Failed", __func__ ); + ESP_LOGE(TAG, "%s Failed", __func__); return ret; } return command_result::OK; @@ -66,13 +66,13 @@ command_result tcp_open(CommandableIf *term, const std::string &host, int port, command_result tcp_close(CommandableIf *term) { - ESP_LOGV(TAG, "%s", __func__ ); + ESP_LOGV(TAG, "%s", __func__); return dce_commands::generic_command(term, "AT+CIPCLOSE=0\r", "+CIPCLOSE:", "ERROR", 10000); } command_result tcp_send(CommandableIf *term, uint8_t *data, size_t len) { - ESP_LOGV(TAG, "%s", __func__ ); + ESP_LOGV(TAG, "%s", __func__); std::string send = "AT+CIPSEND=0," + std::to_string(len) + "\r"; auto ret = term->command(send, [&](uint8_t *data, size_t len) { std::string_view response((char *)data, len); @@ -107,7 +107,7 @@ command_result tcp_send(CommandableIf *term, uint8_t *data, size_t len) uint8_t ctrl_z = '\x1A'; term->write(&ctrl_z, 1); int count = 0; - while (ret == command_result::TIMEOUT && count++ < 1000 ) { + while (ret == command_result::TIMEOUT && count++ < 1000) { vTaskDelay(pdMS_TO_TICKS(1000)); } term->on_read(nullptr); @@ -116,7 +116,7 @@ command_result tcp_send(CommandableIf *term, uint8_t *data, size_t len) command_result tcp_recv(CommandableIf *term, uint8_t *data, size_t len, size_t &out_len) { - ESP_LOGV(TAG, "%s", __func__ ); + ESP_LOGV(TAG, "%s", __func__); std::string out; auto ret = dce_commands::generic_get_string(term, "AT+CIPRXGET=4,0\r", out); if (ret != command_result::OK) { @@ -195,17 +195,17 @@ void Responder::start_sending(size_t len) { data_to_send = len; send_stat = 0; - send_cmd("AT+CIPSEND=0," + std::to_string(len) + "\r"); + send_cmd("AT+CIPSEND=" + std::to_string(link_id) + "," + std::to_string(len) + "\r"); } void Responder::start_receiving(size_t len) { - send_cmd("AT+CIPRXGET=2,0," + std::to_string(len) + "\r"); + send_cmd("AT+CIPRXGET=2," + std::to_string(link_id) + "," + std::to_string(len) + "\r"); } bool Responder::start_connecting(std::string host, int port) { - send_cmd(R"(AT+CIPOPEN=0,"TCP",")" + host + "\"," + std::to_string(port) + "\r"); + send_cmd(std::string("AT+CIPOPEN=") + std::to_string(link_id) + R"(,"TCP",")" + host + "\"," + std::to_string(port) + "\r"); return true; } @@ -215,7 +215,7 @@ Responder::ret Responder::recv(uint8_t *data, size_t len) size_t actual_len = 0; auto *recv_data = (char *)data; if (data_to_recv == 0) { - static constexpr std::string_view head = "+CIPRXGET: 2,0,"; + const std::string head = std::string("+CIPRXGET: 2,") + std::to_string(link_id) + ","; auto head_pos = std::search(recv_data, recv_data + len, head.begin(), head.end()); if (head_pos == recv_data + len) { return ret::FAIL; @@ -329,7 +329,8 @@ Responder::ret Responder::send(std::string_view response) Responder::ret Responder::connect(std::string_view response) { - if (response.find("+CIPOPEN: 0,0") != std::string::npos) { + std::string open_response = "+CIPOPEN: " + std::to_string(link_id) + ",0"; + if (response.find(open_response) != std::string::npos) { ESP_LOGI(TAG, "Connected!"); return ret::OK; } @@ -340,14 +341,22 @@ Responder::ret Responder::connect(std::string_view response) return Responder::ret::IN_PROGRESS; } -Responder::ret Responder::check_async_replies(status state, std::string_view &response) +Responder::ret Responder::check_urc(status state, std::string_view &response) { - ESP_LOGD(TAG, "response %.*s", static_cast(response.size()), response.data()); - if (response.find("+CIPRXGET: 1") != std::string::npos) { + // 1. When is set to 1 and the 2-4 mode will take effect. + // 2. If AT+CIPRXGET=1, it will report +CIPRXGET: 1,(multi client) when + const std::string expected = std::string("+CIPRXGET: 1,") + std::to_string(link_id); + if (response.find(expected) != std::string::npos) { uint64_t data_ready = 1; write(data_ready_fd, &data_ready, sizeof(data_ready)); ESP_LOGD(TAG, "Got data on modem!"); } + return ret::IN_PROGRESS; +} + +Responder::ret Responder::check_async_replies(status state, std::string_view &response) +{ + ESP_LOGD(TAG, "response %.*s", static_cast(response.size()), response.data()); if (state == status::SENDING) { return send(response); } else if (state == status::CONNECTING) { 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