Skip to content

Commit 2e80084

Browse files
committed
custom LED animations code
1 parent c408781 commit 2e80084

File tree

10 files changed

+527
-0
lines changed

10 files changed

+527
-0
lines changed

Custom_LED_Animations/conways/code.py

Whitespace-only changes.

Custom_LED_Animations/conways/conways.py

Whitespace-only changes.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import board
2+
import neopixel
3+
4+
from rainbowsweep import RainbowSweepAnimation
5+
6+
# Update to match the pin connected to your NeoPixels
7+
pixel_pin = board.D10
8+
# Update to match the number of NeoPixels you have connected
9+
pixel_num = 32
10+
11+
# initialize the neopixels. Change out for dotstars if needed.
12+
pixels = neopixel.NeoPixel(pixel_pin, pixel_num, brightness=0.02, auto_write=False)
13+
14+
# initialize the animation
15+
rainbowsweep = RainbowSweepAnimation(pixels, speed=0.05, color=0x000000, sweep_speed=0.1,
16+
sweep_direction=RainbowSweepAnimation.DIRECTION_END_TO_START)
17+
18+
while True:
19+
# call animation to show the next animation frame
20+
rainbowsweep.animate()
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
"""
6+
Adapted From `adafruit_led_animation.animation.rainbow`
7+
"""
8+
9+
from adafruit_led_animation.animation import Animation
10+
from adafruit_led_animation.color import BLACK, colorwheel
11+
from adafruit_led_animation import MS_PER_SECOND, monotonic_ms
12+
13+
14+
class RainbowSweepAnimation(Animation):
15+
"""
16+
The classic rainbow color wheel that gets swept across by another specified color.
17+
18+
:param pixel_object: The initialised LED object.
19+
:param float speed: Animation refresh rate in seconds, e.g. ``0.1``.
20+
:param float sweep_speed: How long in seconds to wait between sweep steps
21+
:param float period: Period to cycle the rainbow over in seconds. Default 1.
22+
:param sweep_direction: which way to sweep across the rainbow. Must be one of
23+
DIRECTION_START_TO_END or DIRECTION_END_TO_START
24+
:param str name: Name of animation (optional, useful for sequences and debugging).
25+
26+
"""
27+
28+
# constants to represent the different directions
29+
DIRECTION_START_TO_END = 0
30+
DIRECTION_END_TO_START = 1
31+
# pylint: disable=too-many-arguments
32+
def __init__(
33+
self, pixel_object, speed, color, sweep_speed=0.3, period=1, name=None, sweep_direction=DIRECTION_START_TO_END
34+
):
35+
super().__init__(pixel_object, speed, color, name=name)
36+
self._period = period
37+
# internal var step used inside of color generator
38+
self._step = 256 // len(pixel_object)
39+
40+
# internal var wheel_index used inside of color generator
41+
self._wheel_index = 0
42+
43+
# instance of the generator
44+
self._generator = self._color_wheel_generator()
45+
46+
# convert swap speed from seconds to ms and store it
47+
self._sweep_speed = sweep_speed * 1000
48+
49+
# set the initial sweep index
50+
self.sweep_index = len(pixel_object)
51+
52+
# internal variable to store the timestamp of when a sweep step occurs
53+
self._last_sweep_time = 0
54+
55+
# store the direction argument
56+
self.direction = sweep_direction
57+
58+
# this animation supports on cycle complete callbacks
59+
on_cycle_complete_supported = True
60+
61+
def _color_wheel_generator(self):
62+
# convert period to ms
63+
period = int(self._period * MS_PER_SECOND)
64+
65+
# how many pixels in the strand
66+
num_pixels = len(self.pixel_object)
67+
68+
# current timestamp
69+
last_update = monotonic_ms()
70+
71+
cycle_position = 0
72+
last_pos = 0
73+
while True:
74+
cycle_completed = False
75+
# time vars
76+
now = monotonic_ms()
77+
time_since_last_draw = now - last_update
78+
last_update = now
79+
80+
# cycle position vars
81+
pos = cycle_position = (cycle_position + time_since_last_draw) % period
82+
83+
# if it's time to signal cycle complete
84+
if pos < last_pos:
85+
cycle_completed = True
86+
87+
# update position var for next iteration
88+
last_pos = pos
89+
90+
# calculate wheel_index
91+
wheel_index = int((pos / period) * 256)
92+
93+
# set all pixels to their color based on the wheel color and step
94+
self.pixel_object[:] = [
95+
colorwheel(((i * self._step) + wheel_index) % 255) for i in range(num_pixels)
96+
]
97+
98+
# if it's time for a sweep step
99+
if self._last_sweep_time + self._sweep_speed <= now:
100+
101+
# udpate sweep timestamp
102+
self._last_sweep_time = now
103+
104+
# decrement the sweep index
105+
self.sweep_index -= 1
106+
107+
# if it's finished the last step
108+
if self.sweep_index == -1:
109+
# reset it to the number of pixels in the strand
110+
self.sweep_index = len(self.pixel_object)
111+
112+
# if end to start direction
113+
if self.direction == self.DIRECTION_END_TO_START:
114+
# set the current pixels at the end of the strand to the specified color
115+
self.pixel_object[self.sweep_index:] = [self.color] * (len(self.pixel_object) - self.sweep_index)
116+
117+
# if start to end direction
118+
elif self.direction == self.DIRECTION_START_TO_END:
119+
# set the pixels at the begining of the strand to the specified color
120+
inverse_index = len(self.pixel_object) - self.sweep_index
121+
self.pixel_object[:inverse_index] = [self.color] * (inverse_index)
122+
123+
# update the wheel index
124+
self._wheel_index = wheel_index
125+
126+
# signal cycle complete if it's time
127+
if cycle_completed:
128+
self.cycle_complete = True
129+
yield
130+
131+
132+
def draw(self):
133+
"""
134+
draw the current frame of the animation
135+
:return:
136+
"""
137+
next(self._generator)
138+
139+
def reset(self):
140+
"""
141+
Resets the animation.
142+
"""
143+
self._generator = self._color_wheel_generator()

