format
This commit is contained in:
parent
f32f606e56
commit
0bc8f50443
@ -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"]
|
||||
|
@ -1,4 +1,6 @@
|
||||
import requests
|
||||
import warnings
|
||||
|
||||
|
||||
class Api:
|
||||
def __init__(self, refresh_token: str, base_64: str):
|
||||
@ -13,11 +15,13 @@ 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"]
|
||||
|
||||
|
||||
def getPlaying(self):
|
||||
url = "https://api.spotify.com/v1/me/player/currently-playing"
|
||||
req = requests.get(url, headers=self.header)
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
|
@ -27,31 +27,40 @@
|
||||
# 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.RST_PIN = rst
|
||||
self.DC_PIN = dc
|
||||
self.BL_PIN = bl
|
||||
self.SPEED =spi_freq
|
||||
self.BL_freq=bl_freq
|
||||
self.SPEED = spi_freq
|
||||
self.BL_freq = bl_freq
|
||||
self.GPIO = RPi.GPIO
|
||||
#self.GPIO.cleanup()
|
||||
# self.GPIO.cleanup()
|
||||
self.GPIO.setmode(self.GPIO.BCM)
|
||||
self.GPIO.setwarnings(False)
|
||||
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
|
||||
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
|
||||
self.GPIO.setup(self.BL_PIN, self.GPIO.OUT)
|
||||
self.GPIO.output(self.BL_PIN, self.GPIO.HIGH)
|
||||
#Initialize SPI
|
||||
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
|
||||
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
|
||||
self.GPIO.setup(self.BL_PIN, self.GPIO.OUT)
|
||||
self.GPIO.output(self.BL_PIN, self.GPIO.HIGH)
|
||||
# Initialize SPI
|
||||
self.SPI = spidev.SpiDev()
|
||||
self.SPI.open(0, 0)
|
||||
self.SPI.max_speed_hz = spi_freq
|
||||
@ -67,28 +76,29 @@ class RaspberryPi:
|
||||
time.sleep(delaytime / 1000.0)
|
||||
|
||||
def spi_writebyte(self, data):
|
||||
if self.SPI!=None :
|
||||
if self.SPI != None:
|
||||
self.SPI.writebytes(data)
|
||||
|
||||
def bl_DutyCycle(self, duty):
|
||||
self._pwm.ChangeDutyCycle(duty)
|
||||
|
||||
def bl_Frequency(self,freq):
|
||||
def bl_Frequency(self, freq):
|
||||
self._pwm.ChangeFrequency(freq)
|
||||
|
||||
def module_init(self):
|
||||
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
|
||||
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
|
||||
self.GPIO.setup(self.BL_PIN, self.GPIO.OUT)
|
||||
self._pwm=self.GPIO.PWM(self.BL_PIN,self.BL_freq)
|
||||
self._pwm = self.GPIO.PWM(self.BL_PIN, self.BL_freq)
|
||||
self._pwm.start(100)
|
||||
if self.SPI!=None :
|
||||
if self.SPI != None:
|
||||
self.SPI.max_speed_hz = self.SPEED
|
||||
self.SPI.mode = 0b00
|
||||
return 0
|
||||
|
||||
def module_exit(self):
|
||||
logging.debug("spi end")
|
||||
if self.SPI!=None :
|
||||
if self.SPI != None:
|
||||
self.SPI.close()
|
||||
|
||||
logging.debug("gpio cleanup...")
|
||||
@ -97,15 +107,15 @@ class RaspberryPi:
|
||||
self._pwm.stop()
|
||||
time.sleep(0.001)
|
||||
self.GPIO.output(self.BL_PIN, 1)
|
||||
#self.GPIO.cleanup()
|
||||
# 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 ###
|
||||
|
@ -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,13 +13,14 @@ 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)
|
||||
self.GPIO.output(self.RST_PIN, self.GPIO.HIGH)
|
||||
time.sleep(0.01)
|
||||
self.GPIO.output(self.RST_PIN,self.GPIO.LOW)
|
||||
self.GPIO.output(self.RST_PIN, self.GPIO.LOW)
|
||||
time.sleep(0.01)
|
||||
self.GPIO.output(self.RST_PIN,self.GPIO.HIGH)
|
||||
self.GPIO.output(self.RST_PIN, self.GPIO.HIGH)
|
||||
time.sleep(0.01)
|
||||
|
||||
def Init(self):
|
||||
@ -116,63 +117,74 @@ class LCD_2inch(lcdconfig.RaspberryPi):
|
||||
|
||||
self.command(0x29)
|
||||
|
||||
|
||||
def SetWindows(self, Xstart, Ystart, Xend, Yend):
|
||||
#set the X coordinates
|
||||
# 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(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(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(Xend >> 8) # Set the horizontal end to the high octet
|
||||
self.data((Xend - 1) & 0xFF) # Set the horizontal end to the low octet
|
||||
|
||||
#set the Y coordinates
|
||||
# set the Y coordinates
|
||||
self.command(0x2B)
|
||||
self.data(Ystart>>8)
|
||||
self.data((Ystart & 0xff))
|
||||
self.data(Yend>>8)
|
||||
self.data((Yend - 1) & 0xff )
|
||||
self.data(Ystart >> 8)
|
||||
self.data((Ystart & 0xFF))
|
||||
self.data(Yend >> 8)
|
||||
self.data((Yend - 1) & 0xFF)
|
||||
|
||||
self.command(0x2C)
|
||||
|
||||
def ShowImage(self,Image,Xstart=0,Ystart=0):
|
||||
def ShowImage(self, Image, Xstart=0, Ystart=0):
|
||||
"""Set buffer to value of Python Imaging Library image."""
|
||||
"""Write display buffer to physical display"""
|
||||
imwidth, imheight = Image.size
|
||||
if imwidth == self.height and imheight == self.width:
|
||||
if imwidth == self.height and imheight == self.width:
|
||||
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 = 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 = pix.flatten().tolist()
|
||||
|
||||
self.command(0x36)
|
||||
self.data(0x70)
|
||||
self.SetWindows ( 0, 0, self.height,self.width)
|
||||
self.digital_write(self.DC_PIN,self.GPIO.HIGH)
|
||||
for i in range(0,len(pix),4096):
|
||||
self.spi_writebyte(pix[i:i+4096])
|
||||
self.SetWindows(0, 0, self.height, self.width)
|
||||
self.digital_write(self.DC_PIN, self.GPIO.HIGH)
|
||||
for i in range(0, len(pix), 4096):
|
||||
self.spi_writebyte(pix[i : i + 4096])
|
||||
|
||||
else :
|
||||
else:
|
||||
img = self.np.asarray(Image)
|
||||
pix = self.np.zeros((imheight,imwidth , 2), dtype = self.np.uint8)
|
||||
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()
|
||||
|
||||
self.command(0x36)
|
||||
self.data(0x00)
|
||||
self.SetWindows ( 0, 0, self.width, self.height)
|
||||
self.digital_write(self.DC_PIN,self.GPIO.HIGH)
|
||||
for i in range(0,len(pix),4096):
|
||||
self.spi_writebyte(pix[i:i+4096])
|
||||
self.SetWindows(0, 0, self.width, self.height)
|
||||
self.digital_write(self.DC_PIN, self.GPIO.HIGH)
|
||||
for i in range(0, len(pix), 4096):
|
||||
self.spi_writebyte(pix[i : i + 4096])
|
||||
|
||||
def clear(self):
|
||||
"""Clear contents of image buffer"""
|
||||
_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):
|
||||
self.spi_writebyte(_buffer[i:i+4096])
|
||||
_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):
|
||||
self.spi_writebyte(_buffer[i : i + 4096])
|
||||
|
@ -27,7 +27,7 @@ def display_loop(api: Api, cfg: Dict):
|
||||
d.clear()
|
||||
bg = Image.new("RGB", (d.height, d.width), (0, 0, 0))
|
||||
d.ShowImage(bg)
|
||||
|
||||
|
||||
Font0 = ImageFont.truetype("Font/Font00.ttf", 14)
|
||||
Font1 = ImageFont.truetype("Font/Font00.ttf", 18)
|
||||
Font2 = ImageFont.truetype("Font/Font00.ttf", 36)
|
||||
@ -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,12 +64,12 @@ 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()
|
||||
last_api_call = time.time()
|
||||
|
||||
|
||||
if data == None:
|
||||
warnings.warn("No data found")
|
||||
d.ShowImage(error_img)
|
||||
@ -73,45 +78,85 @@ 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")
|
||||
d.ShowImage(not_playing_img)
|
||||
time.sleep(cfg["api_interval"])
|
||||
|
||||
|
||||
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= progress_time / data["duration_ms"]
|
||||
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)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user