Add segmented_movement pattern with alternating forward/backward movement

- Add n4 parameter support to main.py ESP NOW receiver
- Implement segmented_movement pattern with configurable parameters:
  * n1: segment length (number of LEDs per segment)
  * n2: spacing between segments
  * n3: forward movement speed (positions per beat)
  * n4: backward movement speed (positions per beat)
- Pattern alternates between forward and backward movement each beat
- If only n3 or n4 is set, moves in that direction every beat
- Draws repeating segments with spacing across entire LED strip
- Add Pipfile script to run dev.py directly with arguments
This commit is contained in:
2025-10-03 19:56:24 +13:00
parent 355d113e32
commit e516b49eb8
4 changed files with 114 additions and 3 deletions

View File

@@ -16,4 +16,4 @@ uvicorn = "*"
python_version = "3.12"
[scripts]
dev = 'watchfiles "./dev.py /dev/ttyACM0 src reset follow"'
dev = "./dev.py"

View File

@@ -49,6 +49,7 @@ def main():
if last_msg:
try:
data = json.loads(last_msg)
print(data)
defaults = data.get("d", {})
bar = data.get(settings.get("name"), {})
@@ -62,6 +63,7 @@ def main():
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.n4 = bar.get("n4", defaults.get("n4", patterns.n4))
patterns.step = bar.get("s", defaults.get("s", patterns.step))
# Only execute pattern if it's a beat message

View File

@@ -13,6 +13,7 @@ class Patterns(PatternBase): # Inherit from PatternBase
self.n1 = 0 # Default start 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.patterns = {
"on": self.on,
@@ -25,6 +26,7 @@ class Patterns(PatternBase): # Inherit from PatternBase
"rainbow": self.rainbow,
"specto": self.specto,
"radiate": self.radiate,
"segmented_movement": self.segmented_movement,
# Shortened pattern names for optimized JSON payloads
"o": self.off,
"f": self.flicker,
@@ -35,6 +37,7 @@ class Patterns(PatternBase): # Inherit from PatternBase
"r": self.rainbow,
"s": self.specto,
"rd": self.radiate,
"sm": self.segmented_movement,
}
self.step = 0
@@ -286,6 +289,112 @@ class Patterns(PatternBase): # Inherit from PatternBase
self.run = False
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__":
import time

View File

@@ -13,8 +13,8 @@ class Settings(dict):
else: self.color_order = (1, 3, 5)
def set_defaults(self):
self["led_pin"] = 4
self["num_leds"] = 59
self["led_pin"] = 10
self["num_leds"] = 119
self["color_order"] = "rgb"
self["name"] = f"103"