Releases: rvben/esprite
Release list
v0.4.5: real LVGL firmware support
Highlights
A genuine LVGL app, fully drivable under the emulator
tools/qemu/lvgl_demo is a real LVGL 9 application (a two-screen device control panel) on the standard component stack - lvgl + esp_lvgl_port + the standard esp_lcd_touch interface. esprite taps flip its switch, set its slider, and BOOT changes screens, with every state byte-matched against committed goldens. It runs on the same board spec as the bundled fixture: ESPRITE_QEMU_IMAGE selects the firmware.
- New component
esp_lcd_touch_esprite: the standard touch-driver contract implemented over the input agent, so touch reaches LVGL through a normal indev driver - no esprite-specific input code in the application. - Guest-held taps: the agent's new
tap x y msverb holds the press guest-side; live-state touch pollers (LVGL indev drivers) can miss a host-paced press entirely. - Capture pumping around injections: a pending frame blocks the guest's whole UI task, input polling included, until the host consumes it; the backend now syncs the display around every tap, swipe, and gpio injection so interactions complete.
- The README documents the full recipe, including the full-refresh + static-buffer display rule for RAM-tight chips.
The informational CI job builds and caches the LVGL image alongside the fixture and runs the whole gated suite against real emulated firmware on every push.
Binaries below carry the generic native targets plus both qemu board specs; the waveshare_amoled_18 target needs a source build against a firmware checkout (see README). esprite is alpha software; interfaces may change.
v0.4.4: shutdown, capture, and reporting hardening
Highlights
Hardening
- The
runsession now notices Ctrl-C/SIGTERM while idle: a signal landing in the gap before its input read blocks (or delivered on another thread) previously left the session waiting for the next command line; a self-pipe wake-up closes the window. - The QEMU child's serial capture keeps a bounded 1 MiB tail instead of growing for as long as a
servesession runs; expect regexes and failure diagnostics only ever read recent output. - Boot failures for a missing emulator binary name the offending path on both macOS and Linux.
- The fixture build image is pinned by digest, so an upstream re-tag cannot silently change fixture bytes and golden stability.
Docs
- New README section: What each backend is authoritative for - a fidelity matrix across host-native, QEMU tier 1 (any flash image), and QEMU tier 2 (cooperating firmware), including per-architecture determinism (ESP32-C3 byte-exact under icount; ESP32/S3 wall-clock).
Binaries below carry the generic native targets plus both qemu board specs; the waveshare_amoled_18 target needs a source build against a firmware checkout (see README). esprite is alpha software; interfaces may change.
v0.4.3: QEMU networking
Highlights
snapshot works on emulated firmware (tier 2)
The QEMU machine emulates an OpenCores ethernet; a board spec with "http": {"guest_port": N} boots with user-mode networking and a localhost port forwarded into the guest, so snapshot POSTs into the firmware's real HTTP server (lwIP over the emulated NIC, built with CONFIG_ETH_USE_OPENETH=y):
- One code path for both backends: the CLI targets whatever HTTP port the active backend reports (native: the in-process webserver; qemu: the forward).
- The scenario
snapshotstep works on qemu targets too - the bundled golden scenario now taps, presses a button, and posts data into a real emulated guest, byte-comparing all three frames. serveprints the forwarded URL so live bridges can drive emulated firmware.
Flash images boot in snapshot mode
QEMU opens mtd drives for writing, so a boot could mutate your image file - and a read-only image failed to boot outright. Guest flash writes now land in a throwaway overlay: images stay pristine, and read-only images (CI cache artifacts, shared fixtures) boot fine.
Emulator suite in CI
A separate informational workflow runs the full gated emulator suite (display, input agent, networking, byte-exact goldens) on every push, with the prebuilt emulator and the docker-built fixture cached. Its runtime library list is ldd-derived and documented in tools/qemu/ci-deps.sh.
Binaries below carry the generic native targets plus both qemu board specs; the waveshare_amoled_18 target needs a source build against a firmware checkout (see README). esprite is alpha software; interfaces may change.
v0.4.2: QEMU input
Highlights
Input injection for emulated firmware (tier 2)
tap, swipe, gpio, and button now work on qemu-backed targets whose firmware runs esprite's tiny esprite_qemu_agent component (one task on UART1, wired by esprite to a second serial chardev):
- The QEMU fork emulates no GPIO or touch hardware (its C3 gpio model reflects nothing into
gpio_get_level), so cooperating firmware polls the agent's event-counter APIs - counters, not live state, so a fast host tap or a short pulse is never missed. - Board specs grew
"agent": trueand a"buttons"array; spec buttons render as clickable bezel nubs in--window(view-only on qemu for now) and are pressable by label. button <label>works on every backend now: any board button is pressable by its silk-screen name (e.g.button BOOTon the CYD).- New error kind
agent_failed(exit 10) when the guest agent is absent or unresponsive.
Scenarios run on qemu targets, with the first emulator goldens
scenario boots through the backend seam and gates per step:
- New
settle {ms}step: the portable time verb (native pumps the loop, qemu pumps the child). - New
pixel {x,y,value,timeout_ms}step: a framebuffer assertion with a retry deadline - deterministic against wall-clock guests, exact on flat-color fixtures. scenarios/qemu_esp32c3_rgb.jsontaps and presses a real emulated guest and its screenshots byte-match committed goldens (make qemu-goldensregenerates).stepsandexpectstay native-only and say so;serialmatches against the backend's capture on both.
Binaries below carry the generic native targets plus both qemu board specs; the waveshare_amoled_18 target needs a source build against a firmware checkout (see README). esprite is alpha software; interfaces may change.
v0.4.1: QEMU display
Highlights
The QEMU backend gained a display (tier 2)
A qemu-backed target whose firmware is built against Espressif's esp_lcd_qemu_rgb component now renders into esprite's framebuffer via QMP screendump:
- New target
qemu_esp32c3_rgb(320x240 virtual RGB panel).screenshot,serve --shot, and the live--windowwork exactly as on native targets - the bezel window shows the real emulated guest's pixels, refreshed at 10 Hz. - Draw full frames in cooperating firmware: the virtual panel consumes one pending draw per host-side capture, so per-line drawing stalls headless. The bundled fixture (
tools/qemu/rgb_demo) shows the pattern. - New error kind
capture_failed(exit 9) for a screendump or decode failure after boot.
Qemu targets are data, not code
Board specs live in targets/qemu/*.json (key, machine, arch, optional display dimensions) and ship inside the binary. ESPRITE_QEMU_BOARD=/path/to/board.json registers your own qemu board at runtime, no rebuild. The serial-only qemu_esp32c3 target is unchanged in behavior, now defined by data.
More
- Gated end-to-end display tests against a scripted
esp_lcd_qemu_rgbfixture image (make qemu-fixtures); the default build and tests still never require QEMU. - A qemu boot now records the active target, so board-aware capability gating works identically across backends.
Binaries below carry the generic native targets (sample_gfx, cyd, cyd_tft) plus both qemu board specs; the waveshare_amoled_18 target needs a source build against a firmware checkout (see README). esprite is alpha software; interfaces may change.
v0.4.0: device-bezel window, board-named targets
Highlights
Device-bezel window
serve --window now presents the device instead of a bare framebuffer:
- The screen renders pixel-exact inside a slim device bezel. Bezel chrome draws at desktop density, so
--scale Nenlarges the screen while the chrome stays crisp. - Physical buttons appear as clickable nubs on the bezel edge, at the positions the board declares (
SimButtongainededge/posfields; existing board definitions compile unchanged). Hover a nub for its label and keyboard shortcut. ?opens a key-map overlay; backtick opens a hardware-controls panel (battery level, charging/USB, rotation) on boards that declare those capabilities.- Input semantics hardened: opening an overlay releases every held button and in-flight drag, so no phantom input leaks into the firmware.
- Layout math lives in an SDL-free module with unit tests; SDL2 remains optional.
Targets are named by the board, not the app
The target formerly named agentgauge is now waveshare_amoled_18 (Waveshare ESP32-S3-Touch-AMOLED-1.8, 480x480). Boards are a finite, stable set while firmwares are open-ended, so target ids follow the hardware, matching cyd/cyd_tft. The agentgauge firmware itself keeps its name. If you scripted --target agentgauge, switch to --target waveshare_amoled_18.
More
motioninjection and serialexpectfor scenario tests against motion-driven firmware behavior.- The firmware's LVGL font sources are now globbed, so the sim build tolerates the firmware adding or dropping font sizes between checkouts.
Fixes
serveschema describes the bezel window accurately.- The board-variant integration tests skip cleanly when the firmware checkout is absent.
Binaries below carry the generic targets (sample_gfx, cyd, cyd_tft); the waveshare_amoled_18 target needs a source build against a firmware checkout (see README). esprite is alpha software; interfaces may change.
v0.3.0: QEMU emulator backend
Highlights
QEMU emulator backend (new, tier 1)
esprite can now run a real compiled flash image under Espressif's QEMU fork, alongside the host-native backend, behind the same CLI:
- New target
qemu_esp32c3:serial,logs, and headlessserveagainst any ESP32-C3 image (ESPRITE_QEMU_IMAGE). Boots real ESP-IDF and Arduino binaries. - Deterministic on ESP32-C3: icount mode gives byte-for-byte identical serial output across runs. Xtensa (ESP32/S3) runs wall-clock only (icount hangs in the current fork release).
make qemu-fetch(pinned prebuilt emulators, no source build),make qemu-fixtures(scripted test images),make qemu-test(self-skipping; default build and tests never require QEMU).list-targetsreports abackendfield; missing prerequisites yield the newbackend_unavailableerror kind; everything not yet emulator-capable degrades explicitly tounsupported.- Signal-safe child lifecycle: the emulator process never outlives the CLI, including Ctrl-C mid-boot.
More
- agentgauge firmware target onboarded (Wi-Fi Claude usage-limit desk gauge).
wifi up/wifi downto control the simulated link state.swipecommand for gesture validation.expectassertions in scenarios and the run session.- Live BLE bridge:
serve --ble-port Nexposes the virtual link to real host processes.
Fixes
- LVGL screen ownership for
uisnapshots; display and buffer reuse. strlcpyshim only defined where libc lacks it.- peripherals library declares its real link dependency on core (fixes GNU ld builds).
Binaries below carry the generic targets (sample_gfx, cyd, cyd_tft); the agentgauge target needs a source build against a firmware checkout (see README). esprite is alpha software; interfaces may change.
v0.2.0
The firmware's primary data path, BLE, now runs in the simulator. Still early software; interfaces may evolve.
Added
- BLE Hardware Buddy simulation: a virtual BLE link bound at the firmware's own
ble.hboundary runs the realprotocol.cpp/app_buddy.cppcompiled from source. Heartbeat snapshots, permission prompts (Approve/Deny on-screen, decisions sent back over the link), HID keyboard reports, passkey pairing with the code rendered on the device, and the 3 s hold-to-pair gesture all work end to end. New target:waveshare_amoled_216_c6_buddy. blecommand in all three dialects (one-shot, run session, scenario):connect [--passkey N],pair,disconnect,send,recv,hid. One-shotble sendcompletes the whole round trip: connects bonded, delivers the JSON line, returns the device's replies, and--shotcaptures the resulting frame.button pwr-long/pwr-releaseinject the power button's hold-gesture edges; the SDL window's PWR control follows the hardware's hold semantics (short click vs 1.5 s long-press).- Waveshare AMOLED 2.16 (ESP32-S3) target: rotation-capable variant of the 480x480 panel, sharing the one compiled firmware. Seven targets.
- Prebuilt binaries: release tags now attach macOS (arm64) and Linux (x86_64) tarballs with the generic targets; Clawdmeter-backed targets need a source build against a firmware checkout (
CLAWDMETER_SRC). make dist,make build CLAWDMETER_SRC=...passthrough, and a manual workflow that tests against upstream Clawdmeter.
Fixed
- Firmware version skew no longer breaks configure in either direction: new firmware files warn by name, files missing from an older checkout drop out cleanly.
serveshuts down cleanly on Ctrl-C/SIGTERM.- A webserver test raced the kernel's accept queue under load.
v0.1.1
Hardening and correctness fixes across the CLI, shims, and rendering pipeline. Still early software; interfaces may evolve.
Fixed
- Screenshots taken after injecting data (
snapshot --shot, scenario steps, run-session captures) could show the previous frame: capture now settles past a full LVGL refresh period first. - An invalid
serial expectregex crashed the process; it is now a structuredbad_argserror in both the one-shot CLI and the run session. - Unknown
--options, garbage or out-of-range numbers, out-of-bounds taps, and non-JSON snapshot bodies were silently accepted; all are now rejected against the schema contract. A bare--ends option parsing. - The run session desynced on lines over 16 KB, corrupted LVGL state on a second
boot, and spoke an undocumented flat error vocabulary; it now replies with the same kind/message envelope as the one-shot CLI, rejects re-boots, and answers an oversized line with one error. - Scenario steps bypassed board-capability gating and used off-schema exit codes.
- Shim fidelity:
collectHeadersretains only declared headers (device semantics), real HTTP reason phrases, read-onlyPreferencesreject writes,Serial.printfno longer truncates at 1 KB, webserver listeners reset on every boot, and snapshot posts can no longer land on an unrelated host process. - TFT_eSPI: drawing on an unbuffered sprite no longer writes to the screen, sprites compose through their parent,
createSpriterejects non-positive dimensions, and rotations 2/3 mirror their 0/1 counterparts.
Added
--version(any argument position), version single-sourced from CMake into the schema.make releaseandmake install(optimized build,cmake --install).- CI on ubuntu and macos running the same make targets used locally.
- Configure-time detection of new firmware source files (clear warning instead of a linker error).
Changed
- Run-session and scenario error replies now use the documented
{"error":{"kind":...,"message":...}}envelope; scenario failures exit with the first failing step's schema exit code.
esprite v0.1.0
First tagged release of esprite, a host-native ESP32 firmware simulator.
- Compiles firmware from source and renders the display; agent-facing CLI (screenshots, input injection, scenarios, JSON, clispec).
- Runtime board selection: one firmware, many boards. Targets: Waveshare AMOLED 2.16 C6 (480x480), Waveshare AMOLED 1.8 (368x448), sample_gfx, and the CYD (ESP32-2432S028R) via Arduino_GFX and TFT_eSPI.
- Supported display libraries: LVGL, Arduino_GFX, TFT_eSPI.
Also reserves the esprite name on crates.io and PyPI.