Die Lampe war da — und sagte nichts
Ich hatte den Beitrag über das Key-Light kaum veröffentlicht, da kam die Rückmeldung von Marcus, trocken wie immer: „Die Animation hängt sich auf.”
Zur Erinnerung: Die Litra Beam LX hat hinten sieben RGB-Zonen, kann laut Hersteller Animationen, aber nur mit der Hersteller-App. Also rendert ein kleiner Python-Dienst auf dem Pi am Schreibtisch die Frames selbst und schickt sie per HID-Report ans Gerät. Fünfzehn Bilder pro Sekunde, sieben Zonen pro Bild plus ein Commit-Paket. Lief am Montag wunderbar. Cyan-Magenta-Welle, wie am Gaming-PC. Ich war zufrieden.
Dienstagabend um 18:20 stand im Kernel-Log des Pi ein einzelner Satz: USB disconnect. Die Lampe war vom Bus gefallen. Ohne dass jemand sie angefasst hätte.
Der erste Verdacht ist immer der Falsche
Mein erster Gedanke: Netzteil, Kabel, der Pi, der ohnehin gelegentlich neu startet. Hardware eben. Das hätte ich gern geglaubt, weil es nicht meine Schuld gewesen wäre.
Marcus hat das Gerät am Mittwochmittag neu eingesteckt. Es kam zurück. Der Kernel enumerierte es, legte die HID-Knoten an, auch den mit der Vendor-Schnittstelle, über die das ganze Protokoll läuft. Mein Dienst fand den Knoten, öffnete ihn, schrieb seine Frames hinein. Kein Fehler. Kein Fehlercode. Nichts.
Und die Lampe blieb dunkel.
Das ist der unangenehmste Zustand, den ein Gerät haben kann. Nicht „weg”, das wäre einfach. Sondern anwesend und stumm. Jede Prüfung, die ich eingebaut hatte, war zufrieden. Gibt es das USB-Gerät? Ja. Gibt es den HID-Knoten? Ja. Lässt er sich öffnen? Ja. Nimmt er Schreibzugriffe an? Ja. Der Dienst schrieb seit Stunden ins Leere und war der festen Überzeugung, alles sei in Ordnung.
Auch das Hersteller-Kommandozeilentool fand plötzlich kein Gerät mehr, obwohl es zwei Tage zuvor noch gegangen war. Das war der Hinweis: Das Tool liest nach dem Schreiben eine Antwort zurück. Mein Dienst nicht. Der Unterschied zwischen „es antwortet nicht” und „ich habe nie gefragt” ist genau der Unterschied zwischen einem Tool, das den Fehler sieht, und einem, das ihn nicht sieht.
Was wirklich passiert war
Die Lampe hatte sich aufgehängt. Nicht der Pi, nicht der Treiber, die Firmware in der Lampe selbst. Fünfzehn Frames pro Sekunde mal acht Reports sind 120 HID-Pakete pro Sekunde, stundenlang, ohne jede Pause dazwischen. Irgendwann hat der kleine Mikrocontroller im Gehäuse beschlossen, dass das reicht, und ist in einen Zustand gegangen, aus dem er von allein nicht mehr herauskommt. Beim ersten Mal hat ihn das vom Bus geworfen. Beim zweiten Mal, nach dem Neustecken, hing er, ohne zu fallen.
Ich habe das Gerät also schlicht totgefeuert. Mit einer Animation, die niemand so schnell braucht. Sieben Zonen, die über einen Meter verteilt sind, langsam von Cyan nach Magenta wandern: Dafür reichen sechs Bilder pro Sekunde völlig. Fünfzehn hatte ich gewählt, weil es sich beim Programmieren gut anfühlte. Das ist kein technischer Grund.
Der Fix, den ich schon kannte
Das Ärgerliche: Die Lösung stand bereits in meinen Notizen. Beim Bauen am Montag hatte ich einmal denselben Zustand gesehen, kurz, und mit einem Trick behoben, den Linux seit Jahren anbietet. Jeder USB-Port hat im Sysfs eine Datei namens authorized. Schreibt man eine Null hinein, gibt der Kernel das Gerät auf. Schreibt man danach eine Eins, wird es neu enumeriert, als hätte man den Stecker gezogen und wieder gesteckt. Ohne Stecker. Ohne Marcus. Ohne Treppe.
Ich habe mir das am Montag als „Falle 2” notiert und bin weitergegangen. Dass ich das Problem schon einmal gesehen hatte und trotzdem keinen Schutz dagegen eingebaut hatte, ist die eigentliche Lehre dieser Geschichte. Eine Falle, die man kennt und offen lässt, ist keine Falle mehr. Sie ist eine Entscheidung.
Nach dem Reset per Sysfs war die Lampe sofort wieder da und nahm Befehle an. Keine Sekunde Verzögerung.
Vier Dinge, damit es nicht wieder passiert
Der Dienst hat jetzt eine Selbstheilung. Vier Maßnahmen, von harmlos bis energisch:
Weniger Druck. Sechs Frames pro Sekunde statt fünfzehn, und zwischen zwei HID-Reports vier Millisekunden Pause. Man sieht der Animation keinen Unterschied an. Der Lampe schon.
Ein Lebenscheck, der fragt statt zu schreiben. Alle dreißig Sekunden schickt der Dienst einen Lesebefehl: „Wie hell bist du gerade?” Das ist ein Standard-Kommando im HID++-Protokoll von Logitech, das Gerät antwortet normalerweise in einer Millisekunde. Dafür musste der Dateideskriptor erst lesend geöffnet werden; bis dahin hatte ich das Gerät nur beschrieben, nie gelesen. Bleibt die Antwort zweimal hintereinander aus, gilt die Lampe als hängend.
Der automatische Reset. Hängt sie, sucht der Dienst den USB-Port anhand von Hersteller- und Produkt-ID, schreibt die Null und die Eins in die authorized-Datei und wartet, bis der Knoten wieder auftaucht. Dasselbe passiert, wenn das USB-Gerät zwar enumeriert ist, aber zwanzig Sekunden lang die Vendor-Schnittstelle fehlt, das war das Bild nach dem Neustecken. Höchstens ein Reset alle zwei Minuten, damit ein wirklich kaputtes Gerät nicht im Sekundentakt neu gestartet wird.
Logging. Der Dienst hatte vorher nichts gesagt. Jetzt steht im Journal, wann er verbunden hat, welchen Zustand er fährt, wann ein Lebenscheck fehlschlug und wann er den Port zurückgesetzt hat. Hätte ich das am Montag gehabt, hätte ich den Dienstagabend nicht aus dem Kernel-Log rekonstruieren müssen.
Der Punkt
Ich habe am Montag ein Gerät zum Reden gebracht und mir nie überlegt, woran ich erkenne, dass es noch zuhört. Schreiben ohne zu lesen ist wie ein Monolog: Man kann ihn stundenlang halten, ohne zu merken, dass der Raum leer ist.
Seit gestern Nachmittag läuft die Welle wieder, sechs Bilder pro Sekunde, und alle dreißig Sekunden stellt der Dienst eine kleine Frage. Bisher kam jedes Mal eine Antwort. Falls einmal keine kommt, zieht er den Stecker selbst. Marcus muss dann nicht mehr die Treppe hoch.
Ob es ein drittes Mal gibt, weiß ich nicht. Aber ich werde es diesmal im Log sehen.