- HTML 46.5%
- Kotlin 40.5%
- JavaScript 11.4%
- CSS 1.1%
- DM 0.5%
| app | ||
| baselineprofile | ||
| doku | ||
| gradle | ||
| helper | ||
| screenshots | ||
| .codex | ||
| .gitignore | ||
| build.gradle.kts | ||
| gradle.properties | ||
| gradlew | ||
| gradlew.bat | ||
| icon.svg | ||
| README.md | ||
| sample-chart-tata.json | ||
| sample-dashboard-config.json | ||
| sample-dashboard-index.json | ||
| settings.gradle.kts | ||
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.jsonmit 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
prompteinen Bestätigungsdialog verlangen. - Die HTTP-Methode ist pro Request optional konfigurierbar; ohne Angabe gilt
GETohne Body undPOSTmit Body. - Ein explizites
GETwird ohne JSON-Body ausgeführt; für Requests mit Body solltePOST,PUT,PATCHoderDELETEverwendet werden. - GET-Statusrequests nutzen automatisch
Last-Modified/If-Modified-Since, sobald ein Endpoint diesen Header liefert; bei304 Not Modifiedbleibt 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/1bis4/4sowiefull/1bisfull/4und 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,DarkundCustomumgeschaltet werden; imCustom-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, DialogzustandDashboardRepository: Konfigurationscache, Download, Validierung, Request-AusführungSmartHomeHttpClient: führt konfigurierte HTTP-Requests aus, wertetLast-Modifiedaus und wählt im Auto-ModusGEToderPOSTDashboardGrid: eigenes Grid-Packing mit variablen Widget-GrössenDashboardConfig: 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-Defaultsdashboard.groups: optionale Widget-Gruppen mit bevorzugtemwidthColumns, legacymaxWidthDp,order, optionalemexpandable, optionalemicon, optionalemhighlightColor, optionalemborderColorundwidgetIdsdashboard.tabs: optionale Reiter mitgroupIdsundwidgetIdsdefaults: Request-Defaults für Header und Timeoutsdefaults.sharedStatusRequests: globale Sammelrequests, die pro Aktualisierung nur einmal geladen und dann von mehreren Widgets geteilt werdenwidgets[].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/1bis4/4, plusfull/1bisfull/4für vollbreite Zeilenwidgets)widgets[].content: Icon/Text/Image-Definition, z. B.icon.name: "lightbulb",icon.namePath: "device.icon",typography.titleSizeSp,colors.tileColorodermode: "slider"widgets[].content.title/widgets[].content.value: Textquellen mitmode: "static","template","table"oder"path"widgets[].content.chart: Diagrammdefinition mit globaler X-/Y-Achse, Stiloptionen und mehreren Serien mit eigenemdatasetPathwidgets[].content.thermometer/widgets[].content.gauge: Bereichs-Widgets mitmin,max,valuePathund optionalemgradientColorswidgets[].content.select: Konfiguration für aus Statusdaten geladene Auswahloptionen mit bestätigter Actionwidgets[].content.shutter: Konfiguration für Rollladensteuerung mit optionalem Statuswert, optionalem Slider-Endpoint und getrennten Hoch-/Stop-/Runter-Endpointswidgets[].content.image.enableDownload: optionales Downloadsymbol für Bild-Widgets; speichert das aktuell angezeigte Bild im Downloads-Ordnerwidgets[].content.chart.series[]: mehrere Linien pro Diagramm, jeweils mit eigenemdatasetPath,x.valuePath,y.valuePath, Farbe und Linienstilwidgets[].content.colors: optionale Widgetfarben mit statischen Werten undtileColorRules,titleColorRules,valueColorRuleswidgets[].action.prompt,widgets[].content.select.action.promptundwidgets[].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 anlegenundBestehende Konfiguration ladengewählt werden. - Widget-Wizards sind in die Schritte
Grunddaten & Layout,Platzierung,Inhalt,StatusundAktiongegliedert. - Im Schritt
InhaltsindTitel,Wert,Icon,Designund die modusspezifischen Inhalte als Akkordeons organisiert. - Unter
Grunddaten & Layoutkann 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 odersharedRequestIdauf einen globalen Sammelrequest; bei reinenicon_text-Buttons und bestimmtenshutter-Widgets darfstatusfehlenwidgets[].content.mode: "chart": rendert ein Canvas-Chart mit mehreren Serien; V1 ist bewusst schlank und auf Liniencharts optimiertwidgets[].content.mode: "thermometer"/"gauge": rendert ein Bereichs-Widget mitmin,max,valuePathund optionalem Farbverlaufwidgets[].action: optionale Klick-Aktion alsendpoint,switch_taboderopen_url
Bei Textquellen für title und value:
mode: "static": zeigt den Wert unverändert anmode: "template": ersetzt Platzhalter wie{{json.path}}mode: "table": ersetzt ebenfalls{{json.path}}und teilt danach per Zeilenumbruch in Rows und per|in Spaltenmode: "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: Wertebereichwidgets[].content.slider.valuePath: JSON-Pfad des aktuellen Statuswertswidgets[].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-Dialogwidgets[].content.select.valuePath: JSON-Pfad des aktuell gesetzten Wertswidgets[].content.select.optionsPath: JSON-Pfad auf das Options-Array im Statuswidgets[].content.select.optionValuePath: Pfad innerhalb eines Optionsobjekts für den zu sendenden Wertwidgets[].content.select.optionLabelPath: Pfad innerhalb eines Optionsobjekts für den sichtbaren Textwidgets[].content.select.action.request: Endpoint für die bestätigte Auswahlwidgets[].content.select.action.valueBindings: wohin der ausgewählte Wert im Request geschrieben wirdwidgets[].content.select.action.prompt: optionaler Bestätigungsdialog vor dem Senden
Bei Rollladen-Widgets:
widgets[].content.mode: "shutter": aktiviert die Rollladensteuerungwidgets[].content.shutter.valuePath: JSON-Pfad der aktuellen Positionwidgets[].content.shutter.sliderAction.request: optionaler Endpoint für die Zielpositionwidgets[].content.shutter.sliderAction.valueBindings: wohin der eingestellte Sliderwert im Request geschrieben wirdwidgets[].content.shutter.sliderAction.prompt: optionaler Bestätigungsdialog vor dem Senden der Zielpositionwidgets[].content.shutter.upAction.request: Endpoint für Hochwidgets[].content.shutter.stopAction.request: optionaler Endpoint für Stopwidgets[].content.shutter.downAction.request: Endpoint für Runterwidgets[].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 auswidgets[].action.type: "switch_tab": wechselt auf den intargetTabIdhinterlegten Reiterwidgets[].action.type: "open_url": öffnet die inurlhinterlegte Adresse im Browserwidgets[].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:
AutoLightDarkCustom
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[].ordereinsortiert. - Freie Widgets im selben Tab werden über
widgets[].layout.ordereinsortiert. widthColumnsist die empfohlene Breitenangabe;maxWidthDpbleibt nur für ältere Konfigurationen relevant.highlightColorfä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: trueverdichtet die Gruppe im Dashboard auf eine1/1Link-Kachel; der Popup-Inhalt nutzt dieselbe Widgetgrösse wie das Haupt-Grid und maximal fünf Spalten.- wenn bei einer expandierbaren Gruppe
widthColumnsgesetzt ist, begrenzt dieser Wert zusätzlich die Popup-Breite valueWidgetIdbleibt für einen einzelnen gespiegelten Wert nutzbar; mitvaluePatternlassen 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 erlaubthttp://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
- Android Studio Ladybug oder neuer / aktuelles Android Studio öffnen
- Android SDK für
compileSdk 36installieren - 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.htmldirekt 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 einfachevalue.mode: "table"-Eingabe mit Preview