Add Pico presets engine, patterns, and tests.

Wire the Pico to UART-driven preset selection, add pattern modules and presets data, remove old p2p/settings code, and update tests and LED driver.

Made-with: Cursor
This commit is contained in:
2026-03-03 19:28:11 +13:00
parent 646b988cdd
commit 52a5f0f8c4
44 changed files with 2175 additions and 373 deletions

98
pico/src/patterns/spin.py Normal file
View File

@@ -0,0 +1,98 @@
"""Spin: continues from Grab — segment (10 each side of center) moves slowly up to the top. Preset color, n1 = rate."""
import utime
SPAN = 10 # LEDs on each side of center (match Grab)
LUT_SIZE = 256 # gradient lookup table entries
class Spin:
def __init__(self, driver):
self.driver = driver
def run(self, preset):
strips = self.driver.strips
active_indices = (0, 4)
c0 = preset.c[0]
c1 = preset.c[1]
# Precompute gradient LUT: t in [0,1] maps to (r,g,b)
lut = []
for k in range(LUT_SIZE):
t = k / (LUT_SIZE - 1) if LUT_SIZE > 1 else 1
r = int(c0[0] + (c1[0] - c0[0]) * t)
g = int(c0[1] + (c1[1] - c0[1]) * t)
b = int(c0[2] + (c1[2] - c0[2]) * t)
lut.append((r, g, b))
# For each active strip we expand from just outside the grab center
# left: from (mid - SPAN) down to 0
# right: from (mid + SPAN) up to end
midpoints = self.driver.strip_midpoints
rate = max(1, int(preset.n1) or 1)
delay_ms = max(1, int(preset.d) or 1)
margin = max(0, int(preset.n2) or 0)
# Track current extents of each arm
left = {}
right = {}
for idx in active_indices:
if 0 <= idx < len(strips):
mid = midpoints[idx]
left[idx] = mid - SPAN # inner edge of left arm
right[idx] = mid + SPAN + 1 # inner edge of right arm
last_update = utime.ticks_ms()
while True:
now = utime.ticks_ms()
if utime.ticks_diff(now, last_update) < delay_ms:
yield
continue
last_update = now
for idx in active_indices:
if idx < 0 or idx >= len(strips):
continue
strip = strips[idx]
n = strip.num_leds
mid = midpoints[idx]
# Expand arms: inside (strip 1, idx 0) moves slower, outside (strip 5, idx 4) faster
step = max(1, rate // 2) if idx == 0 else rate
new_left = max(margin, left[idx] - step)
new_right = min(n - margin, right[idx] + step)
# Left arm: c1 at outer, c0 at inner. Right arm: c0 at inner, c1 at outer.
left_len = max(0, (mid - SPAN) - new_left)
right_len = max(0, new_right - (mid + SPAN + 1))
bright = strip.brightness
ar = strip.ar
for j, i in enumerate(range(new_left, mid - SPAN)):
if 0 <= i < n:
t = 1 - j / (left_len - 1) if left_len > 1 else 0
lut_idx = min(int(t * (LUT_SIZE - 1)), LUT_SIZE - 1)
r, g, b = lut[lut_idx]
base = i * 3
ar[base] = int(g * bright)
ar[base + 1] = int(r * bright)
ar[base + 2] = int(b * bright)
for j, i in enumerate(range(mid + SPAN + 1, new_right)):
if 0 <= i < n:
t = j / (right_len - 1) if right_len > 1 else 0
lut_idx = min(int(t * (LUT_SIZE - 1)), LUT_SIZE - 1)
r, g, b = lut[lut_idx]
base = i * 3
ar[base] = int(g * bright)
ar[base + 1] = int(r * bright)
ar[base + 2] = int(b * bright)
left[idx] = new_left
right[idx] = new_right
# Show only on this strip
strip.show()
yield