mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
Add a `monitor_device` MCP tool that lets an AI agent run a scripted, non-interactive `esp-idf-monitor` session against a flashed device and get back a short status plus a log file path, instead of raw serial output inline. Under the hood: - The agent supplies a plain-text command body (expect/send/sleep/reset/ exit/comments). `assemble_monitor_script_from_agent_commands()` frames it into a script the monitor's non-interactive command mode can consume via stdin: it appends `exit` if the agent didn't already end with one, and rewrites every bare `expect <regex>` into `expect --timeout <timeout_sec> <regex>` via `_monitor_normalize_expect_line()` (an already-bounded `expect --timeout ...` line is left untouched so the monitor itself reports a bad value). A leading `reset` is not prepended - the monitor already resets the chip when it opens the port - and any `reset` the agent wrote is left in place. `_monitor_parse_sleep_duration()` extracts each `sleep <n>` duration. The effective timeout is the sum of every bounded expect duration plus every sleep duration. Scripts whose sum exceeds `MONITOR_MAX_SCRIPT_SEC` are rejected. If the script has neither expect nor sleep (for example only `send`), `timeout_sec` is used so the process still has a kill bound. - `monitor_device()` runs `python -m esp_idf_monitor` via `subprocess.run(..., input=script, timeout=2 * effective_timeout)`. `no_reset` is forwarded as `--no-reset` so the connection reset can be skipped; an explicit `-p` is forwarded when a port is given. Extra arguments match `idf.py monitor` where a build exists: baud (`baud` tool arg, else `monitor_baud` from `project_description.json`), toolchain prefix, `--target`/`--revision`, coredump/panic decode, and ELF files with the app ELF first. The 2x hard timeout is a safety net independent of the script's own `expect --timeout`/`exit` logic; on `TimeoutExpired` the process is killed but any output already captured is preserved and logged. `decode_stream()` normalizes that captured output, which can be `bytes` on the timeout path even though the process otherwise runs in text mode. - The monitor's exit code drives the reported status via `_monitor_status()`, using `EXIT_EXPECT_TIMEOUT` and `EXIT_SCRIPT_ERROR` from `esp_idf_monitor.base.constants`: 0 is success, 110 means an `expect` pattern never showed up before its `--timeout` elapsed, 2 means the monitor rejected the script (bad syntax/timeout/regex), anything else is reported generically. - Serial output and the monitor's own messages share one pipe (`stderr=STDOUT`) so decoded panic backtraces stay next to the lines that triggered them. `_save_monitor_output()` writes the full merge to `<tempdir>/esp_idf_mcp_log/action_monitor/monitor_<timestamp>.log` and reports a dedicated `Log file:` line. On non-zero exit or process kill, a short tail of that same merge is also returned inline so the agent has some failure context without a second file read. If the log file can't be written, it falls back to inlining a truncated tail. Closes https://github.com/espressif/esp-idf/issues/18757 Closes https://github.com/espressif/esp-idf/pull/18385 Co-authored-by: Cursor <cursoragent@cursor.com>
idf.py extensions
Python modules (subdirectories and files) in this directory named [your_extension]_ext will be loaded as idf.py extensions.
If you want to provide extra extensions just provide ; separated list of directories with extensions in IDF_EXTRA_ACTIONS_PATH. Extensions will be loaded in alphanumeric order.
Command line arguments parsing and extension mechanism is implemented on top of Click (versions >=5.0 are supported).
They should define a function action_extensions(base_actions, project_path) where:
- base_actions - dictionary with actions that are already available for idf.py
- project_path - working dir, may be defaulted to
os.getcwd()
This function have to return a dict with 3 possible keys:
{
# Additional options that will be available from id
"global_options": [{
"names": ["--option-name"],
"help": "Help for option --option-name.",
}],
# List of functions that will have access to full app context, and can mangle with arguments
"global_action_callbacks": [global_callback],
# Additional subcommands for idf.py
"actions": {
"subcommand_name": {
"callback": subcommand_callback,
"help": "Help for subcommand.",
},
},
}
Where function global_callback(ctx, global_args, tasks) accepts 3 arguments:
- ctx - Click context
- global_args - dictionary of all available global arguments
- tasks - list of Task objects
And subcommand_callback(subcommand_name, ctx, args) accepts 3 arguments:
- subcommand_name - name of subcommand
- ctx - Click context
- args - list of command's arguments