Bench manual
Everything on the bench page, and how the same runs are reproduced headlessly for validation.
Run firmware
Pick a gallery example or load your own .merged.bin
(arduino-cli produces one per sketch). Press Run — the
emulator steps both cores in a background worker (never on the UI
thread), and output appears in the console as the firmware prints.
Stop halts stepping; Reset reloads the
same image from its first boot instruction. The steps-per-frame slider
sets the base batch the worker scales adaptively each frame; the MIPS
readout shows smoothed emulated instructions per wall second.
Speed controls
Clock selects the emulated time rate:
Accurate ticks cycle-exactly,
Balanced (default) batches the same ratio with fewer
overheads, Turbo runs 2× virtual time so
delay-heavy boots finish sooner (timing-sensitive demos may misread —
switch back to Accurate if a demo looks off). Engine
selects Single (both cores serialized in one worker,
stable) or Dual (virtual-device dispatch offloaded to
a second worker, experimental). Transport selects how
frames reach the page: Auto uses zero-copy
SharedArrayBuffer rings when the page is cross-origin isolated,
else postMessage. Local SAB development needs COOP/COEP
headers — serve with python3 tools/serve.py instead of
plain http.server.
MicroPython REPL
The MicroPython preset in the firmware rail boots the
stock v1.29.0 GENERIC_S3 interpreter to an interactive
>>> prompt — no build needed. Press
▶ REPL: the page loads the bundled
tools/firmware/ESP32_GENERIC_S3-20260824-v1.29.0.bin
(same-origin, works offline), pads flash to 3 MiB, appends the
littlefs vfs partition the firmware mounts (same recipe
as tools/micropython_repl.sh), and loads it. Press
Run and wait for >>> (tens
of seconds: the interpreter is a ~35M-instruction boot). The preset
flips the serial port to UART0 — the REPL listens
there, not on USB-CDC — and offers print(6*7) as a first
line; type any Python and press Enter. The URL box accepts any other
image (a local copy served beside the page, or
Load merged.bin). Upstream
micropython.org serves no CORS header, so a cross-origin
fetch fails in the status line — self-host the file in that case.
Serial in and out
The console merges USB-CDC (Serial) and UART output.
Search, copy, and clear act on the scrollback buffer, which holds the
last 5000 lines. To send keystrokes, choose the port your firmware
reads — USB-CDC for Serial sketches, UART1 for the echo
example — type, and press Enter. Lines are sent with a trailing newline
unless you hold Shift, matching what readStringUntil and
REPL readers wait for. Long pastes are chunked under the 128-byte
hardware FIFO depth, the way silicon drops overrun bytes.
Pin header
The header shows live levels for GPIO0–39. Amber means driven high, dim means low, blue marks pins switched to input view. Click any pin to flip its display between output and input. The levels come from the same GPIO matrix the firmware drives, including peripheral-routed signals such as PWM, RMT, and I2S — so a blinking LED sketch visibly blinks here.
Fixtures
Some gallery entries need a stand-in for hardware that cannot exist in a browser tab. The manifest declares them and the bench applies them before boot — the same values the headless harness feeds through environment variables:
TOUCH_INJECT=3:1877 touch pad 3 reads counter 1877 ADC_INJECT_MV=825 ADC1_CH3 (GPIO4) reads 825 mV TEMP_C=25 internal temperature sensor reads 25 °C WIFI_SCAN_APS=... fixture access points for the scan sketch WIFI_STA_CONN=1 complete the station association SPI_SDSPI=1 attach the virtual SD card over SPI FLASHENC_KEY=... boot the image through XTS flash encryption
Nothing about the firmware changes — fixtures only answer the reads real hardware would answer.
Validate
The battery replays all 124 cases headlessly: build (or reuse) each
merged image, boot it through run_flash or the Node bridge,
and assert serial markers plus internal state. Run a slice locally with
./tools/run_battery.sh wifi_scan wifi_sta, or the whole
suite with ./tools/run_battery.sh. The freshness guard
(tools/check_firmware_freshness.py) keeps committed bins in
sync with their sketch sources, gallery copies in sync with the bins
(python3 tools/sync_gallery.py refreshes them), and
tools/webui_playwright.py
drives the real page in Chromium — 32 checks covering boot, MIPS, echo,
UART round-trip, SD mount, touch read, worker boot, Wi-Fi/ESP-NOW/L3,
BLE, MicroPython, gateway panels — failing loudly on any page error.