Compare commits
3 Commits
47c17dba36
...
5f457b3ae7
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f457b3ae7 | |||
| 3e58f4e97e | |||
| 47c19eecf1 |
@@ -1,58 +0,0 @@
|
|||||||
{
|
|
||||||
"buttons": [
|
|
||||||
{"id": "start", "preset": "off"},
|
|
||||||
{"id": "grab", "preset": "grab"},
|
|
||||||
{"id": "spin1", "preset": "spin1"},
|
|
||||||
{"id": "lift", "preset": "lift"},
|
|
||||||
{"id": "flare", "preset": "flare"},
|
|
||||||
{"id": "hook", "preset": "hook"},
|
|
||||||
{"id": "roll1", "preset": "roll1"},
|
|
||||||
{"id": "invertsplit", "preset": "invertsplit"},
|
|
||||||
{"id": "pose1", "preset": "pose1"},
|
|
||||||
{"id": "pose1", "preset": "pose2"},
|
|
||||||
{"id": "roll2", "preset": "roll2"},
|
|
||||||
{"id": "backbalance1", "preset": "backbalance1"},
|
|
||||||
{"id": "beat1", "preset": "beat1"},
|
|
||||||
{"id": "pose3", "preset": "pose3"},
|
|
||||||
{"id": "roll3", "preset": "roll3"},
|
|
||||||
{"id": "crouch", "preset": "crouch"},
|
|
||||||
{"id": "pose4", "preset": "pose4"},
|
|
||||||
{"id": "roll4", "preset": "roll4"},
|
|
||||||
{"id": "backbendsplit", "preset": "backbendsplit"},
|
|
||||||
{"id": "backbalance2", "preset": "backbalance2"},
|
|
||||||
{"id": "backbalance3", "preset": "backbalance3"},
|
|
||||||
{"id": "beat2", "preset": "beat2"},
|
|
||||||
{"id": "straddle", "preset": "straddle"},
|
|
||||||
{"id": "beat3", "preset": "beat3"},
|
|
||||||
{"id": "frontbalance1", "preset": "frontbalance1"},
|
|
||||||
{"id": "pose5", "preset": "pose5"},
|
|
||||||
{"id": "pose6", "preset": "pose6"},
|
|
||||||
{"id": "elbowhang", "preset": "elbowhang"},
|
|
||||||
{"id": "elbowhangspin", "preset": "elbowhangspin"},
|
|
||||||
{"id": "spin2", "preset": "spin2"},
|
|
||||||
{"id": "dismount", "preset": "dismount"},
|
|
||||||
{"id": "spin3", "preset": "spin3"},
|
|
||||||
{"id": "fluff", "preset": "fluff"},
|
|
||||||
{"id": "spin4", "preset": "spin4"},
|
|
||||||
{"id": "flare2", "preset": "flare2"},
|
|
||||||
{"id": "elbowhang", "preset": "elbowhang"},
|
|
||||||
{"id": "elbowhangsplit2", "preset": "elbowhangsplit2"},
|
|
||||||
{"id": "invert", "preset": "invert"},
|
|
||||||
{"id": "roll5", "preset": "roll5"},
|
|
||||||
{"id": "backbend", "preset": "backbend"},
|
|
||||||
{"id": "pose7", "preset": "pose7"},
|
|
||||||
{"id": "roll6", "preset": "roll6"},
|
|
||||||
{"id": "seat", "preset": "seat"},
|
|
||||||
{"id": "kneehang", "preset": "kneehang"},
|
|
||||||
{"id": "legswoop", "preset": "legswoop"},
|
|
||||||
{"id": "split", "preset": "split"},
|
|
||||||
{"id": "foothang", "preset": "foothang"},
|
|
||||||
{"id": "end", "preset": "end"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -65,7 +65,9 @@
|
|||||||
<option value="legswoop">legswoop</option>
|
<option value="legswoop">legswoop</option>
|
||||||
<option value="split">split</option>
|
<option value="split">split</option>
|
||||||
<option value="foothang">foothang</option>
|
<option value="foothang">foothang</option>
|
||||||
<option value="point">point</option>
|
<option value="segments">segments</option>
|
||||||
|
<option value="segments_transition">segments_transition</option>
|
||||||
|
<option value="double_circle">double_circle</option>
|
||||||
<option value="off">off</option>
|
<option value="off">off</option>
|
||||||
<option value="on">on</option>
|
<option value="on">on</option>
|
||||||
<option value="blink">blink</option>
|
<option value="blink">blink</option>
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
"""
|
|
||||||
ESP32-C6 test: send JSON messages to Pico over UART (GPIO17).
|
|
||||||
Settings use strips = [[pin, num_leds], ...]. Run with Pico connected on RX.
|
|
||||||
|
|
||||||
Run with mpremote (from repo root):
|
|
||||||
./esp32/run_test_uart_json.sh
|
|
||||||
# or
|
|
||||||
mpremote run esp32/test/test_uart_send_json.py
|
|
||||||
# or with port
|
|
||||||
mpremote connect /dev/ttyUSB0 run esp32/test/test_uart_send_json.py
|
|
||||||
"""
|
|
||||||
import machine
|
|
||||||
import time
|
|
||||||
import json
|
|
||||||
|
|
||||||
UART_TX_PIN = 17
|
|
||||||
UART_BAUD = 921600
|
|
||||||
LED_PIN = 15
|
|
||||||
|
|
||||||
|
|
||||||
def send_json(uart, obj):
|
|
||||||
line = json.dumps(obj) + "\n"
|
|
||||||
uart.write(line)
|
|
||||||
print("TX:", line.strip())
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
uart = machine.UART(1, baudrate=UART_BAUD, tx=UART_TX_PIN)
|
|
||||||
led = machine.Pin(LED_PIN, machine.Pin.OUT)
|
|
||||||
|
|
||||||
# 1) Settings: one strip, pin 2, 10 LEDs (list of lists)
|
|
||||||
send_json(uart, {
|
|
||||||
"v": 1,
|
|
||||||
"settings": {
|
|
||||||
"strips": [[2, 10]],
|
|
||||||
"brightness": 30,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
led.value(1)
|
|
||||||
time.sleep(0.2)
|
|
||||||
led.value(0)
|
|
||||||
time.sleep(0.3)
|
|
||||||
|
|
||||||
# 2) led-controller format: light + settings.color (hex)
|
|
||||||
send_json(uart, {"light": "strip1", "settings": {"color": "#FF0000"}, "save": False})
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
# 3) led-controller format: light + settings.r,g,b
|
|
||||||
send_json(uart, {"light": "strip1", "settings": {"r": 0, "g": 255, "b": 0}, "save": False})
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
# 4) led-controller format: blue (hex)
|
|
||||||
send_json(uart, {"light": "strip1", "settings": {"color": "#0000FF"}, "save": False})
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
# 5) Off (existing format)
|
|
||||||
send_json(uart, {"v": 1, "off": True})
|
|
||||||
time.sleep(0.3)
|
|
||||||
|
|
||||||
print("Done. Pico: settings -> red (hex) -> green (r,g,b) -> blue (hex) -> off.")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
"""
|
|
||||||
ESP32-C6 UART TX + LED test. Sends a few commands on GPIO17, blinks LED on GPIO15.
|
|
||||||
Run on device: exec(open('test/test_uart_tx').read()) or import test.test_uart_tx
|
|
||||||
Does not require Pico connected.
|
|
||||||
"""
|
|
||||||
import machine
|
|
||||||
import time
|
|
||||||
|
|
||||||
UART_TX_PIN = 17
|
|
||||||
LED_PIN = 15
|
|
||||||
|
|
||||||
def main():
|
|
||||||
uart = machine.UART(1, baudrate=115200, tx=UART_TX_PIN)
|
|
||||||
led = machine.Pin(LED_PIN, machine.Pin.OUT)
|
|
||||||
|
|
||||||
def send(cmd):
|
|
||||||
uart.write(cmd + "\n")
|
|
||||||
print("TX:", cmd)
|
|
||||||
|
|
||||||
# Blink and send a short command sequence
|
|
||||||
commands = ["off", "fill 255 0 0", "fill 0 255 0", "fill 0 0 255", "off"]
|
|
||||||
for i, cmd in enumerate(commands):
|
|
||||||
led.value(1)
|
|
||||||
send(cmd)
|
|
||||||
time.sleep(0.3)
|
|
||||||
led.value(0)
|
|
||||||
time.sleep(0.2)
|
|
||||||
|
|
||||||
print("Done. Connect Pico to see strip follow commands.")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -4,6 +4,7 @@ from .pulse import Pulse
|
|||||||
from .transition import Transition
|
from .transition import Transition
|
||||||
from .chase import Chase
|
from .chase import Chase
|
||||||
from .circle import Circle
|
from .circle import Circle
|
||||||
|
from .double_circle import DoubleCircle
|
||||||
from .roll import Roll
|
from .roll import Roll
|
||||||
from .calibration import Calibration
|
from .calibration import Calibration
|
||||||
from .test import Test
|
from .test import Test
|
||||||
@@ -14,4 +15,5 @@ from .lift import Lift
|
|||||||
from .flare import Flare
|
from .flare import Flare
|
||||||
from .hook import Hook
|
from .hook import Hook
|
||||||
from .pose import Pose
|
from .pose import Pose
|
||||||
from .point import Point
|
from .segments import Segments
|
||||||
|
from .segments_transition import SegmentsTransition
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
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,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)
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,57 @@ from machine import Pin
|
|||||||
from ws2812 import WS2812B
|
from ws2812 import WS2812B
|
||||||
from preset import Preset
|
from preset import Preset
|
||||||
from patterns import (
|
from patterns import (
|
||||||
Blink, Rainbow, Pulse, Transition, Chase, Circle, Roll, Calibration, Test,
|
Blink,
|
||||||
Grab, Spin, Lift, Flare, Hook, Pose, Point,
|
Rainbow,
|
||||||
|
Pulse,
|
||||||
|
Transition,
|
||||||
|
Chase,
|
||||||
|
Circle,
|
||||||
|
DoubleCircle,
|
||||||
|
Roll,
|
||||||
|
Calibration,
|
||||||
|
Test,
|
||||||
|
Grab,
|
||||||
|
Spin,
|
||||||
|
Lift,
|
||||||
|
Flare,
|
||||||
|
Hook,
|
||||||
|
Pose,
|
||||||
|
Segments,
|
||||||
|
SegmentsTransition,
|
||||||
)
|
)
|
||||||
import json
|
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).
|
# Order: strips[0]=physical 1 … strips[7]=physical 8. (pin, num_leds, midpoint_index).
|
||||||
STRIP_CONFIG = (
|
STRIP_CONFIG = (
|
||||||
(6, 291, 291 // 2), # 1
|
(6, 291, 291 // 2), # 1
|
||||||
@@ -34,8 +80,12 @@ class Presets:
|
|||||||
state_machine += 1
|
state_machine += 1
|
||||||
self.scale_map.append(self.create_scale_map(num_leds))
|
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)
|
# 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
|
self.step = 0
|
||||||
# Remember which strip was last used as the roll head (for flare, etc.)
|
# Remember which strip was last used as the roll head (for flare, etc.)
|
||||||
self.last_roll_head = 0
|
self.last_roll_head = 0
|
||||||
@@ -56,6 +106,7 @@ class Presets:
|
|||||||
"transition": Transition(self).run,
|
"transition": Transition(self).run,
|
||||||
"chase": Chase(self).run,
|
"chase": Chase(self).run,
|
||||||
"circle": Circle(self).run,
|
"circle": Circle(self).run,
|
||||||
|
"double_circle": DoubleCircle(self).run,
|
||||||
"roll": Roll(self).run,
|
"roll": Roll(self).run,
|
||||||
"calibration": Calibration(self).run,
|
"calibration": Calibration(self).run,
|
||||||
"test": Test(self).run,
|
"test": Test(self).run,
|
||||||
@@ -65,7 +116,9 @@ class Presets:
|
|||||||
"flare": Flare(self).run,
|
"flare": Flare(self).run,
|
||||||
"hook": Hook(self).run,
|
"hook": Hook(self).run,
|
||||||
"pose": Pose(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 -------------------------------------------------
|
# --- Strip geometry utilities -------------------------------------------------
|
||||||
@@ -159,11 +212,13 @@ class Presets:
|
|||||||
|
|
||||||
def off(self, preset=None):
|
def off(self, preset=None):
|
||||||
self.fill((0, 0, 0))
|
self.fill((0, 0, 0))
|
||||||
|
self.show_all()
|
||||||
|
|
||||||
def on(self, preset):
|
def on(self, preset):
|
||||||
colors = preset.c
|
colors = preset.c
|
||||||
color = colors[0] if colors else (255, 255, 255)
|
color = colors[0] if colors else (255, 255, 255)
|
||||||
self.fill(self.apply_brightness(color, preset.b))
|
self.fill(self.apply_brightness(color, preset.b))
|
||||||
|
self.show_all()
|
||||||
|
|
||||||
def fill(self, color):
|
def fill(self, color):
|
||||||
for strip in self.strips:
|
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):
|
Usage (from pico/ dir or project root with adjusted paths):
|
||||||
|
|
||||||
mpremote connect <device> cp src/*.py :
|
mpremote connect <device> cp src/*.py :
|
||||||
mpremote connect <device> cp src/patterns/*.py :patterns
|
mpremote connect <device> cp src/patterns/*.py :patterns
|
||||||
mpremote connect <device> cp lib/*.py :
|
mpremote connect <device> cp lib/*.py :
|
||||||
mpremote connect <device> cp test/test_point.py :
|
mpremote connect <device> cp test/test_segments.py :
|
||||||
mpremote connect <device> run test_point.py
|
mpremote connect <device> run test_segments.py
|
||||||
|
|
||||||
This script:
|
This script:
|
||||||
- Instantiates Presets
|
- 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
|
- Selects each one so you can visually confirm the segments
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from presets import Presets, Preset
|
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
|
colors: list of up to 4 (r,g,b) tuples
|
||||||
n_values: list/tuple of 8 ints [n1..n8]
|
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
|
n1, n2, n3, n4, n5, n6, n7, n8 = n_values
|
||||||
data = {
|
data = {
|
||||||
"p": "point",
|
"p": "segments", # pattern key for Segments
|
||||||
"c": cs,
|
"c": cs,
|
||||||
"b": brightness,
|
"b": brightness,
|
||||||
"n1": n1,
|
"n1": n1,
|
||||||
@@ -43,16 +43,16 @@ def make_point_preset(name, colors, n_values, brightness=255):
|
|||||||
"n6": n6,
|
"n6": n6,
|
||||||
"n7": n7,
|
"n7": n7,
|
||||||
"n8": n8,
|
"n8": n8,
|
||||||
# 'a' is not used by point; it's static
|
# 'a' is not used by segments; it's static
|
||||||
}
|
}
|
||||||
return name, Preset(data)
|
return name, Preset(data)
|
||||||
|
|
||||||
|
|
||||||
def show_and_wait(presets, name, preset_obj, wait_ms):
|
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.presets[name] = preset_obj
|
||||||
presets.select(name)
|
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()
|
presets.tick()
|
||||||
|
|
||||||
import utime
|
import utime
|
||||||
@@ -69,46 +69,46 @@ def main():
|
|||||||
|
|
||||||
num_leds = presets.strip_length(0)
|
num_leds = presets.strip_length(0)
|
||||||
if num_leds <= 0:
|
if num_leds <= 0:
|
||||||
print("No strips; aborting point test.")
|
print("No strips; aborting segments test.")
|
||||||
return
|
return
|
||||||
|
|
||||||
print("Starting point pattern test...")
|
print("Starting segments pattern test...")
|
||||||
|
|
||||||
quarter = num_leds // 4
|
quarter = num_leds // 4
|
||||||
half = num_leds // 2
|
half = num_leds // 2
|
||||||
|
|
||||||
point_presets = []
|
segments_presets = []
|
||||||
|
|
||||||
# 1. Single band: first quarter, red
|
# 1. Single band: first quarter, red
|
||||||
point_presets.append(
|
segments_presets.append(
|
||||||
make_point_preset(
|
make_segments_preset(
|
||||||
"point_red_q1",
|
"segments_red_q1",
|
||||||
colors=[(255, 0, 0)],
|
colors=[(255, 0, 0)],
|
||||||
n_values=[0, quarter - 1, 0, -1, 0, -1, 0, -1],
|
n_values=[0, quarter - 1, 0, -1, 0, -1, 0, -1],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# 2. Two bands: red first half, green second half
|
# 2. Two bands: red first half, green second half
|
||||||
point_presets.append(
|
segments_presets.append(
|
||||||
make_point_preset(
|
make_segments_preset(
|
||||||
"point_red_green_halves",
|
"segments_red_green_halves",
|
||||||
colors=[(255, 0, 0), (0, 255, 0)],
|
colors=[(255, 0, 0), (0, 255, 0)],
|
||||||
n_values=[0, half - 1, half, num_leds - 1, 0, -1, 0, -1],
|
n_values=[0, half - 1, half, num_leds - 1, 0, -1, 0, -1],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# 3. Three bands: R, G, B quarters
|
# 3. Three bands: R, G, B quarters
|
||||||
point_presets.append(
|
segments_presets.append(
|
||||||
make_point_preset(
|
make_segments_preset(
|
||||||
"point_rgb_quarters",
|
"segments_rgb_quarters",
|
||||||
colors=[(255, 0, 0), (0, 255, 0), (0, 0, 255)],
|
colors=[(255, 0, 0), (0, 255, 0), (0, 0, 255)],
|
||||||
n_values=[
|
n_values=[
|
||||||
0,
|
0,
|
||||||
quarter - 1, # red
|
quarter - 1, # red
|
||||||
quarter,
|
quarter,
|
||||||
2 * quarter - 1, # green
|
2 * quarter - 1, # green
|
||||||
2 * quarter,
|
2 * quarter,
|
||||||
3 * quarter - 1, # blue
|
3 * quarter - 1, # blue
|
||||||
0,
|
0,
|
||||||
-1,
|
-1,
|
||||||
],
|
],
|
||||||
@@ -116,11 +116,11 @@ def main():
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Show each for ~4 seconds
|
# Show each for ~4 seconds
|
||||||
for name, preset_obj in point_presets:
|
for name, preset_obj in segments_presets:
|
||||||
print("Showing point preset:", name)
|
print("Showing segments preset:", name)
|
||||||
show_and_wait(presets, name, preset_obj, wait_ms=4000)
|
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.select("off")
|
||||||
presets.tick()
|
presets.tick()
|
||||||
|
|
||||||
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