Ensure current brightness value is used in calculations, not the previous one Ensure holding volume buttons continues to change volume, even if data hasn't been refreshed from remove
214 lines
8.9 KiB
Python
214 lines
8.9 KiB
Python
from font import cntr_st, rght_st
|
|
from utils import colour
|
|
from bmp_file_reader import BMPFileReader
|
|
from api import getMediaPlayerData, getLightData, playPause, nextTrack, prevTrack, changeVolume, setVolume, toggleLight, setBrightness
|
|
|
|
class Screen():
|
|
def __init__(self, n: str) -> None:
|
|
self.name = n
|
|
self.d = {}
|
|
self.prev = {}
|
|
|
|
def display(self, lcd) -> None:
|
|
lcd.fill(0x0000)
|
|
cntr_st(lcd, lcd.width, self.name, 20, 2, 255, 255, 255)
|
|
|
|
def update(self, lcd) -> None:
|
|
pass
|
|
|
|
def handleButtons(self, up: bool, down: bool, left: bool, right: bool, keyA: bool, keyB: bool, keyX: bool, keyY: bool, ctrl: bool) -> bool:
|
|
return False
|
|
|
|
def _updateData(self) -> dict:
|
|
self.prev = self.d.copy()
|
|
return {}
|
|
|
|
def _invalidConfig(self, lcd) -> None:
|
|
cntr_st(lcd, lcd.width, "Invalid config", lcd.height//2, 2, 255, 255, 255)
|
|
lcd.show()
|
|
|
|
class MediaScreen(Screen):
|
|
def __init__(self, n: str, e: str) -> None:
|
|
super().__init__(n)
|
|
self.e = e
|
|
self.valid = e != None and e != ""
|
|
|
|
def display(self, lcd) -> None:
|
|
super().display(lcd)
|
|
if (not self.valid):
|
|
self._invalidConfig(lcd)
|
|
return
|
|
if (self.d == {}):
|
|
self.d = self._updateData()
|
|
self.__updateMediaPositionBar(lcd, self.d["media_position"], self.d["media_duration"])
|
|
if (self.d["media_duration"] != None):
|
|
mins = self.d["media_duration"] // 60
|
|
secs = self.d["media_duration"] % 60
|
|
rght_st(lcd, f"{mins}:{secs}", lcd.width, lcd.height - 16, 1, 180, 180, 180)
|
|
cntr_st(lcd, lcd.width, self.d["media_title"], lcd.height - 72, 3, 255, 255, 255)
|
|
cntr_st(lcd, lcd.width, self.d["media_artist"], lcd.height - 96, 2, 255, 255, 255)
|
|
lcd.show()
|
|
|
|
def __updateMediaPositionBar(self, lcd, p: int, d: int):
|
|
if (d > 0):
|
|
for x in range (0, (lcd.width * p)//d):
|
|
lcd.pixel(x, lcd.height - 5, 0xffff)
|
|
lcd.pixel(x, lcd.height - 4, 0xffff)
|
|
lcd.pixel(x, lcd.height - 3, 0xffff)
|
|
lcd.pixel(x, lcd.height - 2, 0xffff)
|
|
lcd.pixel(x, lcd.height - 1, 0xffff)
|
|
lcd.pixel(x, lcd.height, 0xffff)
|
|
|
|
def update(self, lcd):
|
|
if (not self.valid):
|
|
super().display(lcd)
|
|
self._invalidConfig(lcd)
|
|
return
|
|
self._updateData()
|
|
# if same media is playing (same title and duration), just update the position bar
|
|
if (self.d["media_title"] == self.prev["media_title"] and self.d["media_duration"] == self.prev["media_duration"]):
|
|
self.__updateMediaPositionBar(lcd, self.d["media_position"], self.d["media_duration"])
|
|
lcd.show()
|
|
# otherwise redraw the whole screen
|
|
else:
|
|
self.display(lcd)
|
|
|
|
def _updateData(self) -> dict:
|
|
super()._updateData()
|
|
self.d = getMediaPlayerData(self.e)
|
|
return self.d
|
|
|
|
def handleButtons(self, up: bool, down: bool, left: bool, right: bool, keyA: bool, keyB: bool, keyX: bool, keyY: bool, ctrl: bool) -> bool:
|
|
a = False
|
|
v = self.d["volume_level"] if "volume_level" in self.d else None
|
|
if (v != None and up and v < 1):
|
|
vn = min(1.0, v + 0.08)
|
|
setVolume(self.e, vn)
|
|
self.d["volume_level"] = vn
|
|
a = True
|
|
elif (v != None and down and v > 0.01):
|
|
vn = max(0.01, v - 0.08)
|
|
setVolume(self.e, vn)
|
|
self.d["volume_level"] = vn
|
|
a = True
|
|
if (keyX):
|
|
nextTrack(self.e)
|
|
elif (keyY):
|
|
prevTrack(self.e)
|
|
if (keyA):
|
|
playPause(self.e)
|
|
return a
|
|
|
|
class LightsScreen(Screen):
|
|
def __init__(self, n: str, es: list) -> None:
|
|
super().__init__(n)
|
|
self.es = es
|
|
self.valid = es != None and len(es) != 0
|
|
|
|
def display(self, lcd) -> None:
|
|
super().display(lcd)
|
|
if (not self.valid):
|
|
self._invalidConfig(lcd)
|
|
return
|
|
if (self.d == {}):
|
|
self._updateData()
|
|
# display up to four lights as defined in env.py
|
|
# for each defined entity (up to a max total of 4), display its data in a 2x2 grid
|
|
for i in range(0, len(self.d)):
|
|
self.__displayLightEntity(lcd, i, lcd.width//2, lcd.height//2, lcd.width//2 * (i % 2), lcd.height//2 * (i//2), self.es[i]["name"], self.d[i])
|
|
lcd.show()
|
|
|
|
def __displayLightEntity(self, lcd, i: int, w: int, h: int, xo: int, yo: int, n: str, d) -> None:
|
|
# if the light is turned on, display the filled-in lightbulb icon in the colour of the light, centrally in the light's grid square
|
|
if (d["on"]):
|
|
color = colour(d["rgb_color"][0], d["rgb_color"][1], d["rgb_color"][2])
|
|
with open("images/lightbulb-on.bmp", "rb") as file_handle:
|
|
reader = BMPFileReader(file_handle)
|
|
img_height = reader.get_height()
|
|
x_offset = w//2 + xo - reader.get_width()//2
|
|
y_offset = h//2 + yo - reader.get_height()//2 - 4
|
|
for row_i in range(0, reader.get_height()):
|
|
row = reader.get_row(row_i)
|
|
for col_i, color in enumerate(row):
|
|
r = d["rgb_color"][0] if (color.red) != 0 else 0
|
|
g = d["rgb_color"][1] if (color.green) != 0 else 0
|
|
b = d["rgb_color"][2] if (color.blue) != 0 else 0
|
|
if (color.red != 0 or color.green != 0 or color.blue != 0):
|
|
lcd.pixel(col_i + x_offset, row_i + y_offset, colour(r,g,b))
|
|
# otherwise display the outline lightbulb icon in grey, centrally in the light's grid square
|
|
else:
|
|
color = colour(80, 80, 80)
|
|
with open("images/lightbulb-off.bmp", "rb") as file_handle:
|
|
reader = BMPFileReader(file_handle)
|
|
img_height = reader.get_height()
|
|
x_offset = w//2 + xo - reader.get_width()//2
|
|
y_offset = h//2 + yo - reader.get_height()//2 - 4
|
|
for row_i in range(0, reader.get_height()):
|
|
row = reader.get_row(row_i)
|
|
for col_i, color in enumerate(row):
|
|
if (color.red != 0 or color.green != 0 or color.blue != 0):
|
|
lcd.pixel(col_i + x_offset, row_i + y_offset, colour(color.red, color.green, color.blue))
|
|
else:
|
|
lcd.pixel(col_i + x_offset, row_i + y_offset, colour(0,0,0))
|
|
# display the name of the light 8px below the lightbulb icon
|
|
cntr_st(lcd, w, n, y_offset + img_height + 8, 2, 220, 220, 220, xo)
|
|
|
|
def update(self, lcd):
|
|
if (not self.valid):
|
|
super().display(lcd)
|
|
self._invalidConfig(lcd)
|
|
return
|
|
self._updateData()
|
|
# for each light to be displayed
|
|
for i in range(0, len(self.d)):
|
|
# if its settings have changed, re-draw them without clearing the display
|
|
if (self.d[i] != self.prev[i]):
|
|
self.__displayLightEntity(lcd, i, lcd.width//2, lcd.height//2, lcd.width//2 * (i % 2), lcd.height//2 * (i//2), self.es[i]["name"], self.d[i])
|
|
lcd.show()
|
|
|
|
def _updateData(self) -> dict:
|
|
super()._updateData()
|
|
for i in range(0, min(len(self.es), 4)):
|
|
self.d[i] = getLightData(self.es[i]["id"])
|
|
return self.d
|
|
|
|
def handleButtons(self, up: bool, down: bool, left: bool, right: bool, keyA: bool, keyB: bool, keyX: bool, keyY: bool, ctrl: bool) -> bool:
|
|
e = min(len(self.es), 4)
|
|
b = [keyA, keyB, keyX, keyY]
|
|
a = False
|
|
for i in range(0, e):
|
|
# if button for light clicked, toggle the light
|
|
if (b[i]):
|
|
toggleLight(self.es[i]["id"])
|
|
a = True
|
|
# if up/down clicked, adjust brightness for all lights that are turned on
|
|
pcfg = i in self.d and "on" in self.d[i] and self.d[i]["on"] and "brightness" in self.d[i]
|
|
if (up and pcfg):
|
|
v = min(255, self.d[i]["brightness"] + 35)
|
|
setBrightness(self.es[i]["id"], v)
|
|
self.d[i]["brightness"] = v
|
|
a = True
|
|
elif (down and pcfg):
|
|
v = max(1, self.d[i]["brightness"] - 35)
|
|
setBrightness(self.es[i]["id"], v)
|
|
self.d[i]["brightness"] = v
|
|
a = True
|
|
return a
|
|
|
|
class UnknownScreen(Screen):
|
|
def __init__(self, n: str) -> None:
|
|
super().__init__(n)
|
|
|
|
def display(self, lcd) -> None:
|
|
super().display(lcd)
|
|
self._invalidConfig(lcd)
|
|
|
|
def update(self, lcd) -> None:
|
|
pass
|
|
|
|
def _updateData(self) -> dict:
|
|
return {}
|
|
|
|
def handleButtons(self, up: bool, down: bool, left: bool, right: bool, keyA: bool, keyB: bool, keyX: bool, keyY: bool, ctrl: bool) -> bool:
|
|
return False
|