Refine calibration, chase, and spin patterns and add spin test

Increase calibration brightness, update chase to use the new logical ring abstraction, and make spin start from a cleared frame with symmetric arm speed, alongside a dedicated on-device spin test script.

Made-with: Cursor
This commit is contained in:
2026-03-05 23:41:25 +13:00
parent 3e58f4e97e
commit 5f457b3ae7
4 changed files with 157 additions and 25 deletions

View File

@@ -1,6 +1,6 @@
"""Calibration: strips 2 and 6 only. First 10 green, then alternating 10 blue / 10 red. 10% brightness.""" """Calibration: strips 2 and 6 only. First 10 green, then alternating 10 blue / 10 red. 10% brightness."""
BRIGHTNESS = 0.10 BRIGHTNESS = 1
BLOCK = 10 BLOCK = 10
STRIPS_ON = (2, 6) # 0-based: 3rd and 7th strip only STRIPS_ON = (2, 6) # 0-based: 3rd and 7th strip only
@@ -22,6 +22,7 @@ class Calibration:
green = _scale(GREEN, BRIGHTNESS) green = _scale(GREEN, BRIGHTNESS)
red = _scale(RED, BRIGHTNESS) red = _scale(RED, BRIGHTNESS)
blue = _scale(BLUE, BRIGHTNESS) blue = _scale(BLUE, BRIGHTNESS)
blue = (0,0,0)
on_set = set(STRIPS_ON) on_set = set(STRIPS_ON)
for strip_idx, strip in enumerate(strips): for strip_idx, strip in enumerate(strips):
n = strip.num_leds n = strip.num_leds

View File

@@ -35,7 +35,7 @@ class Chase:
segment_length = n1 + n2 segment_length = n1 + n2
# Calculate position from step_count # Calculate position from step_count
step_count = self.driver.step step_count = int(self.driver.step)
# Position alternates: step 0 adds n3, step 1 adds n4, step 2 adds n3, etc. # Position alternates: step 0 adds n3, step 1 adds n4, step 2 adds n3, etc.
if step_count % 2 == 0: if step_count % 2 == 0:
# Even steps: (step_count//2) pairs of (n3+n4) plus one extra n3 # Even steps: (step_count//2) pairs of (n3+n4) plus one extra n3
@@ -52,23 +52,23 @@ class Chase:
# If auto is False, run a single step and then stop # If auto is False, run a single step and then stop
if not preset.a: if not preset.a:
# Clear all LEDs # Draw repeating pattern starting at position across all physical strips
self.driver.n.fill((0, 0, 0)) num_leds = self.driver.num_leds
num_strips = len(self.driver.strips)
# Draw repeating pattern starting at position for i in range(num_leds):
for i in range(self.driver.num_leds):
# Calculate position in the repeating segment # Calculate position in the repeating segment
relative_pos = (i - position) % segment_length relative_pos = (i - position) % segment_length
if relative_pos < 0: if relative_pos < 0:
relative_pos = (relative_pos + segment_length) % segment_length relative_pos = (relative_pos + segment_length) % segment_length
# Determine which color based on position in segment # Determine which color based on position in segment
if relative_pos < n1: color = color0 if relative_pos < n1 else color1
self.driver.n[i] = color0
else:
self.driver.n[i] = color1
self.driver.n.write() # 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 for next beat # Increment step for next beat
self.driver.step = step_count + 1 self.driver.step = step_count + 1
@@ -97,23 +97,23 @@ class Chase:
if position < 0: if position < 0:
position += max_pos position += max_pos
# Clear all LEDs # Draw repeating pattern starting at position across all physical strips
self.driver.n.fill((0, 0, 0)) num_leds = self.driver.num_leds
num_strips = len(self.driver.strips)
# Draw repeating pattern starting at position for i in range(num_leds):
for i in range(self.driver.num_leds):
# Calculate position in the repeating segment # Calculate position in the repeating segment
relative_pos = (i - position) % segment_length relative_pos = (i - position) % segment_length
if relative_pos < 0: if relative_pos < 0:
relative_pos = (relative_pos + segment_length) % segment_length relative_pos = (relative_pos + segment_length) % segment_length
# Determine which color based on position in segment # Determine which color based on position in segment
if relative_pos < n1: color = color0 if relative_pos < n1 else color1
self.driver.n[i] = color0
else:
self.driver.n[i] = color1
self.driver.n.write() # 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 # Increment step
step_count += 1 step_count += 1

