It's never a bad time to make a NeoPixel project, but the holiday season certainly does lend itself to creating large-scale, blinky, colorful displays. In this project, we're taking the classic Christmas tree shape, cutting it out of wood and mounting some NeoPixels in the tree to create a festive yuletide light display.

The whole thing is controlled by a Circuit Playground Bluefruit board, which sits proudly on top of the tree in a 3D printed star with all ten of its NeoPixels shining brightly and swirling along with the additional pixels on the tree.

Project Video

Supplies

To create this project, you'll need:

Electronics

shot of a Black woman's neon-green manicured hand holding up a Circuit Playground Bluefruit glowing rainbow LEDs.
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
Glowing NeoPixel Mini Button PCB wired up to a microcontroller
These are the smallest NeoPixel breakouts around! Tiny, bright RGB pixels to your project. These little PCBs are only 8mm x 10mm and have two sets of three pads on the back for...
$4.95
In Stock
Silicone Cover Stranded-Core Wire - 30AWG in Various Colors laid out beside each other.
Silicone-sheathing wire is super-flexible and soft, and its also strong! Able to handle up to 200°C and up to 600V, it will do when PVC covered wire wimps out. We like this wire...
Out of Stock

3D Printing

Filament for 3D printers in various colors and types stacked together.
Having a 3D printer without filament is sort of like having a regular printer without paper or ink.  And while a lot of printers come with some filament there's a good chance...
Out of Stock

Other Supplies and Tools

1 x 2 x 4 Plywood
2 x 4 piece of plywood
12 x Plastic Ornament Baubles
Plastic DIY ornaments
1 x Shellac
Shellac to seal the wood
1 x Foam brush
Brush to apply shellac
2 x Sand paper
120 and 220 grit
1 x Wood filler (optional)
Wood filler in case any issues after drilling
1 x Jigsaw
Jigsaw to cut the wood
1 x Drill
Drill for holes
1 x 1 inch Hole Saw
1 in. hole saw for the bauble holes

Don't feel confined by these supplies though for the tree body and NeoPixel mounting options. You could use fabric, cardboard, foam, etc. to create your own tree shape.

I first want to start off with a disclaimer: this was my first woodworking project. As a result, it might be a great first woodworking project for you too! All we're doing is cutting the tree shape out of the wood and then doing some basic finishing. Again though, as stated on the previous page, if you really don't want to use wood you can use basically any other material (3D printing, cardboard, foam, etc.) to create your tree shape. The sky is the limit.

However, if you're going to journey into the fine maker art of woodworking, here are the steps that I followed.

Cutting the Shape

First, you'll want to trace your tree pattern onto a piece of paper that will then be taped to your piece of wood. This way, if anything happens to the paper while you're cutting the wood you'll still have your original.

Then, take your pattern and tape it to the top of your piece of wood. You'll want to tape your pattern in the corner of your wood piece to make cutting easier.

A 2'x4' piece of plywood is a good budget option and will ensure that you have enough space to fit your design. It also means you can make a mistake and cut out another one, which for a first woodworking project is definitely a possibly.

The tool I used to cut out my shape is a jigsaw, which is a handheld tool shaped similarly to an iron. You guide the blade, that travels up and down to make a cut. Following the cuts is similar to following lines when you're sewing with a sewing machine, if you've ever done that. Some other tool options could be a router, scroll saw or CNC. If you're more comfortable with any of those tools, feel free to try them out. As a beginner, I was very comfortable using a jigsaw.

This tree shape is made out of straight lines so we can use each line as a single cut to keep things simple. Always use at least two clamps to secure your piece of wood to your work surface. If you find that your wood is bouncing when you try to cut it that means it isn't supported enough. Stop sawing and move the wood further onto your work surface and re-clamp.

Before you know it you'll have a lovely wooden tree, which is a little ironic if you think about it.

NeoPixel Layout

The NeoPixels are going to be mounted inside some clear plastic ornament baubles. You find these around various craft and discount stores during the holiday season. They come in a variety of sizes so once you've chosen your baubles, I recommend placing them on your tree to get an idea of the layout that you're going for.

Once you like how things are spaced out, use a pencil and trace around your baubles to mark where your holes will be drilled on your tree.

Drilling, Sanding and Shellac

