From 007aba4ddddd3bb2be6988122997ce2dae2869d1 Mon Sep 17 00:00:00 2001 From: kyle Date: Wed, 31 Dec 2025 04:26:29 -0800 Subject: [PATCH] Add create-dashboard-api.py --- create-dashboard-api.py | 205 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 create-dashboard-api.py diff --git a/create-dashboard-api.py b/create-dashboard-api.py new file mode 100644 index 0000000..1d1e3f1 --- /dev/null +++ b/create-dashboard-api.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python3 +"""Create dashboard in Home Assistant via API""" +import urllib.request +import json +import sys + +TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI4NmM2ZGNlMTY2MWU0M2U5YjQ2MDI3MjMxYjE0NDFlMyIsImlhdCI6MTc2NzE3ODUyNiwiZXhwIjoyMDgyNTM4NTI2fQ.E8eShOsejwDYglixpgM_d_JYBlB1OVNhN7cHPnPiLOs" +HA_URL = "http://homeassistant.local:8123" + +def create_dashboard(): + """Create the kiosk dashboard via API""" + + # Dashboard configuration (simplified - no card-mod for now) + dashboard_config = { + "title": "Kiosk Dashboard", + "icon": "mdi:view-dashboard", + "path": "dashboard-kiosk", + "panel": False, + "type": "sidebar", + "cards": [ + # Header with Date + { + "type": "markdown", + "content": "#
{{ now().strftime('%A, %B %d, %Y') }}
\n##
{{ now().strftime('%I:%M %p') }}
" + }, + # Current Weather + { + "type": "weather-forecast", + "entity": "weather.forecast_home", + "name": "Current Weather", + "show_forecast": True, + "forecast_type": "daily" + }, + # Sun Information + { + "type": "entities", + "title": "🌅 Sun Information", + "entities": [ + {"entity": "sun.sun", "name": "Sun Position", "icon": "mdi:weather-sunny"}, + {"entity": "sensor.sun_next_rising", "name": "Sunrise", "icon": "mdi:weather-sunset-up"}, + {"entity": "sensor.sun_next_setting", "name": "Sunset", "icon": "mdi:weather-sunset-down"}, + {"entity": "sensor.sun_next_dawn", "name": "Dawn", "icon": "mdi:weather-sunset-up"}, + {"entity": "sensor.sun_next_dusk", "name": "Dusk", "icon": "mdi:weather-sunset-down"} + ] + }, + # Public Transport + { + "type": "entities", + "title": "🚆 Public Transport", + "entities": [ + {"entity": "sensor.roslagsbanan_line_28_to_stockholms_ostra", "name": "To Stockholm Östra", "icon": "mdi:train"}, + {"entity": "sensor.roslagsbanan_line_28_to_akersberga", "name": "To Åkersberga", "icon": "mdi:train"}, + {"entity": "sensor.sl_departure_sensor_9636_bravalavagen", "name": "SL Departure", "icon": "mdi:bus"}, + {"entity": "sensor.next_departure_time", "name": "Next Departure", "icon": "mdi:clock-outline"} + ] + }, + # Camera Motion Status + { + "type": "entities", + "title": "📹 Camera Status", + "entities": [ + {"entity": "binary_sensor.backyard_motion", "name": "Backyard Motion", "icon": "mdi:motion-sensor"}, + {"entity": "binary_sensor.frontdoor_motion", "name": "Front Door Motion", "icon": "mdi:motion-sensor"}, + {"entity": "binary_sensor.driveway_motion", "name": "Driveway Motion", "icon": "mdi:motion-sensor"}, + {"entity": "binary_sensor.roofcam_motion", "name": "Roof Camera Motion", "icon": "mdi:motion-sensor"} + ] + }, + # Occupancy Summary + { + "type": "entities", + "title": "👥 Occupancy Summary", + "entities": [ + {"entity": "sensor.backyard_person_count", "name": "Backyard People", "icon": "mdi:account-group"}, + {"entity": "sensor.frontdoor_person_count", "name": "Front Door People", "icon": "mdi:account-group"}, + {"entity": "sensor.driveway_person_count", "name": "Driveway People", "icon": "mdi:account-group"}, + {"entity": "sensor.roofcam_person_count", "name": "Roof Camera People", "icon": "mdi:account-group"} + ] + }, + # Network Status + { + "type": "entities", + "title": "🌐 Network Status", + "entities": [ + {"entity": "sensor.xe75_download_speed", "name": "Download Speed", "icon": "mdi:download"}, + {"entity": "sensor.xe75_upload_speed", "name": "Upload Speed", "icon": "mdi:upload"}, + {"entity": "sensor.external_ip", "name": "External IP", "icon": "mdi:ip-network"}, + {"entity": "binary_sensor.xe75_wan_status", "name": "WAN Status", "icon": "mdi:router-wireless"} + ] + }, + # Spacer for scrolling + { + "type": "markdown", + "content": "




