Skip to content

Commit 0254d50

Browse files
committed
fix(modem): Support for custom modules with C-API
MAJOR CHANGE: Added support for implementing user defined modules in standard C-API
1 parent 2661b4d commit 0254d50

File tree

9 files changed

+152
-9
lines changed

9 files changed

+152
-9
lines changed

components/esp_modem/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ set_target_properties(${COMPONENT_LIB} PROPERTIES
4242
CXX_EXTENSIONS ON
4343
)
4444

45+
if(CONFIG_ESP_MODEM_ADD_CUSTOM_MODULE)
46+
idf_component_optional_requires(PUBLIC main)
47+
endif()
4548

4649
if(${target} STREQUAL "linux")
4750
# This is needed for ESP_LOGx() macros, as integer formats differ on ESP32(..) and x64

components/esp_modem/Kconfig

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,22 @@ menu "esp-modem"
4545
to make the protocol more robust on noisy environments or when underlying
4646
transport gets corrupted often (for example by Rx buffer overflows)
4747

48+
config ESP_MODEM_ADD_CUSTOM_MODULE
49+
bool "Add support for custom module in C-API"
50+
default n
51+
help
52+
If enabled, we adapt the C-API to create a DCE from a user defined class
53+
54+
config ESP_MODEM_CUSTOM_MODULE_HEADER
55+
string "Header file name which defines custom DCE creation"
56+
depends on ESP_MODEM_ADD_CUSTOM_MODULE
57+
default "custom_module.hpp"
58+
help
59+
Name of the header file in the main component which implements esp_modem_create_custom_dce()
60+
called from C-API for creating esp_modem_dce object.
61+
This header provides definition of the custom module with some additional and/or updated commands
62+
and API. It also defines creation of DCE based on this custom module, typically calling:
63+
dce_factory::Factory::create_unique_dce_from<CustomModule, DCE*>(dce_config, std::move(dte), netif)
64+
Please refer to the pppos_client example for more details.
65+
4866
endmenu

components/esp_modem/examples/pppos_client/main/Kconfig.projbuild

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ menu "Example Configuration"
4848
bool "A7670"
4949
help
5050
A7670X is Multi-Band LTE-FDD/LTE-TDD/GSM/GPRS/EDGE module.
51+
52+
config EXAMPLE_MODEM_DEVICE_CUSTOM
53+
select ESP_MODEM_ADD_CUSTOM_MODULE
54+
bool "Custom device"
55+
help
56+
This demonstrates use of a custom device in C-API.
57+
5158
endchoice
5259

