Customization

This chapter covers how to customize ESP-MODEM for your specific requirements by creating custom modules and adding new commands.

Custom Module Development

For most customization needs, you don’t need development mode. Instead, you can create custom modules that inherit from existing ESP-MODEM classes.

Creating Custom Modules

The recommended approach for adding support for new modem modules or custom commands is to create a custom module class. This approach:

  • Doesn’t require development mode

  • Keeps your changes separate from the core library

  • Allows easy updates of the ESP-MODEM library

  • Provides full flexibility for custom commands

Basic Custom Module Example

Here’s a simple example of creating a custom module:

#include "cxx_include/esp_modem_api.hpp"
#include "cxx_include/esp_modem_command_library_utils.hpp"

class MyCustomModule: public GenericModule {
    using GenericModule::GenericModule;

public:
    // Add a new command
    command_result get_custom_info(std::string &info) {
        return esp_modem::dce_commands::generic_get_string(
            dte.get(), "AT+CUSTOM?\r", info);
    }

    // Override an existing command
    command_result get_signal_quality(int &rssi, int &ber) override {
        // Custom implementation
        return esp_modem::dce_commands::generic_get_string(
            dte.get(), "AT+CSQ\r", rssi, ber);
    }
};

Using Custom Modules with C++ API

With the C++ API, you can use your custom module directly:

// Create DCE with custom module
auto dce = dce_factory::Factory::create_unique_dce_from<MyCustomModule>(
    dce_config, std::move(dte), netif);

// Use custom commands
std::string info;
auto result = dce->get_custom_info(info);

Using Custom Modules with C API

To use custom modules with the C API, you need to:

  1. Enable custom module support in Kconfig:

    idf.py menuconfig
    # Navigate to: Component config → ESP-MODEM
    # Enable: "Add support for custom module in C-API"
    
  2. Create a custom module header (e.g., custom_module.hpp) in your main component

  3. Implement the required functions:

    // Create custom DCE function
    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<MyCustomModule, DCE *>(
            dce_config, std::move(dte), netif);
    }
    
    // Add C API wrappers for custom commands
    extern "C" esp_err_t esp_modem_get_custom_info(esp_modem_dce_t *dce_wrap, char *info) {
        if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
            return ESP_ERR_INVALID_ARG;
        }
        std::string info_str{CONFIG_ESP_MODEM_C_API_STR_MAX};
        auto ret = command_response_to_esp_err(
            static_cast<MyCustomModule *>(dce_wrap->dce->get_module())->get_custom_info(info_str));
        if (ret == ESP_OK && !info_str.empty()) {
            strlcpy(info, info_str.c_str(), CONFIG_ESP_MODEM_C_API_STR_MAX);
        }
        return ret;
    }
    
  4. Use the custom commands in your C code:

    char info[128];
    esp_err_t ret = esp_modem_get_custom_info(dce, info);
    

Complete Example

See the examples/pppos_client example for a complete demonstration of custom module development. This example shows:

  • Creating a custom module that inherits from GenericModule

  • Adding new commands (get_time())

  • Overriding existing commands (get_signal_quality())

  • Integration with both C++ and C APIs

Available Base Classes

You can inherit from several base classes depending on your needs:

GenericModule

The most general implementation of a common modem. Use this when: - Your modem supports most standard AT commands - You need to add a few custom commands - You want to override some existing commands

Specific Module Classes

Inherit from existing module classes (e.g., SIM800, BG96, SIM7600) when: - Your modem is very similar to an existing one - You only need minor modifications - You want to leverage existing device-specific optimizations

ModuleIf

Use this minimal interface when: - You only need basic AT command functionality - You don’t need network interface features - You want to implement a custom DTE without DCE

Command Utilities

ESP-MODEM provides utility functions to help implement custom commands:

Generic Command Helpers
  • generic_get_string() - Parse string responses

  • generic_get_int() - Parse integer responses

  • generic_set_string() - Send string commands

  • generic_set_int() - Send integer commands

Response Parsing
  • get_number_from_string() - Extract numbers from responses

  • get_string_from_response() - Extract strings from responses

  • get_urc() - Handle unsolicited result codes

Example Usage: .. code-block:: cpp

// Get a string value command_result get_imei(std::string &imei) {

return esp_modem::dce_commands::generic_get_string(

dte.get(), “AT+CGSNr”, imei);

}

// Get an integer value command_result get_signal_strength(int &rssi) {

return esp_modem::dce_commands::generic_get_int(

dte.get(), “AT+CSQr”, rssi);

}

Best Practices

For Application Developers: - Use production mode for better IDE support and faster builds - Create custom modules for new modem support - Inherit from GenericModule or other existing modules - Keep customizations in your project, not in the ESP-MODEM library

Module Design: - Choose the most appropriate base class for your needs - Override only the commands you need to modify - Use the provided utility functions for common operations - Follow the existing command naming conventions

Testing: - Test your custom module with real hardware - Verify compatibility with existing ESP-MODEM features - Test both C++ and C API usage if applicable - Consider edge cases and error handling

Documentation: - Document your custom commands clearly - Provide usage examples - Explain any device-specific requirements - Note any limitations or known issues