84 lines
1.8 KiB
Python
84 lines
1.8 KiB
Python
"""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()
|