- Updated n_chase to support bidirectional movement with n3 and n4 parameters - n3 controls forward steps per direction change - n4 controls backward steps per direction change - Pattern alternates between moving forward n3 steps and backward n4 steps - Each direction repeats for the specified number of steps before switching - Added test/9.py with n1=20, n2=20, n3=20, n4=-5 parameters - Updated to run as a thread-based pattern similar to other patterns
739 lines
29 KiB
Python
739 lines
29 KiB
Python
|
|
import utime
|
|
import random
|
|
import math
|
|
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):
|
|
super().__init__(pin, num_leds, color1, color2, brightness, selected, delay) # Call parent constructor
|
|
|
|
# Pattern-specific initializations
|
|
self.on_width = 1 # Default on width
|
|
self.off_width = 2 # Default off width (so total segment is 3, matching original behavior)
|
|
self.n1 = 0 # Default start of fill range
|
|
self.n2 = self.num_leds - 1 # Default end of fill range
|
|
self.n3 = 1 # Default step factor
|
|
self.n4 = 0
|
|
self.oneshot = False # New: One-shot flag for patterns like fill_range
|
|
self.patterns = {
|
|
|
|
# Shortened pattern names for optimized JSON payloads
|
|
"o": self.off,
|
|
"on": self.on,
|
|
"bl": self.blink,
|
|
"cl": self.circle_loading,
|
|
"sb": self.sine_brightness,
|
|
"rb": self.rainbow,
|
|
"fl": self.flicker,
|
|
"nc": self.n_chase,
|
|
}
|
|
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]))
|
|
|
|
|
|
def off(self):
|
|
"""Turn off all LEDs"""
|
|
self.fill((0, 0, 0))
|
|
|
|
def blink(self):
|
|
self.run = True
|
|
start = utime.ticks_ms()
|
|
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
|
|
self.running = False
|
|
|
|
def circle_loading(self):
|
|
"""Circle loading pattern - grows to n2, then tail moves forward at n3 until min length n4"""
|
|
self.run = True
|
|
head = 0
|
|
tail = 0
|
|
|
|
# Calculate timing
|
|
head_rate = max(1, int(self.n1)) # n1 = head moves per second
|
|
tail_rate = max(1, int(self.n3)) # n3 = tail moves per second
|
|
max_length = max(1, int(self.n2)) # n2 = max length
|
|
min_length = max(0, int(self.n4)) # n4 = min length
|
|
|
|
head_delay = 1000 // head_rate # ms between head movements
|
|
tail_delay = 1000 // tail_rate # ms between tail movements
|
|
|
|
last_head_move = utime.ticks_ms()
|
|
last_tail_move = utime.ticks_ms()
|
|
|
|
phase = "growing" # "growing", "shrinking", or "off"
|
|
|
|
while self.run:
|
|
self.wdt.feed()
|
|
current_time = utime.ticks_ms()
|
|
|
|
# Clear all LEDs
|
|
self.n.fill((0, 0, 0))
|
|
|
|
# Calculate segment length
|
|
segment_length = (head - tail) % self.num_leds
|
|
if segment_length == 0 and head != tail:
|
|
segment_length = self.num_leds
|
|
|
|
# Draw segment from tail to head
|
|
color = self.apply_brightness(self.colors[0])
|
|
for i in range(segment_length + 1):
|
|
led_pos = (tail + i) % self.num_leds
|
|
self.n[led_pos] = color
|
|
|
|
# Move head continuously at n1 LEDs per second
|
|
if utime.ticks_diff(current_time, last_head_move) >= head_delay:
|
|
head = (head + 1) % self.num_leds
|
|
last_head_move = current_time
|
|
|
|
# Tail behavior based on phase
|
|
if phase == "growing":
|
|
# Growing phase: tail stays at 0 until max length reached
|
|
if segment_length >= max_length:
|
|
phase = "shrinking"
|
|
elif phase == "shrinking":
|
|
# Shrinking phase: move tail forward at n3 LEDs per second
|
|
if utime.ticks_diff(current_time, last_tail_move) >= tail_delay:
|
|
tail = (tail + 1) % self.num_leds
|
|
last_tail_move = current_time
|
|
|
|
# Check if we've reached min length
|
|
current_length = (head - tail) % self.num_leds
|
|
if current_length == 0 and head != tail:
|
|
current_length = self.num_leds
|
|
|
|
# For min_length = 0, we need at least 1 LED (the head)
|
|
if min_length == 0 and current_length <= 1:
|
|
phase = "off" # All LEDs off for 1 step
|
|
elif min_length > 0 and current_length <= min_length:
|
|
phase = "growing" # Cycle repeats
|
|
else: # phase == "off"
|
|
# Off phase: all LEDs off for 1 step, then restart
|
|
phase = "growing"
|
|
|
|
self.n.write()
|
|
|
|
self.run = False
|
|
self.running = False
|
|
|
|
def sine_brightness(self):
|
|
"""Sine wave brightness pattern - n1=min brightness, brightness=max brightness, wavelength=delay"""
|
|
self.run = True
|
|
|
|
# Calculate sine wave parameters
|
|
min_brightness = max(0, int(self.n1)) # n1 = minimum brightness
|
|
max_brightness = self.brightness # brightness = maximum brightness
|
|
amplitude = max_brightness - min_brightness # Range between min and max
|
|
wavelength = max(1, self.delay) # Wavelength = delay in ms
|
|
|
|
# Convert wavelength to frequency (cycles per second)
|
|
frequency = 1000.0 / wavelength # Hz
|
|
|
|
start_time = utime.ticks_ms()
|
|
|
|
while self.run:
|
|
self.wdt.feed()
|
|
current_time = utime.ticks_ms()
|
|
|
|
# Calculate time elapsed in seconds
|
|
elapsed_ms = utime.ticks_diff(current_time, start_time)
|
|
elapsed_seconds = elapsed_ms / 1000.0
|
|
|
|
# Calculate sine wave value (-1 to 1)
|
|
sine_value = math.sin(2 * math.pi * frequency * elapsed_seconds)
|
|
|
|
# Convert to brightness (min_brightness to max_brightness)
|
|
current_brightness = int(min_brightness + (sine_value + 1) * amplitude / 2)
|
|
current_brightness = max(0, min(255, current_brightness))
|
|
|
|
# Apply brightness to all LEDs
|
|
color = self.apply_brightness(self.colors[0])
|
|
# Override brightness with calculated value
|
|
adjusted_color = (
|
|
int(color[0] * current_brightness / 255),
|
|
int(color[1] * current_brightness / 255),
|
|
int(color[2] * current_brightness / 255)
|
|
)
|
|
|
|
self.fill(adjusted_color)
|
|
self.n.write()
|
|
|
|
self.run = False
|
|
self.running = False
|
|
|
|
def rainbow(self):
|
|
"""Rainbow pattern - delay = cycle time, n1 = number of nodes"""
|
|
self.run = True
|
|
|
|
# Calculate timing
|
|
cycle_time = max(100, self.delay) # delay = total cycle time in ms
|
|
num_nodes = max(1, int(self.n1)) # n1 = number of rainbow nodes/segments
|
|
steps_per_cycle = 360 # 360 steps for full cycle
|
|
step_delay = cycle_time // steps_per_cycle # ms per step
|
|
|
|
last_update = utime.ticks_ms()
|
|
|
|
while self.run:
|
|
self.wdt.feed()
|
|
current_time = utime.ticks_ms()
|
|
|
|
# Update rainbow every step_delay ms
|
|
if utime.ticks_diff(current_time, last_update) >= step_delay:
|
|
# Clear all LEDs
|
|
self.n.fill((0, 0, 0))
|
|
|
|
# Rainbow travels along the length - distribute colors along the strip
|
|
for i in range(self.num_leds):
|
|
# Calculate hue based on LED position along the strip
|
|
# Distribute full 360 degrees across the strip, repeat num_nodes times
|
|
# Position along the strip (0.0 to 1.0)
|
|
position = i / self.num_leds
|
|
# Hue cycles based on position and number of nodes
|
|
hue = int((position * 360 * num_nodes + self.step) % 360)
|
|
|
|
# Convert HSV to RGB
|
|
rgb = self.hsv_to_rgb(hue, 255, self.brightness)
|
|
self.n[i] = rgb
|
|
|
|
self.n.write()
|
|
self.step = (self.step + 1) % 360 # Increment step for animation
|
|
last_update = current_time
|
|
|
|
self.run = False
|
|
self.running = False
|
|
|
|
def hsv_to_rgb(self, h, s, v):
|
|
"""Convert HSV to RGB"""
|
|
h = h % 360
|
|
s = min(255, max(0, s))
|
|
v = min(255, max(0, v))
|
|
|
|
c = v * s // 255
|
|
x = c * (60 - abs((h % 120) - 60)) // 60
|
|
m = v - c
|
|
|
|
if h < 60:
|
|
r, g, b = c, x, 0
|
|
elif h < 120:
|
|
r, g, b = x, c, 0
|
|
elif h < 180:
|
|
r, g, b = 0, c, x
|
|
elif h < 240:
|
|
r, g, b = 0, x, c
|
|
elif h < 300:
|
|
r, g, b = x, 0, c
|
|
else:
|
|
r, g, b = c, 0, x
|
|
|
|
return (r + m, g + m, b + m)
|
|
|
|
def flicker(self):
|
|
"""Flicker pattern - random brightness variation on base color"""
|
|
self.run = True
|
|
base_color = self.colors[0]
|
|
|
|
# n1 = minimum brightness (default to 10 if not set)
|
|
min_brightness = max(0, int(self.n1)) if hasattr(self, 'n1') and self.n1 > 0 else 10
|
|
|
|
# Calculate update rate from delay (n3 is not used, delay controls speed)
|
|
update_delay = max(10, int(self.delay)) # At least 10ms to avoid too fast updates
|
|
|
|
last_update = utime.ticks_ms()
|
|
|
|
while self.run:
|
|
self.wdt.feed()
|
|
current_time = utime.ticks_ms()
|
|
|
|
# Update flicker every update_delay ms
|
|
if utime.ticks_diff(current_time, last_update) >= update_delay:
|
|
# Calculate random brightness variation
|
|
# Flicker between min_brightness and full brightness
|
|
max_flicker = self.brightness - min_brightness
|
|
flicker_offset = random.randint(0, max(max_flicker, 1))
|
|
flicker_brightness = min_brightness + flicker_offset
|
|
|
|
# Apply brightness to color
|
|
flicker_color = (
|
|
int(base_color[0] * flicker_brightness / 255),
|
|
int(base_color[1] * flicker_brightness / 255),
|
|
int(base_color[2] * flicker_brightness / 255)
|
|
)
|
|
|
|
self.fill(flicker_color)
|
|
self.n.write()
|
|
last_update = current_time
|
|
|
|
utime.sleep_ms(1)
|
|
|
|
self.run = False
|
|
self.running = False
|
|
|
|
|
|
|
|
# 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):
|
|
"""Chase pattern - n1 LEDs on, n2 LEDs off, bidirectional movement"""
|
|
self.run = True
|
|
|
|
# n1 = on width, n2 = off width
|
|
on_width = max(1, int(self.n1))
|
|
off_width = max(0, int(self.n2))
|
|
segment_length = on_width + off_width
|
|
|
|
if segment_length == 0:
|
|
segment_length = 1
|
|
|
|
# n3 = forward steps per move, n4 = backward steps per move
|
|
forward_steps = max(1, abs(int(self.n3)))
|
|
backward_steps = max(1, abs(int(self.n4)))
|
|
|
|
# Calculate timing from delay
|
|
step_delay = max(10, int(self.delay)) # At least 10ms
|
|
|
|
position = 0 # Current position of the chase head
|
|
phase = "forward" # "forward" or "backward"
|
|
steps_remaining = forward_steps
|
|
total_steps = 0 # Track total steps for wrapping
|
|
|
|
last_update = utime.ticks_ms()
|
|
color = self.apply_brightness(self.colors[0])
|
|
|
|
while self.run:
|
|
self.wdt.feed()
|
|
current_time = utime.ticks_ms()
|
|
|
|
# Check if it's time to move
|
|
if utime.ticks_diff(current_time, last_update) >= step_delay:
|
|
# Move position based on current phase
|
|
if phase == "forward":
|
|
total_steps = (total_steps + 1) % (self.num_leds * segment_length)
|
|
position = total_steps % segment_length
|
|
steps_remaining -= 1
|
|
if steps_remaining == 0:
|
|
phase = "backward"
|
|
steps_remaining = backward_steps
|
|
else: # backward
|
|
total_steps = (total_steps - 1) % (self.num_leds * segment_length)
|
|
position = total_steps % segment_length
|
|
steps_remaining -= 1
|
|
if steps_remaining == 0:
|
|
phase = "forward"
|
|
steps_remaining = forward_steps
|
|
|
|
# Clear all LEDs
|
|
self.n.fill((0, 0, 0))
|
|
|
|
# Draw the chase pattern - repeating segments across all LEDs
|
|
# Position determines where to start drawing on the strip
|
|
for i in range(self.num_leds):
|
|
# Create repeating pattern of on_width on, off_width off
|
|
pos_in_segment = ((i + position) % segment_length)
|
|
if pos_in_segment < on_width:
|
|
self.n[i] = color
|
|
|
|
self.n.write()
|
|
last_update = current_time
|
|
|
|
utime.sleep_ms(1)
|
|
|
|
self.run = False
|
|
self.running = False
|
|
|
|
|
|
# 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:
|
|
# 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
|
|
|
|
|
|
# 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)
|
|
|
|
# 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": 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 ---")
|
|
|
|
|