Files
led-hoop/pico/src/patterns/roll.py
Jimmy 52a5f0f8c4 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
2026-03-03 19:28:11 +13:00

155 lines
5.5 KiB
Python

import utime
class Roll:
def __init__(self, driver):
self.driver = driver
def run(self, preset):
"""Roll: moving band with gradient from color1 to color2 over the strips.
- n1: offset from start of strip (effective start = start + n1)
- n2: offset from end of strip (effective end = end - n2, inclusive)
- n3: number of full rotations before stopping (0 = infinite)
- n4: direction (0 = clockwise, 1 = anti-clockwise)
- c[0]: color1 at the head strip
- c[1]: color2 at the tail strip
"""
colors = preset.c
color1_raw = colors[0] if colors else (255, 255, 255)
color2_raw = colors[1] if len(colors) > 1 else (0, 0, 0)
color1 = self.driver.apply_brightness(color1_raw, preset.b)
color2 = self.driver.apply_brightness(color2_raw, preset.b)
n_segments = self.driver.n.num_strips if hasattr(self.driver.n, "num_strips") else 1
# Margins from the start and end of each strip
start_margin = max(0, int(getattr(preset, "n1", 0)))
end_margin = max(0, int(getattr(preset, "n2", 0)))
# Debug info to see why roll might be black
try:
print(
"ROLL preset",
"p=", getattr(preset, "p", None),
"b=", getattr(preset, "b", None),
"colors_raw=", color1_raw, color2_raw,
"colors_bright=", color1, color2,
)
print(
"ROLL n1..n4",
getattr(preset, "n1", None),
getattr(preset, "n2", None),
getattr(preset, "n3", None),
getattr(preset, "n4", None),
"n_segments=", n_segments,
"start_margin=", start_margin,
"end_margin=", end_margin,
)
except Exception:
pass
# n3: number of rotations (0 = infinite)
max_rotations = int(getattr(preset, "n3", 0)) or 0
# n4: direction (0=cw, 1=ccw); default clockwise if missing
clockwise = int(getattr(preset, "n4", 0)) == 0
step = self.driver.step
delay_ms = max(1, int(preset.d) or 1)
last_update = utime.ticks_ms()
rotations_done = 0
def scale_color(c, f):
return tuple(int(x * f) for x in c)
def lerp_color(c1, c2, t):
"""Linear gradient between two colors."""
if t <= 0:
return c1
if t >= 1:
return c2
return (
int(c1[0] + (c2[0] - c1[0]) * t),
int(c1[1] + (c2[1] - c1[1]) * t),
int(c1[2] + (c2[2] - c1[2]) * t),
)
def draw(head):
# Remember head strip for flare
try:
self.driver.last_roll_head = head
except AttributeError:
pass
strips_list = self.driver.strips
for strip_idx, strip in enumerate(strips_list):
if strip_idx < 0 or strip_idx >= n_segments:
continue
# Distance from head along direction, 0..n_segments-1
if clockwise:
dist = (head - strip_idx) % n_segments
else:
dist = (strip_idx - head) % n_segments
# Color gradient from color1 at the head strip to color2 at the tail strip
if n_segments > 1:
t = dist / (n_segments - 1)
else:
t = 0.0
c_strip = lerp_color(color1, color2, t)
n = strip.num_leds
# Effective segment per strip:
# start = 0 + start_margin
# end = (n - 1) - end_margin (inclusive)
width = n - start_margin - end_margin
if width <= 0:
# If margins are too large, fall back to full strip
seg_s = 0
seg_e = n
else:
seg_s = max(0, min(n, start_margin))
seg_e = min(n, n - end_margin)
# Debug for first strip/head to see segment
try:
if strip_idx == 0 and head == 0:
print("ROLL seg strip0 n=", n, "seg_s=", seg_s, "seg_e=", seg_e)
except Exception:
pass
for i in range(n):
if seg_s <= i < seg_e:
strip.set(i, c_strip)
else:
strip.set(i, (0, 0, 0))
strip.show()
if not preset.a:
head = step % n_segments if n_segments > 0 else 0
draw(head)
self.driver.step = step + 1
yield
return
while True:
current_time = utime.ticks_ms()
if utime.ticks_diff(current_time, last_update) >= delay_ms:
head = step % n_segments if n_segments > 0 else 0
if not clockwise and n_segments > 0:
head = (n_segments - 1 - head)
draw(head)
step += 1
if max_rotations > 0 and n_segments > 0 and (step % n_segments) == 0:
rotations_done += 1
if rotations_done >= max_rotations:
self.driver.step = step
last_update = current_time
return
self.driver.step = step
last_update = current_time
yield