From a0b85e57a145b613b944ec26c2243bbcc857107e Mon Sep 17 00:00:00 2001 From: jimmy Date: Fri, 24 Oct 2025 20:32:31 +1300 Subject: [PATCH] Add simplified radiate pattern to dev branch - Add radiate pattern to patterns dictionary - Implement clean radiate function with single-shot and auto modes - Add WDT import and radiate-specific attributes - Single wave at a time in auto mode (no overlapping complexity) - Simple debug output for restart timing - Maintains same functionality with cleaner implementation --- src/patterns.py | 167 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 166 insertions(+), 1 deletion(-) diff --git a/src/patterns.py b/src/patterns.py index 09acb24..ea63d88 100644 --- a/src/patterns.py +++ b/src/patterns.py @@ -1,4 +1,4 @@ -from machine import Pin +from machine import Pin, WDT from neopixel import NeoPixel import utime import random @@ -19,6 +19,7 @@ class Patterns: "blink": self.blink_step, "color_transition": self.color_transition_step, # Added new pattern "flicker": self.flicker_step, + "radiate": self.radiate, } self.selected = selected # Ensure colors list always starts with at least two for robust transition handling @@ -33,6 +34,17 @@ class Patterns: self.current_color = self.colors[self.current_color_idx] # The actual blended color self.hold_start_time = utime.ticks_ms() # Time when the current color hold started + + # Radiate pattern attributes + self.n1 = 10 # Distance between nodes + self.n2 = 0 # Unused + self.n3 = 500 # Time to radiate out in ms + self.n4 = 500 # Time to radiate in in ms + self.run = True + self.stopped = False + self.debug = False + self._last_debug_print_ms = 0 + self._wdt = WDT() def sync(self): @@ -312,3 +324,156 @@ class Patterns: flicker_color = self.apply_brightness(base_color, brightness_override=flicker_brightness) self.fill(flicker_color) self.last_update = current_time + + def radiate(self): + # Radiate pattern: creates radiating effects from multiple nodes + # n1 = distance between nodes, n2 = time for pattern to complete in ms + # Pattern repeats every self.delay milliseconds + self.run = True + last = utime.ticks_ms() + accum = 0 + pattern_start = utime.ticks_ms() + step = 0 + + if self.delay == 0: + # Single-shot mode: run one complete cycle + delay_ms = 20 # Use 20ms for smooth animation + while self.run: + now = utime.ticks_ms() + dt = utime.ticks_diff(now, last) + if dt < 0: + dt = 0 + last = now + accum += dt + + if accum < delay_ms: + continue + accum = 0 + step += 1 + + # Clear all LEDs + self.n.fill((0, 0, 0)) + + # Calculate pattern parameters + n1 = self.n1 if self.n1 > 0 else 10 # Distance between nodes + n3 = self.n3 if self.n3 > 0 else 500 # Time to radiate out in ms + n4 = self.n4 if self.n4 > 0 else 500 # Time to radiate in in ms + + # Calculate current time within the radiate cycle (out + in) + elapsed = utime.ticks_diff(now, pattern_start) + total_cycle_time = n3 + n4 if (n3 + n4) > 0 else 1 + cycle_time = elapsed % total_cycle_time + + # Calculate nodes positions + nodes = [] + for i in range(0, self.num_leds, n1): + nodes.append(i) + + # For each node, create radiating effect + for node_pos in nodes: + max_radius = min(node_pos, self.num_leds - node_pos - 1) + if max_radius <= 0: + continue + if cycle_time <= n3 and n3 > 0: + out_progress = cycle_time / n3 + wave_radius = int(out_progress * max_radius) + else: + in_time = cycle_time - n3 + in_progress = (in_time / n4) if n4 > 0 else 1.0 + wave_radius = int((1.0 - in_progress) * max_radius) + for radius in range(wave_radius + 1): + intensity = 1.0 - (radius / max_radius) if max_radius > 0 else 1.0 + if intensity <= 0: + continue + scaled_intensity = int(self.brightness * intensity) + r = (self.colors[0][0] * scaled_intensity) // 255 + g = (self.colors[0][1] * scaled_intensity) // 255 + b = (self.colors[0][2] * scaled_intensity) // 255 + left_pos = node_pos - radius + if left_pos >= 0: + self.n[left_pos] = (r, g, b) + right_pos = node_pos + radius + if right_pos < self.num_leds: + self.n[right_pos] = (r, g, b) + + self.n.write() + self._wdt.feed() + + # Single-shot: stop after one full out+in cycle + if elapsed >= total_cycle_time: + self.run = False + self.n.fill((0, 0, 0)) + self.n.write() + self.stopped = True + return + else: + # Auto mode: restart wave every delay_ms + delay_ms = self.delay + while self.run: + now = utime.ticks_ms() + dt = utime.ticks_diff(now, last) + if dt < 0: + dt = 0 + last = now + accum += dt + + if accum < delay_ms: + continue + accum = 0 + step += 1 + + # Clear all LEDs + self.n.fill((0, 0, 0)) + + # Calculate pattern parameters + n1 = self.n1 if self.n1 > 0 else 10 # Distance between nodes + n3 = self.n3 if self.n3 > 0 else 500 # Time to radiate out in ms + n4 = self.n4 if self.n4 > 0 else 500 # Time to radiate in in ms + + # Calculate current time within the radiate cycle (out + in) + elapsed = utime.ticks_diff(now, pattern_start) + total_cycle_time = n3 + n4 if (n3 + n4) > 0 else 1 + cycle_time = elapsed % total_cycle_time + + # Calculate nodes positions + nodes = [] + for i in range(0, self.num_leds, n1): + nodes.append(i) + + # For each node, create radiating effect + for node_pos in nodes: + max_radius = min(node_pos, self.num_leds - node_pos - 1) + if max_radius <= 0: + continue + if cycle_time <= n3 and n3 > 0: + out_progress = cycle_time / n3 + wave_radius = int(out_progress * max_radius) + else: + in_time = cycle_time - n3 + in_progress = (in_time / n4) if n4 > 0 else 1.0 + wave_radius = int((1.0 - in_progress) * max_radius) + for radius in range(wave_radius + 1): + intensity = 1.0 - (radius / max_radius) if max_radius > 0 else 1.0 + if intensity <= 0: + continue + scaled_intensity = int(self.brightness * intensity) + r = (self.colors[0][0] * scaled_intensity) // 255 + g = (self.colors[0][1] * scaled_intensity) // 255 + b = (self.colors[0][2] * scaled_intensity) // 255 + left_pos = node_pos - radius + if left_pos >= 0: + self.n[left_pos] = (r, g, b) + right_pos = node_pos + radius + if right_pos < self.num_leds: + self.n[right_pos] = (r, g, b) + + self.n.write() + self._wdt.feed() + + # Auto mode: restart the wave every delay_ms + if elapsed >= delay_ms: + if self.debug: + print("[radiate] restart cycle: delay=", delay_ms, "elapsed=", elapsed, "step=", step) + pattern_start = now + + self.stopped = True