From d2583baf5a4565cee426248a9f0843c620013924 Mon Sep 17 00:00:00 2001 From: nick Date: Wed, 3 Jun 2026 02:49:22 -0600 Subject: [PATCH] Add Pico display rendering abstraction --- pico-dashboard/hardware/display.py | 31 +++++++++++++++++ pico-dashboard/ui/renderers.py | 51 +++++++++++++++++++++++++++ tests/test_pico_core.py | 56 ++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 pico-dashboard/ui/renderers.py diff --git a/pico-dashboard/hardware/display.py b/pico-dashboard/hardware/display.py index e69de29..b03fe0e 100644 --- a/pico-dashboard/hardware/display.py +++ b/pico-dashboard/hardware/display.py @@ -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 = [] diff --git a/pico-dashboard/ui/renderers.py b/pico-dashboard/ui/renderers.py new file mode 100644 index 0000000..664a4bf --- /dev/null +++ b/pico-dashboard/ui/renderers.py @@ -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 diff --git a/tests/test_pico_core.py b/tests/test_pico_core.py index fe96543..814e550 100644 --- a/tests/test_pico_core.py +++ b/tests/test_pico_core.py @@ -722,3 +722,59 @@ def test_touch_router_ignores_unpressed_event(): assert handled is False 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