The Bluefruit Playground is an app for iOS & iPadOS. It allows you to interact with a Circuit Playground Bluefruit or CLUE board in a variety of different ways, including:

  • Control LED color & animation
  • View continuous light sensor readings
  • View state of built-in buttons & switch
  • Turn a CPB into a musical instrument
  • View orientation based on accelerometer data
  • View temperature readings

The app is designed from the ground up for Circuit Playground Bluefruit & CLUE. You can pick one up from the Adafruit Shop:

Circuit Playground Bluefruit - Bluetooth Low Energy

PRODUCT ID: 4333
Circuit Playground Bluefruit is our third board in the Circuit Playground series, another step towards a perfect introduction to electronics and programming. We've...
OUT OF STOCK

Adafruit CLUE - nRF52840 Express with Bluetooth LE

PRODUCT ID: 4500
Do you feel like you just don't have a CLUE? Well, we can help with that - get a CLUE here at Adafruit by picking up this sensor-packed development board. We wanted to build some...
$39.95
IN STOCK

Download the app

Before we get started, download and install Bluefruit Playground on your iPhone or iPad by following the link below:

At this time, Adafruit does not have equivalent functionality for Android devices.

In order to use the Bluefruit Playground app, you'll need to load special uf2 firmware file on your Circuit Playground Bluefruit (aka – CPB) or CLUE.

Download uf2 file

Circuit Playground Bluefruit users should click the button below to download the Bluefruit Playground uf2 firmware file and save it to your computer:

CLUE users should click the link below to download firmware for their device:

