Dashboard Studio 1.8.0: vacuum card, clock position and background, light groups
- Vacuum card: status, battery, start/pause, dock, locate, suction power and an optional live map from a camera or image entity - Clock: 3x3 text position picker and a background image from links or Immich albums (shared slideshow code with the Photos card), dimmable - Light card lists the lights of a light group with their own switches; picking a group grows the card to fit Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,10 +13,10 @@ def now_iso():
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
def entity(entity_id, state, **attrs):
|
||||
def entity(eid, state, /, **attrs):
|
||||
ts = now_iso()
|
||||
return {
|
||||
"entity_id": entity_id,
|
||||
"entity_id": eid,
|
||||
"state": state,
|
||||
"attributes": attrs,
|
||||
"last_changed": ts,
|
||||
@@ -47,6 +47,10 @@ class MockHA:
|
||||
entity("light.reading_lamp", "on", friendly_name="Reading Lamp", brightness=140, rgb_color=[255, 140, 60], supported_color_modes=["rgb"]),
|
||||
entity("light.kitchen", "off", friendly_name="Kitchen", brightness=None, supported_color_modes=["brightness"]),
|
||||
entity("light.bedroom", "on", friendly_name="Bedroom", brightness=90, supported_color_modes=["brightness"]),
|
||||
entity("light.downstairs", "on", friendly_name="Downstairs", brightness=160, supported_color_modes=["brightness"],
|
||||
entity_id=["light.living_room", "light.kitchen", "light.reading_lamp"]),
|
||||
entity("vacuum.robot", "docked", friendly_name="Robot Vacuum", battery_level=82, fan_speed="standard",
|
||||
fan_speed_list=["quiet", "standard", "turbo", "max"], status="Charging"),
|
||||
entity("switch.coffee_machine", "off", friendly_name="Coffee Machine"),
|
||||
entity("fan.office", "off", friendly_name="Office Fan"),
|
||||
entity("sensor.living_room_temperature", "21.4", friendly_name="Living Room Temperature", unit_of_measurement="°C", device_class="temperature"),
|
||||
@@ -104,6 +108,8 @@ class MockHA:
|
||||
new["attributes"].update(attrs)
|
||||
new["last_updated"] = now_iso()
|
||||
self.states[entity_id] = new
|
||||
if entity_id.startswith("light.") and "entity_id" not in new["attributes"]:
|
||||
self._sync_groups(entity_id)
|
||||
event = {"event_type": "state_changed", "data": {"entity_id": entity_id, "old_state": old, "new_state": new}, "time_fired": now_iso()}
|
||||
for ws, sub_ids in list(self.clients):
|
||||
for sid in sub_ids:
|
||||
@@ -115,13 +121,40 @@ class MockHA:
|
||||
except Exception: # client went away
|
||||
pass
|
||||
|
||||
def _vacuum(self, eid, service, data):
|
||||
if service == "start":
|
||||
self.set_state(eid, "cleaning", status="Cleaning")
|
||||
elif service == "pause":
|
||||
self.set_state(eid, "paused", status="Paused")
|
||||
elif service in ("return_to_base", "stop"):
|
||||
self.set_state(eid, "returning", status="Returning home")
|
||||
asyncio.get_event_loop().call_later(6, lambda: self.set_state(eid, "docked", status="Charging"))
|
||||
elif service == "set_fan_speed":
|
||||
self.set_state(eid, None, fan_speed=data.get("fan_speed"))
|
||||
|
||||
def _sync_groups(self, changed):
|
||||
for gid, g in list(self.states.items()):
|
||||
members = g["attributes"].get("entity_id")
|
||||
if not gid.startswith("light.") or not isinstance(members, list) or changed not in members:
|
||||
continue
|
||||
on = [self.states[m] for m in members if m in self.states and self.states[m]["state"] == "on"]
|
||||
bright = round(sum(m["attributes"].get("brightness") or 0 for m in on) / len(on)) if on else None
|
||||
self.set_state(gid, "on" if on else "off", brightness=bright)
|
||||
|
||||
def call_service(self, domain, service, data):
|
||||
targets = data.get("entity_id") or (data.get("target") or {}).get("entity_id") or []
|
||||
if isinstance(targets, str):
|
||||
targets = [targets]
|
||||
expanded = []
|
||||
for eid in targets:
|
||||
members = (self.states.get(eid) or {}).get("attributes", {}).get("entity_id")
|
||||
expanded += members if eid.startswith("light.") and isinstance(members, list) else [eid]
|
||||
for eid in expanded:
|
||||
if eid not in self.states:
|
||||
continue
|
||||
if domain == "vacuum" or eid.startswith("vacuum."):
|
||||
self._vacuum(eid, service, data)
|
||||
continue
|
||||
st = self.states[eid]["state"]
|
||||
if eid.startswith("climate.") and service in ("turn_on", "turn_off", "toggle"):
|
||||
on = service == "turn_on" or (service == "toggle" and st == "off")
|
||||
|
||||
Reference in New Issue
Block a user