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_consoleREPL is embedded directly into the service lifecycle, starting whenesp_service_start()is called and shutting down on stop/destroyBuilt-in system commands:
sys_heap,sys_chip,sys_uptime, andsys_rebootare registered automaticallyStatic commands: any
esp_console_cmd_tcan be registered at any time, before or after the REPL starts, viaesp_cli_service_register_static_command()Dynamic
svccommand: lists, inspects, and controls (start/stop/pause/resume) tracked services from the terminalDynamic
toolcommand: enumerates and invokes the JSON Schema tools exposed byesp_service_manager, supporting bothkey=valueand--jsonargument formatsAll 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; runidf.py set-target <chip>followed byidf.py build flash monitorto 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_sizemust equalsizeof(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/toolcommands.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
svclist / 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
toolcommands
-
uint32_t struct_size
Macros
-
ESP_CLI_SERVICE_CONFIG_DEFAULT()
Type Definitions
-
typedef struct esp_cli_service esp_cli_service_t