The Apple Notification Center Service (ANCS) allows iOS devices to act as a provider of notification alerts to Bluetooth Low Energy (BLE) accessories, such as the Apple Watch, or in our case, the Circuit Playground Bluefruit!
We've created a simple program that allows the CPB to pair with your iOS device over Bluetooth and then it will receive ANCS notifications to display on the TFT Gizmo.
In general, we recommend installing the libraries mentioned on the previous page for your Circuit Playground Bluefruit projects, however you can get away with a subset of them for this one. You can see them listed in the image here.
You'll also need to add the adafruit_ble_apple_notification_center.mpy file from the library bundle as seen in the image here.
Once you save the code.py, graphics, and .wav file to your CIRCUITPY drive as directed below, this image is what your drive should look like.
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.
# SPDX-FileCopyrightText: 2019 Melissa LeBlanc-Williams for Adafruit Industries # # SPDX-License-Identifier: MIT """ This demo shows the latest icons from a connected Apple device on a TFT Gizmo screen. The A and B buttons on the CircuitPlayground Bluefruit can be used to scroll through all active notifications. The screen's backlight will turn off after a certain number of seconds to save power. New notifications or pressing the buttons should turn it back on. """ import time import board import digitalio import displayio import adafruit_ble from adafruit_ble.advertising.standard import SolicitServicesAdvertisement from adafruit_ble_apple_notification_center import AppleNotificationCenterService from adafruit_gizmo import tft_gizmo from audiocore import WaveFile from audiopwmio import PWMAudioOut as AudioOut # Enable the speaker speaker_enable = digitalio.DigitalInOut(board.SPEAKER_ENABLE) speaker_enable.direction = digitalio.Direction.OUTPUT speaker_enable.value = True audio = AudioOut(board.SPEAKER) # This is a whitelist of apps to show notifications from. APP_ICONS = { "com.tinyspeck.chatlyio": "/ancs_slack.bmp", "com.basecamp.bc3-ios": "/ancs_basecamp.bmp", "com.apple.MobileSMS": "/ancs_sms.bmp", "com.hammerandchisel.discord": "/ancs_discord.bmp", "com.apple.mobilecal": "/ancs_ical.bmp", "com.apple.mobilephone": "/ancs_phone.bmp" } BLOCKLIST = [] DELAY_AFTER_PRESS = 15 DEBOUNCE = 0.1 DIM_TIMEOUT = 20 # Amount of timeout to turn off backlight DIM_LEVEL = 0.05 a = digitalio.DigitalInOut(board.BUTTON_A) a.switch_to_input(pull=digitalio.Pull.DOWN) b = digitalio.DigitalInOut(board.BUTTON_B) b.switch_to_input(pull=digitalio.Pull.DOWN) file = open("/triode_rise.wav", "rb") wave = WaveFile(file) def play_sound(): audio.play(wave) time.sleep(1) def find_connection(): for connection in radio.connections: if AppleNotificationCenterService not in connection: continue if not connection.paired: connection.pair() return connection, connection[AppleNotificationCenterService] return None, None class Dimmer: def __init__(self): self._update_time = time.monotonic() self._level = DIM_LEVEL self._timeout = DIM_TIMEOUT def update(self): self._update_time = time.monotonic() def check_timeout(self): if a.value or b.value: self._update_time = time.monotonic() if time.monotonic() - self._update_time > self._timeout: if display.brightness > self._level: display.brightness = self._level else: if display.brightness == self._level: display.brightness = 1.0 dimmer = Dimmer() # Start advertising before messing with the display so that we can connect immediately. radio = adafruit_ble.BLERadio() advertisement = SolicitServicesAdvertisement() advertisement.complete_name = "CIRCUITPY" advertisement.solicited_services.append(AppleNotificationCenterService) 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) display = tft_gizmo.TFT_Gizmo() group = displayio.Group() group.append(wrap_in_tilegrid("/ancs_connect.bmp")) display.root_group = group current_notification = None current_notifications = {} all_ids = [] last_press = time.monotonic() active_connection, notification_service = find_connection() cleared = False while True: if not active_connection: radio.start_advertising(advertisement) while not active_connection: active_connection, notification_service = find_connection() dimmer.check_timeout() # Connected dimmer.update() play_sound() no_notifications = "/ancs_none.bmp" group.append(wrap_in_tilegrid(no_notifications)) while active_connection.connected: all_ids.clear() current_notifications = notification_service.active_notifications for notif_id in current_notifications: notification = current_notifications[notif_id] if notification.app_id not in APP_ICONS or notification.app_id in BLOCKLIST: continue all_ids.append(notif_id) # pylint: disable=protected-access all_ids.sort(key=lambda x: current_notifications[x]._raw_date) # pylint: enable=protected-access if current_notification and current_notification.removed: # Stop showing the latest and show that there are no new notifications. current_notification = None if not current_notification and not all_ids and not cleared: cleared = True dimmer.update() group[1] = wrap_in_tilegrid(no_notifications) elif all_ids: cleared = False now = time.monotonic() if current_notification and current_notification.id in all_ids and \ now - last_press < DELAY_AFTER_PRESS: index = all_ids.index(current_notification.id) else: index = len(all_ids) - 1 if now - last_press >= DEBOUNCE: if b.value and index > 0: last_press = now index += -1 if a.value and index < len(all_ids) - 1: last_press = now index += 1 notif_id = all_ids[index] if not current_notification or current_notification.id != notif_id: dimmer.update() current_notification = current_notifications[notif_id] # pylint: disable=protected-access print(current_notification._raw_date, current_notification) # pylint: enable=protected-access group[1] = wrap_in_tilegrid(APP_ICONS[current_notification.app_id]) dimmer.check_timeout() # Bluetooth Disconnected group.pop() dimmer.update() active_connection = None notification_service = None
Code Explaination
Apple devices centralize the management of notifications into the "Notification Center", which is accessed on the lock screen or after swiping down from the top of the screen. Since this info is centralized, Apple provides access to the current notifications through a Bluetooth Low Energy Service on the device. Bluetooth Services are a collection of data referred to as Characteristics. In CircuitPython libraries we provide definitions for common services like the Apple Notification Center Service (or ANCS for short) so that they are easier to use.
The ANCS library is designed for two main uses. First, it allows one to list all currently active notifications. This is done by reading the active_notifications
attribute of the service. Second, it allows you to wait for new notifications to come in and react to them. This is done by looping over wait_for_new_notifications()
.
Both of these uses provide a Notification object for each active notification. Reading the attributes of these objects will load the data from the peer device as needed. This can make printing slow but it conserves data which in turn saves battery. So, only read the attributes you need to know. Printing the object is useful for debugging but it loads many attributes and therefore, takes time.
The most useful attribute for this project is the app_id
. The app_id
identifies which app generated the notification. It is not the app name, but may be derived from it or the name of the folks who created the app. In the example code.py we've already added a few app_id
s. For example, Twitter is com.atebits.Tweetie2
, Slack is com.tinyspeck.chatlyio
and GMail is com.google.Gmail
. To add support for an app you use, connect to the serial terminal while running the example the Apple Notification Center library's simple test, which prints out all of the current notification's app_ids and title.
Page last edited January 22, 2025
Text editor powered by tinymce.