"""Default wiring for one or two WS2812 strips per board.""" from __future__ import annotations from dataclasses import dataclass from typing import Literal from leds.detect import board_family, pio_available, pio_device, pi5_second_spi_bus try: from leds.array_config import MATRIX_SPI_BUS, MATRIX_SPI_DEVICE except ImportError: MATRIX_SPI_BUS = 0 MATRIX_SPI_DEVICE = 0 BackendName = Literal["auto", "pio", "spi", "rpi5_ws2812", "ws281x"] @dataclass class StripConfig: count: int backend: BackendName = "auto" pin: int = 18 channel: int = 0 device: str = "/dev/leds0" spi_bus: int = 0 spi_device: int = 0 spi_max_speed_hz: int | None = None wire_order: str | None = None brightness: float = 1.0 passthrough: bool = False swap_rg: bool = True def panel_strip_config( panel_index: int, count: int, *, spi_max_speed_hz: int | None = None, wire_order: str | None = None, brightness: float = 1.0, passthrough: bool = True, swap_rg: bool = True, ) -> StripConfig: from leds.array_config import LOCAL_PANEL_INDICES, PANEL_SPI_BUS_BY_INDEX if panel_index in PANEL_SPI_BUS_BY_INDEX: spi_bus = PANEL_SPI_BUS_BY_INDEX[panel_index] elif panel_index in LOCAL_PANEL_INDICES: spi_bus = pi5_second_spi_bus() else: spi_bus = MATRIX_SPI_BUS return spi_strip_config( count, spi_bus=spi_bus, spi_device=MATRIX_SPI_DEVICE, spi_max_speed_hz=spi_max_speed_hz, wire_order=wire_order, brightness=brightness, passthrough=passthrough, swap_rg=swap_rg, ) def spi_strip_config( count: int, *, spi_bus: int = MATRIX_SPI_BUS, spi_device: int = MATRIX_SPI_DEVICE, spi_max_speed_hz: int | None = None, wire_order: str | None = None, brightness: float = 1.0, backend: BackendName | None = None, passthrough: bool = False, swap_rg: bool = True, ) -> StripConfig: if backend is None: backend = "rpi5_ws2812" if board_family() == "pi5" else "spi" return StripConfig( count, backend=backend, spi_bus=spi_bus, spi_device=spi_device, spi_max_speed_hz=spi_max_speed_hz, wire_order=wire_order, brightness=brightness, passthrough=passthrough, swap_rg=swap_rg, ) def default_strip_config(count: int, *, brightness: float = 1.0) -> StripConfig: family = board_family() if family == "pi5": if pio_available(): return StripConfig( count, backend="pio", pin=18, device=pio_device(0) or "/dev/leds0", brightness=brightness, ) return spi_strip_config(count, brightness=brightness) return StripConfig( count, backend="ws281x", pin=18, channel=0, brightness=brightness, ) def default_dual_strip_configs( count0: int, count1: int, *, brightness: float = 1.0, ) -> tuple[StripConfig, StripConfig]: """ Suggested wiring for two independent strips: Pi 5 (SPI — requires dtoverlay=spi1-3cs under [all], not [cm5]): strip 0 → GPIO 10 (SPI0 MOSI), /dev/spidev0.0 strip 1 → GPIO 20 (SPI1 MOSI), /dev/spidev1.0 Pi 5 (ws2812-pio — alternative): strip 0 → GPIO 18, /dev/leds0 strip 1 → GPIO 13, /dev/leds1 Pi 4 / Zero: strip 0 → GPIO 18 (PWM channel 0) strip 1 → GPIO 13 (PWM channel 1) """ family = board_family() if family == "pi5": if pio_available(): return ( StripConfig( count0, backend="pio", pin=18, device=pio_device(0) or "/dev/leds0", brightness=brightness, ), StripConfig( count1, backend="pio", pin=13, device=pio_device(1) or "/dev/leds1", brightness=brightness, ), ) return ( StripConfig( count0, backend="spi", spi_bus=0, spi_device=0, brightness=brightness, ), StripConfig( count1, backend="spi", spi_bus=pi5_second_spi_bus(), spi_device=0, brightness=brightness, ), ) return ( StripConfig( count0, backend="ws281x", pin=18, channel=0, brightness=brightness, ), StripConfig( count1, backend="ws281x", pin=13, channel=1, brightness=brightness, ), ) def pi5_setup_hint(count0: int, count1: int) -> str: return ( "Pi 5 second SPI bus not found. Add under [all] in /boot/firmware/config.txt:\n" " dtparam=spi=on\n" " dtoverlay=spi1-3cs\n" "Remove dtparam=spi1=on (ignored on Pi 5). Do not put the overlay under [cm5]\n" "unless you have a Compute Module 5.\n" "Wire strip 0 DIN → GPIO 10, strip 1 DIN → GPIO 20, then reboot." )