From fe2717f51da51a0ed781377285b0f9172f56fc97 Mon Sep 17 00:00:00 2001 From: jimmy Date: Mon, 24 Nov 2025 22:07:39 +1300 Subject: [PATCH] Add circle pattern --- src/patterns.py | 81 +++++++++++++++++++++++- test/patterns/circle.py | 132 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+), 2 deletions(-) create mode 100644 test/patterns/circle.py diff --git a/src/patterns.py b/src/patterns.py index 087b210..1cf7509 100644 --- a/src/patterns.py +++ b/src/patterns.py @@ -26,7 +26,7 @@ param_mapping = { } class Patterns(PatternsBase): - def __init__(self, pin, num_leds, color1=(0,0,0), color2=(0,0,0), brightness=127, selected="rainbow_cycle", delay=100): + def __init__(self, pin, num_leds, color1=(0,0,0), color2=(0,0,0), brightness=127, selected="off", delay=100): super().__init__(pin, num_leds, color1, color2, brightness, selected, delay) self.auto = True self.step = 0 @@ -38,6 +38,7 @@ class Patterns(PatternsBase): "pulse": self.pulse, "transition": self.transition, "n_chase": self.n_chase, + "circle": self.circle, } @@ -337,4 +338,80 @@ class Patterns(PatternsBase): last_update = current_time self.running = False - self.stopped = True \ No newline at end of file + self.stopped = True + + def circle(self): + """Circle loading pattern - grows to n2, then tail moves forward at n3 until min length n4""" + self.stopped = False + self.running = True + head = 0 + tail = 0 + + # Calculate timing + head_rate = max(1, int(self.n1)) # n1 = head moves per second + tail_rate = max(1, int(self.n3)) # n3 = tail moves per second + max_length = max(1, int(self.n2)) # n2 = max length + min_length = max(0, int(self.n4)) # n4 = min length + + head_delay = 1000 // head_rate # ms between head movements + tail_delay = 1000 // tail_rate # ms between tail movements + + last_head_move = utime.ticks_ms() + last_tail_move = utime.ticks_ms() + + phase = "growing" # "growing", "shrinking", or "off" + + while self.running: + current_time = utime.ticks_ms() + + # Clear all LEDs + self.n.fill((0, 0, 0)) + + # Calculate segment length + segment_length = (head - tail) % self.num_leds + if segment_length == 0 and head != tail: + segment_length = self.num_leds + + # Draw segment from tail to head + color = self.apply_brightness(self.colors[0]) + for i in range(segment_length + 1): + led_pos = (tail + i) % self.num_leds + self.n[led_pos] = color + + # Move head continuously at n1 LEDs per second + if utime.ticks_diff(current_time, last_head_move) >= head_delay: + head = (head + 1) % self.num_leds + last_head_move = current_time + + # Tail behavior based on phase + if phase == "growing": + # Growing phase: tail stays at 0 until max length reached + if segment_length >= max_length: + phase = "shrinking" + elif phase == "shrinking": + # Shrinking phase: move tail forward at n3 LEDs per second + if utime.ticks_diff(current_time, last_tail_move) >= tail_delay: + tail = (tail + 1) % self.num_leds + last_tail_move = current_time + + # Check if we've reached min length + current_length = (head - tail) % self.num_leds + if current_length == 0 and head != tail: + current_length = self.num_leds + + # For min_length = 0, we need at least 1 LED (the head) + if min_length == 0 and current_length <= 1: + phase = "off" # All LEDs off for 1 step + elif min_length > 0 and current_length <= min_length: + phase = "growing" # Cycle repeats + else: # phase == "off" + # Off phase: all LEDs off for 1 step, then restart + tail = head # Reset tail to head position to start fresh + phase = "growing" + + self.n.write() + + self.running = False + self.stopped = True + + \ No newline at end of file diff --git a/test/patterns/circle.py b/test/patterns/circle.py new file mode 100644 index 0000000..7cb9b19 --- /dev/null +++ b/test/patterns/circle.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +import uasyncio as asyncio +import utime +from machine import WDT +from settings import Settings +from patterns import Patterns + + +async def main(): + s = Settings() + pin = s.get("led_pin", 10) + num = s.get("num_leds", 30) + + p = Patterns(pin=pin, num_leds=num) + wdt = WDT(timeout=10000) + + # Test 1: Basic circle (n1=50, n2=100, n3=200, n4=0) + print("Test 1: Basic circle (n1=50, n2=100, n3=200, n4=0)") + p.set_param("br", 255) + p.set_param("n1", 50) # Head moves 50 LEDs/second + p.set_param("n2", 100) # Max length 100 LEDs + p.set_param("n3", 200) # Tail moves 200 LEDs/second + p.set_param("n4", 0) # Min length 0 LEDs + p.set_param("cl", [(255, 0, 0)]) # Red + p.select("circle") + task = asyncio.create_task(p.run()) + start = utime.ticks_ms() + # Run for 5 seconds to see full cycle + while utime.ticks_diff(utime.ticks_ms(), start) < 5000: + wdt.feed() + await asyncio.sleep_ms(10) + await p.stop() + await task + + # Test 2: Slow growth, fast shrink (n1=20, n2=50, n3=100, n4=0) + print("Test 2: Slow growth, fast shrink (n1=20, n2=50, n3=100, n4=0)") + p.stopped = False + p.set_param("n1", 20) # Head moves 20 LEDs/second (slow) + p.set_param("n2", 50) # Max length 50 LEDs + p.set_param("n3", 100) # Tail moves 100 LEDs/second (fast) + p.set_param("n4", 0) # Min length 0 LEDs + p.set_param("cl", [(0, 255, 0)]) # Green + p.select("circle") + task = asyncio.create_task(p.run()) + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < 5000: + wdt.feed() + await asyncio.sleep_ms(10) + await p.stop() + await task + + # Test 3: Fast growth, slow shrink (n1=100, n2=30, n3=20, n4=0) + print("Test 3: Fast growth, slow shrink (n1=100, n2=30, n3=20, n4=0)") + p.stopped = False + p.set_param("n1", 100) # Head moves 100 LEDs/second (fast) + p.set_param("n2", 30) # Max length 30 LEDs + p.set_param("n3", 20) # Tail moves 20 LEDs/second (slow) + p.set_param("n4", 0) # Min length 0 LEDs + p.set_param("cl", [(0, 0, 255)]) # Blue + p.select("circle") + task = asyncio.create_task(p.run()) + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < 5000: + wdt.feed() + await asyncio.sleep_ms(10) + await p.stop() + await task + + # Test 4: With minimum length (n1=50, n2=40, n3=100, n4=10) + print("Test 4: With minimum length (n1=50, n2=40, n3=100, n4=10)") + p.stopped = False + p.set_param("n1", 50) # Head moves 50 LEDs/second + p.set_param("n2", 40) # Max length 40 LEDs + p.set_param("n3", 100) # Tail moves 100 LEDs/second + p.set_param("n4", 10) # Min length 10 LEDs (never fully disappears) + p.set_param("cl", [(255, 255, 0)]) # Yellow + p.select("circle") + task = asyncio.create_task(p.run()) + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < 5000: + wdt.feed() + await asyncio.sleep_ms(10) + await p.stop() + await task + + # Test 5: Very fast (n1=200, n2=20, n3=200, n4=0) + print("Test 5: Very fast (n1=200, n2=20, n3=200, n4=0)") + p.stopped = False + p.set_param("n1", 200) # Head moves 200 LEDs/second (very fast) + p.set_param("n2", 20) # Max length 20 LEDs + p.set_param("n3", 200) # Tail moves 200 LEDs/second (very fast) + p.set_param("n4", 0) # Min length 0 LEDs + p.set_param("cl", [(255, 0, 255)]) # Magenta + p.select("circle") + task = asyncio.create_task(p.run()) + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < 3000: + wdt.feed() + await asyncio.sleep_ms(10) + await p.stop() + await task + + # Test 6: Very slow (n1=10, n2=25, n3=10, n4=0) + print("Test 6: Very slow (n1=10, n2=25, n3=10, n4=0)") + p.stopped = False + p.set_param("n1", 10) # Head moves 10 LEDs/second (very slow) + p.set_param("n2", 25) # Max length 25 LEDs + p.set_param("n3", 10) # Tail moves 10 LEDs/second (very slow) + p.set_param("n4", 0) # Min length 0 LEDs + p.set_param("cl", [(0, 255, 255)]) # Cyan + p.select("circle") + task = asyncio.create_task(p.run()) + start = utime.ticks_ms() + while utime.ticks_diff(utime.ticks_ms(), start) < 5000: + wdt.feed() + await asyncio.sleep_ms(10) + await p.stop() + await task + + # Cleanup + print("Test complete, turning off") + p.stopped = False + p.select("off") + task = asyncio.create_task(p.run()) + await asyncio.sleep_ms(100) + await p.stop() + await task + + +if __name__ == "__main__": + asyncio.run(main()) +