Eine moderne Android-App für frei konfigurierbare Smart-Home-Dashboards.
  • HTML 64.5%
  • Kotlin 23.6%
  • JavaScript 9.5%
  • DM 1.5%
  • CSS 0.9%
Find a file
2026-03-21 21:08:45 +01:00
app release 2026-03-21 20:48:06 +01:00
doku Doku & Readme Update 2026-03-21 21:08:45 +01:00
gradle massive Funktionserweiterung, Doku 2026-03-16 23:35:00 +01:00
helper Optimierungen des Shutter Widgets, jetzt auch ohne Status und Slider möglich 2026-03-21 00:06:03 +01:00
.gitignore gitignore 2026-03-21 00:15:05 +01:00
build.gradle.kts init 2026-03-16 17:49:38 +01:00
gradle.properties init 2026-03-16 17:49:38 +01:00
gradlew init 2026-03-16 17:49:38 +01:00
gradlew.bat init 2026-03-16 17:49:38 +01:00
icon.svg massive Funktionserweiterung, Doku 2026-03-16 23:35:00 +01:00
README.md Doku & Readme Update 2026-03-21 21:08:45 +01:00
sample-dashboard-config.json Optimierungen des Shutter Widgets, jetzt auch ohne Status und Slider möglich 2026-03-21 00:06:03 +01:00
sample-dashboard-index.json massive Funktionserweiterung, Doku 2026-03-16 23:35:00 +01:00
settings.gradle.kts init 2026-03-16 17:49:38 +01:00

JSON SmartHome Dashboard

Eine moderne Android-App auf Basis von Jetpack Compose für frei konfigurierbare Smart-Home-Dashboards.

Die vollständige Dokumentation findest du unter https://json-smarthome-dashboard.ringelbaer.de/

Kernideen

  • Widgets werden aus einer externen JSON-Datei geladen und lokal gecacht.
  • Die Konfiguration kann direkt per Dashboard-JSON, über eine index.json mit Profilen oder per lokaler JSON-Datei vom Gerät geladen werden.
  • Status und Aktionen laufen über Requests mit optionalen Headern und optionalem JSON-Body.
  • Aktionen können optional per prompt einen Bestätigungsdialog verlangen.
  • Die HTTP-Methode ist pro Request optional konfigurierbar; ohne Angabe gilt GET ohne Body und POST mit Body.
  • Ein explizites GET wird ohne JSON-Body ausgeführt; für Requests mit Body sollte POST, PUT, PATCH oder DELETE verwendet werden.
  • Antworten können JSON, Text, PNG oder JPG sein.
  • Widgets können Text mit tintbaren Material-Icons, Emoji-Fallback oder ein Bild darstellen.
  • Widgets können ihre Titel-/Wert-Schriftgrössen über die JSON-Konfiguration überschreiben.
  • Widgets können optional eine eigene Kachelfarbe sowie individuelle Titel-/Wertfarben aus der JSON-Konfiguration erhalten.
  • Das Dashboard kann mehrere Reiter mit frei zugeordneten Widgets anzeigen.
  • Widgets können in optisch zusammenhängende Gruppen mit eigener Breite im Raster, integriertem Titel-Badge, optional eigener Umrandungsfarbe und optional einklappbarer Popup-Ansicht gelegt werden.
  • Slider-Widgets können einen Statuswert anzeigen und per Dialog einen neuen Wert an den Action-Endpoint senden.
  • Select-Widgets können Optionen aus dem Status laden und per Dialog einen neuen Wert bestätigt an einen Action-Endpoint senden.
  • Rollladen-Widgets können optional einen Positionswert anzeigen und im Dialog getrennte Hoch-/Stop-/Runter-Kommandos sowie optional einen Slider-Endpoint nutzen.
  • Layout basiert auf konfigurierbaren Rastergrössen von 1/1 bis 4/4 sowie full/1 bis full/4 und sortiert sich bei Orientierungswechsel neu.
  • Die Einstellungen sind als ganzseitige Seite mit Reitern für Widget-Konfiguration, Optik und Debug aufgebaut.
  • Die Widget-Konfiguration kann zusätzlich eine automatische Status-Aktualisierung im Intervall von 1, 5, 10 oder 30 Minuten steuern.
  • Das App-Theme kann zwischen Auto, Light, Dark und Custom umgeschaltet werden; im Custom-Modus sind die wichtigsten Theme-Farben inklusive Gruppenhervorhebung editierbar.
  • Widget-Refreshes können optional pro Kachel, global links in der Titelleiste oder direkt im Aktualisieren-Button signalisiert werden.
  • Custom-Farbpaletten können zusätzlich per JSON-Template-Datei importiert werden.
  • Im Debug-Reiter kann optional vor jedem Refresh die Konfiguration erneut von der gespeicherten URL geladen werden.
  • Für lokale Tests kann die App im Debug-Reiter fehlerhafte SSL-Zertifikate ignorieren.

