Skip to content

Latest commit

 

History

History
161 lines (126 loc) · 7.91 KB

File metadata and controls

161 lines (126 loc) · 7.91 KB

Hacking on ThemeParkWaits

This project assumes you'll modify it. It's MIT-licensed, the art pipeline needs no code changes, and everything domain-specific lives in a dozen small files. The README covers installing and running the stock sign; this page is the map for changing it: the dev loops, where each behavior lives, and the traps that cost the author real time.

Set up a dev environment (no hardware needed)

Two sibling checkouts — the app, and the ScrollKit LED-matrix library it runs on:

git clone https://github.com/czei/themeparkwaits
git clone https://github.com/czei/scrollkit
cd themeparkwaits
python3 -m pip install pygame pillow pytest

The app runs unchanged on desktop in ScrollKit's pygame simulator — real screens, real themeparks.wiki data:

# Live 64×32 preview window
PYTHONPATH="../scrollkit/src:src" python -m src.themeparkwaits --dev

# Same, throttled to the board's real frame rate
SCROLLKIT_HW_SIM=1 PYTHONPATH="../scrollkit/src:src" python -m src.themeparkwaits --dev

# Domain tests (mocked HTTP)
pytest tests/

Path note: examples elsewhere in this repo (docs, tool docstrings) write the library path as ../ScrollKit Library/src — the author's checkout name. Same thing; substitute wherever your scrollkit checkout lives. python -m src.themeparkwaits --dev finds either sibling name on its own.

Two desktop traps (SCROLLKIT_NOTES.md is the full gotcha ledger):

  • Never import displayio or import wifi bare in app code — PyPI packages by those names shadow the platform modules. If the simulator behaves strangely, run pip uninstall wifi displayio.
  • Simulator-green is not device-green. RAM limits, native-type quirks, and animation start-time stalls only show on the board (watch serial for ANIM-*-FAIL).

The device loop

scripts/deploy.sh --dry-run   # show exactly what would copy
scripts/deploy.sh             # deploy to a USB-connected board

Four things trip people up:

  • deploy.sh ships git archive HEAD, not your working tree. Uncommitted edits silently don't deploy. Commit first.
  • The board decides who may write the flash. Hold the DOWN button while powering on to make the drive Mac-writable (deploy mode). A normal boot makes it read-only to your Mac — a cp failing with Read-only file system means reboot holding DOWN. Holding UP at boot is a factory reset (deletes WiFi credentials and settings); hold it for a full second, because it now demands a sustained press. Logic lives in boot.py. Both buttons enable the internal pull-up and debounce: the board has no external pull-ups, so a bare read is undefined — the UP check was doing exactly that until 2026-08, which made every boot a coin flip on wiping the device and its error_log.
  • The library comes from the sibling checkout, rsynced by deploy.sh. Point SCROLLKIT_SRC at your scrollkit package if it isn't at ../ScrollKit Library/src/scrollkit; MPY=1 deploys it compiled (half the flash, worse tracebacks).
  • Watch serial or you're flying blind: screen /dev/tty.usbmodem* 115200, or python tools/dev_serial_capture.py (reconnects across resets).

The config page is http://themeparkwaits.local/ on the device; the simulator serves the same page on http://localhost:8080/.

What do you want to change?

A ride's icon or intro animation (no code required)

The fully-paved path. docs/ride-intro-images.md takes an ASCII-art sketch to an on-device BMP plus its manifest.json line; docs/ride-intro-animations.md adds motion with one registry line. Icons are keyed by ride UUID in src/images/rides/manifest.json, so any image can serve any ride — drawing your own family into the Jungle Cruise boat is a design file, a manifest line, and a spec line. Preview everything with tools/intro_preview.py (see the README).

The screens themselves

src/ui/ride_screen_content.py is the dual-zone ride screen (scrolling name, 2× wait number, intro phases); src/ui/tpw_display.py has the scaled digit drawing; src/ui/reveal_splash.py is the boot reveal. The classic trap: draw_text y is the text baseline, not the top — y=0 clips the glyph off the panel. On a microcontroller the render loop can't allocate: reuse a Label and set .text only on change, move .x to scroll, keep bit_depth=4.

What's in the rotation

src/ui/content_builder.py turns fetched parks into the display queue: sort, group, filter, the vacation countdown, the attribution screen. A new kind of screen — say, an animated interstitial between rides — is a DisplayContent subclass appended here (reveal_splash.py is a good template). There's no data-driven extension point for custom screens yet; it's a code change.

Settings and the config page

A new setting is two edits: its default (and bool_keys entry, if boolean) in src/settings_schema.py, and a form field + handler in src/web/config_server.py. It persists to settings.json on the board.

The data source

src/api/theme_park_service.py fetches and parses the plain-HTTP relay, not themeparks.wiki: trimmed static snapshots at data_base_url (default http://www.themeparkhallofshame.com/device), regenerated every five minutes from a collector that polls the wiki. src/models/ is the domain (parks, rides, vacation).

The relay is not a convenience, it's the fix for the TLS-SRAM exhaustion that used to wedge the fleet — the device now does no routine TLS at all, so keep new fetches on http: and read docs/relay.md before adding a network call. The constraints that shaped the rest: free RAM is scarce, so parks fetch sequentially with a gc.collect() between, drained into one reused 16 KB buffer that is also the device-side size cap on a relay payload; HTTP is synchronous and freezes the display, so always paint a status frame before a blocking call.

Behavior that actually lives in the library

The display abstraction, effects and transitions, WiFi (including the setup portal), HTTP, the settings engine, and the web-server core are all scrollkit. If the defect is there, fix it there — this repo is only the theme-park domain. (Scrollkit still ships an OTA client. This app stopped calling it in 3.5.21; see below.)

Nothing overwrites your mods any more

Signs used to update themselves from this repo's live branch, which meant a modified board could silently eat a stock release. That path was removed in 3.5.21 along with src/ota_glue.py: a full install is 762 KB against a ~400-500 KB TLS budget before the internal-SRAM exhaustion bites, so only a delta at fresh boot could ever have fit, and every 9.x box needed a USB bootstrap regardless. docs/relay.md has the arithmetic; RELEASING.md describes the retired flow and is kept only in case the ESP-IDF leak is fixed upstream.

So your build stays on the board until you replace it. Ship it the same two ways everyone else does: scripts/deploy.sh over USB while you're iterating, or scripts/make_usb_zip.sh for a zip someone else can drop onto CIRCUITPY (no flags by design, and its REQUIRED list is a hard gate, so add to that list when you add a device file). Keep settings.json in place and the board keeps its parks, colors, and WiFi across the swap; delete it and the sign comes back in first-run setup.

Where else to look

  • docs/README.md — which docs are how-to guides and which are engineering investigation ledgers.
  • SCROLLKIT_NOTES.md — the numbered ledger of hard-won platform gotchas (CircuitPython, TLS, RAM, sockets). Read it before fighting the device.
  • specs/ — spec-kit design history for the major features: the why behind the current shape, not a how-to.
  • designs/ — the editable ASCII source for every shipped icon.