Add Pico display rendering abstraction

This commit is contained in:
2026-06-03 02:49:22 -06:00
parent b5e8bf43fc
commit d2583baf5a
3 changed files with 138 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
class Display:
def __init__(self, driver=None):
self.driver = driver
self.commands = []
def clear(self):
self.commands.append(("clear",))
def text(self, x, y, value, size=1):
self.commands.append(("text", x, y, str(value), size))
def rect(self, x, y, w, h, filled=False):
self.commands.append(("rect", x, y, w, h, filled))
def flush(self):
if not self.driver:
return
for command in self.commands:
name = command[0]
if name == "clear":
self.driver.clear()
elif name == "text":
_, x, y, value, size = command
self.driver.text(x, y, value, size)
elif name == "rect":
_, x, y, w, h, filled = command
self.driver.rect(x, y, w, h, filled)
self.commands = []
+51
View File
@@ -0,0 +1,51 @@
class DashboardRenderer:
def __init__(self, display):
self.display = display
def render(self, view_model):
vm = view_model.as_dict()
self.display.clear()
top = vm["top_bar"]
self.display.text(0, 0, f'SOC {top["soc"]}')
self.display.text(120, 0, top["comms"])
self.display.text(260, 0, f'ALM {top["alarms"]}')
battery = vm["battery"]
self.display.text(0, 48, "Battery")
self.display.text(
0,
72,
f'{battery["voltage"]} {battery["current"]} {battery["runtime"]}',
)
fridge = vm["fridge"]
self.display.text(0, 128, "Fridge")
self.display.text(0, 152, f'Zone 1: {fridge["zone_1"]}')
self.display.text(0, 176, f'Zone 2: {fridge["zone_2"]}')
power = vm["power"]
self.display.text(0, 232, "Power")
self.display.text(
0,
256,
f'Starlink {power["starlink"]} Fridge {power["fridge"]}',
)
self.render_nav("dashboard")
def render_nav(self, active_screen):
labels = [
("dashboard", "Dash"),
("battery", "Bat"),
("temps", "Temps"),
("power", "Power"),
("system", "System"),
]
x = 0
for screen, label in labels:
text = f"[{label}]" if screen == active_screen else label
self.display.text(x, 440, text)
x += 64
+56
View File
@@ -722,3 +722,59 @@ def test_touch_router_ignores_unpressed_event():
assert handled is False assert handled is False
assert screens.current_screen == "dashboard" assert screens.current_screen == "dashboard"
def test_display_records_commands():
from hardware.display import Display
display = Display()
display.clear()
display.text(1, 2, "hello", size=2)
display.rect(3, 4, 5, 6, filled=True)
assert display.commands == [
("clear",),
("text", 1, 2, "hello", 2),
("rect", 3, 4, 5, 6, True),
]
def test_dashboard_renderer_creates_draw_commands():
from hardware.display import Display
from ui.dashboard_view_model import DashboardViewModel
from ui.renderers import DashboardRenderer
state = AppState()
state.update_from_status({
"battery": {
"soc": 82,
"voltage": 13.2,
"current": -6.4,
"runtime_hours": 12.0,
},
"temps": {
"fridge_zone_1": 34.5,
"fridge_zone_2": 36.0,
},
"relays": {
"starlink": False,
"fridge": True,
},
"network": {
"uart_connected": True,
},
})
display = Display()
renderer = DashboardRenderer(display)
renderer.render(DashboardViewModel(state))
assert display.commands[0] == ("clear",)
assert ("text", 0, 0, "SOC 82%", 1) in display.commands
assert ("text", 120, 0, "UART OK", 1) in display.commands
assert ("text", 0, 48, "Battery", 1) in display.commands
assert ("text", 0, 128, "Fridge", 1) in display.commands
assert ("text", 0, 232, "Power", 1) in display.commands
assert ("text", 0, 440, "[Dash]", 1) in display.commands