import utime class Pulse: def __init__(self, driver): self.driver = driver def run(self, preset): self.driver.off() # Get colors from preset colors = preset.c if not colors: colors = [(255, 255, 255)] color_index = 0 cycle_start = utime.ticks_ms() # State machine based pulse using a single generator loop while True: # 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 = 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((0, 0, 0)) else: # End of cycle, move to next color and restart timing color_index += 1 cycle_start = now if not preset.a: break # Skip drawing this tick, start next cycle yield continue # Yield once per tick yield