Code the Color Remote with CircuitPython

Text Editor

Adafruit recommends using the Mu editor for using your CircuitPython code with the Circuit Playground Bluefruit boards. You can get more info in this guide.

Alternatively, you can use any text editor that saves files.

Copy or download the code below, paste it into Mu, and then save it to your Circuit Playground Bluefruit as code.py

# Magic Light Bulb remote color mixer
# Sends RGB color values, read from three faders on CPB to the bulb
# https://www.magiclightbulbs.com/collections/bluetooth-bulbs

import adafruit_ble
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble_magic_light import MagicLightService
import _bleio
import board
from analogio import AnalogIn
from adafruit_circuitplayground import cp


def find_connection():
    for connection in radio.connections:
        if MagicLightService not in connection:  # Filter services
            continue
        return connection, connection[MagicLightService]
    return None, None

radio = adafruit_ble.BLERadio()


def scale(value):
    # Scale a value from 0-65535 (AnalogIn range) to 0-255 (RGB range)
    return int(value / 65535 * 255)
a4 = AnalogIn(board.A4)  # red slider
a5 = AnalogIn(board.A5)  # green slider
a6 = AnalogIn(board.A6)  # blue slider

cp.pixels.brightness = 0.1
dimmer = 1.0

active_connection, bulb = find_connection()  # In case already connected

while True:
    if not active_connection:  # There's no connection, so let's scan for one
        cp.pixels[0] = (60, 40, 0)  # set CPB NeoPixel 0 to yellow while searching
        print("Scanning for Magic Light...")
        # Scan and filter for advertisements with ProvideServicesAdvertiesment type
        for advertisement in radio.start_scan(ProvideServicesAdvertisement):
            # Filter further for advertisements with MagicLightService
            if MagicLightService in advertisement.services:
                active_connection = radio.connect(advertisement)
                print("Connected to Magic Light")
                cp.pixels[0] = (0, 0, 255)  # Set NeoPixel 0 to blue when connected
                # Play a happy tone
                cp.play_tone(440, 0.1)
                cp.play_tone(880, 0.1)
                print("Adjust slide potentiometers to mix RGB colors")
                try:
                    bulb = active_connection[MagicLightService]
                except _bleio.ConnectionError:
                    print("disconnected")
                    continue
                break
        radio.stop_scan()  # Now that we're connected, stop scanning

    while active_connection.connected:  # Connected, now we can set attrs to change colors
        # Toggle slide switch to go to half or full brightness
        if cp.switch:
            cp.red_led = True
            dimmer = 0.5
        else:
            cp.red_led = False
            dimmer = 1.0

        # Press the 'A' button to momentarily black the bulb
        if cp.button_a:
            dimmer = 0.0

        r = scale(a4.value * dimmer)
        g = scale(a5.value * dimmer)
        b = scale(a6.value * dimmer)

        # Press the 'B' button to momentarily white the bulb
        if cp.button_b:
            r, g, b = (255, 255, 255)

        color = (r, g, b)

        try:
            bulb[0] = color  # Send color to bulb's color characteristic
        except _bleio.ConnectionError:
            print("disconnected")
            continue
        cp.pixels[2] = (r, 0, 0)
        cp.pixels[3] = (0, g, 0)
        cp.pixels[4] = (0, 0, b)
        cp.pixels[7] = (color)

    active_connection = None  # Not connected, start scanning again
    cp.pixels[0] = (60, 40, 0)

Libraries

In addition to the libraries you copied over to the board  following this guide page we'll also add one more library for dealing specifically with the Magic Light.

From the library bundle you downloaded in that guide page, transfer the following library onto the CPB boards' /lib directory:

  • adafruit_ble_magic_light.mpy

Your CBP should look like the screenshot to the left.

How it Works

Libraries

First, the code imports the libraries necessary for using Bluetooth LE (adafruit_ble, _bleio) and the more specialized adafruit_ble_magic_light library to deal with the specifics of this profile.

We also import adafruit_ble.advertising.standard ProvideServicesAdvertisement so we can do some filtering of the Peripheral advertisements being broadcast by myriad devices and hone in on the ones we want.

Additionally, we import board, analogio AnalogIn, and adafruit_circuitplayground so we can use the sliders, buttons, slide switch, on-board NeoPixels, and speaker on the CPB with simple, high level commands.

Download: file
import adafruit_ble
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble_magic_light import MagicLightService
import _bleio
import board
from analogio import AnalogIn
from adafruit_circuitplayground import cp

Find Connection

Next we'll define a function called find_connection() that we'll use later to search through available Peripheral connections for only the one that offers the MagicLightService.

