metronome/pico-explorer/code.py
Me Here 3192f3debc PM_X-1 0.0.1: Pimoroni Explorer sibling firmware + Kit 0.0.23 device-id reply
Adds pico-explorer/ as a parallel CircuitPython firmware target alongside the 52Pi
Kit in pico-cp/. Same engine, same program-string grammar, same programs.json, same
live-sync protocol. Read-only on the device (no on-device beat editing); the web
editor's Live sync mirrors all edits in real time and the Explorer emits its own
play/stop/bpm/sel deltas back.

Hardware (Pimoroni Explorer PIM744):
- RP2350B + 2.8" ST7789V 320x240 LCD (8-bit parallel; CircuitPython's official
  board definition pre-builds the BusDisplay so we just use board.DISPLAY).
- 6 user buttons - A/B/C on the left of the screen, X/Y/Z on the right.
- Piezo speaker on GP12 (PWM) with amp enable on GP13.
- I2C QwSTEMMA on GP20/21 - reserved, unused by the firmware.
- No touchscreen, no joystick, no RGB LED. Run state shows on a tiny on-screen dot.

Buttons:
- A = play/stop. B = tap tempo. C = menu.
- X = prev track (hold-repeat). Z = next track (hold-repeat).
- Y = tempo -1 (hold-repeat; -5 after 1.5s).
- X+Z chord = tempo +1 (mirrors Y).
- In a menu: X/Z move the row cursor, Y decrements, A cycles/increments/selects,
  B = back, C = close.

Files added:
- pico-explorer/{boot.py, code.py, app.py, programs.json, README.md}.
  app.py = 1444 lines (~73KB source -> 29.8KB compiled .mpy).
- info-explorer.html.

Files touched:
- pico-cp/app.py: bump to 0.0.23. Version-query (SysEx 0x02 -> 0x03) reply now
  includes the device id as "K;<version>" (backward-compat: editor parses
  "contains ';'?" - old firmware sent bare version, treated as K).
- editor.html + editor-beta.html: _parseDeviceReply() splits id;version, FW_PATHS
  maps id to .py/.mpy URL pair, so Update firmware now pushes the right binary.
- build.sh + deploy.sh: precompile pico-explorer/app.py -> dist/explorer-app.mpy,
  zip pm_x1_circuitpy.zip alongside pm_k1_circuitpy.zip, ship
  pico-explorer-app.{py,mpy} next to pico-cp-app.{py,mpy}.
- docs/livesync-protocol.md: new section 7 - per-device emit/apply matrix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 20:43:38 -05:00

24 lines
1.3 KiB
Python

# code.py - PM_X-1 A/B firmware loader (stable; rarely changes).
#
# The real application is the PRECOMPILED app.mpy (CircuitPython compiles a big .py at boot, which
# fragments the heap and OOMs; a .mpy loads without compiling). app.bak holds the previous known-good
# build. The web editor pushes a new app.mpy to a "trial" slot over USB-MIDI; this loader runs it, and
# if it fails to boot it AUTOMATICALLY ROLLS BACK to app.bak. (Unbrickable: BOOTSEL -> drag a .uf2.)
# app.mpy clears the /trial marker once it has run healthily for ~5s.
import supervisor, os
supervisor.runtime.autoreload = False # updates reboot explicitly; never auto-restart on our own writes
def _trial():
try: os.stat("/trial"); return True
except OSError: return False
try:
import app # runs the application (app.mpy; ends with App().run())
except Exception:
if _trial(): # a freshly-pushed build crashed on startup -> roll back
try:
os.remove("/app.mpy"); os.rename("/app.bak", "/app.mpy"); os.remove("/trial")
except Exception: pass
supervisor.reload() # reboot into the restored known-good build
else:
raise # the active build failed unexpectedly (rare) -> on-screen traceback