The Circuit Playground Bluefruit has all kinds of features built in, including NeoPixels, an accelerometer, two buttons, alligator-clip-friendly pads and Bluetooth Low Energy (BLE). This project uses these features to create a wireless NeoPixel animation and color remote control using CircuitPython. CircuitPython is a version of Python designed to run on microcontrollers like the Circuit Playground Bluefruit.

You'll need two Circuit Playground Bluefruit (CPB) boards, an alligator-clip NeoPixel strip or two, and two batteries. You'll connect the NeoPixel strip(s) to the NeoPixel Animator CPB, and use the other as the Remote Control. This guide will show you how to connect everything up, and load the software onto the CPBs. It will walk you through using your Remote Control CPB to change the colors and animations running on the NeoPixels connected to the NeoPixel Animator CPB. Let's get started!

Parts

Circuit Playground Bluefruit is our third board in the Circuit Playground series, another step towards a perfect introduction to electronics and programming. We've...
$24.95
In Stock
Adding glowy color to your projects has never been easier: no more soldering or stripping wires, clip 'em on and glow! This Adafruit NeoPixel LED Strip with Alligator...
$12.50
In Stock
Need a massive battery for your project? This lithium ion pack is made of 3 balanced 2200mAh cells for a total of 6600mA capacity! The cells are connected in parallel and spot-welded...
$29.50
In Stock
Lithium ion polymer (also known as 'lipo' or 'lipoly') batteries are thin, light and powerful. The output ranges from 4.2V when completely charged to 3.7V. This battery...
$6.95
In Stock
We've got nice cases for many of our beloved boards, but the Circuit Playground Express and
$4.95
In Stock

Additional Items

You may also want to pick up the following:

  • zip ties - to secure the NeoPixel Strips, larger battery, and NeoPixel Animator CPB to your project
  • double-sided tape - to attach the smaller battery to the back of the Remote Control CPB

These items are optional, but come in super handy!

Install or Update CircuitPython

Follow this quick step-by-step to install or update CircuitPython on your Circuit Playground Bluefruit.

Click the link above and download the latest UF2 file

Download and save it to your Desktop (or wherever is handy)

Plug your Circuit Playground Bluefruit into your computer using a known-good data-capable USB cable.

A lot of people end up using charge-only USB cables and it is very frustrating! So make sure you have a USB cable you know is good for data sync.

Double-click the small Reset button in the middle of the CPB (indicated by the red arrow in the image). The ten NeoPixel LEDs will all turn red, and then will all turn green. If they turn all red and stay red, check the USB cable, try another USB port, etc. The little red LED next to the USB connector will pulse red - this is ok!

If double-clicking doesn't work the first time, try again. Sometimes it can take a few tries to get the rhythm right!

(If double-clicking doesn't do it, try a single-click!)

You will see a new disk drive appear called CPLAYBTBOOT.

 

 

 

Drag the adafruit_circuitpython_etc.uf2 file to CPLAYBTBOOT.

The LEDs will turn red. Then, the CPLAYBTBOOT drive will disappear and a new disk drive called CIRCUITPY will appear.

That's it, you're done! :)

The Circuit Playground Bluefruit is packed full of features like Bluetooth and NeoPixel LEDs. Now that you have CircuitPython installed on your Circuit Playground Bluefruit, you'll need to install a base set of CircuitPython libraries to use the features of the board with CircuitPython.

Follow these steps to get the necessary libraries installed.

Installing CircuitPython Libraries on Circuit Playground Bluefruit

If you do not already have a lib folder on your CIRCUITPY drive, create one now.

Then, download the CircuitPython library bundle that matches your version of CircuitPython from CircuitPython.org.

The bundle download as a .zip file. Extract the file. Open the resulting folder.

Open the lib folder found within.

Once inside, you'll find a lengthy list of folders and .mpy files. To install a CircuitPython library, you drag the file or folder from the bundle lib folder to the lib folder on your CIRCUITPY drive.