The baubles that I'm using have a 1 inch outer diameter, so I drilled a 1 inch hole. Your diameter may vary, so be sure to measure. For the actual drilling, I use a handheld power drill. You could also use a drill press if you have access to one. I used a general 1 inch (2.5 cm) drill bit, but if I had to do it again I would use a 1 inch hole saw. I just didn't have that size hole saw available. Hole saws tend to make cleaner cuts and you have a little more control when using a handheld drill.

Another important note is to clamp a piece of scrap wood under your project and clamp both down together. This allows you to drill through to the scrap piece and avoid tear-out in the back of your project piece. I did not do this when I drilled my holes and did suffer from some tear out in the back.

All is not lost though if you do get tear out. I used a rasp, which is basically a wood file, to file down the more jagged pieces in the holes. I used some wood glue to glue down the larger pieces that were still attached and then applied wood filler to the places that had the most damage. After everything was dry and setup, I sanded the back with 120 and then 220 grit sandpaper with a palm sander. You could also do it by hand too or with a sanding block. This really smoothed everything out.

The front

The back, pre-wood filler and post-wood filler

Even if you don't have tear out, you'll want to sand the back of your piece starting with 120 grit and then 220 grit sandpaper. Then you can move to the front, again beginning with 120 grit and then 220 grit. For our purposes that should be smooth enough.

This brings us to the finishing phase. There are countless options for finishing wood but a great, simple option is shellac. It's easy to apply and dries quickly. You'll want to follow the directions on your can's packaging, but in general you should clean your project by vacuuming it and wiping it down with a dry lint-free cloth to make sure that there isn't any dust or dirt. Then, apply the shellac with a foam brush in smooth strokes going the entire length of your piece. By doing this, the finish will remain even and dry quickly. Once it's dry, sand it again with 220 grit sandpaper and apply a second coat. You can repeat this process as many times as you want. For mine, I did two coats which achieved the color I was looking for in the wood.

This truly a multi-material project. There are two pieces that are 3D printed: the star topper that also doubles as our Circuit Playground Bluefruit case, and some mounts that hold our baubles in place in the drilled holes and allow us to mount the NeoPixels.

Star Topper

The star topper is actually a remix of the Circuit Playground case by the Ruiz brothers. I simply took the circular case and added the star points and a mount at the bottom. I also added cutouts in the back to access the pins on the Circuit Playground for soldering. You can download this remix on Thingiverse.

NeoPixel Bauble Mount

The mount is designed to serve two purposes: to hold the bauble in the hole and hold the NeoPixel button PCB. Both of these are done by force fitting in place. The mount actually fits inside the bauble's hole to press against the back of the tree to kind of sandwich the bauble in place. The NeoPixel button is then suspended snugly inside the mount so that it's centered nicely in the bauble. The Fusion360 file is available for the mount so that you can edit it to fit with your bauble or other mounting option. The .STL can also be downloaded on Thingiverse.

The electronics for this project are fairly straight forward. The whole thing is powered by the Circuit Playground Bluefruit board and then we'll be daisy chaining 12 NeoPixel buttons to the Circuit Playground. The NeoPixel buttons are a really great way to freely place your NeoPixels throughout your project while keeping everything really compact. The PCB features the surface mount pixels that you've come to know and love on the NeoPixel strips along with a resistor and capacitor on top, which means that you don't have to build out that circuit in your wiring. On the back there are six pads: 5V in, GND in, DATA in, 5V out, GND out and DATA out. This makes soldering really straight forward. And speaking of soldering…

Soldering the NeoPixel Buttons

When you have a lot of small components to solder together across a large area, it can be a little intimidating, but fear not! I have a strategy for getting all these pixelated NeoPixels connected up and rainbow swirling in no time.

Take the 3D printed mounts and mount all of the NeoPixel buttons into the inner NeoPixel slot so that the back of the button is flush with the back of the mount.

Then, take your plastic bauble and place them in the drilled holes in the tree, mounting them in place with the 3D printed mounts. Your NeoPixels should all be facing out into the baubles.

This is a great opportunity to figure out the order of your NeoPixels and number them with some faint pencil right on the back of tree. This will help with wiring and coding.

Now we can get into the wiring. Take your wire spool and pull out wire between the first two NeoPixels to figure out the correct length between the outputs from the first NeoPixel and inputs on the second NeoPixel. I recommend working with one signal at a time. Continue this for the remaining NeoPixels and once you have all the wire cut for one signal, strip the ends of each piece so that they'll be ready for tinning and soldering.

