# 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

- components/esp_cli_service/include/esp_cli_service.h

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

Provide feedback about this document