From 3e58f4e97ea7181c556314076d74654b1da2f006 Mon Sep 17 00:00:00 2001 From: jimmy Date: Thu, 5 Mar 2026 23:41:13 +1300 Subject: [PATCH] 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 --- pico/src/patterns/__init__.py | 4 +- pico/src/patterns/double_circle.py | 84 ++++++ pico/src/patterns/point.py | 18 -- pico/src/patterns/segments.py | 18 ++ pico/src/patterns/segments_transition.py | 136 +++++++++ pico/src/presets.py | 63 ++++- pico/test/test_double_circle.py | 157 +++++++++++ pico/test/test_multi_patterns.py | 264 ++++++++++++++++++ pico/test/{test_point.py => test_segments.py} | 56 ++-- pico/src/presets.json => presets.json | 0 10 files changed, 749 insertions(+), 51 deletions(-) create mode 100644 pico/src/patterns/double_circle.py delete mode 100644 pico/src/patterns/point.py create mode 100644 pico/src/patterns/segments.py create mode 100644 pico/src/patterns/segments_transition.py create mode 100644 pico/test/test_double_circle.py create mode 100644 pico/test/test_multi_patterns.py rename pico/test/{test_point.py => test_segments.py} (62%) rename pico/src/presets.json => presets.json (100%) diff --git a/pico/src/patterns/__init__.py b/pico/src/patterns/__init__.py index d775e93..c3268fe 100644 --- a/pico/src/patterns/__init__.py +++ b/pico/src/patterns/__init__.py @@ -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 diff --git a/pico/src/patterns/double_circle.py b/pico/src/patterns/double_circle.py new file mode 100644 index 0000000..a0e257f --- /dev/null +++ b/pico/src/patterns/double_circle.py @@ -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 + diff --git a/pico/src/patterns/point.py b/pico/src/patterns/point.py deleted file mode 100644 index 0c3d6ae..0000000 --- a/pico/src/patterns/point.py +++ /dev/null @@ -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() - diff --git a/pico/src/patterns/segments.py b/pico/src/patterns/segments.py new file mode 100644 index 0000000..27100fd --- /dev/null +++ b/pico/src/patterns/segments.py @@ -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() + diff --git a/pico/src/patterns/segments_transition.py b/pico/src/patterns/segments_transition.py new file mode 100644 index 0000000..0a64405 --- /dev/null +++ b/pico/src/patterns/segments_transition.py @@ -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 + diff --git a/pico/src/presets.py b/pico/src/presets.py index 53d81f0..ae5ef04 100644 --- a/pico/src/presets.py +++ b/pico/src/presets.py @@ -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: diff --git a/pico/test/test_double_circle.py b/pico/test/test_double_circle.py new file mode 100644 index 0000000..1fb12b3 --- /dev/null +++ b/pico/test/test_double_circle.py @@ -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 cp src/*.py : + mpremote connect cp src/patterns/*.py :patterns + mpremote connect cp lib/*.py : + mpremote connect cp test/test_double_circle.py : + mpremote connect 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() + diff --git a/pico/test/test_multi_patterns.py b/pico/test/test_multi_patterns.py new file mode 100644 index 0000000..6004c4b --- /dev/null +++ b/pico/test/test_multi_patterns.py @@ -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 cp src/*.py : + mpremote connect cp src/patterns/*.py :patterns + mpremote connect cp lib/*.py : + mpremote connect cp test/test_multi_patterns.py : + mpremote connect 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() + diff --git a/pico/test/test_point.py b/pico/test/test_segments.py similarity index 62% rename from pico/test/test_point.py rename to pico/test/test_segments.py index a793f1b..4f44ba1 100644 --- a/pico/test/test_point.py +++ b/pico/test/test_segments.py @@ -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 cp src/*.py : mpremote connect cp src/patterns/*.py :patterns mpremote connect cp lib/*.py : - mpremote connect cp test/test_point.py : - mpremote connect run test_point.py + mpremote connect cp test/test_segments.py : + mpremote connect 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() diff --git a/pico/src/presets.json b/presets.json similarity index 100% rename from pico/src/presets.json rename to presets.json