Copy the following folders and files from the bundle lib folder to the lib folder on your CIRCUITPY drive:

  • adafruit_ble
  • adafruit_bluefruit_connect
  • adafruit_bus_device
  • adafruit_circuitplayground
  • adafruit_gizmo
  • adafruit_hid
  • adafruit_lis3dh.mpy
  • adafruit_thermistor.mpy
  • neopixel.mpy

Your lib folder should look like the image on the left.

Now you're all set to use CircuitPython with the features of the Circuit Playground Bluefruit!

Assembling this project is super simple. No soldering required! Simply connect the alligator clips on the NeoPixel strip to the correct Circuit Playground Bluefruit alligator-clip-friendly pads.

You can optionally include the Adafruit Circuit Playground Enclosure. To use the enclosure with the Remote Control, simply snap it onto a Circuit Playground Bluefruit. If you'd like to use the enclosure with the NeoPixel Animator, snap it onto the CPB before attaching the NeoPixel strip alligator clips to the pads.

Single NeoPixel Strip

To complete this project with one NeoPixel strip, connect the NeoPixel Animator CPB and NeoPixel strip up as follows:

  • Bluefruit GND to NeoPixel GND (black wire/clip)
  • Bluefruit A1 to NeoPixel DIN (white wire/clip)
  • Bluefruit VOUT to NeoPixel 5V (red wire/clip)

Two NeoPixel Strips

You can easily add a second NeoPixel strip to this project. If you connect them both as suggested, there are no changes needed to the code. To complete this project with two NeoPixel strips, connect them up as follows.

The two ground wires can be connected to separate pads as there are multiple GNDs on the Bluefruit. The 5V and NeoPixel data clips need to be connected to the same pins for both strips. After experimentation, the most stable connection is achieved when clipping the alligator clips on the second strip to the alligator clips on the first.

  • Bluefruit GND to NeoPixel strip 1 GND (black wire/clip)
  • Bluefruit A1 to NeoPixel strip 1 DIN (white wire/clip)
  • Bluefruit VOUT to NeoPixel strip 1 5V (red wire/clip)
  • Bluefruit GND to NeoPixel strip 2 GND (black wire/clip)
  • NeoPixel strip 2 DIN (white wire/clip) to NeoPixel strip 1 DIN (white wire/clip)
  • NeoPixel strip 2 5V (red wire/clip) to NeoPixel strip 1 5V (red wire/clip)

The first image shows the 5V red alligator clip on the second strip clipped to the 5V alligator clip on the first strip, not attached to the Circuit Playground Bluefruit. Slide the cover back on the clip on the first strip, and attach the second strip by clipping onto the first clip below the teeth.

The second and third images show both strips attached to the Circuit Playground Bluefruit.

Now you can decorate anything with it, including a wreath, tree or garland! We'll cover the craft aspect of this project further at the end of the guide.

Now that everything is connected, it's time to move on to the code!

This project uses two Circuit Playground Bluefruit boards. One acts as the Remote Control, the other as the NeoPixel Animator. There are two separate pieces of code used in this project: one for the remote control, and one for the NeoPixel animator. You'll load each piece of code onto a separate Circuit Playground Bluefruit.

This project requires the latest version of CircuitPython! Be sure to follow the instructions found on the CircuitPython on Circuit Playground Bluefruit page to download the latest version before continuing.

For this code to work, you'll need to install the necessary libraries first. Follow the instructions on the Circuit Playground Bluefruit CircuitPython Libraries page, and then continue with the instructions in the next section.

Installing CircuitPython LED Animation Library

In addition to the libraries listed on the Circuit Playground Bluefruit CircuitPython Libraries page, this project requires one more library to work. Follow the same instructions found on the Libraries page, and include the Adafruit CircuitPython LED Animation library as well.

