This project involves two Circuit Playground Bluefruit boards. The second acts as the NeoPixel Animator which receives data from the Remote Control which is used to cycle through animations, change the color, freeze the color, and turn on and off the LEDs. This section will go through the NeoPixel Animator code. Let's take a look!

Library Imports

First we import all the necessary libraries and modules.

import board
import neopixel
from adafruit_circuitplayground.bluefruit import cpb
from adafruit_led_animation.animation.blink import Blink
from adafruit_led_animation.animation.comet import Comet
from adafruit_led_animation.animation.sparkle import Sparkle
from adafruit_led_animation.group import AnimationGroup
from adafruit_led_animation.sequence import AnimationSequence
import adafruit_led_animation.color as color

from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble.services.nordic import UARTService

from adafruit_bluefruit_connect.packet import Packet
from adafruit_bluefruit_connect.color_packet import ColorPacket
from adafruit_bluefruit_connect.button_packet import ButtonPacket

The first two are needed to set up the external NeoPixel strip(s).

We'll be able to call on Circuit Playground Bluefruit board functions with the cpb command, including simplified ways to access the built-in NeoPixels.

We'll use the Adafruit CircuitPython LED Animation library to display the Blink, Comet and Sparkle animations using animation groups and animation sequence. We'll also use it to set the initial animation color.

Then we import all the necessary libraries for connecting to another Bluefruit, and the modules to be able to receive and interpret color packets and button packets. We'll use the color packets to receive data from the Remote Control CPB and set the NeoPixel color based on that data, and button packets to change the animations and freeze the color of the NeoPixels.

Customisations

There are a number of features of the animations that you can customise to your liking. This section of code contains all of the customisable options.

First, set the number of pixels in your externally connected NeoPixel strip. The default is 30. If you are using two strips connected to the same pin, count the length of both strips only once, i.e. if you have two strips of length 30 attached to pin A1, your STRIP_PIXEL_NUMBER will be 30.

# The number of NeoPixels in the externally attached strip
# If using two strips connected to the same pin, count only one strip for this number!
STRIP_PIXEL_NUMBER = 30

The first animation in the cycle is Blink. This is the only animation that the color really matters for as it is the only animation that occurs when there is not another Circuit Playground Bluefruit connected and sending color data. You can set the blink speed and color here. Speed defaults to 0.5 seconds. The color defaults to red.

# Setup for blink animation
BLINK_SPEED = 0.5  # Lower numbers increase the animation speed
BLINK_INITIAL_COLOR = color.RED  # Color before Remote Control is connected

Some of the color options available in the LED Animation library are: RED, YELLOW, ORANGE, GREEN, TEAL, CYAN, BLUE, PURPLE, MAGENTA, WHITE, GOLD, PINK, AQUA, JADE, AMBER.

Next is the setup for the comet animation. First you can set the comet speed. Default is 0.03 seconds. Next you can set the tail length of the comet animating on the Circuit Playground Bluefruit - this comet defaults to a length of 5. Then you can set the tail length of the comet animating on the NeoPixel strip(s) - this comet defaults to a length of 15.

The comet animation has an option to "bounce" which means when it completes animating in one direction, it returns the other direction. You can set whether or not the comet animations bounce. The comet on the Bluefruit defaults to not bouncing. The comet on the strip(s) defaults to bouncing. Set these to True or False depending on whether you want it to bounce or not.

# Setup for comet animation
COMET_SPEED = 0.03  # Lower numbers increase the animation speed
CPB_COMET_TAIL_LENGTH = 5  # The length of the comet on the Circuit Playground Bluefruit
STRIP_COMET_TAIL_LENGTH = 15  # The length of the comet on the NeoPixel strip
CPB_COMET_BOUNCE = False  # Set to True to make the comet "bounce" the opposite direction on CPB
STRIP_COMET_BOUNCE = True  # Set to False to stop comet from "bouncing" on NeoPixel strip

Last is the setup for the sparkle animation. The only thing to set here is sparkle speed. It defaults to 0.03 seconds.

# Setup for sparkle animation
SPARKLE_SPEED = 0.03  # Lower numbers increase the animation speed

NeoPixel Strip and Bluetooth Setup

Next we create the NeoPixel strip object. If you're using two strips connected to the same pin, you still only need one strip object as it treats them both as the same strip.

strip_pixels = neopixel.NeoPixel(board.A1, STRIP_PIXEL_NUMBER, auto_write=False)

Then we create the BLE, UART service, and advertisement objects.

ble = BLERadio()
uart = UARTService()
advertisement = ProvideServicesAdvertisement(uart)

Animations

Next we create an animation sequence made up of animation groups. Animation sequences allow you to cycle through multiple animations, such as Blink, Comet and Sparkle. Animation groups allow you to animate on multiple LED objects at the same time, in this case, the built-in NeoPixels on the Bluefruit and the externally connected NeoPixel strip(s).

This animation sequence is made up of three animation groups. Each group contains two instances of one of three animations, grouped together by animation type. Within the groups, two animations objects are created for each of the three animations - one for the Circuit Playground Bluefruit as cpb.pixels, and one for the external NeoPixel strip(s) as strip_pixels. Each animation object includes the necessary information for setup, e.g. for Blink, you provide the pixel object, the speed and the initial color.

