Skip to content

Repository files navigation

Blinky Badger

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.

What ships out of the box

  • 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 ticker
  • apps/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 with tools/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 secrets
  • tools/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 icon
  • tools/scan_wifi.py — runnable via mpremote to list every 2.4 GHz network the badge can see
  • docs/HARDWARE.md — Blinky 2350 hardware notes + badgeware API surface
  • docs/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 you cd into the repo)

What you need before starting

  • 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)

Install to Blinky using Claude

Goal: a freshly-unboxed Blinky 2350 → both apps running on it. ~90 seconds, mostly waiting on pip install.

Step 1 — connect the badge to your computer

This is the same on Windows, macOS, and Linux:

  1. Plug the Blinky 2350 into a USB-C port on your computer. The badge powers on and shows whatever app was last running.
  2. 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.
  3. A removable drive mounts:
    • Windows: appears as D: (or E:, F:...) in File Explorer
    • macOS: appears as /Volumes/PIMORONI (or similar) in Finder
    • Linux: appears at /run/media/$USER/... or /media/$USER/...

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.

Step 2 — start Claude in an empty folder

mkdir badger                    # name it whatever you want — this is your project home
cd badger
claude                          # starts Claude Code in this directory

Step 3 — paste this prompt into Claude

Install 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:

  1. git clone https://github.com/gilfila/VLD . into your current folder
  2. python -m pip install -r requirements.txt (pygame-ce, qrcode, pillow)
  3. Ask you for the drive letter / mount path (or scan if it can't auto-detect)
  4. Run python tools/deploy.py --drive <your-drive> which copies both space_invaders and qr_code onto the badge
  5. Stop and tell you to eject the drive and press RESET on the back of the badge once

Replace gilfila with the GitHub owner of your fork if different.

Step 4 — eject and relaunch

  1. 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
  2. Press the RESET button on the back of the badge once. It reboots into Pimoroni's launcher menu.
  3. 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.

Setting up the WiFi leaderboard

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:

  1. WiFi credentials (SSID + password) for a 2.4 GHz WPA2 network the badge can reach
  2. A GitHub Gist holding the leaderboard JSON (one file: leaderboard.json, initial content {"scores": []})
  3. A GitHub Personal Access Token with the gist scope, 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.

WiFi gotchas

The Blinky's RM2 module (CYW43439) is finicky. The two things that catch people:

  1. 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.4 and MyNet-5) or temporarily disable 5 GHz.
  2. 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.py

That 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.

Debugging — watching the badge over USB serial

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 mpremote

Use it to watch the badge:

python -m mpremote connect auto

Then 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.

The dev loop

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.

What you can build with this

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 clock app in the firmware for reference
  • Your LinkedIn / portfolio / GitHub QR codepython 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.

Customizing the QR code

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_code

URL 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 ⚠️ Bottom 3 rows overlap the button area
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.

Swapping in your LinkedIn / GitHub / portfolio URL

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.

  1. Grab your full URL. LinkedIn: https://www.linkedin.com/in/your-handle. GitHub: https://github.com/your-username. Personal site: https://yourname.dev.
  2. Shorten it at bit.ly (free, ~14 chars), t.ly (free, ~13 chars), or qrcodify.onrender.com.
  3. Regenerate the badge QR: python tools/make_qr.py --url "https://bit.ly/your-short-code". The tool prints the resulting matrix size — 21x21 is the win, 25x25 means your URL is still too long.
  4. Redeploy: python tools/deploy.py --drive D: --app-name qr_code
  5. 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.

Tips if the QR doesn't scan

  1. 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.
  2. 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.
  3. Avoid glare. The badge's transparent PCB reflects ambient light — dim the room or tilt the badge.
  4. Shorter URL → bigger / chunkier modules → easier scan.

Quick start — desktop emulator (no hardware needed)

python -m pip install -r requirements.txt
python emulator/main.py

Controls: A/C move, B fire (also "start" / "continue"), ESC quit.

Manual deploy (skipping Claude)

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.

Privacy

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).

License

MIT — see LICENSE.

About

No description, website, or topics provided.

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages