Medius - Rust LibraryFlash

Flash

Reflash a box from Rust

Writes new firmware onto a box's two chips (see Flashing), rebooting a chip into download mode then writing the image. Behind the flash Cargo feature, off by default.

cargo add medius --features flash

With the feature off, none of the medius::flash items below exist.

What you need first

esptool.py, the chip, the address

esptool.py must be on PATH as the script, not a bare esptool binary (else Error::FlashTool).

flash exists on Linux and Windows only, compiled out on macOS. The consts and esptool_args are always present.

The four consts are public:

const ESPTOOL: &str
const CHIP: &str
const FLASH_ADDR: &str
const ROM_SETTLE: Duration
EXAMPLE
use medius::flash;

println!("tool:    {}", flash::ESPTOOL);     // esptool.py
println!("chip:    {}", flash::CHIP);        // esp32s3
println!("address: {}", flash::FLASH_ADDR);  // 0x10000
println!("settle:  {:?}", flash::ROM_SETTLE); // 2s

flash

Reboot into download mode, then write the image
fn flash(port: &str, bin_path: impl AsRef<Path>, host: bool) -> Result<()>

Blocks

PARAMETERS
ParameterTypeDescription
port&strSerial port the box is on, for example /dev/ttyACM0.
bin_pathimpl AsRef<Path>Path to the firmware image to write.
hostboolPicks the chip. false flashes the device chip, true the host chip.

The host flag picks the reboot target: false is RebootTarget::DeviceDownload, true is RebootTarget::HostDownload (see reboot for the full set).

One call reboots the chosen chip into download mode, frees the port, then runs esptool.py write_flash; the firmware-side sequence is on Flashing.

Blocks for the wait plus the tool's runtime, returning Ok(()) on a clean exit else Err(Error::FlashTool).

EXAMPLE
use medius::flash;

// false -> device chip
flash::flash("/dev/ttyACM0", "device.bin", false)?;

// true -> host chip
flash::flash("/dev/ttyACM0", "host.bin", true)?;

Inspecting the command

See exactly what esptool runs
fn esptool_args(port: &str, bin_path: &Path) -> Vec<String>

No round-trip

Builds the exact argv flash passes to esptool.py, without running it. To debug, reboot the chip into download mode via reboot and run these args by hand.

EXAMPLE
use std::path::Path;
use medius::flash;

let argv = flash::esptool_args("/dev/ttyACM0", Path::new("device.bin"));
println!("esptool.py {}", argv.join(" "));
// esptool.py --chip esp32s3 --port /dev/ttyACM0 --before no_reset
//   --after hard_reset write_flash 0x10000 device.bin

When it fails

Error::FlashTool

The feature adds Error::FlashTool(String) to the Error enum. It fires when esptool.py can't be spawned (not on PATH) or exits non-zero, the inner String carrying the stderr tail.

EXAMPLE
use medius::{flash, Error};

match flash::flash("/dev/ttyACM0", "device.bin", false) {
    Ok(()) => println!("flashed"),
    Err(Error::FlashTool(msg)) => {
        eprintln!("flash failed: {msg}");
        eprintln!("hint: is esptool.py on PATH and the port correct?");
    }
    Err(e) => eprintln!("other error: {e}"),
}

Flashing in tests

Swap the runner, skip the hardware

flash wraps flash_with; pass a fake CommandRunner and a no-op reboot closure to test without hardware.

fn flash_with<R, F>(port: &str, bin_path: &Path, host: bool, runner: &R, reboot: F) -> Result<()> where R: CommandRunner, F: FnOnce(&str, bool) -> Result<()>

Blocks

EXAMPLE
// A fake runner records the argv instead of running esptool; the reboot is a no-op.
flash::flash_with("/dev/ttyACM0", bin, false, &recording_runner, |_port, _host| Ok(()))?;