diff --git a/.github/workflows/modem__build-host-tests.yml b/.github/workflows/modem__build-host-tests.yml index 7993324d1..79a8d992f 100644 --- a/.github/workflows/modem__build-host-tests.yml +++ b/.github/workflows/modem__build-host-tests.yml @@ -14,10 +14,14 @@ jobs: strategy: matrix: idf_ver: ["latest", "release-v4.2", "release-v4.3", "release-v4.4", "release-v5.0"] - example: ["pppos_client", "modem_console", "ap_to_pppos", "simple_cmux_client"] + example: ["pppos_client", "modem_console", "modem_tcp_client", "ap_to_pppos", "simple_cmux_client"] exclude: - idf_ver: "release-v4.2" example: simple_cmux_client + - idf_ver: "release-v4.2" + example: modem_tcp_client + - idf_ver: "release-v4.3" + example: modem_tcp_client include: - idf_ver: "release-v4.2" skip_config: usb diff --git a/components/esp_modem/examples/modem_console/CMakeLists.txt b/components/esp_modem/examples/modem_console/CMakeLists.txt index 12e4daf0a..eaf4959a8 100644 --- a/components/esp_modem/examples/modem_console/CMakeLists.txt +++ b/components/esp_modem/examples/modem_console/CMakeLists.txt @@ -1,6 +1,7 @@ # The following lines of boilerplate have to be in your project's CMakeLists # in this exact order for cmake to work correctly -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.8) +set(CMAKE_CXX_STANDARD 17) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(modem-console) diff --git a/components/esp_modem/examples/modem_console/main/CMakeLists.txt b/components/esp_modem/examples/modem_console/main/CMakeLists.txt index 9fe560cf0..e09da6eb3 100644 --- a/components/esp_modem/examples/modem_console/main/CMakeLists.txt +++ b/components/esp_modem/examples/modem_console/main/CMakeLists.txt @@ -1,5 +1,6 @@ idf_component_register(SRCS "modem_console_main.cpp" "console_helper.cpp" + "my_module_dce.cpp" "httpget_handle.c" "ping_handle.c" REQUIRES console esp_http_client nvs_flash diff --git a/components/esp_modem/examples/modem_console/main/Kconfig.projbuild b/components/esp_modem/examples/modem_console/main/Kconfig.projbuild index f568158c4..3eb51e488 100644 --- a/components/esp_modem/examples/modem_console/main/Kconfig.projbuild +++ b/components/esp_modem/examples/modem_console/main/Kconfig.projbuild @@ -17,7 +17,7 @@ menu "Example Configuration" choice EXAMPLE_MODEM_DEVICE prompt "Choose supported modem device (DCE)" - default EXAMPLE_MODEM_DEVICE_BG96 + default EXAMPLE_MODEM_DEVICE_SHINY help Select modem device connected to the ESP DTE. config EXAMPLE_MODEM_DEVICE_SHINY diff --git a/components/esp_modem/examples/modem_console/main/modem_console_main.cpp b/components/esp_modem/examples/modem_console/main/modem_console_main.cpp index a6ff09635..2f8052e0e 100644 --- a/components/esp_modem/examples/modem_console/main/modem_console_main.cpp +++ b/components/esp_modem/examples/modem_console/main/modem_console_main.cpp @@ -89,6 +89,14 @@ void wakeup_modem(void) vTaskDelay(pdMS_TO_TICKS(2000)); } +#ifdef CONFIG_EXAMPLE_MODEM_DEVICE_SHINY +command_result handle_urc(uint8_t *data, size_t len) +{ + ESP_LOG_BUFFER_HEXDUMP("on_read", data, len, ESP_LOG_INFO); + return command_result::TIMEOUT; +} +#endif + extern "C" void app_main(void) { static RTC_RODATA_ATTR char apn_rtc[20] = DEFAULT_APN; @@ -122,19 +130,19 @@ extern "C" void app_main(void) dte_config.dte_buffer_size = CONFIG_EXAMPLE_MODEM_UART_RX_BUFFER_SIZE / 2; auto uart_dte = create_uart_dte(&dte_config); -#if CONFIG_EXAMPLE_MODEM_DEVICE_SHINY == 1 +#if defined(CONFIG_EXAMPLE_MODEM_DEVICE_SHINY) ESP_LOGI(TAG, "Initializing esp_modem for the SHINY module..."); auto dce = create_shiny_dce(&dce_config, uart_dte, esp_netif); -#elif CONFIG_EXAMPLE_MODEM_DEVICE_BG96 == 1 +#elif defined(CONFIG_EXAMPLE_MODEM_DEVICE_BG96) ESP_LOGI(TAG, "Initializing esp_modem for the BG96 module..."); auto dce = create_BG96_dce(&dce_config, uart_dte, esp_netif); -#elif CONFIG_EXAMPLE_MODEM_DEVICE_SIM800 == 1 +#elif defined(CONFIG_EXAMPLE_MODEM_DEVICE_SIM800) ESP_LOGI(TAG, "Initializing esp_modem for the SIM800 module..."); auto dce = create_SIM800_dce(&dce_config, uart_dte, esp_netif); -#elif CONFIG_EXAMPLE_MODEM_DEVICE_SIM7000 == 1 +#elif defined(CONFIG_EXAMPLE_MODEM_DEVICE_SIM7000) ESP_LOGI(TAG, "Initializing esp_modem for the SIM7000 module..."); auto dce = create_SIM7000_dce(&dce_config, uart_dte, esp_netif); -#elif CONFIG_EXAMPLE_MODEM_DEVICE_SIM7070 == 1 +#elif defined(CONFIG_EXAMPLE_MODEM_DEVICE_SIM7070) ESP_LOGI(TAG, "Initializing esp_modem for the SIM7070 module..."); auto dce = create_SIM7070_dce(&dce_config, uart_dte, esp_netif); #elif CONFIG_EXAMPLE_MODEM_DEVICE_SIM7600 == 1 @@ -303,8 +311,9 @@ extern "C" void app_main(void) const ConsoleCommand GetOperatorName("get_operator_name", "reads the operator name", no_args, [&](ConsoleCommand * c) { std::string operator_name; + int act; ESP_LOGI(TAG, "Reading operator name..."); - CHECK_ERR(dce->get_operator_name(operator_name), ESP_LOGI(TAG, "OK. Operator name: %s", operator_name.c_str())); + CHECK_ERR(dce->get_operator_name(operator_name, act), ESP_LOGI(TAG, "OK. Operator name: %s", operator_name.c_str())); }); const struct GenericCommandArgs { @@ -356,6 +365,20 @@ extern "C" void app_main(void) ESP_LOGI(TAG, "Resetting the module..."); CHECK_ERR(dce->reset(), ESP_LOGI(TAG, "OK")); }); +#ifdef CONFIG_EXAMPLE_MODEM_DEVICE_SHINY + const ConsoleCommand HandleURC("urc", "toggle urc handling", no_args, [&](ConsoleCommand * c) { + static int cnt = 0; + if (++cnt % 2) { + ESP_LOGI(TAG, "Adding URC handler"); + dce->set_on_read(handle_urc); + } else { + ESP_LOGI(TAG, "URC removed"); + dce->set_on_read(nullptr); + } + return 0; + }); +#endif + const struct SetApn { SetApn(): apn(STR1, nullptr, nullptr, "", "APN (Access Point Name)") {} CommandArgs apn; diff --git a/components/esp_modem/examples/modem_console/main/my_module_dce.cpp b/components/esp_modem/examples/modem_console/main/my_module_dce.cpp new file mode 100644 index 000000000..3d35722b5 --- /dev/null +++ b/components/esp_modem/examples/modem_console/main/my_module_dce.cpp @@ -0,0 +1,114 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* Modem console example: Custom DCE + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include "cxx_include/esp_modem_api.hpp" +#include "cxx_include/esp_modem_dce_module.hpp" +#include "generate/esp_modem_command_declare.inc" +#include "my_module_dce.hpp" + +using namespace esp_modem; + +// +// Define preprocessor's forwarding to dce_commands definitions +// + +// Helper macros to handle multiple arguments of declared API +#define ARGS0 +#define ARGS1 , p1 +#define ARGS2 , p1 , p2 +#define ARGS3 , p1 , p2 , p3 +#define ARGS4 , p1 , p2 , p3, p4 +#define ARGS5 , p1 , p2 , p3, p4, p5 +#define ARGS6 , p1 , p2 , p3, p4, p5, p6 + +#define _ARGS(x) ARGS ## x +#define ARGS(x) _ARGS(x) + +#define CMD_OK (1) +#define CMD_FAIL (2) + +// +// Repeat all declarations and forward to the AT commands defined in esp_modem::dce_commands:: namespace +// +#define ESP_MODEM_DECLARE_DCE_COMMAND(name, return_type, arg_nr, ...) \ + return_type Shiny::DCE::name(__VA_ARGS__) { return esp_modem::dce_commands::name(this ARGS(arg_nr) ); } + +DECLARE_ALL_COMMAND_APIS(return_type name(...) ) + +#undef ESP_MODEM_DECLARE_DCE_COMMAND + +std::unique_ptr create_shiny_dce(const esp_modem::dce_config *config, + std::shared_ptr dte, + esp_netif_t *netif) +{ + return Shiny::Factory::create(config, std::move(dte), netif); +} + +/** + * @brief Definition of the command API, which makes the Shiny::DCE "command-able class" + * @param cmd Command to send + * @param got_line Recv line callback + * @param time_ms timeout in ms + * @param separator line break separator + * @return OK, FAIL or TIMEOUT + */ +command_result Shiny::DCE::command(const std::string &cmd, got_line_cb got_line, uint32_t time_ms, const char separator) +{ + if (!handling_urc) { + return dte->command(cmd, got_line, time_ms, separator); + } + handle_cmd = got_line; + signal.clear(CMD_OK | CMD_FAIL); + esp_modem::DTE_Command command{cmd}; + dte->write(command); + signal.wait_any(CMD_OK | CMD_FAIL, time_ms); + handle_cmd = nullptr; + if (signal.is_any(CMD_OK)) { + return esp_modem::command_result::OK; + } + if (signal.is_any(CMD_FAIL)) { + return esp_modem::command_result::FAIL; + } + return esp_modem::command_result::TIMEOUT; +} + +/** + * @brief Handle received data + * + * @param data Data received from the device + * @param len Length of the data + * @return standard command return code (OK|FAIL|TIMEOUT) + */ +command_result Shiny::DCE::handle_data(uint8_t *data, size_t len) +{ + if (std::memchr(data, '\n', len)) { + if (handle_urc) { + handle_urc(data, len); + } + if (handle_cmd) { + auto ret = handle_cmd(data, len); + if (ret == esp_modem::command_result::TIMEOUT) { + return command_result::TIMEOUT; + } + if (ret == esp_modem::command_result::OK) { + signal.set(CMD_OK); + } + if (ret == esp_modem::command_result::FAIL) { + signal.set(CMD_FAIL); + } + } + } + return command_result::TIMEOUT; +} diff --git a/components/esp_modem/examples/modem_console/main/my_module_dce.hpp b/components/esp_modem/examples/modem_console/main/my_module_dce.hpp index 907a5a744..43205bfb2 100644 --- a/components/esp_modem/examples/modem_console/main/my_module_dce.hpp +++ b/components/esp_modem/examples/modem_console/main/my_module_dce.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -11,6 +11,8 @@ #pragma once +#include + #include "cxx_include/esp_modem_dce_factory.hpp" #include "cxx_include/esp_modem_dce_module.hpp" @@ -28,13 +30,84 @@ public: } }; +namespace Shiny { + +using namespace esp_modem; + +class DCE : public esp_modem::DCE_T, public CommandableIf { +public: + using DCE_T::DCE_T; + + command_result + command(const std::string &cmd, got_line_cb got_line, uint32_t time_ms) override + { + return command(cmd, got_line, time_ms, '\n'); + } + + command_result + command(const std::string &cmd, got_line_cb got_line, uint32_t time_ms, const char separator) override; + + int write(uint8_t *data, size_t len) override + { + return dte->write(data, len); + } + + void on_read(got_line_cb on_data) override + { + return dte->on_read(on_data); + } + +#define ESP_MODEM_DECLARE_DCE_COMMAND(name, return_type, num, ...) \ + esp_modem::return_type name(__VA_ARGS__); + + DECLARE_ALL_COMMAND_APIS(forwards name(...)) + +#undef ESP_MODEM_DECLARE_DCE_COMMAND + + void set_on_read(esp_modem::got_line_cb on_read_cb) + { + if (on_read_cb == nullptr) { + handling_urc = false; + handle_urc = nullptr; + dte->on_read(nullptr); + return; + } + handle_urc = std::move(on_read_cb); + dte->on_read([this](uint8_t *data, size_t len) { + this->handle_data(data, len); + return command_result::TIMEOUT; + }); + handling_urc = true; + } + +private: + got_line_cb handle_urc{nullptr}; + got_line_cb handle_cmd{nullptr}; + SignalGroup signal; + bool handling_urc {false}; + + command_result handle_data(uint8_t *data, size_t len); + +}; + +class Factory: public ::esp_modem::dce_factory::Factory { +public: + + static std::unique_ptr create(const esp_modem::dce_config *config, + std::shared_ptr dte, + esp_netif_t *netif) + { + return build_generic_DCE>(config, std::move(dte), netif); + } + +}; + +} // namespace Shiny + /** * @brief Helper create method which employs the DCE factory for creating DCE objects templated by a custom module * @return unique pointer of the resultant DCE */ -std::unique_ptr create_shiny_dce(const esp_modem::dce_config *config, +std::unique_ptr create_shiny_dce(const esp_modem::dce_config *config, std::shared_ptr dte, - esp_netif_t *netif) -{ - return esp_modem::dce_factory::Factory::build_unique(config, std::move(dte), netif); -} + esp_netif_t *netif); diff --git a/components/esp_modem/examples/modem_tcp_client/CMakeLists.txt b/components/esp_modem/examples/modem_tcp_client/CMakeLists.txt new file mode 100644 index 000000000..2901da172 --- /dev/null +++ b/components/esp_modem/examples/modem_tcp_client/CMakeLists.txt @@ -0,0 +1,9 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.8) +set(CMAKE_CXX_STANDARD 17) + +set(EXTRA_COMPONENT_DIRS "../..") + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(modem_tcp_client) diff --git a/components/esp_modem/examples/modem_tcp_client/README.md b/components/esp_modem/examples/modem_tcp_client/README.md new file mode 100644 index 000000000..a95643571 --- /dev/null +++ b/components/esp_modem/examples/modem_tcp_client/README.md @@ -0,0 +1,10 @@ +# Modem TCP client + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +## Overview +This example demonstrates how to act as a MQTT client using modem's TCP commands (provided, the device supports "socket" related commands) + +### Supported IDF versions + +This example is supported from IDF `v4.4`. diff --git a/components/esp_modem/examples/modem_tcp_client/main/CMakeLists.txt b/components/esp_modem/examples/modem_tcp_client/main/CMakeLists.txt new file mode 100644 index 000000000..b7d3874ab --- /dev/null +++ b/components/esp_modem/examples/modem_tcp_client/main/CMakeLists.txt @@ -0,0 +1,11 @@ +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) +endif() + +idf_component_register(SRCS "modem_client.cpp" + "sock_dce.cpp" + "${device_srcs}" + INCLUDE_DIRS ".") +target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format") diff --git a/components/esp_modem/examples/modem_tcp_client/main/Kconfig.projbuild b/components/esp_modem/examples/modem_tcp_client/main/Kconfig.projbuild new file mode 100644 index 000000000..cf8e189a2 --- /dev/null +++ b/components/esp_modem/examples/modem_tcp_client/main/Kconfig.projbuild @@ -0,0 +1,96 @@ +menu "Example Configuration" + + choice EXAMPLE_MODEM_DEVICE + prompt "Choose supported modem device (DCE)" + default EXAMPLE_MODEM_DEVICE_BG96 + help + Select modem device connected to the ESP DTE. + config EXAMPLE_MODEM_DEVICE_BG96 + bool "BG96" + help + Quectel BG96 is a series of LTE Cat M1/Cat NB1/EGPRS module. + config EXAMPLE_MODEM_DEVICE_SIM7600 + bool "SIM7600" + help + SIM7600 is Multi-Band LTE-TDD/LTE-FDD/HSPA+ and GSM/GPRS/EDGE module + endchoice + + config EXAMPLE_MODEM_APN + string "Set MODEM APN" + default "internet" + help + Set APN (Access Point Name), a logical name to choose data network + + menu "UART Configuration" + config EXAMPLE_MODEM_UART_TX_PIN + int "TXD Pin Number" + default 25 + range 0 31 + help + Pin number of UART TX. + + config EXAMPLE_MODEM_UART_RX_PIN + int "RXD Pin Number" + default 26 + range 0 31 + help + Pin number of UART RX. + + config EXAMPLE_MODEM_UART_RTS_PIN + int "RTS Pin Number" + default 27 + range 0 31 + help + Pin number of UART RTS. + + config EXAMPLE_MODEM_UART_CTS_PIN + int "CTS Pin Number" + default 23 + range 0 31 + help + Pin number of UART CTS. + + config EXAMPLE_MODEM_UART_EVENT_TASK_STACK_SIZE + int "UART Event Task Stack Size" + range 2000 6000 + default 2048 + help + Stack size of UART event task. + + config EXAMPLE_MODEM_UART_EVENT_TASK_PRIORITY + int "UART Event Task Priority" + range 3 22 + default 5 + help + Priority of UART event task. + + config EXAMPLE_MODEM_UART_EVENT_QUEUE_SIZE + int "UART Event Queue Size" + range 10 40 + default 30 + help + Length of UART event queue. + + config EXAMPLE_MODEM_UART_PATTERN_QUEUE_SIZE + int "UART Pattern Queue Size" + range 10 40 + default 20 + help + Length of UART pattern queue. + + config EXAMPLE_MODEM_UART_TX_BUFFER_SIZE + int "UART TX Buffer Size" + range 256 2048 + default 512 + help + Buffer size of UART TX buffer. + + config EXAMPLE_MODEM_UART_RX_BUFFER_SIZE + int "UART RX Buffer Size" + range 256 2048 + default 1024 + help + Buffer size of UART RX buffer. + endmenu + +endmenu 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 new file mode 100644 index 000000000..5d3011ecb --- /dev/null +++ b/components/esp_modem/examples/modem_tcp_client/main/modem_client.cpp @@ -0,0 +1,140 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* PPPoS Client Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "esp_netif.h" +#include "esp_netif_ppp.h" +#include "mqtt_client.h" +#include "esp_modem_config.h" +#include "cxx_include/esp_modem_api.hpp" +#include "sock_dce.hpp" +#include "esp_log.h" + +#define BROKER_URL "mqtt.eclipseprojects.io" + +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 void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) +{ + ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%d", base, event_id); + esp_mqtt_event_handle_t event = (esp_mqtt_event_handle_t)event_data; + esp_mqtt_client_handle_t client = event->client; + int msg_id; + switch ((esp_mqtt_event_id_t)event_id) { + case MQTT_EVENT_CONNECTED: + ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED"); + msg_id = esp_mqtt_client_subscribe(client, "/topic/esp-pppos", 0); + ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id); + break; + case MQTT_EVENT_DISCONNECTED: + ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED"); + break; + case MQTT_EVENT_SUBSCRIBED: + ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id); + msg_id = esp_mqtt_client_publish(client, "/topic/esp-pppos", "esp32-pppos", 0, 0, 0); + ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id); + break; + case MQTT_EVENT_UNSUBSCRIBED: + ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id); + break; + case MQTT_EVENT_PUBLISHED: + ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id); + break; + case MQTT_EVENT_DATA: + ESP_LOGI(TAG, "MQTT_EVENT_DATA"); + printf("TOPIC=%.*s\r\n", event->topic_len, event->topic); + printf("DATA=%.*s\r\n", event->data_len, event->data); + xEventGroupSetBits(event_group, GOT_DATA_BIT); + break; + case MQTT_EVENT_ERROR: + ESP_LOGI(TAG, "MQTT_EVENT_ERROR"); + break; + default: + ESP_LOGI(TAG, "MQTT other event id: %d", event->event_id); + break; + } +} + +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()); + + event_group = xEventGroupCreate(); + + /* Configure and create the UART DTE */ + esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG(); + /* setup UART specific configuration based on kconfig options */ + dte_config.uart_config.tx_io_num = CONFIG_EXAMPLE_MODEM_UART_TX_PIN; + dte_config.uart_config.rx_io_num = CONFIG_EXAMPLE_MODEM_UART_RX_PIN; + dte_config.uart_config.rts_io_num = CONFIG_EXAMPLE_MODEM_UART_RTS_PIN; + dte_config.uart_config.cts_io_num = CONFIG_EXAMPLE_MODEM_UART_CTS_PIN; + dte_config.uart_config.rx_buffer_size = CONFIG_EXAMPLE_MODEM_UART_RX_BUFFER_SIZE; + dte_config.uart_config.tx_buffer_size = CONFIG_EXAMPLE_MODEM_UART_TX_BUFFER_SIZE; + dte_config.uart_config.event_queue_size = CONFIG_EXAMPLE_MODEM_UART_EVENT_QUEUE_SIZE; + dte_config.task_stack_size = CONFIG_EXAMPLE_MODEM_UART_EVENT_TASK_STACK_SIZE * 2; + dte_config.task_priority = CONFIG_EXAMPLE_MODEM_UART_EVENT_TASK_PRIORITY; + dte_config.dte_buffer_size = CONFIG_EXAMPLE_MODEM_UART_RX_BUFFER_SIZE / 2; + + auto dte = esp_modem::create_uart_dte(&dte_config); + assert(dte); + + /* Configure the DCE */ + 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)); + if (!dce->init_network()) { + ESP_LOGE(TAG, "Failed to setup network"); + return; + } + + dce->init_sock(8883); + esp_mqtt_client_config_t mqtt_config = {}; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + mqtt_config.broker.address.uri = "mqtts://127.0.0.1"; + mqtt_config.session.message_retransmit_timeout = 10000; +#else + mqtt_config.uri = "mqtt://127.0.0.1"; + mqtt_config.message_retransmit_timeout = 10000; +#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_start(mqtt_client); + if (!dce->start(BROKER_URL, 8883)) { + ESP_LOGE(TAG, "Failed to start DCE"); + return; + } + while (1) { + while (dce->perform_sock()) { + ESP_LOGV(TAG, "...performing"); + } + ESP_LOGE(TAG, "Loop exit.. retrying"); + // handle disconnections errors + if (!dce->init_network()) { + ESP_LOGE(TAG, "Failed to reinit network"); + return; + } + if (!dce->start("test.mosquitto.org", 1883)) { + ESP_LOGI(TAG, "Network reinitialized, retrying"); + } + } + +} diff --git a/components/esp_modem/examples/modem_tcp_client/main/sock_commands.hpp b/components/esp_modem/examples/modem_tcp_client/main/sock_commands.hpp new file mode 100644 index 000000000..ed1f68242 --- /dev/null +++ b/components/esp_modem/examples/modem_tcp_client/main/sock_commands.hpp @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "cxx_include/esp_modem_dte.hpp" +#include "cxx_include/esp_modem_dce_module.hpp" +#include "cxx_include/esp_modem_types.hpp" +#include "socket_commands.inc" + +namespace sock_commands { + +//using namespace esp_modem; + +#define ESP_MODEM_DECLARE_DCE_COMMAND(name, return_type, num, ...) \ + esp_modem::return_type name(esp_modem::CommandableIf *t, ## __VA_ARGS__); + +DECLARE_SOCK_COMMANDS(declare name(Commandable *p, ...);) + +#undef ESP_MODEM_DECLARE_DCE_COMMAND + +} 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 new file mode 100644 index 000000000..9d79d7e38 --- /dev/null +++ b/components/esp_modem/examples/modem_tcp_client/main/sock_commands_bg96.cpp @@ -0,0 +1,348 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#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"; + +namespace sock_commands { + +using namespace esp_modem; + +command_result net_open(CommandableIf *t) +{ + ESP_LOGV(TAG, "%s", __func__ ); + std::string out; + auto ret = dce_commands::generic_get_string(t, "AT+QISTATE?\r", out, 1000); + if (ret != command_result::OK) { + return ret; + } + if (out.find("+QISTATE: 0") != std::string::npos) { + ESP_LOGV(TAG, "%s", out.data() ); + ESP_LOGD(TAG, "Already there"); + return command_result::FAIL; + } else if (out.empty()) { + return dce_commands::generic_command(t, "AT+QIACT=1\r", "OK", "ERROR", 150000); + } + return command_result::FAIL; +} + +command_result net_close(CommandableIf *t) +{ + ESP_LOGV(TAG, "%s", __func__ ); + dce_commands::generic_command(t, "AT+QICLOSE=0\r", "OK", "ERROR", 10000); + esp_modem::Task::Delay(1000); + return dce_commands::generic_command(t, "AT+QIDEACT=1\r", "OK", "ERROR", 40000); +} + +command_result tcp_open(CommandableIf *t, const std::string &host, int port, int timeout) +{ + ESP_LOGV(TAG, "%s", __func__ ); + std::string ip_open = R"(AT+QIOPEN=1,0,"TCP",")" + host + "\"," + std::to_string(port) + "\r"; + auto ret = dce_commands::generic_command(t, ip_open, "+QIOPEN: 0,0", "ERROR", timeout); + if (ret != command_result::OK) { + ESP_LOGE(TAG, "%s Failed", __func__ ); + return ret; + } + return command_result::OK; +} + +command_result tcp_close(CommandableIf *t) +{ + ESP_LOGV(TAG, "%s", __func__ ); + return dce_commands::generic_command(t, "AT+QICLOSE=0\r", "OK", "ERROR", 10000); +} + +command_result tcp_send(CommandableIf *t, uint8_t *data, size_t len) +{ + ESP_LOGV(TAG, "%s", __func__ ); + assert(0); // Remove when fix done + 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__ ); + assert(0); // Remove when fix done + 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+QIACT?\r", out, 5000); + if (ret != command_result::OK) { + return ret; + } + auto pos = out.find("+QIACT: 1"); + auto property = 0; + while (pos != std::string::npos) { + // Looking for: +QIACT: ,,, + if (property++ == 3) { // ip is after 3rd comma (as a 4rd property of QIACT string) + ip = out.substr(++pos); + // strip quotes if present + auto quote1 = ip.find('"'); + auto quote2 = ip.rfind('"'); + if (quote1 != std::string::npos && quote2 != std::string::npos) { + ip = ip.substr(quote1 + 1, quote2 - 1); + } + return command_result::OK; + } + pos = out.find(',', ++pos); + } + return command_result::FAIL; +} + +} // sock_commands + +namespace sock_dce { + +void Responder::start_sending(size_t len) +{ + data_to_send = len; + send_stat = 0; + send_cmd("AT+QISEND=0," + std::to_string(len) + "\r"); +} + +void Responder::start_receiving(size_t len) +{ + send_cmd("AT+QIRD=0," + 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"); + 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 = "+QIRD: "; + auto head_pos = std::search(recv_data, recv_data + len, head.begin(), head.end()); + if (head_pos == nullptr) { + return ret::FAIL; + } + + auto next_nl = (char *)memchr(head_pos + head.size(), '\n', MIN_MESSAGE); + if (next_nl == nullptr) { + return ret::FAIL; + } + + if (std::from_chars(head_pos + head.size(), next_nl, actual_len).ec == std::errc::invalid_argument) { + ESP_LOGE(TAG, "cannot convert"); + return ret::FAIL; + } + + ESP_LOGD(TAG, "Received: actual len=%d", actual_len); + if (actual_len == 0) { + ESP_LOGD(TAG, "no data received"); + return ret::FAIL; + } + + if (actual_len > buffer_size) { + ESP_LOGE(TAG, "TOO BIG"); + return ret::FAIL; + } + + recv_data = next_nl + 1; + auto first_data_len = len - (recv_data - (char *)data) /* minus size of the command marker */; + if (actual_len > first_data_len) { + ::send(sock, recv_data, first_data_len, 0); + data_to_recv = actual_len - first_data_len; + return ret::NEED_MORE_DATA; + } + ::send(sock, recv_data, actual_len, 0); + } else if (data_to_recv > len) { // continue sending + ::send(sock, recv_data, len, 0); + data_to_recv -= len; + return ret::NEED_MORE_DATA; + } else if (data_to_recv <= len) { // last read -> looking for "OK" marker + ::send(sock, recv_data, data_to_recv, 0); + actual_len = data_to_recv; + } + + // "OK" after the data + char *last_pos = nullptr; + if (actual_len + 1 + 2 /* OK */ > 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; + return ret::FAIL; + } + } + if (last_pos != nullptr && (char *)data + len - last_pos - 2 > MIN_MESSAGE) { + // check for async replies after the Recv header + std::string_view response((char *)last_pos + 2 /* OK */, (char *)data + len - last_pos); + check_async_replies(status::RECEIVING, response); + } + // check if some other data? + start_receiving(0); + data_to_recv = 0; + return ret::OK; +} + + +Responder::ret Responder::send(uint8_t *data, size_t len) +{ + if (send_stat < 3) { + if (memchr(data, '>', len) == NULL) { + if (send_stat++ < 2) { + return Responder::ret::NEED_MORE_DATA; + } + ESP_LOGE(TAG, "Missed >"); + return ret::FAIL; + } + auto written = dte->write(&buffer[0], data_to_send); + if (written != data_to_send) { + ESP_LOGE(TAG, "written %d (%d)...", written, len); + return ret::FAIL; + } + data_to_send = 0; + send_stat = 3; + } + return Responder::ret::IN_PROGRESS; +} + +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_stat++; + return ret::IN_PROGRESS; + } else if (response.find("SEND FAIL") != std::string::npos) { + ESP_LOGE(TAG, "Sending buffer full"); + return ret::FAIL; + } else if (response.find("ERROR") != std::string::npos) { + ESP_LOGE(TAG, "Failed to sent"); + return ret::FAIL; + } + } else if (send_stat == 4) { + constexpr std::string_view head = "+QISEND: "; + if (response.find(head) != std::string::npos) { + // Parsing +QISEND: ,, + size_t head_pos = response.find(head); + response = response.substr(head_pos + head.size()); + int pos, property = 0; + int total = 0, ack = 0, unack = 0; + while ((pos = response.find(',')) != std::string::npos) { + auto next_comma = (char *)memchr(response.data(), ',', response.size()); + + // extract value + size_t value; + if (std::from_chars(response.data(), next_comma, value).ec == std::errc::invalid_argument) { + ESP_LOGE(TAG, "cannot convert"); + return ret::FAIL; + } + + switch (property++) { + case 0: total = value; + break; + case 1: ack = value; + break; + default: + return ret::FAIL; + } + response = response.substr(pos + 1); + } + if (std::from_chars(response.data(), response.data() + pos, unack).ec == std::errc::invalid_argument) { + return ret::FAIL; + } + + if (ack < total) { + ESP_LOGD(TAG, "all sending data are not ack (missing %d bytes acked)", (total - ack)); + if (total - ack > 64) { + ESP_LOGW(TAG, "Need a pause: missing %d bytes acked", (total - ack)); + return ret::NEED_MORE_TIME; + } + } + send_stat = 0; + return ret::OK; + } else if (response.find("ERROR") != std::string::npos) { + ESP_LOGE(TAG, "Failed to check sending"); + return ret::FAIL; + } + + } + return Responder::ret::IN_PROGRESS; +} + +Responder::ret Responder::connect(std::string_view response) +{ + if (response.find("+QIOPEN: 0,0") != std::string::npos) { + ESP_LOGI(TAG, "Connected!"); + return ret::OK; + } + if (response.find("ERROR") != std::string::npos) { + ESP_LOGE(TAG, "Failed to open"); + return ret::FAIL; + } + return Responder::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("+QIURC: \"recv\",0") != std::string::npos) { + uint64_t data_ready = 1; + write(data_ready_fd, &data_ready, sizeof(data_ready)); + ESP_LOGD(TAG, "Got data on modem!"); + } else if (response.find("+QIRD: ") != std::string::npos) { + static constexpr std::string_view head = "+QIRD: "; + size_t head_pos = response.find(head); + // Parsing +QIURC: ,, + response = response.substr(head_pos + head.size()); + int next_cr = response.find('\r'); + if (next_cr != std::string::npos) { + response = response.substr(next_cr - 2, next_cr); + if (response.find(",0") != std::string::npos) { + ESP_LOGV(TAG, "Receiving done"); + } else { + uint64_t data_ready = 1; + write(data_ready_fd, &data_ready, sizeof(data_ready)); + ESP_LOGD(TAG, "Got data on modem!"); + } + } + } else if (response.find("+QIURC: \"closed\",0") != std::string::npos) { + 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 Responder::ret::IN_PROGRESS; +} + +status Responder::pending() +{ + send_cmd("AT+QISEND=0,0\r"); + 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 new file mode 100644 index 000000000..e94f2ac95 --- /dev/null +++ b/components/esp_modem/examples/modem_tcp_client/main/sock_commands_sim7600.cpp @@ -0,0 +1,378 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#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"; + +namespace sock_commands { + +using namespace esp_modem; + +command_result net_open(CommandableIf *term) +{ + 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() ); + if (response.find("+NETOPEN: 1") != std::string::npos) { + ESP_LOGD(TAG, "Already there"); + ret = command_result::OK; + } else if (response.find("+NETOPEN: 0") != std::string::npos) { + ESP_LOGD(TAG, "Need to setup"); + ret = dce_commands::generic_command(term, "AT+NETOPEN\r", "+NETOPEN: 1", "+NETOPEN: 0", 10000); + } else { + return command_result::FAIL; + } + if (ret != command_result::OK) { + return ret; + } + return dce_commands::generic_command(term, "AT+CIPRXGET=1\r", "OK", "ERROR", 5000); +} + +command_result net_close(CommandableIf *term) +{ + 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__ ); + 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__ ); + 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__ ); + return ret; + } + return command_result::OK; +} + +command_result tcp_close(CommandableIf *term) +{ + 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__ ); + 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); + ESP_LOGI(TAG, "CIPSEND response %.*s", static_cast(response.size()), response.data()); + if (response.find('>') != std::string::npos) { + return command_result::OK; + } + return command_result::TIMEOUT; + }, 50000, '>'); + if (ret != command_result::OK) { + return ret; + } + ret = command_result::TIMEOUT; + ESP_LOGW(TAG, "Before setting..."); + term->on_read([&ret](uint8_t *cmd_data, size_t cmd_len) { + std::string_view response((char *)cmd_data, cmd_len); + ESP_LOGW(TAG, "CIPSEND response %.*s", static_cast(response.size()), response.data()); + + if (response.find("+CIPSEND:") != std::string::npos) { + ret = command_result::OK; + } else if (response.find("ERROR") != std::string::npos) { + ret = command_result::FAIL; + } + return ret; + }); + ESP_LOGW(TAG, "Before writing..."); + auto written = term->write(data, len); + if (written != len) { + ESP_LOGE(TAG, "written %d (%d)...", written, len); + return command_result::FAIL; + } + uint8_t ctrl_z = '\x1A'; + term->write(&ctrl_z, 1); + int count = 0; + while (ret == command_result::TIMEOUT && count++ < 1000 ) { + vTaskDelay(pdMS_TO_TICKS(1000)); + } + term->on_read(nullptr); + return ret; +} + +command_result tcp_recv(CommandableIf *term, uint8_t *data, size_t len, size_t &out_len) +{ + 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) { + return ret; + } + constexpr std::string_view pattern = "+CIPRXGET: 4,0,"; + if (out.find(pattern) == std::string::npos) { + + return command_result::FAIL; + } + size_t data_len; + if (std::from_chars(out.data() + pattern.size(), out.data() + out.size(), data_len).ec == std::errc::invalid_argument) { + return command_result::FAIL; + } + ESP_LOGD(TAG, "size=%d", data_len); + if (data_len == 0) { + out_len = data_len; + return command_result::OK; + } + return term->command("AT+CIPRXGET=2,0,100\r", [&](uint8_t *cmd_data, size_t cmd_len) { + char pattern[] = "+CIPRXGET: 2,0,"; + ESP_LOG_BUFFER_HEXDUMP(TAG, cmd_data, cmd_len, ESP_LOG_DEBUG); + char *pos = strstr((char *)cmd_data, pattern); + if (pos == nullptr) { + return command_result::FAIL; + } + auto p1 = memchr(pos + sizeof(pattern) - 1, ',', 4); + if (p1 == nullptr) { + return command_result::FAIL; + } + *(char *)p1 = '\0'; + size_t actual_len = atoi(pos + sizeof(pattern) - 1); + ESP_LOGD(TAG, "actual len=%d", actual_len); + + pos = strchr((char *)p1 + 1, '\n'); + if (pos == nullptr) { + ESP_LOGE(TAG, "not found"); + return command_result::FAIL; + } + if (actual_len > len) { + ESP_LOGE(TAG, "TOO BIG"); + return command_result::FAIL; + } + out_len = actual_len; + memcpy(data, pos + 1, actual_len); + pos = strstr((char *)pos + 1 + actual_len, "OK"); + if (pos == nullptr) { + ESP_LOGE(TAG, "ok NOT FOUND"); + return command_result::FAIL; + } + return command_result::OK; + }, 50000); +} + +command_result get_ip(CommandableIf *term, std::string &ip) +{ + std::string resp; + auto ret = dce_commands::generic_get_string(term, "AT+IPADDR\r", resp, 5000); + if (ret != command_result::OK) { + return ret; + } + ip = resp; + return command_result::OK; +} + +command_result set_rx_mode(CommandableIf *term, int mode) +{ + return dce_commands::generic_command(term, "AT+CIPRXGET=" + std::to_string(mode) + "\r", "OK", "ERROR", 5000); +} + +} // sock_commands + +namespace sock_dce { + +void Responder::start_sending(size_t len) +{ + data_to_send = len; + send_stat = 0; + send_cmd("AT+CIPSEND=0," + std::to_string(len) + "\r"); +} + +void Responder::start_receiving(size_t len) +{ + send_cmd("AT+CIPRXGET=2,0," + 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"); + 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) { + static constexpr std::string_view head = "+CIPRXGET: 2,0,"; + auto head_pos = std::search(recv_data, recv_data + len, head.begin(), head.end()); + if (head_pos == nullptr) { + return ret::FAIL; + } + + if (head_pos - (char *)data > MIN_MESSAGE) { + // check for async replies before the Recv header + std::string_view response((char *)data, head_pos - (char *)data); + check_async_replies(status::RECEIVING, response); + } + + auto next_comma = (char *)memchr(head_pos + head.size(), ',', MIN_MESSAGE); + if (next_comma == nullptr) { + return ret::FAIL; + } + if (std::from_chars(head_pos + head.size(), next_comma, actual_len).ec == std::errc::invalid_argument) { + ESP_LOGE(TAG, "cannot convert"); + return ret::FAIL; + } + + auto next_nl = (char *)memchr(next_comma, '\n', 8 /* total_len size (~4) + markers */); + if (next_nl == nullptr) { + ESP_LOGE(TAG, "not found"); + return ret::FAIL; + } + if (actual_len > buffer_size) { + ESP_LOGE(TAG, "TOO BIG"); + return ret::FAIL; + } + size_t total_len = 0; + if (std::from_chars(next_comma + 1, next_nl - 1, total_len).ec == std::errc::invalid_argument) { + ESP_LOGE(TAG, "cannot convert"); + return ret::FAIL; + } + read_again = (total_len > 0); + recv_data = next_nl + 1; + auto first_data_len = len - (recv_data - (char *)data) /* minus size of the command marker */; + if (actual_len > first_data_len) { + ::send(sock, recv_data, first_data_len, 0); + data_to_recv = actual_len - first_data_len; + return ret::NEED_MORE_DATA; + } + ::send(sock, recv_data, actual_len, 0); + } else if (data_to_recv > len) { // continue sending + ::send(sock, recv_data, len, 0); + data_to_recv -= len; + return ret::NEED_MORE_DATA; + } else if (data_to_recv <= len) { // last read -> looking for "OK" marker + ::send(sock, recv_data, data_to_recv, 0); + actual_len = data_to_recv; + } + + // "OK" after the data + char *last_pos = nullptr; + if (actual_len + 1 + 2 /* OK */ > 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; + return ret::FAIL; + } + } + if (last_pos != nullptr && (char *)data + len - last_pos - 2 > MIN_MESSAGE) { + // check for async replies after the Recv header + std::string_view response((char *)last_pos + 2 /* OK */, (char *)data + len - last_pos - 2); + check_async_replies(status::RECEIVING, response); + } + data_to_recv = 0; + if (read_again) { + uint64_t data_ready = 1; + write(data_ready_fd, &data_ready, sizeof(data_ready)); + } + return ret::OK; +} + +Responder::ret Responder::send(uint8_t *data, size_t len) +{ + if (send_stat == 0) { + if (memchr(data, '>', len) == NULL) { + ESP_LOGE(TAG, "Missed >"); + return ret::FAIL; + } + auto written = dte->write(&buffer[0], data_to_send); + if (written != data_to_send) { + ESP_LOGE(TAG, "written %d (%d)...", written, len); + return ret::FAIL; + } + data_to_send = 0; + uint8_t ctrl_z = '\x1A'; + dte->write(&ctrl_z, 1); + send_stat++; + return ret::IN_PROGRESS; + } + return Responder::ret::IN_PROGRESS; +} + +Responder::ret Responder::send(std::string_view response) +{ + if (send_stat == 1) { + if (response.find("+CIPSEND:") != std::string::npos) { + send_stat = 0; + return ret::OK; + } + if (response.find("ERROR") != std::string::npos) { + ESP_LOGE(TAG, "Failed to sent"); + send_stat = 0; + return ret::FAIL; + } + } + return Responder::ret::IN_PROGRESS; +} + +Responder::ret Responder::connect(std::string_view response) +{ + if (response.find("+CIPOPEN: 0,0") != std::string::npos) { + ESP_LOGI(TAG, "Connected!"); + return ret::OK; + } + if (response.find("ERROR") != std::string::npos) { + ESP_LOGE(TAG, "Failed to open"); + return ret::FAIL; + } + return Responder::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) +{ + if (state == status::SENDING) { + return send(data, len); + } + if (state == status::RECEIVING) { + return recv(data, len); + } + return Responder::ret::IN_PROGRESS; +} + +status Responder::pending() +{ + return status::PENDING; +} + + +} // sock_dce diff --git a/components/esp_modem/examples/modem_tcp_client/main/sock_dce.cpp b/components/esp_modem/examples/modem_tcp_client/main/sock_dce.cpp new file mode 100644 index 000000000..17fba4125 --- /dev/null +++ b/components/esp_modem/examples/modem_tcp_client/main/sock_dce.cpp @@ -0,0 +1,326 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "esp_vfs.h" +#include "esp_vfs_eventfd.h" + +#include "sock_dce.hpp" + +namespace sock_dce { + +constexpr auto const *TAG = "sock_dce"; + + +bool DCE::perform_sock() +{ + if (listen_sock == -1) { + ESP_LOGE(TAG, "Listening socket not ready"); + close_sock(); + return false; + } + if (sock == -1) { // no active socket, need to accept one first + return accept_sock(); + } + + // we have a socket, let's check the status + struct timeval tv = { + .tv_sec = 0, + .tv_usec = 500000, + }; + if (state == status::PENDING) { + vTaskDelay(pdMS_TO_TICKS(500)); + state = at.pending(); + return true; + } + fd_set fdset; + FD_ZERO(&fdset); + FD_SET(sock, &fdset); + FD_SET(data_ready_fd, &fdset); + int s = select(std::max(sock, data_ready_fd) + 1, &fdset, nullptr, nullptr, &tv); + if (s == 0) { + ESP_LOGV(TAG, "perform select timeout..."); + return true; + } else if (s < 0) { + ESP_LOGE(TAG, "select error %d", errno); + close_sock(); + return false; + } + if (FD_ISSET(sock, &fdset) && !sock_to_at()) { + return false; + } + if (FD_ISSET(data_ready_fd, &fdset) && !at_to_sock()) { + return false; + } + return true; +} + +void DCE::perform_at(uint8_t *data, size_t len) +{ + ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_VERBOSE); + switch (at.process_data(state, data, len)) { + case Responder::ret::OK: + state = status::IDLE; + signal.set(IDLE); + return; + case Responder::ret::FAIL: + state = status::FAILED; + signal.set(IDLE); + return; + case Responder::ret::NEED_MORE_DATA: + return; + case Responder::ret::IN_PROGRESS: + break; + case Responder::ret::NEED_MORE_TIME: + state = status::PENDING; + return; + } + std::string_view response((char *)data, len); + switch (at.check_async_replies(state, response)) { + case Responder::ret::OK: + state = status::IDLE; + signal.set(IDLE); + return; + case Responder::ret::FAIL: + state = status::FAILED; + signal.set(IDLE); + return; + case Responder::ret::NEED_MORE_TIME: + state = status::PENDING; + return; + case Responder::ret::NEED_MORE_DATA: + case Responder::ret::IN_PROGRESS: + break; + } +} + +void DCE::close_sock() +{ + if (sock > 0) { + close(sock); + sock = -1; + } + dte->on_read(nullptr); + const int retries = 5; + int i = 0; + while (net_close() != esp_modem::command_result::OK) { + if (i++ > retries) { + ESP_LOGE(TAG, "Failed to close network"); + return; + } + esp_modem::Task::Delay(1000); + } +} + +bool DCE::at_to_sock() +{ + uint64_t data; + read(data_ready_fd, &data, sizeof(data)); + ESP_LOGD(TAG, "select read: modem data available %x", data); + if (!signal.wait(IDLE, 1000)) { + ESP_LOGE(TAG, "Failed to get idle"); + close_sock(); + return false; + } + if (state != status::IDLE) { + ESP_LOGE(TAG, "Unexpected state %d", state); + close_sock(); + return false; + } + state = status::RECEIVING; + at.start_receiving(at.get_buf_len()); + return true; +} + +bool DCE::sock_to_at() +{ + ESP_LOGD(TAG, "socket read: data available"); + if (!signal.wait(IDLE, 1000)) { + ESP_LOGE(TAG, "Failed to get idle"); + close_sock(); + return false; + } + if (state != status::IDLE) { + ESP_LOGE(TAG, "Unexpected state %d", state); + close_sock(); + return false; + } + state = status::SENDING; + int len = ::recv(sock, at.get_buf(), at.get_buf_len(), 0); + if (len < 0) { + ESP_LOGE(TAG, "read error %d", errno); + close_sock(); + return false; + } else if (len == 0) { + ESP_LOGE(TAG, "EOF %d", errno); + close_sock(); + return false; + } + ESP_LOG_BUFFER_HEXDUMP(TAG, at.get_buf(), len, ESP_LOG_VERBOSE); + at.start_sending(len); + return true; +} + +bool DCE::accept_sock() +{ + struct timeval tv = { + .tv_sec = 0, + .tv_usec = 500000, + }; + fd_set fdset; + FD_ZERO(&fdset); + FD_SET(listen_sock, &fdset); + int s = select(listen_sock + 1, &fdset, nullptr, nullptr, &tv); + if (s > 0 && FD_ISSET(listen_sock, &fdset)) { + struct sockaddr_in source_addr = {}; + socklen_t addr_len = sizeof(source_addr); + sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len); + if (sock < 0) { + ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno); + return false; + } + ESP_LOGD(TAG, "Socket accepted!"); + FD_ZERO(&fdset); + return true; + } else if (s == 0) { + return true; + } + return false; +} + +void DCE::init_sock(int port) +{ + 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); + + listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + if (listen_sock < 0) { + ESP_LOGE(TAG, "Unable to create socket: errno %d", errno); + return; + } + int opt = 1; + setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + ESP_LOGI(TAG, "Socket created"); + struct sockaddr_in addr = { }; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); +// inet_aton("127.0.0.1", &addr.sin_addr); + + int err = bind(listen_sock, (struct sockaddr *)&addr, sizeof(addr)); + if (err != 0) { + ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno); + return; + } + ESP_LOGI(TAG, "Socket bound, port %d", 1883); + err = listen(listen_sock, 1); + if (err != 0) { + ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno); + return; + } + +} + +bool DCE::start(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; + }); + if (!at.start_connecting(host, port)) { + ESP_LOGE(TAG, "Unable to start connecting"); + dte->on_read(nullptr); + return false; + } + state = status::CONNECTING; + return true; +} + +bool DCE::init_network() +{ + dte->on_read(nullptr); + const int retries = 5; + int i = 0; + while (sync() != esp_modem::command_result::OK) { + if (i++ > retries) { + ESP_LOGE(TAG, "Failed to sync up"); + return false; + } + esp_modem::Task::Delay(1000); + } + ESP_LOGD(TAG, "Modem in sync"); + i = 0; + while (setup_data_mode() != true) { + if (i++ > retries) { + ESP_LOGE(TAG, "Failed to setup pdp/data"); + return false; + } + esp_modem::Task::Delay(1000); + } + ESP_LOGD(TAG, "PDP configured"); + i = 0; + while (net_open() != esp_modem::command_result::OK) { + if (i++ > retries) { + ESP_LOGE(TAG, "Failed to open network"); + return false; + } + net_close(); + esp_modem::Task::Delay(1000); + } + ESP_LOGD(TAG, "Network opened"); + i = 0; + std::string ip_addr; + while (get_ip(ip_addr) != esp_modem::command_result::OK) { + if (i++ > retries) { + ESP_LOGE(TAG, "Failed obtain an IP address"); + return false; + } + esp_modem::Task::Delay(5000); + } + ESP_LOGI(TAG, "Got IP %s", ip_addr.c_str()); + return true; +} + + +class Factory: public ::esp_modem::dce_factory::Factory { +public: + static std::unique_ptr create(const esp_modem::dce_config *config, std::shared_ptr dte) + { + return esp_modem::dce_factory::Factory::build_module_T>(config, std::move(dte)); + } +}; + +std::unique_ptr create(const esp_modem::dce_config *config, std::shared_ptr dte) +{ + return Factory::create(config, std::move(dte)); +} + +// Helper macros to handle multiple arguments of declared API +#define ARGS0 +#define ARGS1 , p1 +#define ARGS2 , p1 , p2 +#define ARGS3 , p1 , p2 , p3 + +#define EXPAND_ARGS(x) ARGS ## x +#define ARGS(x) EXPAND_ARGS(x) + +// +// Repeat all declarations and forward to the AT commands defined in ::sock_commands namespace +// +#define ESP_MODEM_DECLARE_DCE_COMMAND(name, return_type, arg_nr, ...) \ + esp_modem::return_type DCE::name(__VA_ARGS__) { return sock_commands::name(dte.get() ARGS(arg_nr) ); } + +DECLARE_SOCK_COMMANDS(return_type name(...) ) + +#undef ESP_MODEM_DECLARE_DCE_COMMAND + +} // namespace sock_dce diff --git a/components/esp_modem/examples/modem_tcp_client/main/sock_dce.hpp b/components/esp_modem/examples/modem_tcp_client/main/sock_dce.hpp new file mode 100644 index 000000000..37dd19223 --- /dev/null +++ b/components/esp_modem/examples/modem_tcp_client/main/sock_dce.hpp @@ -0,0 +1,108 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_modem_config.h" +#include "cxx_include/esp_modem_api.hpp" +#include +#include "socket_commands.inc" +#include "sock_commands.hpp" + +#pragma once + +namespace sock_dce { + + +enum class status { + IDLE, + CONNECTING, + SENDING, + RECEIVING, + FAILED, + PENDING +}; + +class Responder { +public: + enum class ret { + OK, FAIL, IN_PROGRESS, NEED_MORE_DATA, NEED_MORE_TIME + }; + Responder(int &s, int &ready_fd, std::shared_ptr &dte_arg): + 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); + + void start_sending(size_t len); + void start_receiving(size_t len); + bool start_connecting(std::string host, int port); + status pending(); + uint8_t *get_buf() + { + return &buffer[0]; + } + size_t get_buf_len() + { + return buffer_size; + } +private: + static constexpr size_t buffer_size = 512; + + ret recv(uint8_t *data, size_t len); + ret send(uint8_t *data, size_t len); + ret send(std::string_view response); + ret connect(std::string_view response); + void send_cmd(std::string_view command) + { + dte->write((uint8_t *) command.begin(), command.size()); + } + std::array buffer; + size_t data_to_recv = 0; + bool read_again = false; + int &sock; + int &data_ready_fd; + int send_stat = 0; + size_t data_to_send = 0; + std::shared_ptr &dte; +}; + +class DCE : public ::esp_modem::GenericModule { + using esp_modem::GenericModule::GenericModule; +public: + +#define ESP_MODEM_DECLARE_DCE_COMMAND(name, return_type, num, ...) \ +esp_modem::return_type name(__VA_ARGS__); + + DECLARE_SOCK_COMMANDS(declare name(Commandable *p, ...);) + +#undef ESP_MODEM_DECLARE_DCE_COMMAND + + bool init_network(); + bool start(std::string host, int port); + + void init_sock(int port); + + bool perform_sock(); + +private: + esp_modem::SignalGroup signal; + + void close_sock(); + bool accept_sock(); + bool sock_to_at(); + bool at_to_sock(); + + void perform_at(uint8_t *data, size_t len); + + status state{status::IDLE}; + static constexpr uint8_t IDLE = 1; + Responder at{sock, data_ready_fd, dte}; + int sock {-1}; + int listen_sock {-1}; + int data_ready_fd {-1}; +}; + +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/socket_commands.inc b/components/esp_modem/examples/modem_tcp_client/main/socket_commands.inc new file mode 100644 index 000000000..1036df84c --- /dev/null +++ b/components/esp_modem/examples/modem_tcp_client/main/socket_commands.inc @@ -0,0 +1,58 @@ +// Copyright 2021-2022 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "generate/esp_modem_command_declare_helper.inc" + +#define DECLARE_SOCK_COMMANDS(...) \ +/** + * @brief Opens network in AT command mode + * @return OK, FAIL or TIMEOUT + */ \ +ESP_MODEM_DECLARE_DCE_COMMAND(net_open, command_result, 0) \ + \ +/** + * @brief Closes network in AT command mode + * @return OK, FAIL or TIMEOUT + */ \ +ESP_MODEM_DECLARE_DCE_COMMAND(net_close, command_result, 0) \ + \ +/** + * @brief Opens a TCP connection + * @param[in] host Host name or IP address to connect to + * @param[in] port Port number + * @param[in] timeout Connection timeout + * @return OK, FAIL or TIMEOUT + */ \ +ESP_MODEM_DECLARE_DCE_COMMAND(tcp_open, command_result, 3, STRING_IN(p1, host), INT_IN(p2, port), INT_IN(p3, timeout)) \ + \ +/** + * @brief Closes opened TCP socket + * @return OK, FAIL or TIMEOUT + */ \ +ESP_MODEM_DECLARE_DCE_COMMAND(tcp_close, command_result, 0) \ + \ +/** + * @brief Gets modem IP address + * @param[out] addr String representation of modem's IP + * @return OK, FAIL or TIMEOUT + */ \ +ESP_MODEM_DECLARE_DCE_COMMAND(get_ip, command_result, 1, STRING_OUT(p1, addr)) \ + \ +/** + * @brief Sets Rx mode + * @param[in] mode 0=auto, 1=manual + * @return OK, FAIL or TIMEOUT + */ \ +ESP_MODEM_DECLARE_DCE_COMMAND(set_rx_mode, command_result, 1, INT_IN(p1, mode)) diff --git a/components/esp_modem/examples/simple_cmux_client/CMakeLists.txt b/components/esp_modem/examples/simple_cmux_client/CMakeLists.txt index 4f298cdb6..be1c2ac41 100644 --- a/components/esp_modem/examples/simple_cmux_client/CMakeLists.txt +++ b/components/esp_modem/examples/simple_cmux_client/CMakeLists.txt @@ -1,6 +1,7 @@ # The following lines of boilerplate have to be in your project's CMakeLists # in this exact order for cmake to work correctly -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.8) +set(CMAKE_CXX_STANDARD 17) set(EXTRA_COMPONENT_DIRS "../..") diff --git a/components/esp_modem/include/cxx_include/esp_modem_command_library.hpp b/components/esp_modem/include/cxx_include/esp_modem_command_library.hpp index feba2febc..6e3729e13 100644 --- a/components/esp_modem/include/cxx_include/esp_modem_command_library.hpp +++ b/components/esp_modem/include/cxx_include/esp_modem_command_library.hpp @@ -22,6 +22,19 @@ namespace dce_commands { * @{ */ +/** + * @brief Generic AT command to be send with pass and fail phrases + * + * @param t Commandable object (anything that can accept commands) + * @param command Command to be sent do the commandable object + * @param pass_phrase String to be present in the reply to pass this command + * @param fail_phrase String to be present in the reply to fail this command + * @param timeout_ms Timeout in ms + */ +command_result generic_command(CommandableIf *t, const std::string &command, + const std::string &pass_phrase, + const std::string &fail_phrase, uint32_t timeout_ms); + /** * @brief Declaration of all commands is generated from esp_modem_command_declare.inc */ diff --git a/components/esp_modem/include/cxx_include/esp_modem_dce_factory.hpp b/components/esp_modem/include/cxx_include/esp_modem_dce_factory.hpp index 46113029a..a9aeead65 100644 --- a/components/esp_modem/include/cxx_include/esp_modem_dce_factory.hpp +++ b/components/esp_modem/include/cxx_include/esp_modem_dce_factory.hpp @@ -69,6 +69,10 @@ public: ESP_MODEM_THROW_IF_FALSE(netif != nullptr, "Null netif"); } + explicit Creator(std::shared_ptr dte): dte(std::move(dte)), device(nullptr), netif(nullptr) + { + } + ~Creator() { if (device != nullptr) { diff --git a/components/esp_modem/include/cxx_include/esp_modem_dte.hpp b/components/esp_modem/include/cxx_include/esp_modem_dte.hpp index d6be01856..2ee56099a 100644 --- a/components/esp_modem/include/cxx_include/esp_modem_dte.hpp +++ b/components/esp_modem/include/cxx_include/esp_modem_dte.hpp @@ -29,6 +29,13 @@ class CMux; * @{ */ +struct DTE_Command { + DTE_Command(const std::string &cmd): data((uint8_t *)cmd.c_str()), len(cmd.length()) {} + + uint8_t *data; + size_t len; +}; + /** * DTE (Data Terminal Equipment) class */ @@ -54,7 +61,9 @@ public: * @param len Data len to write * @return number of bytes written */ - int write(uint8_t *data, size_t len); + int write(uint8_t *data, size_t len) override; + + int write(DTE_Command command); /** * @brief Reading from the underlying terminal @@ -70,6 +79,8 @@ public: */ void set_read_cb(std::function f); + void on_read(got_line_cb on_data) override; + /** * @brief Sets DTE error callback * @param f Function to be called on DTE error diff --git a/components/esp_modem/include/cxx_include/esp_modem_types.hpp b/components/esp_modem/include/cxx_include/esp_modem_types.hpp index 80abbcfd9..513b1f847 100644 --- a/components/esp_modem/include/cxx_include/esp_modem_types.hpp +++ b/components/esp_modem/include/cxx_include/esp_modem_types.hpp @@ -80,6 +80,9 @@ public: */ virtual command_result command(const std::string &command, got_line_cb got_line, uint32_t time_ms, const char separator) = 0; virtual command_result command(const std::string &command, got_line_cb got_line, uint32_t time_ms) = 0; + + virtual int write(uint8_t *data, size_t len) = 0; + virtual void on_read(got_line_cb on_data) = 0; }; /** diff --git a/components/esp_modem/include/generate/esp_modem_command_declare.inc b/components/esp_modem/include/generate/esp_modem_command_declare.inc index 0b6c343c2..71a734f80 100644 --- a/components/esp_modem/include/generate/esp_modem_command_declare.inc +++ b/components/esp_modem/include/generate/esp_modem_command_declare.inc @@ -1,4 +1,4 @@ -// Copyright 2021 Espressif Systems (Shanghai) PTE LTD +// Copyright 2021-2022 Espressif Systems (Shanghai) PTE LTD // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,36 +11,11 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +#pragma once -#ifndef _ESP_MODEM_COMMAND_DECLARE_INC_ -#define _ESP_MODEM_COMMAND_DECLARE_INC_ +#include "esp_modem_command_declare_helper.inc" -// Parameters -// * handle different parameters for C++ and C API -// * make parameter unique names, so they could be easily referenced and forwarded -#define _ARG(param, name) param -#define INT_IN(param, name) int _ARG(param, name) -#ifdef __cplusplus -#include -#define STRING_IN(param, name) const std::string& _ARG(param, name) -#define STRING_OUT(param, name) std::string& _ARG(param, name) -#define BOOL_IN(param, name) const bool _ARG(param, name) -#define BOOL_OUT(param, name) bool& _ARG(param, name) -#define INT_OUT(param, name) int& _ARG(param, name) -#define INTEGER_LIST_IN(param, name) const int* _ARG(param, name) - -#define STRUCT_OUT(struct_name, p1) struct_name& p1 -#else -#define STRING_IN(param, name) const char* _ARG(param, name) -#define STRING_OUT(param, name) char* _ARG(param, name) -#define BOOL_IN(param, name) const bool _ARG(param, name) -#define BOOL_OUT(param, name) bool* _ARG(param, name) -#define INT_OUT(param, name) int* _ARG(param, name) -#define INTEGER_LIST_IN(param, name) const int* _ARG(param, name) -#define STRUCT_OUT(struct_name, p1) esp_modem_ ## struct_name ## _t* p1 -#endif - #define DECLARE_ALL_COMMAND_APIS(...) \ /** * @brief Sends the initial AT sequence to sync up with the device @@ -331,5 +306,3 @@ public: #endif #endif - -#endif // _ESP_MODEM_COMMAND_DECLARE_INC_ diff --git a/components/esp_modem/include/generate/esp_modem_command_declare_helper.inc b/components/esp_modem/include/generate/esp_modem_command_declare_helper.inc new file mode 100644 index 000000000..2d2cec90d --- /dev/null +++ b/components/esp_modem/include/generate/esp_modem_command_declare_helper.inc @@ -0,0 +1,26 @@ + + +// Parameters +// * handle different parameters for C++ and C API +// * make parameter unique names, so they could be easily referenced and forwarded +#define _ARG(param, name) param +#define INT_IN(param, name) int _ARG(param, name) +#ifdef __cplusplus +#include +#define STRING_IN(param, name) const std::string& _ARG(param, name) +#define STRING_OUT(param, name) std::string& _ARG(param, name) +#define BOOL_IN(param, name) const bool _ARG(param, name) +#define BOOL_OUT(param, name) bool& _ARG(param, name) +#define INT_OUT(param, name) int& _ARG(param, name) +#define INTEGER_LIST_IN(param, name) const int* _ARG(param, name) + +#define STRUCT_OUT(struct_name, p1) struct_name& p1 +#else +#define STRING_IN(param, name) const char* _ARG(param, name) +#define STRING_OUT(param, name) char* _ARG(param, name) +#define BOOL_IN(param, name) const bool _ARG(param, name) +#define BOOL_OUT(param, name) bool* _ARG(param, name) +#define INT_OUT(param, name) int* _ARG(param, name) +#define INTEGER_LIST_IN(param, name) const int* _ARG(param, name) +#define STRUCT_OUT(struct_name, p1) esp_modem_ ## struct_name ## _t* p1 +#endif diff --git a/components/esp_modem/src/esp_modem_dte.cpp b/components/esp_modem/src/esp_modem_dte.cpp index f5ad1339b..98e3ff687 100644 --- a/components/esp_modem/src/esp_modem_dte.cpp +++ b/components/esp_modem/src/esp_modem_dte.cpp @@ -202,6 +202,34 @@ int DTE::write(uint8_t *data, size_t len) return secondary_term->write(data, len); } +int DTE::write(DTE_Command command) +{ + return primary_term->write(command.data, command.len); +} + +void DTE::on_read(got_line_cb on_read_cb) +{ + if (on_read_cb == nullptr) { + primary_term->set_read_cb(nullptr); + internal_lock.unlock(); + return; + } + internal_lock.lock(); + primary_term->set_read_cb([this, on_read_cb](uint8_t *data, size_t len) { + if (!data) { + data = buffer.get(); + len = primary_term->read(data, buffer.size); + } + auto res = on_read_cb(data, len); + if (res == command_result::OK || res == command_result::FAIL) { + primary_term->set_read_cb(nullptr); + internal_lock.unlock(); + return true; + } + return false; + }); +} + /** * Implemented here to keep all headers C++11 compliant */