On the NeoPixel buttons, tin the pads for the signal you're working with. Then, starting either at the beginning or end of your NeoPixel button chain, tin your first piece of wire and solder the ends to the tinned pads on the NeoPixel button. Continue this process for the entire signal flow and then repeat for the two remaining signals, paying close attention to your NeoPixel order; especially with the DATA signal.

Wiring up the Circuit Playground Bluefruit

We'll be using three pads on the Circuit Playground Bluefruit: GND, VOUT and A1, to connect to our NeoPixels. VOUT is the 5V power output, which the NeoPixels need and A1 will be data out. All three of these pads are on the same side of the Circuit Playground so it will make wire management a little easier. First tin the pads on the Circuit Playground and then cut three pieces of wire long enough to reach the first NeoPixel at the top of the tree when the Circuit Playground is mounted in the star at the top. Strip and tin those pieces of wire and then solder them to the Circuit Playground pads.

Next, take your 3D printed star case and mount the Circuit Playground Bluefruit into the case, running the three wires through the back cutout. Then attach the star to the top of the tree. Tin the input pads on the first NeoPixel and then solder the three wires from the Circuit Playground Bluefruit to the input pads on the NeoPixel.

You may want to take this opportunity to check the continuity of your circuit with a multimeter to make sure you don't have any loose or missed connections.

One final touch for the Circuit Playground Bluefruit is to add a power switch and a power input extension via a JST connector; the same size that fits into the battery input on the CircuitPlayground. Take a slide switch one pin to the power wire on the JST input connector and a second pin to the power wire on the JST output connector. Use one wire to connect GND between the two JST connectors.

With the Circuit Playground Bluefruit outside of the star case, slot the slide switch into the cutout in the back. Connect the JST output in our power circuit to the JST input on the Circuit Playground Bluefruit. This allows us to comfortably plug-in a LiPo battery that can sit behind the Circuit Playground inside the case or run the wiring out the back to receive power in a different way. Most importantly though, we have a way to turn the whole tree on or off via a hardware switch.

We've sawed, drilled, 3D printed and soldered in this project so far, and now we're going to do some typing. The code is of course written in CircuitPython and uses the Adafruit Bluefruit app to switch between different color animations. For maximum animation smoothness, we'll be using the FancyLED library. It allows for more control over swirling animations which are ideal for this project.

Setting Up CircuitPython

First, you'll want to install the latest version of CircuitPython on your Circuit Playground Bluefruit

CircuitPython Libraries

We'll need some libraries for our CircuitPython code. Be sure to download the library bundle that matches your version of CircuitPython on your Circuit Playground Bluefruit.

Once your Circuit Playground Bluefruit is connected to your computer, access its file system and create a new folder called lib. From the Library bundle that you just downloaded, drag and drop the following libraries:

  • adafruit_ble
  • adafruit_bluefruit_connect
  • adafruit_bus_device
  • adafruit_circuitplayground
  • adafruit_fancyled
  • adafruit_bluefruitspi.mpy
  • neopixel.mpy

Upload the Code

Now we can copy the code below into a file called code.py and drag and drop it onto our CIRCUITPY drive. Afterwards you should be able to connect to the Circuit Playground Bluefruit via the Adafruit Bluefruit app and control the various NeoPixel animations.

# SPDX-FileCopyrightText: 2019 Liz Clark for Adafruit Industries
#
# SPDX-License-Identifier: MIT

import random
import board
import neopixel
import adafruit_fancyled.adafruit_fancyled as fancy
from adafruit_bluefruit_connect.packet import Packet
from adafruit_bluefruit_connect.button_packet import ButtonPacket
from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble.services.nordic import UARTService

# setting up # of neopixels
TREE_LEDS = 12
CPX_LEDS = 10
#  setting up pins for neopixels
TREE_PIN = board.A1
CPX_PIN = board.D8

#  neopixel setup
tree = neopixel.NeoPixel(TREE_PIN, TREE_LEDS, brightness=0.5, auto_write=False)
cpx = neopixel.NeoPixel(CPX_PIN, CPX_LEDS, brightness=0.1, auto_write=False)

#  BLE setup
ble = BLERadio()
uart = UARTService()
advertisement = ProvideServicesAdvertisement(uart)
advertising = False

