Software

This code uses the Adafruit Bluefruit app's Control Pad and Color Picker features. The Color Picker will send a solid color to the luminary, and the Control Pad will allow you to choose between four different modes. You can also speed up and slow down the animations to get just the look you want using the arrow keys.

For instant gratification, we've also made the four Control Pad modes accessible via capacitive touch "buttons" made from copper tape and stuck to the outside of the luminary. This way you don't have to look around for your phone just to turn out the lights.

Modes

  1. Off
  2. Reading Light (bright solid warm yellow)
  3. Candle Flame
  4. Rainbow Swirl

Your Circuit Playground Bluefruit should ship with the CircuitPython software already installed. At the time of writing this software is fairly new and changing rapidly, so it's still a good idea to update to the latest version of CircuitPython before you start. Here's what we'll do:

  1. Install / Update CircuitPython
  2. Install the necessary CircuitPython libraries
  3. Copy and customize the Python code
  4. Save the code to your board

Ready to start? Here we go!

Install CircuitPython

This guide tells you all you need to know about CircuitPython:

You probably want to follow the instructions to update CircuitPython to the latest version - this project requires version 5.0.0-beta.0 or higher.

Install Libraries

Now we need to install a few libraries onto our board. The libraries contain pre-written code blocks that we can reference to make our code simpler to write. Here's a guide that tells you all you'll ever want to know about installing libraries:

https://learn.adafruit.com/welcome-to-circuitpython/circuitpython-libraries

I'll just hit the highlights again to get you up and running.

Find the bundle that matches the version of CircuitPython you're running. At the time of writing, we're running 5.x so download the 5.x bundle for the Circuit Playground.

Now go to your CIRCUITPY drive and create a new folder called lib. Unzip the Library bundle and find:

  • adafruit_ble
  • adafruit_bluefruit_connect
  • adafruit_fancyled
  • neopixel.mpy

Drag these folders/files into your brand new lib folder.

Upload the Code

The last thing we need to add is a file called code.py on the CIRCUITPY drive. This is where the board will look for actual instructions on what to do. Copy the code below into a text or code editor -- we recommend the Mu editor which can be downloaded here.

""" FancyLED Palette and Color Picker Control with BlueFruit App
    Code by Phil Burgess, Dan Halbert & Erin St Blaine for Adafruit Industries
"""
import board
import neopixel
import touchio
import adafruit_fancyled.adafruit_fancyled as fancy
from adafruit_bluefruit_connect.packet import Packet
from adafruit_bluefruit_connect.button_packet import ButtonPacket
from adafruit_bluefruit_connect.color_packet import ColorPacket


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



NUM_LEDS = 24             # change to reflect your total number of ring LEDs
RING_PIN = board.A1       # change to reflect your wiring
CPX_PIN = board.D8        # CPX Neopixels live on pin D8

touch_A2 = touchio.TouchIn(board.A2)
touch_A3 = touchio.TouchIn(board.A3)
touch_A4 = touchio.TouchIn(board.A4)
touch_A5 = touchio.TouchIn(board.A5)
touch_A6 = touchio.TouchIn(board.A6)
touch_TX = touchio.TouchIn(board.TX)

# Palettes can have any number of elements in various formats
# check https://learn.adafruit.com/fancyled-library-for-circuitpython/colors
# for more info
# Declare a 6-element RGB rainbow palette
PALETTE_RAINBOW = [fancy.CRGB(1.0, 0.0, 0.0),  # Red
                   fancy.CRGB(0.5, 0.3, 0.0),  # Orange
                   fancy.CRGB(0.5, 0.5, 0.0),  # Yellow
                   fancy.CRGB(0.3, 0.7, 0.0),  # Yellow Green
                   fancy.CRGB(0.0, 1.0, 0.0),  # Green
                   fancy.CRGB(0.0, 0.7, 0.3),  # Teal
                   fancy.CRGB(0.0, 0.5, 0.5),  # Cyan
                   fancy.CRGB(0.0, 0.3, 0.7),  # Blue
                   fancy.CRGB(0.0, 0.0, 1.0),  # Blue
                   fancy.CRGB(0.5, 0.0, 0.5),  # Magenta
                   fancy.CRGB(0.7, 0.0, 0.3)]  # Purple

# Reading Lamp mode - Warm Yellow
PALETTE_BRIGHT = [fancy.CRGB(255, 183, 55)]

# Black Only palette for "off" mode
PALETTE_DARK = [fancy.CRGB(0, 0, 0)]

# Declare a FIRE palette
PALETTE_FIRE = [fancy.CRGB(160, 30, 0),  # Reds and Yellows
                fancy.CRGB(27, 65, 0),
                fancy.CRGB(0, 0, 0),
                fancy.CRGB(224, 122, 0),
                fancy.CRGB(0, 0, 0),
                fancy.CRGB(250, 80, 0),
                fancy.CRGB(0, 0, 0),
                fancy.CRGB(0, 0, 0),
                fancy.CRGB(200, 40, 0)]

# Declare a NeoPixel object on NEOPIXEL_PIN with NUM_LEDS pixels,
# no auto-write.
# Set brightness to max because we'll be using FancyLED's brightness control.
ring = neopixel.NeoPixel(RING_PIN, NUM_LEDS, brightness=1.0, auto_write=False)
cpx = neopixel.NeoPixel(CPX_PIN, NUM_LEDS, brightness=1.0, auto_write=False)

offset = 0  # Positional offset into color palette to get it to 'spin'
offset_increment = 6
OFFSET_MAX = 1000000

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


def set_palette(palette):
    for i in range(NUM_LEDS):
        # Load each pixel's color from the palette using an offset, run it
        # through the gamma function, pack RGB value and assign to pixel.
        color = fancy.palette_lookup(palette, (offset + i) / NUM_LEDS)
        color = fancy.gamma_adjust(color, brightness=1.0)
        ring[i] = color.pack()
    ring.show()

    for i in range(NUM_LEDS):
        # Load each pixel's color from the palette using an offset, run it
        # through the gamma function, pack RGB value and assign to pixel.
        color = fancy.palette_lookup(palette, (offset + i) / NUM_LEDS)
        color = fancy.gamma_adjust(color, brightness=1.0)
        cpx[i] = color.pack()
    cpx.show()

# set initial palette to run on startup
palette_choice = PALETTE_FIRE

# True if cycling a palette
cycling = True

# Are we already advertising?
advertising = False


while True:

    if cycling:
        set_palette(palette_choice)
        offset = (offset + offset_increment) % OFFSET_MAX

    if not ble.connected and not advertising:
        ble.start_advertising(advertisement)
        advertising = True

    # Are we connected via Bluetooth now?
    if ble.connected:
        # Once we're connected, we're not advertising any more.
        advertising = False
        # Have we started to receive a packet?
        if uart.in_waiting:
            packet = Packet.from_stream(uart)
            if isinstance(packet, ColorPacket):
                cycling = False
                # Set all the pixels to one color and stay there.
                ring.fill(packet.color)
                cpx.fill(packet.color)
                ring.show()
                cpx.show()
            elif isinstance(packet, ButtonPacket):
                cycling = True
                if packet.pressed:
                    if packet.button == ButtonPacket.BUTTON_1:
                        palette_choice = PALETTE_DARK
                    elif packet.button == ButtonPacket.BUTTON_2:
                        palette_choice = PALETTE_BRIGHT
                    elif packet.button == ButtonPacket.BUTTON_3:
                        palette_choice = PALETTE_FIRE
                        offset_increment = 6
                    elif packet.button == ButtonPacket.BUTTON_4:
                        palette_choice = PALETTE_RAINBOW
                        offset_increment = 1

                # change the speed of the animation by incrementing offset
                    elif packet.button == ButtonPacket.UP:
                        offset_increment += 1
                    elif packet.button == ButtonPacket.DOWN:
                        offset_increment -= 1

    # Whether or not we're connected via Bluetooth,
    # we also want to handle touch inputs.
    if touch_A2.value:
        cycling = True
        palette_choice = PALETTE_DARK
    elif touch_A3.value:
        cycling = True
        palette_choice = PALETTE_BRIGHT
    elif touch_A4.value:
        cycling = True
        palette_choice = PALETTE_FIRE
        offset_increment = 6
    elif touch_A5.value:
        cycling = True
        palette_choice = PALETTE_RAINBOW
        offset_increment = 1
    # Also check for touch speed control
#    if touch_A6.value:
#        offset_increment += 1
#    if touch_TX.value:
#        offset_increment -= 1

Once you've got the code in your editor, look near the top and find this line:

Download: file
NUM_LEDS = 24             # change to reflect your total number of ring LEDs
RING_PIN = board.A1       # change to reflect your wiring
CPX_PIN = board.D8        # CPX Neopixels live on pin D8

NUM_LEDS refers to the total number of NeoPixels you have just in the rings -- NOT including the pixels on the face of the Circuit Playground Bluefruit. Since I have two 12-pixel rings attached, NUM_LEDS = 24 in my case. 

Change this number to reflect your total number of LEDS. 

You can also play with this number to change the feel of your animations. As long as the number is greater than your actual number of pixels (so, not less than 24 in this case) you can make the animations more "spread out" and more slow-moving by telling the code you have more pixels than you actually have. Try making this number 34 or 44 and see what kind of results you get.

Customizing Palettes

I've added four different color palettes for the animations accessed from the Control Pad: a "dark" palette (for turning the lights off), a "light" palette for the reading light mode, a "fire" and a "rainbow" palette. You can customize these fairly easily in the code. The power of the FancyLED library allows you so much control when it comes to choosing custom colors and animating them smoothly.

Find the palette definitions in the code:

Download: file
# Palettes can have any number of elements in various formats
# check https://learn.adafruit.com/fancyled-library-for-circuitpython/colors for more info
# Declare a 6-element RGB rainbow palette
PALETTE_RAINBOW = [fancy.CRGB(1.0, 0.0, 0.0),  # Red
           fancy.CRGB(0.5, 0.3, 0.0),  # Orange
           fancy.CRGB(0.5, 0.5, 0.0),  # Yellow     
           fancy.CRGB(0.3, 0.7, 0.0),  # Yellow Green      
           fancy.CRGB(0.0, 1.0, 0.0),  # Green
           fancy.CRGB(0.0, 0.7, 0.3),  # Teal
           fancy.CRGB(0.0, 0.5, 0.5),  # Cyan
           fancy.CRGB(0.0, 0.3, 0.7),  # Blue
           fancy.CRGB(0.0, 0.0, 1.0),  # Blue
           fancy.CRGB(0.5, 0.0, 0.5),  # Magenta
           fancy.CRGB(0.7, 0.0, 0.3)]  # Purple

# Reading Lamp mode - Warm Yellow 
PALETTE_BRIGHT = [fancy.CRGB(255, 183, 55)]

# Black Only palette for "off" mode
PALETTE_DARK = [fancy.CRGB(0, 0, 0)]

# Declare a FIRE palette
PALETTE_FIRE = [fancy.CRGB(160, 30, 0),  # Reds and Yellows
           fancy.CRGB(27, 65, 0),
           fancy.CRGB(0, 0, 0),
           fancy.CRGB(224, 122, 0),
           fancy.CRGB(0, 0, 0),
           fancy.CRGB(250, 80, 0),
           fancy.CRGB(0, 0, 0),
           fancy.CRGB(0, 0, 0),
           fancy.CRGB(200, 40, 0)]

You can use CRGB values or CHSV values to choose colors, or use them both at the same time. There are also multiple ways to declare values and a lot of control over how spread out the gradients can be.

This is explained in detail in the FancyLED guide so take a look to find out all you need to know about creating your own custom color palettes.

Offset Increment

Once you've customized your palettes, you can also customize the speed of the animation. Look for offset_increment in the code. A higher number will make the palettes swirl faster. Since I want the candle flame mode to look flickery, I've set offset_increment to 6 for that mode. I want the rainbow mode to be slow and smooth, so I've set the increment to 1

You can play with these values to customize the speed of your animations.  Just note that you need to change this in TWO places in the code. Lines 124-134 change modes while using Bluetooth, and lines 143-156 change modes via the capacitive touch buttons.

Save the code on your CIRCUITPY drive, called code.py and the candle flame animation should start. You can test and make changes as many times as you'd like. Clicking "save" will push the changes to your board so you can see the results.

That's it! You're ready to start controlling your lights with Bluetooth.

This guide was first published on Nov 27, 2019. It was last updated on Nov 27, 2019. This page (Software) was last updated on Dec 10, 2019.