Merge pull request #2922 from FoamyGuy/custom_animations
Custom LED Animations Updates
This commit is contained in:
commit
81ba1ce023
7 changed files with 736 additions and 0 deletions
43
Custom_LED_Animations/grid_both/code.py
Normal file
43
Custom_LED_Animations/grid_both/code.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""
|
||||
Uses NeoPixel Featherwing connected to D10 and
|
||||
Dotstar Featherwing connected to D13, and D11.
|
||||
Update pins as needed for your connections.
|
||||
"""
|
||||
import board
|
||||
import neopixel
|
||||
import adafruit_dotstar as dotstar
|
||||
from conways import ConwaysLifeAnimation
|
||||
from snake import SnakeAnimation
|
||||
|
||||
# Update to match the pin connected to your NeoPixels
|
||||
pixel_pin = board.D10
|
||||
# Update to match the number of NeoPixels you have connected
|
||||
pixel_num = 32
|
||||
|
||||
# initialize the neopixels featherwing
|
||||
pixels = neopixel.NeoPixel(pixel_pin, pixel_num, brightness=0.02, auto_write=False)
|
||||
|
||||
# initialize the dotstar featherwing
|
||||
dots = dotstar.DotStar(board.D13, board.D11, 72, brightness=0.02)
|
||||
|
||||
# initial live cells for conways
|
||||
initial_cells = [
|
||||
(2, 1),
|
||||
(3, 1),
|
||||
(4, 1),
|
||||
(5, 1),
|
||||
(6, 1),
|
||||
]
|
||||
|
||||
# initialize the animations
|
||||
conways = ConwaysLifeAnimation(dots, 0.1, 0xff00ff, 12, 6, initial_cells)
|
||||
|
||||
snake = SnakeAnimation(pixels, speed=0.1, color=0xff00ff, width=8, height=4)
|
||||
|
||||
while True:
|
||||
# call animate to show the next animation frames
|
||||
conways.animate()
|
||||
snake.animate()
|
||||
192
Custom_LED_Animations/grid_both/conways.py
Normal file
192
Custom_LED_Animations/grid_both/conways.py
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
# SPDX-FileCopyrightText: 2024 Tim Cocks
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""
|
||||
ConwaysLifeAnimation helper class
|
||||
"""
|
||||
from micropython import const
|
||||
|
||||
from adafruit_led_animation.animation import Animation
|
||||
from adafruit_led_animation.grid import PixelGrid, HORIZONTAL
|
||||
|
||||
|
||||
def _is_pixel_off(pixel):
|
||||
return pixel[0] == 0 and pixel[1] == 0 and pixel[2] == 0
|
||||
|
||||
|
||||
class ConwaysLifeAnimation(Animation):
|
||||
# Constants
|
||||
DIRECTION_OFFSETS = [
|
||||
(0, 1),
|
||||
(0, -1),
|
||||
(1, 0),
|
||||
(-1, 0),
|
||||
(1, 1),
|
||||
(-1, 1),
|
||||
(1, -1),
|
||||
(-1, -1),
|
||||
]
|
||||
LIVE = const(0x01)
|
||||
DEAD = const(0x00)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
pixel_object,
|
||||
speed,
|
||||
color,
|
||||
width,
|
||||
height,
|
||||
initial_cells,
|
||||
equilibrium_restart=True,
|
||||
):
|
||||
"""
|
||||
Conway's Game of Life implementation. Watch the cells
|
||||
live and die based on the classic rules.
|
||||
|
||||
:param pixel_object: The initialised LED object.
|
||||
:param float speed: Animation refresh rate in seconds, e.g. ``0.1``.
|
||||
:param color: the color to use for live cells
|
||||
:param width: the width of the grid
|
||||
:param height: the height of the grid
|
||||
:param initial_cells: list of initial cells to be live
|
||||
:param equilibrium_restart: whether to restart when the simulation gets stuck unchanging
|
||||
"""
|
||||
super().__init__(pixel_object, speed, color)
|
||||
|
||||
# list to hold which cells are live
|
||||
self.drawn_pixels = []
|
||||
|
||||
# store the initial cells
|
||||
self.initial_cells = initial_cells
|
||||
|
||||
# PixelGrid helper to access the strand as a 2D grid
|
||||
self.pixel_grid = PixelGrid(
|
||||
pixel_object, width, height, orientation=HORIZONTAL, alternating=False
|
||||
)
|
||||
|
||||
# size of the grid
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
# equilibrium restart boolean
|
||||
self.equilibrium_restart = equilibrium_restart
|
||||
|
||||
# counter to store how many turns since the last change
|
||||
self.equilibrium_turns = 0
|
||||
|
||||
# self._init_cells()
|
||||
|
||||
def _is_grid_empty(self):
|
||||
"""
|
||||
Checks if the grid is empty.
|
||||
|
||||
:return: True if there are no live cells, False otherwise
|
||||
"""
|
||||
for y in range(self.height):
|
||||
for x in range(self.width):
|
||||
if not _is_pixel_off(self.pixel_grid[x, y]):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _init_cells(self):
|
||||
"""
|
||||
Turn off all LEDs then turn on ones cooresponding to the initial_cells
|
||||
|
||||
:return: None
|
||||
"""
|
||||
self.pixel_grid.fill(0x000000)
|
||||
for cell in self.initial_cells:
|
||||
self.pixel_grid[cell] = self.color
|
||||
|
||||
def _count_neighbors(self, cell):
|
||||
"""
|
||||
Check how many live cell neighbors are found at the given location
|
||||
:param cell: the location to check
|
||||
:return: the number of live cell neighbors
|
||||
"""
|
||||
neighbors = 0
|
||||
for direction in ConwaysLifeAnimation.DIRECTION_OFFSETS:
|
||||
try:
|
||||
if not _is_pixel_off(
|
||||
self.pixel_grid[cell[0] + direction[0], cell[1] + direction[1]]
|
||||
):
|
||||
neighbors += 1
|
||||
except IndexError:
|
||||
pass
|
||||
return neighbors
|
||||
|
||||
def draw(self):
|
||||
# pylint: disable=too-many-branches
|
||||
"""
|
||||
draw the current frame of the animation
|
||||
|
||||
:return: None
|
||||
"""
|
||||
# if there are no live cells
|
||||
if self._is_grid_empty():
|
||||
# spawn the inital_cells and return
|
||||
self._init_cells()
|
||||
return
|
||||
|
||||
# list to hold locations to despawn live cells
|
||||
despawning_cells = []
|
||||
|
||||
# list to hold locations spawn new live cells
|
||||
spawning_cells = []
|
||||
|
||||
# loop over the grid
|
||||
for y in range(self.height):
|
||||
for x in range(self.width):
|
||||
|
||||
# check and set the current cell type, live or dead
|
||||
if _is_pixel_off(self.pixel_grid[x, y]):
|
||||
cur_cell_type = ConwaysLifeAnimation.DEAD
|
||||
else:
|
||||
cur_cell_type = ConwaysLifeAnimation.LIVE
|
||||
|
||||
# get a count of the neigbors
|
||||
neighbors = self._count_neighbors((x, y))
|
||||
|
||||
# if the current cell is alive
|
||||
if cur_cell_type == ConwaysLifeAnimation.LIVE:
|
||||
# if it has fewer than 2 neighbors
|
||||
if neighbors < 2:
|
||||
# add its location to the despawn list
|
||||
despawning_cells.append((x, y))
|
||||
|
||||
# if it has more than 3 neighbors
|
||||
if neighbors > 3:
|
||||
# add its location to the despawn list
|
||||
despawning_cells.append((x, y))
|
||||
|
||||
# if the current location is not a living cell
|
||||
elif cur_cell_type == ConwaysLifeAnimation.DEAD:
|
||||
# if it has exactly 3 neighbors
|
||||
if neighbors == 3:
|
||||
# add the current location to the spawn list
|
||||
spawning_cells.append((x, y))
|
||||
|
||||
# loop over the despawn locations
|
||||
for cell in despawning_cells:
|
||||
# turn off LEDs at each location
|
||||
self.pixel_grid[cell] = 0x000000
|
||||
|
||||
# loop over the spawn list
|
||||
for cell in spawning_cells:
|
||||
# turn on LEDs at each location
|
||||
self.pixel_grid[cell] = self.color
|
||||
|
||||
# if equilibrium restart mode is enabled
|
||||
if self.equilibrium_restart:
|
||||
# if there were no cells spawned or despaned this round
|
||||
if len(despawning_cells) == 0 and len(spawning_cells) == 0:
|
||||
# increment equilibrium turns counter
|
||||
self.equilibrium_turns += 1
|
||||
# if the counter is 3 or higher
|
||||
if self.equilibrium_turns >= 3:
|
||||
# go back to the initial_cells
|
||||
self._init_cells()
|
||||
|
||||
# reset the turns counter to zero
|
||||
self.equilibrium_turns = 0
|
||||
171
Custom_LED_Animations/grid_both/snake.py
Normal file
171
Custom_LED_Animations/grid_both/snake.py
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
# SPDX-FileCopyrightText: 2024 Tim Cocks
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""
|
||||
SnakeAnimation helper class
|
||||
"""
|
||||
import random
|
||||
from micropython import const
|
||||
|
||||
from adafruit_led_animation.animation import Animation
|
||||
from adafruit_led_animation.grid import PixelGrid, HORIZONTAL
|
||||
|
||||
|
||||
|
||||
class SnakeAnimation(Animation):
|
||||
UP = const(0x00)
|
||||
DOWN = const(0x01)
|
||||
LEFT = const(0x02)
|
||||
RIGHT = const(0x03)
|
||||
ALL_DIRECTIONS = [UP, DOWN, LEFT, RIGHT]
|
||||
DIRECTION_OFFSETS = {
|
||||
DOWN: (0, 1),
|
||||
UP: (0, -1),
|
||||
RIGHT: (1, 0),
|
||||
LEFT: (-1, 0)
|
||||
}
|
||||
|
||||
def __init__(self, pixel_object, speed, color, width, height, snake_length=3):
|
||||
"""
|
||||
Renders a snake that slithers around the 2D grid of pixels
|
||||
"""
|
||||
super().__init__(pixel_object, speed, color)
|
||||
|
||||
# how many segments the snake will have
|
||||
self.snake_length = snake_length
|
||||
|
||||
# create a PixelGrid helper to access our strand as a 2D grid
|
||||
self.pixel_grid = PixelGrid(pixel_object, width, height,
|
||||
orientation=HORIZONTAL, alternating=False)
|
||||
|
||||
# size variables
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
# list that will hold locations of snake segments
|
||||
self.snake_pixels = []
|
||||
|
||||
self.direction = None
|
||||
|
||||
# initialize the snake
|
||||
self._new_snake()
|
||||
|
||||
def _clear_snake(self):
|
||||
"""
|
||||
Clear the snake segments and turn off all pixels
|
||||
"""
|
||||
while len(self.snake_pixels) > 0:
|
||||
self.pixel_grid[self.snake_pixels.pop()] = 0x000000
|
||||
|
||||
def _new_snake(self):
|
||||
"""
|
||||
Create a new single segment snake. The snake has a random
|
||||
direction and location. Turn on the pixel representing the snake.
|
||||
"""
|
||||
# choose a random direction and store it
|
||||
self.direction = random.choice(SnakeAnimation.ALL_DIRECTIONS)
|
||||
|
||||
# choose a random starting tile
|
||||
starting_tile = (random.randint(0, self.width - 1), random.randint(0, self.height - 1))
|
||||
|
||||
# add the starting tile to the list of segments
|
||||
self.snake_pixels.append(starting_tile)
|
||||
|
||||
# turn on the pixel at the chosen location
|
||||
self.pixel_grid[self.snake_pixels[0]] = self.color
|
||||
|
||||
def _can_move(self, direction):
|
||||
"""
|
||||
returns true if the snake can move in the given direction
|
||||
"""
|
||||
# location of the next tile if we would move that direction
|
||||
next_tile = tuple(map(sum, zip(
|
||||
SnakeAnimation.DIRECTION_OFFSETS[direction], self.snake_pixels[0])))
|
||||
|
||||
# if the tile is one of the snake segments
|
||||
if next_tile in self.snake_pixels:
|
||||
# can't move there
|
||||
return False
|
||||
|
||||
# if the tile is within the bounds of the grid
|
||||
if 0 <= next_tile[0] < self.width and 0 <= next_tile[1] < self.height:
|
||||
# can move there
|
||||
return True
|
||||
|
||||
# return false if any other conditions not met
|
||||
return False
|
||||
|
||||
|
||||
def _choose_direction(self):
|
||||
"""
|
||||
Choose a direction to go in. Could continue in same direction
|
||||
as it's already going, or decide to turn to a dirction that
|
||||
will allow movement.
|
||||
"""
|
||||
|
||||
# copy of all directions in a list
|
||||
directions_to_check = list(SnakeAnimation.ALL_DIRECTIONS)
|
||||
|
||||
# if we can move the direction we're currently going
|
||||
if self._can_move(self.direction):
|
||||
# "flip a coin"
|
||||
if random.random() < 0.5:
|
||||
# on "heads" we stay going the same direction
|
||||
return self.direction
|
||||
|
||||
# loop over the copied list of directions to check
|
||||
while len(directions_to_check) > 0:
|
||||
# choose a random one from the list and pop it out of the list
|
||||
possible_direction = directions_to_check.pop(
|
||||
random.randint(0, len(directions_to_check)-1))
|
||||
# if we can move the chosen direction
|
||||
if self._can_move(possible_direction):
|
||||
# return the chosen direction
|
||||
return possible_direction
|
||||
|
||||
# if we made it through all directions and couldn't move in any of them
|
||||
# then raise the SnakeStuckException
|
||||
raise SnakeAnimation.SnakeStuckException
|
||||
|
||||
|
||||
def draw(self):
|
||||
"""
|
||||
Draw the current frame of the animation
|
||||
"""
|
||||
# if the snake is currently the desired length
|
||||
if len(self.snake_pixels) == self.snake_length:
|
||||
# remove the last segment from the list and turn it's LED off
|
||||
self.pixel_grid[self.snake_pixels.pop()] = 0x000000
|
||||
|
||||
# if the snake is less than the desired length
|
||||
# e.g. because we removed one in the previous step
|
||||
if len(self.snake_pixels) < self.snake_length:
|
||||
# wrap with try to catch the SnakeStuckException
|
||||
try:
|
||||
# update the direction, could continue straight, or could change
|
||||
self.direction = self._choose_direction()
|
||||
|
||||
# the location of the next tile where the head of the snake will move to
|
||||
next_tile = tuple(map(sum, zip(
|
||||
SnakeAnimation.DIRECTION_OFFSETS[self.direction], self.snake_pixels[0])))
|
||||
|
||||
# insert the next tile at list index 0
|
||||
self.snake_pixels.insert(0, next_tile)
|
||||
|
||||
# turn on the LED for the tile
|
||||
self.pixel_grid[next_tile] = self.color
|
||||
|
||||
# if the snake exception is caught
|
||||
except SnakeAnimation.SnakeStuckException:
|
||||
# clear the snake to get rid of the old one
|
||||
self._clear_snake()
|
||||
|
||||
# make a new snake
|
||||
self._new_snake()
|
||||
|
||||
class SnakeStuckException(RuntimeError):
|
||||
"""
|
||||
Exception indicating the snake is stuck and can't move in any direction
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__("SnakeStuckException")
|
||||
36
Custom_LED_Animations/strand_sequence/code.py
Normal file
36
Custom_LED_Animations/strand_sequence/code.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
import board
|
||||
|
||||
import neopixel
|
||||
from adafruit_led_animation.color import PINK, JADE
|
||||
from adafruit_led_animation.sequence import AnimationSequence
|
||||
|
||||
from rainbowsweep import RainbowSweepAnimation
|
||||
from sweep import SweepAnimation
|
||||
from zipper import ZipperAnimation
|
||||
|
||||
# Update to match the pin connected to your NeoPixels
|
||||
pixel_pin = board.A1
|
||||
# Update to match the number of NeoPixels you have connected
|
||||
pixel_num = 30
|
||||
|
||||
# initialize the neopixels. Change out for dotstars if needed.
|
||||
pixels = neopixel.NeoPixel(pixel_pin, pixel_num, brightness=0.02, auto_write=False)
|
||||
|
||||
# initialize the animations
|
||||
sweep = SweepAnimation(pixels, speed=0.05, color=PINK)
|
||||
|
||||
zipper = ZipperAnimation(pixels, speed=0.1, color=PINK, alternate_color=JADE)
|
||||
|
||||
rainbowsweep = RainbowSweepAnimation(pixels, speed=0.05, color=0x000000, sweep_speed=0.1,
|
||||
sweep_direction=RainbowSweepAnimation.DIRECTION_END_TO_START)
|
||||
|
||||
# sequence to play them all one after another
|
||||
animations = AnimationSequence(
|
||||
sweep, zipper, rainbowsweep, advance_interval=6, auto_clear=True
|
||||
)
|
||||
|
||||
while True:
|
||||
animations.animate()
|
||||
145
Custom_LED_Animations/strand_sequence/rainbowsweep.py
Normal file
145
Custom_LED_Animations/strand_sequence/rainbowsweep.py
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
Adapted From `adafruit_led_animation.animation.rainbow`
|
||||
"""
|
||||
|
||||
from adafruit_led_animation.animation import Animation
|
||||
from adafruit_led_animation.color import colorwheel
|
||||
from adafruit_led_animation import MS_PER_SECOND, monotonic_ms
|
||||
|
||||
|
||||
class RainbowSweepAnimation(Animation):
|
||||
"""
|
||||
The classic rainbow color wheel that gets swept across by another specified color.
|
||||
|
||||
:param pixel_object: The initialised LED object.
|
||||
:param float speed: Animation refresh rate in seconds, e.g. ``0.1``.
|
||||
:param float sweep_speed: How long in seconds to wait between sweep steps
|
||||
:param float period: Period to cycle the rainbow over in seconds. Default 1.
|
||||
:param sweep_direction: which way to sweep across the rainbow. Must be one of
|
||||
DIRECTION_START_TO_END or DIRECTION_END_TO_START
|
||||
:param str name: Name of animation (optional, useful for sequences and debugging).
|
||||
|
||||
"""
|
||||
|
||||
# constants to represent the different directions
|
||||
DIRECTION_START_TO_END = 0
|
||||
DIRECTION_END_TO_START = 1
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__(
|
||||
self, pixel_object, speed, color, sweep_speed=0.3, period=1,
|
||||
name=None, sweep_direction=DIRECTION_START_TO_END
|
||||
):
|
||||
super().__init__(pixel_object, speed, color, name=name)
|
||||
self._period = period
|
||||
# internal var step used inside of color generator
|
||||
self._step = 256 // len(pixel_object)
|
||||
|
||||
# internal var wheel_index used inside of color generator
|
||||
self._wheel_index = 0
|
||||
|
||||
# instance of the generator
|
||||
self._generator = self._color_wheel_generator()
|
||||
|
||||
# convert swap speed from seconds to ms and store it
|
||||
self._sweep_speed = sweep_speed * 1000
|
||||
|
||||
# set the initial sweep index
|
||||
self.sweep_index = len(pixel_object)
|
||||
|
||||
# internal variable to store the timestamp of when a sweep step occurs
|
||||
self._last_sweep_time = 0
|
||||
|
||||
# store the direction argument
|
||||
self.direction = sweep_direction
|
||||
|
||||
# this animation supports on cycle complete callbacks
|
||||
on_cycle_complete_supported = True
|
||||
|
||||
def _color_wheel_generator(self):
|
||||
# convert period to ms
|
||||
period = int(self._period * MS_PER_SECOND)
|
||||
|
||||
# how many pixels in the strand
|
||||
num_pixels = len(self.pixel_object)
|
||||
|
||||
# current timestamp
|
||||
last_update = monotonic_ms()
|
||||
|
||||
cycle_position = 0
|
||||
last_pos = 0
|
||||
while True:
|
||||
cycle_completed = False
|
||||
# time vars
|
||||
now = monotonic_ms()
|
||||
time_since_last_draw = now - last_update
|
||||
last_update = now
|
||||
|
||||
# cycle position vars
|
||||
pos = cycle_position = (cycle_position + time_since_last_draw) % period
|
||||
|
||||
# if it's time to signal cycle complete
|
||||
if pos < last_pos:
|
||||
cycle_completed = True
|
||||
|
||||
# update position var for next iteration
|
||||
last_pos = pos
|
||||
|
||||
# calculate wheel_index
|
||||
wheel_index = int((pos / period) * 256)
|
||||
|
||||
# set all pixels to their color based on the wheel color and step
|
||||
self.pixel_object[:] = [
|
||||
colorwheel(((i * self._step) + wheel_index) % 255) for i in range(num_pixels)
|
||||
]
|
||||
|
||||
# if it's time for a sweep step
|
||||
if self._last_sweep_time + self._sweep_speed <= now:
|
||||
|
||||
# udpate sweep timestamp
|
||||
self._last_sweep_time = now
|
||||
|
||||
# decrement the sweep index
|
||||
self.sweep_index -= 1
|
||||
|
||||
# if it's finished the last step
|
||||
if self.sweep_index == -1:
|
||||
# reset it to the number of pixels in the strand
|
||||
self.sweep_index = len(self.pixel_object)
|
||||
|
||||
# if end to start direction
|
||||
if self.direction == self.DIRECTION_END_TO_START:
|
||||
# set the current pixels at the end of the strand to the specified color
|
||||
self.pixel_object[self.sweep_index:] = (
|
||||
[self.color] * (len(self.pixel_object) - self.sweep_index))
|
||||
|
||||
# if start to end direction
|
||||
elif self.direction == self.DIRECTION_START_TO_END:
|
||||
# set the pixels at the begining of the strand to the specified color
|
||||
inverse_index = len(self.pixel_object) - self.sweep_index
|
||||
self.pixel_object[:inverse_index] = [self.color] * (inverse_index)
|
||||
|
||||
# update the wheel index
|
||||
self._wheel_index = wheel_index
|
||||
|
||||
# signal cycle complete if it's time
|
||||
if cycle_completed:
|
||||
self.cycle_complete = True
|
||||
yield
|
||||
|
||||
|
||||
def draw(self):
|
||||
"""
|
||||
draw the current frame of the animation
|
||||
:return:
|
||||
"""
|
||||
next(self._generator)
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Resets the animation.
|
||||
"""
|
||||
self._generator = self._color_wheel_generator()
|
||||
68
Custom_LED_Animations/strand_sequence/sweep.py
Normal file
68
Custom_LED_Animations/strand_sequence/sweep.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# SPDX-FileCopyrightText: 2024 Tim Cocks
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""
|
||||
SweepAnimation helper class
|
||||
"""
|
||||
from adafruit_led_animation.animation import Animation
|
||||
|
||||
|
||||
class SweepAnimation(Animation):
|
||||
|
||||
def __init__(self, pixel_object, speed, color):
|
||||
"""
|
||||
Sweeps across the strand lighting up one pixel at a time.
|
||||
Once the full strand is lit, sweeps across again turning off
|
||||
each pixel one at a time.
|
||||
|
||||
:param pixel_object: The initialized pixel object
|
||||
:param speed: The speed to run the animation
|
||||
:param color: The color the pixels will be lit up.
|
||||
"""
|
||||
|
||||
# Call super class initialization
|
||||
super().__init__(pixel_object, speed, color)
|
||||
|
||||
# custom variable to store the current step of the animation
|
||||
self.current_step = 0
|
||||
|
||||
# one step per pixel
|
||||
self.last_step = len(pixel_object)
|
||||
|
||||
# boolean indicating whether we're currently sweeping LEDs on or off
|
||||
self.sweeping_on = True
|
||||
|
||||
self.cycle_complete = False
|
||||
|
||||
# This animation supports the cycle complete callback
|
||||
on_cycle_complete_supported = True
|
||||
|
||||
def draw(self):
|
||||
"""
|
||||
Display the current frame of the animation
|
||||
|
||||
:return: None
|
||||
"""
|
||||
if self.sweeping_on:
|
||||
# Turn on the next LED
|
||||
self.pixel_object[self.current_step] = self.color
|
||||
else: # sweeping off
|
||||
# Turn off the next LED
|
||||
self.pixel_object[self.current_step] = 0x000000
|
||||
|
||||
# increment the current step variable
|
||||
self.current_step += 1
|
||||
|
||||
# if we've reached the last step
|
||||
if self.current_step >= self.last_step:
|
||||
|
||||
# if we are currently sweeping off
|
||||
if not self.sweeping_on:
|
||||
# signal that the cycle is complete
|
||||
self.cycle_complete = True
|
||||
|
||||
# reset the step variable to 0
|
||||
self.current_step = 0
|
||||
|
||||
# flop sweeping on/off indicator variable
|
||||
self.sweeping_on = not self.sweeping_on
|
||||
81
Custom_LED_Animations/strand_sequence/zipper.py
Normal file
81
Custom_LED_Animations/strand_sequence/zipper.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# SPDX-FileCopyrightText: 2024 Tim Cocks
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""
|
||||
ZipperAnimation helper class
|
||||
"""
|
||||
from adafruit_led_animation.animation import Animation
|
||||
|
||||
|
||||
class ZipperAnimation(Animation):
|
||||
|
||||
def __init__(self, pixel_object, speed, color, alternate_color=None):
|
||||
"""
|
||||
Lights up every other LED from each ends of the strand, passing each
|
||||
other in the middle and resulting in the full strand being lit at the
|
||||
end of the cycle.
|
||||
|
||||
:param pixel_object: The initialized pixel object
|
||||
:param speed: The speed to run the animation
|
||||
:param color: The color the pixels will be lit up.
|
||||
"""
|
||||
|
||||
# Call super class initialization
|
||||
super().__init__(pixel_object, speed, color)
|
||||
|
||||
# if alternate color is None then use single color
|
||||
if alternate_color is None:
|
||||
self.alternate_color = color
|
||||
else:
|
||||
self.alternate_color = alternate_color
|
||||
|
||||
# custom variable to store the current step of the animation
|
||||
self.current_step = 0
|
||||
|
||||
# We're lighting up every other LED, so we have half the strand
|
||||
# length in steps.
|
||||
self.last_step = len(pixel_object) // 2
|
||||
|
||||
self.cycle_complete = False
|
||||
|
||||
# This animation supports the cycle complete callback
|
||||
on_cycle_complete_supported = True
|
||||
|
||||
def draw(self):
|
||||
"""
|
||||
Display the current frame of the animation
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
# Use try/except to ignore indexes outside the strand
|
||||
try:
|
||||
# Turn on 1 even indexed pixel starting from the start of the strand
|
||||
self.pixel_object[self.current_step * 2] = self.color
|
||||
|
||||
# Turn on 1 odd indexed pixel starting from the end of the strand
|
||||
self.pixel_object[-(self.current_step * 2) - 1] = self.alternate_color
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
# increment the current step variable
|
||||
self.current_step += 1
|
||||
|
||||
# if we've reached the last step
|
||||
if self.current_step > self.last_step:
|
||||
# signal that the cycle is complete
|
||||
self.cycle_complete = True
|
||||
|
||||
# call internal reset() function
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Turns all the LEDs off and resets the current step variable to 0
|
||||
:return: None
|
||||
"""
|
||||
# turn LEDs off
|
||||
self.pixel_object.fill(0x000000)
|
||||
|
||||
# reset current step variable
|
||||
self.current_step = 0
|
||||
Loading…
Reference in a new issue