animations = AnimationSequence(
    AnimationGroup(
        Blink(cpb.pixels, BLINK_SPEED, BLINK_INITIAL_COLOR),
        Blink(strip_pixels, BLINK_SPEED, BLINK_INITIAL_COLOR),
        sync=True
    ),
    AnimationGroup(
        Comet(cpb.pixels, COMET_SPEED, COMET_INITIAL_COLOR, tail_length=CPB_COMET_TAIL_LENGTH,
              bounce=CPB_COMET_BOUNCE),
        Comet(strip_pixels, COMET_SPEED, COMET_INITIAL_COLOR, tail_length=STRIP_COMET_TAIL_LENGTH,
              bounce=STRIP_COMET_BOUNCE)
    ),
    AnimationGroup(
        Sparkle(cpb.pixels, SPARKLE_SPEED, SPARKLE_INITIAL_COLOR),
        Sparkle(strip_pixels, SPARKLE_SPEED, SPARKLE_INITIAL_COLOR)
    ),
)

The last thing before the main loop are three variables created for various uses in the code. We set animation_color to None, the mode to 0, and blanked to False.

animation_color = None
mode = 0
blanked = False

Advertising

The first thing we do in the main loop is begin advertising, i.e. sending out a signal that says the NeoPixel Animator is available for a BLE connection.

[...]
    ble.start_advertising(advertisement)  # Start advertising.

Animate the Animations

Regardless of whether we are connected or not, as long as we haven't set blanked to True, we animate the animations.

[...]
    while not was_connected or ble.connected:
        if not blanked:  # If LED-off signal is not being sent...
            animations.animate()  # Run the animations.

Packet Time

Once we are connected, we check to see if any data is available from the Remote Control CPB, and then create the packet object.

[...]
        if ble.connected:  # If BLE is connected...
            was_connected = True
            if uart.in_waiting:  # Check to see if any data is available
                try:
                    packet = Packet.from_stream(uart)  # Create the packet object.
                except ValueError:
                    continue

Color Packets

The color of the NeoPixels on the NeoPixel Animator are constantly changing based on the orientation of the Remote Control, which is constantly sending updated color packets. There are two color modes: color changing and color frozen.

The code first checks to see if the packet received is a color packet. If it is, we check to see if the mode is 0 or 1.

If the mode is 0, we set the animation color to the packet color. Then we save the current packet color as animation_color.

This is so we can use it if the mode is 1. What if you find a favorite color and want to keep the animations that color? If the mode is 1, we "freeze" the animation color by setting it to animation_color. This only updates when the mode is returned to 0, so as long as the mode is 1, the animations will remain the same color.

[...]
                if isinstance(packet, ColorPacket):
                    if mode == 0:
                        animations.color = packet.color
                        print("Color:", packet.color)
                        animation_color = packet.color
                    elif mode == 1:
                        animations.color = animation_color
                        print("Color:", animation_color)

Button Packets

The NeoPixel Animator is also constantly listening for button packets from the Remote Control. These are used to turn the NeoPixels on and off, change animations, and freeze the animation color.

First we check to see if the packet received is a button packet. If it is, we check to see which button packet it is: BUTTON_1, LEFT or RIGHT.

The Remote Control CPB slide switch is sending the BUTTON_1 packet. Even though it's not a button you can "press" we're using the "pressed" or "not pressed" state to determine direction: left is the "pressed" state, and right is the "not pressed" state. So we check to see if BUTTON_1 is "pressed. If it is pressed, and the LEDs are currently on (not blanked), we turn off the LEDs by setting all of them to BLACK.

Last, we set blanked equal to the "pressed" state of the slide switch - this is so we can track whether the LEDs are currently on or off so we know whether to turn them off or on in the previous two lines of code.

[...]
                elif isinstance(packet, ButtonPacket):
                    if packet.button == ButtonPacket.BUTTON_1:
                        if packet.pressed:
                            print("Remote Control switch is to the left: LEDs off!")
                        else:
                            print("Remote Control switch is to the right: LEDs on!")
                        if packet.pressed and not blanked:
                            animations.fill(color.BLACK)
                        blanked = packet.pressed

Next we are looking for the button presses. The two buttons on the Remote Control are sending LEFT and RIGHT button packets.

If the packet received is a LEFT packet, we move to the next animation.

If the packet received is a RIGHT packet, we increase the mode by 1. The mode starts as 0 in the beginning of the code. If the mode is 1, we print that the color is frozen. If the mode is greater than 1, we set the mode back to 0, and print that the color is changing. Increasing the mode by 1, and setting it back to 0 when it is greater than 1 allows us to cycle between two different modes.

[...]
                    if packet.pressed:
                        if packet.button == ButtonPacket.LEFT:
                            print("A pressed: animation mode changed.")
                            animations.next()
                        elif packet.button == ButtonPacket.RIGHT:
                            mode += 1
                            if mode == 1:
                                print("B pressed: color frozen!")
                            if mode > 1:
                                mode = 0
                                print("B pressed: color changing!")

That's how the NeoPixel Animator code for the Circuit Playground Bluefruit Animation and Color Remote Control works!

This guide was first published on Dec 14, 2019. It was last updated on Mar 28, 2024.

This page (NeoPixel Animator Code) was last updated on Mar 08, 2024.

Text editor powered by tinymce.