Compare commits
2 Commits
5f457b3ae7
...
292c5bde01
| Author | SHA1 | Date | |
|---|---|---|---|
| 292c5bde01 | |||
| a0687cff57 |
@@ -1,123 +1,165 @@
|
|||||||
import utime
|
import utime
|
||||||
|
|
||||||
|
|
||||||
|
def _make_chase_double(num_leds, cumulative_leds, total_ring_leds, color0, color1, n1, n2):
|
||||||
|
"""Pregenerate strip double buffer with repeating segments:
|
||||||
|
color0 for n1 pixels, then color1 for n2 pixels, around the full ring. GRB order."""
|
||||||
|
n = 2 * num_leds
|
||||||
|
buf = bytearray(n * 3)
|
||||||
|
pattern_len = n1 + n2
|
||||||
|
for b in range(n):
|
||||||
|
# Position of this pixel along the logical ring
|
||||||
|
pos = (2 * cumulative_leds - b) % total_ring_leds
|
||||||
|
seg_pos = pos % pattern_len
|
||||||
|
if seg_pos < n1:
|
||||||
|
r, g, b_ = color0
|
||||||
|
else:
|
||||||
|
r, g, b_ = color1
|
||||||
|
o = b * 3
|
||||||
|
buf[o] = g
|
||||||
|
buf[o + 1] = r
|
||||||
|
buf[o + 2] = b_
|
||||||
|
strip_len_bytes = num_leds * 3
|
||||||
|
return buf, strip_len_bytes
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_chase_buffers(driver, color0, color1, n1, n2, cache):
|
||||||
|
"""Build or refresh per-strip double buffers for the chase pattern."""
|
||||||
|
strips = driver.strips
|
||||||
|
key = (
|
||||||
|
color0,
|
||||||
|
color1,
|
||||||
|
int(n1),
|
||||||
|
int(n2),
|
||||||
|
tuple(s.num_leds for s in strips),
|
||||||
|
)
|
||||||
|
if cache.get("key") == key and cache.get("data") is not None:
|
||||||
|
return cache["data"], cache["cumulative_leds"], cache["total_ring_leds"]
|
||||||
|
|
||||||
|
if not strips:
|
||||||
|
cache["key"] = key
|
||||||
|
cache["data"] = []
|
||||||
|
cache["cumulative_leds"] = []
|
||||||
|
cache["total_ring_leds"] = 0
|
||||||
|
return cache["data"], cache["cumulative_leds"], cache["total_ring_leds"]
|
||||||
|
|
||||||
|
cumulative_leds = [0]
|
||||||
|
for s in strips[:-1]:
|
||||||
|
cumulative_leds.append(cumulative_leds[-1] + s.num_leds)
|
||||||
|
total_ring_leds = cumulative_leds[-1] + strips[-1].num_leds
|
||||||
|
|
||||||
|
chase_data = []
|
||||||
|
for idx, s in enumerate(strips):
|
||||||
|
buf, strip_len_bytes = _make_chase_double(
|
||||||
|
s.num_leds,
|
||||||
|
cumulative_leds[idx],
|
||||||
|
total_ring_leds,
|
||||||
|
color0,
|
||||||
|
color1,
|
||||||
|
n1,
|
||||||
|
n2,
|
||||||
|
)
|
||||||
|
chase_data.append((buf, strip_len_bytes))
|
||||||
|
|
||||||
|
cache["key"] = key
|
||||||
|
cache["data"] = chase_data
|
||||||
|
cache["cumulative_leds"] = cumulative_leds
|
||||||
|
cache["total_ring_leds"] = total_ring_leds
|
||||||
|
return chase_data, cumulative_leds, total_ring_leds
|
||||||
|
|
||||||
|
|
||||||
class Chase:
|
class Chase:
|
||||||
def __init__(self, driver):
|
def __init__(self, driver):
|
||||||
self.driver = driver
|
self.driver = driver
|
||||||
|
self._buffers_cache = {}
|
||||||
|
|
||||||
def run(self, preset):
|
def run(self, preset):
|
||||||
"""Chase pattern: n1 LEDs of color0, n2 LEDs of color1, repeating.
|
"""Chase pattern: n1 LEDs of color0, n2 LEDs of color1, repeating around the full ring.
|
||||||
Moves by n3 on even steps, n4 on odd steps (n3/n4 can be positive or negative)"""
|
Moves by n3 on even steps, n4 on odd steps (n3/n4 can be positive or negative)."""
|
||||||
colors = preset.c
|
colors = preset.c or []
|
||||||
if len(colors) < 1:
|
|
||||||
# Need at least 1 color
|
|
||||||
return
|
|
||||||
|
|
||||||
# Access colors, delay, and n values from preset
|
|
||||||
if not colors:
|
if not colors:
|
||||||
return
|
return
|
||||||
|
|
||||||
# If only one color provided, use it for both colors
|
# If only one color provided, use it for both colors
|
||||||
if len(colors) < 2:
|
if len(colors) < 2:
|
||||||
color0 = colors[0]
|
base0 = colors[0]
|
||||||
color1 = colors[0]
|
base1 = colors[0]
|
||||||
else:
|
else:
|
||||||
color0 = colors[0]
|
base0 = colors[0]
|
||||||
color1 = colors[1]
|
base1 = colors[1]
|
||||||
|
|
||||||
color0 = self.driver.apply_brightness(color0, preset.b)
|
# Apply preset/global brightness
|
||||||
color1 = self.driver.apply_brightness(color1, preset.b)
|
color0 = self.driver.apply_brightness(base0, preset.b)
|
||||||
|
color1 = self.driver.apply_brightness(base1, preset.b)
|
||||||
|
|
||||||
n1 = max(1, int(preset.n1)) # LEDs of color 0
|
n1 = max(1, int(getattr(preset, "n1", 1)) or 1) # LEDs of color 0
|
||||||
n2 = max(1, int(preset.n2)) # LEDs of color 1
|
n2 = max(1, int(getattr(preset, "n2", 1)) or 1) # LEDs of color 1
|
||||||
n3 = int(preset.n3) # Step movement on even steps (can be negative)
|
n3 = int(getattr(preset, "n3", 0) or 0) # step on even steps
|
||||||
n4 = int(preset.n4) # Step movement on odd steps (can be negative)
|
n4 = int(getattr(preset, "n4", 0) or 0) # step on odd steps
|
||||||
|
|
||||||
segment_length = n1 + n2
|
if n3 == 0 and n4 == 0:
|
||||||
|
# Nothing to move; default to simple forward motion
|
||||||
|
n3 = 1
|
||||||
|
n4 = 1
|
||||||
|
|
||||||
# Calculate position from step_count
|
chase_data, cumulative_leds, total_ring_leds = _ensure_chase_buffers(
|
||||||
step_count = int(self.driver.step)
|
self.driver, color0, color1, n1, n2, self._buffers_cache
|
||||||
# Position alternates: step 0 adds n3, step 1 adds n4, step 2 adds n3, etc.
|
)
|
||||||
if step_count % 2 == 0:
|
|
||||||
# Even steps: (step_count//2) pairs of (n3+n4) plus one extra n3
|
|
||||||
position = (step_count // 2) * (n3 + n4) + n3
|
|
||||||
else:
|
|
||||||
# Odd steps: ((step_count+1)//2) pairs of (n3+n4)
|
|
||||||
position = ((step_count + 1) // 2) * (n3 + n4)
|
|
||||||
|
|
||||||
# Wrap position to keep it reasonable
|
strips = self.driver.strips
|
||||||
max_pos = self.driver.num_leds + segment_length
|
|
||||||
position = position % max_pos
|
|
||||||
if position < 0:
|
|
||||||
position += max_pos
|
|
||||||
|
|
||||||
# If auto is False, run a single step and then stop
|
def show_frame(chase_pos):
|
||||||
|
for i, (strip, (buf, strip_len_bytes)) in enumerate(zip(strips, chase_data)):
|
||||||
|
# head in [0, strip_len_bytes) so DMA read head..head+strip_len_bytes stays in double buffer
|
||||||
|
head = ((chase_pos + cumulative_leds[i]) * 3) % strip_len_bytes
|
||||||
|
strip.show(buf, head)
|
||||||
|
|
||||||
|
# Helper to compute head position from current step_count
|
||||||
|
def head_from_step(step_count):
|
||||||
|
if step_count % 2 == 0:
|
||||||
|
# Even steps: (step_count//2) pairs of (n3+n4) plus one extra n3
|
||||||
|
pos = (step_count // 2) * (n3 + n4) + n3
|
||||||
|
else:
|
||||||
|
# Odd steps: ((step_count+1)//2) pairs of (n3+n4)
|
||||||
|
pos = ((step_count + 1) // 2) * (n3 + n4)
|
||||||
|
if total_ring_leds <= 0:
|
||||||
|
return 0
|
||||||
|
pos %= total_ring_leds
|
||||||
|
if pos < 0:
|
||||||
|
pos += total_ring_leds
|
||||||
|
return pos
|
||||||
|
|
||||||
|
# Single-step mode: render one frame, then stop
|
||||||
if not preset.a:
|
if not preset.a:
|
||||||
# Draw repeating pattern starting at position across all physical strips
|
step_count = int(self.driver.step)
|
||||||
num_leds = self.driver.num_leds
|
chase_pos = head_from_step(step_count)
|
||||||
num_strips = len(self.driver.strips)
|
|
||||||
for i in range(num_leds):
|
|
||||||
# Calculate position in the repeating segment
|
|
||||||
relative_pos = (i - position) % segment_length
|
|
||||||
if relative_pos < 0:
|
|
||||||
relative_pos = (relative_pos + segment_length) % segment_length
|
|
||||||
|
|
||||||
# Determine which color based on position in segment
|
show_frame(chase_pos)
|
||||||
color = color0 if relative_pos < n1 else color1
|
|
||||||
|
|
||||||
# Apply this logical LED to every physical strip via driver.set()
|
# Advance step for next trigger
|
||||||
for strip_idx in range(num_strips):
|
|
||||||
self.driver.set(strip_idx, i, color)
|
|
||||||
|
|
||||||
self.driver.show_all()
|
|
||||||
|
|
||||||
# Increment step for next beat
|
|
||||||
self.driver.step = step_count + 1
|
self.driver.step = step_count + 1
|
||||||
|
|
||||||
# Allow tick() to advance the generator once
|
|
||||||
yield
|
yield
|
||||||
return
|
return
|
||||||
|
|
||||||
# Auto mode: continuous loop
|
# Auto mode: continuous loop driven by delay d
|
||||||
# Use transition_duration for timing and force the first update to happen immediately
|
transition_duration = max(10, int(getattr(preset, "d", 50)) or 10)
|
||||||
transition_duration = max(10, int(preset.d))
|
|
||||||
last_update = utime.ticks_ms() - transition_duration
|
last_update = utime.ticks_ms() - transition_duration
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
current_time = utime.ticks_ms()
|
current_time = utime.ticks_ms()
|
||||||
if utime.ticks_diff(current_time, last_update) >= transition_duration:
|
if utime.ticks_diff(current_time, last_update) >= transition_duration:
|
||||||
# Calculate current position from step_count
|
# Rebuild buffers if geometry/colors changed
|
||||||
if step_count % 2 == 0:
|
chase_data, cumulative_leds, total_ring_leds = _ensure_chase_buffers(
|
||||||
position = (step_count // 2) * (n3 + n4) + n3
|
self.driver, color0, color1, n1, n2, self._buffers_cache
|
||||||
else:
|
)
|
||||||
position = ((step_count + 1) // 2) * (n3 + n4)
|
|
||||||
|
|
||||||
# Wrap position
|
step_count = int(self.driver.step)
|
||||||
max_pos = self.driver.num_leds + segment_length
|
chase_pos = head_from_step(step_count)
|
||||||
position = position % max_pos
|
|
||||||
if position < 0:
|
|
||||||
position += max_pos
|
|
||||||
|
|
||||||
# Draw repeating pattern starting at position across all physical strips
|
show_frame(chase_pos)
|
||||||
num_leds = self.driver.num_leds
|
|
||||||
num_strips = len(self.driver.strips)
|
|
||||||
for i in range(num_leds):
|
|
||||||
# Calculate position in the repeating segment
|
|
||||||
relative_pos = (i - position) % segment_length
|
|
||||||
if relative_pos < 0:
|
|
||||||
relative_pos = (relative_pos + segment_length) % segment_length
|
|
||||||
|
|
||||||
# Determine which color based on position in segment
|
# Advance step for next frame
|
||||||
color = color0 if relative_pos < n1 else color1
|
self.driver.step = step_count + 1
|
||||||
|
|
||||||
# Apply this logical LED to every physical strip via driver.set()
|
|
||||||
for strip_idx in range(num_strips):
|
|
||||||
self.driver.set(strip_idx, i, color)
|
|
||||||
|
|
||||||
self.driver.show_all()
|
|
||||||
|
|
||||||
# Increment step
|
|
||||||
step_count += 1
|
|
||||||
self.driver.step = step_count
|
|
||||||
last_update = current_time
|
last_update = current_time
|
||||||
|
|
||||||
# Yield once per tick so other logic can run
|
# Yield once per tick so other logic can run
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import utime
|
import utime
|
||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
class Roll:
|
class Roll:
|
||||||
@@ -6,149 +7,105 @@ class Roll:
|
|||||||
self.driver = driver
|
self.driver = driver
|
||||||
|
|
||||||
def run(self, preset):
|
def run(self, preset):
|
||||||
"""Roll: moving band with gradient from color1 to color2 over the strips.
|
"""Roll: all strips show a shared color gradient palette, cycling out of phase.
|
||||||
|
|
||||||
- n1: offset from start of strip (effective start = start + n1)
|
- All strips participate; each frame shows N discrete colors
|
||||||
- n2: offset from end of strip (effective end = end - n2, inclusive)
|
(one per strip), from color1 to color2.
|
||||||
- n3: number of full rotations before stopping (0 = infinite)
|
- Over time, each strip cycles through all colors, out of phase with the
|
||||||
|
others, creating a smooth rolling band around the hoop.
|
||||||
- n4: direction (0 = clockwise, 1 = anti-clockwise)
|
- n4: direction (0 = clockwise, 1 = anti-clockwise)
|
||||||
- c[0]: color1 at the head strip
|
- c[0]: head color (full intensity)
|
||||||
- c[1]: color2 at the tail strip
|
- c[1]: tail color (usually darker or off)
|
||||||
"""
|
"""
|
||||||
colors = preset.c
|
colors = preset.c
|
||||||
color1_raw = colors[0] if colors else (255, 255, 255)
|
base1 = colors[0] if colors else (255, 255, 255)
|
||||||
color2_raw = colors[1] if len(colors) > 1 else (0, 0, 0)
|
base2 = colors[1] if len(colors) > 1 else (0, 0, 0)
|
||||||
color1 = self.driver.apply_brightness(color1_raw, preset.b)
|
color1 = self.driver.apply_brightness(base1, preset.b)
|
||||||
color2 = self.driver.apply_brightness(color2_raw, preset.b)
|
color2 = self.driver.apply_brightness(base2, preset.b)
|
||||||
|
|
||||||
n_segments = self.driver.n.num_strips if hasattr(self.driver.n, "num_strips") else 1
|
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
|
# n3: number of full rotations before stopping (0 = continuous)
|
||||||
start_margin = max(0, int(getattr(preset, "n1", 0)))
|
max_rotations = int(getattr(preset, "n3", 0) or 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
|
# n4: direction (0=cw, 1=ccw); default clockwise if missing
|
||||||
clockwise = int(getattr(preset, "n4", 0)) == 0
|
clockwise = int(getattr(preset, "n4", 0)) == 0
|
||||||
|
|
||||||
step = self.driver.step
|
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):
|
# Precompute one shared buffer per brightness level (one per strip),
|
||||||
return tuple(int(x * f) for x in c)
|
# using the longest strip length so any strip can DMA from it safely.
|
||||||
|
strips_list = self.driver.strips
|
||||||
|
if not strips_list or n_segments <= 0:
|
||||||
|
while True:
|
||||||
|
yield
|
||||||
|
|
||||||
def lerp_color(c1, c2, t):
|
max_leds = max(s.num_leds for s in strips_list)
|
||||||
"""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):
|
# Build N discrete color buffers forming a gradient from color1 to color2.
|
||||||
# Remember head strip for flare
|
buffers = []
|
||||||
|
for j in range(n_segments):
|
||||||
|
if n_segments > 1:
|
||||||
|
t = j / (n_segments - 1)
|
||||||
|
else:
|
||||||
|
t = 0.0
|
||||||
|
# Linear interpolation between color1 and color2
|
||||||
|
r = int(color1[0] + (color2[0] - color1[0]) * t)
|
||||||
|
g = int(color1[1] + (color2[1] - color1[1]) * t)
|
||||||
|
b = int(color1[2] + (color2[2] - color1[2]) * t)
|
||||||
|
|
||||||
|
buf = bytearray(max_leds * 3)
|
||||||
|
for i in range(max_leds):
|
||||||
|
o = i * 3
|
||||||
|
buf[o] = g & 0xFF
|
||||||
|
buf[o + 1] = r & 0xFF
|
||||||
|
buf[o + 2] = b & 0xFF
|
||||||
|
buffers.append(buf)
|
||||||
|
|
||||||
|
def draw(step_index):
|
||||||
|
# Each strip picks a buffer index offset by its strip index so that:
|
||||||
|
# - all brightness levels are visible simultaneously (one per strip)
|
||||||
|
# - over time, each strip cycles through all brightness levels
|
||||||
try:
|
try:
|
||||||
self.driver.last_roll_head = head
|
self.driver.last_roll_head = step_index % n_segments
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
strips_list = self.driver.strips
|
|
||||||
|
|
||||||
for strip_idx, strip in enumerate(strips_list):
|
for strip_idx, strip in enumerate(strips_list):
|
||||||
if strip_idx < 0 or strip_idx >= n_segments:
|
if strip_idx < 0 or strip_idx >= n_segments:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Distance from head along direction, 0..n_segments-1
|
|
||||||
if clockwise:
|
if clockwise:
|
||||||
dist = (head - strip_idx) % n_segments
|
buf_index = (step_index + strip_idx) % n_segments
|
||||||
else:
|
else:
|
||||||
dist = (strip_idx - head) % n_segments
|
buf_index = (step_index - strip_idx) % n_segments
|
||||||
|
buf = buffers[buf_index]
|
||||||
|
|
||||||
# Color gradient from color1 at the head strip to color2 at the tail strip
|
# Show the shared buffer; WS2812B will read num_leds*3 bytes.
|
||||||
if n_segments > 1:
|
strip.show(buf, 0)
|
||||||
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:
|
if not preset.a:
|
||||||
head = step % n_segments if n_segments > 0 else 0
|
draw(step)
|
||||||
draw(head)
|
|
||||||
self.driver.step = step + 1
|
self.driver.step = step + 1
|
||||||
yield
|
yield
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Auto mode: advance based on preset.d (ms) for smooth, controllable speed
|
||||||
|
delay_ms = max(10, int(getattr(preset, "d", 60)) or 10)
|
||||||
|
last_update = utime.ticks_ms() - delay_ms
|
||||||
|
rotations_done = 0
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
current_time = utime.ticks_ms()
|
now = utime.ticks_ms()
|
||||||
if utime.ticks_diff(current_time, last_update) >= delay_ms:
|
if utime.ticks_diff(now, last_update) >= delay_ms:
|
||||||
head = step % n_segments if n_segments > 0 else 0
|
draw(step)
|
||||||
if not clockwise and n_segments > 0:
|
|
||||||
head = (n_segments - 1 - head)
|
|
||||||
|
|
||||||
draw(head)
|
|
||||||
step += 1
|
step += 1
|
||||||
|
self.driver.step = step
|
||||||
|
last_update = now
|
||||||
|
# Count full rotations if requested: one rotation per n_segments steps
|
||||||
if max_rotations > 0 and n_segments > 0 and (step % n_segments) == 0:
|
if max_rotations > 0 and n_segments > 0 and (step % n_segments) == 0:
|
||||||
rotations_done += 1
|
rotations_done += 1
|
||||||
if rotations_done >= max_rotations:
|
if rotations_done >= max_rotations:
|
||||||
self.driver.step = step
|
# Hold the final frame and stop advancing; keep yielding so
|
||||||
last_update = current_time
|
# the generator stays alive without changing the LEDs.
|
||||||
return
|
while True:
|
||||||
|
yield
|
||||||
self.driver.step = step
|
|
||||||
last_update = current_time
|
|
||||||
yield
|
yield
|
||||||
|
|||||||
@@ -33,27 +33,28 @@ for ws in strips[:-1]:
|
|||||||
cumulative_leds.append(cumulative_leds[-1] + ws.num_leds)
|
cumulative_leds.append(cumulative_leds[-1] + ws.num_leds)
|
||||||
total_ring_leds = cumulative_leds[-1] + strips[-1].num_leds
|
total_ring_leds = cumulative_leds[-1] + strips[-1].num_leds
|
||||||
|
|
||||||
# Chase: trail length (0 = single LED), color (R,G,B)
|
# Chase: color1 n1 long, then color2 n2 long, stepping n3 pixels
|
||||||
TRAIL_LEN = 8
|
COLOR1 = (255, 0, 0) # red
|
||||||
CHASE_COLOR = (0, 255, 100) # cyan-green
|
COLOR2 = (0, 0, 255) # blue
|
||||||
|
N1 = 24 # length of color1 segment
|
||||||
|
N2 = 24 # length of color2 segment
|
||||||
|
STEP = 1 # step size in pixels per frame
|
||||||
|
|
||||||
|
|
||||||
def make_chase_double(num_leds, cumulative_leds, total_ring_leds, color, trail_len=0):
|
def make_chase_double(num_leds, cumulative_leds, total_ring_leds, color1, color2, n1, n2):
|
||||||
"""Pregenerate strip double buffer: when head shows index b first, that pixel is at
|
"""Pregenerate strip double buffer with repeating segments:
|
||||||
distance (2*cumulative_leds - b) % total_ring_leds from chase head. GRB order."""
|
color1 for n1 pixels, then color2 for n2 pixels, around the full ring. GRB order."""
|
||||||
n = 2 * num_leds
|
n = 2 * num_leds
|
||||||
buf = bytearray(n * 3)
|
buf = bytearray(n * 3)
|
||||||
|
pattern_len = n1 + n2
|
||||||
for b in range(n):
|
for b in range(n):
|
||||||
dist = (2 * cumulative_leds - b) % total_ring_leds
|
# Position of this pixel along the logical ring
|
||||||
if dist == 0:
|
pos = (2 * cumulative_leds - b) % total_ring_leds
|
||||||
r, grn, b_ = color[0], color[1], color[2]
|
seg_pos = pos % pattern_len
|
||||||
elif trail_len and 0 < dist <= trail_len:
|
if seg_pos < n1:
|
||||||
fade = 1.0 - (dist / (trail_len + 1))
|
r, grn, b_ = color1[0], color1[1], color1[2]
|
||||||
r = int(color[0] * fade)
|
|
||||||
grn = int(color[1] * fade)
|
|
||||||
b_ = int(color[2] * fade)
|
|
||||||
else:
|
else:
|
||||||
r = grn = b_ = 0
|
r, grn, b_ = color2[0], color2[1], color2[2]
|
||||||
o = b * 3
|
o = b * 3
|
||||||
buf[o] = grn
|
buf[o] = grn
|
||||||
buf[o + 1] = r
|
buf[o + 1] = r
|
||||||
@@ -63,7 +64,7 @@ def make_chase_double(num_leds, cumulative_leds, total_ring_leds, color, trail_l
|
|||||||
|
|
||||||
# Pregenerate one double buffer per strip
|
# Pregenerate one double buffer per strip
|
||||||
chase_buffers = [
|
chase_buffers = [
|
||||||
make_chase_double(ws.num_leds, cumulative_leds[i], total_ring_leds, CHASE_COLOR, TRAIL_LEN)
|
make_chase_double(ws.num_leds, cumulative_leds[i], total_ring_leds, COLOR1, COLOR2, N1, N2)
|
||||||
for i, ws in enumerate(strips)
|
for i, ws in enumerate(strips)
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -74,5 +75,5 @@ while True:
|
|||||||
strip_len = strip.num_leds * 3
|
strip_len = strip.num_leds * 3
|
||||||
head = (chase_pos + cumulative_leds[i]) * 3 % strip_len
|
head = (chase_pos + cumulative_leds[i]) * 3 % strip_len
|
||||||
strip.show(chase_buffers[i], head)
|
strip.show(chase_buffers[i], head)
|
||||||
chase_pos = (chase_pos + 1) % total_ring_leds
|
chase_pos = (chase_pos + STEP) % total_ring_leds
|
||||||
time.sleep_ms(20)
|
time.sleep_ms(40)
|
||||||
|
|||||||
114
pico/test/roll.py
Normal file
114
pico/test/roll.py
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
"""
|
||||||
|
On-device visual test for the Roll pattern via Presets.
|
||||||
|
|
||||||
|
This exercises src/patterns/roll.py (gradient from color1 to color2 across strips),
|
||||||
|
not the low-level WS2812 driver.
|
||||||
|
|
||||||
|
Usage (from pico/ dir or project root with adjusted paths):
|
||||||
|
|
||||||
|
mpremote connect <device> cp src/*.py :
|
||||||
|
mpremote connect <device> cp src/patterns/*.py :patterns
|
||||||
|
mpremote connect <device> cp lib/*.py :
|
||||||
|
mpremote connect <device> cp test/roll.py :
|
||||||
|
mpremote connect <device> run roll.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import utime
|
||||||
|
|
||||||
|
from presets import Presets, Preset
|
||||||
|
|
||||||
|
|
||||||
|
def make_roll_preset(name, color1, color2, delay_ms=60, brightness=255, direction=0):
|
||||||
|
"""
|
||||||
|
Helper to build a Preset for the 'roll' pattern.
|
||||||
|
|
||||||
|
- color1: head color (full intensity)
|
||||||
|
- color2: tail color (end of gradient)
|
||||||
|
- direction: 0 = clockwise, 1 = anti-clockwise
|
||||||
|
"""
|
||||||
|
data = {
|
||||||
|
"p": "roll",
|
||||||
|
"c": [color1, color2],
|
||||||
|
"b": brightness,
|
||||||
|
"d": delay_ms,
|
||||||
|
"n4": direction,
|
||||||
|
"a": True, # animated
|
||||||
|
}
|
||||||
|
return name, Preset(data)
|
||||||
|
|
||||||
|
|
||||||
|
def run_for(presets, duration_ms):
|
||||||
|
"""Tick the current pattern for duration_ms."""
|
||||||
|
start = utime.ticks_ms()
|
||||||
|
while utime.ticks_diff(utime.ticks_ms(), start) < duration_ms:
|
||||||
|
presets.tick()
|
||||||
|
utime.sleep_ms(10)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
presets = Presets()
|
||||||
|
presets.load()
|
||||||
|
|
||||||
|
num_leds = presets.strip_length(0)
|
||||||
|
if num_leds <= 0:
|
||||||
|
print("No strips; aborting roll test.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("Starting roll pattern gradient tests via Presets...")
|
||||||
|
|
||||||
|
# A few different roll presets to compare:
|
||||||
|
roll_presets = []
|
||||||
|
|
||||||
|
# 1. White → off, clockwise (50% brightness, faster)
|
||||||
|
roll_presets.append(
|
||||||
|
make_roll_preset(
|
||||||
|
"roll_white_off_cw",
|
||||||
|
color1=(255, 255, 255),
|
||||||
|
color2=(0, 0, 0),
|
||||||
|
delay_ms=120,
|
||||||
|
brightness=128,
|
||||||
|
direction=0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2. Warm white → cool blue, clockwise (50% brightness, faster)
|
||||||
|
roll_presets.append(
|
||||||
|
make_roll_preset(
|
||||||
|
"roll_warm_cool_cw",
|
||||||
|
color1=(255, 200, 100),
|
||||||
|
color2=(0, 0, 255),
|
||||||
|
delay_ms=130,
|
||||||
|
brightness=128,
|
||||||
|
direction=0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 3. Red → green, counter-clockwise (50% brightness, faster)
|
||||||
|
roll_presets.append(
|
||||||
|
make_roll_preset(
|
||||||
|
"roll_red_green_ccw",
|
||||||
|
color1=(255, 0, 0),
|
||||||
|
color2=(0, 255, 0),
|
||||||
|
delay_ms=110,
|
||||||
|
brightness=128,
|
||||||
|
direction=1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Register presets and run them one after another
|
||||||
|
for name, preset_obj in roll_presets:
|
||||||
|
presets.presets[name] = preset_obj
|
||||||
|
|
||||||
|
for name, _preset in roll_presets:
|
||||||
|
print("Running roll preset:", name)
|
||||||
|
presets.select(name)
|
||||||
|
run_for(presets, duration_ms=8000)
|
||||||
|
|
||||||
|
print("Roll pattern Presets test finished. Turning off LEDs.")
|
||||||
|
presets.select("off")
|
||||||
|
presets.tick()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
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)
|
|
||||||
6
pico/test/test_all_on.py
Normal file
6
pico/test/test_all_on.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from neopixel import NeoPixel
|
||||||
|
from machine import Pin
|
||||||
|
|
||||||
|
p = NeoPixel(Pin(6), 291)
|
||||||
|
p.fill((255, 255, 255))
|
||||||
|
p.write()
|
||||||
71
pico/test/test_all_on_presets.py
Normal file
71
pico/test/test_all_on_presets.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
"""
|
||||||
|
On-device test that turns all LEDs on via Presets and verifies strip 0 (pin 6).
|
||||||
|
|
||||||
|
Usage (from pico/ dir or project root with adjusted paths):
|
||||||
|
|
||||||
|
mpremote connect <device> cp src/*.py :
|
||||||
|
mpremote connect <device> cp src/patterns/*.py :patterns
|
||||||
|
mpremote connect <device> cp lib/*.py :
|
||||||
|
mpremote connect <device> cp test/test_all_on_presets.py :
|
||||||
|
mpremote connect <device> run test_all_on_presets.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
from presets import Presets, Preset
|
||||||
|
|
||||||
|
|
||||||
|
def verify_strip0_on(presets, expected_color):
|
||||||
|
"""Check that every LED on strip 0 matches expected_color."""
|
||||||
|
if not presets.strips:
|
||||||
|
print("No strips; skipping strip-0 on test.")
|
||||||
|
return
|
||||||
|
|
||||||
|
strip = presets.strips[0]
|
||||||
|
r_exp, g_exp, b_exp = expected_color
|
||||||
|
|
||||||
|
for i in range(strip.num_leds):
|
||||||
|
o = i * 3
|
||||||
|
g = strip.ar[o]
|
||||||
|
r = strip.ar[o + 1]
|
||||||
|
b = strip.ar[o + 2]
|
||||||
|
if (r, g, b) != (r_exp, g_exp, b_exp):
|
||||||
|
raise AssertionError(
|
||||||
|
"Strip 0 LED %d: got (%d,%d,%d), expected (%d,%d,%d)"
|
||||||
|
% (i, r, g, b, r_exp, g_exp, b_exp)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
presets = Presets()
|
||||||
|
|
||||||
|
if not presets.strips:
|
||||||
|
print("No strips; skipping all-on-presets test.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Full-brightness white via the built-in 'on' pattern.
|
||||||
|
base_color = (255, 255, 255)
|
||||||
|
brightness = 255
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"p": "on",
|
||||||
|
"c": [base_color],
|
||||||
|
"b": brightness,
|
||||||
|
}
|
||||||
|
|
||||||
|
name = "test_all_on_presets"
|
||||||
|
preset = Preset(data)
|
||||||
|
presets.presets[name] = preset
|
||||||
|
|
||||||
|
presets.select(name)
|
||||||
|
presets.tick()
|
||||||
|
|
||||||
|
# Compute the color actually written by the pattern after brightness scaling.
|
||||||
|
expected_color = presets.apply_brightness(base_color, brightness)
|
||||||
|
|
||||||
|
verify_strip0_on(presets, expected_color)
|
||||||
|
|
||||||
|
print("test_all_on_presets: OK")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
184
pico/test/test_chase_via_presets.py
Normal file
184
pico/test/test_chase_via_presets.py
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
"""
|
||||||
|
On-device test that exercises the Chase pattern via Presets.
|
||||||
|
|
||||||
|
Usage (from pico/ dir or project root with adjusted paths):
|
||||||
|
|
||||||
|
mpremote connect <device> cp src/*.py :
|
||||||
|
mpremote connect <device> cp src/patterns/*.py :patterns
|
||||||
|
mpremote connect <device> cp lib/*.py :
|
||||||
|
mpremote connect <device> cp test/test_chase_via_presets.py :
|
||||||
|
mpremote connect <device> run test_chase_via_presets.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import utime
|
||||||
|
|
||||||
|
from presets import Presets, Preset
|
||||||
|
|
||||||
|
|
||||||
|
def snapshot_strip_colors(presets, strip_idx=0, max_leds=32):
|
||||||
|
"""Return a list of (r,g,b) tuples for the first max_leds of the given strip."""
|
||||||
|
strip = presets.strips[strip_idx]
|
||||||
|
num = min(strip.num_leds, max_leds)
|
||||||
|
out = []
|
||||||
|
for i in range(num):
|
||||||
|
o = i * 3
|
||||||
|
g = strip.ar[o]
|
||||||
|
r = strip.ar[o + 1]
|
||||||
|
b = strip.ar[o + 2]
|
||||||
|
out.append((r, g, b))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def expected_chase_color(i, num_leds, step_count, color0, color1, n1, n2, n3, n4):
|
||||||
|
"""Mirror the position logic from patterns/chase.py for a single logical LED."""
|
||||||
|
segment_length = n1 + n2
|
||||||
|
|
||||||
|
if step_count % 2 == 0:
|
||||||
|
position = (step_count // 2) * (n3 + n4) + n3
|
||||||
|
else:
|
||||||
|
position = ((step_count + 1) // 2) * (n3 + n4)
|
||||||
|
|
||||||
|
max_pos = num_leds + segment_length
|
||||||
|
position = position % max_pos
|
||||||
|
if position < 0:
|
||||||
|
position += max_pos
|
||||||
|
|
||||||
|
relative_pos = (i - position) % segment_length
|
||||||
|
if relative_pos < 0:
|
||||||
|
relative_pos = (relative_pos + segment_length) % segment_length
|
||||||
|
|
||||||
|
return color0 if relative_pos < n1 else color1
|
||||||
|
|
||||||
|
|
||||||
|
def test_chase_single_step_via_presets():
|
||||||
|
presets = Presets()
|
||||||
|
|
||||||
|
num_leds = presets.num_leds
|
||||||
|
if num_leds <= 0:
|
||||||
|
print("No strips; skipping chase test.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Simple alternating colors with known lengths.
|
||||||
|
base_color0 = (10, 0, 0)
|
||||||
|
base_color1 = (0, 0, 20)
|
||||||
|
|
||||||
|
# Use full brightness so apply_brightness is identity.
|
||||||
|
brightness = 255
|
||||||
|
|
||||||
|
n1 = 2
|
||||||
|
n2 = 3
|
||||||
|
# Same step size on even/odd for easier reasoning.
|
||||||
|
n3 = 1
|
||||||
|
n4 = 1
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"p": "chase",
|
||||||
|
"c": [base_color0, base_color1],
|
||||||
|
"b": brightness,
|
||||||
|
"d": 0,
|
||||||
|
"a": False, # single-step mode
|
||||||
|
"n1": n1,
|
||||||
|
"n2": n2,
|
||||||
|
"n3": n3,
|
||||||
|
"n4": n4,
|
||||||
|
}
|
||||||
|
|
||||||
|
name = "test_chase_pattern"
|
||||||
|
preset = Preset(data)
|
||||||
|
presets.presets[name] = preset
|
||||||
|
|
||||||
|
# Select and run one tick; this should render exactly one chase frame for step 0.
|
||||||
|
presets.select(name, step=0)
|
||||||
|
presets.tick()
|
||||||
|
|
||||||
|
# Colors after brightness scaling (driver.apply_brightness is used in the pattern).
|
||||||
|
color0 = presets.apply_brightness(base_color0, brightness)
|
||||||
|
color1 = presets.apply_brightness(base_color1, brightness)
|
||||||
|
|
||||||
|
# Snapshot first few LEDs of strip 0 and compare against expected pattern for step 0.
|
||||||
|
colors = snapshot_strip_colors(presets, strip_idx=0, max_leds=16)
|
||||||
|
step_count = 0
|
||||||
|
|
||||||
|
for i, actual in enumerate(colors):
|
||||||
|
expected = expected_chase_color(
|
||||||
|
i, num_leds, step_count, color0, color1, n1, n2, n3, n4
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
actual == expected
|
||||||
|
), "LED %d: got %r, expected %r" % (i, actual, expected)
|
||||||
|
|
||||||
|
print("test_chase_single_step_via_presets: OK")
|
||||||
|
|
||||||
|
|
||||||
|
def test_chase_multiple_steps_via_presets():
|
||||||
|
"""Render several steps and verify pattern advances correctly."""
|
||||||
|
presets = Presets()
|
||||||
|
|
||||||
|
num_leds = presets.num_leds
|
||||||
|
if num_leds <= 0:
|
||||||
|
print("No strips; skipping chase multi-step test.")
|
||||||
|
return
|
||||||
|
|
||||||
|
base_color0 = (10, 0, 0)
|
||||||
|
base_color1 = (0, 0, 20)
|
||||||
|
brightness = 255
|
||||||
|
|
||||||
|
n1 = 2
|
||||||
|
n2 = 3
|
||||||
|
n3 = 1
|
||||||
|
n4 = 1
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"p": "chase",
|
||||||
|
"c": [base_color0, base_color1],
|
||||||
|
"b": brightness,
|
||||||
|
"d": 0,
|
||||||
|
"a": False,
|
||||||
|
"n1": n1,
|
||||||
|
"n2": n2,
|
||||||
|
"n3": n3,
|
||||||
|
"n4": n4,
|
||||||
|
}
|
||||||
|
|
||||||
|
name = "test_chase_pattern_multi"
|
||||||
|
preset = Preset(data)
|
||||||
|
presets.presets[name] = preset
|
||||||
|
|
||||||
|
color0 = presets.apply_brightness(base_color0, brightness)
|
||||||
|
color1 = presets.apply_brightness(base_color1, brightness)
|
||||||
|
|
||||||
|
# In non-auto mode (a=False), the Chase pattern advances one step per
|
||||||
|
# invocation of the generator, and Presets is expected to call select()
|
||||||
|
# again for each beat. Emulate that here by re-selecting with an
|
||||||
|
# explicit step value for each frame we want to test.
|
||||||
|
for step_count in range(4):
|
||||||
|
presets.select(name, step=step_count)
|
||||||
|
presets.tick()
|
||||||
|
colors = snapshot_strip_colors(presets, strip_idx=0, max_leds=16)
|
||||||
|
|
||||||
|
for i, actual in enumerate(colors):
|
||||||
|
expected = expected_chase_color(
|
||||||
|
i, num_leds, step_count, color0, color1, n1, n2, n3, n4
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
actual == expected
|
||||||
|
), "step %d, LED %d: got %r, expected %r" % (
|
||||||
|
step_count,
|
||||||
|
i,
|
||||||
|
actual,
|
||||||
|
expected,
|
||||||
|
)
|
||||||
|
|
||||||
|
print("test_chase_multiple_steps_via_presets: OK")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
test_chase_single_step_via_presets()
|
||||||
|
test_chase_multiple_steps_via_presets()
|
||||||
|
# Give a brief pause so message is visible if run interactively.
|
||||||
|
utime.sleep_ms(100)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
"""
|
|
||||||
On-device test for the Roll pattern using mpremote.
|
|
||||||
|
|
||||||
Usage (from pico/ dir or project root with adjusted paths):
|
|
||||||
|
|
||||||
mpremote connect <device> cp src/*.py :
|
|
||||||
mpremote connect <device> cp src/patterns/*.py :patterns
|
|
||||||
mpremote connect <device> cp lib/*.py :
|
|
||||||
mpremote connect <device> cp test/test_roll.py :
|
|
||||||
mpremote connect <device> run test_roll.py
|
|
||||||
|
|
||||||
This script:
|
|
||||||
- Instantiates Presets
|
|
||||||
- Creates a few in-memory roll presets with different parameters
|
|
||||||
- Runs each one for a short time so you can visually compare behaviour
|
|
||||||
"""
|
|
||||||
|
|
||||||
import utime
|
|
||||||
from presets import Presets, Preset
|
|
||||||
|
|
||||||
|
|
||||||
def make_roll_preset(name, color1, color2, n1=0, n2=0, n3=0, n4=0, delay_ms=50, brightness=255):
|
|
||||||
"""Helper to build a Preset dict for the roll pattern."""
|
|
||||||
data = {
|
|
||||||
"p": "roll",
|
|
||||||
"c": [color1, color2],
|
|
||||||
"b": brightness,
|
|
||||||
"d": delay_ms,
|
|
||||||
"n1": n1,
|
|
||||||
"n2": n2,
|
|
||||||
"n3": n3,
|
|
||||||
"n4": n4,
|
|
||||||
"a": True, # animated
|
|
||||||
}
|
|
||||||
return name, Preset(data)
|
|
||||||
|
|
||||||
|
|
||||||
def run_preset(presets, name, preset_obj, duration_ms):
|
|
||||||
"""Run a given roll preset for duration_ms using the existing tick loop."""
|
|
||||||
presets.presets[name] = preset_obj
|
|
||||||
presets.select(name)
|
|
||||||
|
|
||||||
start = utime.ticks_ms()
|
|
||||||
while utime.ticks_diff(utime.ticks_ms(), start) < duration_ms:
|
|
||||||
presets.tick()
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
presets = Presets()
|
|
||||||
presets.load()
|
|
||||||
|
|
||||||
print("Starting roll pattern test...")
|
|
||||||
|
|
||||||
ref_len = presets.strip_length(0)
|
|
||||||
# Use some margins based on strip length to show different bands.
|
|
||||||
quarter = ref_len // 4
|
|
||||||
|
|
||||||
# Define a few different roll presets:
|
|
||||||
roll_presets = []
|
|
||||||
|
|
||||||
# 1. Full-strip, white -> off, clockwise, medium speed
|
|
||||||
roll_presets.append(
|
|
||||||
make_roll_preset(
|
|
||||||
"roll_full_cw",
|
|
||||||
color1=(255, 255, 255),
|
|
||||||
color2=(0, 0, 0),
|
|
||||||
n1=0,
|
|
||||||
n2=0,
|
|
||||||
n3=2, # 2 rotations then stop
|
|
||||||
n4=0, # clockwise
|
|
||||||
delay_ms=40,
|
|
||||||
brightness=255,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# 2. Inner band only, red -> blue, clockwise, slower
|
|
||||||
roll_presets.append(
|
|
||||||
make_roll_preset(
|
|
||||||
"roll_inner_band",
|
|
||||||
color1=(255, 0, 0),
|
|
||||||
color2=(0, 0, 255),
|
|
||||||
n1=quarter, # start margin
|
|
||||||
n2=quarter, # end margin
|
|
||||||
n3=2,
|
|
||||||
n4=0,
|
|
||||||
delay_ms=60,
|
|
||||||
brightness=255,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# 3. Full-strip, green -> off, counter-clockwise, faster
|
|
||||||
roll_presets.append(
|
|
||||||
make_roll_preset(
|
|
||||||
"roll_full_ccw",
|
|
||||||
color1=(0, 255, 0),
|
|
||||||
color2=(0, 0, 0),
|
|
||||||
n1=0,
|
|
||||||
n2=0,
|
|
||||||
n3=2,
|
|
||||||
n4=1, # counter-clockwise
|
|
||||||
delay_ms=30,
|
|
||||||
brightness=255,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Run each roll preset for about 5 seconds
|
|
||||||
for name, preset_obj in roll_presets:
|
|
||||||
print("Running roll preset:", name)
|
|
||||||
run_preset(presets, name, preset_obj, duration_ms=5000)
|
|
||||||
|
|
||||||
print("Roll pattern test finished. Turning off LEDs.")
|
|
||||||
presets.select("off")
|
|
||||||
presets.tick()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user