View File

@@ -2,7 +2,7 @@
import utime import utime
SPAN = 10 # LEDs on each side of center (match Grab) SPAN = 0 # LEDs on each side of center (match Grab)
LUT_SIZE = 256 # gradient lookup table entries LUT_SIZE = 256 # gradient lookup table entries
@@ -12,6 +12,9 @@ class Spin:
def run(self, preset): def run(self, preset):
strips = self.driver.strips strips = self.driver.strips
self.driver.fill((0, 0, 0))
self.driver.show_all()
active_indices = (0, 4) active_indices = (0, 4)
c0 = preset.c[0] c0 = preset.c[0]
c1 = preset.c[1] c1 = preset.c[1]
@@ -58,8 +61,8 @@ class Spin:
n = strip.num_leds n = strip.num_leds
mid = midpoints[idx] mid = midpoints[idx]
# Expand arms: inside (strip 1, idx 0) moves slower, outside (strip 5, idx 4) faster # Expand arms at the same rate on both sides
step = max(1, rate // 2) if idx == 0 else rate step = max(1, rate)
new_left = max(margin, left[idx] - step) new_left = max(margin, left[idx] - step)
new_right = min(n - margin, right[idx] + step) new_right = min(n - margin, right[idx] + step)

128
pico/test/test_spin.py Normal file
View File

@@ -0,0 +1,128 @@
"""
On-device test for the Spin pattern using mpremote and 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 presets.json :
mpremote connect <device> cp test/test_spin.py :
mpremote connect <device> run test_spin.py
This script:
- Instantiates Presets
- Creates a few in-memory spin 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_spin_preset(
name,
color_inner,
color_outer,
rate=4,
delay_ms=30,
margin=0,
brightness=255,
):
"""Helper to build a Preset dict for the spin pattern."""
data = {
"p": "spin",
"c": [color_inner, color_outer],
"b": brightness,
"d": delay_ms,
"n1": rate, # expansion step per tick
"n2": margin, # margin from strip ends
"a": True,
}
return name, Preset(data)
def run_preset(presets, name, preset_obj, duration_ms):
"""Run a given spin preset for duration_ms using the existing tick loop."""
# Start each preset from a blank frame so both sides are balanced.
presets.select("off")
presets.tick()
presets.presets[name] = preset_obj
presets.select(name)
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()
# Ensure we start from a blank frame.
presets.select("off")
presets.tick()
print("Starting spin pattern test...")
# Use strip 0 length to derive a reasonable margin.
ref_len = presets.strip_length(0)
margin_small = ref_len // 16 if ref_len > 0 else 0
margin_large = ref_len // 8 if ref_len > 0 else 0
spin_presets = []
# 1. Slow spin, warm white to orange, small margin
spin_presets.append(
make_spin_preset(
"spin_slow_warm",
color_inner=(255, 200, 120),
color_outer=(255, 100, 0),
rate=2,
delay_ms=40,
margin=margin_small,
brightness=255,
)
)
# 2. Medium spin, cyan to magenta, larger margin
spin_presets.append(
make_spin_preset(
"spin_medium_cyan_magenta",
color_inner=(0, 255, 180),
color_outer=(255, 0, 180),
rate=4,
delay_ms=30,
margin=margin_large,
brightness=255,
)
)
# 3. Fast spin, white to off (fade outwards), no margin
spin_presets.append(
make_spin_preset(
"spin_fast_white",
color_inner=(255, 255, 255),
color_outer=(0, 0, 0),
rate=6,
delay_ms=20,
margin=0,
brightness=255,
)
)
# Run each spin preset for about 6 seconds
for name, preset_obj in spin_presets:
print("Running spin preset:", name)
run_preset(presets, name, preset_obj, duration_ms=6000)
print("Spin pattern test finished. Turning off LEDs.")
presets.select("off")
presets.tick()
if __name__ == "__main__":
main()