Compare commits
2 Commits
52a5f0f8c4
...
47c17dba36
| Author | SHA1 | Date | |
|---|---|---|---|
| 47c17dba36 | |||
| e75723e2e7 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
settings.json
|
||||
__pycache__/
|
||||
@@ -28,6 +28,8 @@ class WS2812B:
|
||||
self.brightness = brightness
|
||||
self.invert = invert
|
||||
self.pio_dma = dma.PIO_DMA_Transfer(state_machine+4, state_machine, 8, num_leds*3)
|
||||
self.dma.start_transfer(self.ar)
|
||||
|
||||
|
||||
def show(self, array=None, offset=0):
|
||||
if array is None:
|
||||
|
||||
@@ -7,29 +7,11 @@ from .circle import Circle
|
||||
from .roll import Roll
|
||||
from .calibration import Calibration
|
||||
from .test import Test
|
||||
from .scale_test import ScaleTest
|
||||
from .grab import Grab
|
||||
from .spin import Spin
|
||||
from .lift import Lift
|
||||
from .flare import Flare
|
||||
from .hook import Hook
|
||||
from .invertsplit import Invertsplit
|
||||
from .pose import Pose
|
||||
from .backbalance import Backbalance
|
||||
from .beat import Beat
|
||||
from .crouch import Crouch
|
||||
from .backbendsplit import Backbendsplit
|
||||
from .straddle import Straddle
|
||||
from .frontbalance import Frontbalance
|
||||
from .elbowhang import Elbowhang
|
||||
from .elbowhangspin import Elbowhangspin
|
||||
from .dismount import Dismount
|
||||
from .fluff import Fluff
|
||||
from .elbowhangsplit import Elbowhangsplit
|
||||
from .invert import Invert
|
||||
from .backbend import Backbend
|
||||
from .seat import Seat
|
||||
from .kneehang import Kneehang
|
||||
from .legswoop import Legswoop
|
||||
from .split import Split
|
||||
from .foothang import Foothang
|
||||
from .point import Point
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
"""Placeholder until implemented."""
|
||||
|
||||
class Backbalance:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
while True:
|
||||
yield
|
||||
@@ -1,9 +0,0 @@
|
||||
"""Placeholder until implemented."""
|
||||
|
||||
class Backbend:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
while True:
|
||||
yield
|
||||
@@ -1,9 +0,0 @@
|
||||
"""Placeholder until implemented."""
|
||||
|
||||
class Backbendsplit:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
while True:
|
||||
yield
|
||||
@@ -1,9 +0,0 @@
|
||||
"""Placeholder until implemented."""
|
||||
|
||||
class Beat:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
while True:
|
||||
yield
|
||||
@@ -1,9 +0,0 @@
|
||||
"""Placeholder until implemented."""
|
||||
|
||||
class Crouch:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
while True:
|
||||
yield
|
||||
@@ -1,9 +0,0 @@
|
||||
"""Placeholder until implemented."""
|
||||
|
||||
class Dismount:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
while True:
|
||||
yield
|
||||
@@ -1,9 +0,0 @@
|
||||
"""Placeholder until implemented."""
|
||||
|
||||
class Elbowhang:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
while True:
|
||||
yield
|
||||
@@ -1,9 +0,0 @@
|
||||
"""Placeholder until implemented."""
|
||||
|
||||
class Elbowhangspin:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
while True:
|
||||
yield
|
||||
@@ -1,9 +0,0 @@
|
||||
"""Placeholder until implemented."""
|
||||
|
||||
class Elbowhangsplit:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
while True:
|
||||
yield
|
||||
@@ -1,9 +0,0 @@
|
||||
"""Placeholder until implemented."""
|
||||
|
||||
class Fluff:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
while True:
|
||||
yield
|
||||
@@ -1,9 +0,0 @@
|
||||
"""Placeholder until implemented."""
|
||||
|
||||
class Foothang:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
while True:
|
||||
yield
|
||||
@@ -1,9 +0,0 @@
|
||||
"""Placeholder until implemented."""
|
||||
|
||||
class Frontbalance:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
while True:
|
||||
yield
|
||||
@@ -1,9 +0,0 @@
|
||||
"""Placeholder until implemented."""
|
||||
|
||||
class Invert:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
while True:
|
||||
yield
|
||||
@@ -1,9 +0,0 @@
|
||||
"""Placeholder until implemented."""
|
||||
|
||||
class Invertsplit:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
while True:
|
||||
yield
|
||||
@@ -1,9 +0,0 @@
|
||||
"""Placeholder until implemented."""
|
||||
|
||||
class Kneehang:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
while True:
|
||||
yield
|
||||
@@ -1,9 +0,0 @@
|
||||
"""Placeholder until implemented."""
|
||||
|
||||
class Legswoop:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
while True:
|
||||
yield
|
||||
@@ -3,66 +3,16 @@ class Point:
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
"""
|
||||
Point pattern: color bands defined by n ranges.
|
||||
|
||||
- n1–n2: LEDs with color1 (c[0])
|
||||
- n3–n4: LEDs with color2 (c[1])
|
||||
- n5–n6: LEDs with color3 (c[2])
|
||||
- n7–n8: LEDs with color4 (c[3])
|
||||
|
||||
All indices are along the logical ring (driver.n), inclusive ranges.
|
||||
"""
|
||||
num_leds = self.driver.num_leds
|
||||
|
||||
# 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
|
||||
c1 = self.driver.apply_brightness(colors[0], preset.b)
|
||||
c2 = self.driver.apply_brightness(colors[1], preset.b)
|
||||
c3 = self.driver.apply_brightness(colors[2], preset.b)
|
||||
c4 = self.driver.apply_brightness(colors[3], preset.b)
|
||||
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
|
||||
def norm_range(a, b):
|
||||
a = int(a)
|
||||
b = int(b)
|
||||
if a > b:
|
||||
a, b = b, a
|
||||
if b < 0 or a >= num_leds:
|
||||
return None
|
||||
a = max(0, a)
|
||||
b = min(num_leds - 1, b)
|
||||
if a > b:
|
||||
return None
|
||||
return a, b
|
||||
|
||||
ranges = []
|
||||
r1 = norm_range(getattr(preset, "n1", 0), getattr(preset, "n2", -1))
|
||||
if r1:
|
||||
ranges.append((r1[0], r1[1], c1))
|
||||
r2 = norm_range(getattr(preset, "n3", 0), getattr(preset, "n4", -1))
|
||||
if r2:
|
||||
ranges.append((r2[0], r2[1], c2))
|
||||
r3 = norm_range(getattr(preset, "n5", 0), getattr(preset, "n6", -1))
|
||||
if r3:
|
||||
ranges.append((r3[0], r3[1], c3))
|
||||
r4 = norm_range(getattr(preset, "n7", 0), getattr(preset, "n8", -1))
|
||||
if r4:
|
||||
ranges.append((r4[0], r4[1], c4))
|
||||
|
||||
# Static draw: last range wins on overlaps
|
||||
for i in range(num_leds):
|
||||
color = (0, 0, 0)
|
||||
for start, end, c in ranges:
|
||||
if start <= i <= end:
|
||||
color = c
|
||||
self.driver.n[i] = color
|
||||
self.driver.n.write()
|
||||
|
||||
while True:
|
||||
yield
|
||||
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()
|
||||
|
||||
|
||||
56
pico/src/patterns/scale_test.py
Normal file
56
pico/src/patterns/scale_test.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import utime
|
||||
|
||||
|
||||
RED = (255, 0, 0)
|
||||
|
||||
|
||||
class ScaleTest:
|
||||
"""
|
||||
Animated test for the scale() helper.
|
||||
|
||||
A single red pixel moves along the reference strip (strip 0). For each other
|
||||
strip, the position is mapped using:
|
||||
|
||||
n2 = scale(l1, l2, n1)
|
||||
|
||||
so that all lit pixels stay aligned by proportional position along the strips.
|
||||
"""
|
||||
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
strips = self.driver.strips
|
||||
if not strips:
|
||||
return
|
||||
|
||||
src_strip_idx = 0
|
||||
l1 = self.driver.strip_length(src_strip_idx)
|
||||
if l1 <= 0:
|
||||
return
|
||||
|
||||
step = self.driver.step
|
||||
delay_ms = max(1, int(getattr(preset, "d", 30) or 30))
|
||||
last_update = utime.ticks_ms()
|
||||
color = self.driver.apply_brightness(RED, getattr(preset, "b", 255))
|
||||
|
||||
while True:
|
||||
now = utime.ticks_ms()
|
||||
if utime.ticks_diff(now, last_update) >= delay_ms:
|
||||
n1 = step % l1
|
||||
|
||||
# Clear all strips
|
||||
for strip in strips:
|
||||
strip.fill((0, 0, 0))
|
||||
|
||||
# Light mapped position on each strip using Presets.set/show
|
||||
for dst_strip_idx, _ in enumerate(strips):
|
||||
self.driver.set(dst_strip_idx, n1, color)
|
||||
self.driver.show(dst_strip_idx)
|
||||
|
||||
step += 1
|
||||
self.driver.step = step
|
||||
last_update = now
|
||||
|
||||
yield
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
"""Placeholder until implemented."""
|
||||
|
||||
class Seat:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
while True:
|
||||
yield
|
||||
@@ -1,9 +0,0 @@
|
||||
"""Placeholder until implemented."""
|
||||
|
||||
class Split:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
while True:
|
||||
yield
|
||||
@@ -1,9 +0,0 @@
|
||||
"""Placeholder until implemented."""
|
||||
|
||||
class Straddle:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
|
||||
def run(self, preset):
|
||||
while True:
|
||||
yield
|
||||
@@ -3,11 +3,7 @@ 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, Invertsplit, Pose,
|
||||
Backbalance, Beat, Crouch, Backbendsplit, Straddle,
|
||||
Frontbalance, Elbowhang, Elbowhangspin, Dismount, Fluff,
|
||||
Elbowhangsplit, Invert, Backbend, Seat, Kneehang,
|
||||
Legswoop, Split, Foothang, Point,
|
||||
Grab, Spin, Lift, Flare, Hook, Pose, Point,
|
||||
)
|
||||
import json
|
||||
|
||||
@@ -23,53 +19,9 @@ STRIP_CONFIG = (
|
||||
(7, 291, 290 // 2-1), # 8
|
||||
)
|
||||
|
||||
class StripRing:
|
||||
"""Treat multiple WS2812B strips as one logical ring. Equal weight per strip (scale by strip length)."""
|
||||
|
||||
def __init__(self, strips):
|
||||
self.strips = strips
|
||||
self._cumul = [0]
|
||||
for s in strips:
|
||||
self._cumul.append(self._cumul[-1] + s.num_leds)
|
||||
self.num_leds = self._cumul[-1]
|
||||
self.num_strips = len(strips)
|
||||
|
||||
def _strip_and_local(self, i):
|
||||
if i < 0 or i >= self.num_leds:
|
||||
raise IndexError(i)
|
||||
for s in range(len(self.strips)):
|
||||
if i < self._cumul[s + 1]:
|
||||
return s, i - self._cumul[s]
|
||||
return len(self.strips) - 1, i - self._cumul[-2]
|
||||
|
||||
def __setitem__(self, i, color):
|
||||
s, local = self._strip_and_local(i)
|
||||
self.strips[s].set(local, color)
|
||||
|
||||
def fill(self, color):
|
||||
for s in self.strips:
|
||||
s.fill(color)
|
||||
|
||||
def write(self):
|
||||
for s in self.strips:
|
||||
s.show()
|
||||
|
||||
def position(self, i):
|
||||
"""Normalized position 0..1 with equal span per strip (longer strips get same angular span)."""
|
||||
s, local = self._strip_and_local(i)
|
||||
strip_len = self.strips[s].num_leds
|
||||
frac = (local / strip_len) if strip_len else 0
|
||||
return (s + frac) / self.num_strips
|
||||
|
||||
def segment(self, i):
|
||||
"""Segment index 0..num_strips-1 (strip index) so segments align with physical strips."""
|
||||
s, _ = self._strip_and_local(i)
|
||||
return s
|
||||
|
||||
|
||||
class Presets:
|
||||
def __init__(self):
|
||||
|
||||
self.scale_map = []
|
||||
self.strips = []
|
||||
self.strip_midpoints = [] # midpoint LED index per strip (from STRIP_CONFIG)
|
||||
|
||||
@@ -80,9 +32,9 @@ class Presets:
|
||||
self.strip_midpoints.append(mid)
|
||||
self.strips.append(WS2812B(num_leds, pin, state_machine, brightness=1.0))
|
||||
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())
|
||||
self.n = StripRing(self.strips)
|
||||
self.num_leds = self.n.num_leds
|
||||
# WS2812B with brightness=1.0 so Presets.apply_brightness() does all scaling (NeoPixel drop-in)
|
||||
self.step = 0
|
||||
# Remember which strip was last used as the roll head (for flare, etc.)
|
||||
@@ -112,26 +64,7 @@ class Presets:
|
||||
"lift": Lift(self).run,
|
||||
"flare": Flare(self).run,
|
||||
"hook": Hook(self).run,
|
||||
"invertsplit": Invertsplit(self).run,
|
||||
"pose": Pose(self).run,
|
||||
"backbalance": Backbalance(self).run,
|
||||
"beat": Beat(self).run,
|
||||
"crouch": Crouch(self).run,
|
||||
"backbendsplit": Backbendsplit(self).run,
|
||||
"straddle": Straddle(self).run,
|
||||
"frontbalance": Frontbalance(self).run,
|
||||
"elbowhang": Elbowhang(self).run,
|
||||
"elbowhangspin": Elbowhangspin(self).run,
|
||||
"dismount": Dismount(self).run,
|
||||
"fluff": Fluff(self).run,
|
||||
"elbowhangsplit": Elbowhangsplit(self).run,
|
||||
"invert": Invert(self).run,
|
||||
"backbend": Backbend(self).run,
|
||||
"seat": Seat(self).run,
|
||||
"kneehang": Kneehang(self).run,
|
||||
"legswoop": Legswoop(self).run,
|
||||
"split": Split(self).run,
|
||||
"foothang": Foothang(self).run,
|
||||
"point": Point(self).run,
|
||||
}
|
||||
|
||||
@@ -143,40 +76,6 @@ class Presets:
|
||||
return self.strips[strip_idx].num_leds
|
||||
return 0
|
||||
|
||||
def strip_index_to_angle(self, strip_idx, index):
|
||||
"""Map an LED index on a given strip to a normalized angle 0..1.
|
||||
|
||||
This accounts for different strip lengths so that the same angle value
|
||||
corresponds to the same physical angle on all concentric strips.
|
||||
"""
|
||||
n = self.strip_length(strip_idx)
|
||||
if n <= 0:
|
||||
return 0.0
|
||||
index = int(index) % n
|
||||
return index / float(n)
|
||||
|
||||
def strip_angle_to_index(self, strip_idx, angle):
|
||||
"""Map a normalized angle 0..1 to an LED index on a given strip.
|
||||
|
||||
Use this when you want patterns to align by angle instead of raw index,
|
||||
despite strips having different circumferences.
|
||||
"""
|
||||
n = self.strip_length(strip_idx)
|
||||
if n <= 0:
|
||||
return 0
|
||||
# Normalize angle into [0,1)
|
||||
angle = float(angle)
|
||||
angle = angle - int(angle)
|
||||
if angle < 0.0:
|
||||
angle += 1.0
|
||||
return int(angle * n) % n
|
||||
|
||||
def remap_strip_index(self, src_strip_idx, dst_strip_idx, src_index):
|
||||
"""Remap an index from one strip to another so they align by angle."""
|
||||
angle = self.strip_index_to_angle(src_strip_idx, src_index)
|
||||
return self.strip_angle_to_index(dst_strip_idx, angle)
|
||||
|
||||
|
||||
def save(self):
|
||||
"""Save the presets to a file."""
|
||||
with open("presets.json", "w") as f:
|
||||
@@ -251,14 +150,6 @@ class Presets:
|
||||
self.selected = preset_name
|
||||
return True
|
||||
|
||||
def update_num_leds(self, pin, num_leds):
|
||||
num_leds = int(num_leds)
|
||||
if isinstance(pin, Pin):
|
||||
self.n = WS2812B(pin, num_leds)
|
||||
else:
|
||||
self.n = WS2812B(num_leds, int(pin), 0, brightness=1.0)
|
||||
self.num_leds = num_leds
|
||||
|
||||
def apply_brightness(self, color, brightness_override=None):
|
||||
# Combine per-preset brightness (override) with global brightness self.b
|
||||
local = brightness_override if brightness_override is not None else 255
|
||||
@@ -266,12 +157,6 @@ class Presets:
|
||||
effective_brightness = int(local * self.b / 255)
|
||||
return tuple(int(c * effective_brightness / 255) for c in color)
|
||||
|
||||
def fill(self, color=None):
|
||||
fill_color = color if color is not None else (0, 0, 0)
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = fill_color
|
||||
self.n.write()
|
||||
|
||||
def off(self, preset=None):
|
||||
self.fill((0, 0, 0))
|
||||
|
||||
@@ -279,3 +164,27 @@ class Presets:
|
||||
colors = preset.c
|
||||
color = colors[0] if colors else (255, 255, 255)
|
||||
self.fill(self.apply_brightness(color, preset.b))
|
||||
|
||||
def fill(self, color):
|
||||
for strip in self.strips:
|
||||
strip.fill(color)
|
||||
|
||||
def fill_n(self, color, n1, n2):
|
||||
for i in range(n1, n2):
|
||||
for strip_idx in range(8):
|
||||
self.set(strip_idx, i, color)
|
||||
|
||||
|
||||
def set(self, strip, index, color):
|
||||
if index >= self.strips[0].num_leds:
|
||||
return False
|
||||
self.strips[strip].set(self.scale_map[strip][index], color)
|
||||
return True
|
||||
|
||||
def create_scale_map(self, num_leds):
|
||||
ref_len = STRIP_CONFIG[0][1]
|
||||
return [int(i * num_leds / ref_len) for i in range(ref_len)]
|
||||
|
||||
def show_all(self):
|
||||
for strip in self.strips:
|
||||
strip.show()
|
||||
52
pico/test/test_fill_n.py
Normal file
52
pico/test/test_fill_n.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""
|
||||
On-device test for Presets.fill_n() 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_fill_n.py :
|
||||
mpremote connect <device> run test_fill_n.py
|
||||
|
||||
This script:
|
||||
- Instantiates Presets
|
||||
- Calls fill_n() with a simple range
|
||||
- Lets you visually confirm that all strips show the same proportional segment
|
||||
and that equal-length strip pairs have identical lit indices.
|
||||
"""
|
||||
|
||||
from presets import Presets
|
||||
|
||||
|
||||
def main():
|
||||
presets = Presets()
|
||||
presets.load()
|
||||
|
||||
# Choose a simple test range on the reference strip (strip 0).
|
||||
ref_len = presets.strip_length(0)
|
||||
if ref_len <= 0:
|
||||
print("No strips or invalid length; aborting fill_n test.")
|
||||
return
|
||||
|
||||
# Use a central segment so it's easy to see.
|
||||
start = ref_len // 4
|
||||
end = 3 * ref_len // 4
|
||||
print("Running fill_n test from", start, "to", end, "on reference strip 0.")
|
||||
|
||||
color = (0, 50, 0) # dim green
|
||||
|
||||
# First, clear everything
|
||||
for strip in presets.strips:
|
||||
strip.fill((0, 0, 0))
|
||||
strip.show()
|
||||
|
||||
# Apply fill_n, which will use scale() internally.
|
||||
presets.fill_n(color, start, end)
|
||||
|
||||
print("fill_n test applied; visually inspect strips.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
130
pico/test/test_point.py
Normal file
130
pico/test/test_point.py
Normal file
@@ -0,0 +1,130 @@
|
||||
"""
|
||||
On-device test for the Point pattern using mpremote.
|
||||
|
||||
Usage (from pico/ dir or project root with adjusted paths):
|
||||
|
||||
mpremote connect <device> cp src/*.py :
|
||||
mpremote connect <device> cp src/patterns/*.py :patterns
|
||||
mpremote connect <device> cp lib/*.py :
|
||||
mpremote connect <device> cp test/test_point.py :
|
||||
mpremote connect <device> run test_point.py
|
||||
|
||||
This script:
|
||||
- Instantiates Presets
|
||||
- Creates a few in-memory 'point' 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):
|
||||
"""
|
||||
Helper to build a Preset for the 'point' pattern.
|
||||
|
||||
colors: list of up to 4 (r,g,b) tuples
|
||||
n_values: list/tuple of 8 ints [n1..n8]
|
||||
"""
|
||||
# Pad or trim colors to 4 entries
|
||||
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": "point",
|
||||
"c": cs,
|
||||
"b": brightness,
|
||||
"n1": n1,
|
||||
"n2": n2,
|
||||
"n3": n3,
|
||||
"n4": n4,
|
||||
"n5": n5,
|
||||
"n6": n6,
|
||||
"n7": n7,
|
||||
"n8": n8,
|
||||
# 'a' is not used by point; 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."""
|
||||
presets.presets[name] = preset_obj
|
||||
presets.select(name)
|
||||
# Point 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:
|
||||
# Keep ticking in case other logic ever depends on it
|
||||
presets.tick()
|
||||
|
||||
|
||||
def main():
|
||||
presets = Presets()
|
||||
presets.load()
|
||||
|
||||
num_leds = presets.strip_length(0)
|
||||
if num_leds <= 0:
|
||||
print("No strips; aborting point test.")
|
||||
return
|
||||
|
||||
print("Starting point pattern test...")
|
||||
|
||||
quarter = num_leds // 4
|
||||
half = num_leds // 2
|
||||
|
||||
point_presets = []
|
||||
|
||||
# 1. Single band: first quarter, red
|
||||
point_presets.append(
|
||||
make_point_preset(
|
||||
"point_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",
|
||||
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",
|
||||
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,
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
# Show each for ~4 seconds
|
||||
for name, preset_obj in point_presets:
|
||||
print("Showing point preset:", name)
|
||||
show_and_wait(presets, name, preset_obj, wait_ms=4000)
|
||||
|
||||
print("Point pattern test finished. Turning off LEDs.")
|
||||
presets.select("off")
|
||||
presets.tick()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
118
pico/test/test_roll.py
Normal file
118
pico/test/test_roll.py
Normal file
@@ -0,0 +1,118 @@
|
||||
"""
|
||||
On-device test for the Roll 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_roll.py :
|
||||
mpremote connect <device> run test_roll.py
|
||||
|
||||
This script:
|
||||
- Instantiates Presets
|
||||
- Creates a few in-memory roll 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_roll_preset(name, color1, color2, n1=0, n2=0, n3=0, n4=0, delay_ms=50, brightness=255):
|
||||
"""Helper to build a Preset dict for the roll pattern."""
|
||||
data = {
|
||||
"p": "roll",
|
||||
"c": [color1, color2],
|
||||
"b": brightness,
|
||||
"d": delay_ms,
|
||||
"n1": n1,
|
||||
"n2": n2,
|
||||
"n3": n3,
|
||||
"n4": n4,
|
||||
"a": True, # animated
|
||||
}
|
||||
return name, Preset(data)
|
||||
|
||||
|
||||
def run_preset(presets, name, preset_obj, duration_ms):
|
||||
"""Run a given roll preset for duration_ms using the existing tick loop."""
|
||||
presets.presets[name] = preset_obj
|
||||
presets.select(name)
|
||||
|
||||
start = utime.ticks_ms()
|
||||
while utime.ticks_diff(utime.ticks_ms(), start) < duration_ms:
|
||||
presets.tick()
|
||||
|
||||
|
||||
def main():
|
||||
presets = Presets()
|
||||
presets.load()
|
||||
|
||||
print("Starting roll pattern test...")
|
||||
|
||||
ref_len = presets.strip_length(0)
|
||||
# Use some margins based on strip length to show different bands.
|
||||
quarter = ref_len // 4
|
||||
|
||||
# Define a few different roll presets:
|
||||
roll_presets = []
|
||||
|
||||
# 1. Full-strip, white -> off, clockwise, medium speed
|
||||
roll_presets.append(
|
||||
make_roll_preset(
|
||||
"roll_full_cw",
|
||||
color1=(255, 255, 255),
|
||||
color2=(0, 0, 0),
|
||||
n1=0,
|
||||
n2=0,
|
||||
n3=2, # 2 rotations then stop
|
||||
n4=0, # clockwise
|
||||
delay_ms=40,
|
||||
brightness=255,
|
||||
)
|
||||
)
|
||||
|
||||
# 2. Inner band only, red -> blue, clockwise, slower
|
||||
roll_presets.append(
|
||||
make_roll_preset(
|
||||
"roll_inner_band",
|
||||
color1=(255, 0, 0),
|
||||
color2=(0, 0, 255),
|
||||
n1=quarter, # start margin
|
||||
n2=quarter, # end margin
|
||||
n3=2,
|
||||
n4=0,
|
||||
delay_ms=60,
|
||||
brightness=255,
|
||||
)
|
||||
)
|
||||
|
||||
# 3. Full-strip, green -> off, counter-clockwise, faster
|
||||
roll_presets.append(
|
||||
make_roll_preset(
|
||||
"roll_full_ccw",
|
||||
color1=(0, 255, 0),
|
||||
color2=(0, 0, 0),
|
||||
n1=0,
|
||||
n2=0,
|
||||
n3=2,
|
||||
n4=1, # counter-clockwise
|
||||
delay_ms=30,
|
||||
brightness=255,
|
||||
)
|
||||
)
|
||||
|
||||
# Run each roll preset for about 5 seconds
|
||||
for name, preset_obj in roll_presets:
|
||||
print("Running roll preset:", name)
|
||||
run_preset(presets, name, preset_obj, duration_ms=5000)
|
||||
|
||||
print("Roll pattern test finished. Turning off LEDs.")
|
||||
presets.select("off")
|
||||
presets.tick()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
100
pico/test/test_scale.py
Normal file
100
pico/test/test_scale.py
Normal file
@@ -0,0 +1,100 @@
|
||||
"""
|
||||
Test the Presets.scale() helper on-device with mpremote.
|
||||
|
||||
Usage (from project root):
|
||||
|
||||
mpremote connect <device> cp pico/src/*.py : &&
|
||||
mpremote connect <device> cp pico/src/patterns/*.py :patterns &&
|
||||
mpremote connect <device> cp pico/lib/*.py : &&
|
||||
mpremote connect <device> cp tests/test_scale.py : &&
|
||||
mpremote connect <device> run test_scale.py
|
||||
|
||||
This script:
|
||||
- Creates a minimal Presets instance
|
||||
- Runs a few numeric test cases for scale()
|
||||
- Optionally displays a short visual check on the LEDs
|
||||
"""
|
||||
|
||||
from presets import Presets
|
||||
|
||||
|
||||
def numeric_tests(presets):
|
||||
"""
|
||||
Numeric sanity checks for scale() using the actual strip config.
|
||||
|
||||
We treat strip 0 as the reference and print the mapped indices for
|
||||
a few positions on each other strip.
|
||||
"""
|
||||
print("Numeric scale() tests (from strip 0):")
|
||||
ref_len = presets.strip_length(0)
|
||||
if ref_len <= 0:
|
||||
print(" strip 0 length <= 0; skipping numeric tests.")
|
||||
return
|
||||
|
||||
test_positions = [0, ref_len // 2, ref_len - 1]
|
||||
for pos in test_positions:
|
||||
print(" pos on strip 0:", pos)
|
||||
for dst_idx in range(len(presets.strips)):
|
||||
dst_len = presets.strip_length(dst_idx)
|
||||
if dst_len <= 0:
|
||||
continue
|
||||
n2 = presets.scale(dst_idx, pos)
|
||||
print(" -> strip", dst_idx, "len", dst_len, "pos", n2)
|
||||
|
||||
|
||||
def visual_test(presets):
|
||||
"""
|
||||
Simple visual test:
|
||||
- Use strip 0 as reference
|
||||
- Move a pixel along strip 0
|
||||
- Map position to all other strips with scale()
|
||||
"""
|
||||
import utime
|
||||
|
||||
strips = presets.strips
|
||||
if not strips:
|
||||
print("No strips available for visual test.")
|
||||
return
|
||||
|
||||
src_strip_idx = 0
|
||||
l1 = presets.strip_length(src_strip_idx)
|
||||
if l1 <= 0:
|
||||
print("strip_length(0) <= 0; aborting visual test.")
|
||||
return
|
||||
|
||||
color = (50, 0, 0) # dim red so it doesn't blind you
|
||||
|
||||
# Run once across the full length of the reference strip,
|
||||
# jumping 10 LEDs at a time.
|
||||
step_size = 10
|
||||
steps = (l1 + step_size - 1) // step_size
|
||||
print("Starting visual scale() test with 10-LED jumps:", steps, "steps...")
|
||||
for step in range(steps):
|
||||
n1 = (step * step_size) % l1
|
||||
|
||||
# Clear all strips
|
||||
for strip in strips:
|
||||
strip.fill((0, 0, 0))
|
||||
|
||||
# Light mapped position on each strip using Presets.set/show
|
||||
for dst_strip_idx, _ in enumerate(strips):
|
||||
presets.set(dst_strip_idx, n1, color)
|
||||
presets.show(dst_strip_idx)
|
||||
|
||||
print("Visual test finished.")
|
||||
|
||||
|
||||
def main():
|
||||
presets = Presets()
|
||||
presets.load()
|
||||
numeric_tests(presets)
|
||||
# Comment this in/out depending on whether you want the LEDs to run:
|
||||
try:
|
||||
visual_test(presets)
|
||||
except Exception as e:
|
||||
print("Visual test error:", e)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user