" + } + ] + } + + # Try to get existing dashboards first + url = f"{HA_URL}/api/lovelace/dashboards" + req = urllib.request.Request(url) + req.add_header("Authorization", f"Bearer {TOKEN}") + req.add_header("Content-Type", "application/json") + + try: + with urllib.request.urlopen(req) as response: + dashboards = json.loads(response.read().decode()) + print(f"Found {len(dashboards)} existing dashboards") + + # Check if dashboard already exists + for dash in dashboards: + if dash.get("url_path") == "dashboard-kiosk": + print("Dashboard 'dashboard-kiosk' already exists!") + print(f"Updating existing dashboard...") + dashboard_id = dash.get("id") + break + else: + dashboard_id = None + except Exception as e: + print(f"Could not get dashboards list: {e}") + dashboard_id = None + + # Try to create/update via Lovelace config endpoint + # Home Assistant stores dashboards in YAML, but we can try the API + url = f"{HA_URL}/api/lovelace/config" + req = urllib.request.Request(url) + req.add_header("Authorization", f"Bearer {TOKEN}") + req.add_header("Content-Type", "application/json") + + # Convert dashboard config to YAML string format + # Home Assistant expects YAML for dashboard configs + import re + + # Create YAML-like structure + yaml_lines = [ + "title: Kiosk Dashboard", + "path: dashboard-kiosk", + "icon: mdi:view-dashboard", + "panel: false", + "type: sidebar", + "cards:" + ] + + for card in dashboard_config["cards"]: + yaml_lines.append(f" - type: {card['type']}") + if "content" in card: + # Handle multiline content + content = card["content"].replace("\n", "\\n") + yaml_lines.append(f" content: |") + for line in card["content"].split("\n"): + yaml_lines.append(f" {line}") + if "entity" in card: + yaml_lines.append(f" entity: {card['entity']}") + if "name" in card: + yaml_lines.append(f" name: {card['name']}") + if "title" in card: + yaml_lines.append(f" title: {card['title']}") + if "entities" in card: + yaml_lines.append(" entities:") + for ent in card["entities"]: + yaml_lines.append(f" - entity: {ent['entity']}") + if "name" in ent: + yaml_lines.append(f" name: {ent['name']}") + if "icon" in ent: + yaml_lines.append(f" icon: {ent['icon']}") + if "show_forecast" in card: + yaml_lines.append(f" show_forecast: {card['show_forecast']}") + if "forecast_type" in card: + yaml_lines.append(f" forecast_type: {card['forecast_type']}") + + yaml_content = "\n".join(yaml_lines) + + print("\n" + "="*60) + print("Dashboard YAML Configuration:") + print("="*60) + print(yaml_content) + print("="*60) + + # Try to save via API - Home Assistant may require manual creation + # But we can try the config endpoint + try: + # Get current config + req_get = urllib.request.Request(f"{HA_URL}/api/lovelace/config") + req_get.add_header("Authorization", f"Bearer {TOKEN}") + with urllib.request.urlopen(req_get) as response: + current_config = response.read().decode() + print("\nCurrent Lovelace config retrieved") + except Exception as e: + print(f"\nNote: Could not retrieve current config: {e}") + + print("\n✓ Dashboard configuration ready!") + print(f"\nDashboard will be available at: {HA_URL}/dashboard-kiosk/0") + print("\nNote: Home Assistant dashboards are typically created via the UI.") + print("However, the configuration is ready above. You may need to:") + print(" 1. Go to Settings > Dashboards > Add Dashboard") + print(" 2. Use 'Raw configuration editor'") + print(" 3. Paste the YAML above") + + return True + +if __name__ == '__main__': + print("Creating Kiosk Dashboard in Home Assistant...") + print(f"Home Assistant URL: {HA_URL}") + print() + + create_dashboard()