import utime class Pulse: def __init__(self, driver): self.driver = driver def run(self, preset): # Get colors from preset colors = preset.c if not colors: colors = [(255, 255, 255)] bg_base = preset.background_or(colors) self.driver.fill(self.driver.apply_brightness(bg_base, preset.b)) manual = not preset.a color_index = self.driver.step % max(1, len(colors)) cycle_start = utime.ticks_ms() # State machine based pulse using a single generator loop while True: bg_color = self.driver.apply_brightness(bg_base, preset.b) # Read current timing parameters from preset attack_ms = max(0, int(preset.n1)) # Attack time in ms hold_ms = max(0, int(preset.n2)) # Hold time in ms decay_ms = max(0, int(preset.n3)) # Decay time in ms delay_ms = 0 if manual else max(0, int(preset.d)) total_ms = attack_ms + hold_ms + decay_ms + delay_ms if total_ms <= 0: total_ms = 1 now = utime.ticks_ms() elapsed = utime.ticks_diff(now, cycle_start) base_color = colors[color_index % len(colors)] if elapsed < attack_ms and attack_ms > 0: # Attack: fade 0 -> 1 factor = elapsed / attack_ms color = tuple(int(c * factor) for c in base_color) self.driver.fill(self.driver.apply_brightness(color, preset.b)) elif elapsed < attack_ms + hold_ms: # Hold: full brightness self.driver.fill(self.driver.apply_brightness(base_color, preset.b)) elif elapsed < attack_ms + hold_ms + decay_ms and decay_ms > 0: # Decay: fade 1 -> 0 dec_elapsed = elapsed - attack_ms - hold_ms factor = max(0.0, 1.0 - (dec_elapsed / decay_ms)) color = tuple(int(c * factor) for c in base_color) self.driver.fill(self.driver.apply_brightness(color, preset.b)) elif elapsed < total_ms: # Delay phase: LEDs off between pulses self.driver.fill(bg_color) else: # End of cycle: advance colour for the next run, then loop or stop. nclr = max(1, len(colors)) color_index = (color_index + 1) % nclr self.driver.step = color_index if manual: self.driver.fill(bg_color) break cycle_start = now yield continue # Yield once per tick yield