backend: per-pattern parameters (n1-n4, delay) with persistence; REST responses include loaded params; logging of API inputs; alternating_phase: alternate colors between selected palette color 1/2 across bars with compact payload under 230 bytes; docs: add PER_PATTERN_PARAMETERS.md
This commit is contained in:
@@ -124,18 +124,32 @@ class LightingController:
|
||||
|
||||
# Lighting state
|
||||
self.current_pattern = ""
|
||||
self.delay = 100
|
||||
self.brightness = 100
|
||||
self.color_r = 0
|
||||
self.color_g = 255
|
||||
self.color_b = 0
|
||||
self.n1 = 10
|
||||
self.n2 = 10
|
||||
self.n3 = 1
|
||||
self.n4 = 1
|
||||
self.beat_index = 0
|
||||
self.beat_sending_enabled = True
|
||||
|
||||
# Per-pattern parameters (pattern_name -> {delay, n1, n2, n3, n4})
|
||||
self.pattern_parameters = {}
|
||||
|
||||
# Default parameters for new patterns
|
||||
self.default_params = {
|
||||
"delay": 100,
|
||||
"n1": 10,
|
||||
"n2": 10,
|
||||
"n3": 1,
|
||||
"n4": 1
|
||||
}
|
||||
|
||||
# Current active parameters (loaded from current pattern)
|
||||
self.delay = self.default_params["delay"]
|
||||
self.n1 = self.default_params["n1"]
|
||||
self.n2 = self.default_params["n2"]
|
||||
self.n3 = self.default_params["n3"]
|
||||
self.n4 = self.default_params["n4"]
|
||||
|
||||
# Color palette (8 slots, 2 selected)
|
||||
self.color_palette = [
|
||||
{"r": 255, "g": 0, "b": 0}, # Red
|
||||
@@ -157,6 +171,35 @@ class LightingController:
|
||||
self.param_update_interval = 0.1
|
||||
self.pending_param_update = False
|
||||
|
||||
def _load_pattern_parameters(self, pattern_name):
|
||||
"""Load parameters for a specific pattern."""
|
||||
if pattern_name in self.pattern_parameters:
|
||||
params = self.pattern_parameters[pattern_name]
|
||||
self.delay = params.get("delay", self.default_params["delay"])
|
||||
self.n1 = params.get("n1", self.default_params["n1"])
|
||||
self.n2 = params.get("n2", self.default_params["n2"])
|
||||
self.n3 = params.get("n3", self.default_params["n3"])
|
||||
self.n4 = params.get("n4", self.default_params["n4"])
|
||||
else:
|
||||
# Use defaults for new pattern
|
||||
self.delay = self.default_params["delay"]
|
||||
self.n1 = self.default_params["n1"]
|
||||
self.n2 = self.default_params["n2"]
|
||||
self.n3 = self.default_params["n3"]
|
||||
self.n4 = self.default_params["n4"]
|
||||
|
||||
def _save_pattern_parameters(self, pattern_name):
|
||||
"""Save current parameters for the active pattern."""
|
||||
if pattern_name:
|
||||
self.pattern_parameters[pattern_name] = {
|
||||
"delay": self.delay,
|
||||
"n1": self.n1,
|
||||
"n2": self.n2,
|
||||
"n3": self.n3,
|
||||
"n4": self.n4
|
||||
}
|
||||
self._save_config()
|
||||
|
||||
def _current_color_rgb(self):
|
||||
"""Get current RGB color tuple from selected palette color (index 0)."""
|
||||
# Use the first selected color from the palette
|
||||
@@ -172,6 +215,16 @@ class LightingController:
|
||||
b = max(0, min(255, int(self.color_b)))
|
||||
return (r, g, b)
|
||||
|
||||
def _palette_color(self, selected_index_position: int):
|
||||
"""Return RGB tuple for the selected palette color at given position (0 or 1)."""
|
||||
if not self.selected_color_indices or selected_index_position >= len(self.selected_color_indices):
|
||||
return self._current_color_rgb()
|
||||
color_index = self.selected_color_indices[selected_index_position]
|
||||
if 0 <= color_index < len(self.color_palette):
|
||||
color = self.color_palette[color_index]
|
||||
return (color['r'], color['g'], color['b'])
|
||||
return self._current_color_rgb()
|
||||
|
||||
def _load_config(self):
|
||||
"""Load configuration from file."""
|
||||
try:
|
||||
@@ -187,6 +240,10 @@ class LightingController:
|
||||
if "selected_color_indices" in config:
|
||||
self.selected_color_indices = config["selected_color_indices"]
|
||||
|
||||
# Load per-pattern parameters
|
||||
if "pattern_parameters" in config:
|
||||
self.pattern_parameters = config["pattern_parameters"]
|
||||
|
||||
logging.info(f"Loaded config from {CONFIG_FILE}")
|
||||
except Exception as e:
|
||||
logging.error(f"Error loading config: {e}")
|
||||
@@ -197,6 +254,7 @@ class LightingController:
|
||||
config = {
|
||||
"color_palette": self.color_palette,
|
||||
"selected_color_indices": self.selected_color_indices,
|
||||
"pattern_parameters": self.pattern_parameters,
|
||||
}
|
||||
with open(CONFIG_FILE, 'w') as f:
|
||||
json.dump(config, f, indent=2)
|
||||
@@ -266,12 +324,21 @@ class LightingController:
|
||||
|
||||
async def _send_normal_pattern(self):
|
||||
"""Send normal pattern to all bars."""
|
||||
patterns_needing_params = ["alternating", "flicker", "n_chase", "rainbow", "radiate", "segmented_movement"]
|
||||
# Patterns that need parameters (both long and short names)
|
||||
patterns_needing_params = [
|
||||
"alternating", "a",
|
||||
"flicker", "f",
|
||||
"n_chase", "nc",
|
||||
"rainbow", "r",
|
||||
"radiate", "rd",
|
||||
"segmented_movement", "sm"
|
||||
]
|
||||
|
||||
payload = {
|
||||
"d": {
|
||||
"t": "b", # Message type: beat
|
||||
"pt": PATTERN_NAMES.get(self.current_pattern, self.current_pattern),
|
||||
"cl": [self._current_color_rgb()], # Always send color
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,23 +379,35 @@ class LightingController:
|
||||
|
||||
async def _handle_alternating_phase(self):
|
||||
"""Handle alternating pattern with phase offset."""
|
||||
# Determine two colors from selected palette (fallback to current color if not set)
|
||||
color_a = self._palette_color(0)
|
||||
color_b = self._palette_color(1)
|
||||
phase = self.beat_index % 2
|
||||
|
||||
payload = {
|
||||
"d": {
|
||||
"t": "b",
|
||||
"pt": "a", # alternating
|
||||
"n1": self.n1,
|
||||
"n2": self.n2,
|
||||
"s": self.beat_index % 2,
|
||||
# Default color for bars not overridden (keeps payload small)
|
||||
"cl": [color_a],
|
||||
"s": phase,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Bars in this list will have inverted phase and explicit color override
|
||||
swap_bars = ["101", "103", "105", "107"]
|
||||
for bar_name in LED_BAR_NAMES:
|
||||
if bar_name in swap_bars:
|
||||
payload[bar_name] = {"s": (self.beat_index + 1) % 2}
|
||||
# Invert phase and explicitly set alternate color
|
||||
inv_phase = (phase + 1) % 2
|
||||
alt_color = color_b if phase == 0 else color_a
|
||||
payload[bar_name] = {"s": inv_phase, "cl": [alt_color]}
|
||||
else:
|
||||
# Keep default (no extra fields) to save bytes
|
||||
payload[bar_name] = {}
|
||||
|
||||
|
||||
await self.led_controller.send_data(payload)
|
||||
|
||||
async def handle_beat(self, bpm_value):
|
||||
@@ -357,7 +436,14 @@ class LightingController:
|
||||
async def handle_ui_command(self, message_type, data):
|
||||
"""Handle command from UI client."""
|
||||
if message_type == "pattern_change":
|
||||
# Save current pattern's parameters before switching
|
||||
if self.current_pattern:
|
||||
self._save_pattern_parameters(self.current_pattern)
|
||||
|
||||
# Switch to new pattern and load its parameters
|
||||
self.current_pattern = data.get("pattern", "")
|
||||
self._load_pattern_parameters(self.current_pattern)
|
||||
|
||||
await self._send_full_parameters()
|
||||
logging.info(f"Pattern changed to: {self.current_pattern}")
|
||||
|
||||
@@ -380,10 +466,20 @@ class LightingController:
|
||||
self.n3 = data["n3"]
|
||||
if "n4" in data:
|
||||
self.n4 = data["n4"]
|
||||
|
||||
# Save parameters for current pattern
|
||||
if self.current_pattern:
|
||||
self._save_pattern_parameters(self.current_pattern)
|
||||
|
||||
await self._request_param_update()
|
||||
|
||||
elif message_type == "delay_change":
|
||||
self.delay = data.get("delay", self.delay)
|
||||
|
||||
# Save parameters for current pattern
|
||||
if self.current_pattern:
|
||||
self._save_pattern_parameters(self.current_pattern)
|
||||
|
||||
await self._request_param_update()
|
||||
|
||||
elif message_type == "beat_toggle":
|
||||
@@ -531,6 +627,7 @@ class ControlServer:
|
||||
"""HTTP POST /api/pattern"""
|
||||
try:
|
||||
data = await request.json()
|
||||
logging.info(f"API received pattern change: {data}")
|
||||
pattern = data.get("pattern")
|
||||
if not pattern:
|
||||
return web.json_response(
|
||||
@@ -538,13 +635,27 @@ class ControlServer:
|
||||
status=400
|
||||
)
|
||||
|
||||
# Save current pattern's parameters before switching
|
||||
if self.lighting_controller.current_pattern:
|
||||
self.lighting_controller._save_pattern_parameters(self.lighting_controller.current_pattern)
|
||||
|
||||
# Switch to new pattern and load its parameters
|
||||
self.lighting_controller.current_pattern = pattern
|
||||
self.lighting_controller._load_pattern_parameters(pattern)
|
||||
|
||||
await self.lighting_controller._send_full_parameters()
|
||||
logging.info(f"Pattern changed to: {pattern}")
|
||||
logging.info(f"Pattern changed to: {pattern} with params: delay={self.lighting_controller.delay}, n1={self.lighting_controller.n1}, n2={self.lighting_controller.n2}, n3={self.lighting_controller.n3}, n4={self.lighting_controller.n4}")
|
||||
|
||||
return web.json_response({
|
||||
"status": "ok",
|
||||
"pattern": self.lighting_controller.current_pattern
|
||||
"pattern": self.lighting_controller.current_pattern,
|
||||
"parameters": {
|
||||
"delay": self.lighting_controller.delay,
|
||||
"n1": self.lighting_controller.n1,
|
||||
"n2": self.lighting_controller.n2,
|
||||
"n3": self.lighting_controller.n3,
|
||||
"n4": self.lighting_controller.n4
|
||||
}
|
||||
})
|
||||
except Exception as e:
|
||||
return web.json_response(
|
||||
@@ -563,6 +674,7 @@ class ControlServer:
|
||||
"""HTTP POST /api/parameters"""
|
||||
try:
|
||||
data = await request.json()
|
||||
logging.info(f"API received parameter update: {data}")
|
||||
|
||||
# Update any provided parameters
|
||||
if "brightness" in data:
|
||||
@@ -578,6 +690,12 @@ class ControlServer:
|
||||
if "n4" in data:
|
||||
self.lighting_controller.n4 = int(data["n4"])
|
||||
|
||||
logging.info(f"Updated parameters for pattern '{self.lighting_controller.current_pattern}': brightness={self.lighting_controller.brightness}, delay={self.lighting_controller.delay}, n1={self.lighting_controller.n1}, n2={self.lighting_controller.n2}, n3={self.lighting_controller.n3}, n4={self.lighting_controller.n4}")
|
||||
|
||||
# Save parameters for current pattern
|
||||
if self.lighting_controller.current_pattern:
|
||||
self.lighting_controller._save_pattern_parameters(self.lighting_controller.current_pattern)
|
||||
|
||||
# Send updated parameters to LED bars
|
||||
await self.lighting_controller._send_full_parameters()
|
||||
|
||||
|
Reference in New Issue
Block a user