A template for shipping your own apps to a Pimoroni Blinky 2350 badge — RP2350, 39 × 26 white-LED matrix, WiFi, five buttons. Plug in the badge, paste one prompt into Claude, get two working apps on the badge in ~90 seconds.
apps/space_invaders/— single-player Space Invaders with a WiFi-backed top-5 leaderboard (a GitHub Gist) scrolling across the home screen as a stock tickerapps/qr_code/— a static 21 × 21 QR code that scans to a configurable URL (default: a short link to the project owner's YouTube channel; regenerate for any URL withtools/make_qr.py)emulator/main.py— pygame desktop emulator that mirrors Space Invaders on your laptop (no hardware needed)tools/deploy.py— finds the badge over USB mass storage, copies all apps in one shot, merges WiFi + leaderboard secretstools/make_qr.py— regenerate the QR app for any URL (auto-fits to the smallest readable size)tools/make_icon.py— regenerate the Space Invaders launcher icontools/scan_wifi.py— runnable via mpremote to list every 2.4 GHz network the badge can seedocs/HARDWARE.md— Blinky 2350 hardware notes + badgeware API surfacedocs/DEMO.md— live demo script for conferences / meetups.claude/skills/blinky-space-invaders/— the instructions Claude follows to do the install + iterate flow (project-local, auto-loaded by Claude Code when youcdinto the repo)
- A Pimoroni Blinky 2350 badge
- A USB-C data cable (not a charge-only cable — phone sync cables work great)
- Python 3.9 or newer on your computer (python.org)
- git (git-scm.com)
- Claude Code installed
- A WiFi network on 2.4 GHz with WPA2 security (see WiFi gotchas below — this catches a lot of people)
Goal: a freshly-unboxed Blinky 2350 → both apps running on it. ~90 seconds, mostly waiting on pip install.
This is the same on Windows, macOS, and Linux:
- Plug the Blinky 2350 into a USB-C port on your computer. The badge powers on and shows whatever app was last running.
- Double-tap the RESET button on the back of the badge. Two quick taps in under a second. The badge screen changes to show
USB / waiting. - A removable drive mounts:
- Windows: appears as
D:(orE:,F:...) in File Explorer - macOS: appears as
/Volumes/PIMORONI(or similar) in Finder - Linux: appears at
/run/media/$USER/...or/media/$USER/...
- Windows: appears as
If the drive doesn't appear: your USB-C cable is probably power-only (very common with cheap cables). Swap it for a known-data cable — a phone sync cable works.
mkdir badger # name it whatever you want — this is your project home
cd badger
claude # starts Claude Code in this directoryInstall the Blinky Badger project onto my Pimoroni Blinky 2350 from
https://github.com/gilfila/VLD. The badge is plugged in and already in mass-storage mode — ask me what drive letter / mount path it appeared as if you can't auto-detect. Clone the repo here, install the Python dependencies, then deploy both apps onto the badge and tell me how to eject and relaunch.
That's the whole install. Claude will:
git clone https://github.com/gilfila/VLD .into your current folderpython -m pip install -r requirements.txt(pygame-ce, qrcode, pillow)- Ask you for the drive letter / mount path (or scan if it can't auto-detect)
- Run
python tools/deploy.py --drive <your-drive>which copies bothspace_invadersandqr_codeonto the badge - Stop and tell you to eject the drive and press RESET on the back of the badge once
Replace
gilfilawith the GitHub owner of your fork if different.
- Eject the drive cleanly. This matters — yanking it dirty can corrupt the badge filesystem.
- Windows: right-click the drive in File Explorer → Eject
- macOS: click the ⏏ eject icon next to the drive name in Finder's sidebar (or drag to Trash)
- Linux:
umount /run/media/$USER/...or click the eject icon in your file manager
- Press the RESET button on the back of the badge once. It reboots into Pimoroni's launcher menu.
- Navigate the launcher with the leftmost (A) and rightmost (C) of the five front buttons until you see either the Space Invaders alien icon or the QR code icon, then press B (middle button) to launch.
Done. Two apps on a 872-LED badge, deployed by talking to Claude in plain English.
Space Invaders ships with a leaderboard that's optional — you can play the game without ever configuring WiFi. To turn it on, you need three things:
- WiFi credentials (SSID + password) for a 2.4 GHz WPA2 network the badge can reach
- A GitHub Gist holding the leaderboard JSON (one file:
leaderboard.json, initial content{"scores": []}) - A GitHub Personal Access Token with the
gistscope, that can write to the gist
Once you have all three, with D: mounted on the badge, deploy them in one shot:
python tools/deploy.py --drive D: \
--ssid "YourNetwork" --password "..." \
--gist-id "abc123..." --leaderboard-token "ghp_..." \
--nickname "YOU"deploy.py merges each flag into /secrets.py on the badge — values you don't pass are preserved. Everything goes badge-local; secrets.py is gitignored, nothing reaches the public repo.
Quick gist setup: go to https://gist.github.com, create a public gist with filename leaderboard.json and content {"scores": []}, copy the long hex ID from the URL. Quick PAT setup: https://github.com/settings/tokens/new → name it, check only gist scope, generate.
Eject, RESET, launch Space Invaders, and the home-screen ticker scrolls the top 5 entries. Get a top-5 score and the badge POSTs it to the gist; the next intro shows your name.
The Blinky's RM2 module (CYW43439) is finicky. The two things that catch people:
- 2.4 GHz only. The chip cannot see 5 GHz networks. If your home network broadcasts both bands under one SSID with band-steering, the badge may still fail. Split the SSIDs in your router (
MyNet-2.4andMyNet-5) or temporarily disable 5 GHz. - WPA3 association is unreliable. The chip officially supports WPA3 but in practice often fails to associate against WPA3-only or WPA3-preferred networks. Use WPA2 or WPA2/WPA3 transition mode, not pure WPA3.
Easiest workaround when your home WiFi fights back: phone hotspot. iOS Personal Hotspot and most Android hotspots default to 2.4 GHz + WPA2 and Just Work:
# After enabling the hotspot on your phone:
python tools/deploy.py --drive D: --ssid "YourHotspotName" --password "..."To verify what your badge can see right now, with the badge plugged in not in MSC mode:
python -m mpremote run tools/scan_wifi.pyThat lists every 2.4 GHz network in range with signal strength + auth mode. If your network shows WPA3 there, the badge won't associate — change to WPA2 in your router admin, or use a hotspot.
When the badge is plugged in (not in MSC mode) it exposes a USB serial REPL. Anything the running app print()s shows up there.
Install mpremote once:
python -m pip install mpremoteUse it to watch the badge:
python -m mpremote connect autoThen press RESET on the badge — boot messages and app print()s stream into your terminal. Press Ctrl+] to exit.
This is how you'll diagnose anything weird: WiFi association issues, leaderboard submit failures, app crashes — they all surface here. The Space Invaders app has diagnostic prints at every fork in the WiFi + leaderboard path ([boot], [pump], [fetch?], [submit?], [lb.fetch], [lb.submit]) so you can pinpoint where things break.
Once installed, iterating on the badge is conversational:
| Step | You | Claude |
|---|---|---|
| 1 | "Make the aliens move faster" / "change the QR to my LinkedIn" / "add a Conway's Life app" | Edits files locally. |
| 2 | "Ready to deploy — put the badge in MSC mode and tell me when it's mounted." | |
| 3 | Double-tap RESET on the back. Drive remounts. | |
| 4 | "D mounted" / "go" / "deploy" | Runs python tools/deploy.py --drive D:. |
| 5 | "Eject the drive and press RESET once on the back to relaunch." | |
| 6 | Test on hardware. If broken → loop to step 1 with what you saw (or paste mpremote output). |
No SD card, no flashing tools, no IDE — just a conversation that ends with code running on an 872-LED badge.
The repo is a template — drop a new folder under apps/<your-app>/ with __init__.py + icon.png and the next deploy ships it alongside the rest. Pimoroni's launcher lists every folder it finds. Some ideas:
- Asteroids (we built and yanked it; check git history)
- Rock-paper-scissors (also in git history; UDP multiplayer for badge-vs-badge)
- Conway's Game of Life — 39 × 26 = 1014 cells fits comfortably
- Pomodoro timer / clock / "do not disturb" sign — Pimoroni already ships a
clockapp in the firmware for reference - Your LinkedIn / portfolio / GitHub QR code —
python tools/make_qr.py --url "..."regenerates the matrix for any URL. Need a free QR generator? The project author built one at qrcodify.onrender.com — paste any URL, get a QR back. - Anything else that fits in 872 LEDs and 5 buttons.
The default QR encodes bit.ly/4wV2DXA (which redirects to the project owner's YouTube). To point it at your own URL — LinkedIn, GitHub, portfolio, whatever — it's a two-step:
python tools/make_qr.py --url "https://your-url-here"
python tools/deploy.py --drive D: --app-name qr_codeURL length matters a lot on the 39 × 26 LED matrix:
| QR version | Modules | Max URL bytes (low EC) | Fits cleanly? |
|---|---|---|---|
| 1 | 21 × 21 | 17 chars | ✅ Yes — sits entirely above the buttons |
| 2 | 25 × 25 | 32 chars | |
| 3 | 29 × 29 | 53 chars | ❌ Won't fit (taller than the 26-row display) |
make_qr.py strips https:// automatically (every modern scanner re-adds it), and auto-fits to the smallest version that holds the data.
Most personal URLs are way too long for 21 × 21 — linkedin.com/in/firstname-lastname-abc1234 is ~45 chars, which forces a version 3 QR that won't fit at all on the badge. Almost always you'll want to shorten the URL first.
- Grab your full URL. LinkedIn:
https://www.linkedin.com/in/your-handle. GitHub:https://github.com/your-username. Personal site:https://yourname.dev. - Shorten it at bit.ly (free, ~14 chars), t.ly (free, ~13 chars), or qrcodify.onrender.com.
- Regenerate the badge QR:
python tools/make_qr.py --url "https://bit.ly/your-short-code". The tool prints the resulting matrix size —21x21is the win,25x25means your URL is still too long. - Redeploy:
python tools/deploy.py --drive D: --app-name qr_code - Eject + RESET on the badge, find the QR icon in the launcher, scan with your phone.
To keep multiple QR apps side-by-side (one for LinkedIn, one for GitHub, etc), copy apps/qr_code/ to a second folder, regenerate its qr_data.py with a different URL, and they'll both appear in the launcher menu.
- Hold the badge 6–12 inches from the camera. Too close and the LEDs blow out the camera's auto-exposure into a featureless blob.
- Use Google Lens or your phone's native Camera app. Both handle inverted (lit-on-dark) QRs since iOS 11 / recent Android. Older / third-party scanner apps often don't.
- Avoid glare. The badge's transparent PCB reflects ambient light — dim the room or tilt the badge.
- Shorter URL → bigger / chunkier modules → easier scan.
python -m pip install -r requirements.txt
python emulator/main.pyControls: A/C move, B fire (also "start" / "continue"), ESC quit.
git clone https://github.com/gilfila/VLD
cd VLD
python -m pip install -r requirements.txt
python tools/deploy.py --list # find the badge drive
python tools/deploy.py --drive D: # deploy every app under apps/Eject, press RESET, navigate the launcher menu.
Everything personal — your WiFi password, your leaderboard PAT, your nickname — lives in /secrets.py on the badge filesystem only. secrets.py is in .gitignore so nothing leaks back to the public repo. To wipe everything: delete secrets.py from the badge drive and the badge forgets the WiFi, stops posting scores. The leaderboard itself lives in a public GitHub Gist (anyone with the URL can read; only the PAT can write).
MIT — see LICENSE.