From 74b4b495f9fd92229686b84c940a5b616c779f1b Mon Sep 17 00:00:00 2001 From: Jimmy Date: Thu, 23 Apr 2026 20:10:01 +1200 Subject: [PATCH] feat(patterns): add expanded animation pack with smoke tests Add a broad set of new pattern modules and matching pattern smoke scripts so the new effects can be validated directly on-device. --- src/patterns/aurora.py | 31 +++++++++++++ src/patterns/bar_graph.py | 21 +++++++++ src/patterns/breathing_dual.py | 40 ++++++++++++++++ src/patterns/clock_sweep.py | 33 ++++++++++++++ src/patterns/comet_dual.py | 43 +++++++++++++++++ src/patterns/fireflies.py | 34 ++++++++++++++ src/patterns/gradient_scroll.py | 57 +++++++++++++++++++++++ src/patterns/heartbeat.py | 25 ++++++++++ src/patterns/marquee.py | 30 ++++++++++++ src/patterns/meteor_rain.py | 62 +++++++++++++++++++++++++ src/patterns/orbit.py | 30 ++++++++++++ src/patterns/palette_morph.py | 76 +++++++++++++++++++++++++++++++ src/patterns/plasma.py | 39 ++++++++++++++++ src/patterns/rain_drops.py | 40 ++++++++++++++++ src/patterns/scanner.py | 66 +++++++++++++++++++++++++++ src/patterns/segment_chase.py | 44 ++++++++++++++++++ src/patterns/snowfall.py | 36 +++++++++++++++ src/patterns/sparkle_trail.py | 31 +++++++++++++ src/patterns/strobe_burst.py | 24 ++++++++++ src/patterns/wave.py | 32 +++++++++++++ tests/patterns/aurora.py | 40 ++++++++++++++++ tests/patterns/bar_graph.py | 40 ++++++++++++++++ tests/patterns/breathing_dual.py | 40 ++++++++++++++++ tests/patterns/clock_sweep.py | 40 ++++++++++++++++ tests/patterns/comet_dual.py | 40 ++++++++++++++++ tests/patterns/fireflies.py | 40 ++++++++++++++++ tests/patterns/gradient_scroll.py | 39 ++++++++++++++++ tests/patterns/heartbeat.py | 40 ++++++++++++++++ tests/patterns/marquee.py | 40 ++++++++++++++++ tests/patterns/meteor_rain.py | 41 +++++++++++++++++ tests/patterns/orbit.py | 40 ++++++++++++++++ tests/patterns/palette_morph.py | 40 ++++++++++++++++ tests/patterns/plasma.py | 40 ++++++++++++++++ tests/patterns/rain_drops.py | 40 ++++++++++++++++ tests/patterns/scanner.py | 40 ++++++++++++++++ tests/patterns/segment_chase.py | 40 ++++++++++++++++ tests/patterns/snowfall.py | 40 ++++++++++++++++ tests/patterns/sparkle_trail.py | 40 ++++++++++++++++ tests/patterns/strobe_burst.py | 40 ++++++++++++++++ tests/patterns/wave.py | 40 ++++++++++++++++ 40 files changed, 1594 insertions(+) create mode 100644 src/patterns/aurora.py create mode 100644 src/patterns/bar_graph.py create mode 100644 src/patterns/breathing_dual.py create mode 100644 src/patterns/clock_sweep.py create mode 100644 src/patterns/comet_dual.py create mode 100644 src/patterns/fireflies.py create mode 100644 src/patterns/gradient_scroll.py create mode 100644 src/patterns/heartbeat.py create mode 100644 src/patterns/marquee.py create mode 100644 src/patterns/meteor_rain.py create mode 100644 src/patterns/orbit.py create mode 100644 src/patterns/palette_morph.py create mode 100644 src/patterns/plasma.py create mode 100644 src/patterns/rain_drops.py create mode 100644 src/patterns/scanner.py create mode 100644 src/patterns/segment_chase.py create mode 100644 src/patterns/snowfall.py create mode 100644 src/patterns/sparkle_trail.py create mode 100644 src/patterns/strobe_burst.py create mode 100644 src/patterns/wave.py create mode 100644 tests/patterns/aurora.py create mode 100644 tests/patterns/bar_graph.py create mode 100644 tests/patterns/breathing_dual.py create mode 100644 tests/patterns/clock_sweep.py create mode 100644 tests/patterns/comet_dual.py create mode 100644 tests/patterns/fireflies.py create mode 100644 tests/patterns/gradient_scroll.py create mode 100644 tests/patterns/heartbeat.py create mode 100644 tests/patterns/marquee.py create mode 100644 tests/patterns/meteor_rain.py create mode 100644 tests/patterns/orbit.py create mode 100644 tests/patterns/palette_morph.py create mode 100644 tests/patterns/plasma.py create mode 100644 tests/patterns/rain_drops.py create mode 100644 tests/patterns/scanner.py create mode 100644 tests/patterns/segment_chase.py create mode 100644 tests/patterns/snowfall.py create mode 100644 tests/patterns/sparkle_trail.py create mode 100644 tests/patterns/strobe_burst.py create mode 100644 tests/patterns/wave.py diff --git a/src/patterns/aurora.py b/src/patterns/aurora.py new file mode 100644 index 0000000..8dca687 --- /dev/null +++ b/src/patterns/aurora.py @@ -0,0 +1,31 @@ +import utime + + +class Aurora: + def __init__(self, driver): + self.driver = driver + + def run(self, preset): + colors = preset.c if preset.c else [(40, 200, 140), (80, 120, 255), (160, 80, 220)] + bands = max(1, int(preset.n1) if int(preset.n1) > 0 else 3) + shimmer = max(0, min(255, int(preset.n2) if int(preset.n2) > 0 else 40)) + phase = self.driver.step % 256 + last = utime.ticks_ms() + while True: + d = max(1, int(preset.d)) + now = utime.ticks_ms() + if utime.ticks_diff(now, last) >= d: + for i in range(self.driver.num_leds): + idx = ((i * bands) // max(1, self.driver.num_leds) + (phase // 32)) % len(colors) + c = self.driver.apply_brightness(colors[idx], preset.b) + w = (255 - abs(128 - ((i * 8 + phase) & 255)) * 2) + w = max(0, min(255, w + shimmer)) + self.driver.n[i] = ((c[0]*w)//255, (c[1]*w)//255, (c[2]*w)//255) + self.driver.n.write() + phase = (phase + 1) & 255 + self.driver.step = phase + last = utime.ticks_add(last, d) + if not preset.a: + yield + return + yield diff --git a/src/patterns/bar_graph.py b/src/patterns/bar_graph.py new file mode 100644 index 0000000..72738d2 --- /dev/null +++ b/src/patterns/bar_graph.py @@ -0,0 +1,21 @@ +import utime + + +class BarGraph: + def __init__(self, driver): + self.driver = driver + + def run(self, preset): + colors = preset.c if preset.c else [(0, 255, 0), (255, 80, 0)] + level = max(0, min(100, int(preset.n1) if int(preset.n1) >= 0 else 50)) + target = (self.driver.num_leds * level) // 100 + lit = self.driver.apply_brightness(colors[0], preset.b) + unlit = self.driver.apply_brightness(colors[1] if len(colors) > 1 else (0, 0, 0), preset.b) + while True: + for i in range(self.driver.num_leds): + self.driver.n[i] = lit if i < target else unlit + self.driver.n.write() + yield + if not preset.a: + return + utime.sleep_ms(max(1, int(preset.d))) diff --git a/src/patterns/breathing_dual.py b/src/patterns/breathing_dual.py new file mode 100644 index 0000000..a5a5af5 --- /dev/null +++ b/src/patterns/breathing_dual.py @@ -0,0 +1,40 @@ +import utime + + +class BreathingDual: + def __init__(self, driver): + self.driver = driver + + def run(self, preset): + colors = preset.c if preset.c else [(255, 0, 140), (0, 120, 255)] + phase_offset = max(0, min(255, int(preset.n1))) + ease = max(1, int(preset.n2) if int(preset.n2) > 0 else 1) + phase = self.driver.step % 256 + last = utime.ticks_ms() + while True: + d = max(1, int(preset.d)) + now = utime.ticks_ms() + if utime.ticks_diff(now, last) >= d: + p1 = phase + p2 = (phase + phase_offset) & 255 + t1 = 255 - abs(128 - p1) * 2 + t2 = 255 - abs(128 - p2) * 2 + if ease > 1: + t1 = (t1 * t1) // 255 + t2 = (t2 * t2) // 255 + c1 = self.driver.apply_brightness(colors[0], preset.b) + c2 = self.driver.apply_brightness(colors[1 % len(colors)] if len(colors) > 1 else colors[0], preset.b) + half = self.driver.num_leds // 2 + for i in range(self.driver.num_leds): + if i < half: + self.driver.n[i] = ((c1[0]*t1)//255, (c1[1]*t1)//255, (c1[2]*t1)//255) + else: + self.driver.n[i] = ((c2[0]*t2)//255, (c2[1]*t2)//255, (c2[2]*t2)//255) + self.driver.n.write() + phase = (phase + 2) & 255 + self.driver.step = phase + last = utime.ticks_add(last, d) + if not preset.a: + yield + return + yield diff --git a/src/patterns/clock_sweep.py b/src/patterns/clock_sweep.py new file mode 100644 index 0000000..17b4dd5 --- /dev/null +++ b/src/patterns/clock_sweep.py @@ -0,0 +1,33 @@ +import utime + + +class ClockSweep: + def __init__(self, driver): + self.driver = driver + + def run(self, preset): + colors = preset.c if preset.c else [(255, 255, 255), (60, 60, 60)] + width = max(1, int(preset.n1) if int(preset.n1) > 0 else 1) + marker = max(0, int(preset.n2) if int(preset.n2) > 0 else 0) + pos = self.driver.step % max(1, self.driver.num_leds) + last = utime.ticks_ms() + while True: + d = max(1, int(preset.d)) + now = utime.ticks_ms() + if utime.ticks_diff(now, last) >= d: + bg = self.driver.apply_brightness(colors[1] if len(colors) > 1 else (0, 0, 0), preset.b) + fg = self.driver.apply_brightness(colors[0], preset.b) + for i in range(self.driver.num_leds): + self.driver.n[i] = bg + if marker > 0 and i % marker == 0: + self.driver.n[i] = ((bg[0]*2)//3, (bg[1]*2)//3, (bg[2]*2)//3) + for w in range(width): + self.driver.n[(pos + w) % self.driver.num_leds] = fg + self.driver.n.write() + pos = (pos + 1) % max(1, self.driver.num_leds) + self.driver.step = pos + last = utime.ticks_add(last, d) + if not preset.a: + yield + return + yield diff --git a/src/patterns/comet_dual.py b/src/patterns/comet_dual.py new file mode 100644 index 0000000..0d62d57 --- /dev/null +++ b/src/patterns/comet_dual.py @@ -0,0 +1,43 @@ +import utime + + +class CometDual: + def __init__(self, driver): + self.driver = driver + + def run(self, preset): + colors = preset.c if preset.c else [(255, 255, 255)] + tail = max(1, int(preset.n1) if int(preset.n1) > 0 else 6) + speed = max(1, int(preset.n2) if int(preset.n2) > 0 else 1) + gap = max(0, int(preset.n3)) + p1 = 0 + p2 = self.driver.num_leds - 1 - gap + last = utime.ticks_ms() + while True: + d = max(1, int(preset.d)) + now = utime.ticks_ms() + if utime.ticks_diff(now, last) >= d: + for i in range(self.driver.num_leds): + self.driver.n[i] = (0, 0, 0) + c1 = self.driver.apply_brightness(colors[0 % len(colors)], preset.b) + c2 = self.driver.apply_brightness(colors[1 % len(colors)] if len(colors) > 1 else colors[0], preset.b) + for t in range(tail): + i1 = p1 - t + if 0 <= i1 < self.driver.num_leds: + s = (255 * (tail - t)) // max(1, tail) + self.driver.n[i1] = ((c1[0]*s)//255, (c1[1]*s)//255, (c1[2]*s)//255) + i2 = p2 + t + if 0 <= i2 < self.driver.num_leds: + s = (255 * (tail - t)) // max(1, tail) + self.driver.n[i2] = ((c2[0]*s)//255, (c2[1]*s)//255, (c2[2]*s)//255) + self.driver.n.write() + p1 += speed + p2 -= speed + if p1 - tail > self.driver.num_leds and p2 + tail < 0: + p1 = 0 + p2 = self.driver.num_leds - 1 - gap + last = utime.ticks_add(last, d) + if not preset.a: + yield + return + yield diff --git a/src/patterns/fireflies.py b/src/patterns/fireflies.py new file mode 100644 index 0000000..20a7ad9 --- /dev/null +++ b/src/patterns/fireflies.py @@ -0,0 +1,34 @@ +import random +import utime + + +class Fireflies: + def __init__(self, driver): + self.driver = driver + + def run(self, preset): + colors = preset.c if preset.c else [(255, 210, 80), (120, 255, 120)] + count = max(1, int(preset.n1) if int(preset.n1) > 0 else 6) + speed = max(1, int(preset.n2) if int(preset.n2) > 0 else 8) + bugs = [[random.randint(0, max(0, self.driver.num_leds - 1)), random.randint(0, 255)] for _ in range(count)] + last = utime.ticks_ms() + while True: + d = max(1, int(preset.d)) + now = utime.ticks_ms() + if utime.ticks_diff(now, last) >= d: + for i in range(self.driver.num_leds): + self.driver.n[i] = (0, 0, 0) + for b in bugs: + idx, ph = b + tri = 255 - abs(128 - ph) * 2 + c = self.driver.apply_brightness(colors[idx % len(colors)], preset.b) + self.driver.n[idx] = ((c[0]*tri)//255, (c[1]*tri)//255, (c[2]*tri)//255) + b[1] = (ph + speed) & 255 + if random.randint(0, 31) == 0: + b[0] = random.randint(0, max(0, self.driver.num_leds - 1)) + self.driver.n.write() + last = utime.ticks_add(last, d) + if not preset.a: + yield + return + yield diff --git a/src/patterns/gradient_scroll.py b/src/patterns/gradient_scroll.py new file mode 100644 index 0000000..dfc8215 --- /dev/null +++ b/src/patterns/gradient_scroll.py @@ -0,0 +1,57 @@ +import utime + + +class GradientScroll: + def __init__(self, driver): + self.driver = driver + + def _render(self, colors, phase, brightness): + num_leds = self.driver.num_leds + color_count = len(colors) + if num_leds <= 0 or color_count <= 0: + return + if color_count == 1: + self.driver.fill(self.driver.apply_brightness(colors[0], brightness)) + return + + full_span = color_count * 256 + phase_shift = (phase * full_span) // 256 + for i in range(num_leds): + pos = ((i * full_span) // num_leds + phase_shift) % full_span + idx = pos // 256 + frac = pos & 255 + + c1 = colors[idx] + c2 = colors[(idx + 1) % color_count] + blended = ( + c1[0] + ((c2[0] - c1[0]) * frac) // 256, + c1[1] + ((c2[1] - c1[1]) * frac) // 256, + c1[2] + ((c2[2] - c1[2]) * frac) // 256, + ) + self.driver.n[i] = self.driver.apply_brightness(blended, brightness) + self.driver.n.write() + + def run(self, preset): + """Scrolling blended gradient. + + n1: phase step amount (default 1) + """ + colors = preset.c if preset.c else [(255, 0, 0), (0, 0, 255)] + phase = self.driver.step % 256 + step_amount = max(1, int(preset.n1) if int(preset.n1) > 0 else 1) + last_update = utime.ticks_ms() + + while True: + delay_ms = max(1, int(preset.d)) + now = utime.ticks_ms() + if utime.ticks_diff(now, last_update) >= delay_ms: + self._render(colors, phase, preset.b) + phase = (phase + step_amount) % 256 + self.driver.step = phase + last_update = utime.ticks_add(last_update, delay_ms) + + if not preset.a: + yield + return + + yield diff --git a/src/patterns/heartbeat.py b/src/patterns/heartbeat.py new file mode 100644 index 0000000..37b4819 --- /dev/null +++ b/src/patterns/heartbeat.py @@ -0,0 +1,25 @@ +import utime + + +class Heartbeat: + def __init__(self, driver): + self.driver = driver + + def run(self, preset): + colors = preset.c if preset.c else [(255, 0, 40)] + p1 = max(20, int(preset.n1) if int(preset.n1) > 0 else 120) + p2 = max(20, int(preset.n2) if int(preset.n2) > 0 else 80) + pause = max(20, int(preset.n3) if int(preset.n3) > 0 else 500) + while True: + c = self.driver.apply_brightness(colors[0], preset.b) + self.driver.fill(c) + utime.sleep_ms(p1) + self.driver.fill((0, 0, 0)) + utime.sleep_ms(max(20, int(preset.d))) + self.driver.fill(c) + utime.sleep_ms(p2) + self.driver.fill((0, 0, 0)) + utime.sleep_ms(pause) + yield + if not preset.a: + return diff --git a/src/patterns/marquee.py b/src/patterns/marquee.py new file mode 100644 index 0000000..0bfab28 --- /dev/null +++ b/src/patterns/marquee.py @@ -0,0 +1,30 @@ +import utime + + +class Marquee: + def __init__(self, driver): + self.driver = driver + + def run(self, preset): + colors = preset.c if preset.c else [(255, 255, 255)] + on_len = max(1, int(preset.n1) if int(preset.n1) > 0 else 3) + off_len = max(1, int(preset.n2) if int(preset.n2) > 0 else 2) + step = max(1, int(preset.n3) if int(preset.n3) > 0 else 1) + phase = self.driver.step % (on_len + off_len) + last = utime.ticks_ms() + while True: + d = max(1, int(preset.d)) + now = utime.ticks_ms() + if utime.ticks_diff(now, last) >= d: + c = self.driver.apply_brightness(colors[0], preset.b) + for i in range(self.driver.num_leds): + m = (i + phase) % (on_len + off_len) + self.driver.n[i] = c if m < on_len else (0, 0, 0) + self.driver.n.write() + phase = (phase + step) % (on_len + off_len) + self.driver.step = phase + last = utime.ticks_add(last, d) + if not preset.a: + yield + return + yield diff --git a/src/patterns/meteor_rain.py b/src/patterns/meteor_rain.py new file mode 100644 index 0000000..d69e9a9 --- /dev/null +++ b/src/patterns/meteor_rain.py @@ -0,0 +1,62 @@ +import utime + + +class MeteorRain: + def __init__(self, driver): + self.driver = driver + + def _fade(self, color, fade_amount): + return ( + (color[0] * fade_amount) // 255, + (color[1] * fade_amount) // 255, + (color[2] * fade_amount) // 255, + ) + + def run(self, preset): + """Single meteor with a fading tail. + + n1: tail length (default 8) + n2: speed in LEDs per frame (default 1) + n3: fade amount per frame, 1..255 (default 192) + """ + colors = preset.c if preset.c else [(255, 255, 255)] + color_index = 0 + head = 0 + direction = 1 + last_update = utime.ticks_ms() + + while True: + delay_ms = max(1, int(preset.d)) + tail_len = max(1, int(preset.n1) if int(preset.n1) > 0 else 8) + speed = max(1, int(preset.n2) if int(preset.n2) > 0 else 1) + fade_amount = int(preset.n3) if int(preset.n3) > 0 else 192 + fade_amount = max(1, min(255, fade_amount)) + + now = utime.ticks_ms() + if utime.ticks_diff(now, last_update) >= delay_ms: + for i in range(self.driver.num_leds): + self.driver.n[i] = self._fade(self.driver.n[i], fade_amount) + + base = colors[color_index % len(colors)] + lit = self.driver.apply_brightness(base, preset.b) + if 0 <= head < self.driver.num_leds: + self.driver.n[head] = lit + self.driver.n.write() + + head += direction * speed + if head >= self.driver.num_leds + tail_len: + head = self.driver.num_leds - 1 + direction = -1 + color_index += 1 + elif head < -tail_len: + head = 0 + direction = 1 + color_index += 1 + + last_update = utime.ticks_add(last_update, delay_ms) + + if not preset.a: + yield + return + + yield diff --git a/src/patterns/orbit.py b/src/patterns/orbit.py new file mode 100644 index 0000000..5ffb139 --- /dev/null +++ b/src/patterns/orbit.py @@ -0,0 +1,30 @@ +import utime + + +class Orbit: + def __init__(self, driver): + self.driver = driver + + def run(self, preset): + colors = preset.c if preset.c else [(255, 255, 255), (0, 180, 255), (255, 0, 120)] + orbits = max(1, int(preset.n1) if int(preset.n1) > 0 else 3) + speed = max(1, int(preset.n2) if int(preset.n2) > 0 else 1) + phase = self.driver.step % 256 + last = utime.ticks_ms() + while True: + d = max(1, int(preset.d)) + now = utime.ticks_ms() + if utime.ticks_diff(now, last) >= d: + for i in range(self.driver.num_leds): + self.driver.n[i] = (0, 0, 0) + for k in range(orbits): + idx = ((phase * (k + 1)) // 8 + (k * self.driver.num_leds // max(1, orbits))) % max(1, self.driver.num_leds) + self.driver.n[idx] = self.driver.apply_brightness(colors[k % len(colors)], preset.b) + self.driver.n.write() + phase = (phase + speed) & 255 + self.driver.step = phase + last = utime.ticks_add(last, d) + if not preset.a: + yield + return + yield diff --git a/src/patterns/palette_morph.py b/src/patterns/palette_morph.py new file mode 100644 index 0000000..72ccd91 --- /dev/null +++ b/src/patterns/palette_morph.py @@ -0,0 +1,76 @@ +import utime + + +class PaletteMorph: + def __init__(self, driver): + self.driver = driver + + def _blend(self, c1, c2, t): + return ( + c1[0] + ((c2[0] - c1[0]) * t) // 255, + c1[1] + ((c2[1] - c1[1]) * t) // 255, + c1[2] + ((c2[2] - c1[2]) * t) // 255, + ) + + def run(self, preset): + """Living color field (non-scrolling palette warp). + + Different from `colour_cycle`: this does not scroll a fixed gradient. + Instead, each LED breathes/warps through the palette with local phase + offsets so the strip looks alive. + + n1: morph duration (ms) + n2: warp rate + n3: spatial turbulence amount + """ + colors = preset.c if preset.c else [(255, 0, 0), (0, 255, 0), (0, 0, 255)] + if len(colors) < 2: + while True: + self.driver.fill(self.driver.apply_brightness(colors[0], preset.b)) + yield + morph = max(50, int(preset.n1) if int(preset.n1) > 0 else 1200) + warp_rate = max(1, int(preset.n2) if int(preset.n2) > 0 else 3) + turbulence = max(1, int(preset.n3) if int(preset.n3) > 0 else 24) + base_idx = 0 + start = utime.ticks_ms() + phase = self.driver.step % 256 + + while True: + now = utime.ticks_ms() + age = utime.ticks_diff(now, start) + if age < morph: + t = (age * 255) // morph + else: + t = 255 + + # Global morph anchor between neighboring palette colors. + a = colors[base_idx % len(colors)] + b = colors[(base_idx + 1) % len(colors)] + anchor = self._blend(a, b, t) + + for i in range(self.driver.num_leds): + # Non-linear local warp per LED to create "living" motion. + pos = (i * 256) // max(1, self.driver.num_leds) + wobble = ((pos * turbulence) // 32 + phase + (t // 2)) & 255 + breath = 255 - abs(128 - wobble) * 2 + local = (pos + (breath // 3) + (t // 4)) % 256 + idx = (base_idx + ((local * len(colors)) // 256)) % len(colors) + frac = (local * len(colors)) & 255 + c1 = colors[idx] + c2 = colors[(idx + 1) % len(colors)] + grad = self._blend(c1, c2, frac) + # Blend with anchor to keep coherent palette morphing. + out = self._blend(grad, anchor, 80) + self.driver.n[i] = self.driver.apply_brightness(out, preset.b) + self.driver.n.write() + + if age >= morph: + base_idx = (base_idx + 1) % len(colors) + start = now + if not preset.a: + yield + return + phase = (phase + warp_rate) & 255 + self.driver.step = phase + utime.sleep_ms(max(1, int(preset.d))) + yield diff --git a/src/patterns/plasma.py b/src/patterns/plasma.py new file mode 100644 index 0000000..1137c13 --- /dev/null +++ b/src/patterns/plasma.py @@ -0,0 +1,39 @@ +import utime + + +class Plasma: + def __init__(self, driver): + self.driver = driver + + def _wheel(self, pos): + if pos < 85: + return (pos * 3, 255 - pos * 3, 0) + if pos < 170: + pos -= 85 + return (255 - pos * 3, 0, pos * 3) + pos -= 170 + return (0, pos * 3, 255 - pos * 3) + + def run(self, preset): + scale = max(1, int(preset.n1) if int(preset.n1) > 0 else 6) + speed = max(1, int(preset.n2) if int(preset.n2) > 0 else 2) + contrast = max(1, int(preset.n3) if int(preset.n3) > 0 else 2) + t = self.driver.step % 256 + last = utime.ticks_ms() + while True: + d = max(1, int(preset.d)) + now = utime.ticks_ms() + if utime.ticks_diff(now, last) >= d: + for i in range(self.driver.num_leds): + v = ((i * scale + t) & 255) + v2 = (((i * scale // max(1, contrast)) - (t * 2)) & 255) + c = self._wheel((v + v2) & 255) + self.driver.n[i] = self.driver.apply_brightness(c, preset.b) + self.driver.n.write() + t = (t + speed) % 256 + self.driver.step = t + last = utime.ticks_add(last, d) + if not preset.a: + yield + return + yield diff --git a/src/patterns/rain_drops.py b/src/patterns/rain_drops.py new file mode 100644 index 0000000..96bc9d5 --- /dev/null +++ b/src/patterns/rain_drops.py @@ -0,0 +1,40 @@ +import random +import utime + + +class RainDrops: + def __init__(self, driver): + self.driver = driver + + def run(self, preset): + colors = preset.c if preset.c else [(120, 180, 255)] + rate = max(1, int(preset.n1) if int(preset.n1) > 0 else 32) + width = max(1, int(preset.n2) if int(preset.n2) > 0 else 3) + drops = [] + last = utime.ticks_ms() + while True: + d = max(1, int(preset.d)) + now = utime.ticks_ms() + if utime.ticks_diff(now, last) >= d: + for i in range(self.driver.num_leds): + self.driver.n[i] = (0, 0, 0) + if random.randint(0, 255) < rate: + drops.append([random.randint(0, max(0, self.driver.num_leds - 1)), 0]) + nd = [] + for pos, age in drops: + for off in range(-width, width + 1): + idx = pos + off + if 0 <= idx < self.driver.num_leds: + s = 255 - min(255, abs(off) * 255 // max(1, width + 1) + age * 40) + base = self.driver.apply_brightness(colors[age % len(colors)], preset.b) + self.driver.n[idx] = ((base[0]*s)//255, (base[1]*s)//255, (base[2]*s)//255) + age += 1 + if age < 8: + nd.append([pos, age]) + drops = nd + self.driver.n.write() + last = utime.ticks_add(last, d) + if not preset.a: + yield + return + yield diff --git a/src/patterns/scanner.py b/src/patterns/scanner.py new file mode 100644 index 0000000..8f58d20 --- /dev/null +++ b/src/patterns/scanner.py @@ -0,0 +1,66 @@ +import utime + + +class Scanner: + def __init__(self, driver): + self.driver = driver + + def run(self, preset): + """Classic scanner eye with soft falloff. + + n1: eye width (default 4) + n2: end pause in frames (default 0) + """ + colors = preset.c if preset.c else [(255, 0, 0)] + color_index = 0 + center = 0 + direction = 1 + pause_frames = 0 + last_update = utime.ticks_ms() + + while True: + delay_ms = max(1, int(preset.d)) + width = max(1, int(preset.n1) if int(preset.n1) > 0 else 4) + end_pause = max(0, int(preset.n2)) + + now = utime.ticks_ms() + if utime.ticks_diff(now, last_update) >= delay_ms: + base = colors[color_index % len(colors)] + base = self.driver.apply_brightness(base, preset.b) + for i in range(self.driver.num_leds): + dist = i - center + if dist < 0: + dist = -dist + if dist > width: + self.driver.n[i] = (0, 0, 0) + else: + scale = ((width - dist) * 255) // max(1, width) + self.driver.n[i] = ( + (base[0] * scale) // 255, + (base[1] * scale) // 255, + (base[2] * scale) // 255, + ) + self.driver.n.write() + + if pause_frames > 0: + pause_frames -= 1 + else: + center += direction + if center >= self.driver.num_leds - 1: + center = self.driver.num_leds - 1 + direction = -1 + pause_frames = end_pause + color_index += 1 + elif center <= 0: + center = 0 + direction = 1 + pause_frames = end_pause + color_index += 1 + + last_update = utime.ticks_add(last_update, delay_ms) + + if not preset.a: + yield + return + + yield diff --git a/src/patterns/segment_chase.py b/src/patterns/segment_chase.py new file mode 100644 index 0000000..6f14ef8 --- /dev/null +++ b/src/patterns/segment_chase.py @@ -0,0 +1,44 @@ +import utime + + +class SegmentChase: + def __init__(self, driver): + self.driver = driver + + def run(self, preset): + """Independent moving segments (distinct from classic two-color chase). + + n1: segment size (LEDs per segment) + n2: step size (phase increment each frame) + n3: per-segment phase offset + n4: gap spacing inside segment (0 = solid segment) + """ + colors = preset.c if preset.c else [(255, 0, 0), (0, 0, 255)] + seg = max(1, int(preset.n1) if int(preset.n1) > 0 else 4) + phase_step = max(1, int(preset.n2) if int(preset.n2) > 0 else 1) + seg_offset = max(0, int(preset.n3)) + gap = max(0, int(preset.n4)) + phase = self.driver.step % 256 + last = utime.ticks_ms() + while True: + d = max(1, int(preset.d)) + now = utime.ticks_ms() + if utime.ticks_diff(now, last) >= d: + for i in range(self.driver.num_leds): + seg_idx = i // seg + in_seg = i % seg + local_phase = (phase + seg_idx * seg_offset) % seg + lit_idx = (in_seg + local_phase) % seg + if gap > 0 and lit_idx >= max(1, seg - gap): + self.driver.n[i] = (0, 0, 0) + else: + color_idx = seg_idx % len(colors) + self.driver.n[i] = self.driver.apply_brightness(colors[color_idx], preset.b) + self.driver.n.write() + phase = (phase + phase_step) % seg + self.driver.step = phase + last = utime.ticks_add(last, d) + if not preset.a: + yield + return + yield diff --git a/src/patterns/snowfall.py b/src/patterns/snowfall.py new file mode 100644 index 0000000..03e9159 --- /dev/null +++ b/src/patterns/snowfall.py @@ -0,0 +1,36 @@ +import random +import utime + + +class Snowfall: + def __init__(self, driver): + self.driver = driver + + def run(self, preset): + colors = preset.c if preset.c else [(255, 255, 255), (180, 220, 255)] + density = max(1, int(preset.n1) if int(preset.n1) > 0 else 20) + speed = max(1, int(preset.n2) if int(preset.n2) > 0 else 1) + flakes = [] + last = utime.ticks_ms() + while True: + d = max(1, int(preset.d)) + now = utime.ticks_ms() + if utime.ticks_diff(now, last) >= d: + if random.randint(0, 255) < density: + flakes.append([self.driver.num_leds - 1, random.randint(0, len(colors)-1)]) + for i in range(self.driver.num_leds): + self.driver.n[i] = (0, 0, 0) + nf = [] + for pos, ci in flakes: + if 0 <= pos < self.driver.num_leds: + self.driver.n[pos] = self.driver.apply_brightness(colors[ci], preset.b) + pos -= speed + if pos >= -1: + nf.append([pos, ci]) + flakes = nf + self.driver.n.write() + last = utime.ticks_add(last, d) + if not preset.a: + yield + return + yield diff --git a/src/patterns/sparkle_trail.py b/src/patterns/sparkle_trail.py new file mode 100644 index 0000000..13b3ee2 --- /dev/null +++ b/src/patterns/sparkle_trail.py @@ -0,0 +1,31 @@ +import random +import utime + + +class SparkleTrail: + def __init__(self, driver): + self.driver = driver + + def run(self, preset): + colors = preset.c if preset.c else [(120, 120, 255)] + density = max(1, int(preset.n1) if int(preset.n1) > 0 else 24) + decay = max(1, min(255, int(preset.n2) if int(preset.n2) > 0 else 210)) + last = utime.ticks_ms() + while True: + d = max(1, int(preset.d)) + now = utime.ticks_ms() + if utime.ticks_diff(now, last) >= d: + for i in range(self.driver.num_leds): + r,g,b = self.driver.n[i] + self.driver.n[i] = ((r*decay)//255, (g*decay)//255, (b*decay)//255) + sparks = max(1, self.driver.num_leds * density // 255) + for _ in range(sparks): + idx = random.randint(0, max(0, self.driver.num_leds - 1)) + c = self.driver.apply_brightness(colors[random.randint(0, len(colors)-1)], preset.b) + self.driver.n[idx] = c + self.driver.n.write() + last = utime.ticks_add(last, d) + if not preset.a: + yield + return + yield diff --git a/src/patterns/strobe_burst.py b/src/patterns/strobe_burst.py new file mode 100644 index 0000000..91d57cb --- /dev/null +++ b/src/patterns/strobe_burst.py @@ -0,0 +1,24 @@ +import utime + + +class StrobeBurst: + def __init__(self, driver): + self.driver = driver + + def run(self, preset): + colors = preset.c if preset.c else [(255, 255, 255)] + count = max(1, int(preset.n1) if int(preset.n1) > 0 else 3) + gap = max(1, int(preset.n2) if int(preset.n2) > 0 else 60) + cooldown = max(1, int(preset.n3) if int(preset.n3) > 0 else 400) + c = self.driver.apply_brightness(colors[0], preset.b) + while True: + for _ in range(count): + self.driver.fill(c) + utime.sleep_ms(max(1, int(preset.d)//2)) + self.driver.fill((0, 0, 0)) + utime.sleep_ms(gap) + yield + utime.sleep_ms(cooldown) + yield + if not preset.a: + return diff --git a/src/patterns/wave.py b/src/patterns/wave.py new file mode 100644 index 0000000..fd9292f --- /dev/null +++ b/src/patterns/wave.py @@ -0,0 +1,32 @@ +import utime + + +class Wave: + def __init__(self, driver): + self.driver = driver + + def run(self, preset): + colors = preset.c if preset.c else [(0, 180, 255)] + wavelength = max(2, int(preset.n1) if int(preset.n1) > 0 else 12) + amp = max(0, min(255, int(preset.n2) if int(preset.n2) > 0 else 180)) + drift = max(1, int(preset.n3) if int(preset.n3) > 0 else 1) + phase = self.driver.step % 256 + last = utime.ticks_ms() + while True: + d = max(1, int(preset.d)) + now = utime.ticks_ms() + if utime.ticks_diff(now, last) >= d: + base = self.driver.apply_brightness(colors[0], preset.b) + for i in range(self.driver.num_leds): + x = (i * 256 // wavelength + phase) & 255 + tri = 255 - abs(128 - x) * 2 + s = (tri * amp) // 255 + self.driver.n[i] = ((base[0]*s)//255, (base[1]*s)//255, (base[2]*s)//255) + self.driver.n.write() + phase = (phase + drift) % 256 + self.driver.step = phase + last = utime.ticks_add(last, d) + if not preset.a: + yield + return + yield diff --git a/tests/patterns/aurora.py b/tests/patterns/aurora.py new file mode 100644 index 0000000..27be98f --- /dev/null +++ b/tests/patterns/aurora.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import utime +from machine import WDT +from settings import Settings +from presets import Presets, run_tick + + +def run_for(p, wdt, ms): + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < ms: + wdt.feed() + run_tick(p) + utime.sleep_ms(10) + + +def main(): + s = Settings() + p = Presets(pin=s.get("led_pin", 10), num_leds=s.get("num_leds", 30)) + wdt = WDT(timeout=10000) + + p.edit("test_aurora", { + "p": "aurora", + "b": 200, + "d": 60, + "c": [(255, 0, 0), (0, 0, 255), (0, 255, 0)], + "n1": 4, + "n2": 2, + "n3": 120, + "a": True, + }) + p.select("test_aurora") + run_for(p, wdt, 3000) + + p.edit("cleanup_off", {"p": "off"}) + p.select("cleanup_off") + run_for(p, wdt, 100) + + +if __name__ == "__main__": + main() diff --git a/tests/patterns/bar_graph.py b/tests/patterns/bar_graph.py new file mode 100644 index 0000000..1a4b390 --- /dev/null +++ b/tests/patterns/bar_graph.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import utime +from machine import WDT +from settings import Settings +from presets import Presets, run_tick + + +def run_for(p, wdt, ms): + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < ms: + wdt.feed() + run_tick(p) + utime.sleep_ms(10) + + +def main(): + s = Settings() + p = Presets(pin=s.get("led_pin", 10), num_leds=s.get("num_leds", 30)) + wdt = WDT(timeout=10000) + + p.edit("test_bar_graph", { + "p": "bar_graph", + "b": 200, + "d": 60, + "c": [(255, 0, 0), (0, 0, 255), (0, 255, 0)], + "n1": 4, + "n2": 2, + "n3": 120, + "a": True, + }) + p.select("test_bar_graph") + run_for(p, wdt, 3000) + + p.edit("cleanup_off", {"p": "off"}) + p.select("cleanup_off") + run_for(p, wdt, 100) + + +if __name__ == "__main__": + main() diff --git a/tests/patterns/breathing_dual.py b/tests/patterns/breathing_dual.py new file mode 100644 index 0000000..4cc35f3 --- /dev/null +++ b/tests/patterns/breathing_dual.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import utime +from machine import WDT +from settings import Settings +from presets import Presets, run_tick + + +def run_for(p, wdt, ms): + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < ms: + wdt.feed() + run_tick(p) + utime.sleep_ms(10) + + +def main(): + s = Settings() + p = Presets(pin=s.get("led_pin", 10), num_leds=s.get("num_leds", 30)) + wdt = WDT(timeout=10000) + + p.edit("test_breathing_dual", { + "p": "breathing_dual", + "b": 200, + "d": 60, + "c": [(255, 0, 0), (0, 0, 255), (0, 255, 0)], + "n1": 4, + "n2": 2, + "n3": 120, + "a": True, + }) + p.select("test_breathing_dual") + run_for(p, wdt, 3000) + + p.edit("cleanup_off", {"p": "off"}) + p.select("cleanup_off") + run_for(p, wdt, 100) + + +if __name__ == "__main__": + main() diff --git a/tests/patterns/clock_sweep.py b/tests/patterns/clock_sweep.py new file mode 100644 index 0000000..0fb1106 --- /dev/null +++ b/tests/patterns/clock_sweep.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import utime +from machine import WDT +from settings import Settings +from presets import Presets, run_tick + + +def run_for(p, wdt, ms): + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < ms: + wdt.feed() + run_tick(p) + utime.sleep_ms(10) + + +def main(): + s = Settings() + p = Presets(pin=s.get("led_pin", 10), num_leds=s.get("num_leds", 30)) + wdt = WDT(timeout=10000) + + p.edit("test_clock_sweep", { + "p": "clock_sweep", + "b": 200, + "d": 60, + "c": [(255, 0, 0), (0, 0, 255), (0, 255, 0)], + "n1": 4, + "n2": 2, + "n3": 120, + "a": True, + }) + p.select("test_clock_sweep") + run_for(p, wdt, 3000) + + p.edit("cleanup_off", {"p": "off"}) + p.select("cleanup_off") + run_for(p, wdt, 100) + + +if __name__ == "__main__": + main() diff --git a/tests/patterns/comet_dual.py b/tests/patterns/comet_dual.py new file mode 100644 index 0000000..bcb3e8e --- /dev/null +++ b/tests/patterns/comet_dual.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import utime +from machine import WDT +from settings import Settings +from presets import Presets, run_tick + + +def run_for(p, wdt, ms): + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < ms: + wdt.feed() + run_tick(p) + utime.sleep_ms(10) + + +def main(): + s = Settings() + p = Presets(pin=s.get("led_pin", 10), num_leds=s.get("num_leds", 30)) + wdt = WDT(timeout=10000) + + p.edit("test_comet_dual", { + "p": "comet_dual", + "b": 200, + "d": 60, + "c": [(255, 0, 0), (0, 0, 255), (0, 255, 0)], + "n1": 4, + "n2": 2, + "n3": 120, + "a": True, + }) + p.select("test_comet_dual") + run_for(p, wdt, 3000) + + p.edit("cleanup_off", {"p": "off"}) + p.select("cleanup_off") + run_for(p, wdt, 100) + + +if __name__ == "__main__": + main() diff --git a/tests/patterns/fireflies.py b/tests/patterns/fireflies.py new file mode 100644 index 0000000..e6645f6 --- /dev/null +++ b/tests/patterns/fireflies.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import utime +from machine import WDT +from settings import Settings +from presets import Presets, run_tick + + +def run_for(p, wdt, ms): + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < ms: + wdt.feed() + run_tick(p) + utime.sleep_ms(10) + + +def main(): + s = Settings() + p = Presets(pin=s.get("led_pin", 10), num_leds=s.get("num_leds", 30)) + wdt = WDT(timeout=10000) + + p.edit("test_fireflies", { + "p": "fireflies", + "b": 200, + "d": 60, + "c": [(255, 0, 0), (0, 0, 255), (0, 255, 0)], + "n1": 4, + "n2": 2, + "n3": 120, + "a": True, + }) + p.select("test_fireflies") + run_for(p, wdt, 3000) + + p.edit("cleanup_off", {"p": "off"}) + p.select("cleanup_off") + run_for(p, wdt, 100) + + +if __name__ == "__main__": + main() diff --git a/tests/patterns/gradient_scroll.py b/tests/patterns/gradient_scroll.py new file mode 100644 index 0000000..e7ed744 --- /dev/null +++ b/tests/patterns/gradient_scroll.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +import utime +from machine import WDT +from settings import Settings +from presets import Presets, run_tick + + +def run_for(p, wdt, ms): + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < ms: + wdt.feed() + run_tick(p) + utime.sleep_ms(10) + + +def main(): + s = Settings() + p = Presets(pin=s.get("led_pin", 10), num_leds=s.get("num_leds", 30)) + wdt = WDT(timeout=10000) + + print("Test gradient_scroll") + p.edit("gradient_test", { + "p": "gradient_scroll", + "b": 220, + "d": 60, + "c": [(255, 0, 0), (0, 255, 0), (0, 0, 255)], + "n1": 2, + "a": True, + }) + p.select("gradient_test") + run_for(p, wdt, 4000) + + p.edit("cleanup_off", {"p": "off"}) + p.select("cleanup_off") + run_for(p, wdt, 100) + + +if __name__ == "__main__": + main() diff --git a/tests/patterns/heartbeat.py b/tests/patterns/heartbeat.py new file mode 100644 index 0000000..e5f6995 --- /dev/null +++ b/tests/patterns/heartbeat.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import utime +from machine import WDT +from settings import Settings +from presets import Presets, run_tick + + +def run_for(p, wdt, ms): + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < ms: + wdt.feed() + run_tick(p) + utime.sleep_ms(10) + + +def main(): + s = Settings() + p = Presets(pin=s.get("led_pin", 10), num_leds=s.get("num_leds", 30)) + wdt = WDT(timeout=10000) + + p.edit("test_heartbeat", { + "p": "heartbeat", + "b": 200, + "d": 60, + "c": [(255, 0, 0), (0, 0, 255), (0, 255, 0)], + "n1": 4, + "n2": 2, + "n3": 120, + "a": True, + }) + p.select("test_heartbeat") + run_for(p, wdt, 3000) + + p.edit("cleanup_off", {"p": "off"}) + p.select("cleanup_off") + run_for(p, wdt, 100) + + +if __name__ == "__main__": + main() diff --git a/tests/patterns/marquee.py b/tests/patterns/marquee.py new file mode 100644 index 0000000..ba30d36 --- /dev/null +++ b/tests/patterns/marquee.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import utime +from machine import WDT +from settings import Settings +from presets import Presets, run_tick + + +def run_for(p, wdt, ms): + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < ms: + wdt.feed() + run_tick(p) + utime.sleep_ms(10) + + +def main(): + s = Settings() + p = Presets(pin=s.get("led_pin", 10), num_leds=s.get("num_leds", 30)) + wdt = WDT(timeout=10000) + + p.edit("test_marquee", { + "p": "marquee", + "b": 200, + "d": 60, + "c": [(255, 0, 0), (0, 0, 255), (0, 255, 0)], + "n1": 4, + "n2": 2, + "n3": 120, + "a": True, + }) + p.select("test_marquee") + run_for(p, wdt, 3000) + + p.edit("cleanup_off", {"p": "off"}) + p.select("cleanup_off") + run_for(p, wdt, 100) + + +if __name__ == "__main__": + main() diff --git a/tests/patterns/meteor_rain.py b/tests/patterns/meteor_rain.py new file mode 100644 index 0000000..e71c4f2 --- /dev/null +++ b/tests/patterns/meteor_rain.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +import utime +from machine import WDT +from settings import Settings +from presets import Presets, run_tick + + +def run_for(p, wdt, ms): + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < ms: + wdt.feed() + run_tick(p) + utime.sleep_ms(10) + + +def main(): + s = Settings() + p = Presets(pin=s.get("led_pin", 10), num_leds=s.get("num_leds", 30)) + wdt = WDT(timeout=10000) + + print("Test meteor_rain") + p.edit("meteor_test", { + "p": "meteor_rain", + "b": 200, + "d": 40, + "c": [(255, 80, 0), (0, 120, 255)], + "n1": 10, + "n2": 1, + "n3": 200, + "a": True, + }) + p.select("meteor_test") + run_for(p, wdt, 4000) + + p.edit("cleanup_off", {"p": "off"}) + p.select("cleanup_off") + run_for(p, wdt, 100) + + +if __name__ == "__main__": + main() diff --git a/tests/patterns/orbit.py b/tests/patterns/orbit.py new file mode 100644 index 0000000..dada446 --- /dev/null +++ b/tests/patterns/orbit.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import utime +from machine import WDT +from settings import Settings +from presets import Presets, run_tick + + +def run_for(p, wdt, ms): + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < ms: + wdt.feed() + run_tick(p) + utime.sleep_ms(10) + + +def main(): + s = Settings() + p = Presets(pin=s.get("led_pin", 10), num_leds=s.get("num_leds", 30)) + wdt = WDT(timeout=10000) + + p.edit("test_orbit", { + "p": "orbit", + "b": 200, + "d": 60, + "c": [(255, 0, 0), (0, 0, 255), (0, 255, 0)], + "n1": 4, + "n2": 2, + "n3": 120, + "a": True, + }) + p.select("test_orbit") + run_for(p, wdt, 3000) + + p.edit("cleanup_off", {"p": "off"}) + p.select("cleanup_off") + run_for(p, wdt, 100) + + +if __name__ == "__main__": + main() diff --git a/tests/patterns/palette_morph.py b/tests/patterns/palette_morph.py new file mode 100644 index 0000000..9079516 --- /dev/null +++ b/tests/patterns/palette_morph.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import utime +from machine import WDT +from settings import Settings +from presets import Presets, run_tick + + +def run_for(p, wdt, ms): + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < ms: + wdt.feed() + run_tick(p) + utime.sleep_ms(10) + + +def main(): + s = Settings() + p = Presets(pin=s.get("led_pin", 10), num_leds=s.get("num_leds", 30)) + wdt = WDT(timeout=10000) + + p.edit("test_palette_morph", { + "p": "palette_morph", + "b": 200, + "d": 60, + "c": [(255, 0, 0), (0, 0, 255), (0, 255, 0)], + "n1": 4, + "n2": 2, + "n3": 120, + "a": True, + }) + p.select("test_palette_morph") + run_for(p, wdt, 3000) + + p.edit("cleanup_off", {"p": "off"}) + p.select("cleanup_off") + run_for(p, wdt, 100) + + +if __name__ == "__main__": + main() diff --git a/tests/patterns/plasma.py b/tests/patterns/plasma.py new file mode 100644 index 0000000..aae72f7 --- /dev/null +++ b/tests/patterns/plasma.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import utime +from machine import WDT +from settings import Settings +from presets import Presets, run_tick + + +def run_for(p, wdt, ms): + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < ms: + wdt.feed() + run_tick(p) + utime.sleep_ms(10) + + +def main(): + s = Settings() + p = Presets(pin=s.get("led_pin", 10), num_leds=s.get("num_leds", 30)) + wdt = WDT(timeout=10000) + + p.edit("test_plasma", { + "p": "plasma", + "b": 200, + "d": 60, + "c": [(255, 0, 0), (0, 0, 255), (0, 255, 0)], + "n1": 4, + "n2": 2, + "n3": 120, + "a": True, + }) + p.select("test_plasma") + run_for(p, wdt, 3000) + + p.edit("cleanup_off", {"p": "off"}) + p.select("cleanup_off") + run_for(p, wdt, 100) + + +if __name__ == "__main__": + main() diff --git a/tests/patterns/rain_drops.py b/tests/patterns/rain_drops.py new file mode 100644 index 0000000..50e36b8 --- /dev/null +++ b/tests/patterns/rain_drops.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import utime +from machine import WDT +from settings import Settings +from presets import Presets, run_tick + + +def run_for(p, wdt, ms): + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < ms: + wdt.feed() + run_tick(p) + utime.sleep_ms(10) + + +def main(): + s = Settings() + p = Presets(pin=s.get("led_pin", 10), num_leds=s.get("num_leds", 30)) + wdt = WDT(timeout=10000) + + p.edit("test_rain_drops", { + "p": "rain_drops", + "b": 200, + "d": 60, + "c": [(255, 0, 0), (0, 0, 255), (0, 255, 0)], + "n1": 4, + "n2": 2, + "n3": 120, + "a": True, + }) + p.select("test_rain_drops") + run_for(p, wdt, 3000) + + p.edit("cleanup_off", {"p": "off"}) + p.select("cleanup_off") + run_for(p, wdt, 100) + + +if __name__ == "__main__": + main() diff --git a/tests/patterns/scanner.py b/tests/patterns/scanner.py new file mode 100644 index 0000000..03823fc --- /dev/null +++ b/tests/patterns/scanner.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import utime +from machine import WDT +from settings import Settings +from presets import Presets, run_tick + + +def run_for(p, wdt, ms): + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < ms: + wdt.feed() + run_tick(p) + utime.sleep_ms(10) + + +def main(): + s = Settings() + p = Presets(pin=s.get("led_pin", 10), num_leds=s.get("num_leds", 30)) + wdt = WDT(timeout=10000) + + print("Test scanner") + p.edit("scanner_test", { + "p": "scanner", + "b": 255, + "d": 30, + "c": [(255, 0, 0)], + "n1": 4, + "n2": 2, + "a": True, + }) + p.select("scanner_test") + run_for(p, wdt, 4000) + + p.edit("cleanup_off", {"p": "off"}) + p.select("cleanup_off") + run_for(p, wdt, 100) + + +if __name__ == "__main__": + main() diff --git a/tests/patterns/segment_chase.py b/tests/patterns/segment_chase.py new file mode 100644 index 0000000..59e810c --- /dev/null +++ b/tests/patterns/segment_chase.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import utime +from machine import WDT +from settings import Settings +from presets import Presets, run_tick + + +def run_for(p, wdt, ms): + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < ms: + wdt.feed() + run_tick(p) + utime.sleep_ms(10) + + +def main(): + s = Settings() + p = Presets(pin=s.get("led_pin", 10), num_leds=s.get("num_leds", 30)) + wdt = WDT(timeout=10000) + + p.edit("test_segment_chase", { + "p": "segment_chase", + "b": 200, + "d": 60, + "c": [(255, 0, 0), (0, 0, 255), (0, 255, 0)], + "n1": 4, + "n2": 2, + "n3": 120, + "a": True, + }) + p.select("test_segment_chase") + run_for(p, wdt, 3000) + + p.edit("cleanup_off", {"p": "off"}) + p.select("cleanup_off") + run_for(p, wdt, 100) + + +if __name__ == "__main__": + main() diff --git a/tests/patterns/snowfall.py b/tests/patterns/snowfall.py new file mode 100644 index 0000000..ba35880 --- /dev/null +++ b/tests/patterns/snowfall.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import utime +from machine import WDT +from settings import Settings +from presets import Presets, run_tick + + +def run_for(p, wdt, ms): + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < ms: + wdt.feed() + run_tick(p) + utime.sleep_ms(10) + + +def main(): + s = Settings() + p = Presets(pin=s.get("led_pin", 10), num_leds=s.get("num_leds", 30)) + wdt = WDT(timeout=10000) + + p.edit("test_snowfall", { + "p": "snowfall", + "b": 200, + "d": 60, + "c": [(255, 0, 0), (0, 0, 255), (0, 255, 0)], + "n1": 4, + "n2": 2, + "n3": 120, + "a": True, + }) + p.select("test_snowfall") + run_for(p, wdt, 3000) + + p.edit("cleanup_off", {"p": "off"}) + p.select("cleanup_off") + run_for(p, wdt, 100) + + +if __name__ == "__main__": + main() diff --git a/tests/patterns/sparkle_trail.py b/tests/patterns/sparkle_trail.py new file mode 100644 index 0000000..bc98cfa --- /dev/null +++ b/tests/patterns/sparkle_trail.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import utime +from machine import WDT +from settings import Settings +from presets import Presets, run_tick + + +def run_for(p, wdt, ms): + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < ms: + wdt.feed() + run_tick(p) + utime.sleep_ms(10) + + +def main(): + s = Settings() + p = Presets(pin=s.get("led_pin", 10), num_leds=s.get("num_leds", 30)) + wdt = WDT(timeout=10000) + + p.edit("test_sparkle_trail", { + "p": "sparkle_trail", + "b": 200, + "d": 60, + "c": [(255, 0, 0), (0, 0, 255), (0, 255, 0)], + "n1": 4, + "n2": 2, + "n3": 120, + "a": True, + }) + p.select("test_sparkle_trail") + run_for(p, wdt, 3000) + + p.edit("cleanup_off", {"p": "off"}) + p.select("cleanup_off") + run_for(p, wdt, 100) + + +if __name__ == "__main__": + main() diff --git a/tests/patterns/strobe_burst.py b/tests/patterns/strobe_burst.py new file mode 100644 index 0000000..15a5e56 --- /dev/null +++ b/tests/patterns/strobe_burst.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import utime +from machine import WDT +from settings import Settings +from presets import Presets, run_tick + + +def run_for(p, wdt, ms): + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < ms: + wdt.feed() + run_tick(p) + utime.sleep_ms(10) + + +def main(): + s = Settings() + p = Presets(pin=s.get("led_pin", 10), num_leds=s.get("num_leds", 30)) + wdt = WDT(timeout=10000) + + p.edit("test_strobe_burst", { + "p": "strobe_burst", + "b": 200, + "d": 60, + "c": [(255, 0, 0), (0, 0, 255), (0, 255, 0)], + "n1": 4, + "n2": 2, + "n3": 120, + "a": True, + }) + p.select("test_strobe_burst") + run_for(p, wdt, 3000) + + p.edit("cleanup_off", {"p": "off"}) + p.select("cleanup_off") + run_for(p, wdt, 100) + + +if __name__ == "__main__": + main() diff --git a/tests/patterns/wave.py b/tests/patterns/wave.py new file mode 100644 index 0000000..6a5d5bb --- /dev/null +++ b/tests/patterns/wave.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import utime +from machine import WDT +from settings import Settings +from presets import Presets, run_tick + + +def run_for(p, wdt, ms): + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < ms: + wdt.feed() + run_tick(p) + utime.sleep_ms(10) + + +def main(): + s = Settings() + p = Presets(pin=s.get("led_pin", 10), num_leds=s.get("num_leds", 30)) + wdt = WDT(timeout=10000) + + p.edit("test_wave", { + "p": "wave", + "b": 200, + "d": 60, + "c": [(255, 0, 0), (0, 0, 255), (0, 255, 0)], + "n1": 4, + "n2": 2, + "n3": 120, + "a": True, + }) + p.select("test_wave") + run_for(p, wdt, 3000) + + p.edit("cleanup_off", {"p": "off"}) + p.select("cleanup_off") + run_for(p, wdt, 100) + + +if __name__ == "__main__": + main()