Installing Project Code

To use with CircuitPython, you need to first install a few libraries, into the lib folder on your CIRCUITPY drive. Then you need to update code.py with the example script.

Thankfully, we can do this in one go. In the example below, click the Download Project Bundle button below to download the necessary libraries and the code.py file in a zip file. Extract the contents of the zip file, open the directory CPB_Magic_Light_Mixer/ and then click on the directory that matches the version of CircuitPython you're using and copy the contents of that directory to your CIRCUITPY drive.

Your CIRCUITPY drive should now look similar to the following image:

CIRCUITPY
# SPDX-FileCopyrightText: 2020 John Edgar Park for Adafruit Industries
#
# SPDX-License-Identifier: MIT

# 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)

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.

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.

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.

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.

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.

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.

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 Apr 10, 2024.

This page (Code the Color Remote with CircuitPython) was last updated on Apr 10, 2024.

Text editor powered by tinymce.