-
-
Save Neradoc/17a2e97d38544d086d8a53ad8d1f4d8c to your computer and use it in GitHub Desktop.
Bike commuter computer with the Adafruit CLUE
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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