Adding Hexpansion support

The code lives on Github - clone it to work on adding support for another hexpansion. Each supported Hexpansion has a small Python class in badge/hexpansion/. It declares the VID/PID for detection, the commands it can receive, and how to tell when a command has been carried out. Everything else is handled by the base class.

What a module needs

Extend HexpansionModule from badge/hexpansion/base.py and define:

AttributeDescription
VID, PID The vendor and product IDs from the Hexpansion's EEPROM. The base class uses these to detect that the hardware is plugged in and to look up FRIENDLY_NAME from the lookup table (see below).
COMMAND_OPTIONS A list of every command this Hexpansion can be asked to perform.

And define at least one of:

MethodDescription
on_button_down(event) Called for every button event. When the event satisfies the current command, set self.last_status = CommandStatus.PASSED. Use when the relevant hardware fires button events.
check_command() Called on every poll (~1 s). If the current command has been satisfied, set self.last_status = CommandStatus.PASSED. Use when relevant hardware doesn't fire events.

Optionally, you can also override:

methodDescription
decorate(command) A classmethod that turns a bare command name into the phrase shown on screen, e.g. "a" becomes "Smash A". This is not necessary, especially if your commands are clear, but it adds flavour. It is for display only, and even if the same command resolves to pontentially multiple strings (e.g. "Smash A" and "Bash A"), they are the same command. It's a classmethod because an instruction typically describes another badge's Hexpansion - there may be no instance, so it can't use per-instance state. If not overridden, the commands are shown directly. See Tildagon2024 (random verb + arrow glyphs) and MegaDrive (combo glyph sequences like "↓ ↘ → A") for examples.

The three modules that ship with the game - Tildagon2024, MegaDrive and GPS, in badge/hexpansion/ - should be good references.

Minimal example

A Hexpansion with a single big red button:

from .base import HexpansionModule, CommandStatus


class BigRedButtonModule(HexpansionModule):
    VID, PID = 0x????, 0x????  # from your hexpansion's EEPROM manifest
    COMMAND_OPTIONS = ["press"]

    def on_button_down(self, event):
        button = getattr(event, "button", None)
        if button is None:
            return
        if button.group != "BigRedButton": # This string comes from your EEPROM app
            return
        self.last_status = CommandStatus.PASSED

Because the button press sets self.last_status directly, nothing else needs overriding.

Register it

  1. Save your class as badge/hexpansion/MyHexpansion.py.
  2. Add it to MODULE_TYPES in badge/hexpansion/__init__.py:
from .MyHexpansion import MyHexpansionModule

MODULE_TYPES = [
    Tildagon2024Module,
    MegaDriveModule,
    GPSModule,
    MyHexpansionModule,  # add here
]
VID/PID must be in the lookup table. The table in badge/hexpansion_names.py is generated from the hexpansion-firmwares repo - run scripts/generate_hexpansion_names.py to regenerate it if your Hexpansion is added to the repo. Otherwise, add your VID/PID manually to MANUAL_FRIENDLY_NAME_OVERRIDES in the script.

Test it on hardware

With the badge plugged in over USB, run:

scripts/deploy-dev.sh

This requires either mpremote or pipx available, and builds the published payload from your working tree (uncommitted and untracked files included, so no git add needed while testing), installs it to /apps/racecondition_dev/ on the badge and resets it. It shows up in the launcher as Race Condition (dev), alongside any store-installed copy.

Then, with your Hexpansion plugged in, open the Race Condition app and select Test modules from the main menu and pick your hexpansion. It will step through each detected command for the hexpansion; perform the action and it advances automatically (hold Cancel for 2 s to skip). If your module doesn't appear it wasn't detected - check that its VID/PID is in badge/hexpansion_names.py and regenerate with scripts/generate_hexpansion_names.py if needed.