#  to turn neopixels off
OFF = (0, 0, 0)

#  fancyLED color palettes

fairy_palette = [fancy.CRGB(1.0, 0.0, 0.0),
                 fancy.CRGB(1.0, 0.5, 0.0),
                 fancy.CRGB(0.0, 0.5, 0.0),
                 fancy.CRGB(0.0, 1.0, 1.0),
                 fancy.CRGB(0.0, 0.0, 1.0),
                 fancy.CRGB(0.75, 0.0, 1.0)]

merry_palette = [fancy.CRGB(1.0, 0.0, 0.0),
                 fancy.CRGB(0.0, 1.0, 0.0)]

winter_palette = [fancy.CRGB(0.0, 0.75, 0.0),
                  fancy.CRGB(0.0, 1.0, 1.0),
                  fancy.CRGB(0.75, 0.0, 1.0),
                  fancy.CRGB(1.0, 1.0, 1.0),
                  fancy.CRGB(0.0, 0.75, 0.0),
                  fancy.CRGB(0.75, 0.0, 1.0),
                  fancy.CRGB(0.0, 0.0, 1.0),
                  fancy.CRGB(0.0, 1.0, 1.0),
                  fancy.CRGB(1.0, 0.0, 1.0)]

star_palette = [fancy.CRGB(1.0, 0.75, 0.0),
                fancy.CRGB(1.0, 1.0, 1.0),
                fancy.CRGB(1.0, 0.75, 0.0),
                fancy.CRGB(0.75, 0.75, 0.75),
                fancy.CRGB(1.0, 0.75, 0.0)]

hanukkah_palette = [fancy.CRGB(0.0, 1.0, 1.0),
                    fancy.CRGB(0.0, 0.0, 1.0),
                    fancy.CRGB(1.0, 0.75, 0.0),
                    fancy.CRGB(0.0, 0.0, 1.0),
                    fancy.CRGB(1.0, 1.0, 1.0)]

#  default offset value
offset = 0

def gimel():
    for i in range(TREE_LEDS):
        color = fancy.palette_lookup(hanukkah_palette, (offset - i) / 5)
        color = fancy.gamma_adjust(color, brightness=0.3)
        tree[i] = color.pack()
    tree.show()

    for i in range(CPX_LEDS):
        color = fancy.palette_lookup(hanukkah_palette, (offset - i) / 3)
        color = fancy.gamma_adjust(color, brightness=0.3)
        cpx[i] = color.pack()
    cpx.show()

#  neopixel animations

def jazzy():
    for i in range(TREE_LEDS):
        color = fancy.palette_lookup(fairy_palette, (offset - i) / 4.8)
        color = fancy.gamma_adjust(color, brightness=0.3)
        tree[i] = color.pack()
    tree.show()

    for i in range(CPX_LEDS):
        color = fancy.palette_lookup(fairy_palette, (offset + i) / 4)
        color = fancy.gamma_adjust(color, brightness=0.3)
        cpx[i] = color.pack()
    cpx.show()

def latkes():
    for i in range(TREE_LEDS):
        color = fancy.palette_lookup(hanukkah_palette, (offset - 24) / TREE_LEDS)
        color = fancy.gamma_adjust(color, brightness=0.3)
        tree[i] = color.pack()
    tree.show()

    for i in range(CPX_LEDS):
        color = fancy.palette_lookup(hanukkah_palette, (offset - 20) / CPX_LEDS)
        color = fancy.gamma_adjust(color, brightness=0.3)
        cpx[i] = color.pack()
    cpx.show()

def twinkle():
    for i in range(60):
        color = fancy.palette_lookup(fairy_palette, offset + i / CPX_LEDS)
        color = fancy.gamma_adjust(color, brightness=0.25)
        p = random.randint(0, (CPX_LEDS - 1))
        cpx[p] = color.pack()
    cpx.show()

    for i in range(60):
        color = fancy.palette_lookup(fairy_palette, offset + i / TREE_LEDS)
        color = fancy.gamma_adjust(color, brightness=0.25)
        p = random.randint(0, (TREE_LEDS - 1))
        tree[p] = color.pack()
    tree.show()

