import utime class Circle: def __init__(self, driver): self.driver = driver def run(self, preset): """Circle loading pattern - grows to n2, then tail moves forward at n3 until min length n4""" head = 0 tail = 0 # Calculate timing from preset head_rate = max(1, int(preset.n1)) # n1 = head moves per second tail_rate = max(1, int(preset.n3)) # n3 = tail moves per second max_length = max(1, int(preset.n2)) # n2 = max length min_length = max(0, int(preset.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" # Support up to two colors (like chase). If only one color is provided, # use black for the second; if none, default to white. colors = preset.c if not colors: base0 = base1 = (255, 255, 255) elif len(colors) == 1: base0 = colors[0] base1 = (0, 0, 0) else: base0 = colors[0] base1 = colors[1] color0 = self.driver.apply_brightness(base0, preset.b) color1 = self.driver.apply_brightness(base1, preset.b) while True: current_time = utime.ticks_ms() # Background: use second color during the "off" phase, otherwise clear to black if phase == "off": self.driver.n.fill(color1) else: self.driver.n.fill((0, 0, 0)) # Calculate segment length segment_length = (head - tail) % self.driver.num_leds if segment_length == 0 and head != tail: segment_length = self.driver.num_leds # Draw segment from tail to head as a solid color (no per-LED alternation) current_color = color0 for i in range(segment_length + 1): led_pos = (tail + i) % self.driver.num_leds self.driver.n[led_pos] = current_color # Move head continuously at n1 LEDs per second if utime.ticks_diff(current_time, last_head_move) >= head_delay: head = (head + 1) % self.driver.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.driver.num_leds last_tail_move = current_time # Check if we've reached min length current_length = (head - tail) % self.driver.num_leds if current_length == 0 and head != tail: current_length = self.driver.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: second color fills the ring for 1 step, then restart tail = head # Reset tail to head position to start fresh phase = "growing" self.driver.n.write() # Yield once per tick so other logic can run yield