Repurposing my Temporal Replay 2026 conference badge (Moscone Center, San
Francisco, May 6–7 2026) by writing new MicroPython apps against its
existing on-device API. The firmware stays
intact — we don't reflash, we just upload Python to /apps/.
The badge is an ESP32-S3-WROOM-1 16N8 running Amelia Wietting's custom firmware (Arduino C++ + embedded MicroPython v1.28-preview). Hardware: 128×64 SSD1306 OLED, 8×8 IS31FL3731 LED matrix, LIS2DH12 IMU, NEC IR TX/RX, vibration motor with coil-tone audio, 4 face buttons + analog joystick, LiPo battery.
badge-fs/ # read-only mirror of the badge filesystem — gitignored;
# populate locally with scripts/pull-fs.sh
# (firmware code is Amelia's work, not redistributed here)
docs/ # tooling & recovery notes specific to this workspace
tooling.md # mpremote `resume` quirk, push/pull workflows
recovery.md # esptool restore from the 16 MB backup
my-apps/ # new OLED apps (folder-per-app or single-file)
hello_world/
main.py # entry point (tiny — just calls run_app())
app.py # OLED + buttons + IMU demo, native chrome, crash-safe
my-matrix-apps/ # new LED-matrix-only apps (created on demand)
scripts/
repl.sh # drop into screen at the right baud
pull-fs.sh # sync badge -> ./badge-fs/, with retries
push-app.sh # deploy my-apps/<name> -> /apps/<name> on the badge
.gitignore
CLAUDE.md
Instructions.md # original handover prompt; kept for context
The on-device docs in badge-fs/docs/ are excellent — MicroPythonDeveloperGuide.md
is 29 KB of well-written app-author guidance and API_REFERENCE.md is
39 KB of function-by-function reference. Read those for API details.
This workspace's docs/ is intentionally thin and only covers
workspace-specific things (tooling quirks, recovery from backup).
# 1. (one-time) sync the badge filesystem locally — required, the docs
# in badge-fs/docs/ are the canonical API reference and are not
# redistributed in this repo
scripts/pull-fs.sh
# 2. drop into the REPL
scripts/repl.sh # Ctrl-A K Y to detach
# 3. deploy and run the starter app
scripts/push-app.sh hello_world --runThe conventions enforced by the firmware are written up in
badge-fs/apps/README.md and badge-fs/docs/MicroPythonDeveloperGuide.md.
The short version:
- Single-file (
my-apps/foo.py) for one-shot demos. AllbadgeAPI functions are auto-injected as globals at the entry script's scope. - Folder (
my-apps/foo/main.py) for anything serious. The folder gets added tosys.pathand youfrom badge import *in submodules. - Wrap the main function in
badge_app.run_app("name", main)so an uncaught exception ends up in/last_mpy_error.txtwith a native crash screen, not a frozen display. badge_ui.chrome("Title", "right", "OK", "label", "BACK", "label")for header + footer matching the firmware. The button glyphs follow the user's confirm/back swap setting automatically.- Use
BTN_CONFIRM/BTN_BACKfor menu-style buttons (semantic) andBTN_UP/DOWN/LEFT/RIGHT(or PS aliases) for game controls. - Wrap LED-matrix drawing in
with_led_override(callback)so the ambient matrix mode resumes cleanly on exit.
my-apps/hello_world/ is a working starter that demonstrates all of the
above.
- No GPIO from Python.
import machineandimport esp32are intentionally blocked. The bottom-edge JST connectors labeled CAM and I²C are unreachable without a custom firmware build. (See "Rebuild?" below.) - 2 MB Python heap from PSRAM. Use
GCTickerorDualScreenSessionfrombadge_appin long-running loops. - Python runs on Core 1 only. Don't busy-wait — the firmware service pump on Core 0 needs cycles to keep USB, IR, and the LED matrix alive.
- IR is mode-gated. Call
ir_start()before anyir_*andir_stop()when done. Pollir_read()within 50 ms or the RX buffer overflows. - Native USB-CDC + JTAG over USB-C. Toggling DTR/RTS resets the
device.
mpremoteneeds theresumekeyword (seedocs/tooling.md). - Escape chord: holding all four face buttons for ~1 s force-exits any running app — firmware-level, you don't implement it.
I considered replacing the firmware and got pushed back hard by the trade-off:
- You'd gain: GPIO access (CAM/I²C JSTs become useful), no curated-API ceiling, freedom to pull in BLE/ESP-NOW/MQTT.
- You'd lose: native UI chrome with button-glyph hints, hardware
mouse-overlay, the multi-word IR NEC frame extension with software CRC,
the crash-screen lifecycle, the production game ports (Doom!), the
JumperIDE workflow, and the auto-imported flat
badgeAPI. Each of those is days-to-weeks of replacement work. - You'd risk: one bad
esptool write-flashand you're soldering JTAG. Native USB-CDC means there's no UART fallback if early-boot firmware crashes. The 16 MB backup at~/temporal-replay-2025-badge-backup.binexists but writing it back takes ~25 minutes.
Decision: stay on the existing firmware. If a future project
specifically needs the I²C JST, look up Amelia's firmware repo (the
on-device docs reference firmware/src/, pio run -e echo-dev, and
scripts/generate_startup_files.py — strongly suggests a public
PlatformIO project) and do a targeted fork, with the backup verified
beforehand.
doom1.wadis excluded by.gitignoreand skipped bypull-fs.shby default — it's multi-megabyte over USB-CDC and not architecturally interesting. Usepull-fs.sh --with-doomif you want it locally.- The original full flash image. It lives at
~/temporal-replay-2025-badge-backup.bin— keep it there, not in this repo.
Badge firmware and the on-device docs/API are by Amelia Wietting. This workspace just adds new MicroPython apps on top.