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

81
pico/test/roll_strips.py Normal file
View File

@@ -0,0 +1,81 @@
import math
import sys
if "lib" not in sys.path:
sys.path.insert(0, "lib")
if "../lib" not in sys.path:
sys.path.insert(0, "../lib")
from ws2812 import WS2812B
import time
# --- Roll: N buffers (length = max strip), gradient full -> off; sequence through strips ---
N_BUFFERS = 32 # more buffers = smoother transition
STRIP_CONFIG = (
(2, 291),
(3, 290),
(4, 283),
(7, 278),
(0, 275),
(28, 278),
(29, 283),
(6, 290),
)
strips = []
sm = 0
for pin, num_leds in STRIP_CONFIG:
print(pin, num_leds)
ws = WS2812B(num_leds, pin, sm, brightness=1.0)
strips.append(ws)
sm += 1
num_strips = len(strips)
max_leds = max(ws.num_leds for ws in strips)
# Color when "on" (R, G, B); GRB order in buffer
ROLL_COLOR = (0, 255, 120) # cyan-green
def make_gradient_buffers(n_buffers, max_leds, color):
"""Create n_buffers buffers, each max_leds long. Buffer 0 = full brightness, last = off.
Gradient is logarithmic (perceptually smoother: more steps near full, fewer near off). GRB order."""
out = []
for j in range(n_buffers):
# log gradient: scale = 255 * log(1 + (n - 1 - j)) / log(n) so 255 at j=0, 0 at j=n-1
if n_buffers <= 1:
scale = 255
elif j >= n_buffers - 1:
scale = 0
else:
# 1 + (n_buffers - 1 - j) runs from n_buffers down to 1
scale = int(255 * math.log(1 + (n_buffers - 1 - j)) / math.log(n_buffers))
scale = min(255, scale)
buf = bytearray(max_leds * 3)
r = (color[0] * scale) // 255
g = (color[1] * scale) // 255
b = (color[2] * scale) // 255
for i in range(max_leds):
o = i * 3
buf[o] = g & 0xFF
buf[o + 1] = r & 0xFF
buf[o + 2] = b & 0xFF
out.append(buf)
return out
# N buffers: first full, last off, gradient between
buffers = make_gradient_buffers(N_BUFFERS, max_leds, ROLL_COLOR)
step = 0
delay_ms = 50
# Deadline-based loop: no extra pause at rotation wrap, smooth continuous roll
next_ms = time.ticks_ms()
while True:
for i, strip in enumerate(strips):
buf_index = (step + i) % N_BUFFERS
strip.show(buffers[buf_index], 0)
step += 1 # unbounded; wrap only in index so no hitch at cycle end
next_ms += delay_ms
# Sleep until next frame time (handles drift, no pause at wrap)
while time.ticks_diff(next_ms, time.ticks_ms()) > 0:
time.sleep_ms(1)