ESP CLI Service

[中文]

Introduction

CLI Service is a subclass of ESP Service that embeds the ESP-IDF console UART REPL into the service lifecycle. It provides two layers of commands: built-in static system commands, and dynamic service and tool commands driven by the service manager. Interactive debugging and runtime control can be done from a serial terminal.

Note

Requires ESP-IDF >= 5.4.

Feature List

  • UART REPL integration: the esp_console REPL is embedded directly into the service lifecycle, starting when esp_service_start() is called and shutting down on stop/destroy

  • Built-in system commands: sys_heap, sys_chip, sys_uptime, and sys_reboot are registered automatically

  • Static commands: any esp_console_cmd_t can be registered at any time, before or after the REPL starts, via esp_cli_service_register_static_command()

  • Dynamic svc command: lists, inspects, and controls (start / stop / pause / resume) tracked services from the terminal

  • Dynamic tool command: enumerates and invokes the JSON Schema tools exposed by esp_service_manager, supporting both key=value and --json argument formats

  • All public APIs are protected by an internal mutex and can be safely called from any task

Technical Deep Dive

Command Architecture

Commands attached to the REPL fall into three categories: built-in system commands, which are always available; static commands that the application registers via esp_cli_service_register_static_command(); and dynamic svc / tool commands, which become available only after esp_service_manager is bound. Once esp_service_start() is called, the REPL task is created, and any pending static commands are registered with esp_console.

        flowchart TD
    UART[UART Terminal] --> REPL[esp_console REPL]
    REPL --> Sys["Built-in sys_* commands"]
    REPL --> Static["Static commands"]
    REPL --> Dyn["Dynamic commands (requires bound manager)"]
    Dyn --> Svc["svc command: tracked services / manager"]
    Dyn --> Tool["tool command: manager tool registry"]
    
esp_cli_service_config_t cfg = ESP_CLI_SERVICE_CONFIG_DEFAULT();
cfg.prompt = "app>";

esp_cli_service_t *cli = NULL;
esp_cli_service_create(&cfg, &cli);

esp_cli_service_track_service(cli, (esp_service_t *)my_svc, "audio");

const esp_console_cmd_t my_cmd = {
    .command = "version",
    .help    = "Print firmware version",
    .func    = cmd_version,
};
esp_cli_service_register_static_command(cli, &my_cmd);

esp_service_start((esp_service_t *)cli);

svc and tool Commands

The svc command (list / info / start / stop / pause / resume) shows services that are explicitly tracked via esp_cli_service_track_service() first, followed by services registered in the bound esp_service_manager. The tool command (list / info / call) enumerates the JSON Schema tools exposed by the manager; tool call serializes key=value pairs into a JSON object according to the tool’s inputSchema, supporting string, integer, number, and boolean types, while complex types such as array or object require --json to pass raw JSON arguments instead. The manager is bound either through the manager field in the configuration or by calling esp_cli_service_bind_manager() at runtime; both dynamic commands depend on this step.

Application Examples

  • components/esp_cli_service/examples/ provides a standalone example; run idf.py set-target <chip> followed by idf.py build flash monitor to try it on real hardware.

FAQ

Q1: What if no prompt appears in the serial terminal?

First confirm that esp_service_start() has been called (the REPL task is only created at startup), then check that the UART port and baud rate selected in the terminal emulator match the console UART configured in the IDF menuconfig. If the registered commands allocate a large amount of data, the default 4096-byte task_stack may not be sufficient and should be increased accordingly.

Q2: Why do the ``svc`` / ``tool`` commands report “manager not bound”?

Both dynamic commands require esp_service_manager to be bound first: set cfg.manager in the configuration, or call esp_cli_service_bind_manager() at runtime before using them.

API Reference

Header File

Functions

esp_err_t esp_cli_service_create(const esp_cli_service_config_t *cfg, esp_cli_service_t **out)

Create a CLI service handle (UART REPL backed by esp_console)

Note

Not ISR-safe. Not thread-safe for concurrent creation of the same logical instance; typical use is from one init task. May allocate heap memory.

Note

Internal mutex acquisition may block briefly when contended.

Parameters:
  • cfg[in] Configuration; struct_size must equal sizeof(esp_cli_service_config_t)

  • out[out] Receives new handle on success; set to NULL on error

Returns:

  • ESP_OK On success

  • ESP_ERR_INVALID_ARG cfg or out is NULL, or struct_size is wrong

  • ESP_ERR_NO_MEM Allocation failed

esp_err_t esp_cli_service_destroy(esp_cli_service_t *svc)

