This project involves two Circuit Playground Bluefruit boards. The first acts as the Remote Control which sends data to the NeoPixel Animator which is used to cycle through animations, change the color, freeze the color, and turn on and off the LEDs. This section will go through the Remote Control code. Let's take a look!

Library Imports

First we import all the necessary libraries and modules.

import time

from adafruit_circuitplayground.bluefruit import cpb

from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble.services.nordic import UARTService
from adafruit_bluefruit_connect.color_packet import ColorPacket
from adafruit_bluefruit_connect.button_packet import ButtonPacket

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, the accelerometer, and the built-in NeoPixels.

We import all the necessary BLE modules for connecting to another Bluefruit, and the modules to be able to send color packets and button packets. We'll use the color packets to send color information to and change the color of the LEDs on the NeoPixel Animator CPB, and the button packets to send button presses and slide switch changes to the NeoPixel Animator CPB to change animations or turn off the LEDs.

Helper Functions

Then we have two helper functions: scale() and send_packet().

The first helper scales the acceleration value range provided by the built-in accelerometer to a 0-255 RGB color range. This allows us to use the accelerometer to change the color of the NeoPixels.

def scale(value):
    """Scale a value from acceleration value range to 0-255 (RGB range)"""
    value = abs(value)
    value = max(min(19.6, value), 0)
    return int(value / 19.6 * 255)

The second helper handles sending BLE packets to another Bluefruit over a UART connection. BLE connections can be flaky for a multitude of reasons, and if the connection drops while sending packets, the code will return an error and stop running. Therefore, we have this helper function to do error handling on the packet sending code so your code continues running even if there are connection issues.

def send_packet(uart_connection_name, packet):
    """Returns False if no longer connected."""
    try:
        uart_connection_name[UARTService].write(packet.to_bytes())
    except:  # pylint: disable=bare-except
        try:
            uart_connection_name.disconnect()
        except:  # pylint: disable=bare-except
            pass
        return False
    return True

Bluetooth and Input Setup

We finish setup by creating the BLE object, creating a series of variables to use in preventing multiple button presses being sent and tracking the last switch location, and then check to see if any existing BLE connections are valid for our purposes.

ble = BLERadio()

# Setup for preventing repeated button presses and tracking switch state
button_a_pressed = False
button_b_pressed = False
last_switch_state = None

uart_connection = None
# See if any existing connections are providing UARTService.
if ble.connected:
    for connection in ble.connections:
        if UARTService in connection:
            uart_connection = connection
        break

Scanning for BLE Connections

If the board is not already connected via BLE, the first thing it does is begin scanning for any valid options and connects to the first one it finds.

[...]
    if not uart_connection or not uart_connection.connected:  # If not connected...
        print("Scanning...")
        for adv in ble.start_scan(ProvideServicesAdvertisement, timeout=5):  # Scan...
            if UARTService in adv.services:  # If UARTService found...
                print("Found a UARTService advertisement.")
                uart_connection = ble.connect(adv)  # Create a UART connection...
                break
        # Stop scanning whether or not we are connected.
        ble.stop_scan()  # And stop scanning.

Once connected, it moves on to the rest of the code. While the Circuit Playground Bluefruit is connected, you can do a few things: send button presses from button A and button B, send the location of the slide switch, and use the accelerometer to change and send the color of the NeoPixels.

Button Presses

The code for button A and button B is essentially the same. Let's take a look at button A.

Remember, during setup we set button_a_pressed = False to set the initial state.

First the code checks to see if BOTH button A is pressed and button_a_pressed = False, and then sends a LEFT button packet and sets button_a_pressed = True.

[...]
        if cpb.button_a and not button_a_pressed:  # If button A pressed...
            print("Button A pressed.")
            # Send a LEFT button packet.
            if not send_packet(uart_connection,
                               ButtonPacket(ButtonPacket.LEFT, pressed=True)):
                uart_connection = None
                continue
            button_a_pressed = True  # Set to True.
            time.sleep(0.05)  # Debounce.

Then it checks to see if the button has been released by BOTH checking to see if button a is not currently being pressed and that button_a_pressed = True, and sets button_a_pressed = False again.

[...]
        if not cpb.button_a and button_a_pressed:  # On button release...
            button_a_pressed = False  # Set to False.
            time.sleep(0.05)  # Debounce.

The code for button B is identical except that it sends a RIGHT button packet instead.

This set of code prevents accidentally sending multiple button presses by holding down the buttons. This is important for the NeoPixel Animator code, as the button presses are used to switch animations and freeze the color.

Slide Switch

The slide switch works a little differently than the buttons since it can remain in either state for an extended period of time without physical interaction. So instead of simply tracking the state, we're checking to see when the state changes, since that is the important part.

So, we check to see if the switch state is no longer the last switch state, i.e. it has changed since the last check. If it has changed, we set last_switch_state to the current state. We print to the serial console a message that depends on the switch direction. Each time the switch position changes, we send a BUTTON_1 packet.

[...]
        if cpb.switch is not last_switch_state:  # If the switch state is changed...
            last_switch_state = cpb.switch  # Set state to current switch state.
            if cpb.switch:
                print("Switch is to the left: LEDs off!")
            else:
                print("Switch is to the right: LEDs on!")
            # Send a BUTTON_1 button packet.
            if not send_packet(uart_connection,
                               ButtonPacket(ButtonPacket.BUTTON_1, pressed=cpb.switch)):
                uart_connection = None
                continue

On the Remote Control Circuit Playground Bluefruit, if the switch is to the left, we turn off the NeoPixels.

[...]
        if cpb.switch:  # If switch is to the left...
            cpb.pixels.fill((0, 0, 0))  # Turn off the LEDs.

This is because the switch being to the left also turns off the NeoPixels on the NeoPixel Animator CPB.

Acceleration and NeoPixel Color

Finally, if the slide switch is to the right, we use the accelerometer to set the NeoPixel colors and send that data to the NeoPixel Animator CPB.

First, we set r, g, b to  the acceleration values mapped to RGB values using the scale helper function. We set color = (r, g, b) and fill the Remote Control NeoPixels with the color. Then, we send a color packet with the color to the NeoPixel Animator. As long as the switch is to the right, color packets are being sent.

[...]
            r, g, b = map(scale, cpb.acceleration)  # Map acceleration values to RGB values...
            color = (r, g, b)  # Set color to current mapped RGB value...
            print("Color:", color)
            cpb.pixels.fill(color)  # Fill Remote Control LEDs with current color...
            if not send_packet(uart_connection, ColorPacket(color)):  # And send a color packet.
                uart_connection = None
                continue
        time.sleep(0.1)  # Delay to prevent sending packets too quickly.

We end with a time.sleep(0.1) to prevent packets from sending too quickly and being received as corrupted.

That's how the Remote Control code for the Circuit Playground Bluefruit Animation and Color Remote Control works!

This guide was first published on Dec 14, 2019. It was last updated on Dec 14, 2019.

This page (Remote Control Code) was last updated on Oct 15, 2021.

Text editor powered by tinymce.