To install the Adafruit CircuitPython LED Animation library, drag the following folder out of the CircuitPython library bundle lib folder to the lib folder on your CIRCUITPY drive:

  • adafruit_led_animation

Before continuing, ensure you have ALL of the files and folders mentioned on the Circuit Playground Bluefruit CircuitPython Libraries page AND the adafruit_led_animation folder in the lib folder on your CIRCUITPY drive.

CircuitPython Code

Download the following file and save it as code.py to the Circuit Playground Bluefruit you'll be using as the Remote Control:

"""
Remote Control code for Circuit Playground Bluefruit NeoPixel Animation and Color Remote Control.
To be used with another Circuit Playground Bluefruit running the NeoPixel Animator code.
"""

import time

from adafruit_circuitplayground.bluefruit import cpb

from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble.services.nordic import UARTService
from adafruit_bluefruit_connect.color_packet import ColorPacket
from adafruit_bluefruit_connect.button_packet import ButtonPacket


def scale(value):
    """Scale a value from acceleration value range to 0-255 (RGB range)"""
    value = abs(value)
    value = max(min(19.6, value), 0)
    return int(value / 19.6 * 255)


def send_packet(uart_connection_name, packet):
    """Returns False if no longer connected."""
    try:
        uart_connection_name[UARTService].write(packet.to_bytes())
    except:  # pylint: disable=bare-except
        try:
            uart_connection_name.disconnect()
        except:  # pylint: disable=bare-except
            pass
        return False
    return True


ble = BLERadio()

# Setup for preventing repeated button presses and tracking switch state
button_a_pressed = False
button_b_pressed = False
last_switch_state = None

uart_connection = None
# See if any existing connections are providing UARTService.
if ble.connected:
    for connection in ble.connections:
        if UARTService in connection:
            uart_connection = connection
        break

while True:
    last_switch_state = None
    if not uart_connection or not uart_connection.connected:  # If not connected...
        print("Scanning...")
        for adv in ble.start_scan(ProvideServicesAdvertisement, timeout=5):  # Scan...
            if UARTService in adv.services:  # If UARTService found...
                print("Found a UARTService advertisement.")
                uart_connection = ble.connect(adv)  # Create a UART connection...
                break
        # Stop scanning whether or not we are connected.
        ble.stop_scan()  # And stop scanning.
    while uart_connection and uart_connection.connected:  # If connected...
        if cpb.button_a and not button_a_pressed:  # If button A pressed...
            print("Button A pressed.")
            # Send a LEFT button packet.
            if not send_packet(uart_connection,
                               ButtonPacket(ButtonPacket.LEFT, pressed=True)):
                uart_connection = None
                continue
            button_a_pressed = True  # Set to True.
            time.sleep(0.05)  # Debounce.
        if not cpb.button_a and button_a_pressed:  # On button release...
            button_a_pressed = False  # Set to False.
            time.sleep(0.05)  # Debounce.
        if cpb.button_b and not button_b_pressed:  # If button B pressed...
            print("Button B pressed.")
            # Send a RIGHT button packet.
            if not send_packet(uart_connection,
                               ButtonPacket(ButtonPacket.RIGHT, pressed=True)):
                uart_connection = None
                continue
            button_b_pressed = True  # Set to True.
            time.sleep(0.05)  # Debounce.
        if not cpb.button_b and button_b_pressed:  # On button release...
            button_b_pressed = False  # Set to False.
            time.sleep(0.05)  # Debounce.
        if cpb.switch is not last_switch_state:  # If the switch state is changed...
            last_switch_state = cpb.switch  # Set state to current switch state.
            if cpb.switch:
                print("Switch is to the left: LEDs off!")
            else:
                print("Switch is to the right: LEDs on!")
            # Send a BUTTON_1 button packet.
            if not send_packet(uart_connection,
                               ButtonPacket(ButtonPacket.BUTTON_1, pressed=cpb.switch)):
                uart_connection = None
                continue
        if cpb.switch:  # If switch is to the left...
            cpb.pixels.fill((0, 0, 0))  # Turn off the LEDs.
        else:  # Otherwise...
            r, g, b = map(scale, cpb.acceleration)  # Map acceleration values to RGB values...
            color = (r, g, b)  # Set color to current mapped RGB value...
            print("Color:", color)
            cpb.pixels.fill(color)  # Fill Remote Control LEDs with current color...
            if not send_packet(uart_connection, ColorPacket(color)):  # And send a color packet.
                uart_connection = None
                continue
        time.sleep(0.1)  # Delay to prevent sending packets too quickly.

