Code with CircuitPython

The adafruit_circuitplayground library allows us to use essential commands and features of the board without a lot of setup in our code! Since we're using the adafruit_circuitplayground library in this project, be sure you have all of the libraries installed as specified on the previous page of the guide. 

Your root directory on the CIRCUITPY drive should look like the one pictured here.

The Mu Editor

Adafruit recommends using the free program Mu to edit your CircuitPython programs and save them on your Circuit Playground Bluefruit. You can use any text editor, but Mu has some handy features.

See this page on the Circuit Playground Bluefruit guide on the steps used to install Mu.

Ornament Code

Here is the code that we'll run on the Circuit Playground Bluefruit.

Copy this code and then paste it into a new document in Mu, then save it to your CIRCUITPY drive as code.py

You'll run the same code on all of the CPB boards, since you can just flip the switch to determine if a give board is broadcasting or detecting the signal.

"""
Circuit Playground Bluefruit Ornament Proximity
This demo uses advertising to set the color of scanning devices depending on the strongest broadcast
signal received. Circuit Playgrounds can be switched between advertising and scanning using the
slide switch. The buttons change the color when advertising.
"""

import time
from adafruit_circuitplayground.bluefruit import cpb

from adafruit_ble import BLERadio
from adafruit_ble.advertising.adafruit import AdafruitColor

# The color pickers will cycle through this list with buttons A and B.
color_options = [0x110000,
                 0x111100,
                 0x001100,
                 0x001111,
                 0x000011,
                 0x110011,
                 0x111111,
                 0x221111,
                 0x112211,
                 0x111122]

ble = BLERadio()

i = 0
advertisement = AdafruitColor()
advertisement.color = color_options[i]
cpb.pixels.auto_write = False
cpb.pixels.fill(color_options[i])
while True:
    # The first mode is the color selector which broadcasts it's current color to other devices.
    if cpb.switch:
        print("Broadcasting color")
        ble.start_advertising(advertisement)
        while cpb.switch:
            last_i = i
            if cpb.button_a:
                i += 1
            if cpb.button_b:
                i -= 1
            i %= len(color_options)
            if last_i != i:
                color = color_options[i]
                cpb.pixels.fill(color)
                cpb.pixels.show()
                print("New color {:06x}".format(color))
                advertisement.color = color
                ble.stop_advertising()
                ble.start_advertising(advertisement)
                time.sleep(0.5)
        ble.stop_advertising()
    # The second mode listens for color broadcasts and shows the color of the strongest signal.
    else:
        closest = None
        closest_rssi = -80
        closest_last_time = 0
        print("Scanning for colors")
        while not cpb.switch:
            for entry in ble.start_scan(AdafruitColor, minimum_rssi=-100, timeout=1):
                if cpb.switch:
                    break
                now = time.monotonic()
                new = False
                if entry.address == closest:
                    pass
                elif entry.rssi > closest_rssi or now - closest_last_time > 0.4:
                    closest = entry.address
                else:
                    continue
                closest_rssi = entry.rssi
                closest_last_time = now
                discrete_strength = min((100 + entry.rssi) // 5, 10)
                cpb.pixels.fill(0x000000)
                for i in range(0, discrete_strength):
                    cpb.pixels[i] = entry.color
                cpb.pixels.show()

            # Clear the pixels if we haven't heard from anything recently.
            now = time.monotonic()
            if now - closest_last_time > 1:
                cpb.pixels.fill(0x000000)
                cpb.pixels.show()
        ble.stop_scan()

Here's how the code works.

Library Import

First, we import the libraries we'll be using:

Download: file
import time
from adafruit_circuitplayground.bluefruit import cpb

from adafruit_ble import BLERadio
from adafruit_ble.advertising.adafruit import AdafruitColor

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, as well as the on board red LED and the NeoPixels.

We're also setting up the Bluetooth LE radio and the package needed to advertise color values.

Color List

Next, we create a list of color values (in hex):

Download: file
# The color pickers will cycle through this list with buttons A and B.
color_options = [0x110000,
                 0x111100,
                 0x001100,
                 0x001111,
                 0x000011,
                 0x110011,
                 0x111111,
                 0x221111,
                 0x112211,
                 0x111122]

Bluetooth and NeoPixel Setup

The BLE radio is instantiated next, as well as the AdafruitColor() object to advertise the color value of the board.

Also, we'll use the cpb.pixels.auto_write command to set the pixel auto write to False this helps avoid flickering NeoPixels (thanks Roy!) and the NeoPixel fill color.

Download: file
ble = BLERadio()

i = 0
advertisement = AdafruitColor()
advertisement.color = color_options[i]
cpb.pixels.auto_write = False
cpb.pixels.fill(color_options[i])

Main Loop

The main loop of the code happens in the while True: section.

Switch Left to Broadcast

Here, we check the position of the switch by asking if cpb.switch: and if it is True, this means the switch is positioned to the left and the board will be in broadcast mode (the "hidden" ornaments are going to be the ones in broadcast mode).

Then, button presses are used to increment or decrement through the color list, and set the NeoPixels to that color.

Every half second, the board will advertise this color information over Bluetooth with the ble.start_advertising(advertisement) command.

Download: file
if cpb.switch:
        print("Broadcasting color")
        ble.start_advertising(advertisement)
        while cpb.switch:
            last_i = i
            if cpb.button_a:
                i += 1
            if cpb.button_b:
                i -= 1
            i %= len(color_options)
            if last_i != i:
                color = color_options[i]
                cpb.pixels.fill(color)
                cpb.pixels.show()
                print("New color {:06x}".format(color))
                advertisement.color = color
                ble.stop_advertising()
                ble.start_advertising(advertisement)
                time.sleep(0.5)
        ble.stop_advertising()

Switch Right to Detect

In the case that the cpb.switch value is False (in the code, not True), this means it's flipped to the right and the board will be in listening/detect mode.

Now, we'll used the Bluetooth LE radio to scan the airwaves and use its received signal strength indicator (RSSI) capability to single out the board with the strongest signal. 

The color entry being broadcast by the strongest (usually nearest) board is used in setting the NeoPixel color, while the number of pixels being lit is based on the signal strength.

Download: file
else:
        closest = None
        closest_rssi = -80
        closest_last_time = 0
        print("Scanning for colors")
        while not cpb.switch:
            for entry in ble.start_scan(AdafruitColor, minimum_rssi=-100, timeout=1):
                if cpb.switch:
                    break
                now = time.monotonic()
                new = False
                if entry.address == closest:
                    pass
                elif entry.rssi > closest_rssi or now - closest_last_time > 0.4:
                    closest = entry.address
                else:
                    continue
                closest_rssi = entry.rssi
                closest_last_time = now
                discrete_strength = min((100 + entry.rssi) // 5, 10)
                cpb.pixels.fill(0x000000)
                for i in range(0, discrete_strength):
                    cpb.pixels[i] = entry.color
                cpb.pixels.show()

            # Clear the pixels if we haven't heard from anything recently.
            now = time.monotonic()
            if now - closest_last_time > 1:
                cpb.pixels.fill(0x000000)
                cpb.pixels.show()
        ble.stop_scan()

Next, we'll put together the ornaments and use them in a scavenger hunt!

This guide was first published on Dec 04, 2019. It was last updated on Dec 04, 2019.
This page (Code with CircuitPython) was last updated on Jul 30, 2020.