def merry():
    for i in range(TREE_LEDS):
        color = fancy.palette_lookup(merry_palette, (offset + i) / (TREE_LEDS / 2))
        color = fancy.gamma_adjust(color, brightness=0.25)
        tree[i] = color.pack()
    tree.show()

    for i in range(60):
        color = fancy.palette_lookup(star_palette, (offset + i) / CPX_LEDS)
        color = fancy.gamma_adjust(color, brightness=0.25)
        p = random.randint(0, (CPX_LEDS - 1))
        cpx[p] = color.pack()
    cpx.show()

def festive():
    for i in range(TREE_LEDS):
        color = fancy.palette_lookup(merry_palette, (offset - i) / 2)
        color = fancy.gamma_adjust(color, brightness=0.25)
        tree[i] = color.pack()
    tree.show()

    for i in range(CPX_LEDS):
        color = fancy.palette_lookup(star_palette, (offset + i) / CPX_LEDS)
        color = fancy.gamma_adjust(color, brightness=0.25)
        cpx[i] = color.pack()
    cpx.show()

def fancy_swirl():
    for i in range(TREE_LEDS):
        color = fancy.palette_lookup(winter_palette, (offset + i) / TREE_LEDS)
        color = fancy.gamma_adjust(color, brightness=0.25)
        tree[i] = color.pack()
    tree.show()

    for i in range(CPX_LEDS):
        color = fancy.palette_lookup(star_palette, (offset - i) / CPX_LEDS)
        color = fancy.gamma_adjust(color, brightness=0.25)
        cpx[i] = color.pack()
    cpx.show()

#  states for different neopixel displays
fairies = False
feeling_fancy = False
feeling_festive = False
feeling_jazzy = False
feeling_merry = False
frying_latkes = False
rolling_gimel = False

while True:
    #  states to trigger the different neopixel modes
    if fairies:
        twinkle()
        offset += 0.5
    if feeling_fancy:
        fancy_swirl()
        offset += 0.05
    if feeling_festive:
        festive()
        offset += 0.05
    if feeling_jazzy:
        jazzy()
        offset += 0.08
    if feeling_merry:
        merry()
        offset += 0.12
    if frying_latkes:
        latkes()
        offset += 0.05
    if rolling_gimel:
        gimel()
        offset += 0.05

    if not ble.connected and not advertising:
        #  not connected in the app yet
        ble.start_advertising(advertisement)
        advertising = True

    if ble.connected:
        # after connected via app
        advertising = False
        if uart.in_waiting:
            #  waiting for input from app
            packet = Packet.from_stream(uart)
            if isinstance(packet, ButtonPacket):
                #  if buttons in the app are pressed
                if packet.pressed:
                    #  fairies
                    if packet.button == ButtonPacket.UP:
                        fairies = True
                        feeling_fancy = False
                        feeling_festive = False
                        feeling_jazzy = False
                        feeling_merry = False
                        frying_latkes = False
                        rolling_gimel = False
                    #  fancy
                    if packet.button == ButtonPacket.LEFT:
                        fairies = False
                        feeling_fancy = True
                        feeling_festive = False
                        feeling_jazzy = False
                        feeling_merry = False
                        frying_latkes = False
                        rolling_gimel = False
                    #  festive
                    if packet.button == ButtonPacket.RIGHT:
                        fairies = False
                        feeling_fancy = False
                        feeling_festive = True
                        feeling_jazzy = False
                        feeling_merry = False
                        frying_latkes = False
                        rolling_gimel = False
                    #  jazzy
                    if packet.button == ButtonPacket.DOWN:
                        fairies = False
                        feeling_fancy = False
                        feeling_festive = False
                        feeling_jazzy = True
                        feeling_merry = False
                        frying_latkes = False
                        rolling_gimel = False
                    #  merry
                    if packet.button == ButtonPacket.BUTTON_1:
                        fairies = False
                        feeling_fancy = False
                        feeling_festive = False
                        feeling_jazzy = False
                        feeling_merry = True
                        frying_latkes = False
                        rolling_gimel = False
                    #  latkes
                    if packet.button == ButtonPacket.BUTTON_2:
                        fairies = False
                        feeling_fancy = False
                        feeling_festive = False
                        feeling_jazzy = False
                        feeling_merry = False
                        frying_latkes = True
                        rolling_gimel = False
                    #  gimel
                    if packet.button == ButtonPacket.BUTTON_3:
                        fairies = False
                        feeling_fancy = False
                        feeling_festive = False
                        feeling_jazzy = False
                        feeling_merry = False
                        frying_latkes = False
                        rolling_gimel = True
                    #  off
                    if packet.button == ButtonPacket.BUTTON_4:
                        fairies = False
                        feeling_fancy = False
                        feeling_festive = False
                        feeling_jazzy = False
                        feeling_merry = False
                        frying_latkes = False
                        rolling_gimel = False
                        cpx.fill(OFF)
                        tree.fill(OFF)
                        tree.show()
                        cpx.show()