Download the following file and save it as code.py to the Circuit Playground Bluefruit you'll be using as the NeoPixel Animator, i.e. the CPB with the NeoPixel strip(s) connected to it:

"""
NeoPixel Animator code for Circuit Playground Bluefruit NeoPixel Animation and Color Remote Control.
To be used with another Circuit Playground Bluefruit running the Remote Control code.
"""

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 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

# 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

# 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

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

# Create the NeoPixel strip
strip_pixels = neopixel.NeoPixel(board.A1, STRIP_PIXEL_NUMBER, auto_write=False)

# Setup BLE connection
ble = BLERadio()
uart = UARTService()
advertisement = ProvideServicesAdvertisement(uart)

# Setup animations
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, color.MAGENTA, tail_length=CPB_COMET_TAIL_LENGTH,
              bounce=CPB_COMET_BOUNCE),
        Comet(strip_pixels, COMET_SPEED, color.MAGENTA, tail_length=STRIP_COMET_TAIL_LENGTH,
              bounce=STRIP_COMET_BOUNCE)
    ),
    AnimationGroup(
        Sparkle(cpb.pixels, SPARKLE_SPEED, color.PURPLE),
        Sparkle(strip_pixels, SPARKLE_SPEED, color.PURPLE)
    ),
)

animation_color = None
mode = 0
blanked = False

while True:
    ble.start_advertising(advertisement)  # Start advertising.
    was_connected = False
    while not was_connected or ble.connected:
        if not blanked:  # If LED-off signal is not being sent...
            animations.animate()  # Run the animations.
        if ble.connected:  # If BLE is connected...
            was_connected = True
            if uart.in_waiting:  # Check to see if any data is available from the Remote Control.
                try:
                    packet = Packet.from_stream(uart)  # Create the packet object.
                except ValueError:
                    continue
                if isinstance(packet, ColorPacket):  # If the packet is color packet...
                    if mode == 0:  # And mode is 0...
                        animations.color = packet.color  # Update the animation to the color.
                        # Uncomment below to see the color tuple printed to the serial console.
                        # print("Color:", packet.color)
                        animation_color = packet.color  # Keep track of the current color...
                    elif mode == 1:  # Because if mode is 1...
                        animations.color = animation_color  # Freeze the animation color.
                        # Uncomment below to see the color tuple printed to the serial console.
                        # print("Color:", animation_color)
                elif isinstance(packet, ButtonPacket):  # If the packet is a button packet...
                    # Check to see if it's BUTTON_1 (which is being sent by the slide switch)
                    if packet.button == ButtonPacket.BUTTON_1:
                        if packet.pressed:  # If Remote Control switch is to the left...
                            print("Remote Control switch is to the left: LEDs off!")
                        else:  # If the Remote Control switch is to the right...
                            print("Remote Control switch is to the right: LEDs on!")
                        # If the Remote Control switch is moved from right to left...
                        if packet.pressed and not blanked:
                            animations.fill(color.BLACK)  # Turn off the LEDs.
                        blanked = packet.pressed  # Track the state of the slide switch.
                    if packet.pressed:  # If the buttons on the Remote Control are pressed...
                        if packet.button == ButtonPacket.LEFT:  # If button A is pressed...
                            print("A pressed: animation mode changed.")
                            animations.next()  # Change to the next animation.
                        elif packet.button == ButtonPacket.RIGHT:  # If button B is pressed...
                            mode += 1  # Increase the mode by 1.
                            if mode == 1:  # If mode is 1, print the following:
                                print("B pressed: color frozen!")
                            if mode > 1:  # If mode is > 1...
                                mode = 0  # Set mode to 0, and print the following:
                                print("B pressed: color changing!")

