Add segments and double_circle patterns with shared presets
Introduce double_circle and segments-based patterns on the Pico, refactor the Presets engine to expose a logical ring over all strips, and migrate presets/test code from the old point pattern to segments while switching to a top-level presets.json. Made-with: Cursor
This commit is contained in:
@@ -4,6 +4,7 @@ from .pulse import Pulse
|
||||
from .transition import Transition
|
||||
from .chase import Chase
|
||||
from .circle import Circle
|
||||
from .double_circle import DoubleCircle
|
||||
from .roll import Roll
|
||||
from .calibration import Calibration
|
||||
from .test import Test
|
||||
@@ -14,4 +15,5 @@ from .lift import Lift
|
||||
from .flare import Flare
|
||||
from .hook import Hook
|
||||
from .pose import Pose
|
||||
from .point import Point
|
||||
from .segments import Segments
|
||||
from .segments_transition import SegmentsTransition
|
||||
|
||||
84
pico/src/patterns/double_circle.py
Normal file
84
pico/src/patterns/double_circle.py
Normal file
@@ -0,0 +1,84 @@
|
||||
class DoubleCircle:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
"""
|
||||
DoubleCircle: symmetric band around a center index on the logical ring.
|
||||
|
||||
- n1: center index on the logical ring (0-based, on reference strip 0)
|
||||
- n2: radius of the band (max distance from center)
|
||||
- n3: direction mode
|
||||
0 → LEDs start ALL OFF and turn ON n4 LEDs at a time outward from n1 toward n1±n2
|
||||
1 → LEDs start ALL ON within radius n2 and turn OFF n4 LEDs at a time inward toward n1
|
||||
- n4: step size in LEDs per update
|
||||
- c[0]: base color used for the band
|
||||
"""
|
||||
num_leds = self.driver.num_leds
|
||||
if num_leds <= 0:
|
||||
while True:
|
||||
yield
|
||||
|
||||
colors = preset.c or []
|
||||
base1 = colors[0] if len(colors) >= 1 else (255, 255, 255)
|
||||
off = (0, 0, 0)
|
||||
|
||||
# Apply preset/global brightness
|
||||
color_on = self.driver.apply_brightness(base1, preset.b)
|
||||
color_off = off
|
||||
|
||||
# Center index and radius from preset; clamp center to ring length
|
||||
center = int(getattr(preset, "n1", 0)) % num_leds
|
||||
radius = max(1, int(getattr(preset, "n2", 0)) or 1)
|
||||
mode = int(getattr(preset, "n3", 0) or 0) # 0 = grow band outward, 1 = shrink band inward
|
||||
step_size = max(1, int(getattr(preset, "n4", 1)) or 1)
|
||||
|
||||
num_strips = len(self.driver.strips)
|
||||
|
||||
# Current "front" of the band, as a distance from center
|
||||
# mode 0: grow band outward (0 → radius)
|
||||
# mode 1: shrink band inward (radius → 0)
|
||||
if mode == 0:
|
||||
current = 0
|
||||
else:
|
||||
current = radius
|
||||
|
||||
while True:
|
||||
# Draw current frame based on current radius
|
||||
for i in range(num_leds):
|
||||
# Shortest circular distance from i to center
|
||||
forward = (i - center) % num_leds
|
||||
backward = (center - i) % num_leds
|
||||
dist = forward if forward < backward else backward
|
||||
|
||||
if dist > radius:
|
||||
c = color_off
|
||||
else:
|
||||
if mode == 0:
|
||||
# Grow outward: lit if within current radius
|
||||
c = color_on if dist <= current else color_off
|
||||
else:
|
||||
# Shrink inward: lit if within current radius (band contracts toward center)
|
||||
c = color_on if dist <= current else color_off
|
||||
|
||||
for strip_idx in range(num_strips):
|
||||
self.driver.set(strip_idx, i, c)
|
||||
|
||||
self.driver.show_all()
|
||||
|
||||
# Update current radius for next frame
|
||||
if mode == 0:
|
||||
if current >= radius:
|
||||
# Finished growing; hold final frame
|
||||
while True:
|
||||
yield
|
||||
current = min(radius, current + step_size)
|
||||
else:
|
||||
if current <= 0:
|
||||
# Finished shrinking; hold final frame
|
||||
while True:
|
||||
yield
|
||||
current = max(0, current - step_size)
|
||||
|
||||
yield
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
class Point:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
# Apply preset/global brightness once per color
|
||||
c1 = self.driver.apply_brightness(preset.c[0], preset.b)
|
||||
c2 = self.driver.apply_brightness(preset.c[1], preset.b)
|
||||
c3 = self.driver.apply_brightness(preset.c[2], preset.b)
|
||||
c4 = self.driver.apply_brightness(preset.c[3], preset.b)
|
||||
|
||||
# Helper to normalize and clamp a range
|
||||
self.driver.fill_n(c1, preset.n1, preset.n2)
|
||||
self.driver.fill_n(c2, preset.n3, preset.n4)
|
||||
self.driver.fill_n(c3, preset.n5, preset.n6)
|
||||
self.driver.fill_n(c4, preset.n7, preset.n8)
|
||||
self.driver.show_all()
|
||||
|
||||
18
pico/src/patterns/segments.py
Normal file
18
pico/src/patterns/segments.py
Normal file
@@ -0,0 +1,18 @@
|
||||
class Segments:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
# Apply preset/global brightness once per color
|
||||
ranges = [
|
||||
(preset.n1, preset.n2),
|
||||
(preset.n3, preset.n4),
|
||||
(preset.n5, preset.n6),
|
||||
(preset.n7, preset.n8),
|
||||
]
|
||||
|
||||
for n, color in enumerate(preset.c):
|
||||
self.driver.fill_n(color, ranges[n][0], ranges[n][1])
|
||||
|
||||
self.driver.show_all()
|
||||
|
||||
136
pico/src/patterns/segments_transition.py
Normal file
136
pico/src/patterns/segments_transition.py
Normal file
@@ -0,0 +1,136 @@
|
||||
import utime
|
||||
|
||||
|
||||
class SegmentsTransition:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
"""
|
||||
SegmentsTransition: fade from whatever is currently on the strips
|
||||
to a new static Segments layout defined by n1–n8 and c[0..3].
|
||||
|
||||
- Uses the existing strip buffers as the starting state.
|
||||
- Target state matches the Segments pattern: up to 4 colored bands
|
||||
along the logical reference strip, mapped to all physical strips.
|
||||
- Transition duration is taken from preset.d (ms), minimum 50ms.
|
||||
"""
|
||||
strips = self.driver.strips
|
||||
if not strips:
|
||||
while True:
|
||||
yield
|
||||
|
||||
# Snapshot starting GRB buffers (already scaled by per-strip brightness)
|
||||
start_bufs = [bytes(strip.ar) for strip in strips]
|
||||
|
||||
# Prepare target buffers (same length as each strip's ar)
|
||||
target_bufs = [bytearray(len(strip.ar)) for strip in strips]
|
||||
|
||||
# Base colors (up to 4), missing ones default to black
|
||||
colors = list(preset.c) if getattr(preset, "c", None) else []
|
||||
while len(colors) < 4:
|
||||
colors.append((0, 0, 0))
|
||||
|
||||
# Apply preset/global brightness once per color
|
||||
bright_colors = [
|
||||
self.driver.apply_brightness(colors[0], preset.b),
|
||||
self.driver.apply_brightness(colors[1], preset.b),
|
||||
self.driver.apply_brightness(colors[2], preset.b),
|
||||
self.driver.apply_brightness(colors[3], preset.b),
|
||||
]
|
||||
|
||||
# Logical reference length for all strips (from scale_map[0])
|
||||
ref_len = len(self.driver.scale_map[0]) if self.driver.scale_map else 0
|
||||
if ref_len <= 0:
|
||||
# Fallback: nothing to do, just hold current state
|
||||
while True:
|
||||
yield
|
||||
|
||||
# Helper to clamp and normalize a logical range [a, b] (inclusive) over ref_len.
|
||||
# Returns (start, end_exclusive) suitable for range(start, end_exclusive).
|
||||
def norm_range(a, b):
|
||||
a = int(a)
|
||||
b = int(b)
|
||||
if a > b:
|
||||
a, b = b, a
|
||||
if b < 0 or a >= ref_len:
|
||||
return None
|
||||
a = max(0, a)
|
||||
b = min(ref_len - 1, b)
|
||||
if a > b:
|
||||
return None
|
||||
return a, b + 1
|
||||
|
||||
raw_ranges = [
|
||||
(getattr(preset, "n1", 0), getattr(preset, "n2", -1), bright_colors[0]),
|
||||
(getattr(preset, "n3", 0), getattr(preset, "n4", -1), bright_colors[1]),
|
||||
(getattr(preset, "n5", 0), getattr(preset, "n6", -1), bright_colors[2]),
|
||||
(getattr(preset, "n7", 0), getattr(preset, "n8", -1), bright_colors[3]),
|
||||
]
|
||||
|
||||
# Build target buffers using the same logical indexing idea as Segments
|
||||
for strip_idx, strip in enumerate(strips):
|
||||
bright = strip.brightness
|
||||
scale_map = self.driver.scale_map[strip_idx]
|
||||
buf = target_bufs[strip_idx]
|
||||
n_leds = strip.num_leds
|
||||
|
||||
# Start from black everywhere
|
||||
for i in range(len(buf)):
|
||||
buf[i] = 0
|
||||
|
||||
# Apply each logical range to this strip
|
||||
for a, b, color in raw_ranges:
|
||||
rng = norm_range(a, b)
|
||||
if not rng:
|
||||
continue
|
||||
start, end = rng
|
||||
r, g, bl = color
|
||||
for logical_idx in range(start, end):
|
||||
if logical_idx < 0 or logical_idx >= len(scale_map):
|
||||
continue
|
||||
phys_idx = scale_map[logical_idx]
|
||||
if phys_idx < 0 or phys_idx >= n_leds:
|
||||
continue
|
||||
base = phys_idx * 3
|
||||
if base + 2 >= len(buf):
|
||||
continue
|
||||
buf[base] = int(g * bright)
|
||||
buf[base + 1] = int(r * bright)
|
||||
buf[base + 2] = int(bl * bright)
|
||||
|
||||
# Duration in ms for the whole transition (slower by default)
|
||||
# If preset.d is provided, use it; otherwise default to a slow 3000ms fade.
|
||||
raw_d = int(getattr(preset, "d", 3000) or 3000)
|
||||
duration = max(1000, raw_d) # enforce at least 1s for a clearly visible transition
|
||||
start_time = utime.ticks_ms()
|
||||
|
||||
while True:
|
||||
now = utime.ticks_ms()
|
||||
elapsed = utime.ticks_diff(now, start_time)
|
||||
|
||||
if elapsed >= duration:
|
||||
# Final frame: commit target buffers and hold, then update all strips together
|
||||
for strip, target in zip(strips, target_bufs):
|
||||
ar = strip.ar
|
||||
for i in range(len(ar)):
|
||||
ar[i] = target[i]
|
||||
self.driver.show_all()
|
||||
while True:
|
||||
yield
|
||||
|
||||
# Interpolation factor in [0,1]
|
||||
factor = elapsed / duration
|
||||
inv = 1.0 - factor
|
||||
|
||||
# Blend from start to target in GRB space per byte
|
||||
for idx, strip in enumerate(strips):
|
||||
start_buf = start_bufs[idx]
|
||||
target_buf = target_bufs[idx]
|
||||
ar = strip.ar
|
||||
for i in range(len(ar)):
|
||||
ar[i] = int(start_buf[i] * inv + target_buf[i] * factor)
|
||||
self.driver.show_all()
|
||||
|
||||
yield
|
||||
|
||||
@@ -2,11 +2,57 @@ from machine import Pin
|
||||
from ws2812 import WS2812B
|
||||
from preset import Preset
|
||||
from patterns import (
|
||||
Blink, Rainbow, Pulse, Transition, Chase, Circle, Roll, Calibration, Test,
|
||||
Grab, Spin, Lift, Flare, Hook, Pose, Point,
|
||||
Blink,
|
||||
Rainbow,
|
||||
Pulse,
|
||||
Transition,
|
||||
Chase,
|
||||
Circle,
|
||||
DoubleCircle,
|
||||
Roll,
|
||||
Calibration,
|
||||
Test,
|
||||
Grab,
|
||||
Spin,
|
||||
Lift,
|
||||
Flare,
|
||||
Hook,
|
||||
Pose,
|
||||
Segments,
|
||||
SegmentsTransition,
|
||||
)
|
||||
import json
|
||||
|
||||
|
||||
class _LogicalRing:
|
||||
"""
|
||||
Lightweight logical ring over all strips.
|
||||
Used by patterns that expect driver.n (e.g. Circle, Roll legacy API).
|
||||
"""
|
||||
|
||||
def __init__(self, driver):
|
||||
self._driver = driver
|
||||
self.num_strips = len(driver.strips)
|
||||
|
||||
def __len__(self):
|
||||
return self._driver.num_leds
|
||||
|
||||
def fill(self, color):
|
||||
# Apply color to all logical positions across all strips
|
||||
for i in range(self._driver.num_leds):
|
||||
for strip_idx in range(self.num_strips):
|
||||
self._driver.set(strip_idx, i, color)
|
||||
|
||||
def __setitem__(self, index, color):
|
||||
if index < 0 or index >= self._driver.num_leds:
|
||||
return
|
||||
for strip_idx in range(self.num_strips):
|
||||
self._driver.set(strip_idx, index, color)
|
||||
|
||||
def write(self):
|
||||
self._driver.show_all()
|
||||
|
||||
|
||||
# Order: strips[0]=physical 1 … strips[7]=physical 8. (pin, num_leds, midpoint_index).
|
||||
STRIP_CONFIG = (
|
||||
(6, 291, 291 // 2), # 1
|
||||
@@ -34,8 +80,12 @@ class Presets:
|
||||
state_machine += 1
|
||||
self.scale_map.append(self.create_scale_map(num_leds))
|
||||
|
||||
# Single logical strip over all 8 strips for patterns (n[i], .fill(), .write())
|
||||
# Single logical strip using strip 0 as reference for patterns (n[i], .fill(), .write())
|
||||
# WS2812B with brightness=1.0 so Presets.apply_brightness() does all scaling (NeoPixel drop-in)
|
||||
# Reference logical length for patterns that use driver.num_leds (Rainbow/Chase/Circle, etc.)
|
||||
self.num_leds = self.strips[0].num_leds if self.strips else 0
|
||||
# Legacy logical ring interface for patterns expecting driver.n
|
||||
self.n = _LogicalRing(self)
|
||||
self.step = 0
|
||||
# Remember which strip was last used as the roll head (for flare, etc.)
|
||||
self.last_roll_head = 0
|
||||
@@ -56,6 +106,7 @@ class Presets:
|
||||
"transition": Transition(self).run,
|
||||
"chase": Chase(self).run,
|
||||
"circle": Circle(self).run,
|
||||
"double_circle": DoubleCircle(self).run,
|
||||
"roll": Roll(self).run,
|
||||
"calibration": Calibration(self).run,
|
||||
"test": Test(self).run,
|
||||
@@ -65,7 +116,9 @@ class Presets:
|
||||
"flare": Flare(self).run,
|
||||
"hook": Hook(self).run,
|
||||
"pose": Pose(self).run,
|
||||
"point": Point(self).run,
|
||||
"segments": Segments(self).run,
|
||||
"segments_transition": SegmentsTransition(self).run,
|
||||
"point": Segments(self).run, # backwards-compatible alias
|
||||
}
|
||||
|
||||
# --- Strip geometry utilities -------------------------------------------------
|
||||
@@ -159,11 +212,13 @@ class Presets:
|
||||
|
||||
def off(self, preset=None):
|
||||
self.fill((0, 0, 0))
|
||||
self.show_all()
|
||||
|
||||
def on(self, preset):
|
||||
colors = preset.c
|
||||
color = colors[0] if colors else (255, 255, 255)
|
||||
self.fill(self.apply_brightness(color, preset.b))
|
||||
self.show_all()
|
||||
|
||||
def fill(self, color):
|
||||
for strip in self.strips:
|
||||
|
||||
157
pico/test/test_double_circle.py
Normal file
157
pico/test/test_double_circle.py
Normal file
@@ -0,0 +1,157 @@
|
||||
"""
|
||||
On-device test for the double_circle 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_double_circle.py :
|
||||
mpremote connect <device> run test_double_circle.py
|
||||
|
||||
This script:
|
||||
- Instantiates Presets
|
||||
- Creates a few in-memory 'double_circle' presets with different centers, widths, and colors
|
||||
- Selects each one so you can visually confirm the symmetric bands and color gradients
|
||||
"""
|
||||
|
||||
from presets import Presets, Preset
|
||||
|
||||
|
||||
def make_double_circle_preset(
|
||||
name, center, half_width, colors, direction=0, step_size=1, brightness=255
|
||||
):
|
||||
"""
|
||||
Helper to build a Preset for the 'double_circle' pattern.
|
||||
|
||||
center: logical index (0-based, on reference strip 0)
|
||||
half_width: number of LEDs each side of center
|
||||
colors: [color1, color2] where each color is (r,g,b)
|
||||
"""
|
||||
cs = list(colors)[:2]
|
||||
while len(cs) < 2:
|
||||
cs.append((0, 0, 0))
|
||||
|
||||
data = {
|
||||
"p": "double_circle",
|
||||
"c": cs,
|
||||
"b": brightness,
|
||||
"n1": center,
|
||||
"n2": half_width,
|
||||
"n3": direction,
|
||||
"n4": step_size,
|
||||
}
|
||||
return name, Preset(data)
|
||||
|
||||
|
||||
def show_and_wait(presets, name, preset_obj, wait_ms):
|
||||
"""Select a static double_circle preset and hold it for wait_ms."""
|
||||
presets.presets[name] = preset_obj
|
||||
presets.select(name)
|
||||
# DoubleCircle draws immediately in run(), then just yields; one tick is enough.
|
||||
presets.tick()
|
||||
|
||||
import utime
|
||||
|
||||
start = utime.ticks_ms()
|
||||
while utime.ticks_diff(utime.ticks_ms(), start) < wait_ms:
|
||||
presets.tick()
|
||||
|
||||
|
||||
def main():
|
||||
presets = Presets()
|
||||
presets.load()
|
||||
|
||||
num_leds = presets.strip_length(0)
|
||||
if num_leds <= 0:
|
||||
print("No strips; aborting double_circle test.")
|
||||
return
|
||||
|
||||
print("Starting double_circle pattern test...")
|
||||
|
||||
quarter = num_leds // 4
|
||||
half = num_leds // 2
|
||||
|
||||
dc_presets = []
|
||||
|
||||
# 1. Center at top (0), moderate width, color1 at center (n3=0)
|
||||
dc_presets.append(
|
||||
make_double_circle_preset(
|
||||
"dc_top_red_to_blue",
|
||||
center=0,
|
||||
half_width=quarter,
|
||||
colors=[(255, 0, 0), (0, 0, 255)],
|
||||
direction=0,
|
||||
)
|
||||
)
|
||||
|
||||
# 2. Center at bottom (half), narrow band, color1 at endpoints (n3=1)
|
||||
dc_presets.append(
|
||||
make_double_circle_preset(
|
||||
"dc_bottom_green_to_purple",
|
||||
center=half,
|
||||
half_width=quarter // 2,
|
||||
colors=[(0, 255, 0), (128, 0, 128)],
|
||||
direction=1,
|
||||
)
|
||||
)
|
||||
|
||||
# 3. Center at quarter, wide band, both directions for comparison
|
||||
dc_presets.append(
|
||||
make_double_circle_preset(
|
||||
"dc_quarter_white_to_cyan_inward",
|
||||
center=quarter,
|
||||
half_width=half,
|
||||
colors=[(255, 255, 255), (0, 255, 255)],
|
||||
direction=0,
|
||||
)
|
||||
)
|
||||
|
||||
dc_presets.append(
|
||||
make_double_circle_preset(
|
||||
"dc_quarter_white_to_cyan_outward",
|
||||
center=quarter,
|
||||
half_width=half,
|
||||
colors=[(255, 255, 255), (0, 255, 255)],
|
||||
direction=1,
|
||||
)
|
||||
)
|
||||
|
||||
# 4. Explicit test: n1 = 50, n2 = 40 (half of 80) inward
|
||||
dc_presets.append(
|
||||
make_double_circle_preset(
|
||||
"dc_n1_50_n2_40_inward",
|
||||
center=50,
|
||||
half_width=40,
|
||||
colors=[(255, 100, 0), (0, 0, 0)],
|
||||
direction=0,
|
||||
)
|
||||
)
|
||||
|
||||
# 5. Explicit test: n1 = num_leds//2, n2 = num_leds//4 outward, stepping as fast as possible
|
||||
center_half = num_leds // 2
|
||||
radius_quarter = max(1, num_leds // 4)
|
||||
dc_presets.append(
|
||||
make_double_circle_preset(
|
||||
"dc_n1_half_n2_quarter_outward",
|
||||
center=center_half,
|
||||
half_width=radius_quarter,
|
||||
colors=[(0, 150, 255), (0, 0, 0)],
|
||||
direction=1,
|
||||
step_size=radius_quarter, # jump to full radius in one step
|
||||
)
|
||||
)
|
||||
|
||||
# Show each for ~4 seconds
|
||||
for name, preset_obj in dc_presets:
|
||||
print("Showing double_circle preset:", name)
|
||||
show_and_wait(presets, name, preset_obj, wait_ms=4000)
|
||||
|
||||
print("Double_circle pattern test finished. Turning off LEDs.")
|
||||
presets.select("off")
|
||||
presets.tick()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
264
pico/test/test_multi_patterns.py
Normal file
264
pico/test/test_multi_patterns.py
Normal file
@@ -0,0 +1,264 @@
|
||||
"""
|
||||
On-device test that exercises Segments and multiple SegmentsTransition presets 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_multi_patterns.py :
|
||||
mpremote connect <device> run test_multi_patterns.py
|
||||
"""
|
||||
|
||||
import utime
|
||||
|
||||
from presets import Presets, Preset
|
||||
|
||||
|
||||
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 make_segments_preset(name, colors, n_values, brightness=255):
|
||||
"""
|
||||
Helper to build a Preset for the 'segments' pattern.
|
||||
|
||||
colors: list of up to 4 (r,g,b) tuples
|
||||
n_values: list/tuple of 8 ints [n1..n8]
|
||||
"""
|
||||
cs = list(colors)[:4]
|
||||
while len(cs) < 4:
|
||||
cs.append((0, 0, 0))
|
||||
|
||||
n1, n2, n3, n4, n5, n6, n7, n8 = n_values
|
||||
data = {
|
||||
"p": "segments",
|
||||
"c": cs,
|
||||
"b": brightness,
|
||||
"n1": n1,
|
||||
"n2": n2,
|
||||
"n3": n3,
|
||||
"n4": n4,
|
||||
"n5": n5,
|
||||
"n6": n6,
|
||||
"n7": n7,
|
||||
"n8": n8,
|
||||
}
|
||||
return name, Preset(data)
|
||||
|
||||
|
||||
def make_segments_transition_preset(name, colors, n_values, duration_ms=1000, brightness=255):
|
||||
"""
|
||||
Helper to build a Preset for the 'segments_transition' pattern.
|
||||
|
||||
Starts from whatever is currently displayed and fades to the
|
||||
new segments layout over duration_ms.
|
||||
"""
|
||||
cs = list(colors)[:4]
|
||||
while len(cs) < 4:
|
||||
cs.append((0, 0, 0))
|
||||
|
||||
n1, n2, n3, n4, n5, n6, n7, n8 = n_values
|
||||
data = {
|
||||
"p": "segments_transition",
|
||||
"c": cs,
|
||||
"b": brightness,
|
||||
"d": duration_ms,
|
||||
"n1": n1,
|
||||
"n2": n2,
|
||||
"n3": n3,
|
||||
"n4": n4,
|
||||
"n5": n5,
|
||||
"n6": n6,
|
||||
"n7": n7,
|
||||
"n8": n8,
|
||||
}
|
||||
return name, Preset(data)
|
||||
|
||||
|
||||
def main():
|
||||
presets = Presets()
|
||||
presets.load()
|
||||
|
||||
num_leds = presets.strip_length(0)
|
||||
if num_leds <= 0:
|
||||
print("No strips; aborting multi-pattern test.")
|
||||
return
|
||||
|
||||
print("Starting multi-pattern test with Presets...")
|
||||
|
||||
quarter = num_leds // 4
|
||||
half = num_leds // 2
|
||||
|
||||
# 1. Static segments: simple R/G/B bands
|
||||
name_static, preset_static = make_segments_preset(
|
||||
"mp_segments_static",
|
||||
colors=[(255, 0, 0), (0, 255, 0), (0, 0, 255)],
|
||||
n_values=[
|
||||
0,
|
||||
quarter - 1, # red
|
||||
quarter,
|
||||
2 * quarter - 1, # green
|
||||
2 * quarter,
|
||||
3 * quarter - 1, # blue
|
||||
0,
|
||||
-1,
|
||||
],
|
||||
)
|
||||
|
||||
# 2a. Segments transition: fade from previous buffer to new colors (slow)
|
||||
name_trans1, preset_trans1 = make_segments_transition_preset(
|
||||
"mp_segments_transition_1",
|
||||
colors=[(255, 255, 255), (255, 0, 255), (0, 255, 255)],
|
||||
n_values=[
|
||||
0,
|
||||
half - 1, # white on first half
|
||||
half,
|
||||
num_leds - 1, # magenta on second half
|
||||
0,
|
||||
-1, # cyan unused in this example
|
||||
0,
|
||||
-1,
|
||||
],
|
||||
duration_ms=3000,
|
||||
)
|
||||
|
||||
# 2b. Segments transition: fade between two different segment layouts
|
||||
name_trans2, preset_trans2 = make_segments_transition_preset(
|
||||
"mp_segments_transition_2",
|
||||
colors=[(255, 0, 0), (0, 0, 255)],
|
||||
n_values=[
|
||||
0,
|
||||
quarter - 1, # red first quarter
|
||||
quarter,
|
||||
2 * quarter - 1, # blue second quarter
|
||||
0,
|
||||
-1,
|
||||
0,
|
||||
-1,
|
||||
],
|
||||
duration_ms=4000,
|
||||
)
|
||||
|
||||
# 2c. Segments transition: thin moving band (center quarter only)
|
||||
band_start = quarter // 2
|
||||
band_end = band_start + quarter
|
||||
name_trans3, preset_trans3 = make_segments_transition_preset(
|
||||
"mp_segments_transition_3",
|
||||
colors=[(0, 255, 0)],
|
||||
n_values=[
|
||||
band_start,
|
||||
band_end - 1, # green band in the middle
|
||||
0,
|
||||
-1,
|
||||
0,
|
||||
-1,
|
||||
0,
|
||||
-1,
|
||||
],
|
||||
duration_ms=5000,
|
||||
)
|
||||
|
||||
# 2d. Segments transition: full-ring warm white fade
|
||||
name_trans4, preset_trans4 = make_segments_transition_preset(
|
||||
"mp_segments_transition_4",
|
||||
colors=[(255, 200, 100)],
|
||||
n_values=[
|
||||
0,
|
||||
num_leds - 1, # entire strip
|
||||
0,
|
||||
-1,
|
||||
0,
|
||||
-1,
|
||||
0,
|
||||
-1,
|
||||
],
|
||||
duration_ms=6000,
|
||||
)
|
||||
|
||||
# 2e. Segments transition: alternating warm/cool halves
|
||||
name_trans5, preset_trans5 = make_segments_transition_preset(
|
||||
"mp_segments_transition_5",
|
||||
colors=[(255, 180, 100), (100, 180, 255)],
|
||||
n_values=[
|
||||
0,
|
||||
half - 1, # warm first half
|
||||
half,
|
||||
num_leds - 1, # cool second half
|
||||
0,
|
||||
-1,
|
||||
0,
|
||||
-1,
|
||||
],
|
||||
duration_ms=5000,
|
||||
)
|
||||
|
||||
# 2f. Segments transition: narrow red band near start
|
||||
narrow_start = num_leds // 16
|
||||
narrow_end = narrow_start + max(4, num_leds // 32)
|
||||
name_trans6, preset_trans6 = make_segments_transition_preset(
|
||||
"mp_segments_transition_6",
|
||||
colors=[(255, 0, 0)],
|
||||
n_values=[
|
||||
narrow_start,
|
||||
narrow_end - 1,
|
||||
0,
|
||||
-1,
|
||||
0,
|
||||
-1,
|
||||
0,
|
||||
-1,
|
||||
],
|
||||
duration_ms=4000,
|
||||
)
|
||||
|
||||
# Register presets in Presets and run them in sequence
|
||||
presets.presets[name_static] = preset_static
|
||||
presets.presets[name_trans1] = preset_trans1
|
||||
presets.presets[name_trans2] = preset_trans2
|
||||
presets.presets[name_trans3] = preset_trans3
|
||||
presets.presets[name_trans4] = preset_trans4
|
||||
presets.presets[name_trans5] = preset_trans5
|
||||
presets.presets[name_trans6] = preset_trans6
|
||||
|
||||
print("Showing static segments...")
|
||||
presets.select(name_static)
|
||||
presets.tick() # draw once
|
||||
run_for(presets, 3000)
|
||||
|
||||
print("Running segments transition 1 (fading to new half/half layout)...")
|
||||
presets.select(name_trans1)
|
||||
run_for(presets, 3500)
|
||||
|
||||
print("Running segments transition 2 (fading to quarter-band layout)...")
|
||||
presets.select(name_trans2)
|
||||
run_for(presets, 4500)
|
||||
|
||||
print("Running segments transition 3 (fading to center green band)...")
|
||||
presets.select(name_trans3)
|
||||
run_for(presets, 5500)
|
||||
|
||||
print("Running segments transition 4 (fading to full warm white ring)...")
|
||||
presets.select(name_trans4)
|
||||
run_for(presets, 6500)
|
||||
|
||||
print("Running segments transition 5 (fading to warm/cool halves)...")
|
||||
presets.select(name_trans5)
|
||||
run_for(presets, 5500)
|
||||
|
||||
print("Running segments transition 6 (fading to narrow red band)...")
|
||||
presets.select(name_trans6)
|
||||
run_for(presets, 4500)
|
||||
|
||||
print("Multi-pattern test finished. Turning off LEDs.")
|
||||
presets.select("off")
|
||||
presets.tick()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
"""
|
||||
On-device test for the Point pattern using mpremote.
|
||||
On-device test for the Segments 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_point.py :
|
||||
mpremote connect <device> run test_point.py
|
||||
mpremote connect <device> cp test/test_segments.py :
|
||||
mpremote connect <device> run test_segments.py
|
||||
|
||||
This script:
|
||||
- Instantiates Presets
|
||||
- Creates a few in-memory 'point' presets with different ranges/colors
|
||||
- Creates a few in-memory 'point' (Segments) presets with different ranges/colors
|
||||
- Selects each one so you can visually confirm the segments
|
||||
"""
|
||||
|
||||
from presets import Presets, Preset
|
||||
|
||||
|
||||
def make_point_preset(name, colors, n_values, brightness=255):
|
||||
def make_segments_preset(name, colors, n_values, brightness=255):
|
||||
"""
|
||||
Helper to build a Preset for the 'point' pattern.
|
||||
Helper to build a Preset for the 'segments' pattern (key 'point').
|
||||
|
||||
colors: list of up to 4 (r,g,b) tuples
|
||||
n_values: list/tuple of 8 ints [n1..n8]
|
||||
@@ -32,7 +32,7 @@ def make_point_preset(name, colors, n_values, brightness=255):
|
||||
|
||||
n1, n2, n3, n4, n5, n6, n7, n8 = n_values
|
||||
data = {
|
||||
"p": "point",
|
||||
"p": "segments", # pattern key for Segments
|
||||
"c": cs,
|
||||
"b": brightness,
|
||||
"n1": n1,
|
||||
@@ -43,16 +43,16 @@ def make_point_preset(name, colors, n_values, brightness=255):
|
||||
"n6": n6,
|
||||
"n7": n7,
|
||||
"n8": n8,
|
||||
# 'a' is not used by point; it's static
|
||||
# 'a' is not used by segments; it's static
|
||||
}
|
||||
return name, Preset(data)
|
||||
|
||||
|
||||
def show_and_wait(presets, name, preset_obj, wait_ms):
|
||||
"""Select a static 'point' preset and hold it for wait_ms."""
|
||||
"""Select a static segments preset and hold it for wait_ms."""
|
||||
presets.presets[name] = preset_obj
|
||||
presets.select(name)
|
||||
# Point draws immediately in run(), then just yields; one tick is enough.
|
||||
# Segments draws immediately in run(), then just yields; one tick is enough.
|
||||
presets.tick()
|
||||
|
||||
import utime
|
||||
@@ -69,46 +69,46 @@ def main():
|
||||
|
||||
num_leds = presets.strip_length(0)
|
||||
if num_leds <= 0:
|
||||
print("No strips; aborting point test.")
|
||||
print("No strips; aborting segments test.")
|
||||
return
|
||||
|
||||
print("Starting point pattern test...")
|
||||
print("Starting segments pattern test...")
|
||||
|
||||
quarter = num_leds // 4
|
||||
half = num_leds // 2
|
||||
|
||||
point_presets = []
|
||||
segments_presets = []
|
||||
|
||||
# 1. Single band: first quarter, red
|
||||
point_presets.append(
|
||||
make_point_preset(
|
||||
"point_red_q1",
|
||||
segments_presets.append(
|
||||
make_segments_preset(
|
||||
"segments_red_q1",
|
||||
colors=[(255, 0, 0)],
|
||||
n_values=[0, quarter - 1, 0, -1, 0, -1, 0, -1],
|
||||
)
|
||||
)
|
||||
|
||||
# 2. Two bands: red first half, green second half
|
||||
point_presets.append(
|
||||
make_point_preset(
|
||||
"point_red_green_halves",
|
||||
segments_presets.append(
|
||||
make_segments_preset(
|
||||
"segments_red_green_halves",
|
||||
colors=[(255, 0, 0), (0, 255, 0)],
|
||||
n_values=[0, half - 1, half, num_leds - 1, 0, -1, 0, -1],
|
||||
)
|
||||
)
|
||||
|
||||
# 3. Three bands: R, G, B quarters
|
||||
point_presets.append(
|
||||
make_point_preset(
|
||||
"point_rgb_quarters",
|
||||
segments_presets.append(
|
||||
make_segments_preset(
|
||||
"segments_rgb_quarters",
|
||||
colors=[(255, 0, 0), (0, 255, 0), (0, 0, 255)],
|
||||
n_values=[
|
||||
0,
|
||||
quarter - 1, # red
|
||||
quarter - 1, # red
|
||||
quarter,
|
||||
2 * quarter - 1, # green
|
||||
2 * quarter - 1, # green
|
||||
2 * quarter,
|
||||
3 * quarter - 1, # blue
|
||||
3 * quarter - 1, # blue
|
||||
0,
|
||||
-1,
|
||||
],
|
||||
@@ -116,11 +116,11 @@ def main():
|
||||
)
|
||||
|
||||
# Show each for ~4 seconds
|
||||
for name, preset_obj in point_presets:
|
||||
print("Showing point preset:", name)
|
||||
for name, preset_obj in segments_presets:
|
||||
print("Showing segments preset:", name)
|
||||
show_and_wait(presets, name, preset_obj, wait_ms=4000)
|
||||
|
||||
print("Point pattern test finished. Turning off LEDs.")
|
||||
print("Segments pattern test finished. Turning off LEDs.")
|
||||
presets.select("off")
|
||||
presets.tick()
|
||||
|
||||
Reference in New Issue
Block a user