210 lines
7.2 KiB
Python
210 lines
7.2 KiB
Python
# SPDX-FileCopyrightText: 2024 Liz Clark for Adafruit Industries
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
import time
|
|
import gc
|
|
import board
|
|
import displayio
|
|
import terminalio
|
|
import adafruit_ble
|
|
from adafruit_ble.advertising.standard import SolicitServicesAdvertisement
|
|
from adafruit_ble_apple_media import AppleMediaService
|
|
from adafruit_bitmap_font import bitmap_font
|
|
from adafruit_button import Button
|
|
from adafruit_display_text import label, wrap_text_to_lines
|
|
import adafruit_hx8357
|
|
import adafruit_tsc2007
|
|
|
|
gc.collect()
|
|
displayio.release_displays()
|
|
small_font = bitmap_font.load_font("/Arial-16.bdf")
|
|
|
|
# display
|
|
spi = board.SPI()
|
|
tft_cs = board.D9
|
|
tft_dc = board.D10
|
|
display_bus = displayio.FourWire(spi, command=tft_dc, chip_select=tft_cs)
|
|
display = adafruit_hx8357.HX8357(display_bus, width=480, height=320)
|
|
# touch
|
|
i2c = board.I2C()
|
|
tsc = adafruit_tsc2007.TSC2007(i2c, invert_x=True, swap_xy=True)
|
|
splash = displayio.Group()
|
|
|
|
def ble_media_command(conn, command):
|
|
gc.collect()
|
|
# print("function started")
|
|
if not conn.paired:
|
|
print("trying to pair")
|
|
conn.pair()
|
|
print("paired")
|
|
# print("connected, getting ready")
|
|
ams = conn[AppleMediaService]
|
|
# print(ams)
|
|
tries = 10
|
|
for i in range(tries):
|
|
try:
|
|
# print("sending..")
|
|
if command != "refresh":
|
|
command(ams)
|
|
time.sleep(2)
|
|
# print("sent")
|
|
song_text = f"{ams.title}"
|
|
song_text = "\n".join(wrap_text_to_lines(song_text, 29))
|
|
song_label.text = song_text
|
|
album_label.text = f"Album: {ams.album}"
|
|
artist_label.text = f"Artist: {ams.artist}"
|
|
app_label.text = f"App: {ams.player_name}"
|
|
if ams.playing:
|
|
buttons[0].label = "Pause"
|
|
elif ams.paused:
|
|
buttons[0].label = "Play"
|
|
except Exception as error: # pylint: disable = broad-except
|
|
print(error)
|
|
# time.sleep(2)
|
|
if i < tries - 1:
|
|
continue
|
|
break
|
|
gc.collect()
|
|
|
|
connection = None
|
|
|
|
# commands
|
|
commands = [
|
|
lambda ams: ams.toggle_play_pause(),
|
|
lambda ams: ams.next_track(),
|
|
lambda ams: ams.previous_track(),
|
|
lambda ams: ams.advance_repeat_mode(),
|
|
lambda ams: ams.advance_shuffle_mode(),
|
|
lambda ams: ams.volume_up(),
|
|
lambda ams: ams.volume_down(),
|
|
]
|
|
|
|
# colors
|
|
RED = (255, 0, 0)
|
|
ORANGE = (255, 34, 0)
|
|
YELLOW = (255, 170, 0)
|
|
GREEN = (0, 255, 0)
|
|
CYAN = (0, 255, 255)
|
|
BLUE = (0, 0, 255)
|
|
VIOLET = (153, 0, 255)
|
|
MAGENTA = (255, 0, 51)
|
|
PINK = (255, 51, 119)
|
|
AQUA = (85, 125, 255)
|
|
WHITE = (255, 255, 255)
|
|
OFF = (0, 0, 0)
|
|
|
|
gc.collect()
|
|
spots = [
|
|
{'label': "Play/Pause", 'font': small_font,
|
|
'pos': ((display.width // 2) - 60, display.height - 70),
|
|
'size': (120, 60), 'color': RED,
|
|
'control': lambda: ble_media_command(connection, commands[0])},
|
|
{'label': ">>", 'font': small_font,
|
|
'pos': ((display.width // 2) + 70, display.height - 70),
|
|
'size': (60, 60), 'color': ORANGE,
|
|
'control': lambda: ble_media_command(connection, commands[1])},
|
|
{'label': "<<", 'font': small_font,
|
|
'pos': ((display.width // 2) - 130, display.height - 70),
|
|
'size': (60, 60), 'color': YELLOW,
|
|
'control': lambda: ble_media_command(connection, commands[2])},
|
|
{'label': "Repeat", 'font': terminalio.FONT,
|
|
'pos': (10, 250), 'size': (60, 60), 'color': GREEN,
|
|
'control': lambda: ble_media_command(connection, commands[3])},
|
|
{'label': "Shuffle", 'font': terminalio.FONT,
|
|
'pos': (410, 250), 'size': (60, 60), 'color': CYAN,
|
|
'control': lambda: ble_media_command(connection, commands[4])},
|
|
{'label': "Vol +", 'font': small_font, 'pos': (10, 10), 'size': (60, 120), 'color': BLUE,
|
|
'control': lambda: ble_media_command(connection, commands[5])},
|
|
{'label': "Vol -", 'font': small_font, 'pos': (410, 10), 'size': (60, 120), 'color': VIOLET,
|
|
'control': lambda: ble_media_command(connection, commands[6])},
|
|
{'label': "Refresh", 'font': small_font, 'pos': (70, 10), 'size': (340, display.height - 80),
|
|
'color': None, 'control': lambda: ble_media_command(connection, "refresh")},
|
|
]
|
|
|
|
buttons = []
|
|
for spot in spots:
|
|
button = Button(x=spot['pos'][0], y=spot['pos'][1],
|
|
width=spot['size'][0], height=spot['size'][1],
|
|
label=spot['label'], label_font=spot['font'],
|
|
style=Button.ROUNDRECT,
|
|
fill_color=spot['color'],
|
|
name=spot['label'])
|
|
splash.append(button)
|
|
buttons.append(button)
|
|
|
|
header_label = label.Label(small_font, text="Now Playing:", color=WHITE)
|
|
header_label.anchor_point = (0.5, 0.0)
|
|
header_label.anchored_position = (display.width / 2, 10)
|
|
splash.append(header_label)
|
|
|
|
song_label = label.Label(small_font, text=" ", color=WHITE)
|
|
song_label.anchor_point = (0.5, 0.0)
|
|
song_label.anchored_position = (display.width / 2, 40)
|
|
splash.append(song_label)
|
|
artist_label = label.Label(small_font, text="Artist: ", color=WHITE)
|
|
artist_label.anchor_point = (0.5, 0.0)
|
|
artist_label.anchored_position = (display.width / 2, 124)
|
|
splash.append(artist_label)
|
|
album_label = label.Label(small_font, text="Album: ", color=WHITE)
|
|
album_label.anchor_point = (0.5, 0.0)
|
|
album_label.anchored_position = (display.width / 2, 154)
|
|
splash.append(album_label)
|
|
app_label = label.Label(small_font, text="App: ", color=WHITE)
|
|
app_label.anchor_point = (0.5, 0.0)
|
|
app_label.anchored_position = (display.width / 2, 184)
|
|
splash.append(app_label)
|
|
|
|
touch_state = False
|
|
|
|
display.root_group = splash
|
|
|
|
def scale_touch_coordinates(raw_x, raw_y):
|
|
# raw coordinate ranges
|
|
raw_x_min = 275
|
|
raw_x_max = 3900
|
|
raw_y_min = 487
|
|
raw_y_max = 3800
|
|
# scale the raw coordinates to display coordinates
|
|
display_x = (raw_x - raw_x_min) * display.width / (raw_x_max - raw_x_min)
|
|
display_y = (raw_y - raw_y_min) * display.height / (raw_y_max - raw_y_min)
|
|
# clamp values to ensure they stay within display bounds
|
|
display_x = max(0, min(display_x, display.width))
|
|
display_y = max(0, min(display_y, display.height))
|
|
return int(display_x), int(display_y)
|
|
|
|
gc.collect()
|
|
# PyLint can't find BLERadio for some reason so special case it here.
|
|
radio = adafruit_ble.BLERadio() # pylint: disable=no-member
|
|
a = SolicitServicesAdvertisement()
|
|
a.solicited_services.append(AppleMediaService)
|
|
if not radio.connected:
|
|
print("advertising")
|
|
radio.start_advertising(a)
|
|
else:
|
|
print("already connected")
|
|
print(radio.connected)
|
|
print(gc.mem_free())
|
|
|
|
while True:
|
|
while not radio.connected:
|
|
pass
|
|
known_notifications = set()
|
|
while radio.connected:
|
|
if tsc.touched and not touch_state:
|
|
gc.collect()
|
|
touch_state = True
|
|
p = tsc.touch
|
|
point = scale_touch_coordinates(p["x"], p["y"])
|
|
connection = radio.connections[0]
|
|
for button in buttons:
|
|
b = buttons.index(button)
|
|
if button.contains(point):
|
|
print(gc.mem_free())
|
|
print("Touched", button.name)
|
|
spots[b]['control']()
|
|
if not tsc.touched and touch_state:
|
|
touch_state = False
|
|
print("disconnected")
|
|
print("advertising")
|
|
radio.start_advertising(a)
|
|
print("reconnected")
|