(For developers interested in the source code for the firmware and other details, see the BLE Services page in this guide. But you don't need to read or understand that page to use the app.)

Upload firmware to device

Connect your board to your computer via a known good USB data cable (not a "charge-only" cable!).

Double-click the Reset button near the center of the board. You should see your board's LEDs turn red, then green.

A new drive will appear on your computer with the following name:

  • Circuit Playground Bluefruit users will see a drive named CPLAYBTBOOT
  • CLUE users will see a drive named FTHR840BOOT (or similar)

Drag the Bluefruit Playground uf2 file onto the drive to copy it to your device.

Done

Once the uf2 file is finished copying, your device will reboot – its NeoPixel LEDs will turn off, and its drive will disappear from your computer.

You're now ready to connect to your device using the Bluefruit Playground app.

You can also use the Bluefruit Playground app and the Bluefruit Bluetooth Web Dashboard with CircuitPython. There are CircuitPython programs that do the same thing as the Arduino UF2s listed in this Guide.

Install CircuitPython and Libraries

You'll need to install a version of CircuitPython and a number of libraries specific to your board.

You need CircuitPython 6.0.0 or later. Version 5.3.1 will raise errors.

Install CircuitPython on Circuit Playground Bluefruit

Download the latest version of CircuitPython from the link below. If you need detailed help, follow these instructions.

Install CircuitPython on CLUE

Download the latest version of CircuitPython for CLUE from the link below. If you need detailed help, follow these instructions.

Install CircuitPython on Feather Bluefruit Sense

Download the latest version of CircuitPython for Feather Bluefruit Sense from the link below. If you need detailed help, follow these instructions.

Install Libraries

Now you'll need to get the libraries. First download the library bundle that matches your CircuitPython version from the link below. You'll be download a zip file. Unzip the file, find the lib folder, and open it. Then copy the libraries listed for your particular board to the CIRCUITPY drive, which will show up when CircuitPython is running.

Libraries for Circuit Playground Bluefruit

Copy these folders and files from the lib folder in the bundle to the lib folder on CIRCUITPY. If you need detailed help, follow these instructions. You may already have many of these libraries if you are already using CircuitPython on the board, but make sure they are up to date, particularly the BLE-related libraries.

  • adafruit_ble
  • adafruit_ble_adafruit (you may not have this already)
  • adafruit_circuitplayground
  • adafruit_lis3dh.mpy
  • adafruit_thermistor.mpy
  • neopixel.mpy

 

Libraries for CLUE and Feather Bluefruit Sense

These boards have the same sensors, so the libraries you need are the same. Copy these folders and files from the lib folder in the bundle to the lib folder on CIRCUITPY. If you need detailed help, follow these instructions. You may already have many of these libraries if you are already using CircuitPython on the board, but make sure they are up to date, particularly the BLE-related libraries.

  • adafruit_apds9960
  • adafruit_ble
  • adafruit_ble_adafruit (you may not have this already)
  • adafruit_bmp280.mpy
  • adafruit_bus_device
  • adafruit_clue.mpy
  • adafruit_lis3mdl.mpy
  • adafruit_lsm6ds.mpy
  • adafruit_register
  • adafruit_sht31d.mpy
  • neopixel.mpy

Add code.py

Finally, you'll add a code.py file that will talk to the Bluefruit Playground app.

Circuit Playground Bluefruit code.py

Download this file and copy it to CIRCUITPY, naming it code.py.

# Adafruit Service demo for Adafruit CLUE Circuit Playground Bluefruit board.
# Accessible via Adafruit Bluefruit Playground app and Web Bluetooth Dashboard.

import time

import board
from digitalio import DigitalInOut
import neopixel_write

from adafruit_ble import BLERadio

from adafruit_circuitplayground import cp

from adafruit_ble_adafruit.adafruit_service import AdafruitServerAdvertisement

from adafruit_ble_adafruit.accelerometer_service import AccelerometerService
from adafruit_ble_adafruit.addressable_pixel_service import AddressablePixelService
from adafruit_ble_adafruit.button_service import ButtonService
from adafruit_ble_adafruit.light_sensor_service import LightSensorService
from adafruit_ble_adafruit.temperature_service import TemperatureService
from adafruit_ble_adafruit.tone_service import ToneService

accel_svc = AccelerometerService()
accel_svc.measurement_period = 100
accel_last_update = 0

# 3 RGB bytes * 10 pixels.
NEOPIXEL_BUF_LENGTH = 3 * 10
neopixel_svc = AddressablePixelService()
neopixel_buf = bytearray(NEOPIXEL_BUF_LENGTH)
# Take over NeoPixel control from cp.
cp._pixels.deinit()  # pylint: disable=protected-access
neopixel_out = DigitalInOut(board.NEOPIXEL)
neopixel_out.switch_to_output()

button_svc = ButtonService()
button_svc.set_pressed(cp.switch, cp.button_a, cp.button_b)

light_svc = LightSensorService()
light_svc.measurement_period = 100
light_last_update = 0

temp_svc = TemperatureService()
temp_svc.measurement_period = 100
temp_last_update = 0

tone_svc = ToneService()

ble = BLERadio()
# The Web Bluetooth dashboard identifies known boards by their
# advertised name, not by advertising manufacturer data.
ble.name = "CPlay"

# The Bluefruit Playground app looks in the manufacturer data
# in the advertisement. That data uses the USB PID as a unique ID.
# Adafruit Circuit Playground Bluefruit USB PID:
# Arduino: 0x8045,  CircuitPython: 0x8046, app supports either
adv = AdafruitServerAdvertisement()
adv.pid = 0x8046

while True:
    # Advertise when not connected.
    ble.start_advertising(adv)
    while not ble.connected:
        pass
    ble.stop_advertising()

    while ble.connected:
        now_msecs = time.monotonic_ns() // 1000000  # pylint: disable=no-member

        if now_msecs - accel_last_update >= accel_svc.measurement_period:
            accel_svc.acceleration = cp.acceleration
            accel_last_update = now_msecs

        button_svc.set_pressed(cp.switch, cp.button_a, cp.button_b)

        if now_msecs - light_last_update >= light_svc.measurement_period:
            light_svc.light_level = cp.light
            light_last_update = now_msecs

        neopixel_values = neopixel_svc.values
        if neopixel_values is not None:
            start = neopixel_values.start
            if start > NEOPIXEL_BUF_LENGTH:
                continue
            data = neopixel_values.data
            data_len = min(len(data), NEOPIXEL_BUF_LENGTH - start)
            neopixel_buf[start : start + data_len] = data[:data_len]
            if neopixel_values.write_now:
                neopixel_write.neopixel_write(neopixel_out, neopixel_buf)

        if now_msecs - temp_last_update >= temp_svc.measurement_period:
            temp_svc.temperature = cp.temperature
            temp_last_update = now_msecs

        tone = tone_svc.tone
        if tone is not None:
            freq, duration_msecs = tone
            if freq != 0:
                if duration_msecs != 0:
                    # Note that this blocks. Alternatively we could
                    # use now_msecs to time a tone in a non-blocking
                    # way, but then the other updates might make the
                    # tone interval less consistent.
                    cp.play_tone(freq, duration_msecs / 1000)
                else:
                    cp.stop_tone()
                    cp.start_tone(freq)
            else:
                cp.stop_tone()
        last_tone = tone

CLUE code.py

Download this file and copy it to CIRCUITPY, naming it code.py.

# Adafruit Service demo for Adafruit CLUE board.
# Accessible via Adafruit Bluefruit Playground app and Web Bluetooth Dashboard.

import time

import board
from digitalio import DigitalInOut
import neopixel_write
from adafruit_ble import BLERadio

import ulab

from adafruit_clue import clue

from adafruit_ble_adafruit.adafruit_service import AdafruitServerAdvertisement

from adafruit_ble_adafruit.accelerometer_service import AccelerometerService
from adafruit_ble_adafruit.addressable_pixel_service import AddressablePixelService
from adafruit_ble_adafruit.barometric_pressure_service import BarometricPressureService
from adafruit_ble_adafruit.button_service import ButtonService
from adafruit_ble_adafruit.humidity_service import HumidityService
from adafruit_ble_adafruit.light_sensor_service import LightSensorService
from adafruit_ble_adafruit.microphone_service import MicrophoneService
from adafruit_ble_adafruit.temperature_service import TemperatureService
from adafruit_ble_adafruit.tone_service import ToneService

accel_svc = AccelerometerService()
accel_svc.measurement_period = 100
accel_last_update = 0

# CLUE has just one board pixel. 3 RGB bytes * 1 pixel.
NEOPIXEL_BUF_LENGTH = 3 * 1
neopixel_svc = AddressablePixelService()
neopixel_buf = bytearray(NEOPIXEL_BUF_LENGTH)
# Take over NeoPixel control from clue.
clue._pixel.deinit()  # pylint: disable=protected-access
neopixel_out = DigitalInOut(board.NEOPIXEL)
neopixel_out.switch_to_output()

baro_svc = BarometricPressureService()
baro_svc.measurement_period = 100
baro_last_update = 0

button_svc = ButtonService()
button_svc.set_pressed(False, clue.button_a, clue.button_b)

humidity_svc = HumidityService()
humidity_svc.measurement_period = 100
humidity_last_update = 0

light_svc = LightSensorService()
light_svc.measurement_period = 100
light_last_update = 0

# Send 256 16-bit samples at a time.
MIC_NUM_SAMPLES = 256
mic_svc = MicrophoneService()
mic_svc.number_of_channels = 1
mic_svc.measurement_period = 100
mic_last_update = 0
mic_samples = ulab.zeros(MIC_NUM_SAMPLES, dtype=ulab.uint16)

temp_svc = TemperatureService()
temp_svc.measurement_period = 100
temp_last_update = 0

tone_svc = ToneService()

ble = BLERadio()
# The Web Bluetooth dashboard identifies known boards by their
# advertised name, not by advertising manufacturer data.
ble.name = "CLUE"

# The Bluefruit Playground app looks in the manufacturer data
# in the advertisement. That data uses the USB PID as a unique ID.
# Adafruit CLUE USB PID:
# Arduino: 0x8071,  CircuitPython: 0x8072, app supports either
adv = AdafruitServerAdvertisement()
adv.pid = 0x8072

while True:
    # Advertise when not connected.
    ble.start_advertising(adv)
    while not ble.connected:
        pass
    ble.stop_advertising()

    while ble.connected:
        now_msecs = time.monotonic_ns() // 1000000  # pylint: disable=no-member

        if now_msecs - accel_last_update >= accel_svc.measurement_period:
            accel_svc.acceleration = clue.acceleration
            accel_last_update = now_msecs

        if now_msecs - baro_last_update >= baro_svc.measurement_period:
            baro_svc.pressure = clue.pressure
            baro_last_update = now_msecs

        button_svc.set_pressed(False, clue.button_a, clue.button_b)

        if now_msecs - humidity_last_update >= humidity_svc.measurement_period:
            humidity_svc.humidity = clue.humidity
            humidity_last_update = now_msecs

        if now_msecs - light_last_update >= light_svc.measurement_period:
            # Return "clear" color value from color sensor.
            light_svc.light_level = clue.color[3]
            light_last_update = now_msecs

        if now_msecs - mic_last_update >= mic_svc.measurement_period:
            clue._mic.record(  # pylint: disable=protected-access
                mic_samples, len(mic_samples)
            )
            # This subtraction yields unsigned values which are
            # reinterpreted as signed after passing.
            mic_svc.sound_samples = mic_samples - 32768
            mic_last_update = now_msecs

        neopixel_values = neopixel_svc.values
        if neopixel_values is not None:
            start = neopixel_values.start
            if start > NEOPIXEL_BUF_LENGTH:
                continue
            data = neopixel_values.data
            data_len = min(len(data), NEOPIXEL_BUF_LENGTH - start)
            neopixel_buf[start : start + data_len] = data[:data_len]
            if neopixel_values.write_now:
                neopixel_write.neopixel_write(neopixel_out, neopixel_buf)

        if now_msecs - temp_last_update >= temp_svc.measurement_period:
            temp_svc.temperature = clue.temperature
            temp_last_update = now_msecs

        tone = tone_svc.tone
        if tone is not None:
            freq, duration_msecs = tone
            if freq != 0:
                if duration_msecs != 0:
                    # Note that this blocks. Alternatively we could
                    # use now_msecs to time a tone in a non-blocking
                    # way, but then the other updates might make the
                    # tone interval less consistent.
                    clue.play_tone(freq, duration_msecs / 1000)
                else:
                    clue.stop_tone()
                    clue.start_tone(freq)
            else:
                clue.stop_tone()
        last_tone = tone

Feather Bluefruit Sense code.py

# Adafruit Service demo for Adafruit Feather Bluefruit Sense board.
# Accessible via Adafruit Web Bluetooth Dashboard.
# (As of this writing, not yet accessible via Bluefruit Playground app.)

import time

import board

import digitalio
import neopixel_write

import ulab

from adafruit_ble import BLERadio

import audiobusio

import adafruit_apds9960.apds9960
import adafruit_bmp280
import adafruit_lsm6ds.lsm6ds33
import adafruit_sht31d

from adafruit_ble_adafruit.adafruit_service import AdafruitServerAdvertisement

from adafruit_ble_adafruit.accelerometer_service import AccelerometerService
from adafruit_ble_adafruit.addressable_pixel_service import AddressablePixelService
from adafruit_ble_adafruit.barometric_pressure_service import BarometricPressureService
from adafruit_ble_adafruit.button_service import ButtonService
from adafruit_ble_adafruit.humidity_service import HumidityService
from adafruit_ble_adafruit.light_sensor_service import LightSensorService
from adafruit_ble_adafruit.microphone_service import MicrophoneService
from adafruit_ble_adafruit.temperature_service import TemperatureService

# Accelerometer
lsm6ds33 = adafruit_lsm6ds.lsm6ds33.LSM6DS33(board.I2C())
# Used for pressure and temperature.
bmp280 = adafruit_bmp280.Adafruit_BMP280_I2C(board.I2C())
# Humidity.
sht31d = adafruit_sht31d.SHT31D(board.I2C())
# Used only for light sensor
apds9960 = adafruit_apds9960.apds9960.APDS9960(board.I2C())
apds9960.enable_color = True

mic = audiobusio.PDMIn(
    board.MICROPHONE_CLOCK, board.MICROPHONE_DATA, sample_rate=16000, bit_depth=16,
)

# Create and initialize the available services.

accel_svc = AccelerometerService()
accel_svc.measurement_period = 100
accel_last_update = 0

# Feather Bluefruit Sense has just one board pixel. 3 RGB bytes * 1 pixel
NEOPIXEL_BUF_LENGTH = 3 * 1
neopixel_svc = AddressablePixelService()
neopixel_buf = bytearray(NEOPIXEL_BUF_LENGTH)
neopixel_out = digitalio.DigitalInOut(board.NEOPIXEL)
neopixel_out.switch_to_output()

baro_svc = BarometricPressureService()
baro_svc.measurement_period = 100
baro_last_update = 0

button_svc = ButtonService()
button = digitalio.DigitalInOut(board.SWITCH)
button.pull = digitalio.Pull.UP
button_svc.set_pressed(False, not button.value, False)

humidity_svc = HumidityService()
humidity_svc.measurement_period = 100
humidity_last_update = 0

light_svc = LightSensorService()
light_svc.measurement_period = 100
light_last_update = 0

# Send 256 16-bit samples at a time.
MIC_NUM_SAMPLES = 256
mic_svc = MicrophoneService()
mic_svc.number_of_channels = 1
mic_svc.measurement_period = 100
mic_last_update = 0
mic_samples = ulab.zeros(MIC_NUM_SAMPLES, dtype=ulab.uint16)

temp_svc = TemperatureService()
temp_svc.measurement_period = 100
temp_last_update = 0

ble = BLERadio()
# The Web Bluetooth dashboard identifies known boards by their
# advertised name, not by advertising manufacturer data.
ble.name = "Sense"

# The Bluefruit Playground app looks in the manufacturer data
# in the advertisement. That data uses the USB PID as a unique ID.
# Feather Bluefruit Sense USB PID:
# This board is not yet support on the app.
# Arduino: 0x8087,  CircuitPython: 0x8088
adv = AdafruitServerAdvertisement()
adv.pid = 0x8088

while True:
    # Advertise when not connected.
    ble.start_advertising(adv)
    while not ble.connected:
        pass
    ble.stop_advertising()

    while ble.connected:
        now_msecs = time.monotonic_ns() // 1000000  # pylint: disable=no-member

        if now_msecs - accel_last_update >= accel_svc.measurement_period:
            accel_svc.acceleration = lsm6ds33.acceleration
            accel_last_update = now_msecs

        if now_msecs - baro_last_update >= baro_svc.measurement_period:
            baro_svc.pressure = bmp280.pressure
            baro_last_update = now_msecs

        button_svc.set_pressed(False, not button.value, False)

        if now_msecs - humidity_last_update >= humidity_svc.measurement_period:
            humidity_svc.humidity = sht31d.relative_humidity
            humidity_last_update = now_msecs

        if now_msecs - light_last_update >= light_svc.measurement_period:
            # Return "clear" color value from color sensor.
            light_svc.light_level = apds9960.color_data[3]
            light_last_update = now_msecs

        if now_msecs - mic_last_update >= mic_svc.measurement_period:
            mic.record(mic_samples, len(mic_samples))
            # This subtraction yields unsigned values which are
            # reinterpreted as signed after passing.
            mic_svc.sound_samples = mic_samples - 32768
            mic_last_update = now_msecs

        neopixel_values = neopixel_svc.values
        if neopixel_values is not None:
            start = neopixel_values.start
            if start > NEOPIXEL_BUF_LENGTH:
                continue
            data = neopixel_values.data
            data_len = min(len(data), NEOPIXEL_BUF_LENGTH - start)
            neopixel_buf[start : start + data_len] = data[:data_len]
            if neopixel_values.write_now:
                neopixel_write.neopixel_write(neopixel_out, neopixel_buf)

        if now_msecs - temp_last_update >= temp_svc.measurement_period:
            temp_svc.temperature = bmp280.temperature
            temp_last_update = now_msecs

Now that you're done uploading the correct firmware, feel free to disconnect your device from your computer and power it via LiPoly or AAA battery pack.

Scan & connect

Open the Bluefruit Playground app on your iPhone or iPad. Read through the startup screens and tap the Begin Pairing button at the bottom.

The Finding CPB list will appear, showing all the compatible devices detected by the app.

The icon at the left of each device name represents its wireless signal strength (RSSI).

Tap the row titled Bluefruit52 to connect Bluefruit Playground to your CPB.

Don't see your device?

If your CPB doesn't appear on the Finding CPB list:

  1. Check to see if the CPB is powered on. Verify that the green On light is lit.
  2. Make sure CPB is running the correct firmware. See the Firmware page in this guide.
  3. Try resetting the CPB by pressing the small Reset button near the center of the board.

Once connected, the app will display a list of modules to choose from.

Each module allows you to interact with your CPB in a different way:

Choose one of the modules from the list and go to its related page in this guide to learn more.

The Neopixels module allows you to control a CPB's ten NeoPixel LEDs together or individually.

Change Modes

Switch between three different control modes by swiping left/right on the title area of the white panel at the bottom of the screen.

Light Sequence

The Light Sequence mode lets you play light sequence animations on the CPB NeoPixels.

  • Play a light sequence by tapping one of the four preview animations.
  • The sequence playback speed can be changed using the Speed slider control at the bottom.

Neopixel Selection

The Color Palette and Color Wheel modes allow you to select neopixels individually and in groups.

  • Tap a NeoPixel in the CPB image to select or deselect it. Selected NeoPixels are indicated by a surrounding yellow square.
  • Tap the O button on the left to select all NeoPixels. Tap the X button to deselect all NeoPixels.
  • Tap the trash can icon to turn all NeoPixels off.

Color Palette 

The Color Palette mode provides a variety of presets to use for setting NeoPixel color.

  • Tap one of the colors to change the color of all selected NeoPixels.
  • Use the slider at the bottom to set the Brightness of colors sent to the CPB.

Color Wheel

Color Wheel mode lets you pick a color from a continuous color field.

  • Touch the selection ring in the color field to send the currently selected color.
  • Drag the selection ring across the color field to select and send a new color.
  • The chosen color will be displayed in the square to the right along with its hexadecimal representation below.
  • The slider at the bottom allows you to set the Brightness of colors sent to the CPB.

The Light Sensor module allows you to view the amount of light detected by the CPB built-in ALS-PT19 light sensor.

Luminance Reading

The current light level is displayed as a numeric value on the Luminance Reading panel at the bottom of the screen. New values are sent from the CPB ten times per second.

Try shining a flashlight on the CPB to see the values change in realtime.

The Button Status module displays the activity of a CPB's two built-in momentary pushbuttons and slide switch.

  • When a button on the CPB is pressed, it's corresponding letter icon in the bottom panel will turn orange.
  • When the CPB's slide switch is activated, it's corresponding arrow icon in the bottom panel will turn orange.
  • The current position of the CPB's slide switch is indicated by the direction of the arrow icon.

The Tone Generator module allows you to use a Circuit Playground Bluefruit as a simple monophonic synthesizer.

  • Press a key on the piano keyboard at the bottom to play a corresponding note on the CPB's built-in speaker.
  • Try pressing keys in sequence to perform a song.

Frequency values

The Tone Generator's keyboard can play notes C4 through G5. Below, you can see the frequency values for each of the included notes.

Note Frequency(Hz) Wavelength(cm)
C4 261.63 131.87
 C#4/Db4  277.18 124.47
D4 293.66 117.48
 D#4/Eb4  311.13 110.89
E4 329.63 104.66
F4 349.23 98.79
 F#4/Gb4  369.99 93.24
G4 392.00 88.01
 G#4/Ab4  415.30 83.07
A4 440.00 78.41
 A#4/Bb4  466.16 74.01
B4 493.88 69.85
C5 523.25 65.93
 C#5/Db5  554.37 62.23
D5 587.33 58.74
 D#5/Eb5  622.25 55.44
E5 659.25 52.33
F5 698.46 49.39
 F#5/Gb5  739.99 46.62
G5 783.99 44.01

The Accelerometer module uses readings from the Circuit Playground Bluefruit LIS3DH 3-axis XYZ accelerometer to determine the board orientation.

The accelerometer values sent from the CPB are used to calculate orientation, measured in Euler angles. This orientation is then used to set the rotation of the 3D model in the main view.

  • Individual values are displayed in the white panel at the bottom of the screen.
  • Accelerometer X, Y, & Z values are displayed in meters per second squared.
  • Euler Angle X, Y, & Z values are displayed in degrees.
Accelerometers cannot detect 'twisting' motion so you will see changes when you tilt the board, but not spin it.

Offset Controls

The CPB 3D model can be rotated manually to set a rotation offset.

  • Tap and drag across the 3D model to rotate it manually.
  • Double-tap the 3D model to reset its orientation.

The Temperature module displays continuous temperature readings from Circuit Playground Bluefruit’s built-in thermistor.

  • The current temperature reading is displayed in the center of the screen. 
  • Switch between Fahrenheit and Celsius units by tapping the °F/°C button on the right side.

Temperature Graph

Temperature values are sent from the CPB twice per second. Previous values are recorded and displayed in the graph in the bottom panel.

Try breathing hot air on CPB to warm it up quickly – then place it in the refrigerator to cool it down.

This page is intended for developers who are interested in communicating with a Circuit Playground Bluefruit from their own custom Bluetooth LE application. The source code for the firmware is available at this link:

All of the BLE services used by CPB's Bluefruit Playground firmware are documented below.

All Adafruit Service/Characteristic UUID128 share the same Base UUID :

  • ADAFxxxx-C332-42A8-93BD-25E905756CB8

Each service's UUID can be found by replacing xxxx with the service four-digit hex number.

Advertising

A device will advertise as a connectable type using the manufacturer specific data flag with Adafruit Company ID of 0x0822 as follows: 

| 1 byte     | 1 byte |  2 bytes | 1 byte   | 2 bytes | variable

-------------------------------------------------------------------------------------

| total len | Mfr Tag | 0x0822 | field len | field key | field value

There could be several of field (len, key, value) as long as they are all fitted into an advertising packet (31 byte for normal advertising, 200 bytes for extended advertising).

  • Total len : 1 + 2 + sum(field_len + 1)
  • Field len : 2 + len(field_value)
  • Field key is enumeration constant
    • 0x0000 :  Color ID, followed by 3 bytes of RGB for current color displayed on the board. Helpful to quickly know which board we are about to connect to.
    • 0x0001 : Board ID, followed by 2 bytes of USB PID of board. Can be used as a filter and display the board image on a mobile application
      • 0x8029: Feather nRF52840 Express (Arduino mode)
      • 0x802A: Feather nRF52840 Express (CircuitPython mode)
      • 0x8045: CPlayground Bluefruit (Arduino mode)
      • 0x8046: CPlayground Bluefruit (CircuitPython mode)
      • 0xEA60: Adafruit nRF52832 Feather (Arduino mode only)

Shared Characteristics

Measurement Period: 0001

  • { int32: period }
    • Read + Write
    • period = 1 to 2^31-1: Milliseconds between measurements
    • period = -1: Stop measuring
    • period = 0: Update the measurement whenever it changes, as often as possible

Service Version: 0002

  • { uint32: version }
    • Read
  • If characteristic is not present, version =  1 (v1)

Temperature Service

Temperature Service: 0100

  • Temperature: 0101
    • { float32: temperature }
      • Read + Notify
      • temperature in degrees Celsius
  • Measurement Period: 0001
  • Service Version: 0002 (optional): v1

Accelerometer Service

Accelerometer Service: 0200

  • Acceleration: 0201
    • ( float32: x, float32: y, float32: z }
      • Read + Notify
      • x, y, z acceleration values, in m/s^2
  • Measurement Period: 0001
  • Service Version: 0002 (optional): v1

Light Sensor Service

Light Sensor Service: 0300

  • Light Level: 0301
    • { level: float32 }
      • Read + Notify
  • Measurement Period: 0001
  • Service Version: 0002 (optional)

Button Status

Board Button Service: 0600

  • Pressed: 0601
    • {state: uint32}
      • Read + Notify
      • bit 0: slide switch: 1 for left, 0 for right
      • bit 1: 1 = button A pressed
      • bit 2: 1 = button B pressed
      • Other bits available for future buttons
  • Measurement Period: 0001
  • Service Version: 0002 (optional): v1

Tone Generator

Tone Service: 0C00

  • Tone: 0C01
    • { frequency: uint16, duration: uint32 }
      • Write
      • frequency: Hz, 0 for no tone (useful to turn off a non-stop tone)
      • duration: milliseconds, 0 for non-stop

NeoPixels

Addressable Pixel Service: 0900

  • General service for any kind of addressable pixels, including WS2812 (NeoPixel), WS2801, APA102 (DotStar), etc. It is the client’s responsibility to know the number of colors, color order, etc. and to fill the buffer appropriately. The server is only concerned with the electrical protocol.
  • Pixel Pin: 0901
    • { pin: uint8 }
      • Read + Write
      • pin: Send data out on this pin.
  • Pixel Pin Type: 0902
    • { type: uint8 }
      • Read + Write
      • 0 = WS2812 (NeoPixel), 800 kHz
      • 1 = SPI (APA102: DotStar)
      • [more protocols later]
  • Pixel Data: 0903
    • { start: uint16,  flags: uint8; data: uint8[up to 509] }
      • variable length
      • Write
      • index: start writing data into buffer at this byte number (byte, not pixel)
      • flags:
        • bit 0: 0 = don’t write to pixels yet
        • 1 = write entire buffer to pixels now
      • data: raw array of data for all pixels, in proper color order for type of pixel
  • Pixel Buffer Size: 0904
    • { length: uint16 }
      • Read + Write
      • Size of buffer, in bytes, needed to hold data for entire pixel string
This guide was first published on Dec 16, 2019. It was last updated on Dec 16, 2019.