from machine import Pin from neopixel import NeoPixel import utime import random import _thread import asyncio import json from presets import Presets # Short-key parameter mapping for convenience setters param_mapping = { "pt": "selected", "pa": "selected", "cl": "colors", "br": "brightness", "dl": "delay", "nl": "num_leds", "co": "color_order", "lp": "led_pin", "n1": "n1", "n2": "n2", "n3": "n3", "n4": "n4", "n5": "n5", "n6": "n6", "auto": "auto", } class Patterns: def __init__(self, pin, num_leds, color1=(0,0,0), color2=(0,0,0), brightness=127, selected="rainbow_cycle", delay=100): self.n = NeoPixel(Pin(pin, Pin.OUT), num_leds) self.num_leds = num_leds self.pattern_step = 0 self.last_update = utime.ticks_ms() self.delay = delay self.brightness = brightness self.auto = False self.patterns = {} self.selected = selected # Ensure colors list always starts with at least two for robust transition handling self.colors = [color1, color2] if color1 != color2 else [color1, (255, 255, 255)] # Fallback if initial colors are same if not self.colors: # Ensure at least one color exists self.colors = [(0, 0, 0)] self.transition_duration = delay * 50 # Default transition duration self.hold_duration = delay * 10 # Default hold duration at each color self.transition_step = 0 # Current step in the transition self.current_color_idx = 0 # Index of the color currently being held/transitioned from self.current_color = self.colors[self.current_color_idx] # The actual blended color self.hold_start_time = utime.ticks_ms() # Time when the current color hold started # New attributes for scanner patterns self.scanner_direction = 1 # 1 for forward, -1 for backward self.scanner_tail_length = 3 # Number of trailing pixels self.running = False self.stopped = True self.presets = Presets() self.n1 = 0 self.n2 = 0 self.n3 = 0 self.n4 = 0 self.n5 = 0 self.n6 = 0 def select(self, pattern): if pattern in self.patterns: self.selected = pattern return True return False async def run(self): print(f"Stopping pattern") await self.stop() self.running = True print(f"Starting pattern {self.selected}") if self.selected in self.patterns: _thread.start_new_thread(self.patterns[self.selected], ()) else: print(f"Pattern {self.selected} not found") async def stop(self): self.running = False start = utime.ticks_ms() while not self.stopped and utime.ticks_diff(utime.ticks_ms(), start) < 1000: await asyncio.sleep_ms(0) self.stopped = True def set_param(self, key, value): if key in param_mapping: setattr(self, param_mapping[key], value) return True print(f"Invalid parameter: {key}") return False def update_num_leds(self, pin, num_leds): self.n = NeoPixel(Pin(pin, Pin.OUT), num_leds) self.num_leds = num_leds self.pattern_step = 0 def set_color(self, num, color): # Changed: More robust index check if 0 <= num < len(self.colors): self.colors[num] = color # If the changed color is part of the current or next transition, # restart the transition for smoother updates return True elif num == len(self.colors): # Allow setting a new color at the end self.colors.append(color) return True return False def del_color(self, num): # Changed: More robust index check and using del for lists if 0 <= num < len(self.colors): del self.colors[num] return True return False def apply_brightness(self, color, brightness_override=None): effective_brightness = brightness_override if brightness_override is not None else self.brightness 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 self.colors[0] for i in range(self.num_leds): self.n[i] = fill_color self.n.write() def off(self): self.fill((0, 0, 0)) def on(self): self.fill(self.apply_brightness(self.colors[0])) def wheel(self, 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)