Skip to content

Instantly share code, notes, and snippets.

@Neradoc
Forked from joeycastillo/arrows.bmp
Created May 15, 2022 03:13
Show Gist options
  • Save Neradoc/17a2e97d38544d086d8a53ad8d1f4d8c to your computer and use it in GitHub Desktop.
Save Neradoc/17a2e97d38544d086d8a53ad8d1f4d8c to your computer and use it in GitHub Desktop.
Bike commuter computer with the Adafruit CLUE
"""
Based on the Bluefruit TFT Gizmo ANCS Notifier for iOS Learn guide:
https://learn.adafruit.com/ancs-gizmo?view=all
"""
import time
import math
import array
import board
import digitalio
import analogio
import audiobusio
import displayio
import adafruit_ble
import adafruit_imageload
import adafruit_lis3mdl
import adafruit_sht31d
from adafruit_ble.advertising.standard import SolicitServicesAdvertisement
from adafruit_ble_apple_notification_center import AppleNotificationCenterService
from babel.babel import Babel # used for Unifont support but you can BYO font
from adafruit_display_text import label
# apps we want to listen for
WHITELIST = ["com.apple.reminders", "com.grailr.CARROTweather", "com.google.Maps", "com.tinyspeck.chatlyio"]
# buttons (not currently used)
DELAY_AFTER_PRESS = 15
DEBOUNCE = 0.1
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)
# sensors
i2c = board.I2C()
sht31 = adafruit_sht31d.SHT31D(i2c)
lis3mdl = adafruit_lis3mdl.LIS3MDL(i2c)
mic = audiobusio.PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA, sample_rate=16000, bit_depth=16)
samples = array.array('H', [0] * 160)
# fonts
babel = Babel()
def normalized_rms(values):
mean_values = int(sum(values) / len(values))
return math.sqrt(sum(float(sample - mean_values) * (sample - mean_values)
for sample in values) / len(values))
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
# Start advertising before messing with the display so that we can connect immediately.
radio = adafruit_ble.BLERadio()
advertisement = SolicitServicesAdvertisement()
advertisement.solicited_services.append(AppleNotificationCenterService)
def wrap_in_tilegrid(open_file):
odb = displayio.OnDiskBitmap(open_file)
return displayio.TileGrid(odb, pixel_shader=displayio.ColorConverter())
display = board.DISPLAY
group = displayio.Group(max_size=10)
group.append(wrap_in_tilegrid(open("/background.bmp", "rb")))
compass_sheet, palette = adafruit_imageload.load("/compass.bmp",
bitmap=displayio.Bitmap,
palette=displayio.Palette)
compass = displayio.TileGrid(compass_sheet, pixel_shader=palette,
width = 1,
height = 1,
tile_width = 31,
tile_height = 31)
compass.x = 209
group.append(compass)
arrow_sheet, arrow_palette = adafruit_imageload.load("/arrows.bmp",
bitmap=displayio.Bitmap,
palette=displayio.Palette)
arrow = displayio.TileGrid(arrow_sheet, pixel_shader=arrow_palette,
width = 1,
height = 1,
tile_width = 64,
tile_height = 64)
arrow.y = 176
group.append(arrow)
heading_label = label.Label(babel.font, max_glyphs=30, color=0xFFFFFF)
heading_label.y = 8
group.append(heading_label)
temp_label = label.Label(babel.font, max_glyphs=10, color=0xFFFFFF)
temp_label.x = 0
temp_label.y = 24
group.append(temp_label)
humidity_label = label.Label(babel.font, max_glyphs=10, color=0xFFFFFF)
humidity_label.x = 72
humidity_label.y = 24
group.append(humidity_label)
noise_label = label.Label(babel.font, max_glyphs=30, color=0xFFFFFF)
noise_label.x = 144
noise_label.y = 24
group.append(noise_label)
main_label = label.Label(babel.font, max_glyphs=7 * 30, color=0xFFFFFF)
main_label.y = 102
group.append(main_label)
directions_label = label.Label(babel.font, max_glyphs=4 * 22, color=0x000000)
directions_label.x = 68
directions_label.y = 208
group.append(directions_label)
display.show(group)
current_notifications = {}
all_ids = []
last_update = None
active_connection, notification_service = find_connection()
# this method hilariously doesn't return anything remotely resembling a standard timestamp,
# i kind of gave up halfway through, but it mostly seems to order things in order.
def iso_to_timestamp(isodate):
years = int(isodate[0:4])
months = int(isodate[4:6])
days = int(isodate[6:8])
hours = int(isodate[9:11])
minutes = int(isodate[11:13])
seconds = int(isodate[13:15])
centuries = years // 100
leaps = centuries // 4
leapDays = 2 - centuries + leaps
yearDays = int(365.25 * (years + 4716))
monthDays = int(30.6001 * (months + 1))
julian_date = int(leapDays + days + monthDays + yearDays -1524.5)
julian_date -= 2458800
return julian_date + (seconds + minutes * 60 + hours * 3600) / 26400
def wrap(string, length):
words = string.split(' ')
wrapped = ""
line_length = 0
for word in words:
if line_length + len(word) <= length:
wrapped += word + ' '
line_length += len(word) + 1
else:
wrapped += '\n' + word + ' '
line_length = len(word) + 1
return wrapped
while True:
if not active_connection:
radio.start_advertising(advertisement)
while not active_connection:
active_connection, notification_service = find_connection()
# Connected
while active_connection.connected:
start_time = None
all_ids.clear()
current_notifications = notification_service.active_notifications
for notif_id in current_notifications:
notification = current_notifications[notif_id]
t = iso_to_timestamp(notification._raw_date)
if start_time is None or t > start_time:
start_time = t
if notification.app_id in WHITELIST:
all_ids.append(notif_id)
else:
del current_notifications[notif_id] # i have way too many notifications to keep in memory
all_ids.sort(key=lambda x: current_notifications[x]._raw_date)
if len(all_ids) == 0:
continue
latest_update = current_notifications[all_ids[len(all_ids) - 1]]
if latest_update != last_update:
# print('LATEST UPDATE:', latest_update._raw_date)
reminders = list()
inthehouse = set()
weather = None
directions = None
text = ""
for notif_id in reversed(all_ids):
current_notification = current_notifications[notif_id]
t = iso_to_timestamp(current_notification._raw_date)
# print(current_notification._raw_date, current_notification.id, start_time - t, current_notification.app_id, current_notification)
if current_notification.app_id == "com.apple.reminders":
reminders.append('- ' + current_notification.title)
elif current_notification.app_id == "com.grailr.CARROTweather" and weather is None:
weather = current_notification.message.replace('↑', 'H').replace('↓', 'L').split('.')[0] + '.'
elif current_notification.app_id == "com.google.Maps" and directions is None:
directions = current_notification.message
elif current_notification.app_id == "com.tinyspeck.chatlyio" and (start_time - t) < 0.5 and current_notification.message.endswith("in the house!"):
# my workshop's slack says something like "Joey is in the house!"; adapt to whatever you want from Slack.
elements = current_notification.message.split(' ')
inthehouse.add(elements[3])
inthehouse_text = ', '.join(inthehouse) if inthehouse else 'No one'
weather_wrapped = wrap(weather, 30) if weather else "No weather forecast."
main_label.text = weather_wrapped + '\nReminders:\n' + ('\n'.join(reminders) if reminders else ' None') + '\nAt the Workshop:\n ' + inthehouse_text
if directions is not None:
directions_label.text = wrap(directions, 21)
directions = directions.lower()
slight = "slight" in directions
if "left" in directions:
arrow[0] = 5 if slight else 1
elif "right" in directions:
arrow[0] = 6 if slight else 2
elif "straight" in directions:
arrow[0] = 3
elif "u-turn" in directions:
arrow[0] = 7
elif "arrive" in directions:
arrow[0] = 4
else:
arrow[0] = 0
else:
arrow[0] = 0
last_update = latest_update
temp = sht31.temperature * 9 / 5 + 32
if temp >= 100:
temp_label.color = 0xFF0000
elif temp >= 90:
temp_label.color = 0xFF9300
elif temp >= 80:
temp_label.color = 0xFFD479
elif temp >= 70:
temp_label.color = 0xD4FB79
elif temp >= 60:
temp_label.color = 0x73FCD6
elif temp >= 50:
temp_label.color = 0x73FDFF
elif temp >= 40:
temp_label.color = 0x76D6FF
elif temp >= 30:
temp_label.color = 0x0096FF
elif temp >= 20:
temp_label.color = 0x0433FF
else:
temp_label.color = 0x0000FF
temp_label.text = str(int(temp)) + '° F'
humidity = sht31.relative_humidity
if humidity <= 20:
humidity_label.color = 0xFFFC79
elif humidity <= 40:
humidity_label.color = 0xD4FB79
elif humidity <= 60:
humidity_label.color = 0x73FA79
elif humidity <= 80:
humidity_label.color = 0x73FCD6
else:
humidity_label.color = 0x73FDFF
humidity_label.text = str(int(humidity)) + '% RH'
mic.record(samples, len(samples))
rms = normalized_rms(samples)
db = 24 + 20 * math.log(rms, 10)
if db < 80:
noise_label.color = 0x00F900
elif db < 100:
noise_label.color = 0xFFCC00
else:
noise_label.color = 0xCC0000
noise_label.text = str(int(db)) + ' dB'
mag_x, mag_y, mag_z = lis3mdl.magnetic
heading = 180 * (math.atan2(mag_y, mag_x) / math.pi)
# print('X:{0:10.2f}, Y:{1:10.2f}, Z:{2:10.2f} uT'.format(mag_x, mag_y, mag_z))
# print(heading)
heading_label.text = "Heading:" + str(int(heading))
compass[0] = int((heading + 22.5 ) / 45) % 8
# Bluetooth Disconnected
active_connection = None
notification_service = None
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment