Code the Bluetooth Snow Globe

Libraries

Now we'll install the libraries that we need to run the code on the Circuit Playground Bluefruit.

Click this link to go to the circuitpython.org Libraries page. Download the latest version of the Bundle library .zip file that matches the version of CircuitPython you're using on the board.

Uncompress the .zip file and then copy the following directories and .mpy files to the lib directory of the CIRCUITPY drive:

  • adafruit_ble
  • adafruit_bluefruit_connect
  • adafruit_bus_device
  • adafruit_lis3dh.mpy
  • neopixel.mpy

Your lib directory on the CIRCUITPY drive should be similar to the picture 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.

Snow Globe 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

import time
import random
import board
import busio
import neopixel
import adafruit_lis3dh

from adafruit_bluefruit_connect.packet import Packet
from adafruit_bluefruit_connect.color_packet import ColorPacket
from adafruit_bluefruit_connect.button_packet import ButtonPacket


from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble.services.nordic import UARTService



#===| User Config |==================================================
SNOWGLOBE_NAME = "SNOWGLOBE" # name that will show up on smart device
DEFAULT_ANIMATION = 0        # 0-3, index in ANIMATIONS list
DEFAULT_DURATION = 5         # total seconds to play animation
DEFAULT_SPEED = 0.1          # delay in seconds between updates
DEFAULT_COLOR = 0xFF0000     # hex color value
DEFAULT_SHAKE = 20           # lower number is more sensitive
# you can define more animation functions below
# here, specify the four to be used
ANIMATIONS = ('spin', 'pulse', 'strobe', 'sparkle')
#===| User Config |==================================================

# Configuration settings
snow_config = {
    'animation' : DEFAULT_ANIMATION,
    'duration' : DEFAULT_DURATION,
    'speed' : DEFAULT_SPEED,
    'color' : DEFAULT_COLOR,
    'shake' : DEFAULT_SHAKE,
}

# Setup NeoPixels
pixels = neopixel.NeoPixel(board.NEOPIXEL, 10)

# Setup accelo
accelo_i2c = busio.I2C(board.ACCELEROMETER_SCL, board.ACCELEROMETER_SDA)
accelo = adafruit_lis3dh.LIS3DH_I2C(accelo_i2c, address=0x19)

# Setup BLE
ble = BLERadio()
uart = UARTService()
advertisement = ProvideServicesAdvertisement(uart)
ble._adapter.name = SNOWGLOBE_NAME #pylint: disable=protected-access

#--| ANIMATIONS |----------------------------------------------------
def spin(config):
    start_time = time.monotonic()
    last_update = start_time
    p = -1
    while time.monotonic() - start_time < config['duration']:
        if time.monotonic() - last_update > config['speed']:
            pixels.fill(0)
            pixels[p % 10] = config['color']
            p -= 1
            last_update = time.monotonic()

def pulse(config):
    start_time = time.monotonic()
    last_update = start_time
    brightness = 0
    delta = 0.05
    pixels.brightness = 0
    pixels.fill(config['color'])
    while time.monotonic() - start_time < config['duration']:
        if time.monotonic() - last_update > config['speed']:
            brightness += delta
            if brightness > 1:
                brightness = 1
                delta *= -1
            if brightness < 0:
                brightness = 0
                delta *= -1
            pixels.brightness = brightness
            last_update = time.monotonic()

def strobe(config):
    start_time = time.monotonic()
    last_update = start_time
    turn_on = True
    while time.monotonic() - start_time < config['duration']:
        if time.monotonic() - last_update > config['speed']:
            if turn_on:
                pixels.fill(config['color'])
            else:
                pixels.fill(0)
            turn_on = not turn_on
            last_update = time.monotonic()

def sparkle(config):
    start_time = time.monotonic()
    last_update = start_time
    while time.monotonic() - start_time < config['duration']:
        if time.monotonic() - last_update > config['speed']:
            pixels.fill(0)
            pixels[random.randint(0, 9)] = config['color']
            last_update = time.monotonic()
