espx

ESP32 firmware tools.

Set up the toolchain, start an esp-hal project, then build, flash and watch the board on your desk.

$ curl -fsSL https://www.espx.dev/install.sh | sh

macOS on Apple silicon · Linux arm64 · Linux x86_64

ESP32-C6
$ espx runESP32-C6 on /dev/cu.usbmodem101
HomeToolchainReadyAI agents1 connectedRecordingsProjectsweather-stationblinkyBoardBoard on usbmodem101Projects/blinky/Users/you/code/blinky · esp32c6StopRun againRSearch…KRunning on Board on usbmodem101since 14:02:10OutputProblemsDetailsAllWarningsErrorsRecordFollowClear00:00.0ESP-ROM:esp32c6-2022091900:00.0Build:Sep 19 202200:00.0rst:0x15 (USB_UART_HPSYS),boot:0x6c (SPI_FAST_FLASH_BOOT)00:00.1I (24) boot: efuse block revision: v0.300:00.1I (36) boot.esp32c6: SPI Flash Size : 4MB00:00.1I (114) boot: Loaded app from partition at offset 0x1000000:00.2INFOHello world!00:00.7INFOLED on00:01.2INFOLED off00:01.7INFOLED on00:02.2INFOLED off00:02.7INFOLED on00:03.2INFOLED off00:03.7INFOLED on00:04.2INFOLED off00:04.7INFOLED on00:05.2INFOLED off00:05.7INFOLED on00:06.2INFOLED off00:06.7INFOLED on00:07.2INFOLED off00:07.7INFOLED on00:08.2INFOLED off00:08.7INFOLED onToolchain readyNo problemsblinkyrunning since 14:02:10Board on usbmodem101Claude Codelast call build 12 min ago
  • Set up

    espflash, cargo-espflash and esp-generate, pinned and SHA-256 checked.

    espx setupInstall
  • Create

    An esp-hal project from esp-generate's template, with an AGENTS.md.

    espx new blinkyN
  • Build

    Release build. Errors at file and line, the image against the app partition.

    espx buildB
  • Flash

    Progress per flash region. If the board needs BOOT held, espx says so.

    espx flashU
  • Monitor

    Boot reason named, defmt decoded, panics resolved to file and line.

    espx runR

Serial monitor

espx reads the port itself. It decodes defmt against your release binary, resolves panics to a file and line, and names the reason for every boot.

  • defmt and panic decoding

    defmt frames become log lines with their source location. Panic addresses become a function, a file and a line.

    target/riscv32imac-unknown-none-elf/release/blinky
    $ espx monitor
    [INFO ] Hello world! (src/bin/main.rs:40)panic: panicked at src/bin/main.rs:24:9  0x42010190 blinky::explode      at src/bin/main.rs:24  0x420116c0 main      at src/bin/main.rs:43
  • Boot reasons

    espx reads the rst: code in the ROM banner and names the reset.

    • rst:0x1power-on
    • rst:0x5wake from deep sleep
    • rst:0x7watchdog reset
    • rst:0xfbrownout reset
    • rst:0x15reset over USB
  • Download mode

    If the bootloader won't answer, even at 115200 baud, espx says which buttons to press.

    BOOTRSTESP32-C6
    Hold BOOT, tap RESET, then let go of BOOT.
  • Shared board access

    espx flash in a second terminal flashes the board the app is watching, through ~/.espx/hub.sock.

    espx desktopespx flashespx mcp~/.espx/hub.sockESP32-C6
  • Recording and replay

    espx replay plays one back with its original timing, already decoded, with no ELF needed.

    $ espx replay crash.jsonl
    0.001=> board booted: reset over USB0.001INFO - Hello world!1.528panic: panicked at src/bin/main.rs:24:91.619warn: board booted: reset after a panic1.752INFO - Hello world!

Scripting and CI

Exit codes CI can read: 0 on a match, 1 on a rejected line, 124 on a timeout.

Each works on run, monitor and replay.

~/blinky
# Exit 0 on the first line that matches.$ espx run --until 'Hello world!' --timeout 2m=> building and flashing blinky for the ESP32-C6 on /dev/cu.usbmodem101 (cargo espflash flash --release)=> monitoring /dev/cu.usbmodem101 at 115200 baud until `Hello world!` for at most 2m=> flashed 65536 bytes in 2.1s=> board booted: reset over USB (rst:0x15 (USB_UART_HPSYS),boot:0x6c (SPI_FAST_FLASH_BOOT))INFO - Hello world!=> expectation 1 met: `Hello world!`
exit 0

Coding agents

espx mcp lets a coding agent build, flash and debug firmware on the board on your desk.

  1. Connect

    ~/blinky
    $ espx mcp install --client claude-codeclaude mcp add espx -- /path/to/espx mcp

    Codex works the same way. For Cursor, VS Code and Claude Desktop, espx writes the config file.

  2. Allow

    espx.toml
    [agent]boards = ["58:8C:81:53:AD:2C"]erase = falseflash = true

    A flash, erase or reset outside this table fails with policy_denied. Your own commands are never checked.

  3. Log

    ~/.espx/agent.log
    {"ts":1762862645123,"client":"claude-code","tool":"flash","board":"58:8C:81:53:AD:2C","outcome":"ok","ms":8400}

    Every tool call an agent makes, with its board, outcome and duration. espx log prints them.

Toolchain

Every download is verified against a SHA-256. Nothing goes in ~/.cargo/bin, and espx uninstall removes it.

espflash 4.5.0cargo-espflash 4.5.0esp-generate 1.3.0234SHA-256 checkedmust match to go in1~/.espx/bin5
  1. 1SHA-256 checked must match to go in
  2. 2espflash 4.5.0
  3. 3cargo-espflash 4.5.0
  4. 4esp-generate 1.3.0
  5. 5~/.espx/bin
$ espx setup$ espx uninstall --allespflash 4.5.0 · sha-256 okcargo-espflash 4.5.0 · sha-256 okesp-generate 1.3.0 · sha-256 ok3 binaries · nothing in ~/.cargo/binremoving ~/.espx~/.espx removed

Supported chips

The ESP32, S2 and S3 need Espressif's Rust fork. The other six build on stable Rust.

ESP32XtensaESP32-S2XtensaESP32-S3XtensaESP32-C2RISC-VESP32-C3RISC-VESP32-C5RISC-VpreviewESP32-C6RISC-VESP32-C61RISC-VpreviewESP32-H2RISC-V
Architecture
RISC-V
Rust target
riscv32imac-unknown-none-elf
Toolchain
stable, Rust stable, with the target added by rustup
Status
Stable
ESP32-C6RISC-Vstablerustup target addriscv32imcriscv32imacespespup installesp32esp32s2esp32s3
~
$ espx setup  # adds the RISC-V targets$ espx new blinky --chip esp32c6$ cd blinky$ espx run

Every chip esp-generate can scaffold. The ESP32-P4 joins when esp-generate supports it.

Questions

Anything else is in the docs.

Install espx

Install
Apple siliconmacOS · Linux
curl -fsSL https://www.espx.dev/install.sh | sh -s -- --desktopcurl -fsSL https://www.espx.dev/install.sh | sh