import utime from patterns.pattern_modes import style_mode _LEGACY = {"comet_dual": 1, "scanner": 2} class Meteor: def __init__(self, driver): self.driver = driver def _fade(self, color, fade_amount): return ( (color[0] * fade_amount) // 255, (color[1] * fade_amount) // 255, (color[2] * fade_amount) // 255, ) def _run_meteor(self, preset, colors, color_index, head, direction, last_update): tail_len = max(1, int(preset.n1) if int(preset.n1) > 0 else 8) speed = max(1, int(preset.n2) if int(preset.n2) > 0 else 1) fade_amount = int(preset.n3) if int(preset.n3) > 0 else 192 fade_amount = max(1, min(255, fade_amount)) delay_ms = max(1, int(preset.d)) now = utime.ticks_ms() if utime.ticks_diff(now, last_update) < delay_ms: return color_index, head, direction, last_update, False for i in range(self.driver.num_leds): self.driver.n[i] = self._fade(self.driver.n[i], fade_amount) base = colors[color_index % len(colors)] lit = self.driver.apply_brightness(base, preset.b) if 0 <= head < self.driver.num_leds: self.driver.n[self.driver.led_i(preset, head)] = lit self.driver.n.write() head += self.driver.signed(preset, direction * speed) if head >= self.driver.num_leds + tail_len: head = self.driver.num_leds - 1 direction = -1 color_index += 1 elif head < -tail_len: head = 0 direction = 1 color_index += 1 return color_index, head, direction, utime.ticks_add(last_update, delay_ms), True def _run_comet_dual(self, preset, colors, p1, p2, last): tail = max(1, int(preset.n1) if int(preset.n1) > 0 else 6) speed = max(1, int(preset.n2) if int(preset.n2) > 0 else 1) gap = max(0, int(preset.n3)) d = max(1, int(preset.d)) now = utime.ticks_ms() if utime.ticks_diff(now, last) < d: return p1, p2, last, False bg_color = self.driver.apply_brightness(preset.background_or(colors), preset.b) for i in range(self.driver.num_leds): self.driver.n[i] = bg_color c1 = self.driver.apply_brightness(colors[0 % len(colors)], preset.b) c2 = self.driver.apply_brightness( colors[1 % len(colors)] if len(colors) > 1 else colors[0], preset.b ) for t in range(tail): i1 = p1 - t if 0 <= i1 < self.driver.num_leds: s = (255 * (tail - t)) // max(1, tail) self.driver.n[self.driver.led_i(preset, i1)] = ( (c1[0] * s) // 255, (c1[1] * s) // 255, (c1[2] * s) // 255, ) i2 = p2 + t if 0 <= i2 < self.driver.num_leds: s = (255 * (tail - t)) // max(1, tail) self.driver.n[self.driver.led_i(preset, i2)] = ( (c2[0] * s) // 255, (c2[1] * s) // 255, (c2[2] * s) // 255, ) self.driver.n.write() p1 += self.driver.signed(preset, speed) p2 -= self.driver.signed(preset, speed) if p1 - tail > self.driver.num_leds and p2 + tail < 0: p1 = 0 p2 = self.driver.num_leds - 1 - gap return p1, p2, utime.ticks_add(last, d), True def _run_scanner(self, preset, colors, color_index, center, direction, pause_frames, last_update): width = max(1, int(preset.n1) if int(preset.n1) > 0 else 4) end_pause = max(0, int(preset.n2)) delay_ms = max(1, int(preset.d)) now = utime.ticks_ms() if utime.ticks_diff(now, last_update) < delay_ms: return color_index, center, direction, pause_frames, last_update, False base = self.driver.apply_brightness(colors[color_index % len(colors)], preset.b) bg_color = self.driver.apply_brightness(preset.background_or(colors), preset.b) for i in range(self.driver.num_leds): dist = i - center if dist < 0: dist = -dist if dist > width: self.driver.n[self.driver.led_i(preset, i)] = bg_color else: scale = ((width - dist) * 255) // max(1, width) self.driver.n[self.driver.led_i(preset, i)] = ( (base[0] * scale) // 255, (base[1] * scale) // 255, (base[2] * scale) // 255, ) self.driver.n.write() if pause_frames > 0: pause_frames -= 1 else: center += self.driver.signed(preset, direction) if center >= self.driver.num_leds - 1: center = self.driver.num_leds - 1 direction = -1 pause_frames = end_pause color_index += 1 elif center <= 0: center = 0 direction = 1 pause_frames = end_pause color_index += 1 return color_index, center, direction, pause_frames, utime.ticks_add(last_update, delay_ms), True def run(self, preset): """Moving lights: n6 style 0 meteor, 1 dual comet, 2 scanner (legacy ids still work).""" mode = style_mode(preset, 0, _LEGACY) colors = preset.c if preset.c else [(255, 255, 255)] if mode == 1: gap = max(0, int(preset.n3)) nled = self.driver.num_leds if self.driver.is_reversed(preset): p1, p2 = nled - 1, gap else: p1, p2 = 0, nled - 1 - gap last = utime.ticks_ms() while True: p1, p2, last, stepped = self._run_comet_dual(preset, colors, p1, p2, last) if stepped and not preset.a: yield return yield if mode == 2: nled = self.driver.num_leds if self.driver.is_reversed(preset): color_index, center, direction, pause_frames = 0, max(0, nled - 1), -1, 0 else: color_index, center, direction, pause_frames = 0, 0, 1, 0 last_update = utime.ticks_ms() while True: color_index, center, direction, pause_frames, last_update, stepped = ( self._run_scanner( preset, colors, color_index, center, direction, pause_frames, last_update ) ) if stepped and not preset.a: yield return yield nled = self.driver.num_leds if self.driver.is_reversed(preset): color_index, head, direction = 0, max(0, nled - 1), -1 else: color_index, head, direction = 0, 0, 1 last_update = utime.ticks_ms() while True: color_index, head, direction, last_update, stepped = self._run_meteor( preset, colors, color_index, head, direction, last_update ) if stepped and not preset.a: yield return yield