Plug your Adafruit LED Glasses Driver - nRF52840 Sensor Board into the computer's USB port with a known good power+data USB cable. You should see a new flash drive appear in the computer's File Explorer or Finder (depending on your operating system) called CIRCUITPY.

Click the "Download Project Bundle" button under the Code section below. 

Unzip the folder and copy the following items to the CIRCUITPY drive:

  • lib folder
  • code.py

The project uses the lib folder libraries to access the accelerometer and to be able to send play and pause commands to an Apple mobile device. 

  • adafruit_ble
  • adafruit_ble_apple_media
  • adafruit_lis3dh
  • neopixel

Your CIRCUITPY drive should contain the files listed below:

# SPDX-FileCopyrightText: 2022 Charlyn G for Adafruit Industries
#
# SPDX-License-Identifier: MIT
#
# Code for the Adafruit Learning System tutorial
#   Exercise Buddy: Motion aware BLE media controller
#   https://learn.adafruit.com/exercise-buddy/overview
#
import time
import board
import supervisor

import neopixel
import adafruit_ble
import adafruit_lis3dh
from adafruit_ble.advertising.standard import SolicitServicesAdvertisement
from adafruit_ble_apple_media import AppleMediaService, UnsupportedCommand

# Initialize the accelerometer
i2c = board.I2C()
lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c)

#  Initialize BLE radio
radio = adafruit_ble.BLERadio()
a = SolicitServicesAdvertisement()
a.solicited_services.append(AppleMediaService)
radio.start_advertising(a)

# Neopixel indicator
pixel_pin = board.NEOPIXEL
pixel = neopixel.NeoPixel(pixel_pin, 1, brightness=0.5)
YELLOW = (200, 150, 0)
CYAN = (0, 100, 100)
PINK = (231, 84, 128)
pixel.fill(PINK)

while not radio.connected:
    pass

print("connected")
pixel.fill(YELLOW)

# Initialize variables
last_x = 0
last_y = 0
last_z = 0
paused = True

WAIT = 0.2
WIGGLE_ROOM = 5  # Increase this for more jitter compensation.


def is_same_pos(last_position, current_position):
    # Returns true if current_position is similar enough
    # to last_position, within the specified wiggle room.
    diff = abs(current_position - last_position)
    print((diff,))
    return diff <= WIGGLE_ROOM


def not_enough_movement(x, y, z):
    same_x = is_same_pos(last_x, x)
    same_y = is_same_pos(last_y, y)
    same_z = is_same_pos(last_z, z)
    return same_x and same_y and same_z


while radio.connected:

    for connection in radio.connections:
        if not connection.paired:
            connection.pair()
            print("paired")
            pixel.fill(PINK)
            time.sleep(1)

        if connection.paired:
            pixel.fill(CYAN)
            ams = connection[AppleMediaService]
            print("app:", ams.player_name)

            try:
                xf, yf, zf = lis3dh.acceleration

                if not_enough_movement(xf, yf, zf):
                    # Keep pausing.
                    print("pause!")
                    paused = True
                    ams.pause()

                else:
                    last_x = xf
                    last_y = yf
                    last_z = zf

                    if paused:
                        print("play!")
                        paused = False
                        ams.play()

            except OSError:
                supervisor.reload()
            except UnsupportedCommand:
                # This means that we tried to pause but there's
                # probably nothing playing yet, so just wait a bit
                # and try again.
                pixel.fill(PINK)
                time.sleep(10)
                supervisor.reload()

            time.sleep(WAIT)

print("disconnected")
pixel.fill(PINK)

Code

This board has a built-in accelerometer to detect and calculate movement. To get a rundown of the basics of how accelerometers work, check out the excellent Adafruit guide Make It Shake, Rattle, and Roll: Accelerometer Use.

The idea behind the code is simple: remember the last x, y and z position, and check to see if there has been enough movement in each axis. If there has not been enough movement since the last saved position, send a pause command to the Apple mobile device (and keep sending the pause command so that the video stays paused even if you press "play" on the device itself). Devious, but necessary.

Also the onboard NeoPixel is used as a status indicator, so that you can get a clue about what the device is up to, even when it's not connected to a computer. 

Pair your Apple device to your project and take it for a spin! If you are not moving, the audio should be paused.

Literally, attach it to your stationary bike pedal and get ready to be motivated to keep pace. You might also consider attaching it to your body if you're doing some other form of physical exercise, but you may have to fiddle around with the WIGGLE_ROOM value depending on what kind of movement is involved.

Good luck and have a good workout!

This guide was first published on Feb 14, 2022. It was last updated on 2022-02-14 17:37:18 -0500.

This page (CircuitPython libraries and code) was last updated on Apr 23, 2022.

Text editor powered by tinymce.