5360
config EXAMPLE_MODEM_PPP_APN
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Unlicense OR CC0-1.0
5+
*/
6+
7+
#pragma once
8+
#include "cxx_include/esp_modem_api.hpp"
9+
#include "cxx_include/esp_modem_command_library_utils.hpp"
10+
11+
/**
12+
* @brief Definition of a custom module based on some already defined module
13+
* Here we use GenericModule, but you can use any kind of device
14+
* that is closer to your CustomModule.
15+
*/
16+
class SIM7600_WITH_TIME: public GenericModule {
17+
/**
18+
* @brief Need to reuse the constructors of our ancestor
19+
*/
20+
using GenericModule::GenericModule;
21+
public:
22+
/**
23+
* @brief New command that is not defined in the GenericModule
24+
*/
25+
command_result get_time(std::string &time)
26+
{
27+
return esp_modem::dce_commands::generic_get_string(dte.get(), "AT+CCLK?\r", time);
28+
}
29+
30+
/**
31+
* @brief This command is already defined in the GenericModule
32+
*
33+
* Here we just modify get_signal_quality() to return zeroes
34+
* for demonstration purpose only, since it's called within this example
35+
*/
36+
command_result get_signal_quality(int &rssi, int &ber) override
37+
{
38+
rssi = ber = 0;
39+
return esp_modem::command_result::OK;
40+
}
41+
42+
};
43+
44+
/**
45+
* @brief esp_modem_create_custom_dce() needs to be defined, as it is called in C-API wrapper when creating esp_modem_dce
46+
*
47+
* This uses public factory function for creating a common DCE with our CustomModule. Creating raw DCE pointer is only needed
48+
* for the C-API wrapper; C++API users would create DCE (any kind of smart pointer) directly with
49+
* Factory::create_unique_dce_from<CustomModule>(dce_config, std::move(dte), netif);
50+
*/
51+
DCE *esp_modem_create_custom_dce(const esp_modem_dce_config_t *dce_config, std::shared_ptr<DTE> dte, esp_netif_t *netif)
52+
{
53+
return dce_factory::Factory::create_unique_dce_from<SIM7600_WITH_TIME, DCE *>(dce_config, std::move(dte), netif);
54+
}
55+
56+
/**
57+
* @brief This API is only needed for extending standard C-API, since we added get_time() method to our CustomModule
58+
*
59+
* @note This header is included from esp_modem_c_api.cpp, so it could use ESP_MODEM_C_API_STR_MAX macro
60+
* indicating maximum C-API string size
61+
*
62+
* @note In order to access the newly added API get_time(), we have to static_cast<> the GenericModule from DCE
63+
* to our CustomModule.
64+
* Alternatively we could use the modem Factory to build our specific DCE_T<CustomModule>, but in that case
65+
* we couldn't use our C-API wrappers which expect DCE type, DCE_T<GenericModule> with lib commands (this alternative
66+
* is cleaner, but more suitable for C++ users)
67+
*/
68+
extern "C" esp_err_t esp_modem_get_time(esp_modem_dce_t *dce_wrap, char *p_time)
69+
{
70+
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
71+
return ESP_ERR_INVALID_ARG;
72+
}
73+
std::string time{ESP_MODEM_C_API_STR_MAX};
74+
auto ret = command_response_to_esp_err(static_cast<SIM7600_WITH_TIME *>(dce_wrap->dce->get_module())->get_time(time));
75+
if (ret == ESP_OK && !time.empty()) {
76+
strlcpy(p_time, time.c_str(), ESP_MODEM_C_API_STR_MAX);
77+
}
78+
return ret;
79+
}

components/esp_modem/examples/pppos_client/main/pppos_client_main.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ static const int CONNECT_BIT = BIT0;
3737
static const int GOT_DATA_BIT = BIT2;
3838
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
3939

40+
#ifdef CONFIG_EXAMPLE_MODEM_DEVICE_CUSTOM
41+
esp_err_t esp_modem_get_time(esp_modem_dce_t *dce_wrap, char *p_time);
42+
#endif
43+
4044
#if defined(CONFIG_EXAMPLE_SERIAL_CONFIG_USB)
4145
#include "esp_modem_usb_c_api.h"
4246
#include "esp_modem_usb_config.h"
@@ -192,6 +196,9 @@ void app_main(void)
192196
#elif CONFIG_EXAMPLE_MODEM_DEVICE_SIM7600 == 1
193197
ESP_LOGI(TAG, "Initializing esp_modem for the SIM7600 module...");
194198
esp_modem_dce_t *dce = esp_modem_new_dev(ESP_MODEM_DCE_SIM7600, &dte_config, &dce_config, esp_netif);
199+
#elif CONFIG_EXAMPLE_MODEM_DEVICE_CUSTOM == 1
200+
ESP_LOGI(TAG, "Initializing esp_modem with custom module...");
201+
esp_modem_dce_t *dce = esp_modem_new_dev(ESP_MODEM_DCE_CUSTOM, &dte_config, &dce_config, esp_netif);
195202
#else
196203
ESP_LOGI(TAG, "Initializing esp_modem for a generic module...");
197204
esp_modem_dce_t *dce = esp_modem_new(&dte_config, &dce_config, esp_netif);
@@ -258,6 +265,18 @@ void app_main(void)
258265
}
259266
ESP_LOGI(TAG, "Signal quality: rssi=%d, ber=%d", rssi, ber);
260267

