Merge pull request #413 from david-cermak/feat/new_modem_cmd

Added support for implementing user defined modules in standard C-API
This commit is contained in:
david-cermak
2023-11-16 17:51:28 +01:00
committed by GitHub
11 changed files with 587 additions and 444 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

@ -76,6 +76,7 @@ public:
* @param command Command to be sent * @param command Command to be sent
* @param got_line callback if a line received * @param got_line callback if a line received
* @param time_ms timeout in milliseconds * @param time_ms timeout in milliseconds
* @param separator Character treated as a line separator, typically '\n'
* @return OK, FAIL or TIMEOUT * @return OK, FAIL or TIMEOUT
*/ */
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, const char separator) = 0;

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

@ -25,8 +25,8 @@ ESP_MODEM_DECLARE_DCE_COMMAND(sync, command_result, 0) \
\ \
/** /**
* @brief Reads the operator name * @brief Reads the operator name
* @param[out] operator name * @param[out] name operator name
* @param[out] access technology * @param[out] act access technology
* @return OK, FAIL or TIMEOUT * @return OK, FAIL or TIMEOUT
*/ \ */ \
ESP_MODEM_DECLARE_DCE_COMMAND(get_operator_name, command_result, 2, STRING_OUT(p1, name), INT_OUT(p2, act)) \ ESP_MODEM_DECLARE_DCE_COMMAND(get_operator_name, command_result, 2, STRING_OUT(p1, name), INT_OUT(p2, act)) \
@ -46,7 +46,7 @@ ESP_MODEM_DECLARE_DCE_COMMAND(set_pin, command_result, 1, STRING_IN(p1, pin)) \
\ \
/** /**
* @brief Execute the supplied AT command * @brief Execute the supplied AT command
* @param[in] at AT command * @param[in] cmd AT command
* @param[out] out Command output string * @param[out] out Command output string
* @param[in] timeout AT command timeout in milliseconds * @param[in] timeout AT command timeout in milliseconds
* @return OK, FAIL or TIMEOUT * @return OK, FAIL or TIMEOUT
@ -252,6 +252,8 @@ ESP_MODEM_DECLARE_DCE_COMMAND(set_preferred_mode, command_result, 1, INT_IN(p1,
/** /**
* @brief Set network bands for CAT-M or NB-IoT * @brief Set network bands for CAT-M or NB-IoT
* @param[in] mode CAT-M or NB-IoT * @param[in] mode CAT-M or NB-IoT
* @param[in] bands bitmap in hex representing bands
* @param[in] size size of teh bands bitmap
* @return OK, FAIL or TIMEOUT * @return OK, FAIL or TIMEOUT
*/ \ */ \
ESP_MODEM_DECLARE_DCE_COMMAND(set_network_bands, command_result, 3, STRING_IN(p1, mode), INTEGER_LIST_IN(p2, bands), INT_IN(p3, size)) \ ESP_MODEM_DECLARE_DCE_COMMAND(set_network_bands, command_result, 3, STRING_IN(p1, mode), INTEGER_LIST_IN(p2, bands), INT_IN(p3, size)) \

View File

@ -1,9 +1,10 @@
/* /*
* 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
*/ */
#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;
#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_factory::Factory f(convert_modem_enum(module));
dce_wrap->dce = f.build(dce_config, std::move(dte), netif); 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

@ -67,22 +67,19 @@ supports SIM800, BG96, SIM7600.
Use cases Use cases
--------- ---------
Users interact with the esp-modem using the DCEs interface, to Users interact with the esp-modem using the DCE's interface, to basically
basically
- Switch between command and data mode to connect to the internet via cellular network. - Switch between command and data mode to connect to the internet via cellular network.
- Send various commands to the device (e.g. send SMS) - Send various commands to the device (e.g. send SMS)
The applications typically register handlers for network events to The applications typically register handlers for network events to
receive notification on the network availability and IP address changes. receive notification on the network availability and IP address changes.
Common use cases of the esp-modem are also listed as the examples: Common use cases of the esp-modem are also listed as the examples:
- ``examples/pppos_client`` simple client which reads some module properties and switches to the data mode to connect to a public mqtt broker. - ``examples/pppos_client`` simple client which reads some module properties and switches to the data mode to connect to a public mqtt broker.
- ``examples/modem_console`` is an example to exercise all possible module commands in a console application. - ``examples/modem_console`` is an example to exercise all possible module commands in a console application.
- ``examples/ap_to_pppos`` this example focuses on the network - ``examples/ap_to_pppos`` this example focuses on the network connectivity of the esp-modem and provides a WiFi AP that forwards packets (and uses NAT) to and from the PPPoS connection.
connectivity of the esp-modem and provides a WiFi AP that forwards
packets (and uses NAT) to and from the PPPoS connection.
Extensibility Extensibility
------------- -------------
@ -95,8 +92,8 @@ allows users to also issue commands in the data mode, after creating
multiple virtual terminals, designating some of them solely to data multiple virtual terminals, designating some of them solely to data
mode, others solely to command mode. mode, others solely to command mode.
DTEs DTE
~~~~~ ~~~
Currently, we support only UART (and USB as a preview feature), but Currently, we support only UART (and USB as a preview feature), but
modern modules support other communication interfaces, such as USB, SPI. modern modules support other communication interfaces, such as USB, SPI.
@ -110,6 +107,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
------------- -------------