177 lines
7.1 KiB
Python
177 lines
7.1 KiB
Python
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
|