268+
#ifdef CONFIG_EXAMPLE_MODEM_DEVICE_CUSTOM
269+
{
270+
char time[64];
271+
err = esp_modem_get_time(dce, time);
272+
if (err != ESP_OK) {
273+
ESP_LOGE(TAG, "esp_modem_get_time failed with %d %s", err, esp_err_to_name(err));
274+
return;
275+
}
276+
ESP_LOGI(TAG, "esp_modem_get_time: %s", time);
277+
}
278+
#endif
279+
261280
#if CONFIG_EXAMPLE_SEND_MSG
262281
if (esp_modem_sms_txt_mode(dce, true) != ESP_OK || esp_modem_sms_character_set(dce) != ESP_OK) {
263282
ESP_LOGE(TAG, "Setting text mode or GSM character set failed");

components/esp_modem/include/cxx_include/esp_modem_dce_factory.hpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -12,6 +12,7 @@
1212
* @brief DCE modem factory
1313
*/
1414

15+
#define esp_modem_create_dce_from dce_factory::Factory::create_unique_dce_from
1516

1617
namespace esp_modem::dce_factory {
1718

@@ -236,12 +237,12 @@ class Factory {
236237
return nullptr;
237238
}
238239

239-
template <typename T_Module>
240-
static std::unique_ptr<DCE> create_unique_dce_from(const esp_modem::dce_config *config,
241-
std::shared_ptr<esp_modem::DTE> dte,
242-
esp_netif_t *netif)
240+
template <typename T_Module, typename Ptr = std::unique_ptr<DCE>>
241+
static Ptr create_unique_dce_from(const esp_modem::dce_config *config,
242+
std::shared_ptr<esp_modem::DTE> dte,
243+
esp_netif_t *netif)
243244
{
244-
return build_generic_DCE<T_Module, DCE, std::unique_ptr<DCE>>(config, std::move(dte), netif);
245+
return build_generic_DCE<T_Module, DCE, Ptr>(config, std::move(dte), netif);
245246
}
246247

247248
private:

components/esp_modem/include/esp_modem_c_api_types.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ typedef enum esp_modem_dce_device {
5353
ESP_MODEM_DCE_SIM7000,
5454
ESP_MODEM_DCE_BG96,
5555
ESP_MODEM_DCE_SIM800,
56+
ESP_MODEM_DCE_CUSTOM
5657
} esp_modem_dce_device_t;
5758

5859
/**

components/esp_modem/src/esp_modem_c_api.cpp

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7+
#include <cstring>
78
#include <cassert>
89
#include "cxx_include/esp_modem_dte.hpp"
910
#include "uart_terminal.hpp"
@@ -14,7 +15,6 @@
1415
#include "esp_modem_config.h"
1516
#include "exception_stub.hpp"
1617
#include "esp_private/c_api_wrapper.hpp"
17-
#include "cstring"
1818

1919
#ifndef ESP_MODEM_C_API_STR_MAX
2020
#define ESP_MODEM_C_API_STR_MAX 64
@@ -24,6 +24,10 @@
2424
size_t strlcpy(char *dest, const char *src, size_t len);
2525
#endif
2626

27+
#ifdef CONFIG_ESP_MODEM_ADD_CUSTOM_MODULE
28+
#include CONFIG_ESP_MODEM_CUSTOM_MODULE_HEADER
29+
#endif
30+
2731
//
2832
// C API definitions
2933
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
4044
return nullptr;
4145
}
4246
dce_wrap->dte = dte;
43-
dce_factory::Factory f(convert_modem_enum(module));
44-
dce_wrap->dce = f.build(dce_config, std::move(dte), netif);
47+
#ifdef CONFIG_ESP_MODEM_ADD_CUSTOM_MODULE
48+
if (module == ESP_MODEM_DCE_CUSTOM) {
49+
dce_wrap->dce = esp_modem_create_custom_dce(dce_config, dte, netif);
50+
} else
51+
#endif
52+
{
53+
dce_factory::Factory f(convert_modem_enum(module));
54+
dce_wrap->dce = f.build(dce_config, std::move(dte), netif);
55+
}
4556
if (dce_wrap->dce == nullptr) {
4657
delete dce_wrap;
4758
return nullptr;

docs/esp_modem/en/README.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ commands might have a different implementation. Adding a new device
110110
means to provide a new implementation as a class derived from
111111
``GenericModule``, where we could add new commands or modify the
112112
existing ones.
113+
If you have to support a custom device with C-API, please refer to
114+
the example ``examples/pppos_client`` and enable ``ESP_MODEM_ADD_CUSTOM_MODULE``.
115+
For advanced use-case, mainly with C++ API and/or usage of esp_modem's
116+
Factory class, please read <advanced_api>.
113117

114118
Configuration
115119
-------------

0 commit comments

Comments
 (0)