Architektur

  • DashboardViewModel: Bootstrapping, Status-Refresh, Aktionen, Dialogzustand
  • DashboardRepository: Konfigurationscache, Download, Validierung, Request-Ausführung
  • SmartHomeHttpClient: führt konfigurierte HTTP-Requests aus und wählt im Auto-Modus GET oder POST
  • DashboardGrid: eigenes Grid-Packing mit variablen Widget-Grössen
  • DashboardConfig: erweiterbare JSON-Struktur mit klar getrennten Bereichen für Layout, Inhalt, Status und Aktion

Konfigurations-JSON

Die Datenstruktur ist absichtlich modular:

  • dashboard: Titel und Raster-Defaults
  • dashboard.groups: optionale Widget-Gruppen mit bevorzugtem widthColumns, legacy maxWidthDp, order, optionalem expandable, optionalem icon, optionalem highlightColor, optionalem borderColor und widgetIds
  • dashboard.tabs: optionale Reiter mit groupIds und widgetIds
  • defaults: Request-Defaults für Header und Timeouts
  • defaults.sharedStatusRequests: globale Sammelrequests, die pro Aktualisierung nur einmal geladen und dann von mehreren Widgets geteilt werden
  • widgets[].status.request.method / widgets[].action.request.method: optionale HTTP-Methode (get, post, put, patch, delete)
  • widgets[].status.responseKind: optionale Antwortart (auto, json, text, jpg, png)
  • widgets[].layout: Reihenfolge und Widget-Seitenverhältnis (1/1 bis 4/4, plus full/1 bis full/4 für vollbreite Zeilenwidgets)
  • widgets[].content: Icon/Text/Image-Definition, z. B. icon.name: "lightbulb", icon.namePath: "device.icon", typography.titleSizeSp, colors.tileColor oder mode: "slider"
  • widgets[].content.select: Konfiguration für aus Statusdaten geladene Auswahloptionen mit bestätigter Action
  • widgets[].content.shutter: Konfiguration für Rollladensteuerung mit optionalem Statuswert, optionalem Slider-Endpoint und getrennten Hoch-/Stop-/Runter-Endpoints
  • widgets[].content.colors: optionale Widgetfarben mit statischen Werten und tileColorRules, titleColorRules, valueColorRules
  • widgets[].action.prompt, widgets[].content.select.action.prompt und widgets[].content.shutter.*.prompt: optionale Bestätigungsdialoge vor dem Ausführen

Im Moment sind unter anderem diese Icons verfügbar:

air bike, bicycle bolt bus camera, camera_alt door, door_front home light, lightbulb lock, lock_open pc, computer power, power_settings_new remote router speaker sensor, sensors temperature, thermostat train, tram tv video, videocam water, water_drop wifi window

  • widgets[].status: entweder direkter Endpoint für Statusabfrage oder sharedRequestId auf einen globalen Sammelrequest; bei reinen icon_text-Buttons und bestimmten shutter-Widgets darf status fehlen
  • widgets[].action: optionale Klick-Aktion als endpoint, switch_tab oder open_url

Bei Slider-Widgets:

  • widgets[].content.slider.min / max / step: Wertebereich
  • widgets[].content.slider.valuePath: JSON-Pfad des aktuellen Statuswerts
  • widgets[].content.slider.actionValueBindings: wohin der eingestellte Wert beim Action-Request geschrieben wird, z. B. JSON-Body, Header oder Query-Parameter

Bei Select-Widgets:

  • widgets[].content.mode: "select": aktiviert den Auswahl-Dialog
  • widgets[].content.select.valuePath: JSON-Pfad des aktuell gesetzten Werts
  • widgets[].content.select.optionsPath: JSON-Pfad auf das Options-Array im Status
  • widgets[].content.select.optionValuePath: Pfad innerhalb eines Optionsobjekts für den zu sendenden Wert
  • widgets[].content.select.optionLabelPath: Pfad innerhalb eines Optionsobjekts für den sichtbaren Text
  • widgets[].content.select.action.request: Endpoint für die bestätigte Auswahl
  • widgets[].content.select.action.valueBindings: wohin der ausgewählte Wert im Request geschrieben wird
  • widgets[].content.select.action.prompt: optionaler Bestätigungsdialog vor dem Senden

