From 90e1511651317d975c0003269b9efc47e0bf5666 Mon Sep 17 00:00:00 2001 From: jimmy Date: Sat, 30 Aug 2025 21:31:24 +1200 Subject: [PATCH] Switch to using itterators --- src/patterns.py | 323 ++++++++++++++++++++++--------------------- src/patterns_base.py | 24 +++- 2 files changed, 186 insertions(+), 161 deletions(-) diff --git a/src/patterns.py b/src/patterns.py index 7afe788..6515501 100644 --- a/src/patterns.py +++ b/src/patterns.py @@ -8,205 +8,220 @@ class Patterns(PatternsBase): def __init__(self, pin, num_leds, color1=(0,0,0), color2=(0,0,0), brightness=127, selected="rainbow_cycle", delay=100): super().__init__(pin, num_leds, color1, color2, brightness, selected, delay) self.patterns = { - "off": self.off, - "on" : self.on, - "color_wipe": self.color_wipe_step, - "rainbow_cycle": self.rainbow_cycle_step, - "theater_chase": self.theater_chase_step, - "blink": self.blink_step, - "color_transition": self.color_transition_step, # Added new pattern - "flicker": self.flicker_step, - "scanner": self.scanner_step, # New: Single direction scanner - "bidirectional_scanner": self.bidirectional_scanner_step, # New: Bidirectional scanner - "external": None + "off": self.off(), + "on" : self.on(), + "color_wipe": self.color_wipe_step(), + "rainbow_cycle": self.rainbow_cycle_step(), + "theater_chase": self.theater_chase_step(), + "blink": self.blink_step(), + "color_transition": self.color_transition_step(), # Added new pattern + "flicker": self.flicker_step(), + "scanner": self.scanner_step(), # New: Single direction scanner + "bidirectional_scanner": self.bidirectional_scanner_step(), # New: Bidirectional scanner } def color_wipe_step(self): - color = self.apply_brightness(self.colors[0]) - current_time = utime.ticks_ms() - if utime.ticks_diff(current_time, self.last_update) >= self.delay: - if self.pattern_step < self.num_leds: - for i in range(self.num_leds): - self.n[i] = (0, 0, 0) - self.n[self.pattern_step] = self.apply_brightness(color) - self.n.write() - self.pattern_step += 1 - else: - self.pattern_step = 0 - self.last_update = current_time + while True: + color = self.apply_brightness(self.colors[0]) + current_time = utime.ticks_ms() + if utime.ticks_diff(current_time, self.last_update) >= self.delay: + if self.pattern_step < self.num_leds: + for i in range(self.num_leds): + self.n[i] = (0, 0, 0) + self.n[self.pattern_step] = self.apply_brightness(color) + self.n.write() + self.pattern_step += 1 + else: + self.pattern_step = 0 + self.last_update = current_time + yield def rainbow_cycle_step(self): - current_time = utime.ticks_ms() - if utime.ticks_diff(current_time, self.last_update) >= self.delay/5: - def wheel(pos): - if pos < 85: - return (pos * 3, 255 - pos * 3, 0) - elif pos < 170: - pos -= 85 - return (255 - pos * 3, 0, pos * 3) - else: - pos -= 170 - return (0, pos * 3, 255 - pos * 3) + while True: + current_time = utime.ticks_ms() + if utime.ticks_diff(current_time, self.last_update) >= self.delay/5: + def wheel(pos): + if pos < 85: + return (pos * 3, 255 - pos * 3, 0) + elif pos < 170: + pos -= 85 + return (255 - pos * 3, 0, pos * 3) + else: + pos -= 170 + return (0, pos * 3, 255 - pos * 3) - for i in range(self.num_leds): - rc_index = (i * 256 // self.num_leds) + self.pattern_step - self.n[i] = self.apply_brightness(wheel(rc_index & 255)) - self.n.write() - self.pattern_step = (self.pattern_step + 1) % 256 - self.last_update = current_time + for i in range(self.num_leds): + rc_index = (i * 256 // self.num_leds) + self.pattern_step + self.n[i] = self.apply_brightness(wheel(rc_index & 255)) + self.n.write() + self.pattern_step = (self.pattern_step + 1) % 256 + self.last_update = current_time + yield def theater_chase_step(self): - current_time = utime.ticks_ms() - if utime.ticks_diff(current_time, self.last_update) >= self.delay: - for i in range(self.num_leds): - if (i + self.pattern_step) % 3 == 0: - self.n[i] = self.apply_brightness(self.colors[0]) - else: - self.n[i] = (0, 0, 0) - self.n.write() - self.pattern_step = (self.pattern_step + 1) % 3 - self.last_update = current_time + while True: + current_time = utime.ticks_ms() + if utime.ticks_diff(current_time, self.last_update) >= self.delay: + for i in range(self.num_leds): + if (i + self.pattern_step) % 3 == 0: + self.n[i] = self.apply_brightness(self.colors[0]) + else: + self.n[i] = (0, 0, 0) + self.n.write() + self.pattern_step = (self.pattern_step + 1) % 3 + self.last_update = current_time + yield def blink_step(self): - current_time = utime.ticks_ms() - if utime.ticks_diff(current_time, self.last_update) >= self.delay: - if self.pattern_step % 2 == 0: - self.fill(self.apply_brightness(self.colors[0])) - else: - self.fill((0, 0, 0)) - self.pattern_step = (self.pattern_step + 1) % 2 - self.last_update = current_time + while True: + current_time = utime.ticks_ms() + if utime.ticks_diff(current_time, self.last_update) >= self.delay: + if self.pattern_step % 2 == 0: + self.fill(self.apply_brightness(self.colors[0])) + else: + self.fill((0, 0, 0)) + self.pattern_step = (self.pattern_step + 1) % 2 + self.last_update = current_time + yield def color_transition_step(self): - current_time = utime.ticks_ms() + while True: + current_time = utime.ticks_ms() - # Check for hold duration first - if utime.ticks_diff(current_time, self.hold_start_time) < self.hold_duration: - # Still in hold phase, just display the current solid color - self.fill(self.apply_brightness(self.current_color)) - self.last_update = current_time # Keep updating last_update to avoid skipping frames - return + # Check for hold duration first + if utime.ticks_diff(current_time, self.hold_start_time) < self.hold_duration: + # Still in hold phase, just display the current solid color + self.fill(self.apply_brightness(self.current_color)) + self.last_update = current_time # Keep updating last_update to avoid skipping frames + yield - # If hold duration is over, proceed with transition - if utime.ticks_diff(current_time, self.last_update) >= self.delay: - num_colors = len(self.colors) - if num_colors < 2: - # Should not happen if select handles it, but as a safeguard - self.select("on") - return + # If hold duration is over, proceed with transition + if utime.ticks_diff(current_time, self.last_update) >= self.delay: + num_colors = len(self.colors) + if num_colors < 2: + # Should not happen if select handles it, but as a safeguard + self.select("on") + yield - from_color = self.colors[self.current_color_idx] - to_color_idx = (self.current_color_idx + 1) % num_colors - to_color = self.colors[to_color_idx] + from_color = self.colors[self.current_color_idx] + to_color_idx = (self.current_color_idx + 1) % num_colors + to_color = self.colors[to_color_idx] - # Calculate interpolation factor (0.0 to 1.0) - # transition_step goes from 0 to transition_duration - 1 - if self.transition_duration > 0: - interp_factor = self.transition_step / self.transition_duration - else: - interp_factor = 1.0 # Immediately transition if duration is zero + # Calculate interpolation factor (0.0 to 1.0) + # transition_step goes from 0 to transition_duration - 1 + if self.transition_duration > 0: + interp_factor = self.transition_step / self.transition_duration + else: + interp_factor = 1.0 # Immediately transition if duration is zero - # Interpolate each color component - r = int(from_color[0] + (to_color[0] - from_color[0]) * interp_factor) - g = int(from_color[1] + (to_color[1] - from_color[1]) * interp_factor) - b = int(from_color[2] + (to_color[2] - from_color[2]) * interp_factor) + # Interpolate each color component + r = int(from_color[0] + (to_color[0] - from_color[0]) * interp_factor) + g = int(from_color[1] + (to_color[1] - from_color[1]) * interp_factor) + b = int(from_color[2] + (to_color[2] - from_color[2]) * interp_factor) - self.current_color = (r, g, b) - self.fill(self.apply_brightness(self.current_color)) + self.current_color = (r, g, b) + self.fill(self.apply_brightness(self.current_color)) - self.transition_step += self.delay # Advance the transition step by the delay + self.transition_step += self.delay # Advance the transition step by the delay - if self.transition_step >= self.transition_duration: - # Transition complete, move to the next color and reset for hold phase - self.current_color_idx = to_color_idx - self.current_color = self.colors[self.current_color_idx] # Ensure current_color is the exact target color - self.transition_step = 0 # Reset transition progress - self.hold_start_time = current_time # Start hold phase for the new color + if self.transition_step >= self.transition_duration: + # Transition complete, move to the next color and reset for hold phase + self.current_color_idx = to_color_idx + self.current_color = self.colors[self.current_color_idx] # Ensure current_color is the exact target color + self.transition_step = 0 # Reset transition progress + self.hold_start_time = current_time # Start hold phase for the new color - self.last_update = current_time + self.last_update = current_time + yield def flicker_step(self): - current_time = utime.ticks_ms() - if utime.ticks_diff(current_time, self.last_update) >= self.delay/5: - base_color = self.colors[0] - # Increase the range for flicker_brightness_offset - # Changed from self.brightness // 4 to self.brightness // 2 (or even self.brightness for max intensity) - flicker_brightness_offset = random.randint(-int(self.brightness // 1.5), int(self.brightness // 1.5)) - flicker_brightness = max(0, min(255, self.brightness + flicker_brightness_offset)) + while True: + current_time = utime.ticks_ms() + if utime.ticks_diff(current_time, self.last_update) >= self.delay/5: + base_color = self.colors[0] + # Increase the range for flicker_brightness_offset + # Changed from self.brightness // 4 to self.brightness // 2 (or even self.brightness for max intensity) + flicker_brightness_offset = random.randint(-int(self.brightness // 1.5), int(self.brightness // 1.5)) + flicker_brightness = max(0, min(255, self.brightness + flicker_brightness_offset)) - flicker_color = self.apply_brightness(base_color, brightness_override=flicker_brightness) - self.fill(flicker_color) - self.last_update = current_time + flicker_color = self.apply_brightness(base_color, brightness_override=flicker_brightness) + self.fill(flicker_color) + self.last_update = current_time + yield def scanner_step(self): """ Mimics a 'Knight Rider' style scanner, moving in one direction. """ - current_time = utime.ticks_ms() - if utime.ticks_diff(current_time, self.last_update) >= self.delay: - self.fill((0, 0, 0)) # Clear all LEDs + while True: + current_time = utime.ticks_ms() + if utime.ticks_diff(current_time, self.last_update) >= self.delay: + self.fill((0, 0, 0)) # Clear all LEDs - # Calculate the head and tail position - head_pos = self.pattern_step - color = self.apply_brightness(self.colors[0]) + # Calculate the head and tail position + head_pos = self.pattern_step + color = self.apply_brightness(self.colors[0]) - # Draw the head - if 0 <= head_pos < self.num_leds: - self.n[head_pos] = color + # Draw the head + if 0 <= head_pos < self.num_leds: + self.n[head_pos] = color - # Draw the trailing pixels with decreasing brightness - for i in range(1, self.scanner_tail_length + 1): - tail_pos = head_pos - i - if 0 <= tail_pos < self.num_leds: - # Calculate fading color for tail - # Example: linear fade from full brightness to off - fade_factor = 1.0 - (i / (self.scanner_tail_length + 1)) - faded_color = tuple(int(c * fade_factor) for c in color) - self.n[tail_pos] = faded_color + # Draw the trailing pixels with decreasing brightness + for i in range(1, self.scanner_tail_length + 1): + tail_pos = head_pos - i + if 0 <= tail_pos < self.num_leds: + # Calculate fading color for tail + # Example: linear fade from full brightness to off + fade_factor = 1.0 - (i / (self.scanner_tail_length + 1)) + faded_color = tuple(int(c * fade_factor) for c in color) + self.n[tail_pos] = faded_color - self.n.write() + self.n.write() - self.pattern_step += 1 - if self.pattern_step >= self.num_leds + self.scanner_tail_length: - self.pattern_step = 0 # Reset to start + self.pattern_step += 1 + if self.pattern_step >= self.num_leds + self.scanner_tail_length: + self.pattern_step = 0 # Reset to start - self.last_update = current_time + self.last_update = current_time + yield def bidirectional_scanner_step(self): """ Mimics a 'Knight Rider' style scanner, moving back and forth. """ - current_time = utime.ticks_ms() - if utime.ticks_diff(current_time, self.last_update) >= self.delay/100: - self.fill((0, 0, 0)) # Clear all LEDs + while True: + current_time = utime.ticks_ms() + if utime.ticks_diff(current_time, self.last_update) >= self.delay/100: + self.fill((0, 0, 0)) # Clear all LEDs - color = self.apply_brightness(self.colors[0]) + color = self.apply_brightness(self.colors[0]) - # Calculate the head position based on direction - head_pos = self.pattern_step + # Calculate the head position based on direction + head_pos = self.pattern_step - # Draw the head - if 0 <= head_pos < self.num_leds: - self.n[head_pos] = color + # Draw the head + if 0 <= head_pos < self.num_leds: + self.n[head_pos] = color - # Draw the trailing pixels with decreasing brightness - for i in range(1, self.scanner_tail_length + 1): - tail_pos = head_pos - (i * self.scanner_direction) - if 0 <= tail_pos < self.num_leds: - fade_factor = 1.0 - (i / (self.scanner_tail_length + 1)) - faded_color = tuple(int(c * fade_factor) for c in color) - self.n[tail_pos] = faded_color + # Draw the trailing pixels with decreasing brightness + for i in range(1, self.scanner_tail_length + 1): + tail_pos = head_pos - (i * self.scanner_direction) + if 0 <= tail_pos < self.num_leds: + fade_factor = 1.0 - (i / (self.scanner_tail_length + 1)) + faded_color = tuple(int(c * fade_factor) for c in color) + self.n[tail_pos] = faded_color - self.n.write() + self.n.write() - self.pattern_step += self.scanner_direction + self.pattern_step += self.scanner_direction - # Change direction if boundaries are reached - if self.scanner_direction == 1 and self.pattern_step >= self.num_leds: - self.scanner_direction = -1 - self.pattern_step = self.num_leds - 1 # Start moving back from the last LED - elif self.scanner_direction == -1 and self.pattern_step < 0: - self.scanner_direction = 1 - self.pattern_step = 0 # Start moving forward from the first LED + # Change direction if boundaries are reached + if self.scanner_direction == 1 and self.pattern_step >= self.num_leds: + self.scanner_direction = -1 + self.pattern_step = self.num_leds - 1 # Start moving back from the last LED + elif self.scanner_direction == -1 and self.pattern_step < 0: + self.scanner_direction = 1 + self.pattern_step = 0 # Start moving forward from the first LED - self.last_update = current_time + self.last_update = current_time + yield diff --git a/src/patterns_base.py b/src/patterns_base.py index a0e9178..30d964f 100644 --- a/src/patterns_base.py +++ b/src/patterns_base.py @@ -47,10 +47,6 @@ class PatternsBase: def set_pattern_step(self, step): self.pattern_step = step - def tick(self): - if self.patterns[self.selected]: - self.patterns[self.selected]() - def update_num_leds(self, pin, num_leds): self.n = NeoPixel(Pin(pin, Pin.OUT), num_leds) self.num_leds = num_leds @@ -158,8 +154,9 @@ class PatternsBase: return tuple(int(c * effective_brightness / 255) for c in color) def select(self, pattern): - if pattern in self.patterns: + if pattern in self.patterns and hasattr(self.patterns[pattern], "__next__"): self.selected = pattern + self.run = True self.sync() # Reset pattern state when selecting a new pattern if pattern == "color_transition": if len(self.colors) < 2: @@ -176,6 +173,13 @@ class PatternsBase: return True return False + def tick(self): + if self.run: + try: + next(self.patterns[self.selected]) + except StopIteration: + self.run = False + def set(self, i, color): self.n[i] = color @@ -189,7 +193,13 @@ class PatternsBase: self.n.write() def off(self): - self.fill((0, 0, 0)) + while True: + self.fill((0, 0, 0)) + self.run = False + yield def on(self): - self.fill(self.apply_brightness(self.colors[0])) + while True: + self.fill(self.apply_brightness(self.colors[0])) + self.run = False + yield