# xteink-web-emulator — architecture & roadmap **Goal:** an offline, static-asset web page where you upload an xteink X3/X4 firmware `.bin` and it runs *client-side* — real ESP32-C3 machine code, with the e-ink screen on a canvas, on-screen buttons, and a virtual SD card. No server, no proprietary engine. ## Why QEMU (and why not the alternatives) | Approach | Runs any `.bin` | Open source | Offline in browser | |----------|:---:|:---:|:---:| | **Wokwi** | ✅ | ❌ (closed engine, licensed) | ❌ | | **Recompile firmware → WASM** (crosspoint-simulator + Emscripten) | ❌ source only | ✅ | ✅ | | **QEMU (espressif fork) → WASM** | ✅ | ✅ | ✅ | Only QEMU satisfies all three. `espressif/qemu` models the ESP32-C3 SoC and executes real firmware; `ktock/qemu-wasm` shows QEMU compiling to WASM and running in-browser from static files. The endgame merges those two. ## Status **Phase 1 (native peripheral models) is done for the X3.** The unmodified canonical firmware (`crosspoint-reader/crosspoint-reader`, X3-selected) boots to the home screen, mounts a FAT32 SD image, lists its contents, and responds to button presses — all under native espressif-qemu with our patch. Reproduce: ```sh make qemu # clone espressif/qemu @ pinned tag, apply patch, build make firmware sdimage # 16 MB flash.bin + FAT32 sd.img from a pio build make run # boot the xteink-x3 machine (add `-display none` for headless) ``` Screenshots (via QMP `screendump`) confirmed: boot → home → Browse Files. ### Device selection (X3 vs X4) X3 and X4 share one ESP32-C3 binary and pick a profile at runtime via an I²C fingerprint (BQ27220 gauge + DS3231 RTC + QMI8658 IMU on SDA20/SCL0), with an NVS override (`cphw/dev_ovr`: 1=X4, 2=X3). **The emulator picks X3 vs X4 at launch** by starting the matching machine (`xteink-x3`, later `xteink-x4`); the X3 machine will expose the X3 fingerprint so stock dual firmware auto-detects. No recompiled/custom firmware — the current test build forces X3 only because the fingerprint I²C devices aren't modelled yet. ## The interfaces (native now → browser later) Each peripheral is modelled behind a QEMU-native seam that the WASM front-end reuses unchanged — no bespoke abstraction layer: | Peripheral | Native seam | Browser binding (phase 3) | |------------|-------------|---------------------------| | e-ink panel | `xteink-x3-eink` SSI device → QEMU graphical console (528×792, physical orientation) | `` from the same display surface | | SD card | `ssi-sd` + `sd-card-spi` backed by QEMU block layer (`-drive if=sd`) | browser-supplied blockdev (File/OPFS) | | Buttons + battery | `esp32c3.adc` QOM props `adci[1]`/`adci[2]`; power button = GPIO3 input | DOM buttons → `qom-set` equivalent | | Firmware image | flash via `-drive if=mtd` | uploaded `.bin` written to emulated flash | ## Phase 1 device models (in `qemu/patches/0001-xteink-x3-machine.patch`) - **`esp32c3.adc`** — SAR ADC: oneshot completes immediately; per-channel value is settable live via QOM (`adci[*]`). Feeds battery and the two resistor-ladder button groups. Replaced the original always-done register shim. - **`esp32_gpio`** — real OUT/ENABLE/IN registers with 32 named input + output lines. Outputs read high while a pin is input-configured (matches the board's pull-ups; without this, display CS floated low during SD probing). - **`ssi.esp32c3.spi2`** — the general-purpose SPI2 controller (CPU-buffer transactions on an SSI bus). DMA path deliberately unimplemented until needed. - **`xteink-x3-eink`** — UC8253 panel: decodes DTM1/DTM2 planes (52 272 B each), drives BUSY, renders to a QEMU console. Protocol from `chip/eink-x3.chip.c`. - **`ssi-sd` fix** — track card idle state so CMD58 reports ready after ACMD41 (the 9.2 fork hardcoded idle, which SdFat rejects). - **`xteink-x3` machine** — subclass of `esp32c3` wiring panel CS21/DC4/RST5/ BUSY6 and SD CS12 onto SPI2, and the SD card onto QEMU's block layer. ## Remaining phases 1. **X4 machine** — SSD1677 panel + X4 battery/ADC profile as a sibling machine. The X3 command trace (`0x18/0x0c/0x44/0x45…`) is already captured. 2. **Compile the patched fork to WASM** — merge Espressif's SoC models with qemu-wasm's WASM TCG/TCI backend; build `qemu-system-riscv32` with Emscripten. Output: static `.wasm` + `.js` + packaged BIOS/ROM. 3. **Browser front-end (static)** — upload `.bin` → emulated flash; `` from the display surface; DOM buttons → ADC/GPIO injection; preloaded SD image; launch-time X3/X4 selection. Fully airgapped. ## Repo layout | Path | Role | |------|------| | `flake.nix` | espressif-qemu release pkg (`nix run .#run-upstream`) + full QEMU build devShell | | `Makefile` | `qemu` (source build), `firmware`, `sdimage`, `run`, `chip` | | `scripts/build-qemu.sh` | clone pinned espressif/qemu, apply patch, build riscv32-softmmu | | `qemu/patches/` | the xteink X3 machine + device models (applied onto the pinned tag) | | `chip/eink-x3.chip.c` | legacy Wokwi model — the reverse-engineered X3 e-ink protocol reference | | `web/` | (phase 3) static front-end: upload + canvas + buttons | ## Debugging notes - Headless capture: run with `-qmp unix:…` and use `scripts/qmp-screendump.py`; the console is 528×792 (physical portrait), rotate not needed. - Inject a button: `scripts/qmp-qom.py set /machine/adc "adci[1]" 2760` (ladder-1 midpoints: Back 3512 / Confirm 2694 / Left 1493 / Right ~5; ladder-2 Up 2242 / Down ~5). Value > 3900 = no button. - Symbolize a panic PC: `llvm-addr2line -f -C -e firmware.elf 0x4210d286`. - Flash must be 16 MB — the partition table spans 16 MB even though the app fits under 4 MB. Espressif QEMU only accepts 2/4/8/16 MB mtd images. - Trace SD/panel: flip `DEBUG_SSI_SD` in `hw/sd/ssi-sd.c` or add an `info_report` in `xteink_x3_eink.c` (both off by default in the patch).