Bei Rollladen-Widgets:

  • widgets[].content.mode: "shutter": aktiviert die Rollladensteuerung
  • widgets[].content.shutter.valuePath: JSON-Pfad der aktuellen Position
  • widgets[].content.shutter.sliderAction.request: optionaler Endpoint für die Zielposition
  • widgets[].content.shutter.sliderAction.valueBindings: wohin der eingestellte Sliderwert im Request geschrieben wird
  • widgets[].content.shutter.sliderAction.prompt: optionaler Bestätigungsdialog vor dem Senden der Zielposition
  • widgets[].content.shutter.upAction.request: Endpoint für Hoch
  • widgets[].content.shutter.stopAction.request: optionaler Endpoint für Stop
  • widgets[].content.shutter.downAction.request: Endpoint für Runter
  • widgets[].content.shutter.upAction.prompt / stopAction.prompt / downAction.prompt: optionale Bestätigungsdialoge für Einzelkommandos

Bei Widget-Aktionen:

  • widgets[].action.type: "endpoint": führt den hinterlegten Request aus
  • widgets[].action.type: "switch_tab": wechselt auf den in targetTabId hinterlegten Reiter
  • widgets[].action.type: "open_url": öffnet die in url hinterlegte Adresse im Browser
  • widgets[].action.prompt: true: zeigt vor dem Ausführen einen Bestätigungsdialog

Ein Beispiel liegt in sample-dashboard-config.json.

Für individuelle Widgetfarben kann optional ein eigener Farbblock gesetzt werden:

{
  "content": {
    "colors": {
      "tileColor": "#1E293B",
      "titleColor": "#E2E8F0",
      "valueColor": "#F8FAFC",
      "tileColorRules": [
        {
          "when": {
            "path": "state",
            "equals": "on"
          },
          "color": "#FFF4D8"
        }
      ],
      "titleColorRules": [
        {
          "when": {
            "path": "state",
            "equals": "on"
          },
          "color": "#4A3412"
        }
      ],
      "valueColorRules": [
        {
          "when": {
            "path": "state",
            "equals": "on"
          },
          "color": "#B45309"
        }
      ]
    }
  }
}

Wenn nur tileColor gesetzt ist, leitet die App automatisch kontrastreiche Standardfarben für Titel und Wert ab. Wenn keine Regel greift, bleiben die statischen Farben aus tileColor, titleColor und valueColor der Fallback.

Konfigurationsquellen

Auf der Einstellungsseite gibt es zwei Wege:

  • URL zu einer direkten Dashboard-JSON oder zu einer index.json
  • Öffnen einer lokalen JSON-Datei vom Gerät

Zusätzlich kann dort eingestellt werden, ob Widgets nur manuell beziehungsweise beim App-Start oder automatisch alle 1, 5, 10 oder 30 Minuten aktualisiert werden.

Wenn die URL auf eine index.json zeigt, wird beim Laden ein Profilauswahldialog angezeigt. Das zuletzt genutzte Profil ist dabei bereits vorausgewählt.

Optik und Farb-Template

Auf der Einstellungsseite unter Optik gibt es vier Modi:

  • Auto
  • Light
  • Dark
  • Custom

Im Custom-Modus lassen sich primary, secondary, tertiary, background, surface, groupHighlight und error direkt anpassen oder als Template-Datei importieren.

Beispiel für ein Farb-Template:

{
  "schemaVersion": 1,
  "mode": "custom",
  "colors": {
    "primary": "#0F5E56",
    "secondary": "#D9773A",
    "tertiary": "#314654",
    "background": "#F4EFE8",
    "surface": "#F9F6F0",
    "groupHighlight": "#D9773A",
    "error": "#D75E67"
  }
}

Nicht angegebene Farben bleiben beim Import unverändert.

index.json

Die index.json dient als Profilverzeichnis für mehrere Dashboard-Konfigurationen:

{
  "schemaVersion": 1,
  "title": "Smart-Home-Profile",
  "profiles": [
    {
      "id": "apartment",
      "title": "Wohnung",
      "description": "Alltag in der Wohnung",
      "configUrl": "https://example.org/configs/apartment-dashboard.json"
    },
    {
      "id": "vacation",
      "title": "Urlaubsmodus",
      "description": "Reduziertes Dashboard für Abwesenheit",
      "configUrl": "vacation-dashboard.json"
    }
  ]
}