The next section shows you how to use the Remote Control Circuit Playground Bluefruit with the NeoPixel Animator Circuit Playground Bluefruit to change color and animations. Check it out!

Once you have the code loaded on both Circuit Playground Bluefruit boards, you should see the NeoPixel Animator start blinking the initial color chosen in setup, which defaults to red. Now you're ready to connect to the Remote Control.

Power up the Remote Control. The little green power LED on the Remote Control CPB will show that it has been powered on. Wait for the Remote Control and the NeoPixel Animator to connect.

Once they have connected, the LEDs will light up on the Remote Control and the color on the NeoPixel Animator will update to match the color on the Remote control.

Try moving the Remote Control around in different orientations to see the NeoPixel color on the Remote Control and the NeoPixel Animator change!

To switch between animations, press button A (the left button). The second animation is the Comet animation.

Press button A again to proceed to the next animation. The third animation is the Sparkle animation. You can continue to change the color, regardless of what animation is running.

Press button A again to return to the Blink animation.

If you find a color you particularly like, you can freeze the NeoPixel Animator LEDs as that color by pressing button B (the right button). While the color is frozen, you can still cycle through the different animations. The NeoPixels on the Remote Control will continue to change color, and when you unfreeze the color, the NeoPixel Animator will update to the color currently on the Remote Control.

To conserve power, you can turn off the LEDs on both the Remote Control and the NeoPixel Animator by moving the slide switch to the left. To turn the LEDs back on, simply move the slide switch back to the right. Note: if you start up the Remote Control with the slide switch to the left, the LEDs on both the Remote Control and the NeoPixel Animator will turn off - if you can't get the LEDs to turn on, make sure the switch on the Remote Control is to the right before trying to troubleshoot anything else.

In the next two sections, we'll take an in-depth look at each example to see how they work - first the remote control code and then the NeoPixel animator code. Let's get started!

This project involves two Circuit Playground Bluefruit boards. The first acts as the Remote Control which sends data to the NeoPixel Animator 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 Remote Control code. Let's take a look!

Library Imports

First we import all the necessary libraries and modules.

import time

from adafruit_circuitplayground.bluefruit import cpb

from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble.services.nordic import UARTService
from adafruit_bluefruit_connect.color_packet import ColorPacket
from adafruit_bluefruit_connect.button_packet import ButtonPacket

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

We import all the necessary BLE modules for connecting to another Bluefruit, and the modules to be able to send color packets and button packets. We'll use the color packets to send color information to and change the color of the LEDs on the NeoPixel Animator CPB, and the button packets to send button presses and slide switch changes to the NeoPixel Animator CPB to change animations or turn off the LEDs.

Helper Functions

Then we have two helper functions: scale() and send_packet().

The first helper scales the acceleration value range provided by the built-in accelerometer to a 0-255 RGB color range. This allows us to use the accelerometer to change the color of the NeoPixels.

def scale(value):
    """Scale a value from acceleration value range to 0-255 (RGB range)"""
    value = abs(value)
    value = max(min(19.6, value), 0)
    return int(value / 19.6 * 255)

The second helper handles sending BLE packets to another Bluefruit over a UART connection. BLE connections can be flaky for a multitude of reasons, and if the connection drops while sending packets, the code will return an error and stop running. Therefore, we have this helper function to do error handling on the packet sending code so your code continues running even if there are connection issues.

