Files
home-assistant-pi-pico-w-da…/screens.py
Matthew Grove 2d3e8d9224 [FIX] Brightness adjustment
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
2024-05-16 23:52:37 +01:00

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