Destroy a CLI service and free its resources.

    Stops the REPL if running, frees tracked/static command state, and invalidates @a svc.

Note

After this returns, svc must not be used even if the return code is not ESP_OK (resources are still released).

Note

Not ISR-safe. Not thread-safe against concurrent use of svc or its REPL task; call when the service is quiesced.

Parameters:

svc[in] Handle from esp_cli_service_create

Returns:

  • ESP_OK On success.

  • ESP_ERR_INVALID_ARG svc is NULL.

  • Other Error from underlying service teardown; all resources are freed regardless.

esp_err_t esp_cli_service_bind_manager(esp_cli_service_t *svc, esp_service_manager_t *mgr)

Bind or replace the service manager used for dynamic svc / tool commands.

Note

Not ISR-safe. Thread-safe with other APIs on the same handle (mutex held briefly). May block briefly when acquiring an internal mutex.

Parameters:
  • svc[in] Service handle

  • mgr[in] Manager pointer (may be NULL to clear)

Returns:

  • ESP_OK Manager bound.

  • ESP_ERR_INVALID_ARG svc is NULL.

  • ESP_ERR_TIMEOUT Mutex acquisition timed out.

esp_err_t esp_cli_service_track_service(esp_cli_service_t *svc, esp_service_t *service, const char *category)

Track a service by name for svc list / lifecycle commands.

    If the name already exists, updates category and service pointer.

Note

Not ISR-safe. Thread-safe with other APIs on the same handle.

Parameters:
  • svc[in] Service handle

  • service[in] Service to track (name from esp_service_get_name)

  • category[in] Category string for display (may be NULL, stored as empty string)

Returns:

  • ESP_OK Tracked (created or updated).

  • ESP_ERR_INVALID_ARG svc or service is NULL; service name is empty.

  • ESP_ERR_NO_MEM Allocation failed.

  • ESP_ERR_TIMEOUT Mutex acquisition timed out.

  • Other Error from esp_service_get_name.

esp_err_t esp_cli_service_untrack_service(esp_cli_service_t *svc, const char *name)

Remove a tracked service entry by logical service name.

Note

Not ISR-safe. Thread-safe with other APIs on the same handle.

Parameters:
  • svc[in] Service handle

  • name[in] Service name previously tracked

Returns:

  • ESP_OK Service untracked.

  • ESP_ERR_INVALID_ARG svc is NULL or name is empty.

  • ESP_ERR_NOT_FOUND name not in the tracked list.

  • ESP_ERR_TIMEOUT Mutex acquisition timed out.

esp_err_t esp_cli_service_register_static_command(esp_cli_service_t *svc, const esp_console_cmd_t *cmd)

Register an extra static esp_console command before or after REPL start.

    Command name must be a single token (no spaces). Strings are copied; the @a cmd
    descriptor need only live for the call. If the REPL is already running, the command is
    registered with esp_console immediately.

Note

Not ISR-safe. Thread-safe with other APIs on the same handle. Do not register from inside another command handler unless re-entrancy is safe for esp_console on your IDF version.

Parameters:
  • svc[in] Service handle

  • cmd[in] Command descriptor (see esp_console_cmd_t)

Returns:

  • ESP_OK Command registered.

  • ESP_ERR_INVALID_ARG svc or cmd is NULL; invalid command descriptor.

  • ESP_ERR_INVALID_STATE Command name already registered.

  • ESP_ERR_NO_MEM Allocation or storage failed.

  • ESP_ERR_TIMEOUT Mutex acquisition timed out.

Structures

struct esp_cli_service_config_t

CLI service creation configuration.

    Set @c struct_size to @c sizeof(esp_cli_service_config_t) on every call. The @c reserved
    fields must be zero. Use @ref ESP_CLI_SERVICE_CONFIG_DEFAULT() unless you intentionally
    override fields.

    String ownership: @c prompt is copied internally; the pointer in @a cfg only needs to
    remain valid for the duration of @ref esp_cli_service_create. @c base_cfg fields follow
    @c esp_service_init rules.

Public Members

uint32_t struct_size

Must equal sizeof(esp_cli_service_config_t)

uint32_t reserved[2]

Reserved; must be zero

esp_service_config_t base_cfg

Core service registration configuration

const char *prompt

REPL prompt string (copied on create)

uint16_t max_cmdline_length

Maximum command line length for esp_console

uint32_t task_stack

REPL task stack size in bytes

uint32_t task_prio

REPL task priority

esp_service_manager_t *manager

Optional manager for tool commands

Macros

ESP_CLI_SERVICE_CONFIG_DEFAULT()

Type Definitions

typedef struct esp_cli_service esp_cli_service_t