def send_packet(uart_connection_name, packet):
    """Returns False if no longer connected."""
    try:
        uart_connection_name[UARTService].write(packet.to_bytes())
    except:  # pylint: disable=bare-except
        try:
            uart_connection_name.disconnect()
        except:  # pylint: disable=bare-except
            pass
        return False
    return True

Bluetooth and Input Setup

We finish setup by creating the BLE object, creating a series of variables to use in preventing multiple button presses being sent and tracking the last switch location, and then check to see if any existing BLE connections are valid for our purposes.

ble = BLERadio()

# Setup for preventing repeated button presses and tracking switch state
button_a_pressed = False
button_b_pressed = False
last_switch_state = None

uart_connection = None
# See if any existing connections are providing UARTService.
if ble.connected:
    for connection in ble.connections:
        if UARTService in connection:
            uart_connection = connection
        break

Scanning for BLE Connections

If the board is not already connected via BLE, the first thing it does is begin scanning for any valid options and connects to the first one it finds.

[...]
    if not uart_connection or not uart_connection.connected:  # If not connected...
        print("Scanning...")
        for adv in ble.start_scan(ProvideServicesAdvertisement, timeout=5):  # Scan...
            if UARTService in adv.services:  # If UARTService found...
                print("Found a UARTService advertisement.")
                uart_connection = ble.connect(adv)  # Create a UART connection...
                break
        # Stop scanning whether or not we are connected.
        ble.stop_scan()  # And stop scanning.

Once connected, it moves on to the rest of the code. While the Circuit Playground Bluefruit is connected, you can do a few things: send button presses from button A and button B, send the location of the slide switch, and use the accelerometer to change and send the color of the NeoPixels.

Button Presses

The code for button A and button B is essentially the same. Let's take a look at button A.

Remember, during setup we set button_a_pressed = False to set the initial state.

First the code checks to see if BOTH button A is pressed and button_a_pressed = False, and then sends a LEFT button packet and sets button_a_pressed = True.

[...]
        if cpb.button_a and not button_a_pressed:  # If button A pressed...
            print("Button A pressed.")
            # Send a LEFT button packet.
            if not send_packet(uart_connection,
                               ButtonPacket(ButtonPacket.LEFT, pressed=True)):
                uart_connection = None
                continue
            button_a_pressed = True  # Set to True.
            time.sleep(0.05)  # Debounce.

Then it checks to see if the button has been released by BOTH checking to see if button a is not currently being pressed and that button_a_pressed = True, and sets button_a_pressed = False again.

[...]
        if not cpb.button_a and button_a_pressed:  # On button release...
            button_a_pressed = False  # Set to False.
            time.sleep(0.05)  # Debounce.

The code for button B is identical except that it sends a RIGHT button packet instead.

This set of code prevents accidentally sending multiple button presses by holding down the buttons. This is important for the NeoPixel Animator code, as the button presses are used to switch animations and freeze the color.

Slide Switch

The slide switch works a little differently than the buttons since it can remain in either state for an extended period of time without physical interaction. So instead of simply tracking the state, we're checking to see when the state changes, since that is the important part.

So, we check to see if the switch state is no longer the last switch state, i.e. it has changed since the last check. If it has changed, we set last_switch_state to the current state. We print to the serial console a message that depends on the switch direction. Each time the switch position changes, we send a BUTTON_1 packet.

[...]
        if cpb.switch is not last_switch_state:  # If the switch state is changed...
            last_switch_state = cpb.switch  # Set state to current switch state.
            if cpb.switch:
                print("Switch is to the left: LEDs off!")
            else:
                print("Switch is to the right: LEDs on!")
            # Send a BUTTON_1 button packet.
            if not send_packet(uart_connection,
                               ButtonPacket(ButtonPacket.BUTTON_1, pressed=cpb.switch)):
                uart_connection = None
                continue

On the Remote Control Circuit Playground Bluefruit, if the switch is to the left, we turn off the NeoPixels.

[...]
        if cpb.switch:  # If switch is to the left...
            cpb.pixels.fill((0, 0, 0))  # Turn off the LEDs.

