From 0254d50128b91825d9d3578c37fdf7d7bb88de04 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Wed, 1 Nov 2023 17:08:26 +0100 Subject: [PATCH] fix(modem): Support for custom modules with C-API MAJOR CHANGE: Added support for implementing user defined modules in standard C-API --- components/esp_modem/CMakeLists.txt | 3 + components/esp_modem/Kconfig | 18 +++++ .../pppos_client/main/Kconfig.projbuild | 7 ++ .../pppos_client/main/custom_module.hpp | 79 +++++++++++++++++++ .../pppos_client/main/pppos_client_main.c | 19 +++++ .../cxx_include/esp_modem_dce_factory.hpp | 13 +-- .../esp_modem/include/esp_modem_c_api_types.h | 1 + components/esp_modem/src/esp_modem_c_api.cpp | 17 +++- docs/esp_modem/en/README.rst | 4 + 9 files changed, 152 insertions(+), 9 deletions(-) create mode 100644 components/esp_modem/examples/pppos_client/main/custom_module.hpp diff --git a/components/esp_modem/CMakeLists.txt b/components/esp_modem/CMakeLists.txt index 10d39577c..447d94af7 100644 --- a/components/esp_modem/CMakeLists.txt +++ b/components/esp_modem/CMakeLists.txt @@ -42,6 +42,9 @@ set_target_properties(${COMPONENT_LIB} PROPERTIES CXX_EXTENSIONS ON ) +if(CONFIG_ESP_MODEM_ADD_CUSTOM_MODULE) + idf_component_optional_requires(PUBLIC main) +endif() if(${target} STREQUAL "linux") # This is needed for ESP_LOGx() macros, as integer formats differ on ESP32(..) and x64 diff --git a/components/esp_modem/Kconfig b/components/esp_modem/Kconfig index d4e6b282a..6896428d0 100644 --- a/components/esp_modem/Kconfig +++ b/components/esp_modem/Kconfig @@ -45,4 +45,22 @@ menu "esp-modem" to make the protocol more robust on noisy environments or when underlying transport gets corrupted often (for example by Rx buffer overflows) + config ESP_MODEM_ADD_CUSTOM_MODULE + bool "Add support for custom module in C-API" + default n + help + If enabled, we adapt the C-API to create a DCE from a user defined class + + config ESP_MODEM_CUSTOM_MODULE_HEADER + string "Header file name which defines custom DCE creation" + depends on ESP_MODEM_ADD_CUSTOM_MODULE + default "custom_module.hpp" + help + Name of the header file in the main component which implements esp_modem_create_custom_dce() + called from C-API for creating esp_modem_dce object. + This header provides definition of the custom module with some additional and/or updated commands + and API. It also defines creation of DCE based on this custom module, typically calling: + dce_factory::Factory::create_unique_dce_from(dce_config, std::move(dte), netif) + Please refer to the pppos_client example for more details. + endmenu diff --git a/components/esp_modem/examples/pppos_client/main/Kconfig.projbuild b/components/esp_modem/examples/pppos_client/main/Kconfig.projbuild index 07a61d59f..8afc7bf98 100644 --- a/components/esp_modem/examples/pppos_client/main/Kconfig.projbuild +++ b/components/esp_modem/examples/pppos_client/main/Kconfig.projbuild @@ -48,6 +48,13 @@ menu "Example Configuration" bool "A7670" help A7670X is Multi-Band LTE-FDD/LTE-TDD/GSM/GPRS/EDGE module. + + config EXAMPLE_MODEM_DEVICE_CUSTOM + select ESP_MODEM_ADD_CUSTOM_MODULE + bool "Custom device" + help + This demonstrates use of a custom device in C-API. + endchoice config EXAMPLE_MODEM_PPP_APN diff --git a/components/esp_modem/examples/pppos_client/main/custom_module.hpp b/components/esp_modem/examples/pppos_client/main/custom_module.hpp new file mode 100644 index 000000000..13125ba7d --- /dev/null +++ b/components/esp_modem/examples/pppos_client/main/custom_module.hpp @@ -0,0 +1,79 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#pragma once +#include "cxx_include/esp_modem_api.hpp" +#include "cxx_include/esp_modem_command_library_utils.hpp" + +/** + * @brief Definition of a custom module based on some already defined module + * Here we use GenericModule, but you can use any kind of device + * that is closer to your CustomModule. + */ +class SIM7600_WITH_TIME: public GenericModule { + /** + * @brief Need to reuse the constructors of our ancestor + */ + using GenericModule::GenericModule; +public: + /** + * @brief New command that is not defined in the GenericModule + */ + command_result get_time(std::string &time) + { + return esp_modem::dce_commands::generic_get_string(dte.get(), "AT+CCLK?\r", time); + } + + /** + * @brief This command is already defined in the GenericModule + * + * Here we just modify get_signal_quality() to return zeroes + * for demonstration purpose only, since it's called within this example + */ + command_result get_signal_quality(int &rssi, int &ber) override + { + rssi = ber = 0; + return esp_modem::command_result::OK; + } + +}; + +/** + * @brief esp_modem_create_custom_dce() needs to be defined, as it is called in C-API wrapper when creating esp_modem_dce + * + * This uses public factory function for creating a common DCE with our CustomModule. Creating raw DCE pointer is only needed + * for the C-API wrapper; C++API users would create DCE (any kind of smart pointer) directly with + * Factory::create_unique_dce_from(dce_config, std::move(dte), netif); + */ +DCE *esp_modem_create_custom_dce(const esp_modem_dce_config_t *dce_config, std::shared_ptr dte, esp_netif_t *netif) +{ + return dce_factory::Factory::create_unique_dce_from(dce_config, std::move(dte), netif); +} + +/** + * @brief This API is only needed for extending standard C-API, since we added get_time() method to our CustomModule + * + * @note This header is included from esp_modem_c_api.cpp, so it could use ESP_MODEM_C_API_STR_MAX macro + * indicating maximum C-API string size + * + * @note In order to access the newly added API get_time(), we have to static_cast<> the GenericModule from DCE + * to our CustomModule. + * Alternatively we could use the modem Factory to build our specific DCE_T, but in that case + * we couldn't use our C-API wrappers which expect DCE type, DCE_T with lib commands (this alternative + * is cleaner, but more suitable for C++ users) + */ +extern "C" esp_err_t esp_modem_get_time(esp_modem_dce_t *dce_wrap, char *p_time) +{ + if (dce_wrap == nullptr || dce_wrap->dce == nullptr) { + return ESP_ERR_INVALID_ARG; + } + std::string time{ESP_MODEM_C_API_STR_MAX}; + auto ret = command_response_to_esp_err(static_cast(dce_wrap->dce->get_module())->get_time(time)); + if (ret == ESP_OK && !time.empty()) { + strlcpy(p_time, time.c_str(), ESP_MODEM_C_API_STR_MAX); + } + return ret; +} diff --git a/components/esp_modem/examples/pppos_client/main/pppos_client_main.c b/components/esp_modem/examples/pppos_client/main/pppos_client_main.c index cb5ff954a..ba3d58a44 100644 --- a/components/esp_modem/examples/pppos_client/main/pppos_client_main.c +++ b/components/esp_modem/examples/pppos_client/main/pppos_client_main.c @@ -37,6 +37,10 @@ static const int CONNECT_BIT = BIT0; static const int GOT_DATA_BIT = BIT2; static const int USB_DISCONNECTED_BIT = BIT3; // Used only with USB DTE but we define it unconditionally, to avoid too many #ifdefs in the code +#ifdef CONFIG_EXAMPLE_MODEM_DEVICE_CUSTOM +esp_err_t esp_modem_get_time(esp_modem_dce_t *dce_wrap, char *p_time); +#endif + #if defined(CONFIG_EXAMPLE_SERIAL_CONFIG_USB) #include "esp_modem_usb_c_api.h" #include "esp_modem_usb_config.h" @@ -192,6 +196,9 @@ void app_main(void) #elif CONFIG_EXAMPLE_MODEM_DEVICE_SIM7600 == 1 ESP_LOGI(TAG, "Initializing esp_modem for the SIM7600 module..."); esp_modem_dce_t *dce = esp_modem_new_dev(ESP_MODEM_DCE_SIM7600, &dte_config, &dce_config, esp_netif); +#elif CONFIG_EXAMPLE_MODEM_DEVICE_CUSTOM == 1 + ESP_LOGI(TAG, "Initializing esp_modem with custom module..."); + esp_modem_dce_t *dce = esp_modem_new_dev(ESP_MODEM_DCE_CUSTOM, &dte_config, &dce_config, esp_netif); #else ESP_LOGI(TAG, "Initializing esp_modem for a generic module..."); esp_modem_dce_t *dce = esp_modem_new(&dte_config, &dce_config, esp_netif); @@ -258,6 +265,18 @@ void app_main(void) } ESP_LOGI(TAG, "Signal quality: rssi=%d, ber=%d", rssi, ber); +#ifdef CONFIG_EXAMPLE_MODEM_DEVICE_CUSTOM + { + char time[64]; + err = esp_modem_get_time(dce, time); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_modem_get_time failed with %d %s", err, esp_err_to_name(err)); + return; + } + ESP_LOGI(TAG, "esp_modem_get_time: %s", time); + } +#endif + #if CONFIG_EXAMPLE_SEND_MSG if (esp_modem_sms_txt_mode(dce, true) != ESP_OK || esp_modem_sms_character_set(dce) != ESP_OK) { ESP_LOGE(TAG, "Setting text mode or GSM character set failed"); 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 5bcb39070..b3cac79da 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 @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -12,6 +12,7 @@ * @brief DCE modem factory */ +#define esp_modem_create_dce_from dce_factory::Factory::create_unique_dce_from namespace esp_modem::dce_factory { @@ -236,12 +237,12 @@ public: return nullptr; } - template - static std::unique_ptr create_unique_dce_from(const esp_modem::dce_config *config, - std::shared_ptr dte, - esp_netif_t *netif) + template > + static Ptr create_unique_dce_from(const esp_modem::dce_config *config, + std::shared_ptr dte, + esp_netif_t *netif) { - return build_generic_DCE>(config, std::move(dte), netif); + return build_generic_DCE(config, std::move(dte), netif); } private: diff --git a/components/esp_modem/include/esp_modem_c_api_types.h b/components/esp_modem/include/esp_modem_c_api_types.h index f47e2a064..fcc54701c 100644 --- a/components/esp_modem/include/esp_modem_c_api_types.h +++ b/components/esp_modem/include/esp_modem_c_api_types.h @@ -53,6 +53,7 @@ typedef enum esp_modem_dce_device { ESP_MODEM_DCE_SIM7000, ESP_MODEM_DCE_BG96, ESP_MODEM_DCE_SIM800, + ESP_MODEM_DCE_CUSTOM } esp_modem_dce_device_t; /** diff --git a/components/esp_modem/src/esp_modem_c_api.cpp b/components/esp_modem/src/esp_modem_c_api.cpp index ac4db3f72..7178bda1a 100644 --- a/components/esp_modem/src/esp_modem_c_api.cpp +++ b/components/esp_modem/src/esp_modem_c_api.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include #include #include "cxx_include/esp_modem_dte.hpp" #include "uart_terminal.hpp" @@ -14,7 +15,6 @@ #include "esp_modem_config.h" #include "exception_stub.hpp" #include "esp_private/c_api_wrapper.hpp" -#include "cstring" #ifndef ESP_MODEM_C_API_STR_MAX #define ESP_MODEM_C_API_STR_MAX 64 @@ -24,6 +24,10 @@ size_t strlcpy(char *dest, const char *src, size_t len); #endif +#ifdef CONFIG_ESP_MODEM_ADD_CUSTOM_MODULE +#include CONFIG_ESP_MODEM_CUSTOM_MODULE_HEADER +#endif + // // C API definitions using namespace esp_modem; @@ -40,8 +44,15 @@ extern "C" esp_modem_dce_t *esp_modem_new_dev(esp_modem_dce_device_t module, con return nullptr; } dce_wrap->dte = dte; - dce_factory::Factory f(convert_modem_enum(module)); - dce_wrap->dce = f.build(dce_config, std::move(dte), netif); +#ifdef CONFIG_ESP_MODEM_ADD_CUSTOM_MODULE + if (module == ESP_MODEM_DCE_CUSTOM) { + dce_wrap->dce = esp_modem_create_custom_dce(dce_config, dte, netif); + } else +#endif + { + dce_factory::Factory f(convert_modem_enum(module)); + dce_wrap->dce = f.build(dce_config, std::move(dte), netif); + } if (dce_wrap->dce == nullptr) { delete dce_wrap; return nullptr; diff --git a/docs/esp_modem/en/README.rst b/docs/esp_modem/en/README.rst index e87ad5a27..4542b5447 100644 --- a/docs/esp_modem/en/README.rst +++ b/docs/esp_modem/en/README.rst @@ -110,6 +110,10 @@ commands might have a different implementation. Adding a new device means to provide a new implementation as a class derived from ``GenericModule``, where we could add new commands or modify the existing ones. +If you have to support a custom device with C-API, please refer to +the example ``examples/pppos_client`` and enable ``ESP_MODEM_ADD_CUSTOM_MODULE``. +For advanced use-case, mainly with C++ API and/or usage of esp_modem's +Factory class, please read . Configuration -------------