Pico panels get static IPs on 10.1.1.10–14 with per-panel LED counts, Makefile deploy targets, and Python examples for animations, sync tests, and direct Pi SPI control. Co-authored-by: Cursor <cursoragent@cursor.com>
65 lines
1.6 KiB
Python
65 lines
1.6 KiB
Python
"""Raspberry Pi model detection for choosing a WS2812 backend."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from functools import lru_cache
|
|
|
|
|
|
@lru_cache(maxsize=1)
|
|
def board_model() -> str:
|
|
try:
|
|
with open("/proc/device-tree/model", "rb") as f:
|
|
return f.read().rstrip(b"\x00").decode("ascii")
|
|
except OSError:
|
|
return "Unknown"
|
|
|
|
|
|
@lru_cache(maxsize=1)
|
|
def board_family() -> str:
|
|
model = board_model().lower()
|
|
if "raspberry pi 5" in model:
|
|
return "pi5"
|
|
if "raspberry pi 4" in model:
|
|
return "pi4"
|
|
if "zero 2" in model:
|
|
return "pi_zero2"
|
|
if "zero" in model:
|
|
return "pi_zero"
|
|
if "raspberry pi 3" in model:
|
|
return "pi3"
|
|
return "unknown"
|
|
|
|
|
|
def supports_ws281x() -> bool:
|
|
return board_family() != "pi5"
|
|
|
|
|
|
def pio_device(index: int = 0) -> str | None:
|
|
from pathlib import Path
|
|
|
|
path = Path(f"/dev/leds{index}")
|
|
return str(path) if path.exists() else None
|
|
|
|
|
|
def pio_available() -> bool:
|
|
return pio_device(0) is not None
|
|
|
|
|
|
def pi5_second_spi_bus() -> int:
|
|
"""
|
|
Second strip on Pi 5 SPI — prefer spi1 from dtoverlay=spi1-3cs
|
|
(/dev/spidev1.0, MOSI on GPIO 20). Falls back to built-in spidev10.0.
|
|
"""
|
|
from pathlib import Path
|
|
|
|
if Path("/dev/spidev1.0").exists():
|
|
return 1
|
|
if Path("/dev/spidev10.0").exists():
|
|
return 10
|
|
raise RuntimeError(
|
|
"No second SPI device found. Add to /boot/firmware/config.txt under [all]:\n"
|
|
" dtoverlay=spi1-3cs\n"
|
|
"Then reboot. Wire strip 1 DIN to GPIO 20 (SPI1 MOSI).\n"
|
|
"Note: dtparam=spi1=on is ignored on Pi 5; use the overlay in [all], not [cm5]."
|
|
)
|