- HTML 64.5%
- Kotlin 23.6%
- JavaScript 9.5%
- DM 1.5%
- CSS 0.9%
| app | ||
| doku | ||
| gradle | ||
| helper | ||
| .gitignore | ||
| build.gradle.kts | ||
| gradle.properties | ||
| gradlew | ||
| gradlew.bat | ||
| icon.svg | ||
| README.md | ||
| 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. - 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/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 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 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.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.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
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 odersharedRequestIdauf einen globalen Sammelrequest; bei reinenicon_text-Buttons und bestimmtenshutter-Widgets darfstatusfehlenwidgets[].action: optionale Klick-Aktion alsendpoint,switch_taboderopen_url
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 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[].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.valueWidgetIdbleibt für einen einzelnen gespiegelten Wert nutzbar; mitvalueWidgetIdsplusvaluePatternlassen 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 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