Files
portal/leds/backends/pio.py
Jimmy 5094c7bcee Add portal web simulator, SPI bridges, and Pico firmware updates.
Bring the five-panel hex portal online with a browser 3D/schematic preview, Pi SPI backends, and renamed multi-panel Pico UDP firmware.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 14:54:51 +12:00

59 lines
1.7 KiB
Python

"""WS2812 via Pi 5 kernel ws2812-pio driver (/dev/ledsN)."""
from __future__ import annotations
from pathlib import Path
class PioBackend:
def __init__(self, led_count: int, *, device: str = "/dev/leds0") -> None:
self._led_count = led_count
self._device = device
self._path = Path(device)
if not self._path.exists():
raise FileNotFoundError(
f"{device} not found — enable ws2812-pio in /boot/firmware/config.txt "
"and reboot. Run: pipenv run python examples/setup_pi5_leds.py"
)
@property
def led_count(self) -> int:
return self._led_count
@property
def device(self) -> str:
return self._device
def write_grb(self, grb_bytes: bytes) -> None:
if len(grb_bytes) != self._led_count * 3:
raise ValueError(
f"expected {self._led_count * 3} bytes, got {len(grb_bytes)}"
)
# Kernel driver expects RGBW uint32 little-endian per pixel (W=0 for RGB).
frame = bytearray(self._led_count * 4)
for i in range(self._led_count):
base = i * 3
r, g, b = grb_bytes[base], grb_bytes[base + 1], grb_bytes[base + 2]
offset = i * 4
frame[offset] = r
frame[offset + 1] = g
frame[offset + 2] = b
frame[offset + 3] = 0
with open(self._path, "wb") as dev:
dev.write(frame)
def clear(self) -> None:
with open(self._path, "wb") as dev:
dev.write(b"\x00" * (self._led_count * 4))
def close(self) -> None:
self.clear()
def __enter__(self) -> PioBackend:
return self
def __exit__(self, *_exc) -> None:
self.close()