"""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 g, r, 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()