Rename patterns module to presets

Rename the driver module and update imports so tests and main entry use the new presets naming, while moving Preset to its own file.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-07 11:40:04 +13:00
parent f35d8f7084
commit 43957adb28
14 changed files with 253 additions and 225 deletions

View File

@@ -2,14 +2,14 @@ from settings import Settings
from machine import WDT
from espnow import ESPNow
import network
from patterns import Patterns
from presets import Presets
from utils import convert_and_reorder_colors
import json
settings = Settings()
print(settings)
patterns = Patterns(settings["led_pin"], settings["num_leds"])
presets = Presets(settings["led_pin"], settings["num_leds"])
wdt = WDT(timeout=10000)
wdt.feed()
@@ -24,7 +24,7 @@ e.active(True)
while True:
wdt.feed()
patterns.tick()
presets.tick()
if e.any():
host, msg = e.recv()
data = json.loads(msg)
@@ -35,7 +35,7 @@ while True:
# Global brightness (0255) for this device
if "b" in data:
try:
patterns.b = max(0, min(255, int(data["b"])))
presets.b = max(0, min(255, int(data["b"])))
except (TypeError, ValueError):
pass
if "presets" in data:
@@ -43,11 +43,11 @@ while True:
# Convert hex color strings to RGB tuples and reorder based on device color order
if "c" in preset_data:
preset_data["c"] = convert_and_reorder_colors(preset_data["c"], settings)
patterns.edit(name, preset_data)
presets.edit(name, preset_data)
if settings.get("name") in data.get("select", {}):
select_list = data["select"][settings.get("name")]
# Select value is always a list: ["preset_name"] or ["preset_name", step]
if select_list:
preset_name = select_list[0]
step = select_list[1] if len(select_list) > 1 else None
patterns.select(preset_name, step=step)
presets.select(preset_name, step=step)

24
src/preset.py Normal file
View File

@@ -0,0 +1,24 @@
class Preset:
def __init__(self, data):
# Set default values for all preset attributes
self.p = "off"
self.d = 100
self.b = 127
self.c = [(255, 255, 255)]
self.a = True
self.n1 = 0
self.n2 = 0
self.n3 = 0
self.n4 = 0
self.n5 = 0
self.n6 = 0
# Override defaults with provided data
self.edit(data)
def edit(self, data=None):
if not data:
return False
for key, value in data.items():
setattr(self, key, value)
return True

View File

@@ -1,6 +1,7 @@
from machine import Pin
from neopixel import NeoPixel
import utime
from preset import Preset
# Short-key parameter mapping for convenience setters
@@ -22,32 +23,7 @@ param_mapping = {
"auto": "auto",
}
class Preset:
def __init__(self, data):
# Set default values for all preset attributes
self.p = "off"
self.d = 100
self.b = 127
self.c = [(255, 255, 255)]
self.a = True
self.n1 = 0
self.n2 = 0
self.n3 = 0
self.n4 = 0
self.n5 = 0
self.n6 = 0
# Override defaults with provided data
self.edit(data)
def edit(self, data=None):
if not data:
return False
for key, value in data.items():
setattr(self, key, value)
return True
class Patterns:
class Presets:
def __init__(self, pin, num_leds):
self.n = NeoPixel(Pin(pin, Pin.OUT), num_leds)
self.num_leds = num_leds
@@ -154,16 +130,26 @@ class Patterns:
return (0, pos * 3, 255 - pos * 3)
def blink(self, preset):
"""Blink pattern: toggles LEDs on/off using preset delay, cycling through colors."""
# Use provided colors, or default to white if none
colors = preset.c if preset.c else [(255, 255, 255)]
color_index = 0
state = True # True = on, False = off
last_update = utime.ticks_ms()
while True:
current_time = utime.ticks_ms()
if utime.ticks_diff(current_time, last_update) >= preset.d:
# Re-read delay each loop so live updates to preset.d take effect
delay_ms = max(1, int(preset.d))
if utime.ticks_diff(current_time, last_update) >= delay_ms:
if state:
color = preset.c[0] if preset.c else (255, 255, 255)
self.fill(self.apply_brightness(color, preset.b))
base_color = colors[color_index % len(colors)]
color = self.apply_brightness(base_color, preset.b)
self.fill(color)
# Advance to next color for the next "on" phase
color_index += 1
else:
# "Off" phase: turn all LEDs off
self.fill((0, 0, 0))
state = not state
last_update = current_time
@@ -447,25 +433,41 @@ class Patterns:
last_tail_move = utime.ticks_ms()
phase = "growing" # "growing", "shrinking", or "off"
# Support up to two colors (like chase). If only one color is provided,
# use black for the second; if none, default to white.
colors = preset.c
color = self.apply_brightness(colors[0] if colors else (255, 255, 255), preset.b)
if not colors:
base0 = base1 = (255, 255, 255)
elif len(colors) == 1:
base0 = colors[0]
base1 = (0, 0, 0)
else:
base0 = colors[0]
base1 = colors[1]
color0 = self.apply_brightness(base0, preset.b)
color1 = self.apply_brightness(base1, preset.b)
while True:
current_time = utime.ticks_ms()
# Clear all LEDs
self.n.fill((0, 0, 0))
# Background: use second color during the "off" phase, otherwise clear to black
if phase == "off":
self.n.fill(color1)
else:
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
# Draw segment from tail to head as a solid color (no per-LED alternation)
current_color = color0
for i in range(segment_length + 1):
led_pos = (tail + i) % self.num_leds
self.n[led_pos] = color
self.n[led_pos] = current_color
# Move head continuously at n1 LEDs per second
if utime.ticks_diff(current_time, last_head_move) >= head_delay:
@@ -494,7 +496,7 @@ class Patterns:
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
# Off phase: second color fills the ring for 1 step, then restart
tail = head # Reset tail to head position to start fresh
phase = "growing"