Skip to content

Commit 78984f6

Browse files
committed
Create code.py
1 parent 3184a56 commit 78984f6

File tree

1 file changed

+257
-0
lines changed

1 file changed

+257
-0
lines changed

Ripple_Slippers/code.py

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
import time
2+
import board
3+
import digitalio
4+
import neopixel
5+
6+
# =========================================================
7+
# Ripple Footstep Lights with Color Cycling
8+
# Pylint-friendly version
9+
#
10+
# What this project does:
11+
# - Reads an FSR on pin A0
12+
# - Triggers a ripple of light from the center of the strip
13+
# - Changes color on each new press
14+
# - If pressed again while running:
15+
# - the timer extends
16+
# - the color changes immediately
17+
# - the ripple restarts from the center
18+
# =========================================================
19+
20+
21+
# -----------------------------
22+
# USER SETTINGS
23+
# -----------------------------
24+
NUM_PIXELS = 20
25+
ACTIVE_SECONDS = 3.0
26+
PIXEL_BRIGHTNESS = 0.3
27+
28+
# Lower = faster ripple, higher = slower ripple
29+
RIPPLE_DELAY = 0.05
30+
31+
# Lower = shorter trail, higher = longer trail
32+
TRAIL_FADE = 0.6
33+
34+
# End fade settings
35+
FADE_DELAY = 0.03
36+
FADE_STEPS = 20
37+
38+
# Color cycle: white -> pink -> purple -> blue
39+
COLOR_SEQUENCE = [
40+
(255, 255, 255), # white
41+
(255, 100, 180), # pink
42+
(180, 0, 255), # purple
43+
(0, 120, 255), # blue
44+
]
45+
46+
print("boot")
47+
48+
49+
# -----------------------------
50+
# SENSOR SETUP
51+
# -----------------------------
52+
# FSR wired between A0 and GND.
53+
# With internal pull-up enabled:
54+
# - unpressed = True
55+
# - pressed = False
56+
motion = digitalio.DigitalInOut(board.A0)
57+
motion.direction = digitalio.Direction.INPUT
58+
motion.pull = digitalio.Pull.UP
59+
print("motion ready")
60+
61+
62+
# -----------------------------
63+
# EXTERNAL POWER SETUP
64+
# -----------------------------
65+
# The Prop-Maker Feather needs EXTERNAL_POWER enabled
66+
# to power the external NeoPixel terminal.
67+
external_power = digitalio.DigitalInOut(board.EXTERNAL_POWER)
68+
external_power.direction = digitalio.Direction.OUTPUT
69+
external_power.value = True
70+
print("external power enabled")
71+
72+
73+
# -----------------------------
74+
# NEOPIXEL SETUP
75+
# -----------------------------
76+
pixels = neopixel.NeoPixel(
77+
board.EXTERNAL_NEOPIXELS,
78+
NUM_PIXELS,
79+
brightness=PIXEL_BRIGHTNESS,
80+
auto_write=False
81+
)
82+
print("pixels ready")
83+
84+
85+
# -----------------------------
86+
# HELPER FUNCTIONS
87+
# -----------------------------
88+
def clear():
89+
"""Turn all pixels off."""
90+
pixels.fill((0, 0, 0))
91+
pixels.show()
92+
93+
94+
def dim_rgb(rgb_value, factor):
95+
"""Return a dimmed version of an RGB color tuple."""
96+
return (
97+
int(rgb_value[0] * factor),
98+
int(rgb_value[1] * factor),
99+
int(rgb_value[2] * factor),
100+
)
101+
102+
103+
def get_next_color(sequence_index):
104+
"""
105+
Advance to the next color in the sequence.
106+
107+
Returns:
108+
tuple: (new_index, new_rgb)
109+
"""
110+
new_index = (sequence_index + 1) % len(COLOR_SEQUENCE)
111+
return new_index, COLOR_SEQUENCE[new_index]
112+
113+
114+
def ripple_frame(center_pixel, radius, ripple_rgb):
115+
"""
116+
Draw one frame of the ripple animation.
117+
118+
center_pixel: where the ripple starts
119+
radius: how far the wave has expanded
120+
ripple_rgb: current ripple color
121+
"""
122+
for pixel_index in range(NUM_PIXELS):
123+
distance = abs(pixel_index - center_pixel)
124+
125+
# Bright wave front
126+
if distance == radius:
127+
pixels[pixel_index] = ripple_rgb
128+
129+
# Optional thicker wave front:
130+
# Uncomment these two lines and comment out the line above
131+
# if abs(distance - radius) <= 1:
132+
# pixels[pixel_index] = ripple_rgb
133+
134+
# Fade the trail behind the wave
135+
elif distance < radius:
136+
red, green, blue = pixels[pixel_index]
137+
pixels[pixel_index] = (
138+
int(red * TRAIL_FADE),
139+
int(green * TRAIL_FADE),
140+
int(blue * TRAIL_FADE),
141+
)
142+
143+
# Pixels ahead of the wave stay off
144+
else:
145+
pixels[pixel_index] = (0, 0, 0)
146+
147+
pixels.show()
148+
149+
150+
def ripple_for(seconds, start_rgb, starting_index):
151+
"""
152+
Run the ripple animation for a set amount of time.
153+
154+
If the sensor is pressed again while the animation is running:
155+
- extend the timer
156+
- change to the next color
157+
- restart the ripple from the center
158+
159+
Returns:
160+
int: updated color sequence index
161+
"""
162+
center_pixel = NUM_PIXELS // 2
163+
active_rgb = start_rgb
164+
sequence_index = starting_index
165+
end_time = time.monotonic() + seconds
166+
was_pressed = False
167+
radius = 0
168+
169+
while time.monotonic() < end_time:
170+
is_pressed = not motion.value
171+
172+
# Detect a new press during the active animation
173+
if is_pressed and not was_pressed:
174+
sequence_index, active_rgb = get_next_color(sequence_index)
175+
print("extended, new color:", active_rgb)
176+
177+
end_time = time.monotonic() + seconds
178+
radius = 0
179+
180+
ripple_frame(center_pixel, radius, active_rgb)
181+
182+
radius += 1
183+
if radius > NUM_PIXELS:
184+
radius = 0
185+
186+
time.sleep(RIPPLE_DELAY)
187+
was_pressed = is_pressed
188+
189+
return sequence_index
190+
191+
192+
def fade_out():
193+
"""Fade the current pixels smoothly to black."""
194+
current_pixels = [pixels[pixel_index] for pixel_index in range(NUM_PIXELS)]
195+
196+
for step in range(FADE_STEPS, -1, -1):
197+
factor = step / FADE_STEPS
198+
199+
for pixel_index in range(NUM_PIXELS):
200+
pixels[pixel_index] = dim_rgb(current_pixels[pixel_index], factor)
201+
202+
pixels.show()
203+
time.sleep(FADE_DELAY)
204+
205+
clear()
206+
207+
208+
# -----------------------------
209+
# STARTUP FLASH
210+
# -----------------------------
211+
startup_colors = [
212+
(255, 0, 0),
213+
(0, 255, 0),
214+
(0, 0, 255),
215+
]
216+
217+
for startup_rgb in startup_colors:
218+
pixels.fill(startup_rgb)
219+
pixels.show()
220+
time.sleep(0.2)
221+
222+
clear()
223+
print("starting loop")
224+
225+
226+
# -----------------------------
227+
# MAIN LOOP
228+
# -----------------------------
229+
last_state = motion.value
230+
current_sequence_index = -1
231+
232+
while True:
233+
current_state = motion.value
234+
235+
if current_state != last_state:
236+
print("changed:", current_state)
237+
238+
# Trigger on press: True -> False
239+
if not current_state:
240+
print("TRIGGERED")
241+
242+
current_sequence_index, trigger_rgb = get_next_color(
243+
current_sequence_index
244+
)
245+
print("color:", trigger_rgb)
246+
247+
current_sequence_index = ripple_for(
248+
ACTIVE_SECONDS,
249+
trigger_rgb,
250+
current_sequence_index
251+
)
252+
253+
fade_out()
254+
255+
last_state = current_state
256+
257+
time.sleep(0.01)

0 commit comments

Comments
 (0)