This commit is contained in:
barry 2025-01-10 15:50:43 +01:00
parent f32f606e56
commit 0bc8f50443
5 changed files with 156 additions and 87 deletions

View File

@ -3,7 +3,7 @@ repos:
rev: v0.9.0 # Use the desired version of ruff
hooks:
- id: ruff
files: spotiplayer_pi/
args: ["--ignore=E711,E721"]
# args: ["--fix"] # Automatically apply fixes where possible
- id: ruff-format
files: spotiplayer_pi/
args: ["--ignore=E711,E721"]

View File

@ -1,4 +1,6 @@
import requests
import warnings
class Api:
def __init__(self, refresh_token: str, base_64: str):
@ -13,7 +15,9 @@ class Api:
"grant_type": "refresh_token",
"refresh_token": self.refresh_token,
}
req = requests.post(uri, data=data, headers={"Authorization": "Basic " + self.base_64}).json()
req = requests.post(
uri, data=data, headers={"Authorization": "Basic " + self.base_64}
).json()
self.access_token = req["access_token"]
self.header = {"Authorization": f"Bearer {self.access_token}"}
return req["expires_in"]
@ -42,8 +46,7 @@ class Api:
if img_urls:
res["img_url"] = img_urls.pop()["url"]
else:
warnings.warn(f"{res['track']} - {res['artists']}\nAlbum art can't be found\n{img_urls}")
warnings.warn(
f"{res['track']} - {res['artists']}\nAlbum art can't be found\n{img_urls}"
)
return res

View File

