Dashboard Studio 1.11.0: energy card, climate popup, car and appliance redesigns, Home Connect

- Energy card: live power flow between solar, grid, battery and home,
  with line width and speed following the power, battery level and
  today's totals; sign of grid and battery sensors can be flipped
- Climate popup (room card thermostat and any climate entity): dial,
  -/+ that batch quick taps, modes, presets and fan modes, live updates
- Car card redesigned around 3x4 with a drawn car or photo, compact,
  wide and tall layouts
- Appliance card redesigned with a progress ring; Home Connect support:
  all operation states, auto-fill from the operation state sensor via
  the device registry, readable program names, door state, pause/
  resume/stop buttons, power switch and alerts. Fixes ready/inactive
  being shown as finished/running
- Horizontal scroll card: width in pixels or cards per view, and a
  width per card
- Swipe card: no lost tap after a touch swipe, no broken swipes over
  images, lost pointer capture ends the swipe, trackpad swiping

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-26 14:06:35 +01:00
parent 78aa85a82f
commit f9822a9c49
9 changed files with 551 additions and 90 deletions
+34 -1
View File
@@ -64,7 +64,27 @@ class MockHA:
entity("sensor.phone_battery", "76", friendly_name="Phone Battery", unit_of_measurement="%", device_class="battery"),
entity("binary_sensor.front_door", "off", friendly_name="Front Door", device_class="door"),
entity("binary_sensor.hallway_motion", "off", friendly_name="Hallway Motion", device_class="motion"),
entity("climate.thermostat", "heat", friendly_name="Thermostat", current_temperature=20.5, temperature=21, min_temp=7, max_temp=30, hvac_modes=["off", "heat", "auto"]),
entity("climate.thermostat", "heat", friendly_name="Thermostat", current_temperature=20.5, temperature=21, min_temp=7, max_temp=30, hvac_modes=["off", "heat", "auto"],
preset_modes=["none", "eco", "comfort", "away"], preset_mode="comfort", fan_modes=["auto", "low", "high"], fan_mode="auto", hvac_action="heating", current_humidity=46, target_temp_step=0.5),
entity("sensor.grid_power", "-680", friendly_name="Grid Power", unit_of_measurement="W", device_class="power"),
entity("sensor.home_battery_power", "500", friendly_name="Home Battery Power", unit_of_measurement="W", device_class="power"),
entity("sensor.home_battery_level", "64", friendly_name="Home Battery Level", unit_of_measurement="%", device_class="battery"),
entity("sensor.solar_energy_today", "12.4", friendly_name="Produced today", unit_of_measurement="kWh", device_class="energy"),
entity("sensor.home_energy_today", "8.1", friendly_name="Used today", unit_of_measurement="kWh", device_class="energy"),
entity("binary_sensor.washer_door", "off", friendly_name="Washer Door", device_class="door"),
# A dishwasher as the Home Connect integration exposes it
entity("sensor.dishwasher_operation_state", "run", friendly_name="Dishwasher Operation state", device_class="enum",
options=["inactive", "ready", "delayedstart", "run", "pause", "actionrequired", "finished", "error", "aborting"]),
entity("sensor.dishwasher_program_progress", "40", friendly_name="Dishwasher Program progress", unit_of_measurement="%"),
entity("sensor.dishwasher_program_finish_time", (datetime.now(timezone.utc) + timedelta(minutes=52)).isoformat(), friendly_name="Dishwasher Program finish time", device_class="timestamp"),
entity("sensor.dishwasher_door", "locked", friendly_name="Dishwasher Door", device_class="enum", options=["closed", "locked", "open"]),
entity("select.dishwasher_active_program", "dishcare_dishwasher_program_eco_50", friendly_name="Dishwasher Active program",
options=["dishcare_dishwasher_program_eco_50", "dishcare_dishwasher_program_auto_2", "dishcare_dishwasher_program_quick_45"]),
entity("button.dishwasher_resume_program", "unknown", friendly_name="Dishwasher Resume program"),
entity("button.dishwasher_stop_program", "unknown", friendly_name="Dishwasher Stop program"),
entity("switch.dishwasher_power", "on", friendly_name="Dishwasher Power"),
entity("sensor.dishwasher_rinse_aid_nearly_empty", "present", friendly_name="Dishwasher Rinse aid nearly empty", device_class="enum"),
entity("sensor.washer_energy", "0.4", friendly_name="Washer Energy", unit_of_measurement="kWh", device_class="energy"),
entity("cover.garage_door", "closed", friendly_name="Garage Door"),
entity("lock.front_door", "locked", friendly_name="Front Door Lock"),
entity("media_player.living_room_speaker", "playing", friendly_name="Living Room Speaker", media_title="Blue in Green", media_artist="Miles Davis", media_album_name="Kind of Blue", volume_level=0.35, entity_picture="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><defs><linearGradient id='g' x1='0' y1='0' x2='1' y2='1'><stop offset='0' stop-color='%23234'/><stop offset='1' stop-color='%23c58'/></linearGradient></defs><rect width='100' height='100' fill='url(%23g)'/><circle cx='50' cy='50' r='22' fill='none' stroke='%23fff' stroke-width='3' opacity='.7'/></svg>"),
@@ -155,6 +175,12 @@ class MockHA:
for eid in expanded:
if eid not in self.states:
continue
if eid.startswith("button.dishwasher_"): # Home Connect program buttons
st_ = {"stop": "aborting", "pause": "pause", "resume": "run"}[eid.split("_")[-2]]
self.set_state("sensor.dishwasher_operation_state", st_)
if st_ == "aborting":
asyncio.get_event_loop().call_later(3, lambda: self.set_state("sensor.dishwasher_operation_state", "ready"))
continue
if domain == "vacuum" or eid.startswith("vacuum."):
self._vacuum(eid, service, data)
continue
@@ -177,6 +203,9 @@ class MockHA:
self.set_state(eid, "on" if on else "off", **extra)
elif service == "set_temperature":
self.set_state(eid, None, temperature=data.get("temperature"))
elif service in ("set_preset_mode", "set_fan_mode"):
key = "preset_mode" if service == "set_preset_mode" else "fan_mode"
self.set_state(eid, None, **{key: data.get(key)})
elif service == "set_hvac_mode":
self.set_state(eid, data.get("hvac_mode"))
elif service in ("open_cover", "close_cover"):
@@ -261,6 +290,8 @@ class MockHA:
lvl = float(self.states["sensor.model_y_battery"]["state"])
if lvl < 80:
self.set_state("sensor.model_y_battery", f"{lvl + 0.2:.1f}")
home, solar, batt = float(self.states["sensor.power_usage"]["state"]), float(self.states["sensor.solar_production"]["state"]), float(self.states["sensor.home_battery_power"]["state"])
self.set_state("sensor.grid_power", f"{home + batt - solar:.0f}")
if self.states["sensor.washer_status"]["state"] == "running":
left = max(0, int(self.states["sensor.washer_remaining_time"]["state"]) - 1)
self.set_state("sensor.washer_remaining_time", str(left))
@@ -329,6 +360,8 @@ class MockHA:
"garage": ["cover.garage_door", "lock.front_door"],
}
rows = [{"entity_id": e, "area_id": a, "device_id": None, "disabled_by": None, "hidden_by": None, "entity_category": None} for a, es in areas.items() for e in es]
rows += [{"entity_id": e, "area_id": "kitchen", "device_id": "hc-dishwasher", "disabled_by": None, "hidden_by": None, "entity_category": None}
for e in self.states if e.split(".")[1].startswith("dishwasher_")]
await ws.send_json({"id": mid, "type": "result", "success": True, "result": rows})
elif typ == "config/device_registry/list":
await ws.send_json({"id": mid, "type": "result", "success": True, "result": []})