diff --git a/.gitignore b/.gitignore index c3d3a20..6bc7caa 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -settings.json \ No newline at end of file +settings.json +__pycache__/ \ No newline at end of file diff --git a/pico/lib/ws2812.py b/pico/lib/ws2812.py index 05d84ab..fb26524 100644 --- a/pico/lib/ws2812.py +++ b/pico/lib/ws2812.py @@ -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: diff --git a/pico/src/patterns/__init__.py b/pico/src/patterns/__init__.py index 9be8479..d775e93 100644 --- a/pico/src/patterns/__init__.py +++ b/pico/src/patterns/__init__.py @@ -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 diff --git a/pico/src/patterns/backbalance.py b/pico/src/patterns/backbalance.py deleted file mode 100644 index 2fb01e9..0000000 --- a/pico/src/patterns/backbalance.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Placeholder until implemented.""" - -class Backbalance: - def __init__(self, driver): - self.driver = driver - - def run(self, preset): - while True: - yield diff --git a/pico/src/patterns/backbend.py b/pico/src/patterns/backbend.py deleted file mode 100644 index 60a318d..0000000 --- a/pico/src/patterns/backbend.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Placeholder until implemented.""" - -class Backbend: - def __init__(self, driver): - self.driver = driver - - def run(self, preset): - while True: - yield diff --git a/pico/src/patterns/backbendsplit.py b/pico/src/patterns/backbendsplit.py deleted file mode 100644 index ec62857..0000000 --- a/pico/src/patterns/backbendsplit.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Placeholder until implemented.""" - -class Backbendsplit: - def __init__(self, driver): - self.driver = driver - - def run(self, preset): - while True: - yield diff --git a/pico/src/patterns/beat.py b/pico/src/patterns/beat.py deleted file mode 100644 index a54aba3..0000000 --- a/pico/src/patterns/beat.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Placeholder until implemented.""" - -class Beat: - def __init__(self, driver): - self.driver = driver - - def run(self, preset): - while True: - yield diff --git a/pico/src/patterns/crouch.py b/pico/src/patterns/crouch.py deleted file mode 100644 index 8d5909f..0000000 --- a/pico/src/patterns/crouch.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Placeholder until implemented.""" - -class Crouch: - def __init__(self, driver): - self.driver = driver - - def run(self, preset): - while True: - yield diff --git a/pico/src/patterns/dismount.py b/pico/src/patterns/dismount.py deleted file mode 100644 index 166d203..0000000 --- a/pico/src/patterns/dismount.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Placeholder until implemented.""" - -class Dismount: - def __init__(self, driver): - self.driver = driver - - def run(self, preset): - while True: - yield diff --git a/pico/src/patterns/elbowhang.py b/pico/src/patterns/elbowhang.py deleted file mode 100644 index 9ff46af..0000000 --- a/pico/src/patterns/elbowhang.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Placeholder until implemented.""" - -class Elbowhang: - def __init__(self, driver): - self.driver = driver - - def run(self, preset): - while True: - yield diff --git a/pico/src/patterns/elbowhangspin.py b/pico/src/patterns/elbowhangspin.py deleted file mode 100644 index 857fb56..0000000 --- a/pico/src/patterns/elbowhangspin.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Placeholder until implemented.""" - -class Elbowhangspin: - def __init__(self, driver): - self.driver = driver - - def run(self, preset): - while True: - yield diff --git a/pico/src/patterns/elbowhangsplit.py b/pico/src/patterns/elbowhangsplit.py deleted file mode 100644 index 45d61a2..0000000 --- a/pico/src/patterns/elbowhangsplit.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Placeholder until implemented.""" - -class Elbowhangsplit: - def __init__(self, driver): - self.driver = driver - - def run(self, preset): - while True: - yield diff --git a/pico/src/patterns/fluff.py b/pico/src/patterns/fluff.py deleted file mode 100644 index ce372e5..0000000 --- a/pico/src/patterns/fluff.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Placeholder until implemented.""" - -class Fluff: - def __init__(self, driver): - self.driver = driver - - def run(self, preset): - while True: - yield diff --git a/pico/src/patterns/foothang.py b/pico/src/patterns/foothang.py deleted file mode 100644 index 20016e5..0000000 --- a/pico/src/patterns/foothang.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Placeholder until implemented.""" - -class Foothang: - def __init__(self, driver): - self.driver = driver - - def run(self, preset): - while True: - yield diff --git a/pico/src/patterns/frontbalance.py b/pico/src/patterns/frontbalance.py deleted file mode 100644 index 152b062..0000000 --- a/pico/src/patterns/frontbalance.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Placeholder until implemented.""" - -class Frontbalance: - def __init__(self, driver): - self.driver = driver - - def run(self, preset): - while True: - yield diff --git a/pico/src/patterns/invert.py b/pico/src/patterns/invert.py deleted file mode 100644 index 6512917..0000000 --- a/pico/src/patterns/invert.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Placeholder until implemented.""" - -class Invert: - def __init__(self, driver): - self.driver = driver - - def run(self, preset): - while True: - yield diff --git a/pico/src/patterns/invertsplit.py b/pico/src/patterns/invertsplit.py deleted file mode 100644 index 89f65d2..0000000 --- a/pico/src/patterns/invertsplit.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Placeholder until implemented.""" - -class Invertsplit: - def __init__(self, driver): - self.driver = driver - - def run(self, preset): - while True: - yield diff --git a/pico/src/patterns/kneehang.py b/pico/src/patterns/kneehang.py deleted file mode 100644 index d0653e1..0000000 --- a/pico/src/patterns/kneehang.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Placeholder until implemented.""" - -class Kneehang: - def __init__(self, driver): - self.driver = driver - - def run(self, preset): - while True: - yield diff --git a/pico/src/patterns/legswoop.py b/pico/src/patterns/legswoop.py deleted file mode 100644 index fc0446c..0000000 --- a/pico/src/patterns/legswoop.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Placeholder until implemented.""" - -class Legswoop: - def __init__(self, driver): - self.driver = driver - - def run(self, preset): - while True: - yield diff --git a/pico/src/patterns/point.py b/pico/src/patterns/point.py index 68f58b0..0c3d6ae 100644 --- a/pico/src/patterns/point.py +++ b/pico/src/patterns/point.py @@ -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) + # 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 - 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() + diff --git a/pico/src/patterns/scale_test.py b/pico/src/patterns/scale_test.py new file mode 100644 index 0000000..44ad77c --- /dev/null +++ b/pico/src/patterns/scale_test.py @@ -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 + diff --git a/pico/src/patterns/seat.py b/pico/src/patterns/seat.py deleted file mode 100644 index 967b59c..0000000 --- a/pico/src/patterns/seat.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Placeholder until implemented.""" - -class Seat: - def __init__(self, driver): - self.driver = driver - - def run(self, preset): - while True: - yield diff --git a/pico/src/patterns/split.py b/pico/src/patterns/split.py deleted file mode 100644 index 37f82c9..0000000 --- a/pico/src/patterns/split.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Placeholder until implemented.""" - -class Split: - def __init__(self, driver): - self.driver = driver - - def run(self, preset): - while True: - yield diff --git a/pico/src/patterns/straddle.py b/pico/src/patterns/straddle.py deleted file mode 100644 index ddb6f43..0000000 --- a/pico/src/patterns/straddle.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Placeholder until implemented.""" - -class Straddle: - def __init__(self, driver): - self.driver = driver - - def run(self, preset): - while True: - yield diff --git a/pico/src/presets.py b/pico/src/presets.py index df8ba3b..53d81f0 100644 --- a/pico/src/presets.py +++ b/pico/src/presets.py @@ -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() \ No newline at end of file