This is because the switch being to the left also turns off the NeoPixels on the NeoPixel Animator CPB.

Acceleration and NeoPixel Color

Finally, if the slide switch is to the right, we use the accelerometer to set the NeoPixel colors and send that data to the NeoPixel Animator CPB.

First, we set r, g, b to  the acceleration values mapped to RGB values using the scale helper function. We set color = (r, g, b) and fill the Remote Control NeoPixels with the color. Then, we send a color packet with the color to the NeoPixel Animator. As long as the switch is to the right, color packets are being sent.

[...]
            r, g, b = map(scale, cpb.acceleration)  # Map acceleration values to RGB values...
            color = (r, g, b)  # Set color to current mapped RGB value...
            print("Color:", color)
            cpb.pixels.fill(color)  # Fill Remote Control LEDs with current color...
            if not send_packet(uart_connection, ColorPacket(color)):  # And send a color packet.
                uart_connection = None
                continue
        time.sleep(0.1)  # Delay to prevent sending packets too quickly.

We end with a time.sleep(0.1) to prevent packets from sending too quickly and being received as corrupted.

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

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!

It's that time of year when snow is falling (here, anyway!) and friends are calling. It's the holiday season! So, we've gone to our local craft store and purchased a rather plain wreath with plans to spruce it up!

For this project, you'll need:

  • A wreath, or anything you'd like to decorate!
  • 2 x Circuit Playground Bluefruits
  • 2 x Circuit Playground Enclosures (optional)
  • 1 (or 2) NeoPixel strip(s) with alligator clips
  • 420mAh lithium ion battery
  • 6600mAh lithium ion battery
  • A handful of zip ties, including one long enough to fit around the 6600mAh battery
  • A strip of double sided tape

Gather everything together, and let's get started!

Assembling the Wreath

Place the Circuit Playground Bluefruit boards in their enclosures.

If you're using one NeoPixel strip, connect it as shown in the first image and described in the Single NeoPixel Strip section of the Assembly page.

If you're using two NeoPixel strips, connect them as shown in the second image and described in the Two NeoPixel Strips section of the Assembly page.

Lay out the wreath and flatten the branches a bit to make a place for the strips to lie. The way they are designed, the strips will not want to lay flat on their own, so we'll need to zip tie them in place. This also makes the project more permanent. Arrange the strips on the wreath.

Starting at the top, begin to place zip ties along the strips, holding the strips flat as you tighten the zip tie. Line the zip ties up so they sit between NeoPixels - we don't want to lose any light if we can avoid it!

Continue to zip tie the strips in place until you've secured them all the way around the wreath.

Slip a zip tie through the slots on the back of the Circuit Playground Enclosure of the NeoPixel Animator Circuit Playground Bluefruit and secure it to the wreath. You may need to arrange the alligator clips around the strips to settle the NeoPixel Animator CPB as flat as possible.

Now it's time to attach the 6600mAh battery to the wreath. Make sure the zip tie you're using will fit around the battery. If it doesn't, you can connect two zip ties together to make one longer one.

Flip the wreath over and slip a zip tie through the front behind the NeoPixel Animator Circuit Playground Bluefruit.

Place the battery so it's centered behind the foliage, and zip tie it down. Tuck the wire around to the front where it will be plugged into the NeoPixel Animator CPB.

The battery may move around some as it's only attached at one point, but there were no issues on our build. As long as the zip tie is tight, it's not going anywhere.

Assembling the Remote Control

Use the double-sided tape to attach the 420mAh to the back of the Remote Control.

Stick the tape to the battery, and then stick the battery on the back of the Circuit Playground Enclosure on the Remote Control Circuit Playground Bluefruit.

Plug in both batteries, and you're ready to go!

As a final touch, you can arrange the branches of the wreath to cover the LED strips for a more subtle look. Or leave them uncovered, whatever you like best!

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