#--| ANIMATIONS |----------------------------------------------------

def play_animation(config):
    #pylint: disable=eval-used
    eval(ANIMATIONS[config['animation']])(config)
    pixels.fill(0)

def indicate(event=None):
    if not isinstance(event, str):
        return
    event = event.strip().upper()
    if event == 'START':
        for _ in range(2):
            for i in range(10):
                pixels[i] = DEFAULT_COLOR
                time.sleep(0.05)
                pixels.fill(0)
    if event == 'CONNECTED':
        for _ in range(5):
            pixels.fill(0x0000FF)
            time.sleep(0.1)
            pixels.fill(0)
            time.sleep(0.1)
    if event == 'DISCONNECTED':
        for _ in range(5):
            pixels.fill(0x00FF00)
            time.sleep(0.1)
            pixels.fill(0)
            time.sleep(0.1)

indicate('START')


# Are we already advertising?
advertising = False


while True:
    # While BLE is *not* connected
    while not ble.connected:
        if accelo.shake(snow_config['shake'], 5, 0):
            play_animation(snow_config)
        if not advertising:
            ble.start_advertising(advertisement)
            advertising = True

    # connected
    indicate('CONNECTED')


    while ble.connected:
        # Once we're connected, we're not advertising any more.
        advertising = False

        if accelo.shake(snow_config['shake'], 5, 0):
            play_animation(snow_config)

        if uart.in_waiting:
            try:
                packet = Packet.from_stream(uart)
            except ValueError:
                continue

            if isinstance(packet, ColorPacket):
                #
                # COLOR
                #
                snow_config['color'] = packet.color
                pixels.fill(snow_config['color'])
                time.sleep(0.5)
                pixels.fill(0)

            if isinstance(packet, ButtonPacket) and packet.pressed:
                #
                # SPEED
                #
                if packet.button == ButtonPacket.UP:
                    speed = snow_config['speed'] - 0.05
                    speed = 0.05 if speed < 0.05 else speed
                    snow_config['speed'] = speed
                    play_animation(snow_config)
                if packet.button == ButtonPacket.DOWN:
                    speed = snow_config['speed'] + 0.05
                    snow_config['speed'] = speed
                    play_animation(snow_config)

                #
                # DURATION
                #
                if packet.button == ButtonPacket.LEFT:
                    duration = snow_config['duration'] - 1
                    duration = 1 if duration < 1 else duration
                    snow_config['duration'] = duration
                    play_animation(snow_config)
                if packet.button == ButtonPacket.RIGHT:
                    duration = snow_config['duration'] + 1
                    snow_config['duration'] = duration
                    play_animation(snow_config)

                #
                # ANIMATION
                #
                if packet.button == ButtonPacket.BUTTON_1:
                    snow_config['animation'] = 0
                    play_animation(snow_config)
                if packet.button == ButtonPacket.BUTTON_2:
                    snow_config['animation'] = 1
                    play_animation(snow_config)
                if packet.button == ButtonPacket.BUTTON_3:
                    snow_config['animation'] = 2
                    play_animation(snow_config)
                if packet.button == ButtonPacket.BUTTON_4:
                    snow_config['animation'] = 3
                    play_animation(snow_config)

    # disconnected
    indicate('DISCONNECTED')

Once you've saved the code to the board, the Circuit Playground Bluefruit will swirl the NeoPixel ring once in the default color. It is now ready to be shaken, try it out!

The default animation will play each time you shake the CPB. You can now connect to it with the Bluefruit app on iOS or Android to adjust the color, animation pattern, speed, and duration.

We'll take a closer look at those settings on the next page after we build the snow globe!

This guide was first published on Nov 27, 2019. It was last updated on Nov 27, 2019. This page (Code the Bluetooth Snow Globe) was last updated on Dec 10, 2019.