From 2e800843d81f057edd8656b6be067e4fa4b11d3b Mon Sep 17 00:00:00 2001 From: foamyguy Date: Thu, 31 Oct 2024 15:17:08 -0500 Subject: [PATCH 1/5] custom LED animations code --- Custom_LED_Animations/conways/code.py | 0 Custom_LED_Animations/conways/conways.py | 0 Custom_LED_Animations/rainbow_sweep/code.py | 20 +++ .../rainbow_sweep/rainbowsweep.py | 143 +++++++++++++++ Custom_LED_Animations/snake/code.py | 19 ++ Custom_LED_Animations/snake/snake.py | 164 ++++++++++++++++++ Custom_LED_Animations/sweep/code.py | 21 +++ Custom_LED_Animations/sweep/sweep.py | 66 +++++++ Custom_LED_Animations/zipper/code.py | 21 +++ Custom_LED_Animations/zipper/zipper.py | 73 ++++++++ 10 files changed, 527 insertions(+) create mode 100644 Custom_LED_Animations/conways/code.py create mode 100644 Custom_LED_Animations/conways/conways.py create mode 100644 Custom_LED_Animations/rainbow_sweep/code.py create mode 100644 Custom_LED_Animations/rainbow_sweep/rainbowsweep.py create mode 100644 Custom_LED_Animations/snake/code.py create mode 100644 Custom_LED_Animations/snake/snake.py create mode 100644 Custom_LED_Animations/sweep/code.py create mode 100644 Custom_LED_Animations/sweep/sweep.py create mode 100644 Custom_LED_Animations/zipper/code.py create mode 100644 Custom_LED_Animations/zipper/zipper.py diff --git a/Custom_LED_Animations/conways/code.py b/Custom_LED_Animations/conways/code.py new file mode 100644 index 000000000..e69de29bb diff --git a/Custom_LED_Animations/conways/conways.py b/Custom_LED_Animations/conways/conways.py new file mode 100644 index 000000000..e69de29bb diff --git a/Custom_LED_Animations/rainbow_sweep/code.py b/Custom_LED_Animations/rainbow_sweep/code.py new file mode 100644 index 000000000..7683125bb --- /dev/null +++ b/Custom_LED_Animations/rainbow_sweep/code.py @@ -0,0 +1,20 @@ +import board +import neopixel + +from rainbowsweep import RainbowSweepAnimation + +# 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. Change out for dotstars if needed. +pixels = neopixel.NeoPixel(pixel_pin, pixel_num, brightness=0.02, auto_write=False) + +# initialize the animation +rainbowsweep = RainbowSweepAnimation(pixels, speed=0.05, color=0x000000, sweep_speed=0.1, + sweep_direction=RainbowSweepAnimation.DIRECTION_END_TO_START) + +while True: + # call animation to show the next animation frame + rainbowsweep.animate() diff --git a/Custom_LED_Animations/rainbow_sweep/rainbowsweep.py b/Custom_LED_Animations/rainbow_sweep/rainbowsweep.py new file mode 100644 index 000000000..0bc9045a4 --- /dev/null +++ b/Custom_LED_Animations/rainbow_sweep/rainbowsweep.py @@ -0,0 +1,143 @@ +# 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 BLACK, 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() diff --git a/Custom_LED_Animations/snake/code.py b/Custom_LED_Animations/snake/code.py new file mode 100644 index 000000000..1c1206452 --- /dev/null +++ b/Custom_LED_Animations/snake/code.py @@ -0,0 +1,19 @@ +import board +import neopixel + +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. Change out for dotstars if needed. +pixels = neopixel.NeoPixel(pixel_pin, pixel_num, brightness=0.02, auto_write=False) + +# initialize the animation +snake = SnakeAnimation(pixels, speed=0.1, color=0xff00ff, width=8, height=4) + +while True: + # call animation to show the next animation frame + snake.animate() diff --git a/Custom_LED_Animations/snake/snake.py b/Custom_LED_Animations/snake/snake.py new file mode 100644 index 000000000..0465eda73 --- /dev/null +++ b/Custom_LED_Animations/snake/snake.py @@ -0,0 +1,164 @@ +# SPDX-FileCopyrightText: 2024 Tim Cocks +# +# SPDX-License-Identifier: MIT +""" +SnakeAnimation helper class +""" +from micropython import const + +from adafruit_led_animation.animation import Animation +from adafruit_led_animation.grid import PixelGrid, HORIZONTAL +import random + + +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 = [] + + # 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") diff --git a/Custom_LED_Animations/sweep/code.py b/Custom_LED_Animations/sweep/code.py new file mode 100644 index 000000000..bd14dc436 --- /dev/null +++ b/Custom_LED_Animations/sweep/code.py @@ -0,0 +1,21 @@ +import board +import neopixel + +from sweep import SweepAnimation + +from adafruit_led_animation.color import PINK + +# 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. Change out for dotstars if needed. +pixels = neopixel.NeoPixel(pixel_pin, pixel_num, brightness=0.02, auto_write=False) + +# initialize the animation +sweep = SweepAnimation(pixels, speed=0.05, color=PINK) + +while True: + # call animation to show the next animation frame + sweep.animate() diff --git a/Custom_LED_Animations/sweep/sweep.py b/Custom_LED_Animations/sweep/sweep.py new file mode 100644 index 000000000..f39074d0f --- /dev/null +++ b/Custom_LED_Animations/sweep/sweep.py @@ -0,0 +1,66 @@ +# 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 + + # 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 diff --git a/Custom_LED_Animations/zipper/code.py b/Custom_LED_Animations/zipper/code.py new file mode 100644 index 000000000..20de03104 --- /dev/null +++ b/Custom_LED_Animations/zipper/code.py @@ -0,0 +1,21 @@ +import board +import neopixel + +from zipper import ZipperAnimation + +from adafruit_led_animation.color import PINK, JADE + +# 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. Change out for dotstars if needed. +pixels = neopixel.NeoPixel(pixel_pin, pixel_num, brightness=0.02, auto_write=False) + +# initialize the animation +zipper = ZipperAnimation(pixels, speed=0.1, color=PINK, alternate_color=JADE) + +while True: + # call animation to show the next animation frame + zipper.animate() \ No newline at end of file diff --git a/Custom_LED_Animations/zipper/zipper.py b/Custom_LED_Animations/zipper/zipper.py new file mode 100644 index 000000000..d5a0ed640 --- /dev/null +++ b/Custom_LED_Animations/zipper/zipper.py @@ -0,0 +1,73 @@ +# 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): + """ + 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) + + # 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 + + # 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.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 From 5ca431625d95d3b808e6d7eb691da4174937bb73 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Thu, 31 Oct 2024 15:20:39 -0500 Subject: [PATCH 2/5] update copyrights and add conways --- Custom_LED_Animations/conways/code.py | 30 ++++ Custom_LED_Animations/conways/conways.py | 177 ++++++++++++++++++++ Custom_LED_Animations/rainbow_sweep/code.py | 3 + Custom_LED_Animations/snake/code.py | 3 + Custom_LED_Animations/sweep/code.py | 3 + Custom_LED_Animations/zipper/code.py | 3 + 6 files changed, 219 insertions(+) diff --git a/Custom_LED_Animations/conways/code.py b/Custom_LED_Animations/conways/code.py index e69de29bb..4a75a5fd3 100644 --- a/Custom_LED_Animations/conways/code.py +++ b/Custom_LED_Animations/conways/code.py @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries +# +# SPDX-License-Identifier: MIT +import board +import neopixel + +from conways import ConwaysLifeAnimation + +# 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. Change out for dotstars if needed. +pixels = neopixel.NeoPixel(pixel_pin, pixel_num, brightness=0.02, auto_write=False) + +initial_cells = [ + (2, 1), + (3, 1), + (4, 1), + (5, 1), + (6, 1), +] + +# initialize the animation +conways = ConwaysLifeAnimation(pixels, 1.0, 0xff00ff, 8, 4, initial_cells) + +while True: + # call animation to show the next animation frame + conways.animate() diff --git a/Custom_LED_Animations/conways/conways.py b/Custom_LED_Animations/conways/conways.py index e69de29bb..6c387dd6e 100644 --- a/Custom_LED_Animations/conways/conways.py +++ b/Custom_LED_Animations/conways/conways.py @@ -0,0 +1,177 @@ +# 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 +import random + + +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_pixel_off(self, pixel): + return pixel[0] == 0 and pixel[1] == 0 and pixel[2] == 0 + + 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 self._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 self._is_pixel_off(self.pixel_grid[cell[0] + direction[0], cell[1] + direction[1]]): + neighbors += 1 + except IndexError: + pass + return neighbors + + def draw(self): + """ + 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 self._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 diff --git a/Custom_LED_Animations/rainbow_sweep/code.py b/Custom_LED_Animations/rainbow_sweep/code.py index 7683125bb..5ed7784cc 100644 --- a/Custom_LED_Animations/rainbow_sweep/code.py +++ b/Custom_LED_Animations/rainbow_sweep/code.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries +# +# SPDX-License-Identifier: MIT import board import neopixel diff --git a/Custom_LED_Animations/snake/code.py b/Custom_LED_Animations/snake/code.py index 1c1206452..c202dfae1 100644 --- a/Custom_LED_Animations/snake/code.py +++ b/Custom_LED_Animations/snake/code.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries +# +# SPDX-License-Identifier: MIT import board import neopixel diff --git a/Custom_LED_Animations/sweep/code.py b/Custom_LED_Animations/sweep/code.py index bd14dc436..ff4da13f5 100644 --- a/Custom_LED_Animations/sweep/code.py +++ b/Custom_LED_Animations/sweep/code.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries +# +# SPDX-License-Identifier: MIT import board import neopixel diff --git a/Custom_LED_Animations/zipper/code.py b/Custom_LED_Animations/zipper/code.py index 20de03104..3c62fec1f 100644 --- a/Custom_LED_Animations/zipper/code.py +++ b/Custom_LED_Animations/zipper/code.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries +# +# SPDX-License-Identifier: MIT import board import neopixel From 4b223c310b9751744f3ee6f342f1a9cc4f57a4b7 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Thu, 31 Oct 2024 15:28:02 -0500 Subject: [PATCH 3/5] pylint fixes --- .../rainbow_sweep/rainbowsweep.py | 8 +++++--- Custom_LED_Animations/snake/snake.py | 17 ++++++++++++----- Custom_LED_Animations/sweep/sweep.py | 2 ++ Custom_LED_Animations/zipper/code.py | 2 +- Custom_LED_Animations/zipper/zipper.py | 12 ++++++++++-- 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/Custom_LED_Animations/rainbow_sweep/rainbowsweep.py b/Custom_LED_Animations/rainbow_sweep/rainbowsweep.py index 0bc9045a4..ff73929cc 100644 --- a/Custom_LED_Animations/rainbow_sweep/rainbowsweep.py +++ b/Custom_LED_Animations/rainbow_sweep/rainbowsweep.py @@ -7,7 +7,7 @@ """ from adafruit_led_animation.animation import Animation -from adafruit_led_animation.color import BLACK, colorwheel +from adafruit_led_animation.color import colorwheel from adafruit_led_animation import MS_PER_SECOND, monotonic_ms @@ -30,7 +30,8 @@ class RainbowSweepAnimation(Animation): 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 + 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 @@ -112,7 +113,8 @@ def _color_wheel_generator(self): # 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) + 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: diff --git a/Custom_LED_Animations/snake/snake.py b/Custom_LED_Animations/snake/snake.py index 0465eda73..b1cf6c157 100644 --- a/Custom_LED_Animations/snake/snake.py +++ b/Custom_LED_Animations/snake/snake.py @@ -4,11 +4,12 @@ """ SnakeAnimation helper class """ +import random from micropython import const from adafruit_led_animation.animation import Animation from adafruit_led_animation.grid import PixelGrid, HORIZONTAL -import random + class SnakeAnimation(Animation): @@ -34,7 +35,8 @@ def __init__(self, pixel_object, speed, color, width, height, snake_length=3): 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) + self.pixel_grid = PixelGrid(pixel_object, width, height, + orientation=HORIZONTAL, alternating=False) # size variables self.width = width @@ -43,6 +45,8 @@ def __init__(self, pixel_object, speed, color, width, height, snake_length=3): # list that will hold locations of snake segments self.snake_pixels = [] + self.direction = None + # initialize the snake self._new_snake() @@ -75,7 +79,8 @@ 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]))) + 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: @@ -111,7 +116,8 @@ def _choose_direction(self): # 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)) + 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 @@ -140,7 +146,8 @@ def draw(self): 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]))) + 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) diff --git a/Custom_LED_Animations/sweep/sweep.py b/Custom_LED_Animations/sweep/sweep.py index f39074d0f..d56a03b3d 100644 --- a/Custom_LED_Animations/sweep/sweep.py +++ b/Custom_LED_Animations/sweep/sweep.py @@ -32,6 +32,8 @@ def __init__(self, pixel_object, speed, color): # 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 diff --git a/Custom_LED_Animations/zipper/code.py b/Custom_LED_Animations/zipper/code.py index 3c62fec1f..884d8b810 100644 --- a/Custom_LED_Animations/zipper/code.py +++ b/Custom_LED_Animations/zipper/code.py @@ -21,4 +21,4 @@ while True: # call animation to show the next animation frame - zipper.animate() \ No newline at end of file + zipper.animate() diff --git a/Custom_LED_Animations/zipper/zipper.py b/Custom_LED_Animations/zipper/zipper.py index d5a0ed640..52859ca1c 100644 --- a/Custom_LED_Animations/zipper/zipper.py +++ b/Custom_LED_Animations/zipper/zipper.py @@ -9,7 +9,7 @@ class ZipperAnimation(Animation): - def __init__(self, pixel_object, speed, color): + 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 @@ -23,6 +23,12 @@ def __init__(self, pixel_object, speed, color): # 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 @@ -30,6 +36,8 @@ def __init__(self, pixel_object, speed, color): # 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 @@ -46,7 +54,7 @@ def draw(self): 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.color + self.pixel_object[-(self.current_step * 2) - 1] = self.alternate_color except IndexError: pass From 6cb551cbe0d524e9416b1f198ee58341e121d098 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Thu, 31 Oct 2024 15:32:22 -0500 Subject: [PATCH 4/5] format conways, remove unused import --- Custom_LED_Animations/conways/conways.py | 28 +++++++++++++++++------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/Custom_LED_Animations/conways/conways.py b/Custom_LED_Animations/conways/conways.py index 6c387dd6e..47b4907ae 100644 --- a/Custom_LED_Animations/conways/conways.py +++ b/Custom_LED_Animations/conways/conways.py @@ -8,7 +8,6 @@ from adafruit_led_animation.animation import Animation from adafruit_led_animation.grid import PixelGrid, HORIZONTAL -import random class ConwaysLifeAnimation(Animation): @@ -21,12 +20,21 @@ class ConwaysLifeAnimation(Animation): (1, 1), (-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): + 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. @@ -47,7 +55,9 @@ def __init__(self, pixel_object, speed, color, width, height, initial_cells, equ 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) + self.pixel_grid = PixelGrid( + pixel_object, width, height, orientation=HORIZONTAL, alternating=False + ) # size of the grid self.width = width @@ -59,7 +69,7 @@ def __init__(self, pixel_object, speed, color, width, height, initial_cells, equ # counter to store how many turns since the last change self.equilibrium_turns = 0 - #self._init_cells() + # self._init_cells() def _is_pixel_off(self, pixel): return pixel[0] == 0 and pixel[1] == 0 and pixel[2] == 0 @@ -72,7 +82,7 @@ def _is_grid_empty(self): """ for y in range(self.height): for x in range(self.width): - if not self._is_pixel_off(self.pixel_grid[x,y]): + if not self._is_pixel_off(self.pixel_grid[x, y]): return False return True @@ -96,7 +106,9 @@ def _count_neighbors(self, cell): neighbors = 0 for direction in ConwaysLifeAnimation.DIRECTION_OFFSETS: try: - if not self._is_pixel_off(self.pixel_grid[cell[0] + direction[0], cell[1] + direction[1]]): + if not self._is_pixel_off( + self.pixel_grid[cell[0] + direction[0], cell[1] + direction[1]] + ): neighbors += 1 except IndexError: pass @@ -125,7 +137,7 @@ def draw(self): for x in range(self.width): # check and set the current cell type, live or dead - if self._is_pixel_off(self.pixel_grid[x,y]): + if self._is_pixel_off(self.pixel_grid[x, y]): cur_cell_type = ConwaysLifeAnimation.DEAD else: cur_cell_type = ConwaysLifeAnimation.LIVE From cf348e01561311f8560578c16e84c328f2fe1b03 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Thu, 31 Oct 2024 15:35:43 -0500 Subject: [PATCH 5/5] more pylint fixes --- Custom_LED_Animations/conways/conways.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Custom_LED_Animations/conways/conways.py b/Custom_LED_Animations/conways/conways.py index 47b4907ae..d9984f01a 100644 --- a/Custom_LED_Animations/conways/conways.py +++ b/Custom_LED_Animations/conways/conways.py @@ -10,6 +10,10 @@ 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 = [ @@ -36,7 +40,8 @@ def __init__( equilibrium_restart=True, ): """ - Conway's Game of Life implementation. Watch the cells live and die based on the classic rules. + 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``. @@ -71,9 +76,6 @@ def __init__( # self._init_cells() - def _is_pixel_off(self, pixel): - return pixel[0] == 0 and pixel[1] == 0 and pixel[2] == 0 - def _is_grid_empty(self): """ Checks if the grid is empty. @@ -82,7 +84,7 @@ def _is_grid_empty(self): """ for y in range(self.height): for x in range(self.width): - if not self._is_pixel_off(self.pixel_grid[x, y]): + if not _is_pixel_off(self.pixel_grid[x, y]): return False return True @@ -106,7 +108,7 @@ def _count_neighbors(self, cell): neighbors = 0 for direction in ConwaysLifeAnimation.DIRECTION_OFFSETS: try: - if not self._is_pixel_off( + if not _is_pixel_off( self.pixel_grid[cell[0] + direction[0], cell[1] + direction[1]] ): neighbors += 1 @@ -115,6 +117,7 @@ def _count_neighbors(self, cell): return neighbors def draw(self): + # pylint: disable=too-many-branches """ draw the current frame of the animation @@ -137,7 +140,7 @@ def draw(self): for x in range(self.width): # check and set the current cell type, live or dead - if self._is_pixel_off(self.pixel_grid[x, y]): + if _is_pixel_off(self.pixel_grid[x, y]): cur_cell_type = ConwaysLifeAnimation.DEAD else: cur_cell_type = ConwaysLifeAnimation.LIVE