# Build and flash

Build release firmware with a size check, flash it, and pass extra arguments to cargo and espflash.

Four commands cover the build side: `espx build`, `espx flash`, `espx run` (build, flash and monitor in one step) and `espx clean`. A fifth, `espx erase`, wipes a board's flash. Each one runs from the project directory, or takes `--dir` to point at one.

## Build

```sh
espx build
```

This runs `cargo build --release` with cargo's JSON messages turned on. cargo's progress lines reach your terminal unchanged, and espx prints each compiler message exactly as cargo would. On success it measures the ELF and prints one line:

```text
=> image 64 KB of a 3.9 MB app partition (1%), 12 KB of static RAM
```

The image size counts what espflash writes to flash. Static RAM counts every allocated section outside flash except the stack, which esp-hal's linker gives all the DRAM that is left.

### The partition check

espx compares the image with the app partition before anything reaches the board. A build whose image does not fit fails with `image_too_large` and tells you to shrink the firmware or give it a bigger partition. espx finds the partition size the way espflash 4.5 does:

- A `partition_table` in the `[idf_format_args]` or `[idf]` table of `espflash.toml` (a CSV or `.bin` file). espx takes the `target_app_partition` named there, else the `factory` partition, else the first app partition.
- Without a table, the default layout: the `[flash] size` from `espflash.toml` (4 MB when unset) minus `0x10000`, capped at 16,000 KiB.

### Build errors

A failed build exits `1` with `cargo build failed with N errors. Fix them and build again.` With `--json`, each compiler message is a `diagnostic` event with its level, code, file, line and column, and cargo's rendered text. See [JSON events](https://www.espx.dev/docs/reference/events#diagnostic).

## Flash

```sh
espx flash
```

`flash` builds through espx first, so the diagnostics and the partition check apply, then runs `cargo espflash flash --release` on the board espx picked. [Boards and ports](https://www.espx.dev/docs/cli/boards) has the selection order. To name one:

```sh
espx flash --port /dev/cu.usbmodem101   # a port
espx flash --port 58:8C:81:53:AD:2C     # a board id
espx flash --port bench-c6              # a name from espx boards name
```

On a terminal espx draws one progress bar per flash region (bootloader, partition table, app), labelled with the region's offset:

```text
0x10000   [====================>                   ] 2/4
```

espflash skips a region whose contents already match, so flashing the image a board already holds shows no bar at all.

### Speed

`--baud` sets the bootloader connection speed. Without it espx takes `[board] baud` from the project's `espx.toml`, and without that espflash uses `espflash.toml`, `ESPFLASH_BAUD` or its own default.

### When the bootloader does not answer

If espflash cannot get the bootloader to talk, espx tries once more at 115200 baud:

```text
=> the bootloader did not answer; trying again at 115200 baud
```

When that fails too, or the board is not on the USB bus at all, espx prints what a person has to do above the error:

```text
needs a person: espx could not get the bootloader to answer, also at 115200 baud. Ask a person to hold BOOT, tap RESET, release BOOT, and then flash again.
```

The two cases are `download_mode` (hold BOOT, tap RESET, release BOOT) and `replug` (plug the board in, or unplug and replug it, with a data cable). AI agents get the same diagnosis as `needs_human` in the tool result. A port that another program holds open is neither case. espx does not retry it or ask for a person; close the other program and flash again.

> **Warning**
>
> A board behind a WCH CH34x or CH9102 USB bridge on macOS's built-in serial driver (a `/dev/cu.usbmodem…` port instead of `/dev/cu.wchusbserial…`) corrupts large writes. Flashing fails with `upload_corrupted`, at any baud. Install WCH's driver, or use the board's native USB port. `espx devices` warns about such a port before you try.

### Several boards

espx never asks which board to use. With several boards attached and none chosen, `flash` stops with `port_required` and lists them. Pass `-i` to let espflash ask instead; it needs a terminal.

### Flash and monitor

`espx flash --monitor` opens the serial monitor after flashing. In a terminal it hands over to `cargo espflash flash --monitor`, with espflash's key handling. With `--json`, or when stdin is not a terminal, espx reads the port itself, the same as `espx run` below.

## Run

```sh
espx run
```

`run` is build, flash and monitor. In a terminal with no other flags it builds through espx, then hands the terminal to `cargo espflash flash --release --monitor`. `Ctrl+R` resets the board and `Ctrl+C` exits.

With `--until`, `--expect`, `--reject`, `--timeout` or `--json`, or when stdin is not a terminal (`espx run | grep panic`), espx runs the steps itself:

1. Build through espx, with the size check.
2. Attach to the board, then release the port for `cargo espflash flash`.
3. Flash, reporting `flashing` and `flashed`.
4. Wait for the board to come back (`reconnected`), reset it so the boot log starts from the top (`reset`), and read its output.
5. Test each line against the patterns, and stop on a match, a rejected line or the timeout.

The timeout clock starts when the flashed board boots, not when the build does. [Scripts and CI](https://www.espx.dev/docs/cli/scripting) covers the patterns and exit codes.

`run` takes no `--baud`. It flashes at `[board] baud` from `espx.toml` (or espflash's default) and monitors at 115200, or `MONITOR_BAUD`.

> **Note**
>
> On this path the board boots twice: once when espflash resets it after writing, and again when espx resets it after reattaching, so the log you read starts at a clean boot.

## Pass arguments through

Everything after `--` goes, unchanged, to the one tool the command wraps:

| Command        | Arguments after `--` go to                   |
| -------------- | -------------------------------------------- |
| `espx build`   | `cargo build`                                |
| `espx flash`   | `cargo espflash flash`                       |
| `espx run`     | `cargo espflash flash` (the flash step only) |
| `espx monitor` | `espflash monitor`                           |

```sh
espx build -- --features wifi
espx flash -- --erase-parts otadata
```

espx validates none of it; cargo or espflash reports mistakes. With extra arguments, `flash` and `run` skip espx's own build, because those arguments are for `cargo espflash`, not `cargo build`. `cargo espflash` then builds on its own and espflash does its own size check.

## Clean

```sh
espx clean
```

Runs `cargo clean` in the project.

## Erase

```sh
espx erase
espx erase --port /dev/cu.usbmodem101
```

Runs `espflash erase-flash`, which wipes the board's whole flash and resets it. The board has no firmware afterwards. With one board attached espflash finds it; otherwise pass `--port`. If another espx process holds the board (a running monitor, the desktop app or `espx mcp`), `erase` borrows it through that process and gives it back.

## Flashing a board that is being watched

A serial port can be open in only one process at a time. espx shares boards between its own processes instead, so you can leave `espx monitor` running in one terminal and run `espx flash` in another. The flash goes through the monitor's process, and the monitor shows the new boot. [Monitor and replay](https://www.espx.dev/docs/cli/monitor#sharing-a-board) explains the board hub.

## Related

- [Monitor and replay](https://www.espx.dev/docs/cli/monitor)
- [Boards and ports](https://www.espx.dev/docs/cli/boards)
- [Commands reference](https://www.espx.dev/docs/cli/commands#espx-flash)