With the help of the Bluefruit and FancyLED libraries, it's fairly simple to get some elegant LED animations up and running with BLE control. Let's take a closer look at how this is all happening in the code.

First, we import our libraries:

import random
import board
import neopixel
import adafruit_fancyled.adafruit_fancyled as fancy
from adafruit_bluefruit_connect.packet import Packet
from adafruit_bluefruit_connect.button_packet import ButtonPacket
from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble.services.nordic import UARTService

Then we setup our NeoPixels. We're keeping the tree's NeoPixels and Circuit Playground Bluefruit Neopixels separate so that we'll be able to run different animations on each of them at the same time.

# setting up # of neopixels
TREE_LEDS = 12
CPX_LEDS = 10
#  setting up pins for neopixels
TREE_PIN = board.A1
CPX_PIN = board.D8

#  neopixel setup
tree = neopixel.NeoPixel(TREE_PIN, TREE_LEDS, brightness=0.5, auto_write=False)
cpx = neopixel.NeoPixel(CPX_PIN, CPX_LEDS, brightness=0.1, auto_write=False)

Next there's some BLE setup.

#  BLE setup
ble = BLERadio()
uart = UARTService()
advertisement = ProvideServicesAdvertisement(uart)
advertising = False

Now we can get into some fun FancyLED setup. FancyLED uses color palettes that can be cycled through during the different animations. These palettes are then called later in the code when the animations are created.

#  to turn neopixels off
OFF = (0, 0, 0)

#  fancyLED color palettes

fairy_palette = [fancy.CRGB(1.0, 0.0, 0.0),
                 fancy.CRGB(1.0, 0.5, 0.0),
                 fancy.CRGB(0.0, 0.5, 0.0),
                 fancy.CRGB(0.0, 1.0, 1.0),
                 fancy.CRGB(0.0, 0.0, 1.0),
                 fancy.CRGB(0.75, 0.0, 1.0)]

merry_palette = [fancy.CRGB(1.0, 0.0, 0.0),
                 fancy.CRGB(0.0, 1.0, 0.0)]

winter_palette = [fancy.CRGB(0.0, 0.75, 0.0),
                  fancy.CRGB(0.0, 1.0, 1.0),
                  fancy.CRGB(0.75, 0.0, 1.0),
                  fancy.CRGB(1.0, 1.0, 1.0),
                  fancy.CRGB(0.0, 0.75, 0.0),
                  fancy.CRGB(0.75, 0.0, 1.0),
                  fancy.CRGB(0.0, 0.0, 1.0),
                  fancy.CRGB(0.0, 1.0, 1.0),
                  fancy.CRGB(1.0, 0.0, 1.0)]

star_palette = [fancy.CRGB(1.0, 0.75, 0.0),
                fancy.CRGB(1.0, 1.0, 1.0),
                fancy.CRGB(1.0, 0.75, 0.0),
                fancy.CRGB(0.75, 0.75, 0.75),
                fancy.CRGB(1.0, 0.75, 0.0)]

hanukkah_palette = [fancy.CRGB(0.0, 1.0, 1.0),
                    fancy.CRGB(0.0, 0.0, 1.0),
                    fancy.CRGB(1.0, 0.75, 0.0),
                    fancy.CRGB(0.0, 0.0, 1.0),
                    fancy.CRGB(1.0, 1.0, 1.0)]

And now for the animations. The tree's NeoPixels and the Circuit Playground Bluefruit NeoPixels are animated separately in a few cases, either to have different effects and/or to show different colors. For example, with the merry() animation, the tree will show red and green fading in and out but the Circuit Playground will have yellow and white swirling around kind of like a traditional Christmas tree.

#  default offset value
offset = 0

