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
)
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

View File

@ -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<CustomModule, DCE*>(dce_config, std::move(dte), netif)
Please refer to the pppos_client example for more details.
endmenu

View File

@ -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

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 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");

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
*/
@ -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 <typename T_Module>
static std::unique_ptr<DCE> create_unique_dce_from(const esp_modem::dce_config *config,
std::shared_ptr<esp_modem::DTE> dte,
esp_netif_t *netif)
template <typename T_Module, typename Ptr = std::unique_ptr<DCE>>
static Ptr create_unique_dce_from(const esp_modem::dce_config *config,
std::shared_ptr<esp_modem::DTE> dte,
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:

View File

@ -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;
/**

View File

@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <cstring>
#include <cassert>
#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;

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
``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 <advanced_api>.
Configuration
-------------