CircuitPython Code Walkthrough

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:

Download: file
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.

Download: file
# 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.

Download: file
#  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.

Download: file
#  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.

Download: file
#  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. 

Download: file
#  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.

Download: file
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.

Download: file
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.

Download: file
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.

Download: file
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()
This guide was first published on Dec 25, 2019. It was last updated on Dec 25, 2019.
This page (CircuitPython Code Walkthrough) was last updated on Apr 23, 2020.