A tiny, silent, toy-like handheld game console built from SeedSigner-class hardware: Raspberry Pi Zero 1.3 + Waveshare 1.3" 240x240 LCD HAT (joystick + 3 buttons) + Pi camera. The device boots from RAM in seconds off a read-only microSD card — no writes, no saves, safe to cut power at any moment.
The app is a stripped-down fork of SeedSigner
(controller / views / PIL-based GUI / ST7789 + GPIO hardware layer preserved; all
Bitcoin functionality removed) with a game picker menu and a pillboy/games/ package.
OS images are built by the sibling PillBoy3000-os repo (Buildroot, modeled on seedsigner-os).
The desktop backend is built in — a tkinter window stands in for the LCD and the keyboard for the buttons. No hardware needed:
The venv's python must include tkinter. On macOS, Homebrew's default python3 does
NOT — install brew install python-tk@3.11 and build the venv with python3.11.
Verify with: python3.11 -c "import tkinter" (no output = good).
# --- SETUP VENV
python3.11 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# --- RUN
cd src
python3 main.pyThe only desktop dependency is Pillow.
Keys: WASD = joystick, Space = joystick center press, U/J/M = the three side buttons (top/middle/bottom).
Flash a PillBoy3000-os image (see that repo). For development, flash a -dev image,
connect the Pi Zero's data micro-USB port to your Mac (one cable = power + network),
and use dev.sh — the app auto-detects real hardware (RPi.GPIO present) and drives
the ST7789 over SPI. One-time SSH setup instructions are in the dev.sh header.
./dev.sh sync --restart # push working tree to the device and relaunch the app
./dev.sh logs -f # follow the app log live
./dev.sh screenshot # capture the device screen to ./screenshot.png
./dev.sh shell # SSH in
./dev.sh status # pillboy start|stop|restart|statussrc/main.py entry point (desktop emulator vs hardware autodetect)
src/pillboy/controller.py main loop: View in, Destination out
src/pillboy/views/ navigation logic (game picker, power, errors)
src/pillboy/games/ one module per game + GAMES registry
src/pillboy/gui/ PIL rendering: components, screens, renderer
src/pillboy/hardware/ buttons (GPIO), camera, displays, platform detect
src/pillboy/emulator/ desktop backend (tkinter display + keyboard GPIO)
- Create
src/pillboy/games/mygame.pywith aGameViewsubclass (seegames/base.py) whoserun()owns its game loop — seebounce.pyfor the reference shape. Every button including the three side buttons belongs to gameplay; the one reserved input is all three side buttons pressed together, which opens the shared pause menu (Resume / Quit game). Your loop must:- start with
self.wait_for_release()(debounce the launch press) - call
dest = self.check_pause_menu()once per frame and returndestif set - claim
self.renderer.lockper frame around draw + show, never across frames
- start with
- Give the class a
CONTROLSlist of(hardware_label, keyboard_hint, action)tuples and an optionalrender_thumbnail(draw, x, y, w, h)classmethod — the picker (Play) routes through a per-game title screen (name + graphic + Play / How-to) that renders both automatically; the pause-combo line is appended to How-to for free. - Register it in the
GAMESlist insrc/pillboy/games/__init__.py.