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:
124
leds/text.py
Normal file
124
leds/text.py
Normal 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)
|
||||
Reference in New Issue
Block a user