configUrl darf absolut oder relativ zur index.json angegeben werden.

Ein Beispiel liegt in sample-dashboard-index.json.

Gemeinsame Statusabfragen

Wenn mehrere Widgets denselben Status-Endpoint verwenden, kann der Request zentral definiert und geteilt werden:

{
  "defaults": {
    "sharedStatusRequests": [
      {
        "id": "living-room-light-state",
        "request": {
          "url": "http://192.168.1.20/api/lights/living-room"
        }
      }
    ]
  },
  "widgets": [
    {
      "id": "living-room-light",
      "status": {
        "sharedRequestId": "living-room-light-state"
      }
    },
    {
      "id": "living-room-dimmer",
      "status": {
        "sharedRequestId": "living-room-light-state"
      }
    }
  ]
}

Der Sammelrequest wird bei einem Refresh nur einmal ausgeführt. Alle Widgets mit derselben sharedRequestId lesen danach unterschiedliche Werte aus derselben Antwort.

Gruppen

Gruppen bilden optisch zusammenhängende Abschnitte auf einem Tab. Eine Gruppe hat bevorzugt eine eigene Breite in Rasterspalten über widthColumns; das ältere Feld maxWidthDp wird aus Kompatibilitätsgründen weiterhin akzeptiert. Mit expandable: true wird sie statt eines normalen Gruppenblocks als 1/1 Link-Kachel angezeigt und öffnet ihre Widgets in einem Popup mit gleicher Kachelgrösse.

{
  "dashboard": {
    "groups": [
      {
        "id": "living-room-windows",
        "title": "Fenster",
        "order": 30,
        "widthColumns": 4,
        "expandable": true,
        "icon": {
          "name": "window",
          "sizeDp": 28
        },
        "valueWidgetIds": ["window-left", "window-right", "door-garden"],
        "valuePattern": "{{window-left}} / {{window-right}} / {{door-garden}}",
        "highlightColor": "#C88A43",
        "borderColor": "#A95F1F",
        "widgetIds": ["window-left", "window-right", "door-garden"]
      }
    ],
    "tabs": [
      {
        "id": "living",
        "title": "Wohnzimmer",
        "groupIds": ["living-room-windows"],
        "widgetIds": ["main-light", "ambient-light"]
      }
    ]
  }
}

Wichtig:

  • Gruppen werden innerhalb eines Tabs über dashboard.groups[].order einsortiert.
  • Freie Widgets im selben Tab werden über widgets[].layout.order einsortiert.
  • widthColumns ist die empfohlene Breitenangabe; maxWidthDp bleibt nur für ältere Konfigurationen relevant.
  • highlightColor färbt standardmässig den Gruppentitel und dient als Fallback für die Kontur.
  • borderColor überschreibt gezielt die Gruppenumrandung; ohne Wert bleibt die bestehende Hervorhebungsfarbe aktiv.
  • expandable: true verdichtet die Gruppe im Dashboard auf eine 1/1 Link-Kachel; der Popup-Inhalt nutzt dieselbe Widgetgrösse wie das Haupt-Grid und maximal fünf Spalten.
  • valueWidgetId bleibt für einen einzelnen gespiegelten Wert nutzbar; mit valueWidgetIds plus valuePattern lassen sich mehrere Widget-Values auf der Link-Kachel kombinieren.
  • Widgets dürfen in einer Gruppe liegen oder direkt in einem Tab referenziert werden. Innerhalb desselben Tabs sind doppelte Referenzen nicht erlaubt.

Sicherheitsregeln für URLs

  • https://... ist erlaubt
  • http://192.168.x.x/... ist erlaubt
  • andere unverschlüsselte HTTP-Ziele werden in der App abgelehnt

Die App aktiviert aus technischen Gründen Cleartext-Traffic auf Android global, erzwingt die eigentliche Einschränkung aber zur Laufzeit in der URL-Validierung.

Build

  1. Android Studio Ladybug oder neuer / aktuelles Android Studio öffnen
  2. Android SDK für compileSdk 36 installieren
  3. Gradle Sync starten

Falls lokal noch kein local.properties existiert, legt Android Studio es beim Öffnen an.

Konfigurationshelper

Im Ordner helper/ liegt ein rein statischer HTML/JavaScript-Helper für die Konfiguration.

  • Start: helper/index.html direkt im Browser öffnen
  • Funktionen: GUI für Dashboard-JSON und index.json, Dateiimport, Copy-and-Paste-JSON, Download, Vorschau für Tabs, Gruppen und Widget-Layout