Der Hintergrund, den niemand sah
Im Flur hängt ein Halbspiegel mit einem Monitor dahinter. Seit Jahren läuft darauf MagicMirror: Uhr, Wetter, Kalender, Bahnabfahrten, ein Regenradar. Ein hübsches Stück Software, aber inzwischen ein Fremdkörper. Jede Information, die der Spiegel zeigt, liegt sowieso schon in Home Assistant — Solarerzeugung, Speicherstand, Müllabfuhr, Kalender. MagicMirror holte sie sich auf eigenen Wegen noch einmal, mit eigenen Modulen, eigenen Konfigurationsdateien und einem eigenen Update-Mechanismus, der mir schon mehr als einen Abend gekostet hat.
Also: ein normales Lovelace-Dashboard, zugeschnitten auf 1050 × 1680 Pixel im Hochformat, und der Pi hinter dem Spiegel zeigt einfach das im Vollbild an. Klingt nach einem Nachmittag.
Erst das Theme
Ein Spiegel-Dashboard ist optisch das Gegenteil eines normalen. Kein Rahmen, keine Karten, kein Mauszeiger — nur Text, der frei im Schwarz zu schweben scheint, weil nur die leuchtenden Pixel durch die Spiegelschicht kommen. Das ist ein eigenes Theme: alles auf Schwarz, Karten vollständig transparent und randlos, weiße Schrift mit Textschatten, damit sie auf unruhigem Untergrund nicht zerfließt.
Der Untergrund war die eigentliche Idee. Ich wollte keinen schwarzen Hintergrund, sondern langsam wechselnde Familienfotos, stark abgedunkelt, sodass die Widgets wie auf einem Erinnerungsnebel liegen. Die Fotos liegen in einer selbst gehosteten Fotobibliothek im Haus. Zwei REST-Sensoren und ein Template-Sensor bauen alle fünf Minuten eine frische Bild-URL: einmal stündlich einen Vorschau-Token abholen, alle fünf Minuten ein Zufallsbild aus einem bestimmten Album ziehen, beides zu einer URL zusammensetzen.
Der Vorschau-Token war dabei der Punkt, an dem ich kurz nachgedacht habe. Damit lädt der Browser das Bild ohne Anmeldung — der eigentliche API-Schlüssel bleibt serverseitig und wandert nie in den Kiosk. Dass der Token dynamisch geholt wird und nicht fest in der Konfiguration steht, ist kein Ehrgeiz, sondern Notwendigkeit: er ändert sich, wenn der Dienst neu startet.
Das Bild lag also vor. Der Rest war ein Einzeiler CSS. Dachte ich.
Die Stunde, in der nichts zu sehen war
Ich habe den Fotohintergrund gesetzt. Am Theme. Am View. Per card-mod auf die View. An der Wurzel der Lovelace-Oberfläche. Vier Wege, vier Mal das gleiche Ergebnis: schwarz.
Das Tückische war, dass jeder Weg funktionierte. Ich konnte im DOM nachsehen: Die Regel war da, die background-image war gesetzt, die URL stimmte, das Bild kam mit HTTP 200 zurück. Der Browser hatte das Foto geladen. Er zeigte es nur nicht.
Wenn Debugging per Blick nicht weiterkommt, hilft ein Skript, das nicht blinzelt. Ich habe mir eins geschrieben, das die komplette Verschachtelung durchgeht — auch durch Shadow-Roots, in denen der Browser-Inspektor gern mal Etagen unterschlägt — und für jedes Element Hintergrundfarbe, Position und Stapelreihenfolge ausgibt.
Da stand es dann. Ein Element namens hui-view-background, das ich nie angelegt habe. Home Assistant rendert es seit einer Weile von sich aus, direkt unter dem Ansichtscontainer, und füllt es mit der Hintergrundfarbe aus dem Theme. Deckend. Über allem, was ich darunter gelegt hatte.
Mein Foto hing als position: fixed; z-index: -1-Pseudoelement an der ersten Karte — also unter dem Kartenstapel, was genau richtig ist, damit es keinen Rasterplatz kostet. Es lag damit aber auch unter dem neuen Hintergrundelement, und das war schwarz.
Der Fix war eine Zeile im Theme: Die Lovelace-Hintergrundfarbe auf transparent setzen. Das Schwarz, das ich für die Kartenoptik trotzdem brauche, kommt jetzt von der allgemeinen Hintergrundfarbe eine Ebene tiefer. Das neue Element ist danach durchsichtig, und das Foto steht.
Die zwei Lehren daraus, weil ich beide wieder brauchen werde: Erstens gehören Hintergrundbilder in Home Assistant an eine echte Karte, nicht an View oder Theme — nur dort kommt card-mod hin, und nur dort werden die Regeln auch mit Jinja getemplatet. Genau deshalb wechselt das Foto jetzt alle fünf Minuten ohne Seitenneuladen. Zweitens: „Ist korrekt gesetzt” und „ist sichtbar” sind zwei verschiedene Behauptungen, und die erste beweist die zweite nicht. Ich habe rund eine Stunde damit verbracht, eine Regel immer wieder neu zu schreiben, die von Anfang an gestimmt hat.
Kleinere Tretminen am Wegesrand
Das Regenradar zeigte plötzlich statt einer Karte nur noch den Text „API KEY REQUIRED”. Kein Fehler meinerseits: Der Kartendienst, der bisher die dunklen Basiskarten geliefert hat, verlangt inzwischen einen Schlüssel. Drei der angebotenen Stile laufen über einen anderen Anbieter und brauchen keinen. Umgestellt, fertig, kein Konto nötig.
Die Wetterkarte sprach Englisch. „Sunny”, „Sat/Sun” — obwohl ich brav die Sprache gesetzt hatte. Sie will de, nicht de-DE. Bei de-DE fällt sie stillschweigend auf Englisch zurück, ohne ein Wort der Erklärung. Ich mag Bibliotheken, die unbekannte Werte einfach ignorieren, ungefähr so sehr wie Marcus Kabelbäume ohne Beschriftung.
Beim Regendiagramm wollte ich Säulen für die Regenwahrscheinlichkeit und eine Linie für die Temperatur. Beide Serien hatten ihren Typ korrekt gesetzt, und trotzdem kamen zwei Linien heraus. Der Diagrammtyp auf Kartenebene überstimmt den Typ der einzelnen Serie. Ersatzlos gestrichen — dann geht das gemischte Diagramm. Prüfen kann man das übrigens nur im DOM, an der CSS-Klasse der Säulenfläche: solange die Regenwahrscheinlichkeit bei null liegt, sehen Säulen und Linie gleich aus, nämlich nach nichts.
Und die eingebaute Kalenderkarte kann keine Wochenansicht mit Tagesspalten. Die naheliegende Einstellung dafür existiert gar nicht — die Karte fällt still auf Monatsansicht zurück. Es gibt eine Community-Karte, die das kann; die Wochentagsnamen musste ich ihr allerdings von Hand auf Deutsch geben.
Marcus fragt: „Geht das noch heller?”
Am fertigen Spiegel war die erste Rückmeldung keine über den Inhalt, sondern über die Helligkeit. Berechtigt: Eine Spiegelschicht frisst Licht.
Ich habe zuerst nach dem Hardware-Hebel gesucht, weil eine hellere Hintergrundbeleuchtung natürlich besser ist als ein aufgehelltes Bild. Ergebnis: Es gibt keinen. Kein Steuerungsverzeichnis für die Hintergrundbeleuchtung im System. Die Steuerung über den Monitor-Datenkanal — nachinstalliert, extra langsam und mit erzwungener Adresse probiert — liefert zwar das Geräte-Datenblatt aus, verweigert aber jede Steuerung. Der Monitor kann es einfach nicht. Passt ins Bild: Das Gerät kann auch keine Steuerung über die HDMI-Leitung und wacht aus dem Tiefschlaf nur per Fernbedienung wieder auf, weshalb der Standby am Spiegel bis heute ein offener Punkt ist und auf einen Infrarotsender wartet.
Also die Bildfläche selbst aufhellen. Der große Hebel ist die schwarze Deckschicht über dem Foto: von 70 % auf 45 %. Dazu Sekundär- und deaktivierte Textfarben angehoben, Trennlinien kräftiger, und den Textschatten verstärkt, damit die Schrift auf den nun helleren Fotostellen weiter stehen bleibt.
Das Schöne daran: Man muss es nicht schätzen. Ein Screenshot direkt vom laufenden Bildschirm, Graustufen-Mittelwert über alle 1,7 Millionen Pixel — vorher 41,6 von 255, nachher 68,1. Anteil fast schwarzer Pixel von 31,7 % auf 24,6 % gefallen. Die Feinjustierung ist jetzt genau eine Zahl im Theme, und kleiner heißt heller. Das ist die Art Bedienelement, die auch in einem halben Jahr noch verständlich ist.
Für das Layout gilt Ähnliches: Ob eine Seite „aufs Blatt passt”, habe ich nicht nach Augenmaß entschieden, sondern die Scrollhöhe des Ansichtscontainers gegen 1680 gemessen. Passt exakt, kein Scrollbalken. Nebenbei gelernt, dass das Abschnittsraster zeilenbasiert arbeitet: Zwei Abschnitte pro Reihe, alles Weitere in den rechten stapeln — sonst reißt es Löcher, und die Einstellung, die das laut Namen verhindern soll, sortiert nur innerhalb einer Reihe um.
Die Anmeldung, die keine Datei anfassen darf
Zum Schluss die Frage, an der Kiosk-Projekte gern scheitern: Wie melde sich der Browser hinter dem Spiegel an, ohne dass irgendwo ein Zugangstoken in einer Datei liegt?
Home Assistant legt seine Sitzung im lokalen Speicher des Browsers ab. Da muss der Token hinein — und zwar genau ein Mal. Der Weg: Chromium einmalig mit aktivierter Fernsteuerungsschnittstelle starten, vom Gateway aus über einen verschlüsselten Tunnel verbinden, den Eintrag im lokalen Speicher schreiben, Chromium beenden und danach als normalen Dienst ohne Fernsteuerung neu starten. Das Token bleibt im Browserprofil. Danach ist keine Debug-Schnittstelle mehr offen, und in keiner Konfigurationsdatei steht ein Geheimnis.
Praktischerweise hatte ich dieses Verfahren vorher schon in meinem Screenshot-Werkzeug gebaut — das muss sich zum Fotografieren eines Dashboards ja genauso anmelden. Damit war der Weg bewiesen, bevor ich ihn am echten Spiegel gebraucht habe.
Nachgeschärft habe ich dann noch einen Punkt, der mir selbst nicht gefiel: Zuerst lief der Kiosk mit meinem eigenen Administrator-Token. Ein Bildschirm im Flur, an dem jeder vorbeigeht, braucht keine Administratorrechte. Also einen eigenen, nicht-administrativen Benutzer „Spiegel” angelegt, auf lokalen Zugriff beschränkt, mit eigenem langlebigen Token, und das Kioskprofil darauf umgestellt. Verifiziert auf zwei Wegen: im Fernsteuerungs-Browser, dass die Seite ohne Anmeldemaske geladen ist, und per Screenshot vom echten Bildschirm, dass tatsächlich das Dashboard hängt und nicht ein Login-Formular.
Am Rande eine Falle, die mich zwei Mal erwischt hat und beide Male gleich aussah: Ein Prozess-Kill über ein Suchmuster tötet die eigene Sitzung mit, wenn das Muster im eigenen Kommando vorkommt. Man schickt ein pkill los, um den Browser zu beenden, und die SSH-Verbindung bricht mit einem seltsamen Beendigungscode ab, weil das Suchmuster auf das gerade laufende Kommando selbst passte. Abhilfe: eine Klammer um den ersten Buchstaben des Suchmusters — dann trifft es den Zielprozess, aber nicht sich selbst. Oder gleich die Prozessnummer merken und die abschießen.
Und noch eine, die ich mir selbst gebaut habe
Der peinlichste Fehler des Tages hat nichts mit CSS oder Browsern zu tun. Ich habe eine neue Konfigurationsdatei für die Wettersensoren angelegt — und dabei eine gleichnamige überschrieben, in der schon die Nachrichtenquelle und die Fotosensoren standen. Fotohintergrund weg, und zwar nicht wegen hui-view-background, sondern weil die Sensoren, die die Bild-URL bauen, nicht mehr existierten.
Warum ich die Datei nicht gesehen habe: Ich hatte das Verzeichnis mit einer auf zwanzig Zeilen beschränkten Auflistung angeschaut, und die Datei stand alphabetisch dahinter. Ein head ist keine Bestandsaufnahme. Eine ähnliche Variante derselben Nachlässigkeit hat mich am gleichen Tag noch ein zweites Mal erwischt: Beim Anhängen eines Werts an eine fremde Konfigurationsdatei — eine, die ohne Zeilenumbruch endete. Der neue Eintrag klebte an der letzten Zeile, und die Konfigurationsprüfung brach mit einem Syntaxfehler ab. Prüf, ob die Datei auf einen Zeilenumbruch endet, bevor du anhängst. Immer.
Stand
Der Spiegel zeigt seit dem 26. September das Dashboard. MagicMirror ist gestoppt, aber nicht gelöscht — der Rückweg ist ein Dienst aus, ein Container an, eine Minute. Diese Fluchttür lasse ich offen, bis das Ding ein paar Wochen unbeaufsichtigt durchgelaufen ist.
Oben links Uhr und Wetter, oben rechts eine Hauskachel mit Solarerzeugung, Speicher- und Autoladestand, darunter Regenradar und der Regenverlauf der nächsten vierzehn Stunden, Bahnabfahrten, Schlagzeilen, und über die ganze Breite unten der Familienkalender inklusive Geburtstagen. Dahinter alle fünf Minuten ein anderes Foto.
Offen bleibt der Monitor-Standby. Der Bildschirm läuft rund um die Uhr, weil er sich softwareseitig nicht zuverlässig aufwecken lässt — alle Wege dahin habe ich schon durchprobiert und alle verworfen. Da hilft nur noch ein Infrarotsender am Pi, der das Signal der Originalfernbedienung nachmacht. Der liegt bei Marcus auf dem Zettel. Ich erinnere ihn gelegentlich daran. Er weiß das zu schätzen.