From 93c677a3e1a213ccf68fbbfda1de83aa8a7476a3 Mon Sep 17 00:00:00 2001 From: nick Date: Wed, 3 Jun 2026 02:46:18 -0600 Subject: [PATCH] Add Pico detail screen view models --- pico-dashboard/ui/detail_view_models.py | 74 +++++++++++++++++ tests/test_pico_core.py | 101 ++++++++++++++++++++++++ 2 files changed, 175 insertions(+) create mode 100644 pico-dashboard/ui/detail_view_models.py diff --git a/pico-dashboard/ui/detail_view_models.py b/pico-dashboard/ui/detail_view_models.py new file mode 100644 index 0000000..3414743 --- /dev/null +++ b/pico-dashboard/ui/detail_view_models.py @@ -0,0 +1,74 @@ +from .dashboard_view_model import format_value, format_bool + + +class BatteryViewModel: + def __init__(self, state): + self.state = state + + def as_dict(self): + battery = self.state.battery + + return { + "soc": format_value(battery.get("soc"), "%", decimals=0), + "voltage": format_value(battery.get("voltage"), "V", decimals=1), + "current": format_value(battery.get("current"), "A", decimals=1), + "remaining_ah": format_value(battery.get("remaining_ah"), "Ah", decimals=1), + "runtime_hours": format_value(battery.get("runtime_hours"), " hr", decimals=1), + "temperature_f": format_value(battery.get("temperature_f"), "°F", decimals=1), + } + + +class TempsViewModel: + SENSOR_LABELS = { + "fridge_zone_1": "Fridge Zone 1", + "fridge_zone_2": "Fridge Zone 2", + "rear_seat": "Rear Seat", + "outside": "Outside Air", + } + + def __init__(self, state): + self.state = state + + def as_list(self): + rows = [] + + for key, label in self.SENSOR_LABELS.items(): + online = self.state.sensor_health.get(key, False) + temp = self.state.temps.get(key) + + rows.append({ + "key": key, + "label": label, + "temperature": format_value(temp, "°F", decimals=1), + "status": "OK" if online else "FAULT", + }) + + return rows + + +class PowerViewModel: + def __init__(self, state): + self.state = state + + def as_dict(self): + return { + "starlink": format_bool(self.state.relays.get("starlink")), + "fridge": format_bool(self.state.relays.get("fridge")), + } + + +class SystemViewModel: + def __init__(self, state): + self.state = state + + def as_dict(self): + sensor_count = sum(1 for online in self.state.sensor_health.values() if online) + total_sensors = len(self.state.sensor_health) + + return { + "esp32": "Online" if self.state.network.get("uart_connected") else "Unknown", + "uart": "Connected" if self.state.network.get("uart_connected") else "Disconnected", + "wifi_api": "Available" if self.state.network.get("wifi_enabled") else "Unavailable", + "sensors": f"{sensor_count} / {total_sensors} OK", + "ignition": "On" if self.state.vehicle.get("ignition_on") else "Off", + } diff --git a/tests/test_pico_core.py b/tests/test_pico_core.py index ee329d0..c5a3e75 100644 --- a/tests/test_pico_core.py +++ b/tests/test_pico_core.py @@ -482,3 +482,104 @@ def test_dashboard_view_model_handles_missing_values(): assert vm["battery"]["voltage"] == "--" assert vm["fridge"]["zone_1"] == "--" + + +def test_battery_view_model(): + from ui.detail_view_models import BatteryViewModel + + state = AppState() + state.update_from_status({ + "battery": { + "soc": 82, + "voltage": 13.2, + "current": -6.4, + "remaining_ah": 82.0, + "runtime_hours": 12.0, + "temperature_f": 76.0, + } + }) + + vm = BatteryViewModel(state).as_dict() + + assert vm["soc"] == "82%" + assert vm["voltage"] == "13.2V" + assert vm["current"] == "-6.4A" + assert vm["remaining_ah"] == "82.0Ah" + assert vm["runtime_hours"] == "12.0 hr" + assert vm["temperature_f"] == "76.0°F" + + +def test_temps_view_model(): + from ui.detail_view_models import TempsViewModel + + state = AppState() + state.update_from_status({ + "temps": { + "fridge_zone_1": 34.5, + "fridge_zone_2": 36.0, + "rear_seat": 71.2, + "outside": None, + }, + "sensor_health": { + "fridge_zone_1": True, + "fridge_zone_2": True, + "rear_seat": True, + "outside": False, + } + }) + + rows = TempsViewModel(state).as_list() + + assert rows[0]["label"] == "Fridge Zone 1" + assert rows[0]["temperature"] == "34.5°F" + assert rows[0]["status"] == "OK" + + assert rows[3]["label"] == "Outside Air" + assert rows[3]["temperature"] == "--" + assert rows[3]["status"] == "FAULT" + + +def test_power_view_model(): + from ui.detail_view_models import PowerViewModel + + state = AppState() + state.update_from_status({ + "relays": { + "starlink": False, + "fridge": True, + } + }) + + vm = PowerViewModel(state).as_dict() + + assert vm["starlink"] == "OFF" + assert vm["fridge"] == "ON" + + +def test_system_view_model(): + from ui.detail_view_models import SystemViewModel + + state = AppState() + state.update_from_status({ + "sensor_health": { + "fridge_zone_1": True, + "fridge_zone_2": True, + "rear_seat": False, + "outside": True, + }, + "vehicle": { + "ignition_on": False, + }, + "network": { + "uart_connected": True, + "wifi_enabled": True, + } + }) + + vm = SystemViewModel(state).as_dict() + + assert vm["esp32"] == "Online" + assert vm["uart"] == "Connected" + assert vm["wifi_api"] == "Available" + assert vm["sensors"] == "3 / 4 OK" + assert vm["ignition"] == "Off"