Custom_LED_Animations/snake/code.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import board
2+
import neopixel
3+
4+
from snake import SnakeAnimation
5+
6+
# Update to match the pin connected to your NeoPixels
7+
pixel_pin = board.D10
8+
# Update to match the number of NeoPixels you have connected
9+
pixel_num = 32
10+
11+
# initialize the neopixels. Change out for dotstars if needed.
12+
pixels = neopixel.NeoPixel(pixel_pin, pixel_num, brightness=0.02, auto_write=False)
13+
14+
# initialize the animation
15+
snake = SnakeAnimation(pixels, speed=0.1, color=0xff00ff, width=8, height=4)
16+
17+
while True:
18+
# call animation to show the next animation frame
19+
snake.animate()

Custom_LED_Animations/snake/snake.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# SPDX-FileCopyrightText: 2024 Tim Cocks
2+
#
3+
# SPDX-License-Identifier: MIT
4+
"""
5+
SnakeAnimation helper class
6+
"""
7+
from micropython import const
8+
9+
from adafruit_led_animation.animation import Animation
10+
from adafruit_led_animation.grid import PixelGrid, HORIZONTAL
11+
import random
12+
13+
14+
class SnakeAnimation(Animation):
15+
UP = const(0x00)
16+
DOWN = const(0x01)
17+
LEFT = const(0x02)
18+
RIGHT = const(0x03)
19+
ALL_DIRECTIONS = [UP, DOWN, LEFT, RIGHT]
20+
DIRECTION_OFFSETS = {
21+
DOWN: (0, 1),
22+
UP: (0, -1),
23+
RIGHT: (1, 0),
24+
LEFT: (-1, 0)
25+
}
26+
27+
def __init__(self, pixel_object, speed, color, width, height, snake_length=3):
28+
"""
29+
Renders a snake that slithers around the 2D grid of pixels
30+
"""
31+
super().__init__(pixel_object, speed, color)
32+
33+
# how many segments the snake will have
34+
self.snake_length = snake_length
35+
36+
# create a PixelGrid helper to access our strand as a 2D grid
37+
self.pixel_grid = PixelGrid(pixel_object, width, height, orientation=HORIZONTAL, alternating=False)
38+
39+
# size variables
40+
self.width = width
41+
self.height = height
42+
43+
# list that will hold locations of snake segments
44+
self.snake_pixels = []
45+
46+
# initialize the snake
47+
self._new_snake()
48+
49+
def _clear_snake(self):
50+
"""
51+
Clear the snake segments and turn off all pixels
52+
"""
53+
while len(self.snake_pixels) > 0:
54+
self.pixel_grid[self.snake_pixels.pop()] = 0x000000
55+
56+
def _new_snake(self):
57+
"""
58+
Create a new single segment snake. The snake has a random
59+
direction and location. Turn on the pixel representing the snake.
60+
"""
61+
# choose a random direction and store it
62+
self.direction = random.choice(SnakeAnimation.ALL_DIRECTIONS)
63+
64+
# choose a random starting tile
65+
starting_tile = (random.randint(0, self.width - 1), random.randint(0, self.height - 1))
66+
67+
# add the starting tile to the list of segments
68+
self.snake_pixels.append(starting_tile)
69+
70+
# turn on the pixel at the chosen location
71+
self.pixel_grid[self.snake_pixels[0]] = self.color
72+
73+
def _can_move(self, direction):
74+
"""
75+
returns true if the snake can move in the given direction
76+
"""
77+
# location of the next tile if we would move that direction
78+
next_tile = tuple(map(sum, zip(SnakeAnimation.DIRECTION_OFFSETS[direction], self.snake_pixels[0])))
79+
80+
# if the tile is one of the snake segments
81+
if next_tile in self.snake_pixels:
82+
# can't move there
83+
return False
84+
85+
# if the tile is within the bounds of the grid
86+
if 0 <= next_tile[0] < self.width and 0 <= next_tile[1] < self.height:
87+
# can move there
88+
return True
89+
90+
# return false if any other conditions not met
91+
return False
92+
93+
94+
def _choose_direction(self):
95+
"""
96+
Choose a direction to go in. Could continue in same direction
97+
as it's already going, or decide to turn to a dirction that
98+
will allow movement.
99+
"""
100+
101+
# copy of all directions in a list
102+
directions_to_check = list(SnakeAnimation.ALL_DIRECTIONS)
103+
104+
# if we can move the direction we're currently going
105+
if self._can_move(self.direction):
106+
# "flip a coin"
107+
if random.random() < 0.5:
108+
# on "heads" we stay going the same direction
109+
return self.direction
110+
111+
# loop over the copied list of directions to check
112+
while len(directions_to_check) > 0:
113+
# choose a random one from the list and pop it out of the list
114+
possible_direction = directions_to_check.pop(random.randint(0, len(directions_to_check)-1))
115+
# if we can move the chosen direction
116+
if self._can_move(possible_direction):
117+
# return the chosen direction
118+
return possible_direction
119+
120+
# if we made it through all directions and couldn't move in any of them
121+
# then raise the SnakeStuckException
122+
raise SnakeAnimation.SnakeStuckException
123+
124+
125+
def draw(self):
126+
"""
127+
Draw the current frame of the animation
128+
"""
129+
# if the snake is currently the desired length
130+
if len(self.snake_pixels) == self.snake_length:
131+
# remove the last segment from the list and turn it's LED off
132+
self.pixel_grid[self.snake_pixels.pop()] = 0x000000
133+
134+
# if the snake is less than the desired length
135+
# e.g. because we removed one in the previous step
136+
if len(self.snake_pixels) < self.snake_length:
137+
# wrap with try to catch the SnakeStuckException
138+
try:
139+
# update the direction, could continue straight, or could change
140+
self.direction = self._choose_direction()
141+
142+
# the location of the next tile where the head of the snake will move to
143+
next_tile = tuple(map(sum, zip(SnakeAnimation.DIRECTION_OFFSETS[self.direction], self.snake_pixels[0])))
144+
145+
# insert the next tile at list index 0
146+
self.snake_pixels.insert(0, next_tile)
147+
148+
# turn on the LED for the tile
149+
self.pixel_grid[next_tile] = self.color
150+
151+
# if the snake exception is caught
152+
except SnakeAnimation.SnakeStuckException:
153+
# clear the snake to get rid of the old one
154+
self._clear_snake()
155+
156+
# make a new snake
157+
self._new_snake()
158+
159+
class SnakeStuckException(RuntimeError):
160+
"""
161+
Exception indicating the snake is stuck and can't move in any direction
162+
"""
163+
def __init__(self):
164+
super().__init__("SnakeStuckException")

Custom_LED_Animations/sweep/code.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import board
2+
import neopixel
3+
4+
from sweep import SweepAnimation
5+
6+
from adafruit_led_animation.color import PINK
7+
8+
# Update to match the pin connected to your NeoPixels
9+
pixel_pin = board.D10
10+
# Update to match the number of NeoPixels you have connected
11+
pixel_num = 32
12+
13+
# initialize the neopixels. Change out for dotstars if needed.
14+
pixels = neopixel.NeoPixel(pixel_pin, pixel_num, brightness=0.02, auto_write=False)
15+
16+
# initialize the animation
17+
sweep = SweepAnimation(pixels, speed=0.05, color=PINK)
18+
19+
while True:
20+
# call animation to show the next animation frame
21+
sweep.animate()

0 commit comments

Comments
 (0)