def gimel():
    for i in range(TREE_LEDS):
        color = fancy.palette_lookup(hanukkah_palette, (offset - i) / 5)
        color = fancy.gamma_adjust(color, brightness=0.3)
        tree[i] = color.pack()
    tree.show()

    for i in range(CPX_LEDS):
        color = fancy.palette_lookup(hanukkah_palette, (offset - i) / 3)
        color = fancy.gamma_adjust(color, brightness=0.3)
        cpx[i] = color.pack()
    cpx.show()

#  neopixel animations

def jazzy():
    for i in range(TREE_LEDS):
        color = fancy.palette_lookup(fairy_palette, (offset - i) / 4.8)
        color = fancy.gamma_adjust(color, brightness=0.3)
        tree[i] = color.pack()
    tree.show()

    for i in range(CPX_LEDS):
        color = fancy.palette_lookup(fairy_palette, (offset + i) / 4)
        color = fancy.gamma_adjust(color, brightness=0.3)
        cpx[i] = color.pack()
    cpx.show()

def latkes():
    for i in range(TREE_LEDS):
        color = fancy.palette_lookup(hanukkah_palette, (offset - 24) / TREE_LEDS)
        color = fancy.gamma_adjust(color, brightness=0.3)
        tree[i] = color.pack()
    tree.show()

    for i in range(CPX_LEDS):
        color = fancy.palette_lookup(hanukkah_palette, (offset - 20) / CPX_LEDS)
        color = fancy.gamma_adjust(color, brightness=0.3)
        cpx[i] = color.pack()
    cpx.show()

def twinkle():
    for i in range(60):
        color = fancy.palette_lookup(fairy_palette, offset + i / CPX_LEDS)
        color = fancy.gamma_adjust(color, brightness=0.25)
        p = random.randint(0, (CPX_LEDS - 1))
        cpx[p] = color.pack()
    cpx.show()

    for i in range(60):
        color = fancy.palette_lookup(fairy_palette, offset + i / TREE_LEDS)
        color = fancy.gamma_adjust(color, brightness=0.25)
        p = random.randint(0, (TREE_LEDS - 1))
        tree[p] = color.pack()
    tree.show()

def merry():
    for i in range(TREE_LEDS):
        color = fancy.palette_lookup(merry_palette, (offset + i) / (TREE_LEDS / 2))
        color = fancy.gamma_adjust(color, brightness=0.25)
        tree[i] = color.pack()
    tree.show()

    for i in range(60):
        color = fancy.palette_lookup(star_palette, (offset + i) / CPX_LEDS)
        color = fancy.gamma_adjust(color, brightness=0.25)
        p = random.randint(0, (CPX_LEDS - 1))
        cpx[p] = color.pack()
    cpx.show()

def festive():
    for i in range(TREE_LEDS):
        color = fancy.palette_lookup(merry_palette, (offset - i) / 2)
        color = fancy.gamma_adjust(color, brightness=0.25)
        tree[i] = color.pack()
    tree.show()

    for i in range(CPX_LEDS):
        color = fancy.palette_lookup(star_palette, (offset + i) / CPX_LEDS)
        color = fancy.gamma_adjust(color, brightness=0.25)
        cpx[i] = color.pack()
    cpx.show()

def fancy_swirl():
    for i in range(TREE_LEDS):
        color = fancy.palette_lookup(winter_palette, (offset + i) / TREE_LEDS)
        color = fancy.gamma_adjust(color, brightness=0.25)
        tree[i] = color.pack()
    tree.show()

    for i in range(CPX_LEDS):
        color = fancy.palette_lookup(star_palette, (offset - i) / CPX_LEDS)
        color = fancy.gamma_adjust(color, brightness=0.25)
        cpx[i] = color.pack()
    cpx.show()

How will we be able to switch between these animations? With state machines! Here are the creation of the different states that will be triggered in the loop to turn the different animations that we just setup on or off. The default state for all of the states (hah) is off. 

#  states for different neopixel displays
fairies = False
feeling_fancy = False
feeling_festive = False
feeling_jazzy = False
feeling_merry = False
frying_latkes = False
rolling_gimel = False

Now for the loop. We begin by defining what will happen when each of our states that we setup are true. I had a bit of fun with naming them to not only correspond with the names of the animations but to also be holiday themed. Each state is tied to an animation and offset value, which affects how the animation will run and varies depending on the effect we're looking for.

