convert blink to use a thread
This commit is contained in:
20
src/p2p.py
20
src/p2p.py
@@ -1,20 +0,0 @@
|
||||
import asyncio
|
||||
import aioespnow
|
||||
import json
|
||||
|
||||
async def p2p(settings, patterns):
|
||||
e = aioespnow.AIOESPNow() # Returns AIOESPNow enhanced with async support
|
||||
e.active(True)
|
||||
async for mac, msg in e:
|
||||
try:
|
||||
data = json.loads(msg)
|
||||
except:
|
||||
print(f"Failed to load espnow data {msg}")
|
||||
continue
|
||||
print(data)
|
||||
if "names" not in data or settings.get("name") in data.get("names", []):
|
||||
if "step" in settings and isinstance(settings["step"], int):
|
||||
patterns.set_pattern_step(settings["step"])
|
||||
else:
|
||||
settings.set_settings(data.get("settings", {}), patterns, data.get("save", False))
|
||||
print("should not print")
|
||||
804
src/patterns.py
804
src/patterns.py
@@ -2,6 +2,8 @@
|
||||
import utime
|
||||
import random
|
||||
from patterns_base import PatternBase # Import PatternBase
|
||||
import _thread
|
||||
from machine import WDT
|
||||
|
||||
class Patterns(PatternBase): # Inherit from PatternBase
|
||||
def __init__(self, pin, num_leds, color1=(0,0,0), color2=(0,0,0), brightness=127, selected="rainbow_cycle", delay=100):
|
||||
@@ -16,438 +18,448 @@ class Patterns(PatternBase): # Inherit from PatternBase
|
||||
self.n4 = 0
|
||||
self.oneshot = False # New: One-shot flag for patterns like fill_range
|
||||
self.patterns = {
|
||||
"on": self.on,
|
||||
"off": self.off,
|
||||
"flicker": self.flicker,
|
||||
"fill_range": self.fill_range,
|
||||
"n_chase": self.n_chase,
|
||||
"alternating": self.alternating,
|
||||
"pulse": self.pulse,
|
||||
"rainbow": self.rainbow,
|
||||
"specto": self.specto,
|
||||
"radiate": self.radiate,
|
||||
"segmented_movement": self.segmented_movement,
|
||||
|
||||
# Shortened pattern names for optimized JSON payloads
|
||||
"o": self.off,
|
||||
"f": self.flicker,
|
||||
"fr": self.fill_range,
|
||||
"nc": self.n_chase,
|
||||
"a": self.alternating,
|
||||
"p": self.pulse,
|
||||
"r": self.rainbow,
|
||||
"s": self.specto,
|
||||
"rd": self.radiate,
|
||||
"sm": self.segmented_movement,
|
||||
"on": self.on,
|
||||
"bl": self.blink,
|
||||
}
|
||||
self.step = 0
|
||||
self.run = True
|
||||
self.running = False
|
||||
self.wdt = WDT(timeout=10000)
|
||||
|
||||
def select(self, pattern):
|
||||
self.selected = pattern
|
||||
self.run = False
|
||||
if pattern not in self.patterns:
|
||||
return False
|
||||
while self.running:
|
||||
utime.sleep_ms(1)
|
||||
self.running = True
|
||||
_thread.start_new_thread(self.patterns[pattern], ())
|
||||
|
||||
|
||||
def on(self):
|
||||
"""Turn on all LEDs with current color"""
|
||||
self.fill(self.apply_brightness(self.colors[0]))
|
||||
self.n.write()
|
||||
return self.delay
|
||||
|
||||
|
||||
def off(self):
|
||||
"""Turn off all LEDs"""
|
||||
self.fill((0, 0, 0))
|
||||
self.n.write()
|
||||
return self.delay
|
||||
|
||||
def flicker(self):
|
||||
current_time = utime.ticks_ms()
|
||||
base_color = self.colors[0]
|
||||
# Use fixed minimum brightness of 10, flicker between 10 and full brightness
|
||||
# Use n3 as step rate multiplier to control how fast patterns step
|
||||
min_brightness = 10
|
||||
step_rate = max(1, int(self.n3))
|
||||
flicker_brightness_offset = random.randint(-int(self.brightness // 1.5), int(self.brightness // 1.5))
|
||||
flicker_brightness = max(min_brightness, min(255, self.brightness + flicker_brightness_offset))
|
||||
|
||||
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 * step_rate)))
|
||||
|
||||
def fill_range(self):
|
||||
"""
|
||||
Fills a range of LEDs from n1 to n2 with a solid color.
|
||||
If self.oneshot is True, it fills once and then turns off the LEDs.
|
||||
"""
|
||||
current_time = utime.ticks_ms()
|
||||
if self.oneshot and self.pattern_step >= 1:
|
||||
self.fill((0, 0, 0)) # Turn off LEDs if one-shot already happened
|
||||
else:
|
||||
color = self.apply_brightness(self.colors[0])
|
||||
for i in range(self.n1, self.n2 + 1):
|
||||
self.n[i] = color
|
||||
self.n.write()
|
||||
self.last_update = current_time
|
||||
return self.delay
|
||||
self.last_update = current_time
|
||||
return self.delay
|
||||
|
||||
def n_chase(self):
|
||||
"""
|
||||
A theater chase pattern using n1 for on-width and n2 for off-width.
|
||||
"""
|
||||
current_time = utime.ticks_ms()
|
||||
step_rate = max(1, int(self.n3))
|
||||
segment_length = self.n1 + self.n2
|
||||
if segment_length == 0: # Avoid division by zero
|
||||
self.fill((0,0,0))
|
||||
self.n.write()
|
||||
self.last_update = current_time
|
||||
return self.delay
|
||||
|
||||
# Use controller's step for synchronization, but scale it for chasing
|
||||
chase_step = (self.step * step_rate) % self.num_leds
|
||||
|
||||
for i in range(self.num_leds):
|
||||
# Calculate position relative to the chase head
|
||||
pos_from_head = (i - chase_step) % self.num_leds
|
||||
if pos_from_head < self.n1:
|
||||
self.n[i] = self.apply_brightness(self.colors[0])
|
||||
else:
|
||||
self.n[i] = (0, 0, 0)
|
||||
self.n.write()
|
||||
|
||||
# Don't update internal step - use controller's step for sync
|
||||
self.last_update = current_time
|
||||
return self.delay
|
||||
|
||||
def alternating(self):
|
||||
# Use n1 as ON width and n2 as OFF width
|
||||
segment_on = max(0, int(self.n1))
|
||||
segment_off = max(0, int(self.n2))
|
||||
total_segment_length = segment_on + segment_off
|
||||
if total_segment_length <= 0:
|
||||
self.fill((0, 0, 0))
|
||||
self.n.write()
|
||||
return self.delay
|
||||
|
||||
current_phase = self.step % 2
|
||||
|
||||
active_color = self.apply_brightness(self.colors[0])
|
||||
|
||||
for i in range(self.num_leds):
|
||||
pos_in_segment = i % total_segment_length
|
||||
if current_phase == 0:
|
||||
# ON then OFF
|
||||
if pos_in_segment < segment_on:
|
||||
self.n[i] = active_color
|
||||
else:
|
||||
self.n[i] = (0, 0, 0)
|
||||
else:
|
||||
# OFF then ON
|
||||
if pos_in_segment < segment_on:
|
||||
self.n[i] = (0, 0, 0)
|
||||
else:
|
||||
self.n[i] = active_color
|
||||
|
||||
self.n.write()
|
||||
# Don't update step - use the step value sent from controller for synchronization
|
||||
return max(1, int(self.delay // 2))
|
||||
|
||||
|
||||
def pulse(self):
|
||||
# Envelope: attack=n1 ms, hold=delay ms, decay=n2 ms
|
||||
attack_ms = max(0, int(self.n1))
|
||||
hold_ms = max(0, int(self.delay))
|
||||
decay_ms = max(0, int(self.n2))
|
||||
|
||||
base = self.colors[0] if len(self.colors) > 0 else (255, 255, 255)
|
||||
full_brightness = max(0, min(255, int(self.brightness)))
|
||||
|
||||
# Attack phase (0 -> full)
|
||||
if attack_ms > 0:
|
||||
def blink(self):
|
||||
self.run = True
|
||||
start = utime.ticks_ms()
|
||||
while utime.ticks_diff(utime.ticks_ms(), start) < attack_ms:
|
||||
elapsed = utime.ticks_diff(utime.ticks_ms(), start)
|
||||
frac = elapsed / attack_ms if attack_ms > 0 else 1.0
|
||||
b = int(full_brightness * frac)
|
||||
self.fill(self.apply_brightness(base, brightness_override=b))
|
||||
else:
|
||||
self.fill(self.apply_brightness(base, brightness_override=full_brightness))
|
||||
|
||||
# Hold phase
|
||||
if hold_ms > 0:
|
||||
start = utime.ticks_ms()
|
||||
while utime.ticks_diff(utime.ticks_ms(), start) < hold_ms:
|
||||
pass
|
||||
|
||||
# Decay phase (full -> 0)
|
||||
if decay_ms > 0:
|
||||
start = utime.ticks_ms()
|
||||
while utime.ticks_diff(utime.ticks_ms(), start) < decay_ms:
|
||||
elapsed = utime.ticks_diff(utime.ticks_ms(), start)
|
||||
frac = 1.0 - (elapsed / decay_ms if decay_ms > 0 else 1.0)
|
||||
if frac < 0:
|
||||
frac = 0
|
||||
b = int(full_brightness * frac)
|
||||
self.fill(self.apply_brightness(base, brightness_override=b))
|
||||
|
||||
# Ensure off at the end and stop auto-run
|
||||
while self.run:
|
||||
self.wdt.feed()
|
||||
diff = utime.ticks_diff(utime.ticks_ms(), start)
|
||||
if diff >= self.delay:
|
||||
self.fill((0, 0, 0))
|
||||
start = utime.ticks_ms()
|
||||
elif diff >= self.delay/2:
|
||||
self.fill(self.apply_brightness(self.colors[0]))
|
||||
|
||||
self.run = False
|
||||
return self.delay
|
||||
self.running = False
|
||||
|
||||
def rainbow(self):
|
||||
# Wheel function to map 0-255 to RGB
|
||||
def wheel(pos):
|
||||
if pos < 85:
|
||||
return (pos * 3, 255 - pos * 3, 0)
|
||||
elif pos < 170:
|
||||
pos -= 85
|
||||
return (255 - pos * 3, 0, pos * 3)
|
||||
else:
|
||||
pos -= 170
|
||||
return (0, pos * 3, 255 - pos * 3)
|
||||
# def flicker(self):
|
||||
# current_time = utime.ticks_ms()
|
||||
# base_color = self.colors[0]
|
||||
|
||||
step_rate = max(1, int(self.n3))
|
||||
# Use controller's step for synchronization, scaled for rainbow cycling
|
||||
rainbow_step = (self.step * step_rate) % 256
|
||||
# # Use fixed minimum brightness of 10, flicker between 10 and full brightness
|
||||
# # Use n3 as step rate multiplier to control how fast patterns step
|
||||
# min_brightness = 10
|
||||
# step_rate = max(1, int(self.n3))
|
||||
# flicker_brightness_offset = random.randint(-int(self.brightness // 1.5), int(self.brightness // 1.5))
|
||||
# flicker_brightness = max(min_brightness, min(255, self.brightness + flicker_brightness_offset))
|
||||
|
||||
for i in range(self.num_leds):
|
||||
rc_index = (i * 256 // max(1, self.num_leds)) + rainbow_step
|
||||
self.n[i] = self.apply_brightness(wheel(rc_index & 255))
|
||||
self.n.write()
|
||||
# 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 * step_rate)))
|
||||
|
||||
# Don't update internal step - use controller's step for sync
|
||||
return max(1, int(self.delay // 5))
|
||||
# def fill_range(self):
|
||||
# """
|
||||
# Fills a range of LEDs from n1 to n2 with a solid color.
|
||||
# If self.oneshot is True, it fills once and then turns off the LEDs.
|
||||
# """
|
||||
# current_time = utime.ticks_ms()
|
||||
# if self.oneshot and self.pattern_step >= 1:
|
||||
# self.fill((0, 0, 0)) # Turn off LEDs if one-shot already happened
|
||||
# else:
|
||||
# color = self.apply_brightness(self.colors[0])
|
||||
# for i in range(self.n1, self.n2 + 1):
|
||||
# self.n[i] = color
|
||||
# self.n.write()
|
||||
# self.last_update = current_time
|
||||
# return self.delay
|
||||
# self.last_update = current_time
|
||||
# return self.delay
|
||||
|
||||
def specto(self):
|
||||
# Light up LEDs from 0 up to n1 (exclusive) and turn the rest off
|
||||
count = int(self.n1)
|
||||
if count < 0:
|
||||
count = 0
|
||||
if count > self.num_leds:
|
||||
count = self.num_leds
|
||||
color = self.apply_brightness(self.colors[0] if len(self.colors) > 0 else (255, 255, 255))
|
||||
for i in range(self.num_leds):
|
||||
self.n[i] = color if i < count else (0, 0, 0)
|
||||
self.n.write()
|
||||
return self.delay
|
||||
# def n_chase(self):
|
||||
# """
|
||||
# A theater chase pattern using n1 for on-width and n2 for off-width.
|
||||
# """
|
||||
# current_time = utime.ticks_ms()
|
||||
# step_rate = max(1, int(self.n3))
|
||||
# segment_length = self.n1 + self.n2
|
||||
# if segment_length == 0: # Avoid division by zero
|
||||
# self.fill((0,0,0))
|
||||
# self.n.write()
|
||||
# self.last_update = current_time
|
||||
# return self.delay
|
||||
|
||||
def radiate(self):
|
||||
# Radiate outward from origins spaced every n1 LEDs, stepping each ring by self.delay
|
||||
sep = max(1, int(self.n1) if self.n1 else 1)
|
||||
color = self.apply_brightness(self.colors[0] if len(self.colors) > 0 else (255, 255, 255))
|
||||
# # Use controller's step for synchronization, but scale it for chasing
|
||||
# chase_step = (self.step * step_rate) % self.num_leds
|
||||
|
||||
# Start with strip off
|
||||
self.fill((0, 0, 0))
|
||||
# for i in range(self.num_leds):
|
||||
# # Calculate position relative to the chase head
|
||||
# pos_from_head = (i - chase_step) % self.num_leds
|
||||
# if pos_from_head < self.n1:
|
||||
# self.n[i] = self.apply_brightness(self.colors[0])
|
||||
# else:
|
||||
# self.n[i] = (0, 0, 0)
|
||||
# self.n.write()
|
||||
|
||||
origins = list(range(0, self.num_leds, sep))
|
||||
radius = 0
|
||||
lit_total = 0
|
||||
while True:
|
||||
drew_any = False
|
||||
for o in origins:
|
||||
left = o - radius
|
||||
right = o + radius
|
||||
if 0 <= left < self.num_leds:
|
||||
if self.n[left] == (0, 0, 0):
|
||||
lit_total += 1
|
||||
self.n[left] = color
|
||||
drew_any = True
|
||||
if 0 <= right < self.num_leds:
|
||||
if self.n[right] == (0, 0, 0):
|
||||
lit_total += 1
|
||||
self.n[right] = color
|
||||
drew_any = True
|
||||
self.n.write()
|
||||
# # Don't update internal step - use controller's step for sync
|
||||
# self.last_update = current_time
|
||||
# return self.delay
|
||||
|
||||
# If we didn't draw anything new, we've reached beyond edges
|
||||
if not drew_any:
|
||||
break
|
||||
# If all LEDs are now lit, immediately proceed to dark sweep
|
||||
if lit_total >= self.num_leds:
|
||||
break
|
||||
# wait self.delay ms before next ring
|
||||
start = utime.ticks_us()
|
||||
while utime.ticks_diff(utime.ticks_us(), start) < self.delay:
|
||||
pass
|
||||
radius += 1
|
||||
# def alternating(self):
|
||||
# # Use n1 as ON width and n2 as OFF width
|
||||
# segment_on = max(0, int(self.n1))
|
||||
# segment_off = max(0, int(self.n2))
|
||||
# total_segment_length = segment_on + segment_off
|
||||
# if total_segment_length <= 0:
|
||||
# self.fill((0, 0, 0))
|
||||
# self.n.write()
|
||||
# return self.delay
|
||||
|
||||
# Radiate back out (darkness outward): turn off from center to edges
|
||||
last_radius = max(0, radius - 1)
|
||||
for r in range(0, last_radius + 1):
|
||||
for o in origins:
|
||||
left = o - r
|
||||
right = o + r
|
||||
if 0 <= left < self.num_leds:
|
||||
self.n[left] = (0, 0, 0)
|
||||
if 0 <= right < self.num_leds:
|
||||
self.n[right] = (0, 0, 0)
|
||||
self.n.write()
|
||||
start = utime.ticks_us()
|
||||
while utime.ticks_diff(utime.ticks_us(), start) < self.delay:
|
||||
pass
|
||||
# current_phase = self.step % 2
|
||||
|
||||
# ensure all LEDs are off at completion
|
||||
self.fill((0, 0, 0))
|
||||
# mark complete so scheduler won't auto-run again until re-selected
|
||||
self.run = False
|
||||
return self.delay
|
||||
# active_color = self.apply_brightness(self.colors[0])
|
||||
|
||||
def segmented_movement(self):
|
||||
"""
|
||||
Segmented movement pattern that alternates forward and backward.
|
||||
# for i in range(self.num_leds):
|
||||
# pos_in_segment = i % total_segment_length
|
||||
# if current_phase == 0:
|
||||
# # ON then OFF
|
||||
# if pos_in_segment < segment_on:
|
||||
# self.n[i] = active_color
|
||||
# else:
|
||||
# self.n[i] = (0, 0, 0)
|
||||
# else:
|
||||
# # OFF then ON
|
||||
# if pos_in_segment < segment_on:
|
||||
# self.n[i] = (0, 0, 0)
|
||||
# else:
|
||||
# self.n[i] = active_color
|
||||
|
||||
Parameters:
|
||||
n1: Number of LEDs per segment
|
||||
n2: Spacing between segments (currently unused)
|
||||
n3: Forward movement steps per beat
|
||||
n4: Backward movement steps per beat
|
||||
|
||||
Movement: Alternates between moving forward n3 steps and backward n4 steps each beat.
|
||||
"""
|
||||
try:
|
||||
# Get parameters
|
||||
segment_length = max(1, int(self.n1)) if hasattr(self, 'n1') else 3
|
||||
segment_spacing = max(0, int(self.n2)) if hasattr(self, 'n2') else 2
|
||||
forward_step = max(0, int(self.n3)) if hasattr(self, 'n3') else 1
|
||||
backward_step = max(0, int(self.n4)) if hasattr(self, 'n4') else 0
|
||||
|
||||
# Initialize position tracking if not exists
|
||||
if not hasattr(self, '_sm_position'):
|
||||
self._sm_position = 0
|
||||
self._sm_last_step = -1
|
||||
|
||||
# Check if this is a new beat (step changed)
|
||||
if self.step != self._sm_last_step:
|
||||
# Alternate between forward and backward movement
|
||||
if self.step % 2 == 0:
|
||||
# Even steps: move forward (if n3 > 0)
|
||||
if forward_step > 0:
|
||||
self._sm_position += forward_step
|
||||
direction = "FWD"
|
||||
elif backward_step > 0:
|
||||
# If no forward, still move backward
|
||||
self._sm_position -= backward_step
|
||||
direction = "BWD"
|
||||
else:
|
||||
direction = "NONE"
|
||||
else:
|
||||
# Odd steps: move backward (if n4 > 0)
|
||||
if backward_step > 0:
|
||||
self._sm_position -= backward_step
|
||||
direction = "BWD"
|
||||
elif forward_step > 0:
|
||||
# If no backward, still move forward
|
||||
self._sm_position += forward_step
|
||||
direction = "FWD"
|
||||
else:
|
||||
direction = "NONE"
|
||||
|
||||
# Wrap position around strip length
|
||||
strip_length = self.num_leds + segment_length
|
||||
self._sm_position = self._sm_position % strip_length
|
||||
|
||||
# Update last step
|
||||
self._sm_last_step = self.step
|
||||
|
||||
# DEBUG: Print every beat
|
||||
if self.step % 5 == 0:
|
||||
print(f"SM: step={self.step}, dir={direction}, n3={forward_step}, n4={backward_step}, pos={self._sm_position}")
|
||||
|
||||
# Clear all LEDs
|
||||
self.fill((0, 0, 0))
|
||||
|
||||
# Get color
|
||||
color = self.apply_brightness(self.colors[0])
|
||||
|
||||
# Calculate segment width (segment + spacing)
|
||||
segment_width = segment_length + segment_spacing
|
||||
|
||||
# Draw multiple segments across the strip
|
||||
if segment_width > 0:
|
||||
base_position = int(self._sm_position) % segment_width
|
||||
|
||||
# Draw segments starting from base_position
|
||||
current_pos = base_position
|
||||
while current_pos < self.num_leds:
|
||||
# Draw segment from current_pos to current_pos + segment_length
|
||||
segment_end = min(current_pos + segment_length, self.num_leds)
|
||||
for i in range(max(0, current_pos), segment_end):
|
||||
self.n[i] = color
|
||||
|
||||
# Move to next segment position
|
||||
current_pos += segment_width
|
||||
|
||||
# Handle wrap-around: draw segments that start before 0
|
||||
wrap_position = base_position - segment_width
|
||||
while wrap_position > -segment_length:
|
||||
if wrap_position < 0:
|
||||
# Partial segment at start
|
||||
segment_end = min(wrap_position + segment_length, self.num_leds)
|
||||
for i in range(0, segment_end):
|
||||
self.n[i] = color
|
||||
wrap_position -= segment_width
|
||||
|
||||
self.n.write()
|
||||
return self.delay
|
||||
|
||||
except Exception as e:
|
||||
# DEBUG: Print error
|
||||
print(f"SM Error: {e}")
|
||||
# If anything goes wrong, turn off LEDs and return
|
||||
self.fill((0, 0, 0))
|
||||
self.n.write()
|
||||
return self.delay
|
||||
# self.n.write()
|
||||
# # Don't update step - use the step value sent from controller for synchronization
|
||||
# return max(1, int(self.delay // 2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
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)
|
||||
# def pulse(self):
|
||||
# # Envelope: attack=n1 ms, hold=delay ms, decay=n2 ms
|
||||
# attack_ms = max(0, int(self.n1))
|
||||
# hold_ms = max(0, int(self.delay))
|
||||
# decay_ms = max(0, int(self.n2))
|
||||
|
||||
print(p.colors, p.brightness)
|
||||
# base = self.colors[0] if len(self.colors) > 0 else (255, 255, 255)
|
||||
# full_brightness = max(0, min(255, int(self.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": 2000, "duration_ms": 2500}),
|
||||
("alternating", {"n1": 5, "n2": 5, "delay": 500, "duration_ms": 2500}),
|
||||
("pulse", {"delay": 100, "duration_ms": 700}),
|
||||
]
|
||||
# # Attack phase (0 -> full)
|
||||
# if attack_ms > 0:
|
||||
# start = utime.ticks_ms()
|
||||
# while utime.ticks_diff(utime.ticks_ms(), start) < attack_ms:
|
||||
# elapsed = utime.ticks_diff(utime.ticks_ms(), start)
|
||||
# frac = elapsed / attack_ms if attack_ms > 0 else 1.0
|
||||
# b = int(full_brightness * frac)
|
||||
# self.fill(self.apply_brightness(base, brightness_override=b))
|
||||
# else:
|
||||
# self.fill(self.apply_brightness(base, brightness_override=full_brightness))
|
||||
|
||||
# # Hold phase
|
||||
# if hold_ms > 0:
|
||||
# start = utime.ticks_ms()
|
||||
# while utime.ticks_diff(utime.ticks_ms(), start) < hold_ms:
|
||||
# pass
|
||||
|
||||
# # Decay phase (full -> 0)
|
||||
# if decay_ms > 0:
|
||||
# start = utime.ticks_ms()
|
||||
# while utime.ticks_diff(utime.ticks_ms(), start) < decay_ms:
|
||||
# elapsed = utime.ticks_diff(utime.ticks_ms(), start)
|
||||
# frac = 1.0 - (elapsed / decay_ms if decay_ms > 0 else 1.0)
|
||||
# if frac < 0:
|
||||
# frac = 0
|
||||
# b = int(full_brightness * frac)
|
||||
# self.fill(self.apply_brightness(base, brightness_override=b))
|
||||
|
||||
# # Ensure off at the end and stop auto-run
|
||||
# self.fill((0, 0, 0))
|
||||
# self.run = False
|
||||
# return self.delay
|
||||
|
||||
# def rainbow(self):
|
||||
# # Wheel function to map 0-255 to RGB
|
||||
# def wheel(pos):
|
||||
# if pos < 85:
|
||||
# return (pos * 3, 255 - pos * 3, 0)
|
||||
# elif pos < 170:
|
||||
# pos -= 85
|
||||
# return (255 - pos * 3, 0, pos * 3)
|
||||
# else:
|
||||
# pos -= 170
|
||||
# return (0, pos * 3, 255 - pos * 3)
|
||||
|
||||
# step_rate = max(1, int(self.n3))
|
||||
# # Use controller's step for synchronization, scaled for rainbow cycling
|
||||
# rainbow_step = (self.step * step_rate) % 256
|
||||
|
||||
# for i in range(self.num_leds):
|
||||
# rc_index = (i * 256 // max(1, self.num_leds)) + rainbow_step
|
||||
# self.n[i] = self.apply_brightness(wheel(rc_index & 255))
|
||||
# self.n.write()
|
||||
|
||||
# # Don't update internal step - use controller's step for sync
|
||||
# return max(1, int(self.delay // 5))
|
||||
|
||||
# def specto(self):
|
||||
# # Light up LEDs from 0 up to n1 (exclusive) and turn the rest off
|
||||
# count = int(self.n1)
|
||||
# if count < 0:
|
||||
# count = 0
|
||||
# if count > self.num_leds:
|
||||
# count = self.num_leds
|
||||
# color = self.apply_brightness(self.colors[0] if len(self.colors) > 0 else (255, 255, 255))
|
||||
# for i in range(self.num_leds):
|
||||
# self.n[i] = color if i < count else (0, 0, 0)
|
||||
# self.n.write()
|
||||
# return self.delay
|
||||
|
||||
# def radiate(self):
|
||||
# # Radiate outward from origins spaced every n1 LEDs, stepping each ring by self.delay
|
||||
# sep = max(1, int(self.n1) if self.n1 else 1)
|
||||
# color = self.apply_brightness(self.colors[0] if len(self.colors) > 0 else (255, 255, 255))
|
||||
|
||||
# # Start with strip off
|
||||
# self.fill((0, 0, 0))
|
||||
|
||||
# origins = list(range(0, self.num_leds, sep))
|
||||
# radius = 0
|
||||
# lit_total = 0
|
||||
# while True:
|
||||
# drew_any = False
|
||||
# for o in origins:
|
||||
# left = o - radius
|
||||
# right = o + radius
|
||||
# if 0 <= left < self.num_leds:
|
||||
# if self.n[left] == (0, 0, 0):
|
||||
# lit_total += 1
|
||||
# self.n[left] = color
|
||||
# drew_any = True
|
||||
# if 0 <= right < self.num_leds:
|
||||
# if self.n[right] == (0, 0, 0):
|
||||
# lit_total += 1
|
||||
# self.n[right] = color
|
||||
# drew_any = True
|
||||
# self.n.write()
|
||||
|
||||
# # If we didn't draw anything new, we've reached beyond edges
|
||||
# if not drew_any:
|
||||
# break
|
||||
# # If all LEDs are now lit, immediately proceed to dark sweep
|
||||
# if lit_total >= self.num_leds:
|
||||
# break
|
||||
# # wait self.delay ms before next ring
|
||||
# start = utime.ticks_us()
|
||||
# while utime.ticks_diff(utime.ticks_us(), start) < self.delay:
|
||||
# pass
|
||||
# radius += 1
|
||||
|
||||
# # Radiate back out (darkness outward): turn off from center to edges
|
||||
# last_radius = max(0, radius - 1)
|
||||
# for r in range(0, last_radius + 1):
|
||||
# for o in origins:
|
||||
# left = o - r
|
||||
# right = o + r
|
||||
# if 0 <= left < self.num_leds:
|
||||
# self.n[left] = (0, 0, 0)
|
||||
# if 0 <= right < self.num_leds:
|
||||
# self.n[right] = (0, 0, 0)
|
||||
# self.n.write()
|
||||
# start = utime.ticks_us()
|
||||
# while utime.ticks_diff(utime.ticks_us(), start) < self.delay:
|
||||
# pass
|
||||
|
||||
# # ensure all LEDs are off at completion
|
||||
# self.fill((0, 0, 0))
|
||||
# # mark complete so scheduler won't auto-run again until re-selected
|
||||
# self.run = False
|
||||
# return self.delay
|
||||
|
||||
# def segmented_movement(self):
|
||||
# """
|
||||
# Segmented movement pattern that alternates forward and backward.
|
||||
|
||||
# Parameters:
|
||||
# n1: Number of LEDs per segment
|
||||
# n2: Spacing between segments (currently unused)
|
||||
# n3: Forward movement steps per beat
|
||||
# n4: Backward movement steps per beat
|
||||
|
||||
# Movement: Alternates between moving forward n3 steps and backward n4 steps each beat.
|
||||
# """
|
||||
# try:
|
||||
# # Get parameters
|
||||
# segment_length = max(1, int(self.n1)) if hasattr(self, 'n1') else 3
|
||||
# segment_spacing = max(0, int(self.n2)) if hasattr(self, 'n2') else 2
|
||||
# forward_step = max(0, int(self.n3)) if hasattr(self, 'n3') else 1
|
||||
# backward_step = max(0, int(self.n4)) if hasattr(self, 'n4') else 0
|
||||
|
||||
# # Initialize position tracking if not exists
|
||||
# if not hasattr(self, '_sm_position'):
|
||||
# self._sm_position = 0
|
||||
# self._sm_last_step = -1
|
||||
|
||||
# # Check if this is a new beat (step changed)
|
||||
# if self.step != self._sm_last_step:
|
||||
# # Alternate between forward and backward movement
|
||||
# if self.step % 2 == 0:
|
||||
# # Even steps: move forward (if n3 > 0)
|
||||
# if forward_step > 0:
|
||||
# self._sm_position += forward_step
|
||||
# direction = "FWD"
|
||||
# elif backward_step > 0:
|
||||
# # If no forward, still move backward
|
||||
# self._sm_position -= backward_step
|
||||
# direction = "BWD"
|
||||
# else:
|
||||
# direction = "NONE"
|
||||
# else:
|
||||
# # Odd steps: move backward (if n4 > 0)
|
||||
# if backward_step > 0:
|
||||
# self._sm_position -= backward_step
|
||||
# direction = "BWD"
|
||||
# elif forward_step > 0:
|
||||
# # If no backward, still move forward
|
||||
# self._sm_position += forward_step
|
||||
# direction = "FWD"
|
||||
# else:
|
||||
# direction = "NONE"
|
||||
|
||||
# # Wrap position around strip length
|
||||
# strip_length = self.num_leds + segment_length
|
||||
# self._sm_position = self._sm_position % strip_length
|
||||
|
||||
# # Update last step
|
||||
# self._sm_last_step = self.step
|
||||
|
||||
# # DEBUG: Print every beat
|
||||
# if self.step % 5 == 0:
|
||||
# print(f"SM: step={self.step}, dir={direction}, n3={forward_step}, n4={backward_step}, pos={self._sm_position}")
|
||||
|
||||
# # Clear all LEDs
|
||||
# self.fill((0, 0, 0))
|
||||
|
||||
# # Get color
|
||||
# color = self.apply_brightness(self.colors[0])
|
||||
|
||||
# # Calculate segment width (segment + spacing)
|
||||
# segment_width = segment_length + segment_spacing
|
||||
|
||||
# # Draw multiple segments across the strip
|
||||
# if segment_width > 0:
|
||||
# base_position = int(self._sm_position) % segment_width
|
||||
|
||||
# # Draw segments starting from base_position
|
||||
# current_pos = base_position
|
||||
# while current_pos < self.num_leds:
|
||||
# # Draw segment from current_pos to current_pos + segment_length
|
||||
# segment_end = min(current_pos + segment_length, self.num_leds)
|
||||
# for i in range(max(0, current_pos), segment_end):
|
||||
# self.n[i] = color
|
||||
|
||||
# # Move to next segment position
|
||||
# current_pos += segment_width
|
||||
|
||||
# # Handle wrap-around: draw segments that start before 0
|
||||
# wrap_position = base_position - segment_width
|
||||
# while wrap_position > -segment_length:
|
||||
# if wrap_position < 0:
|
||||
# # Partial segment at start
|
||||
# segment_end = min(wrap_position + segment_length, self.num_leds)
|
||||
# for i in range(0, segment_end):
|
||||
# self.n[i] = color
|
||||
# wrap_position -= segment_width
|
||||
|
||||
# self.n.write()
|
||||
# return self.delay
|
||||
|
||||
# except Exception as e:
|
||||
# # DEBUG: Print error
|
||||
# print(f"SM Error: {e}")
|
||||
# # If anything goes wrong, turn off LEDs and return
|
||||
# self.fill((0, 0, 0))
|
||||
# self.n.write()
|
||||
# return self.delay
|
||||
|
||||
|
||||
print("\n--- Running pattern self-test ---")
|
||||
for name, cfg in tests:
|
||||
print(f"\nPattern: {name}")
|
||||
# apply simple config helpers
|
||||
if "delay" in cfg:
|
||||
p.set_delay(cfg["delay"])
|
||||
if "on_width" in cfg:
|
||||
p.set_on_width(cfg["on_width"])
|
||||
if "off_width" in cfg:
|
||||
p.set_off_width(cfg["off_width"])
|
||||
if "n1" in cfg and "n2" in cfg:
|
||||
p.set_fill_range(cfg["n1"], cfg["n2"])
|
||||
if "colors" in cfg:
|
||||
p.set_colors(cfg["colors"])
|
||||
# if __name__ == "__main__":
|
||||
# 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)
|
||||
|
||||
p.select(name)
|
||||
# print(p.colors, p.brightness)
|
||||
|
||||
# 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:
|
||||
delay = p.tick(delay)
|
||||
wdt.feed()
|
||||
|
||||
print("\n--- Test routine finished ---")
|
||||
# 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": 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}")
|
||||
# # apply simple config helpers
|
||||
# if "delay" in cfg:
|
||||
# p.set_delay(cfg["delay"])
|
||||
# if "on_width" in cfg:
|
||||
# p.set_on_width(cfg["on_width"])
|
||||
# if "off_width" in cfg:
|
||||
# p.set_off_width(cfg["off_width"])
|
||||
# if "n1" in cfg and "n2" in cfg:
|
||||
# p.set_fill_range(cfg["n1"], cfg["n2"])
|
||||
# if "colors" in cfg:
|
||||
# p.set_colors(cfg["colors"])
|
||||
|
||||
# p.select(name)
|
||||
|
||||
# # 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:
|
||||
# delay = p.tick(delay)
|
||||
# wdt.feed()
|
||||
|
||||
# print("\n--- Test routine finished ---")
|
||||
|
||||
|
||||
|
||||
40
test/blink.py
Normal file
40
test/blink.py
Normal file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for blink pattern
|
||||
Run with: mpremote run test/blink.py
|
||||
"""
|
||||
|
||||
import patterns
|
||||
import utime
|
||||
|
||||
def test_blink():
|
||||
print("Testing blink pattern...")
|
||||
|
||||
# Initialize patterns with LED pin 10 and 59 LEDs at 20% brightness
|
||||
p = patterns.Patterns(pin=10, num_leds=59, brightness=51, delay=500)
|
||||
|
||||
# Set a bright red color
|
||||
p.colors = [(255, 0, 0)] # Red
|
||||
|
||||
print(f"LEDs: {p.num_leds}")
|
||||
print(f"Brightness: {p.brightness}")
|
||||
print(f"Delay: {p.delay}ms")
|
||||
print(f"Color: {p.colors[0]}")
|
||||
|
||||
# Test blink pattern
|
||||
print("Starting blink pattern for 5 seconds...")
|
||||
p.select("bl")
|
||||
|
||||
# Let it run for 5 seconds
|
||||
utime.sleep(5)
|
||||
|
||||
# Stop the pattern
|
||||
p.run = False
|
||||
print("Blink test completed")
|
||||
|
||||
# Turn off LEDs
|
||||
p.off()
|
||||
print("LEDs turned off")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_blink()
|
||||
Reference in New Issue
Block a user