Admin user editing, knight-rider demos, self-contained user seeds
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -48,6 +48,82 @@ def chase_frame(
|
||||
return out
|
||||
|
||||
|
||||
def _bounce_head_index(led_count: int, frame: int) -> int:
|
||||
"""Map frame to a triangular index sweep 0..N-1..0 (Ping-Pong position)."""
|
||||
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:
|
||||
"""Extend tail opposite motion: -1 fades toward lower indices, +1 toward higher."""
|
||||
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: Color = (220, 0, 28),
|
||||
tail_len: int = 8,
|
||||
falloff_gamma: float = 2.6,
|
||||
) -> list[Color]:
|
||||
"""KITT-style bouncing scanner: saturated head with exponential tail fading to off."""
|
||||
if led_count <= 0:
|
||||
return []
|
||||
out: list[Color] = [(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
|
||||
|
||||
|
||||
def scanner_bounce_frame(
|
||||
led_count: int,
|
||||
frame: int,
|
||||
head_color: Color = (0, 220, 255),
|
||||
tail_color: Color = (0, 40, 90),
|
||||
tail_len: int = 5,
|
||||
) -> list[Color]:
|
||||
"""Ping-pong scanner: head reverses at both ends with a directional fading tail."""
|
||||
if led_count <= 0:
|
||||
return []
|
||||
out: list[Color] = [(0, 0, 0) for _ in range(led_count)]
|
||||
tl = max(1, tail_len)
|
||||
for rk in reversed(range(tl)):
|
||||
past = frame - rk
|
||||
if past < 0:
|
||||
continue
|
||||
idx = _bounce_head_index(led_count, past)
|
||||
strength = max(0.0, float(tl - rk) / float(tl))
|
||||
if rk == 0:
|
||||
out[idx] = tuple(_clamp(int(c)) for c in head_color)
|
||||
else:
|
||||
out[idx] = tuple(_clamp(int(tail_color[i] * strength)) for i in range(3))
|
||||
return out
|
||||
|
||||
|
||||
def twinkle_frame(
|
||||
led_count: int,
|
||||
frame: int,
|
||||
|
||||
@@ -1,16 +1,79 @@
|
||||
"""Chase pattern demo using led_patterns helpers."""
|
||||
"""Knight Rider–style bouncing scanner — self-contained (stdlib + simulated hardware only)."""
|
||||
|
||||
import time
|
||||
|
||||
from machine import Pin
|
||||
import neopixel
|
||||
import time
|
||||
|
||||
from led_patterns import chase_frame
|
||||
# --- helpers
|
||||
|
||||
|
||||
np = neopixel.NeoPixel(Pin(4), 24)
|
||||
def _clamp(channel: int) -> int:
|
||||
return max(0, min(255, int(channel)))
|
||||
|
||||
for frame in range(120):
|
||||
frame_colors = chase_frame(len(np), frame, color=(0, 220, 255), tail=(0, 40, 55))
|
||||
|
||||
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()
|
||||
|
||||
@@ -1,13 +1,40 @@
|
||||
"""Rainbow pattern demo using led_patterns helpers."""
|
||||
"""Rainbow NeoPixel sweep — self-contained (stdlib + simulated hardware only)."""
|
||||
|
||||
import time
|
||||
|
||||
from machine import Pin
|
||||
import neopixel
|
||||
import time
|
||||
|
||||
from led_patterns import rainbow_frame
|
||||
# --- helpers (same logic as bundled led_patterns.py, inlined here)
|
||||
|
||||
|
||||
np = neopixel.NeoPixel(Pin(4), 256)
|
||||
def _clamp(channel: int) -> int:
|
||||
return max(0, min(255, int(channel)))
|
||||
|
||||
|
||||
def wheel(pos: int):
|
||||
"""Return rainbow RGB at position 0–255."""
|
||||
pos = 255 - (pos & 255)
|
||||
if pos < 85:
|
||||
return (_clamp(255 - pos * 3), 0, _clamp(pos * 3))
|
||||
if pos < 170:
|
||||
pos -= 85
|
||||
return (0, _clamp(pos * 3), _clamp(255 - pos * 3))
|
||||
pos -= 170
|
||||
return (_clamp(pos * 3), _clamp(255 - pos * 3), 0)
|
||||
|
||||
|
||||
def rainbow_frame(led_count: int, frame: int, step: int = 4):
|
||||
if led_count <= 0:
|
||||
return []
|
||||
return [wheel((i * 256 // led_count + frame * step) & 255) for i in range(led_count)]
|
||||
|
||||
|
||||
# --- demo
|
||||
|
||||
NUM_LEDS = 16
|
||||
|
||||
np = neopixel.NeoPixel(Pin(4), NUM_LEDS)
|
||||
|
||||
for frame in range(120):
|
||||
frame_colors = rainbow_frame(len(np), frame, step=5)
|
||||
|
||||
@@ -1,13 +1,41 @@
|
||||
"""Twinkle pattern demo using led_patterns helpers."""
|
||||
"""Twinkle NeoPixel demo — self-contained (stdlib + simulated hardware only)."""
|
||||
|
||||
import random
|
||||
import time
|
||||
|
||||
from machine import Pin
|
||||
import neopixel
|
||||
import time
|
||||
|
||||
from led_patterns import twinkle_frame
|
||||
# --- helpers
|
||||
|
||||
|
||||
np = neopixel.NeoPixel(Pin(4), 36)
|
||||
def _clamp(channel: int) -> int:
|
||||
return max(0, min(255, int(channel)))
|
||||
|
||||
|
||||
def twinkle_frame(
|
||||
led_count: int,
|
||||
frame: int,
|
||||
base=(0, 0, 8),
|
||||
sparkle=(255, 255, 180),
|
||||
sparkles: int = 3,
|
||||
seed: int = 1337,
|
||||
):
|
||||
if led_count <= 0:
|
||||
return []
|
||||
out = [tuple(_clamp(v) for v in base) for _ in range(led_count)]
|
||||
rng = random.Random(seed + frame)
|
||||
for _ in range(min(max(0, sparkles), led_count)):
|
||||
idx = rng.randrange(led_count)
|
||||
out[idx] = tuple(_clamp(v) for v in sparkle)
|
||||
return out
|
||||
|
||||
|
||||
# --- demo
|
||||
|
||||
NUM_LEDS = 16
|
||||
|
||||
np = neopixel.NeoPixel(Pin(4), NUM_LEDS)
|
||||
|
||||
for frame in range(120):
|
||||
frame_colors = twinkle_frame(
|
||||
@@ -15,7 +43,7 @@ for frame in range(120):
|
||||
frame,
|
||||
base=(0, 0, 6),
|
||||
sparkle=(255, 210, 130),
|
||||
sparkles=5,
|
||||
sparkles=3,
|
||||
)
|
||||
for i, color in enumerate(frame_colors):
|
||||
np[i] = color
|
||||
|
||||
Reference in New Issue
Block a user