We've created this fairly simple program that allows your CPB to connect to an iOS device, Pair and Bond (so they'll auto-reconnect later), and send data between the two devices to display track info and playback commands.

Libraries

First, make sure you have these libraries that you copied over to the board following this guide page

  • adafruit_ble
  • adafruit_bus_device
  • adafruit_circuitplayground
  • adafruit_lis3dh.mpy
  • adafruit_st7789.mpy
  • adafruit_thermistor.mpy
  • neopixel.mpy

Then, we'll also add some libraries for dealing with the Apple Media Service, the TFT display, and the Circuit Playground buttons and switch.

From the library bundle you downloaded in that guide page, transfer the following library onto the CPB boards' /lib directory:

  • adafruit_bitmap_font
  • adafruit_display_shapes
  • adafruit_display_text
  • adafruit_gizmo
  • adafruit_ble_apple_media.mpy

Your CBP should look like the screenshot above.

You'll also need to get the fonts and .bmp images for the project. Click the "Download: Project Zip" link in the code block below to get all the files from the project's GitHub repo. 

Then, uncompress the zip file and open the code.py file in Mu, then save it to your CPB's CIRCUITPY drive as code.py. 

Text Editor

Adafruit recommends using the Mu editor for using your CircuitPython code with the Circuit Playground Bluefruit boards. You can get more info in this guide.

Alternatively, you can use any text editor that saves files.

# SPDX-FileCopyrightText: 2020 Melissa LeBlanc-Williams for Adafruit Industries
#
# SPDX-License-Identifier: MIT

"""
This example solicits that apple devices that provide notifications connect to it, initiates
pairing, prints existing notifications and then prints any new ones as they arrive.
"""

import time
import displayio
import terminalio
from adafruit_gizmo import tft_gizmo
from adafruit_display_text.label import Label
from adafruit_display_shapes.rect import Rect
from adafruit_bitmap_font import bitmap_font
import adafruit_ble
from adafruit_ble.advertising.standard import SolicitServicesAdvertisement
from adafruit_ble_apple_media import AppleMediaService
from adafruit_ble_apple_media import UnsupportedCommand
from adafruit_circuitplayground import cp

BACKGROUND_COLOR = 0x49523b  # Gray
TEXT_COLOR = 0xFF0000  # Red
BORDER_COLOR = 0xAAAAAA  # Light Gray
STATUS_COLOR = BORDER_COLOR

# PyLint can't find BLERadio for some reason so special case it here.
radio = adafruit_ble.BLERadio() # pylint: disable=no-member
radio.name = "Now Playing Gizmo"
a = SolicitServicesAdvertisement()
a.solicited_services.append(AppleMediaService)
radio.start_advertising(a)

def wrap_in_tilegrid(filename:str):
    # CircuitPython 6 & 7 compatible
    odb = displayio.OnDiskBitmap(open(filename, "rb"))
    return displayio.TileGrid(
        odb, pixel_shader=getattr(odb, 'pixel_shader', displayio.ColorConverter())
    )

    # # CircuitPython 7+ compatible
    # odb = displayio.OnDiskBitmap(filename)
    # return displayio.TileGrid(odb, pixel_shader=odb.pixel_shader)

def make_background(width, height, color):
    color_bitmap = displayio.Bitmap(width, height, 1)
    color_palette = displayio.Palette(1)
    color_palette[0] = color

    return displayio.TileGrid(color_bitmap,
                              pixel_shader=color_palette,
                              x=0, y=0)

def load_font(fontname, text):
    font = bitmap_font.load_font(fontname)
    font.load_glyphs(text.encode('utf-8'))
    return font

def make_label(text, x, y, color, font=terminalio.FONT):
    if isinstance(font, str):
        font = load_font(font, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.,?()")
    text_area = Label(font, text=text, color=color)
    text_area.x = x
    text_area.y = y
    return text_area

def set_label(label, value, max_length):
    text = "{}".format(value)
    if len(text) > max_length:
        text = text[:max_length-3] + "..."
    label.text = text

def set_status(label, action_text, player):
    label.text = "{} on {}".format(action_text, player)
    _, _, label_width, _ = label.bounding_box
    label.x = display.width - 10 - label_width

display = tft_gizmo.TFT_Gizmo()
group = displayio.Group()
display.show(group)

while True:
    if not radio.connected:
        group.append(wrap_in_tilegrid("/graphic_tfts_ams_connect.bmp"))

        while not radio.connected:
            pass

        group.pop()
    print("connected")

    known_notifications = set()

    # Draw the text fields
    print("Loading Font Glyphs...")
    group.append(wrap_in_tilegrid("/graphic_tfts_ams_loading.bmp"))
    title_label = make_label("None", 12, 30, TEXT_COLOR, font="/fonts/Arial-Bold-18.bdf")
    artist_label = make_label("None", 12, 70, TEXT_COLOR, font="/fonts/Arial-16.bdf")
    album_label = make_label("None", 12, 184, TEXT_COLOR, font="/fonts/Arial-16.bdf")
    status_label = make_label("None", 80, 220, STATUS_COLOR, font="/fonts/Arial-16.bdf")
    group.pop()
    group.append(make_background(240, 240, BACKGROUND_COLOR))
    border = Rect(4, 4, 232, 200, outline=BORDER_COLOR, stroke=2)
    group.append(title_label)
    group.append(artist_label)
    group.append(album_label)
    group.append(status_label)
    group.append(border)

    while radio.connected:
        for connection in radio.connections:
            try:
                if not connection.paired:
                    connection.pair()
                    print("paired")

                ams = connection[AppleMediaService]
            except (RuntimeError, UnsupportedCommand, AttributeError):
                # Skip Bad Packets, unknown commands, etc.
                continue
            set_label(title_label, ams.title, 18)
            set_label(album_label, ams.album, 21)
            set_label(artist_label, ams.artist, 21)
            action = "?"
            if ams.playing:
                action = "Playing"
            elif ams.paused:
                action = "Paused"
            set_status(status_label, action, ams.player_name)
        if cp.button_a:
            ams.toggle_play_pause()
            time.sleep(0.1)

        if cp.button_b:
            if cp.switch:
                ams.previous_track()
            else:
                ams.next_track()
            time.sleep(0.1)

    print("disconnected")
    # Remove all layers
    while len(group):
        group.pop()

This guide was first published on Jan 26, 2020. It was last updated on Jan 26, 2020.

This page (Code the Apple Media Service Display) was last updated on Oct 01, 2023.

Text editor powered by tinymce.