fix(modem): Support for custom modules with C-API

MAJOR CHANGE: Added support for implementing user defined modules in standard C-API
This commit is contained in:
David Cermak
2023-11-01 17:08:26 +01:00
parent 2661b4d28c
commit 0254d50128
9 changed files with 152 additions and 9 deletions

View File

@ -42,6 +42,9 @@ set_target_properties(${COMPONENT_LIB} PROPERTIES
CXX_EXTENSIONS ON CXX_EXTENSIONS ON
) )
if(CONFIG_ESP_MODEM_ADD_CUSTOM_MODULE)
idf_component_optional_requires(PUBLIC main)
endif()
if(${target} STREQUAL "linux") if(${target} STREQUAL "linux")
# This is needed for ESP_LOGx() macros, as integer formats differ on ESP32(..) and x64 # This is needed for ESP_LOGx() macros, as integer formats differ on ESP32(..) and x64

View File

@ -45,4 +45,22 @@ menu "esp-modem"
to make the protocol more robust on noisy environments or when underlying to make the protocol more robust on noisy environments or when underlying
transport gets corrupted often (for example by Rx buffer overflows) 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<CustomModule, DCE*>(dce_config, std::move(dte), netif)
Please refer to the pppos_client example for more details.
endmenu endmenu

View File

@ -48,6 +48,13 @@ menu "Example Configuration"
bool "A7670" bool "A7670"
help help
A7670X is Multi-Band LTE-FDD/LTE-TDD/GSM/GPRS/EDGE module. 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 endchoice
config EXAMPLE_MODEM_PPP_APN config EXAMPLE_MODEM_PPP_APN

View File

@ -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<CustomModule>(dce_config, std::move(dte), netif);
*/
DCE *esp_modem_create_custom_dce(const esp_modem_dce_config_t *dce_config, std::shared_ptr<DTE> dte, esp_netif_t *netif)
{
return dce_factory::Factory::create_unique_dce_from<SIM7600_WITH_TIME, DCE *>(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<CustomModule>, but in that case
* we couldn't use our C-API wrappers which expect DCE type, DCE_T<GenericModule> 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<SIM7600_WITH_TIME *>(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;
}

View File

@ -37,6 +37,10 @@ static const int CONNECT_BIT = BIT0;
static const int GOT_DATA_BIT = BIT2; 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 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) #if defined(CONFIG_EXAMPLE_SERIAL_CONFIG_USB)
#include "esp_modem_usb_c_api.h" #include "esp_modem_usb_c_api.h"
#include "esp_modem_usb_config.h" #include "esp_modem_usb_config.h"
@ -192,6 +196,9 @@ void app_main(void)
#elif CONFIG_EXAMPLE_MODEM_DEVICE_SIM7600 == 1 #elif CONFIG_EXAMPLE_MODEM_DEVICE_SIM7600 == 1
ESP_LOGI(TAG, "Initializing esp_modem for the SIM7600 module..."); 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); 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 #else
ESP_LOGI(TAG, "Initializing esp_modem for a generic module..."); ESP_LOGI(TAG, "Initializing esp_modem for a generic module...");
esp_modem_dce_t *dce = esp_modem_new(&dte_config, &dce_config, esp_netif); 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); 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 CONFIG_EXAMPLE_SEND_MSG
if (esp_modem_sms_txt_mode(dce, true) != ESP_OK || esp_modem_sms_character_set(dce) != ESP_OK) { 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"); ESP_LOGE(TAG, "Setting text mode or GSM character set failed");

View File

@ -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 * SPDX-License-Identifier: Apache-2.0
*/ */
@ -12,6 +12,7 @@
* @brief DCE modem factory * @brief DCE modem factory
*/ */
#define esp_modem_create_dce_from dce_factory::Factory::create_unique_dce_from
namespace esp_modem::dce_factory { namespace esp_modem::dce_factory {
@ -236,12 +237,12 @@ public:
return nullptr; return nullptr;
} }
template <typename T_Module> template <typename T_Module, typename Ptr = std::unique_ptr<DCE>>
static std::unique_ptr<DCE> create_unique_dce_from(const esp_modem::dce_config *config, static Ptr create_unique_dce_from(const esp_modem::dce_config *config,
std::shared_ptr<esp_modem::DTE> dte, std::shared_ptr<esp_modem::DTE> dte,
esp_netif_t *netif) esp_netif_t *netif)
{ {
return build_generic_DCE<T_Module, DCE, std::unique_ptr<DCE>>(config, std::move(dte), netif); return build_generic_DCE<T_Module, DCE, Ptr>(config, std::move(dte), netif);
} }
private: private:

View File

@ -53,6 +53,7 @@ typedef enum esp_modem_dce_device {
ESP_MODEM_DCE_SIM7000, ESP_MODEM_DCE_SIM7000,
ESP_MODEM_DCE_BG96, ESP_MODEM_DCE_BG96,
ESP_MODEM_DCE_SIM800, ESP_MODEM_DCE_SIM800,
ESP_MODEM_DCE_CUSTOM
} esp_modem_dce_device_t; } esp_modem_dce_device_t;
/** /**

View File

@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
#include <cstring>
#include <cassert> #include <cassert>
#include "cxx_include/esp_modem_dte.hpp" #include "cxx_include/esp_modem_dte.hpp"
#include "uart_terminal.hpp" #include "uart_terminal.hpp"
@ -14,7 +15,6 @@
#include "esp_modem_config.h" #include "esp_modem_config.h"
#include "exception_stub.hpp" #include "exception_stub.hpp"
#include "esp_private/c_api_wrapper.hpp" #include "esp_private/c_api_wrapper.hpp"
#include "cstring"
#ifndef ESP_MODEM_C_API_STR_MAX #ifndef ESP_MODEM_C_API_STR_MAX
#define ESP_MODEM_C_API_STR_MAX 64 #define ESP_MODEM_C_API_STR_MAX 64
@ -24,6 +24,10 @@
size_t strlcpy(char *dest, const char *src, size_t len); size_t strlcpy(char *dest, const char *src, size_t len);
#endif #endif
#ifdef CONFIG_ESP_MODEM_ADD_CUSTOM_MODULE
#include CONFIG_ESP_MODEM_CUSTOM_MODULE_HEADER
#endif
// //
// C API definitions // C API definitions
using namespace esp_modem; 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; return nullptr;
} }
dce_wrap->dte = dte; dce_wrap->dte = dte;
dce_factory::Factory f(convert_modem_enum(module)); #ifdef CONFIG_ESP_MODEM_ADD_CUSTOM_MODULE
dce_wrap->dce = f.build(dce_config, std::move(dte), netif); 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) { if (dce_wrap->dce == nullptr) {
delete dce_wrap; delete dce_wrap;
return nullptr; return nullptr;

View File

@ -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 means to provide a new implementation as a class derived from
``GenericModule``, where we could add new commands or modify the ``GenericModule``, where we could add new commands or modify the
existing ones. 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 <advanced_api>.
Configuration Configuration
------------- -------------