while True:
    #  states to trigger the different neopixel modes
    if fairies:
        twinkle()
        offset += 0.5
    if feeling_fancy:
        fancy_swirl()
        offset += 0.05
    if feeling_festive:
        festive()
        offset += 0.05
    if feeling_jazzy:
        jazzy()
        offset += 0.08
    if feeling_merry:
        merry()
        offset += 0.12
    if frying_latkes:
        latkes()
        offset += 0.05
    if rolling_gimel:
        gimel()
        offset += 0.05

After our states, we move into some Bluetooth setup. First, if the app is not connected to our Circuit Playground, then it continues to advertise as a device.

if not ble.connected and not advertising:
        #  not connected in the app yet
        ble.start_advertising(advertisement)
        advertising = True

Then, if the app is connecting, the Circuit Playground Bluefruit stops advertising as a device and it waits for packets, or inputs, from the app.

if ble.connected:
        # after connected via app
        advertising = False
        if uart.in_waiting:
            #  waiting for input from app
            packet = Packet.from_stream(uart)
            if isinstance(packet, ButtonPacket):
                #  if buttons in the app are pressed
                if packet.pressed:

For input, we're using the buttons available in the app. Each of the 8 available buttons affect our tree. The first 7 (UP, LEFT, RIGHT, DOWN, 1, 2 and 3) turn on animations. Each of these button presses cause all of the states except for one to be defined as false so we don't get any crashes. The final button, 4, defines all of the states as false and writes our OFF value, which we defined earlier as (0, 0, 0) to both the tree and Circuit Playground so that all of the NeoPixels are turned off.

if packet.button == ButtonPacket.UP:
  fairies = True
  feeling_fancy = False
  feeling_festive = False
  feeling_jazzy = False
  feeling_merry = False
  frying_latkes = False
  rolling_gimel = False
#  fancy
if packet.button == ButtonPacket.LEFT:
  fairies = False
  feeling_fancy = True
  feeling_festive = False
  feeling_jazzy = False
  feeling_merry = False
  frying_latkes = False
  rolling_gimel = False
#  festive
if packet.button == ButtonPacket.RIGHT:
  fairies = False
  feeling_fancy = False
  feeling_festive = True
  feeling_jazzy = False
  feeling_merry = False
  frying_latkes = False
  rolling_gimel = False
#  jazzy
if packet.button == ButtonPacket.DOWN:
  fairies = False
  feeling_fancy = False
  feeling_festive = False
  feeling_jazzy = True
  feeling_merry = False
  frying_latkes = False
  rolling_gimel = False
#  merry
if packet.button == ButtonPacket.BUTTON_1:
  fairies = False
  feeling_fancy = False
  feeling_festive = False
  feeling_jazzy = False
  feeling_merry = True
  frying_latkes = False
  rolling_gimel = False
#  latkes
if packet.button == ButtonPacket.BUTTON_2:
  fairies = False
  feeling_fancy = False
  feeling_festive = False
  feeling_jazzy = False
  feeling_merry = False
  frying_latkes = True
  rolling_gimel = False
#  gimel
if packet.button == ButtonPacket.BUTTON_3:
  fairies = False
  feeling_fancy = False
  feeling_festive = False
  feeling_jazzy = False
  feeling_merry = False
  frying_latkes = False
  rolling_gimel = True
#  off
if packet.button == ButtonPacket.BUTTON_4:
  fairies = False
  feeling_fancy = False
  feeling_festive = False
  feeling_jazzy = False
  feeling_merry = False
  frying_latkes = False
  rolling_gimel = False
  cpx.fill(OFF)
  tree.fill(OFF)
  tree.show()
  cpx.show()

Once your tree is assembled and your code has been uploaded, it's time to switch between animations. This is done using the Adafruit Bluefruit LE Connect app.

Once you connect to the Circuit Playground Bluefruit, select the "Controller" option; followed by "Control Pad". This then brings you to a screen with 8 buttons. As we went over in the code, the up, left, right, down, 1, 2 and 3 buttons will all trigger different animations and 4 will turn off all of the NeoPixels.

Here are some examples of what the different animations look like.

After pressing "up" (the twinkle() animation).

After pressing "left" (the fancy_swirl() animation).

After pressing "right" (the festive() animation).

After pressing "down" (the jazzy() animation).

After pressing "1" (the merry() animation).

After pressing "2" (the latkes() animation).

And finally, after pressing "3" (the gimel() animation).

This guide was first published on Dec 25, 2019. It was last updated on Mar 29, 2024.