Files
led-bar/src/patterns.py
jimmy 846d574ad6 feat: add pulse pattern
Configurable attack/hold/decay phases via n1/n2/n3. Single-shot when delay=0.
2025-11-06 19:16:51 +13:00

132 lines
4.8 KiB
Python

from machine import Pin
from neopixel import NeoPixel
import utime
import random
import _thread
import asyncio
from patterns_base import Patterns as PatternsBase
# Short-key parameter mapping for convenience setters
param_mapping = {
"pt": "selected",
"pa": "selected",
"cl": "colors",
"br": "brightness",
"dl": "delay",
"nl": "num_leds",
"co": "color_order",
"lp": "led_pin",
"n1": "n1",
"n2": "n2",
"n3": "n3",
"n4": "n4",
"n5": "n5",
"n6": "n6",
}
class Patterns(PatternsBase):
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)
self.patterns = {
"off": self.off,
"on" : self.on,
"blink": self.blink,
"rainbow": self.rainbow,
"pulse": self.pulse,
}
def blink(self):
self.stopped = False
self.running = True
while self.running:
self.fill(self.apply_brightness(self.colors[0]))
utime.sleep_ms(self.delay)
self.fill((0, 0, 0))
utime.sleep_ms(self.delay)
self.running = False
self.stopped = True
def rainbow(self):
self.stopped = False
self.running = True
step = self.pattern_step % 256
while self.running:
for i in range(self.num_leds):
rc_index = (i * 256 // self.num_leds) + step
self.n[i] = self.apply_brightness(self.wheel(rc_index & 255))
self.n.write()
step = (step + 1) % 256
self.pattern_step = step
# faster animation even with larger delays; scale delay
sleep_ms = max(1, int(self.delay / 5))
utime.sleep_ms(sleep_ms)
self.running = False
self.stopped = True
def pulse(self):
self.stopped = False
self.running = True
self.off()
# Get timing parameters with defaults if not set
attack_ms = getattr(self, 'n1', 200) # Attack time in ms
hold_ms = getattr(self, 'n2', 200) # Hold time in ms
decay_ms = getattr(self, 'n3', 200) # Decay time in ms
base_color = self.colors[0] if self.colors else (255, 255, 255)
update_interval = 10 # Update every ~10ms for smoothness
while self.running:
cycle_start = utime.ticks_ms()
# Attack phase: fade from 0 to full brightness
if attack_ms > 0:
attack_start = utime.ticks_ms()
last_update = attack_start
while self.running and utime.ticks_diff(utime.ticks_ms(), attack_start) < attack_ms:
now = utime.ticks_ms()
if utime.ticks_diff(now, last_update) >= update_interval:
elapsed = utime.ticks_diff(now, attack_start)
brightness_factor = min(1.0, elapsed / attack_ms)
color = tuple(int(c * brightness_factor) for c in base_color)
self.fill(self.apply_brightness(color))
last_update = now
# Hold phase: maintain full brightness
if hold_ms > 0 and self.running:
self.fill(self.apply_brightness(base_color))
hold_start = utime.ticks_ms()
while self.running and utime.ticks_diff(utime.ticks_ms(), hold_start) < hold_ms:
pass
# Decay phase: fade from full brightness to 0
if decay_ms > 0:
decay_start = utime.ticks_ms()
last_update = decay_start
while self.running and utime.ticks_diff(utime.ticks_ms(), decay_start) < decay_ms:
now = utime.ticks_ms()
if utime.ticks_diff(now, last_update) >= update_interval:
elapsed = utime.ticks_diff(now, decay_start)
brightness_factor = max(0.0, 1.0 - (elapsed / decay_ms))
color = tuple(int(c * brightness_factor) for c in base_color)
self.fill(self.apply_brightness(color))
last_update = now
# If delay is 0, run only once and exit
if self.delay == 0:
break
# Ensure the cycle takes exactly delay milliseconds before restarting
if self.running:
self.off()
wait_until = utime.ticks_add(cycle_start, self.delay)
while self.running and utime.ticks_diff(wait_until, utime.ticks_ms()) > 0:
pass
self.running = False
self.stopped = True