pi-ambilight-receiver — Install Guide#
NixOS on Raspberry Pi 3 (B/B+/A+/Zero 2 W) running a WS2812B LED strip receiver over SPI. Receives Hyperion UDP packets on port 19446.
Prerequisites#
On your build host (x86_64 Linux):
- Nix with flakes enabled (
experimental-features = nix-command flakesin~/.config/nix/nix.conf) - QEMU binfmt registered for aarch64 (check:
ls /proc/sys/fs/binfmt_misc/qemu-aarch64) - Nix knows about aarch64:
/etc/nix/nix.confmust containextra-platforms = aarch64-linux(restartnix-daemonafter adding) - just (
cargo install justornix shell nixpkgs#just) - A microSD card (8 GB minimum, 16+ GB recommended)
Quick Start#
# 1. Build the SD image (~5-15 min first time)
just build-image
# 2. Flash to SD card (find your device with lsblk)
just flash /dev/sdX
# 4. Insert SD card into Pi, connect ethernet, power on
# 5. SSH in (wait ~60s for first boot)
ssh pi@pi # or ssh pi@<IP from your router>
What's on the Image#
| Component | Details |
|---|---|
| OS | NixOS 25.05 (aarch64), minimal profile |
| Network | systemd-networkd, DHCP on all ethernet interfaces |
| SSH | Enabled, key-only auth for pi and root |
| Tailscale | Enabled, auto-joins if /var/lib/tailscale/authkey exists |
| Firewall | UDP 19446 open, tailscale0 trusted |
| Service | pi-ambilight-receiver — starts on boot, restarts on failure |
| SPI | Enabled via device tree overlay, udev rule sets group spi |
| Swap | zRAM swap (safety net for the Pi's limited RAM without burning the SD card) |
Updating the Pi#
To apply system updates or push changes over SSH:
just deploy # build on your machine, push result to Pi
just dry-run # preview changes without applying
Hardware Wiring#
The receiver uses SPI0 MOSI (GPIO 10, physical pin 19) to drive WS2812B LEDs. No level shifter is needed for short runs — the Pi's 3.3V output is within the WS2812B's logic threshold when powered at 5V.
Pi GPIO 10 (pin 19) ──────── DIN on LED strip
Pi GND (pin 6) ──────── GND on LED strip
5V supply ──────── VCC on LED strip
5V supply GND ──────── GND on LED strip (and Pi GND)
Do not power the LED strip from the Pi's 5V pin unless you have fewer than ~10 LEDs. Use a separate 5V supply and share a common ground.
Reinstalling from Scratch#
If the SD card corrupts or you want a clean start:
just build-image
just flash /dev/sdX
The Pi's state (Tailscale identity, any local files) will be lost. Tailscale will re-register with a new node if using an ephemeral key.
Troubleshooting#
Pi doesn't boot / no HDMI output#
- Wrong Pi model? This image targets aarch64 (Pi 3, Pi Zero 2 W, Pi 4, Pi 5). It will not boot on Pi Zero (original), Pi 1, or Pi 2 — those are armv6/armv7.
- SD card not flashed correctly. Re-flash with
just flash. Make sure you're writing to the whole device (/dev/sda) not a partition (/dev/sda1). - SD card too slow or failing. NixOS boot takes longer than Raspbian. Wait at least 2 minutes. Try a different SD card if it never comes up.
Can't SSH in#
- Pi hasn't finished booting yet. First boot takes 60-120s. Check your
router's DHCP leases for hostname
pi. - Wrong SSH key. The image only accepts the ed25519 keys hardcoded in
nixos/configuration.nix. If you regenerated your SSH keys, rebuild the image. - No network. If using Wi-Fi, ensure it was properly configured before building. There's no fallback if it fails to connect.
- Firewall on your network. SSH (port 22) must be reachable. If on a restrictive network, use Tailscale (pre-provision the auth key).
Service crashes on start#
Check logs: ssh pi@pi journalctl -u pi-ambilight-receiver
"Failed to open SPI device"
/dev/spidev0.0doesn't exist. The device tree overlay may not have applied. SSH in and check:ls /dev/spidev*. If missing:- Verify the DTB overlay built correctly:
ls /run/current-system/dtbs/ - Check kernel messages:
dmesg | grep -i spi - Try loading the module manually:
sudo modprobe spidev
- Verify the DTB overlay built correctly:
"Permission denied" on SPI
- The udev rule may not have fired. Check device permissions:
ls -l /dev/spidev0.0— it should becrw-rw---- root spi. - Verify the ambilight user is in the spi group:
id ambilight - Reboot to re-trigger udev rules.
"SPI Write Failed"
- Wiring issue. Check GPIO 10 is connected to the LED strip DIN.
- Power issue. The LED strip needs its own 5V supply.
"Kernel buffer limit hit"
- Too many LEDs for the default spidev buffer. The config sets
spidev.bufsiz=65536which supports ~7000 LEDs. If you somehow exceed this, increase the value innixos/service.nix.
LEDs flicker or show wrong colors#
- Wrong SPI clock speed. The WS2812B driver uses 2.4 MHz to encode 3 SPI
bits per data bit. If the kernel adjusts the clock to a different value,
timing breaks. Check:
cat /sys/class/spi_master/spi0/spi0.0/statistics/ - Long wire run. Signal degrades over distance. Keep the wire between GPIO 10 and the first LED under 1 meter. Use a logic level shifter (3.3V→5V) for longer runs.
- Insufficient power. Each WS2812B LED draws up to 60mA at full white. 150 LEDs = 9A peak. If your supply can't keep up, colors go wrong. Inject power at multiple points on long strips.
Tailscale doesn't connect#
- No auth key provisioned. The file
/var/lib/tailscale/authkeymust exist on the SD card before first boot. - Auth key expired or single-use. Generate a new reusable key at https://login.tailscale.com/admin/settings/keys
- Check status:
ssh pi@pi tailscale status - Check service:
ssh pi@pi systemctl status tailscaled
Out of disk space#
NixOS keeps old system generations. The config runs weekly GC, but you can trigger it manually:
ssh pi@pi sudo nix-collect-garbage -d
OOM / Pi freezes during deploy#
The Pi 3 has 1 GB RAM (Zero 2 W has 512 MB). Building NixOS on-device can
OOM. just deploy will build on your machine and only copy the result
to the Pi. zRAM swap is configured as a safety net.
Need to recover a bricked Pi#
If the Pi won't boot and you need data off the SD card:
# Mount root partition on your build host
sudo mount /dev/sdX2 /mnt
# Copy what you need, then reflash
sudo umount /mnt
just flash /dev/sdX
File Layout#
pi-ambilight-receiver/
├── flake.nix # Build + NixOS system definitions
├── flake.lock # Pinned dependency versions
├── justfile # Task runner recipes
├── Cargo.toml # Rust project
├── Cargo.lock
├── src/
│ ├── main.rs # UDP receiver → SPI writer
│ ├── lib.rs # WS2812B SPI driver
│ └── bin/rainbow.rs # Test pattern binary
└── nixos/
├── configuration.nix # System config (users, network, SSH, etc.)
├── service.nix # systemd service + SPI setup
└── disk-config.nix # Partition layout (for nixos-anywhere path)