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:
154
pico/src/patterns/roll.py
Normal file
154
pico/src/patterns/roll.py
Normal file
@@ -0,0 +1,154 @@
|
||||
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
|
||||
Reference in New Issue
Block a user