If you aren't already familiar with the adafruit_led_animation
library, then you should head over to the primary guide for that library to learn the basics about it first and practice using some of the built-in animations on your device.
Once you've done that then you can come back here and have an easier time understanding the class that we'll be working with, and how to instantiate and use it from user code in the code.py file.
The Minimum Requirements
The main thing that we need to do in order to create our custom animations is to define a new class that extends adafruit_led_animation.Animation
. In our class we'll implement a few key functions and fill in the behavior that we want for the desired blinking.
To start, create a new Python file on your CIRCUITPY drive, you can place it at the root of the drive or inside of the lib folder. Try to give at a name that describes what the animation is, hopefully something that your future self will recognize and remember the meaning of. I'll use demo_animation.py, yours should be specific to your project or animation effect.
demo_animation.py
from adafruit_led_animation.animation import Animation # class definiation named similarly to the filename, # extend the Animation class from the library class DemoAnimation(Animation): # init function definition called by user # code to create an instance of the class def __init__(self, pixel_object, speed, color): # call the init function for the super class Animation, # pass through all of the arguments that came to us super().__init__(pixel_object, speed, color) # initialize any other variables you need for your # custom animation here # i.e. self.my_var = "something" # draw function will get called from animate when the time # comes for it based on the configured speed def draw(self): # do something with self.pixel_object self.pixel_object.fill(self.color)
That is the bare minimum required for your custom Animation
. It won't produce anything particularly interesting or blinky, but it can be initialized and you can call animate()
on it in order to test.
Lets break down what the different parts of this code do:
class DemoAnimation(Animation):
This line defines our class, we get to name it whatever want, I've used DemoAnimation
, yours should try to describe your own animation effect. The (Animation)
in parentheses means that our class will extend the Animation
class from the adafruit_led_animation
library. Extending that class will give us access to all of the behavior that is in the Animation
class without us having to write it inside of our own class. We can rely on that Animation
super class to provide most of what we need, we'll be able to just focus on the specific ways we want the RGB lights to turn on and off, and leave the rest up to it.
def __init__(self, pixel_object, speed, color):
This starts the definition for our __init__
function which is what will be called when someone makes a new instance of our class. It accepts the same arguments as the super class Animation
, namely pixel_objects
, speed
, and color
. If we wanted to, we could add additional arguments for our specific custom animation.
super().__init__(pixel_object, speed, color)
This calls the __init__
function for the super class Animation
, it will do all of the internal setup required for it to provide it's behavior. We pass through the same argument values that were passed to our class's __init__
function.
def draw(self):
This starts the definition for the draw()
function. It's the place where we will put the code that controls when and how the lights turn on and off. The Animation
super class that we are using will take care of checking how much time has passed and calling draw()
at the appropriate times based on the speed
configuration. Inside of draw()
we can access self.pixel_object
and use it to manipulate the RGB pixels.
code.py
import board import neopixel from demo_animation import DemoAnimation # 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 pixels = neopixel.NeoPixel(pixel_pin, pixel_num, brightness=0.1, auto_write=False) demo_anim = DemoAnimation(pixels, 0.5, (255, 0, 255)) while True: demo_anim.animate()
If you run this code.py file using the DemoAnimation
class shown above it will simply turn all of the LEDs on to a pink color (assuming they are RGB color order). To do something more interesting we would expand upon the code inside of the draw()
function to make it do other stuff like turning LEDs off sometimes, or setting some to a color and others to different colors, or perhaps changing the color they're showing with each successive call to draw()
. There are examples on the following pages that illustrate different possibles ideas and are thoroughly commented to explain how they work.
The rest of the features of the Animation
class are optional. Certain animations or projects might find some usage for them, but others may be able to work perfectly fine with only the pieces shown above.
on_cycle_complete_supported
The cycle complete receiver is a way for user code to get a callback whenever a full cycle of the animation is complete. This allows the user code to insert some customized functionality when that time comes. For example maybe you have a project that wants to play a short audio effect each time the LED animation reaches its loop point. In order to be able to use the cycle complete receiver the specific Animation
class in use must set on_cycle_complete_supported = True
. It must also set self.cycle_complete = True
somewhere inside of the draw()
function at the appropriate time based on how it's cycle works. The logic used for determining when the cycle is complete is up to the specific usages within the custom Animation
.
class DemoAnimation(Animation): on_cycle_complete_supported = True def draw(self): if <your cycle logic here>: self.cycle_complete = True # ... rest of the Animation definition ...
def play_sound(): print("cycle complete play the sound") # ... your audio code here ... demo_anim = DemoAnimation(pixelmap_up, speed=.1, color=AMBER) demo_anim.add_cycle_complete_receiver(play_sound) while True: demo_anim.animate()
reset()
The reset()
function can be overridden by subclasses of Animation
in order to insert some customized behavior that happens at the end of a full cycle of the Animation
and before the next cycle begins. A good example of this is the built-in Comet
animation which has an overridden reset()
function that carries out the required behavior for it's reverse
and ring
features.
If your custom Animation has some behavior it needs to do when the animation loops then you need to do two things in your custom class: 1) override the reset()
function to put your behavior inside of it, and 2) add some code into the draw()
function that calls reset()
at the appropriate time.
Reset is a similar concept to the cycle complete receiver discussed above. The main difference is reset() is meant for use internally within the Animation. While the cycle complete reciever is intended for user code that is outside of our custom Animation class, but is going to make an instance of our class and call animate() on it.
class DemoAnimation(Animation): def __init__(self, pixel_object, speed, color,): super().__init__( pixel_object, speed, color) self.index = 0 def reset(self): self.pixel_object.fill((0, 0, 0)) self.index = 0 def draw(self): if self.index >= len(self.pixel_object): self.reset() self.pixel_object[self.index] = self.color self.index += 1
This example DemoAnimation uses a custom variable index
to store the current index within the full strip of pixels. The overridden reset()
function sets index
back to zero and turns off all of the pixels. The draw()
function calls reset()
when the index has reached to the length of the pixel_object
. The result is the animation will turn on one additional pixel per frame. Once all pixels are on, they will be turned off and cycle will repeat back at the beginning of the strip.
after_draw()
The after_draw()
function can be overridden by subclasses of Animation
to insert customized behavior that will occur after each animation frame. For example the built-in Sparkle
animation overrides the after_draw()
function to set some of the pixels to a dimmer color in order to create the sparkling visual effect. after_draw()
will get called automatically at the conclusion of the draw()
function, you do not need to manually call it yourself inside draw()
or anywhere else.
class DemoAnimation(Animation): def __init__(self, pixel_object, speed, color,): super().__init__( pixel_object, speed, color) self.drawn_pixels = [] def draw(self): random_index = random.randint(0, len(self.pixel_object)-1) self.pixel_object[random_index] = self.color self.drawn_pixels.append(random_index) def after_draw(self): if len(self.drawn_pixels) > 5: oldest_index = self.drawn_pixels.pop(0) self.pixel_object[oldest_index] = (0,0,0)
This example DemoAnimation
turns on random LEDs one at a time until there are 5 LEDs on. After 5 LEDs are on, the after_draw()
function is used to turn off the LED that has been on the longest. The result is that random LEDs turn on, and stay on for 5 frames of the animation and then turn off. It uses the custom variable drawn_pixels
to store a list of indexes of the LEDs that are currently on.
With all of the tools discussed on this page we have everything we need to create dazzling and delightful animations. On the next page we'll look at some some other specific examples of custom animations.
Text editor powered by tinymce.