"""Three screens on one panel: the house, its comfort, and its electricity. One dashboard each rather than three pages of one, because a panel carries several whole dashboards and switches between them on a rail — which is the thing that exists, and pages are not. Every control binds to the message an arbiter both reads and writes, so a tile shows what actually reached the fixture and setting it is what overrides the automation. There is no second tile saying what the first one really did. """ from __future__ import annotations from typing import Any COLUMNS = 16 def _at(x: int, y: int, w: int, h: int) -> dict[str, Any]: return {"lg": {"x": x, "y": y, "w": w, "h": h}} def stat(id_, title, message, unit="", precision=1, dtype="float", **at): return { "id": id_, "type": "stat", "title": title, "layout": _at(**at), "config": { "message": message, "dtype": dtype, "unit": unit, "precision": precision, }, } def switch(id_, title, target, **at): return { "id": id_, "type": "switch", "title": title, "layout": _at(**at), "config": {"target": target, "dtype": "bool", "style": "button"}, } def button(id_, title, target, value, **at): return { "id": id_, "type": "button", "title": title, "layout": _at(**at), "config": {"target": target, "dtype": "str", "value": value, "label": title}, } def slider(id_, title, target, lo, hi, step=1, unit="", **at): return { "id": id_, "type": "slider", "title": title, "layout": _at(**at), "config": { "target": target, "dtype": "float", "min": lo, "max": hi, "step": step, "unit": unit, }, } def gauge(id_, title, message, lo, hi, unit="", **at): return { "id": id_, "type": "gauge", "title": title, "layout": _at(**at), "config": { "message": message, "dtype": "float", "min": lo, "max": hi, "unit": unit, "precision": 0, }, } def chart(id_, title, request, series, unit="", **at): return { "id": id_, "type": "chart", "title": title, "layout": _at(**at), "config": { "source": "query", "request": request, "request_dtype": "record", "message": series, "dtype": "series", "range_s": 21600, "refresh_s": 300, "unit": unit, }, } def icon(id_, title, message, rules, dtype="int", **at): return { "id": id_, "type": "icon", "title": title, "layout": _at(**at), "config": {"message": message, "dtype": dtype, "rules": rules}, } def dropdown(id_, title, target, options, **at): return { "id": id_, "type": "dropdown", "title": title, "layout": _at(**at), "config": { "target": target, "dtype": "str", "style": "segmented", "options": [{"label": label, "value": value} for label, value in options], }, } def _cover(prefix, title, target, y): """Three buttons, because a shutter with no position sensor has three states.""" return [ button(f"{prefix}_up", f"{title} up", target, "UP", x=0, y=y, w=2, h=2), button(f"{prefix}_stop", "Stop", target, "STOP", x=2, y=y, w=2, h=2), button(f"{prefix}_down", f"{title} down", target, "DOWN", x=4, y=y, w=2, h=2), ] HOME = [ stat("indoor", "Inside", "climate.indoor", "°C", 1, x=0, y=0, w=3, h=2), stat("outdoor", "Outside", "weather.outdoor_temp", "°C", 1, x=3, y=0, w=3, h=2), stat("humidity", "Humidity", "weather.indoor_hum", "%", 0, x=6, y=0, w=3, h=2), icon( "power_state", "Power", "power.watch", [ {"at": 0, "icon": "check", "color": "success", "label": "Normal"}, {"at": 1, "icon": "triangle-alert", "color": "warning", "label": "Watch"}, {"at": 3, "icon": "zap-off", "color": "danger", "label": "Mains"}, ], x=9, y=0, w=3, h=2, ), { "id": "alert", "type": "notification", "title": "What is happening", "layout": _at(x=12, y=0, w=4, h=4), "config": {"message": "power.alert"}, }, dropdown( "scene", "Scene", "lights.scene", [ ("Off", "off"), ("Day", "day"), ("Night", "night"), ("Sleep", "sleep"), ("Outside", "outside"), ], x=0, y=2, w=8, h=2, ), slider( "brightness", "Brightness", "lights.brightness", 0, 100, 5, "%", x=8, y=2, w=4, h=2, ), stat("lit", "Lit", "lights.lit", dtype="str", precision=0, x=0, y=4, w=4, h=2), switch( "appliances", "Appliances", "appliances.appliances_manual", x=4, y=4, w=3, h=2 ), switch("bath", "Bathroom plug", "plugs.bath_manual", x=7, y=4, w=3, h=2), stat( "floor", "Floor heating", "oven.floor_heating", dtype="bool", precision=0, x=10, y=4, w=3, h=2, ), stat( "presence", "Who is in", "presence.state", dtype="str", precision=0, x=13, y=4, w=3, h=2, ), *_cover("door", "Door", "shutters.door_shutter_cmd", y=6), *_cover("bed", "Bed", "shutters.bed_shutter_cmd", y=8), { "id": "load", "type": "bar", "title": "House draw", "layout": _at(x=6, y=6, w=6, h=4), "config": { "message": "power.out_w", "min": 0, "max": 3000, "unit": "W", "precision": 0, "inner": [{"message": "power.pv_w", "dtype": "float"}], }, }, gauge("soc", "Battery", "power.soc", 0, 100, "%", x=12, y=6, w=4, h=4), ] COMFORT = [ { "id": "forecast", "type": "forecast", "title": "The next few days", "layout": _at(x=0, y=0, w=8, h=3), "config": {"message": "weather.forecast", "count": 4}, }, { "id": "agenda", "type": "agenda", "title": "Coming up", "layout": _at(x=8, y=0, w=8, h=3), "config": {"message": "calendar.events", "count": 5}, }, slider("preset", "Wanted", "climate.preset", 16, 26, 0.5, "°C", x=0, y=3, w=5, h=2), stat("setpoint", "Band", "climate.t_max", "°C", 1, x=5, y=3, w=3, h=2), switch("ac", "Heat pump enabled", "hvac.enabled", x=8, y=3, w=4, h=2), stat( "hvac_why", "Heat pump", "hvac.why", dtype="str", precision=0, x=12, y=3, w=4, h=2, ), switch("oven", "Pellet stove", "oven.oven_manual", x=0, y=5, w=4, h=2), stat("oven_why", "Stove", "oven.why", dtype="str", precision=0, x=4, y=5, w=4, h=2), stat( "window_why", "Window", "window.why", dtype="str", precision=0, x=8, y=5, w=4, h=2, ), stat( "canopy_why", "Awning", "canopy.why", dtype="str", precision=0, x=12, y=5, w=4, h=2, ), *_cover("window", "Window", "window.window_manual", y=7), *_cover("canopy", "Awning", "canopy.canopy_manual", y=9), switch("outdoor", "Outdoor plug", "outdoor.outdoor_manual", x=6, y=7, w=4, h=2), stat( "outdoor_why", "Outdoor plug", "outdoor.why", dtype="str", precision=0, x=10, y=7, w=6, h=2, ), chart( "climate_chart", "Inside and out", "history.climate_request", "history.climate_series", "°C", x=6, y=9, w=10, h=5, ), ] ENERGY = [ gauge("pv", "Solar", "power.pv_w", 0, 3000, "W", x=0, y=0, w=4, h=4), gauge("draw", "House", "power.out_w", 0, 3000, "W", x=4, y=0, w=4, h=4), gauge("grid", "Mains", "power.in_v", 0, 260, "V", x=8, y=0, w=4, h=4), gauge("charge", "Battery", "power.soc", 0, 100, "%", x=12, y=0, w=4, h=4), chart( "power_chart", "Where the power went", "history.power_request", "history.power_series", "W", x=0, y=4, w=16, h=6, ), chart( "battery_chart", "State of charge", "history.battery_request", "history.battery_series", "%", x=0, y=10, w=16, h=5, ), ] SCREENS = ( ("home", "Home", "house", HOME), ("comfort", "Comfort", "thermometer", COMFORT), ("energy", "Energy", "zap", ENERGY), )