Download: file
def find_connection():
    for connection in radio.connections:
        if MagicLightService not in connection:  # Filter services
            continue
        return connection, connection[MagicLightService]
    return None, None

Instantiate Radio

We instantiate the Bluefruit LE radio with this command:

radio = adafruit_ble.BLERadio()

Analog Read Setup

To use the slide potentiometers we'll need to define a function called scale(value) that can convert the raw analog voltage readings to a 0-255 range that's used per RGB color value.

We'll also define the analog pin read variables for the CPB's A4, A5, and A6 pads.

Download: file
def scale(value):
    # Scale a value from 0-65535 (AnalogIn range) to 0-255 (RGB range)
    return int(value / 65535 * 255)
a4 = AnalogIn(board.A4)  # red slider
a5 = AnalogIn(board.A5)  # green slider
a6 = AnalogIn(board.A6)  # blue slider

Next, we'll set the on-board NeoPixel brightness using the cp.pixels.brightness = 0.1 command.

We'll also create a variable called dimmer and set it to a value of 1.0 -- this will be used as a multiplier for the color values and will be changed to 0.5 when the slide switch is engaged.

Main Loop

During the main loop of the program, we check to see if there's an active connection, and if not we set the first CPB NeoPixel to yellow.

We then begin scanning through the filtered advertisements for the MagicLightService that's being broadcast by the bulb.

If the bulb is found, a connection is made and the CPB's first pixel is set to blue, and a happy tone is played, and the radio stops scanning for connections.

Download: file
while True:
    if not active_connection:  # There's no connection, so let's scan for one
        cp.pixels[0] = (60, 40, 0)  # set CPB NeoPixel 0 to yellow while searching
        print("Scanning for Magic Light...")
        # Scan and filter for advertisements with ProvideServicesAdvertiesment type
        for advertisement in radio.start_scan(ProvideServicesAdvertisement):
            # Filter further for advertisements with MagicLightService
            if MagicLightService in advertisement.services:
                active_connection = radio.connect(advertisement)
                print("Connected to Magic Light")
                cp.pixels[0] = (0, 0, 255)  # Set NeoPixel 0 to blue when connected
                # Play a happy tone
                cp.play_tone(440, 0.1)
                cp.play_tone(880, 0.1)
                print("Adjust slide potentiometers to mix RGB colors")
                try:
                    bulb = active_connection[MagicLightService]
                except _bleio.ConnectionError:
                    print("disconnected")
                    continue
                break
        radio.stop_scan()  # Now that we're connected, stop scanning

Connected

Once the connection has been made, we check the CPB slide switch to set half or full brightness, and also flip the on board red_led on or off respectively to indicate dim/full mode.

We check for the CPB A button press and set the dimmer value to 0.0 if pressed.

The r, g, b variables are adjusted according to the scaled analog readings of the slide potentiometers, and multiplied by the dimmer variable value.

We check for the CPB B button press and set the r, g, b values to 255, 255, 255 if pressed.

Now, we create a color variable and cast the current r, g, b values to it, so we can send these values to the bulb.

Download: file
while active_connection.connected:  # Connected, now we can set attrs to change colors
        # Toggle slide switch to go to half or full brightness
        if cp.switch:
            cp.red_led = True
            dimmer = 0.5
        else:
            cp.red_led = False
            dimmer = 1.0
 
        # Press the 'A' button to momentarily black the bulb
        if cp.button_a:
            dimmer = 0.0
 
        r = scale(a4.value * dimmer)
        g = scale(a5.value * dimmer)
        b = scale(a6.value * dimmer)
 
        # Press the 'B' button to momentarily white the bulb
        if cp.button_b:
            r, g, b = (255, 255, 255)
 
        color = (r, g, b)
 
        try:
            bulb[0] = color  # Send color to bulb's color characteristic
        except _bleio.ConnectionError:
            print("disconnected")
            continue

Feedback

Since we've got spare pixels on the CPB, why not use them for some user feedback?! We'll set pixels 2, 3, and 4 to the pure red, green, and blue levels and then set pixel 7 to the combined color value, the same as the bulb.

Download: file
cp.pixels[2] = (r, 0, 0)
cp.pixels[3] = (0, g, 0)
cp.pixels[4] = (0, 0, b)
cp.pixels[7] = (color)

Next we'll assemble the parts to use our color mixer!

This guide was first published on Jan 15, 2020. It was last updated on Jan 15, 2020.
This page (Code the Color Remote with CircuitPython) was last updated on Jul 22, 2020.