From 67c4a1a6f69f77b24645e3b2fd17aab0b7b31d1f Mon Sep 17 00:00:00 2001 From: jimmy Date: Thu, 18 Sep 2025 22:10:23 +1200 Subject: [PATCH] Update LED bar to handle message type field - Process 't' field to distinguish between beat ('b') and update ('u') messages - Beat messages: execute pattern immediately using current parameters - Update messages: only update parameters, don't execute pattern - Maintains backward compatibility with default to beat if 't' not specified - Enables proper synchronization between controller and bars --- 8_BAR_SETUP.md | 71 ++++++++++++++++++++++++++++++++++++++++++++++++ configure_bar.py | 58 +++++++++++++++++++++++++++++++++++++++ src/main.py | 34 +++++++++++++++-------- src/patterns.py | 17 ++++++++++++ src/settings.py | 4 +-- 5 files changed, 171 insertions(+), 13 deletions(-) create mode 100644 8_BAR_SETUP.md create mode 100644 configure_bar.py diff --git a/8_BAR_SETUP.md b/8_BAR_SETUP.md new file mode 100644 index 0000000..c340c8f --- /dev/null +++ b/8_BAR_SETUP.md @@ -0,0 +1,71 @@ +# 8-LED Bar System Setup + +This system supports 8 LED bars working together, each with unique names "100" through "107". + +## Quick Setup + +### 1. Configure Each LED Bar +Each LED bar needs a unique name. Run the configuration script on each bar: + +```bash +python configure_bar.py +``` + +Then enter the bar name (100, 101, 102, etc.) when prompted. + +### 2. Update Bar Names (Optional) +To change the bar names, edit `/home/jimmy/projects/lighting-controller/src/bar_config.py`: + +```python +LED_BAR_NAMES = [ + "100", # Bar 1 + "101", # Bar 2 + "102", # Bar 3 + "103", # Bar 4 + "104", # Bar 5 + "105", # Bar 6 + "106", # Bar 7 + "107", # Bar 8 +] +``` + +### 3. Default Settings +All bars use the same default settings defined in `bar_config.py`: + +```python +DEFAULT_BAR_SETTINGS = { + "pattern": "pulse", + "delay": 100, + "colors": [(0, 255, 0)], # Default green + "brightness": 100, + "num_leds": 200, + "n1": 10, + "n2": 10, + "n3": 1, + "n": 0, +} +``` + +## How It Works + +1. **Lighting Controller** sends ESP-NOW messages to all bars simultaneously +2. **Each LED Bar** listens for messages addressed to its unique name +3. **All bars** receive the same pattern/color/brightness settings +4. **Synchronized effects** across all 8 bars + +## Current Features + +- ✅ All bars show the same pattern simultaneously +- ✅ Individual bar addressing (100-107) +- ✅ Optimized JSON payloads with defaults deduplication +- ✅ Easy configuration via `bar_config.py` +- ✅ MIDI control for all bars +- ✅ n3 step rate functionality + +## Future Enhancements + +- Sequential patterns (bar 1 → bar 2 → bar 3...) +- Wave effects across bars +- Individual bar control +- Master/slave synchronization +- Physical arrangement awareness diff --git a/configure_bar.py b/configure_bar.py new file mode 100644 index 0000000..2e4a675 --- /dev/null +++ b/configure_bar.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +""" +LED Bar Configuration Script +Updates the settings.json file for each LED bar with its unique name +""" + +import json +import os + +# LED Bar names/IDs +LED_BAR_NAMES = ["100", "101", "102", "103", "104", "105", "106", "107"] + +def update_bar_settings(bar_name, settings_file="settings.json"): + """Update the settings.json file with the bar name""" + if not os.path.exists(settings_file): + print(f"Error: {settings_file} not found") + return False + + # Read current settings + with open(settings_file, 'r') as f: + settings = json.load(f) + + # Update the name + settings["name"] = bar_name + + # Write back to file + with open(settings_file, 'w') as f: + json.dump(settings, f, indent=4) + + print(f"Updated {settings_file} with name: {bar_name}") + return True + +def main(): + print("LED Bar Configuration Script") + print("=" * 40) + print("Available bar names:", LED_BAR_NAMES) + print() + + while True: + print("Enter bar name to configure (or 'quit' to exit):") + bar_name = input("> ").strip() + + if bar_name.lower() == 'quit': + break + + if bar_name not in LED_BAR_NAMES: + print(f"Invalid bar name. Must be one of: {LED_BAR_NAMES}") + continue + + if update_bar_settings(bar_name): + print(f"Successfully configured LED bar as '{bar_name}'") + else: + print("Failed to update settings") + + print() + +if __name__ == "__main__": + main() diff --git a/src/main.py b/src/main.py index 84a9d36..d24a311 100644 --- a/src/main.py +++ b/src/main.py @@ -1,5 +1,4 @@ -import asyncio -import aioespnow + import patterns from settings import Settings from web import web @@ -28,7 +27,9 @@ def main(): sta_if.active(True) e = espnow.ESPNow() + e.config(rxbuf=1024) e.active(True) + # Increase buffer size for 8-bar payloads (default 526 bytes might be too small) # Set to 1KB to handle larger multi-bar payloads wdt = machine.WDT(timeout=10000) wdt.feed() @@ -50,21 +51,32 @@ def main(): data = json.loads(last_msg) defaults = data.get("d", {}) bar = data.get(settings.get("name"), {}) - - patterns.brightness = bar.get("brightness", defaults.get("brightness", patterns.brightness)) - patterns.delay = bar.get("delay", defaults.get("delay", patterns.delay)) - patterns.colors = bar.get("colors", defaults.get("colors", patterns.colors)) + + # Check message type + message_type = defaults.get("t", "b") # Default to beat if not specified + + # Always update parameters from message + patterns.brightness = bar.get("br", defaults.get("br", patterns.brightness)) + patterns.delay = bar.get("dl", defaults.get("dl", patterns.delay)) + patterns.colors = bar.get("cl", defaults.get("cl", patterns.colors)) patterns.n1 = bar.get("n1", defaults.get("n1", patterns.n1)) patterns.n2 = bar.get("n2", defaults.get("n2", patterns.n2)) patterns.n3 = bar.get("n3", defaults.get("n3", patterns.n3)) patterns.step = bar.get("pattern_step", defaults.get("step", patterns.step)) - selected_pattern = bar.get("pattern", defaults.get("pattern", "off")) - if selected_pattern in patterns.patterns: - # Run the selected pattern ONCE in response to this message. Do not auto-tick elsewhere. - patterns.patterns[selected_pattern]() + # Only execute pattern if it's a beat message + if message_type == "b": # Beat message + selected_pattern = bar.get("pt", defaults.get("pt", "off")) + if selected_pattern in patterns.patterns: + # Run the selected pattern ONCE in response to this beat message + patterns.patterns[selected_pattern]() + else: + print(f"Pattern {selected_pattern} not found") + elif message_type == "u": # Update message + # Just update parameters, don't execute pattern + print(f"Parameters updated: brightness={patterns.brightness}, delay={patterns.delay}") else: - print(f"Pattern {selected_pattern} not found") + print(f"Unknown message type: {message_type}") # except Exception as ex: # print(f"Failed to load espnow data {last_msg}: {ex}") # continue diff --git a/src/patterns.py b/src/patterns.py index 3d26a90..866c713 100644 --- a/src/patterns.py +++ b/src/patterns.py @@ -15,6 +15,7 @@ class Patterns(PatternBase): # Inherit from PatternBase self.n3 = 1 # Default step factor self.oneshot = False # New: One-shot flag for patterns like fill_range self.patterns = { + "off": self.off, "flicker": self.flicker, "fill_range": self.fill_range, "n_chase": self.n_chase, @@ -23,9 +24,25 @@ class Patterns(PatternBase): # Inherit from PatternBase "rainbow": self.rainbow, "specto": self.specto, "radiate": self.radiate, + # 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, } self.step = 0 + 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] diff --git a/src/settings.py b/src/settings.py index 9c4be26..2100b25 100644 --- a/src/settings.py +++ b/src/settings.py @@ -14,9 +14,9 @@ class Settings(dict): def set_defaults(self): self["led_pin"] = 4 - self["num_leds"] = 100 + self["num_leds"] = 59 self["color_order"] = "rgb" - self["name"] = f"3" + self["name"] = f"103" def save(self): try: