"""Knight Rider–style bouncing scanner — self-contained (stdlib + simulated hardware only).""" import time from machine import Pin import neopixel # --- helpers def _clamp(channel: int) -> int: return max(0, min(255, int(channel))) def _bounce_head_index(led_count: int, frame: int) -> int: if led_count <= 1: return 0 span = led_count - 1 cycle = span * 2 if cycle <= 0: return 0 t = frame % cycle return t if t <= span else 2 * span - t def _bounce_phase_tail_direction(led_count: int, frame: int) -> int: if led_count <= 1: return -1 span = led_count - 1 cycle = span * 2 if cycle <= 0: return -1 t = frame % cycle if t <= span: return -1 return 1 def knight_rider_scanner_frame( led_count: int, frame: int, head_color=(220, 0, 28), tail_len: int = 8, falloff_gamma: float = 2.6, ): if led_count <= 0: return [] out = [(0, 0, 0) for _ in range(led_count)] tl = max(1, tail_len) head = _bounce_head_index(led_count, frame) direc = _bounce_phase_tail_direction(led_count, frame) gamma = max(1.05, falloff_gamma) for rk in reversed(range(tl)): idx = head + direc * rk if idx < 0 or idx >= led_count: continue w = max(0.0, float(tl - rk) / float(tl)) strength = w**gamma out[idx] = tuple(_clamp(int(head_color[ch] * strength)) for ch in range(3)) return out # --- demo NUM_LEDS = 16 np = neopixel.NeoPixel(Pin(4), NUM_LEDS) for frame in range(200): frame_colors = knight_rider_scanner_frame( len(np), frame, head_color=(220, 0, 36), tail_len=10, falloff_gamma=2.85, ) for i, color in enumerate(frame_colors): np[i] = color np.write() time.sleep(0.05) np.fill((0, 0, 0)) np.write()