Eine moderne Android-App für frei konfigurierbare Smart-Home-Dashboards.
  • HTML 46.5%
  • Kotlin 40.5%
  • JavaScript 11.4%
  • CSS 1.1%
  • DM 0.5%
Find a file
2026-04-26 22:35:20 +02:00
app Improve refresh performance and expand baseline profile coverage 2026-04-26 22:35:20 +02:00
baselineprofile Improve refresh performance and expand baseline profile coverage 2026-04-26 22:35:20 +02:00
doku mehr icons 2026-04-24 22:09:13 +02:00
gradle Performanceoptimierungen 2026-03-30 22:52:44 +02:00
helper Duplizieren und Löschen von Widgets. Path-Picker. Struktur und Design-Verbesserungen 2026-04-19 22:18:16 +02:00
screenshots Screenshots 2026-03-22 23:19:49 +01:00
.codex neues Widget Thermometer 2026-03-31 00:15:24 +02:00
.gitignore gitignore 2026-03-21 00:15:05 +01:00
build.gradle.kts Performanceoptimierungen 2026-03-30 22:52:44 +02: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 Duplizieren und Löschen von Widgets. Path-Picker. Struktur und Design-Verbesserungen 2026-04-19 22:18:16 +02:00
sample-chart-tata.json Sample Chart Data, Achsenbeschriftung bei Charts 2026-03-23 19:55:43 +01:00
sample-dashboard-config.json Table für App, Optimierungen am Helper 2026-04-08 23:59:29 +02:00
sample-dashboard-index.json massive Funktionserweiterung, Doku 2026-03-16 23:35:00 +01:00
settings.gradle.kts Performanceoptimierungen 2026-03-30 22:52:44 +02: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.
  • GET-Statusrequests nutzen automatisch Last-Modified / If-Modified-Since, sobald ein Endpoint diesen Header liefert; bei 304 Not Modified bleibt der bisherige Snapshot erhalten.
  • 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 zusätzlich performante Canvas-Diagramme mit mehreren Kurven aus separaten Datensätzen rendern.
  • Widgets können numerische Statuswerte als Thermometer oder Gauge visualisieren.
  • 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 Konfigurations-Wizards für Dashboard, Widgets, Gruppen und Reiter arbeiten ganzseitig mit Breadcrumbs, Akkordeons und sofortigem lokalem Speichern.
  • Widgets lassen sich direkt im Wizard duplizieren; Gruppen-, Reiter- und Widget-Einträge können im jeweils ersten Schritt mit Bestätigung gelöscht werden.
  • Textquellen im Widget-Wizard besitzen einen Path-Picker, der eine Beispiel-Statusantwort ausliest und verfügbare JSON-Pfade direkt in Pfad-, Template- oder Tabellenfelder einsetzen kann.
  • Icon- und Farbfelder besitzen integrierte Picker; Hex-Farben bleiben zusätzlich manuell editierbar.
  • 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, wertet Last-Modified 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.title / widgets[].content.value: Textquellen mit mode: "static", "template", "table" oder "path"
  • widgets[].content.chart: Diagrammdefinition mit globaler X-/Y-Achse, Stiloptionen und mehreren Serien mit eigenem datasetPath
  • widgets[].content.thermometer / widgets[].content.gauge: Bereichs-Widgets mit min, max, valuePath und optionalem gradientColors
  • 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.image.enableDownload: optionales Downloadsymbol für Bild-Widgets; speichert das aktuell angezeigte Bild im Downloads-Ordner
  • widgets[].content.chart.series[]: mehrere Linien pro Diagramm, jeweils mit eigenem datasetPath, x.valuePath, y.valuePath, Farbe und Linienstil
  • 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

Konfigurationseditor in der App

Die App enthält einen eingebauten Konfigurationseditor für Dashboard, Widgets, Gruppen und Reiter.

  • Beim ersten Start ohne vorhandene Konfiguration kann zwischen Neue Konfiguration anlegen und Bestehende Konfiguration laden gewählt werden.
  • Widget-Wizards sind in die Schritte Grunddaten & Layout, Platzierung, Inhalt, Status und Aktion gegliedert.
  • Im Schritt Inhalt sind Titel, Wert, Icon, Design und die modusspezifischen Inhalte als Akkordeons organisiert.
  • Unter Grunddaten & Layout kann ein bestehendes Widget direkt dupliziert werden; das Duplikat übernimmt auch seine Platzierung in Gruppen und Reitern.
  • Im jeweils ersten Schritt von Widget-, Gruppen- und Reiter-Wizards steht zusätzlich eine geschützte Löschaktion mit Bestätigungsdialog zur Verfügung.
  • Farbfelder nutzen einen Picker mit Presets, Tints und optionalem Leeren auf Standardwert.
  • Icon-Felder nutzen eine suchbare Material-Icon-Auswahl.
  • Pfad-, Template- und Tabellenfelder für Titel und Wert besitzen einen Path-Picker, der verfügbare JSON-Pfade aus einer Beispiel-Statusantwort anbietet.

Im Moment sind unter anderem diese Icons verfügbar:

air bathroom bike, bicycle blinds blinds_closed bolt bus car, directions_car camera, camera_alt cleaning_services, vacuum coffee_maker curtains curtains_closed door, door_front electric_meter food, restaurant garage heat_pump home kids_room, bedroom_child kitchen laundry, local_laundry_service light, lightbulb lock, lock_open microwave outdoor_grill pc, computer pets power, power_settings_new remote router sensor, sensors sensor_door sensor_window shower solar_power speaker 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[].content.mode: "chart": rendert ein Canvas-Chart mit mehreren Serien; V1 ist bewusst schlank und auf Liniencharts optimiert
  • widgets[].content.mode: "thermometer" / "gauge": rendert ein Bereichs-Widget mit min, max, valuePath und optionalem Farbverlauf
  • widgets[].action: optionale Klick-Aktion als endpoint, switch_tab oder open_url

Bei Textquellen für title und value:

  • mode: "static": zeigt den Wert unverändert an
  • mode: "template": ersetzt Platzhalter wie {{json.path}}
  • mode: "table": ersetzt ebenfalls {{json.path}} und teilt danach per Zeilenumbruch in Rows und per | in Spalten
  • mode: "path": liest den sichtbaren Text direkt aus einem JSON-Pfad
  • bei mode: "table" sind führende oder abschliessende | optional; Markdown-Trennzeilen wie |---|---| werden ignoriert

Beispiel für value.mode: "table":

{
  "value": {
    "mode": "table",
    "value": "Quelle | Leistung\nNetz | {{grid.draw_watts}} W\nPV | {{pv.watts}} W"
  }
}

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. Ist für eine expandierbare Gruppe widthColumns gesetzt, begrenzt dieser Wert zusätzlich die Popup-Breite.

{
  "dashboard": {
    "groups": [
      {
        "id": "living-room-windows",
        "title": "Fenster",
        "order": 30,
        "widthColumns": 4,
        "expandable": true,
        "icon": {
          "name": "window",
          "sizeDp": 28
        },
        "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.
  • wenn bei einer expandierbaren Gruppe widthColumns gesetzt ist, begrenzt dieser Wert zusätzlich die Popup-Breite
  • valueWidgetId bleibt für einen einzelnen gespiegelten Wert nutzbar; mit valuePattern lassen sich mehrere Widget-Values auf der Link-Kachel kombinieren. Die benötigten Widget-IDs werden direkt aus den Platzhaltern extrahiert.
  • 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, plus einfache value.mode: "table"-Eingabe mit Preview