Compare commits

1 Commits

Author SHA1 Message Date
a79c6f4dd3 fix(patterns): remove blocking sleeps from pattern loops
Replace sleep-based timing in pattern generators with non-blocking tick checks so long delays do not block the main loop and risk watchdog resets.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-04 22:37:33 +12:00
4 changed files with 73 additions and 35 deletions

View File

@@ -7,15 +7,23 @@ class BarGraph:
def run(self, preset): def run(self, preset):
colors = preset.c if preset.c else [(0, 255, 0), (255, 80, 0)] colors = preset.c if preset.c else [(0, 255, 0), (255, 80, 0)]
level = max(0, min(100, int(preset.n1) if int(preset.n1) >= 0 else 50)) last_update = utime.ticks_ms()
target = (self.driver.num_leds * level) // 100
lit = self.driver.apply_brightness(colors[0], preset.b)
unlit = self.driver.apply_brightness(colors[1] if len(colors) > 1 else (0, 0, 0), preset.b)
while True: while True:
for i in range(self.driver.num_leds): delay_ms = max(1, int(preset.d))
self.driver.n[i] = lit if i < target else unlit now = utime.ticks_ms()
self.driver.n.write() if utime.ticks_diff(now, last_update) >= delay_ms:
level = max(0, min(100, int(preset.n1) if int(preset.n1) >= 0 else 50))
target = (self.driver.num_leds * level) // 100
lit = self.driver.apply_brightness(colors[0], preset.b)
unlit = self.driver.apply_brightness(
colors[1] if len(colors) > 1 else (0, 0, 0),
preset.b,
)
for i in range(self.driver.num_leds):
self.driver.n[i] = lit if i < target else unlit
self.driver.n.write()
last_update = utime.ticks_add(last_update, delay_ms)
if not preset.a:
yield
return
yield yield
if not preset.a:
return
utime.sleep_ms(max(1, int(preset.d)))

View File

@@ -7,19 +7,24 @@ class Heartbeat:
def run(self, preset): def run(self, preset):
colors = preset.c if preset.c else [(255, 0, 40)] colors = preset.c if preset.c else [(255, 0, 40)]
p1 = max(20, int(preset.n1) if int(preset.n1) > 0 else 120) phase = 0
p2 = max(20, int(preset.n2) if int(preset.n2) > 0 else 80) phase_start = utime.ticks_ms()
pause = max(20, int(preset.n3) if int(preset.n3) > 0 else 500) lit_color = self.driver.apply_brightness(colors[0], preset.b)
while True: while True:
c = self.driver.apply_brightness(colors[0], preset.b) p1 = max(20, int(preset.n1) if int(preset.n1) > 0 else 120)
self.driver.fill(c) p2 = max(20, int(preset.n2) if int(preset.n2) > 0 else 80)
utime.sleep_ms(p1) pause = max(20, int(preset.n3) if int(preset.n3) > 0 else 500)
self.driver.fill((0, 0, 0)) beat_gap = max(20, int(preset.d))
utime.sleep_ms(max(20, int(preset.d))) lit_color = self.driver.apply_brightness(colors[0], preset.b)
self.driver.fill(c) phase_durations = (p1, beat_gap, p2, pause)
utime.sleep_ms(p2) phase_colors = (lit_color, (0, 0, 0), lit_color, (0, 0, 0))
self.driver.fill((0, 0, 0))
utime.sleep_ms(pause) now = utime.ticks_ms()
if utime.ticks_diff(now, phase_start) >= phase_durations[phase]:
phase = (phase + 1) % 4
phase_start = utime.ticks_add(phase_start, phase_durations[(phase - 1) % 4])
self.driver.fill(phase_colors[phase])
yield yield
if not preset.a: if not preset.a and phase == 0:
return return

View File

@@ -34,9 +34,15 @@ class PaletteMorph:
base_idx = 0 base_idx = 0
start = utime.ticks_ms() start = utime.ticks_ms()
phase = self.driver.step % 256 phase = self.driver.step % 256
last_update = start
while True: while True:
now = utime.ticks_ms() now = utime.ticks_ms()
delay_ms = max(1, int(preset.d))
if utime.ticks_diff(now, last_update) < delay_ms:
yield
continue
last_update = utime.ticks_add(last_update, delay_ms)
age = utime.ticks_diff(now, start) age = utime.ticks_diff(now, start)
if age < morph: if age < morph:
t = (age * 255) // morph t = (age * 255) // morph
@@ -72,5 +78,4 @@ class PaletteMorph:
return return
phase = (phase + warp_rate) & 255 phase = (phase + warp_rate) & 255
self.driver.step = phase self.driver.step = phase
utime.sleep_ms(max(1, int(preset.d)))
yield yield

View File

@@ -7,18 +7,38 @@ class StrobeBurst:
def run(self, preset): def run(self, preset):
colors = preset.c if preset.c else [(255, 255, 255)] colors = preset.c if preset.c else [(255, 255, 255)]
count = max(1, int(preset.n1) if int(preset.n1) > 0 else 3) state = "flash_on"
gap = max(1, int(preset.n2) if int(preset.n2) > 0 else 60) flash_idx = 0
cooldown = max(1, int(preset.n3) if int(preset.n3) > 0 else 400) state_start = utime.ticks_ms()
c = self.driver.apply_brightness(colors[0], preset.b)
while True: while True:
for _ in range(count): count = max(1, int(preset.n1) if int(preset.n1) > 0 else 3)
gap = max(1, int(preset.n2) if int(preset.n2) > 0 else 60)
cooldown = max(1, int(preset.n3) if int(preset.n3) > 0 else 400)
on_ms = max(1, int(preset.d) // 2)
c = self.driver.apply_brightness(colors[0], preset.b)
now = utime.ticks_ms()
if state == "flash_on":
self.driver.fill(c) self.driver.fill(c)
utime.sleep_ms(max(1, int(preset.d)//2)) if utime.ticks_diff(now, state_start) >= on_ms:
state = "flash_off"
state_start = utime.ticks_add(state_start, on_ms)
elif state == "flash_off":
self.driver.fill((0, 0, 0)) self.driver.fill((0, 0, 0))
utime.sleep_ms(gap) if utime.ticks_diff(now, state_start) >= gap:
yield flash_idx += 1
utime.sleep_ms(cooldown) if flash_idx >= count:
if not preset.a:
return
state = "cooldown"
flash_idx = 0
state_start = utime.ticks_add(state_start, gap)
else:
state = "flash_on"
state_start = utime.ticks_add(state_start, gap)
else:
self.driver.fill((0, 0, 0))
if utime.ticks_diff(now, state_start) >= cooldown:
state = "flash_on"
state_start = utime.ticks_add(state_start, cooldown)
yield yield
if not preset.a:
return