patterns: fix blink timing; slow alternating; unify self-test with absolute tick scheduling

This commit is contained in:
2025-09-15 14:12:43 +12:00
parent d68817ea18
commit 93560a253e
2 changed files with 53 additions and 56 deletions

View File

@@ -1,3 +1,4 @@
import utime
import random
from patterns_base import PatternBase # Import PatternBase
@@ -91,9 +92,11 @@ class Patterns(PatternBase): # Inherit from PatternBase
def off(self):
self.fill((0, 0, 0))
return self.delay
def on(self):
self.fill(self.apply_brightness(self.colors[0]))
return self.delay
def color_wipe(self):
color = self.apply_brightness(self.colors[0])
@@ -107,6 +110,7 @@ class Patterns(PatternBase): # Inherit from PatternBase
else:
self.pattern_step = 0
self.last_update = current_time
return self.delay
def rainbow_cycle(self):
current_time = utime.ticks_ms()
@@ -126,8 +130,10 @@ class Patterns(PatternBase): # Inherit from PatternBase
self.n.write()
self.pattern_step = (self.pattern_step + 1) % 256
self.last_update = current_time
return max(1, int(self.delay // 5))
def theater_chase(self):
current_time = utime.ticks_ms()
segment_length = self.on_width + self.off_width
for i in range(self.num_leds):
if (i + self.pattern_step) % segment_length < self.on_width:
@@ -136,6 +142,8 @@ class Patterns(PatternBase): # Inherit from PatternBase
self.n[i] = (0, 0, 0)
self.n.write()
self.pattern_step = (self.pattern_step + 1) % segment_length
self.last_update = current_time
return self.delay
def blink(self):
current_time = utime.ticks_ms()
@@ -145,6 +153,7 @@ class Patterns(PatternBase): # Inherit from PatternBase
self.fill((0, 0, 0))
self.pattern_step = (self.pattern_step + 1) % 2
self.last_update = current_time
return self.delay
def color_transition(self):
current_time = utime.ticks_ms()
@@ -154,7 +163,7 @@ class Patterns(PatternBase): # Inherit from PatternBase
# Still in hold phase, just display the current solid color
self.fill(self.apply_brightness(self.current_color))
self.last_update = current_time # Keep updating last_update to avoid skipping frames
return
return self.delay
# If hold duration is over, proceed with transition
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
@@ -162,7 +171,7 @@ class Patterns(PatternBase): # Inherit from PatternBase
if num_colors < 2:
# Should not happen if select handles it, but as a safeguard
self.select("on")
return
return self.delay
from_color = self.colors[self.current_color_idx]
to_color_idx = (self.current_color_idx + 1) % num_colors
@@ -193,6 +202,7 @@ class Patterns(PatternBase): # Inherit from PatternBase
self.hold_start_time = current_time # Start hold phase for the new color
self.last_update = current_time
return self.delay
def flicker(self):
current_time = utime.ticks_ms()
@@ -205,6 +215,7 @@ class Patterns(PatternBase): # Inherit from PatternBase
flicker_color = self.apply_brightness(base_color, brightness_override=flicker_brightness)
self.fill(flicker_color)
self.last_update = current_time
return max(1, int(self.delay // 5))
def scanner(self):
"""
@@ -238,6 +249,7 @@ class Patterns(PatternBase): # Inherit from PatternBase
self.pattern_step = 0 # Reset to start
self.last_update = current_time
return self.delay
def bidirectional_scanner(self):
"""
@@ -276,6 +288,7 @@ class Patterns(PatternBase): # Inherit from PatternBase
self.pattern_step = 0 # Start moving forward from the first LED
self.last_update = current_time
return self.delay
def fill_range(self):
"""
@@ -290,9 +303,10 @@ class Patterns(PatternBase): # Inherit from PatternBase
for i in range(self.n1, self.n2 + 1):
self.n[i] = color
self.n.write()
if self.oneshot:
self.pattern_step += 1 # Increment only for one-shot
self.last_update = current_time
return self.delay
self.last_update = current_time
return self.delay
def n_chase(self):
"""
@@ -304,7 +318,7 @@ class Patterns(PatternBase): # Inherit from PatternBase
self.fill((0,0,0))
self.n.write()
self.last_update = current_time
return
return self.delay
for i in range(self.num_leds):
if (i + self.pattern_step) % segment_length < self.n1:
@@ -314,6 +328,7 @@ class Patterns(PatternBase): # Inherit from PatternBase
self.n.write()
self.pattern_step = (self.pattern_step + 1) % segment_length
self.last_update = current_time
return self.delay
def alternating(self):
"""
@@ -325,7 +340,7 @@ class Patterns(PatternBase): # Inherit from PatternBase
self.fill((0,0,0))
self.n.write()
self.last_update = current_time
return
return self.delay
# current_phase will alternate between 0 and 1
current_phase = self.pattern_step % 2
@@ -348,6 +363,7 @@ class Patterns(PatternBase): # Inherit from PatternBase
self.n.write()
self.pattern_step = (self.pattern_step + 1) % 2 # Toggle between 0 and 1
self.last_update = current_time
return self.delay * 2
def pulse(self):
if self.pattern_step == 0:
@@ -359,47 +375,35 @@ class Patterns(PatternBase): # Inherit from PatternBase
self.fill((0, 0, 0))
print(utime.ticks_diff(utime.ticks_ms(), self.last_update))
self.run = False
return self.delay
if __name__ == "__main__":
import time
import time
from machine import WDT
wdt = WDT(timeout=2000) # Enable watchdog with a 2 second timeout
p = Patterns(pin=4, num_leds=60, color1=(255,0,0), color2=(0,0,255), brightness=127, selected="off", delay=100)
print(p.colors, p.brightness)
# tests = [
# ("off", {"duration_ms": 500}),
# ("on", {"duration_ms": 500}),
# ("color_wipe", {"delay": 200, "duration_ms": 1000}),
# ("rainbow_cycle", {"delay": 100, "duration_ms": 2500}),
# ("theater_chase", {"on_width": 3, "off_width": 3, "delay": 1000, "duration_ms": 2500}),
# ("blink", {"delay": 500, "duration_ms": 2000}),
# ("color_transition", {"delay": 150, "colors": [(255,0,0),(0,255,0),(0,0,255)], "duration_ms": 5000}),
# ("flicker", {"delay": 100, "duration_ms": 2000}),
# ("scanner", {"delay": 150, "duration_ms": 2500}),
# ("bidirectional_scanner", {"delay": 50, "duration_ms": 2500}),
# ("fill_range", {"n1": 10, "n2": 20, "delay": 500, "duration_ms": 2000}),
# ("n_chase", {"n1": 5, "n2": 5, "delay": 1000, "duration_ms": 2500}),
# ("alternating", {"n1": 5, "n2": 5, "delay": 500, "duration_ms": 2500}),
# ("pulse", {"delay": 100, "duration_ms": 700}),
# ]
tests = [
("theater_chase", {"on_width": 3, "off_width": 3, "delay": 10000, "duration_ms": 2500}),
("off", {"duration_ms": 500}),
("on", {"duration_ms": 500}),
("color_wipe", {"delay": 200, "duration_ms": 1000}),
("rainbow_cycle", {"delay": 100, "duration_ms": 2500}),
("theater_chase", {"on_width": 3, "off_width": 3, "delay": 1000, "duration_ms": 2500}),
("blink", {"delay": 500, "duration_ms": 2000}),
("color_transition", {"delay": 150, "colors": [(255,0,0),(0,255,0),(0,0,255)], "duration_ms": 5000}),
("flicker", {"delay": 100, "duration_ms": 2000}),
("scanner", {"delay": 150, "duration_ms": 2500}),
("bidirectional_scanner", {"delay": 50, "duration_ms": 2500}),
("fill_range", {"n1": 10, "n2": 20, "delay": 500, "duration_ms": 2000}),
("n_chase", {"n1": 5, "n2": 5, "delay": 1000, "duration_ms": 2500}),
("n_chase", {"n1": 5, "n2": 5, "delay": 2000, "duration_ms": 2500}),
("alternating", {"n1": 5, "n2": 5, "delay": 500, "duration_ms": 2500}),
("pulse", {"delay": 100, "duration_ms": 700}),
]
print("\n--- Running pattern self-test ---")
for name, cfg in tests:
print(f"\nPattern: {name}")
@@ -417,17 +421,14 @@ if __name__ == "__main__":
p.select(name)
# run per configured or computed duration
# run per configured duration using absolute-scheduled tick(next_due_ms)
start = utime.ticks_ms()
duration_ms = cfg["duration_ms"]
delay = cfg.get("delay", 0)
next_due = utime.ticks_ms() - 1 # force immediate first call
while utime.ticks_diff(utime.ticks_ms(), start) < duration_ms:
interval = p.tick()
delay = p.tick(delay)
wdt.feed()
if isinstance(interval, int) and interval > 0:
# sleep a small fraction to reduce busy loop while keeping responsiveness
time.sleep_ms(max(1, interval // 10))
else:
time.sleep_ms(5)
print("\n--- Test routine finished ---")