Add multi-panel Pico UDP firmware and Python LED control.

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>
This commit is contained in:
2026-06-28 22:46:08 +12:00
parent d8323bb9a3
commit d5cab2efdf
52 changed files with 4677 additions and 1 deletions

124
leds/text.py Normal file
View File

@@ -0,0 +1,124 @@
"""5×7 bitmap text for LED matrices."""
from __future__ import annotations
from typing import Tuple
from leds.array_config import MATRIX_HEIGHT, MATRIX_WIDTH, matrix_pixel_index
RGB = Tuple[int, int, int]
# Each letter: 7 rows, 5 LSBs = columns (bit 4 = left).
_FONT: dict[str, tuple[int, ...]] = {
" ": (0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
"A": (0x0E, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11),
"B": (0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E),
"C": (0x0E, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0E),
"D": (0x1E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1E),
"E": (0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x1F),
"F": (0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x10),
"G": (0x0E, 0x11, 0x10, 0x17, 0x11, 0x11, 0x0E),
"H": (0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11),
"I": (0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E),
"J": (0x07, 0x02, 0x02, 0x02, 0x02, 0x12, 0x0C),
"K": (0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11),
"L": (0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F),
"M": (0x11, 0x1B, 0x15, 0x11, 0x11, 0x11, 0x11),
"N": (0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x11),
"O": (0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E),
"P": (0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10),
"Q": (0x0E, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0D),
"R": (0x1E, 0x11, 0x11, 0x1E, 0x14, 0x12, 0x11),
"S": (0x0E, 0x11, 0x10, 0x0E, 0x01, 0x11, 0x0E),
"T": (0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04),
"U": (0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E),
"V": (0x11, 0x11, 0x11, 0x11, 0x0A, 0x0A, 0x04),
"W": (0x11, 0x11, 0x11, 0x15, 0x15, 0x1B, 0x11),
"X": (0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11),
"Y": (0x11, 0x11, 0x0A, 0x04, 0x04, 0x04, 0x04),
"Z": (0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F),
"0": (0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E),
"1": (0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E),
"2": (0x0E, 0x11, 0x01, 0x06, 0x08, 0x10, 0x1F),
"3": (0x1F, 0x02, 0x04, 0x06, 0x01, 0x11, 0x0E),
"4": (0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02),
"5": (0x1F, 0x10, 0x1E, 0x01, 0x01, 0x11, 0x0E),
"6": (0x06, 0x08, 0x10, 0x1E, 0x11, 0x11, 0x0E),
"7": (0x1F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08),
"8": (0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E),
"9": (0x0E, 0x11, 0x11, 0x0F, 0x01, 0x02, 0x0C),
}
CHAR_WIDTH = 5
CHAR_HEIGHT = 7
CHAR_SPACING = 1
def text_width(text: str, spacing: int = CHAR_SPACING) -> int:
text = text.upper()
if not text:
return 0
return len(text) * CHAR_WIDTH + (len(text) - 1) * spacing
def _set(surface, x: int, y: int, color: RGB) -> None:
if hasattr(surface, "width") and hasattr(surface, "height"):
if 0 <= x < surface.width and 0 <= y < surface.height:
surface[x, y] = color
else:
w = getattr(surface, "width", MATRIX_WIDTH)
idx = matrix_pixel_index(x, y, w)
if 0 <= idx < len(surface):
surface[idx] = color
def draw_char(
surface,
ch: str,
x: int,
y: int,
color: RGB,
*,
scale: int = 1,
) -> None:
glyph = _FONT.get(ch.upper(), _FONT[" "])
for row, bits in enumerate(glyph):
for col in range(CHAR_WIDTH):
if bits & (1 << (CHAR_WIDTH - 1 - col)):
for sy in range(scale):
for sx in range(scale):
_set(surface, x + col * scale + sx, y + row * scale + sy, color)
def draw_text(
surface,
text: str,
x: int,
y: int,
color: RGB,
*,
scale: int = 1,
spacing: int = CHAR_SPACING,
) -> None:
cursor = x
step = (CHAR_WIDTH + spacing) * scale
for ch in text.upper():
draw_char(surface, ch, cursor, y, color, scale=scale)
cursor += step
def draw_text_centered(
surface,
text: str,
color: RGB,
*,
scale: int = 1,
spacing: int = CHAR_SPACING,
) -> None:
w = getattr(surface, "width", MATRIX_WIDTH)
h = getattr(surface, "height", MATRIX_HEIGHT)
tw = text_width(text, spacing) * scale
th = CHAR_HEIGHT * scale
x = max(0, (w - tw) // 2)
y = max(0, (h - th) // 2)
draw_text(surface, text, x, y, color, scale=scale, spacing=spacing)