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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
128
pico/test/test_spin.py
Normal 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()
|
||||||
|
|
||||||
Reference in New Issue
Block a user