Code for the 2026 Spaceagon badge is split into EEPROM Hexpansion apps, host-side tools, and backups:
hexpansions/
compass/ Magnetic compass when flat; identifies all six edges
gravity-ball/ Accelerometer-driven ball in a hexagonal arena
led-fall/ Gravity-aware falling sequence on LEDs 1 through 12
thermal/ Hottest temperature seen by an MLX90640 on the Qwiic bus
tools/
MEMS-sensors.py
backups/
EEPROM-backup-0x7063-app.py
SHA256SUMS
Each Hexpansion directory contains readable source at app.py and the exact,
space-optimized EEPROM artifact at dist/app.py. Copy only the latter to the
root of the Hexpansion filesystem as /app.py. The existing EEPROM header and
filesystem geometry should be preserved.
- Lay the badge flat for a smoothed magnetic compass.
- Rotate it flat through a full turn to improve calibration.
- Rest it on an edge to show one of all six physical edges and its port.
- CONFIRM resets compass calibration; CANCEL minimises the app.
- Tilt the badge to roll the ball in the same screen direction.
- The ball bounces, settles against walls, and cannot leave the pointy-top hexagonal arena.
- CONFIRM returns the ball to the centre; CANCEL minimises the app.
- A bright front and fading tail fall from the current physical top toward the current physical bottom across the 12 top LEDs.
- When the badge is flat and has no meaningful in-plane down direction, the sequence pauses and becomes a faint neutral halo.
- UP increases falling speed; DOWN decreases it.
- CANCEL hides the controls but deliberately leaves the animation running in the background. Removing the Hexpansion clears its LEDs and restores the system pattern service when that service is enabled in settings.
- Needs an MLX90640 thermal camera (default I²C address
0x33) on the Hexpansion's Qwiic/STEMMA-QT connector; it reads the bus asconfig.i2c(or probes ports 1-6 when installed under/apps). - Three modes, cycled with CONFIRM (UP/DOWN also cycle): VIDEO — live ironbow heatmap, auto-ranged, with max/min overlays; MAX and MIN — one big number each. CANCEL minimises.
- With no camera attached the screen stays on "Reading camera..." and retries once a second, so it also recovers from unplugging mid-run.
Readings apply the MLX90640 factory calibration to every valid pixel, skip
pixels the sensor marks broken or outlying, and update at each 4 Hz chess
subpage. Emissivity 0.95 and Tr = Ta - 8 °C are assumed, so shiny or
low-emissivity surfaces read differently from their true temperature.
Ported from the Protogon prototyping board's thermal app in
/home/silvio/dev/codemyriad-protogon-work (branch thermal-camera-test),
then rewritten for speed: calibration is decoded once at startup into flat
per-pixel arrays, the Melexis compensation and palette mapping run as
@micropython.native passes, subpages arrive in two 416-word I²C chunks
(the badge bus is fixed at 133 kHz — ~115 ms per subpage is the wire floor),
and video is a single smoothed ctx.texture() RGB565 blit.
dist/app.py is produced by python3 build.py (NOT the plain pyminify
command used by the other hexpansions): it first strips --dev-*-marked
debug/timing/port-probe code, then minifies, and fails if the result exceeds
the ~6.4 KB EEPROM budget. Copy dist/app.py to the root of the Hexpansion
filesystem as /app.py (header and filesystem geometry preserved), e.g. with
the Hexpansion in slot 1:
mpremote cp hexpansions/thermal/dist/app.py :/hexpansion_1/app.pyFor development, install the full source as a badge app instead — timing
prints (DEBUG = True) then arrive over USB serial:
mpremote mkdir :/apps/thermal
mpremote cp hexpansions/thermal/app.py :/apps/thermal/app.py
mpremote cp hexpansions/thermal/metadata.json :/apps/thermal/metadata.json
mpremote run tools/thermal-bench.py # per-stage pipeline timingshexpansions/thermal/sim/ fakes the badge modules and the I²C bus and replays a
recorded real calibration/frame pair, asserting the expected 42.0 °C maximum,
a plausible minimum, the 32×24 RGB565 texture geometry, and mode cycling:
cd hexpansions/thermal/sim
python3 run_sim.py # readable source
python3 run_sim.py --dist # the exact EEPROM artifact
python3 run_sim.py --no-camera # retry/error handlingThe capture is a regression fixture, not a model of other scenes or of sensor timing. Use real hardware for final temperature and performance checks.
From an individual Hexpansion directory:
uvx --from python-minifier pyminify \
--remove-literal-statements \
--rename-globals \
--output dist/app.py app.pyKeep dist/app.py below about 6,000 bytes for the current 8 KiB EEPROM's
LittleFS partition. Both source and built artifacts must export
__app_export__.
The Spaceagon accelerometer maps to screen gravity as:
screen_x = accelerometer_y
screen_y = accelerometer_xThe firmware checkout used only as an API reference is
/home/silvio/dev/badge-2024-software.