Compare commits
7 Commits
d8e853183b
...
espnow
Author | SHA1 | Date | |
---|---|---|---|
ae407ab3aa | |||
e516b49eb8 | |||
355d113e32 | |||
d715af4344 | |||
67c4a1a6f6 | |||
748ad4b507 | |||
1275d60aaa |
71
8_BAR_SETUP.md
Normal file
71
8_BAR_SETUP.md
Normal file
@@ -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
|
2
Pipfile
2
Pipfile
@@ -16,4 +16,4 @@ uvicorn = "*"
|
|||||||
python_version = "3.12"
|
python_version = "3.12"
|
||||||
|
|
||||||
[scripts]
|
[scripts]
|
||||||
dev = 'watchfiles "./dev.py /dev/ttyACM0 src reset follow"'
|
dev = "./dev.py"
|
||||||
|
58
configure_bar.py
Normal file
58
configure_bar.py
Normal file
@@ -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()
|
45
src/main.py
45
src/main.py
@@ -1,5 +1,4 @@
|
|||||||
import asyncio
|
|
||||||
import aioespnow
|
|
||||||
import patterns
|
import patterns
|
||||||
from settings import Settings
|
from settings import Settings
|
||||||
from web import web
|
from web import web
|
||||||
@@ -18,13 +17,19 @@ def main():
|
|||||||
settings = Settings()
|
settings = Settings()
|
||||||
print(settings)
|
print(settings)
|
||||||
|
|
||||||
|
if settings.get("color_order", "rgb") == "rbg":
|
||||||
|
color_order = (1, 5, 3)
|
||||||
|
else:
|
||||||
|
color_order = (1, 3, 5)
|
||||||
patterns = Patterns(settings["led_pin"], settings["num_leds"], selected="off")
|
patterns = Patterns(settings["led_pin"], settings["num_leds"], selected="off")
|
||||||
|
|
||||||
sta_if = network.WLAN(network.STA_IF)
|
sta_if = network.WLAN(network.STA_IF)
|
||||||
sta_if.active(True)
|
sta_if.active(True)
|
||||||
|
|
||||||
e = espnow.ESPNow()
|
e = espnow.ESPNow()
|
||||||
|
e.config(rxbuf=1024)
|
||||||
e.active(True)
|
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 = machine.WDT(timeout=10000)
|
||||||
wdt.feed()
|
wdt.feed()
|
||||||
@@ -44,23 +49,39 @@ def main():
|
|||||||
if last_msg:
|
if last_msg:
|
||||||
try:
|
try:
|
||||||
data = json.loads(last_msg)
|
data = json.loads(last_msg)
|
||||||
|
print(data)
|
||||||
defaults = data.get("d", {})
|
defaults = data.get("d", {})
|
||||||
bar = data.get(settings.get("name"), {})
|
bar = data.get(settings.get("name"), {})
|
||||||
|
|
||||||
patterns.brightness = bar.get("brightness", defaults.get("brightness", patterns.brightness))
|
# Check message type
|
||||||
patterns.delay = bar.get("delay", defaults.get("delay", patterns.delay))
|
message_type = defaults.get("t", "b") # Default to beat if not specified
|
||||||
colors = bar.get("colors", defaults.get("colors", patterns.colors))
|
|
||||||
patterns.colors = [tuple(int(color[i:i+2], 16) for i in settings.color_order) for color in colors]
|
# 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.n1 = bar.get("n1", defaults.get("n1", patterns.n1))
|
||||||
patterns.n2 = bar.get("n2", defaults.get("n2", patterns.n2))
|
patterns.n2 = bar.get("n2", defaults.get("n2", patterns.n2))
|
||||||
patterns.step = bar.get("pattern_step", defaults.get("step", patterns.step))
|
patterns.n3 = bar.get("n3", defaults.get("n3", patterns.n3))
|
||||||
|
patterns.n4 = bar.get("n4", defaults.get("n4", patterns.n4))
|
||||||
|
patterns.step = bar.get("s", defaults.get("s", patterns.step))
|
||||||
|
|
||||||
selected_pattern = bar.get("pattern", defaults.get("pattern", "off"))
|
# Print received parameters
|
||||||
if selected_pattern in patterns.patterns:
|
print(f"Params: br={patterns.brightness}, dl={patterns.delay}, n1={patterns.n1}, n2={patterns.n2}, n3={patterns.n3}, n4={patterns.n4}, step={patterns.step}")
|
||||||
# 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:
|
else:
|
||||||
print(f"Pattern {selected_pattern} not found")
|
print(f"Unknown message type: {message_type}")
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print(f"Failed to load espnow data {last_msg}: {ex}")
|
print(f"Failed to load espnow data {last_msg}: {ex}")
|
||||||
continue
|
continue
|
||||||
|
176
src/patterns.py
176
src/patterns.py
@@ -12,8 +12,12 @@ class Patterns(PatternBase): # Inherit from PatternBase
|
|||||||
self.off_width = 2 # Default off width (so total segment is 3, matching original behavior)
|
self.off_width = 2 # Default off width (so total segment is 3, matching original behavior)
|
||||||
self.n1 = 0 # Default start of fill range
|
self.n1 = 0 # Default start of fill range
|
||||||
self.n2 = self.num_leds - 1 # Default end 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.oneshot = False # New: One-shot flag for patterns like fill_range
|
||||||
self.patterns = {
|
self.patterns = {
|
||||||
|
"on": self.on,
|
||||||
|
"off": self.off,
|
||||||
"flicker": self.flicker,
|
"flicker": self.flicker,
|
||||||
"fill_range": self.fill_range,
|
"fill_range": self.fill_range,
|
||||||
"n_chase": self.n_chase,
|
"n_chase": self.n_chase,
|
||||||
@@ -22,21 +26,47 @@ class Patterns(PatternBase): # Inherit from PatternBase
|
|||||||
"rainbow": self.rainbow,
|
"rainbow": self.rainbow,
|
||||||
"specto": self.specto,
|
"specto": self.specto,
|
||||||
"radiate": self.radiate,
|
"radiate": self.radiate,
|
||||||
|
"segmented_movement": self.segmented_movement,
|
||||||
|
# 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,
|
||||||
|
"sm": self.segmented_movement,
|
||||||
}
|
}
|
||||||
self.step = 0
|
self.step = 0
|
||||||
|
|
||||||
|
def on(self):
|
||||||
|
"""Turn on all LEDs with current color"""
|
||||||
|
self.fill(self.apply_brightness(self.colors[0]))
|
||||||
|
self.n.write()
|
||||||
|
return self.delay
|
||||||
|
|
||||||
|
def off(self):
|
||||||
|
"""Turn off all LEDs"""
|
||||||
|
self.fill((0, 0, 0))
|
||||||
|
self.n.write()
|
||||||
|
return self.delay
|
||||||
|
|
||||||
def flicker(self):
|
def flicker(self):
|
||||||
current_time = utime.ticks_ms()
|
current_time = utime.ticks_ms()
|
||||||
base_color = self.colors[0]
|
base_color = self.colors[0]
|
||||||
# Increase the range for flicker_brightness_offset
|
# Use fixed minimum brightness of 10, flicker between 10 and full brightness
|
||||||
# Changed from self.brightness // 4 to self.brightness // 2 (or even self.brightness for max intensity)
|
# 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_offset = random.randint(-int(self.brightness // 1.5), int(self.brightness // 1.5))
|
||||||
flicker_brightness = max(0, min(255, self.brightness + flicker_brightness_offset))
|
flicker_brightness = max(min_brightness, min(255, self.brightness + flicker_brightness_offset))
|
||||||
|
|
||||||
flicker_color = self.apply_brightness(base_color, brightness_override=flicker_brightness)
|
flicker_color = self.apply_brightness(base_color, brightness_override=flicker_brightness)
|
||||||
self.fill(flicker_color)
|
self.fill(flicker_color)
|
||||||
self.last_update = current_time
|
self.last_update = current_time
|
||||||
return max(1, int(self.delay // 5))
|
return max(1, int(self.delay // (5 * step_rate)))
|
||||||
|
|
||||||
def fill_range(self):
|
def fill_range(self):
|
||||||
"""
|
"""
|
||||||
@@ -61,6 +91,7 @@ class Patterns(PatternBase): # Inherit from PatternBase
|
|||||||
A theater chase pattern using n1 for on-width and n2 for off-width.
|
A theater chase pattern using n1 for on-width and n2 for off-width.
|
||||||
"""
|
"""
|
||||||
current_time = utime.ticks_ms()
|
current_time = utime.ticks_ms()
|
||||||
|
step_rate = max(1, int(self.n3))
|
||||||
segment_length = self.n1 + self.n2
|
segment_length = self.n1 + self.n2
|
||||||
if segment_length == 0: # Avoid division by zero
|
if segment_length == 0: # Avoid division by zero
|
||||||
self.fill((0,0,0))
|
self.fill((0,0,0))
|
||||||
@@ -68,13 +99,19 @@ class Patterns(PatternBase): # Inherit from PatternBase
|
|||||||
self.last_update = current_time
|
self.last_update = current_time
|
||||||
return self.delay
|
return self.delay
|
||||||
|
|
||||||
|
# Use controller's step for synchronization, but scale it for chasing
|
||||||
|
chase_step = (self.step * step_rate) % self.num_leds
|
||||||
|
|
||||||
for i in range(self.num_leds):
|
for i in range(self.num_leds):
|
||||||
if (i + self.pattern_step) % segment_length < self.n1:
|
# Calculate position relative to the chase head
|
||||||
|
pos_from_head = (i - chase_step) % self.num_leds
|
||||||
|
if pos_from_head < self.n1:
|
||||||
self.n[i] = self.apply_brightness(self.colors[0])
|
self.n[i] = self.apply_brightness(self.colors[0])
|
||||||
else:
|
else:
|
||||||
self.n[i] = (0, 0, 0)
|
self.n[i] = (0, 0, 0)
|
||||||
self.n.write()
|
self.n.write()
|
||||||
self.pattern_step = (self.pattern_step + 1) % segment_length
|
|
||||||
|
# Don't update internal step - use controller's step for sync
|
||||||
self.last_update = current_time
|
self.last_update = current_time
|
||||||
return self.delay
|
return self.delay
|
||||||
|
|
||||||
@@ -108,8 +145,8 @@ class Patterns(PatternBase): # Inherit from PatternBase
|
|||||||
self.n[i] = active_color
|
self.n[i] = active_color
|
||||||
|
|
||||||
self.n.write()
|
self.n.write()
|
||||||
self.step = (self.step + 1) % 2
|
# Don't update step - use the step value sent from controller for synchronization
|
||||||
return self.delay
|
return max(1, int(self.delay // 2))
|
||||||
|
|
||||||
|
|
||||||
def pulse(self):
|
def pulse(self):
|
||||||
@@ -166,11 +203,16 @@ class Patterns(PatternBase): # Inherit from PatternBase
|
|||||||
pos -= 170
|
pos -= 170
|
||||||
return (0, pos * 3, 255 - pos * 3)
|
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):
|
for i in range(self.num_leds):
|
||||||
rc_index = (i * 256 // max(1, self.num_leds)) + self.pattern_step
|
rc_index = (i * 256 // max(1, self.num_leds)) + rainbow_step
|
||||||
self.n[i] = self.apply_brightness(wheel(rc_index & 255))
|
self.n[i] = self.apply_brightness(wheel(rc_index & 255))
|
||||||
self.n.write()
|
self.n.write()
|
||||||
self.pattern_step = (self.pattern_step + 1) % 256
|
|
||||||
|
# Don't update internal step - use controller's step for sync
|
||||||
return max(1, int(self.delay // 5))
|
return max(1, int(self.delay // 5))
|
||||||
|
|
||||||
def specto(self):
|
def specto(self):
|
||||||
@@ -221,8 +263,8 @@ class Patterns(PatternBase): # Inherit from PatternBase
|
|||||||
if lit_total >= self.num_leds:
|
if lit_total >= self.num_leds:
|
||||||
break
|
break
|
||||||
# wait self.delay ms before next ring
|
# wait self.delay ms before next ring
|
||||||
start = utime.ticks_ms()
|
start = utime.ticks_us()
|
||||||
while utime.ticks_diff(utime.ticks_ms(), start) < self.delay:
|
while utime.ticks_diff(utime.ticks_us(), start) < self.delay:
|
||||||
pass
|
pass
|
||||||
radius += 1
|
radius += 1
|
||||||
|
|
||||||
@@ -237,8 +279,8 @@ class Patterns(PatternBase): # Inherit from PatternBase
|
|||||||
if 0 <= right < self.num_leds:
|
if 0 <= right < self.num_leds:
|
||||||
self.n[right] = (0, 0, 0)
|
self.n[right] = (0, 0, 0)
|
||||||
self.n.write()
|
self.n.write()
|
||||||
start = utime.ticks_ms()
|
start = utime.ticks_us()
|
||||||
while utime.ticks_diff(utime.ticks_ms(), start) < self.delay:
|
while utime.ticks_diff(utime.ticks_us(), start) < self.delay:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# ensure all LEDs are off at completion
|
# ensure all LEDs are off at completion
|
||||||
@@ -247,6 +289,112 @@ class Patterns(PatternBase): # Inherit from PatternBase
|
|||||||
self.run = False
|
self.run = False
|
||||||
return self.delay
|
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__":
|
if __name__ == "__main__":
|
||||||
import time
|
import time
|
||||||
|
@@ -13,10 +13,10 @@ class Settings(dict):
|
|||||||
else: self.color_order = (1, 3, 5)
|
else: self.color_order = (1, 3, 5)
|
||||||
|
|
||||||
def set_defaults(self):
|
def set_defaults(self):
|
||||||
self["led_pin"] = 4
|
self["led_pin"] = 10
|
||||||
self["num_leds"] = 100
|
self["num_leds"] = 119
|
||||||
self["color_order"] = "rgb"
|
self["color_order"] = "rgb"
|
||||||
self["name"] = f"3"
|
self["name"] = f"104"
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
try:
|
try:
|
||||||
|
Reference in New Issue
Block a user