@ -27,16 +27,25 @@
# THE SOFTWARE.
#
import os
import sys
import time
import spidev
import logging
import numpy as np
class RaspberryPi:
def __init__(self,spi_freq=40000000,rst=27,dc=25,bl=18,bl_freq=1000,i2c=None,i2c_freq=100000):
def __init__(
self,
spi_freq=40000000,
rst=27,
dc=25,
bl=18,
bl_freq=1000,
i2c=None,
i2c_freq=100000,
):
import RPi.GPIO
self.np = np
self.RST_PIN = rst
self.DC_PIN = dc
@ -69,6 +78,7 @@ class RaspberryPi:
def spi_writebyte(self, data):
if self.SPI != None:
self.SPI.writebytes(data)
def bl_DutyCycle(self, duty):
self._pwm.ChangeDutyCycle(duty)
@ -100,12 +110,12 @@ class RaspberryPi:
# self.GPIO.cleanup()
'''
"""
if os.path.exists('/sys/bus/platform/drivers/gpiomem-bcm2835'):
implementation = RaspberryPi()
for func in [x for x in dir(implementation) if not x.startswith('_')]:
setattr(sys.modules[__name__], func, getattr(implementation, func))
'''
"""
### END OF FILE ###

View File

@ -3,9 +3,9 @@ import lcdconfig
class LCD_2inch(lcdconfig.RaspberryPi):
width = 240
height = 320
def command(self, cmd):
self.digital_write(self.DC_PIN, self.GPIO.LOW)
self.spi_writebyte([cmd])
@ -13,6 +13,7 @@ class LCD_2inch(lcdconfig.RaspberryPi):
def data(self, val):
self.digital_write(self.DC_PIN, self.GPIO.HIGH)
self.spi_writebyte([val])
def reset(self):
"""Reset the display"""
self.GPIO.output(self.RST_PIN, self.GPIO.HIGH)
@ -116,21 +117,20 @@ class LCD_2inch(lcdconfig.RaspberryPi):
self.command(0x29)
def SetWindows(self, Xstart, Ystart, Xend, Yend):
# set the X coordinates
self.command(0x2A)
self.data(Xstart >> 8) # Set the horizontal starting point to the high octet
self.data(Xstart & 0xff) #Set the horizontal starting point to the low octet
self.data(Xstart & 0xFF) # Set the horizontal starting point to the low octet
self.data(Xend >> 8) # Set the horizontal end to the high octet
self.data((Xend - 1) & 0xff)#Set the horizontal end to the low octet
self.data((Xend - 1) & 0xFF) # Set the horizontal end to the low octet
# set the Y coordinates
self.command(0x2B)
self.data(Ystart >> 8)
self.data((Ystart & 0xff))
self.data((Ystart & 0xFF))
self.data(Yend >> 8)
self.data((Yend - 1) & 0xff )
self.data((Yend - 1) & 0xFF)
self.command(0x2C)
@ -142,8 +142,14 @@ class LCD_2inch(lcdconfig.RaspberryPi):
img = self.np.asarray(Image)
pix = self.np.zeros((self.width, self.height, 2), dtype=self.np.uint8)
# RGB888 >> RGB565
pix[...,[0]] = self.np.add(self.np.bitwise_and(img[...,[0]],0xF8),self.np.right_shift(img[...,[1]],5))
pix[...,[1]] = self.np.add(self.np.bitwise_and(self.np.left_shift(img[...,[1]],3),0xE0), self.np.right_shift(img[...,[2]],3))
pix[..., [0]] = self.np.add(
self.np.bitwise_and(img[..., [0]], 0xF8),
self.np.right_shift(img[..., [1]], 5),
)
pix[..., [1]] = self.np.add(
self.np.bitwise_and(self.np.left_shift(img[..., [1]], 3), 0xE0),
self.np.right_shift(img[..., [2]], 3),
)
pix = pix.flatten().tolist()
self.command(0x36)
@ -157,8 +163,14 @@ class LCD_2inch(lcdconfig.RaspberryPi):
img = self.np.asarray(Image)
pix = self.np.zeros((imheight, imwidth, 2), dtype=self.np.uint8)
pix[...,[0]] = self.np.add(self.np.bitwise_and(img[...,[0]],0xF8),self.np.right_shift(img[...,[1]],5))
pix[...,[1]] = self.np.add(self.np.bitwise_and(self.np.left_shift(img[...,[1]],3),0xE0), self.np.right_shift(img[...,[2]],3))
pix[..., [0]] = self.np.add(
self.np.bitwise_and(img[..., [0]], 0xF8),
self.np.right_shift(img[..., [1]], 5),
)
pix[..., [1]] = self.np.add(
self.np.bitwise_and(self.np.left_shift(img[..., [1]], 3), 0xE0),
self.np.right_shift(img[..., [2]], 3),
)
pix = pix.flatten().tolist()
@ -171,7 +183,7 @@ class LCD_2inch(lcdconfig.RaspberryPi):
def clear(self):
"""Clear contents of image buffer"""
_buffer = [0xff]*(self.width * self.height * 2)
_buffer = [0xFF] * (self.width * self.height * 2)
self.SetWindows(0, 0, self.height, self.width)
self.digital_write(self.DC_PIN, self.GPIO.HIGH)
for i in range(0, len(_buffer), 4096):

View File

@ -45,7 +45,12 @@ def display_loop(api: Api, cfg: Dict):
not_playing_img.paste(spoti_logo, (0, 10))
not_playing_img.paste(qr, (120, 40))
draw = ImageDraw.Draw(not_playing_img)
draw.text((124, 10), "Connect to speakers", font=Font1, fill=cfg["color_theme"]["text"])
draw.text(
(124, 10),
"Connect to speakers",
font=Font1,
fill=cfg["color_theme"]["text"],
)
del spoti_logo, qr, draw
last_api_call = 0
@ -59,7 +64,7 @@ def display_loop(api: Api, cfg: Dict):
if time.time() - last_auth_refresh >= auth_interval:
auth_interval = api.refreshAuth() - 120
last_auth_refresh = time.time()
print(f'Refreshed auth at {datetime.now().strftime("%d-%m %H:%M:%S")}')
print(f"Refreshed auth at {datetime.now().strftime('%d-%m %H:%M:%S')}")
if time.time() - last_api_call >= cfg["api_interval"]:
data = api.getPlaying()
@ -73,7 +78,12 @@ def display_loop(api: Api, cfg: Dict):
draw = ImageDraw.Draw(not_playing_img)
current_time = datetime.now().time()
draw.rectangle([(00, 120), (119, 170)], fill=(0, 0, 0))
draw.text((20, 120), f"{current_time.hour}:{current_time.minute:02d}", font=Font2, angle=0)
draw.text(
(20, 120),
f"{current_time.hour}:{current_time.minute:02d}",
font=Font2,
angle=0,
)
if current_mode != 0:
current_mode = 0
print("Standby mode")
@ -83,35 +93,70 @@ def display_loop(api: Api, cfg: Dict):
elif type(data) == dict:
current_track = data["track"] + data["artists"][0] + data["album"]
if current_track != last_track:
print('updating track')
print("updating track")
last_track = current_track
img = None
try:
img = requests.get(data["img_url"], timeout=cfg["refresh_interval"]/2)
img = requests.get(
data["img_url"], timeout=cfg["refresh_interval"] / 2
)
img = Image.open(BytesIO(img.content))
except Exception as e:
warnings.warn(f"Failed to fetch album cover at: {data['img_url']}\n{e}")
warnings.warn(
f"Failed to fetch album cover at: {data['img_url']}\n{e}"
)
if img == None:
img = unavailable_img
img = img.resize((128, 128))
bg = Image.new("RGB", (320, 240), cfg["color_theme"]["background"])
bg.paste(img, (10, 30))
draw = ImageDraw.Draw(bg)
draw.text((150, 40), "\n".join(textwrap.wrap(", ".join(data["artists"]), width=16)), font=Font1, fill=cfg["color_theme"]["text"])
draw.text((10, 160), "\n".join(textwrap.wrap(data["track"], width=32)), font=Font1, fill=cfg["color_theme"]["text"])
draw.text(
(150, 40),
"\n".join(textwrap.wrap(", ".join(data["artists"]), width=16)),
font=Font1,
fill=cfg["color_theme"]["text"],
)
draw.text(
(10, 160),
"\n".join(textwrap.wrap(data["track"], width=32)),
font=Font1,
fill=cfg["color_theme"]["text"],
)
w, h = bg.size
w -= 92
progress_time = data["progress_ms"] + int((time.time() - last_api_call) * 1000)
progress_time = data["progress_ms"] + int(
(time.time() - last_api_call) * 1000
)
progress = progress_time / data["duration_ms"]
bar_width = int(w * progress)
draw.rectangle([(8, h - 22), (w + 2, h - 8)], outline=cfg["color_theme"]["bar_outline"])
draw.rectangle([(10, h - 20), (w, h - 10)], fill=cfg["color_theme"]["background"])
draw.rectangle([(10, h - 20), (bar_width, h - 10)], fill=cfg["color_theme"]["bar_inside"])
f_current_time = "{:02.0f}:{:02.0f}".format(*divmod(progress_time // 1000, 60))
f_total_time = "{:02d}:{:02d}".format(*divmod(data["duration_ms"]// 1000, 60))
draw.rectangle([(234, 215), (310, 235)], fill=cfg["color_theme"]["background"])
draw.text((235, 215), f"{f_current_time}/{f_total_time}", font=Font0, fill=cfg["color_theme"]["text"])
draw.rectangle(
[(8, h - 22), (w + 2, h - 8)],
outline=cfg["color_theme"]["bar_outline"],
)
draw.rectangle(
[(10, h - 20), (w, h - 10)], fill=cfg["color_theme"]["background"]
)
draw.rectangle(
[(10, h - 20), (bar_width, h - 10)],
fill=cfg["color_theme"]["bar_inside"],
)
f_current_time = "{:02.0f}:{:02.0f}".format(
*divmod(progress_time // 1000, 60)
)
f_total_time = "{:02d}:{:02d}".format(
*divmod(data["duration_ms"] // 1000, 60)
)
draw.rectangle(
[(234, 215), (310, 235)], fill=cfg["color_theme"]["background"]
)
draw.text(
(235, 215),
f"{f_current_time}/{f_total_time}",
font=Font0,
fill=cfg["color_theme"]["text"],
)
d.ShowImage(bg)
time.sleep(cfg["refresh_interval"])
@ -125,4 +170,3 @@ if __name__ == "__main__":
cfg = parse_config()
api = Api(cfg["refresh_token"], cfg["base_64"])
display_loop(api, cfg)