E-Paper behaelt sein Bild dauerhaft, auch stromlos. Faehrt der Pi herunter, wird der Anzeigedienst mit abgeraeumt und das zuletzt gezeichnete Bild bleibt fuer immer stehen - bei der Abnahme stand dort die Rueckfrage des Tastermenues, und der ausgeschaltete Pi sah aus, als warte er noch auf eine Eingabe. Zwei neue Units, ninlight-poweroff.service (WantedBy=poweroff.target) und ninlight-reboot.service (WantedBy=reboot.target). Sie haengen am System, nicht am Tasterdienst: so greifen sie bei jedem Herunterfahren - Menue, "poweroff" per SSH, "reboot" - und nicht nur auf einem von drei Wegen. Zwei Units statt einer Fallunterscheidung, weil systemd einem ExecStart nicht sagt, wohin die Reise geht. Die Texte unterscheiden sich: "AUSGESCHALTET / Strom kann getrennt werden" gegen "NEUSTART / Strom NICHT trennen!" samt Hinweis auf die rund zwei Minuten, die der Zero W bis zum Anzeigedienst braucht. An diesem Pi hat ein Stromzug in genau dieser Wartezeit schon eine SD-Karte gekostet. Reihenfolge: die Units ordnen sich per After=ninlight-display.service hinter dessen Stop ein, und "ninlight-station display final <kind>" wartet zusaetzlich, bis systemctl is-active nicht mehr anschlaegt - dasselbe Muster wie display_handover(), nur ohne Wiederanfahren. Gibt der Dienst die Leitungen nicht frei, wird nicht gezeichnet (GPIO busy), sondern gewarnt: ein Anzeigefehler haelt das Herunterfahren nie auf. Nebenbei behoben: display_run() reichte nur "$1" an display.py weiter und haette "--final poweroff" halbiert.
155 lines
8.9 KiB
Markdown
155 lines
8.9 KiB
Markdown
# ninlight-Recovery-Station
|
|
|
|
Ein Raspberry Pi spannt den Zugangspunkt `ninlight-recovery` auf und liefert unter
|
|
`http://192.168.8.1/ninlight_<target>_ota.img` Firmware-Images aus.
|
|
|
|
Jede ninlight-Lampe sucht beim Booten bis zu drei Sekunden nach dieser Station. Findet sie
|
|
sie, lädt sie das Image für ihren Gerätetyp und flasht es — ohne dass die Lampe im WLAN
|
|
erreichbar sein muss. Das ist der Rettungsweg für Lampen, an die man sonst nur noch mit dem
|
|
Lötkolben käme.
|
|
|
|
Trägt das angebotene Image dieselbe Version, die bereits läuft, lehnt die Lampe es ab und
|
|
bootet normal weiter. Eine angelassene Station flasht also nicht in einer Endlosschleife.
|
|
|
|
## Voraussetzung
|
|
|
|
Der SSH-Benutzer auf dem Pi braucht passwortloses `sudo` — beide Skripte und die CLI
|
|
(`ninlight-station on|off|status`) rufen `sudo` nicht-interaktiv auf; ein Passwort-Prompt
|
|
würde dort nur mit einer Fehlermeldung enden.
|
|
|
|
## Bedienung
|
|
|
|
tools/station/setup_pi.sh <pi-host> einmalig einrichten
|
|
tools/station/deploy.sh <pi-host> [target] Images ausliefern
|
|
ssh <pi-host> ninlight-station on einschalten
|
|
ssh <pi-host> ninlight-station status Zustand und letzte Auslieferungen
|
|
ssh <pi-host> ninlight-station bildsatz welcher Migrations-Bildsatz gilt
|
|
ssh <pi-host> ninlight-station off ausschalten
|
|
|
|
## Bildsatz: mit oder ohne WLAN-Zugangsdaten
|
|
|
|
Die WLAN-Zugangsdaten stecken **zur Bauzeit** in der Firmware (`config.local.cmake`). Es
|
|
gibt deshalb zwei Abzüge je Ziel, und die Station hält beide vor:
|
|
|
|
| Umschalterstellung | Verzeichnis auf dem Pi | gebaut mit | die migrierte Lampe … |
|
|
|---|---|---|---|
|
|
| `ohne-wlan` (**Vorgabe**) | `images/migrate-nocreds/` | `tools/build_nocreds.sh` | startet im eigenen Onboarding-Zugangspunkt und wartet auf ein WLAN |
|
|
| `mit-wlan` | `images/migrate-wlan/` | `build/` | hängt sofort im WLAN aus `config.local.cmake` |
|
|
|
|
Umgeschaltet wird per `ninlight-station bildsatz ohne-wlan|mit-wlan` oder am Taster
|
|
(Menüeintrag „Umschalten: …"). Das ist ein **Umschalter, keine Abfrage je Migration**: der
|
|
Zustand steht in `/var/lib/ninlight-station/imageset`, überlebt Neustarts und bleibt, bis
|
|
ihn jemand ändert. Genau deshalb steht er auch **dauerhaft in der Fußzeile des Panels** —
|
|
in jedem Modus, auch bei ausgeschalteter Station: man soll vor jedem Einschalten sehen,
|
|
was gilt, statt zwanzig Lampen mit den eigenen Zugangsdaten zu flashen, weil die
|
|
Einstellung von vorletzter Woche noch stand.
|
|
|
|
**Warum `ohne-wlan` die Vorgabe ist** (und warum jeder Zweifelsfall — Datei fehlt,
|
|
unlesbar, unbekannter Inhalt — dorthin führt): im Automatikmodus wird jede alte
|
|
Klyqa-Lampe in Funkreichweite migriert, auch fremde. Mit dem Abzug ohne Zugangsdaten ist
|
|
ein Fehlgriff ärgerlich, aber harmlos — die Lampe steht im Onboarding-AP und wartet. Mit
|
|
dem Abzug mit Zugangsdaten steht das eigene WLAN-Passwort unwiderruflich in einem fremden
|
|
Gerät. Der teurere Fehler darf nie der sein, der ohne Zutun passiert.
|
|
|
|
**Fehlt der gewählte Satz** (nie ausgeliefert, oder `tools/build_nocreds.sh` nie gelaufen),
|
|
wird **nichts** ausgeliefert — kein stilles Ausweichen auf den anderen Satz. Der Server
|
|
antwortet mit 404 und `status: noimageset`, `ninlight-station status` sagt „FEHLT", und auf
|
|
dem Panel steht „SATZ FEHLT". „Vorhanden" heißt dabei überall dasselbe: Verzeichnis da
|
|
**und** mindestens ein Image darin.
|
|
|
|
`deploy.sh` liefert beide Sätze aus. Fehlt `build-nocreds/`, bleibt der Satz `ohne-wlan`
|
|
auf dem Pi unangetastet (statt geleert zu werden) und der Lauf meldet, womit er entsteht.
|
|
|
|
## Taster am Gerät
|
|
|
|
Sobald die Station läuft, wird `wlan0` zum Zugangspunkt und **jede SSH-Sitzung reißt ab**.
|
|
Für genau diesen Zustand gibt es einen Taster auf **GPIO23**; die Rückmeldung kommt aufs
|
|
E-Paper. Ein Taster, zwei Zusammenhänge:
|
|
|
|
| | kurz (unter 1 s) | gehalten |
|
|
|----------------------|------------------|-------------------------------------------------------|
|
|
| Ruhe (Statusbild) | Menü öffnen | 3 s → Abbruch (Station aus), weiter 10 s → Herunterfahren |
|
|
| Menü offen | nächster Eintrag | 1,5 s → Eintrag auswählen |
|
|
| Rückfrage | abbrechen | 1,5 s → bestätigen |
|
|
|
|
Der Abbruch feuert **bei 3 s**, nicht erst beim Loslassen — danach läuft auf dem Panel ein
|
|
Countdown bis zum Herunterfahren, den Loslassen jederzeit abbricht. Das Menü schließt nach
|
|
20 s ohne Eingabe, eine Rückfrage verfällt nach 10 s als Abbruch. Die Einträge hängen vom
|
|
Zustand ab (Station aus / Rettung läuft / Migration läuft); jede verändernde Auswahl
|
|
bekommt eine Rückfrage.
|
|
|
|
**Die beiden Migrationsarten sind getrennte Einträge.** „Migration: horchen"
|
|
(`migrate on --listen`) steht zuerst und ist der naheliegende: der Zugangspunkt steht,
|
|
aber es geht nichts raus, bis du eine erkannte Lampe freigibst. „Migration: AUTOMATIK"
|
|
(`migrate on`) folgt danach und bekommt eine **andere, deutlichere Rückfrage** — der
|
|
Migrations-Zugangspunkt hat ein öffentlich bekanntes Passwort (es steht in der alten
|
|
Klyqa-Firmware), und im Automatikmodus wird jede alte Klyqa-Lampe in Funkreichweite
|
|
ungefragt migriert, auch fremde. Die Rückfrage benennt genau das, statt nur „Wirklich?"
|
|
zu fragen.
|
|
|
|
Der Taster ist nirgends Voraussetzung: fehlt `gpiozero` oder der Taster, protokolliert der
|
|
Dienst eine Zeile und beendet sich ohne Fehler. Er fasst den SPI-Bus **nicht** an (zwei
|
|
Prozesse auf denselben Leitungen ergeben `GPIO busy`), sondern schreibt seinen
|
|
Bedienzustand nach `/run/ninlight-button/ui.json`; der Anzeigedienst liest ihn und
|
|
zeichnet. Ausgeführt wird ausschließlich über das CLI.
|
|
|
|
Dienst und Unit liegen in `tools/station/button/` bzw.
|
|
`tools/station/ninlight-button.service`. `setup_pi.sh` installiert, aktiviert und startet
|
|
den Tasterdienst wie den Anzeigedienst; `deploy.sh` liefert `button/` bei jedem Deploy mit
|
|
aus. Ein fehlgeschlagenes Aktivieren/Starten ist dabei nur eine Warnung — eine Station
|
|
ohne Taster muss vollständig benutzbar bleiben.
|
|
|
|
## Abschlussbildschirm beim Herunterfahren und beim Neustart
|
|
|
|
E-Paper behält sein Bild **dauerhaft**, auch stromlos. Fährt der Pi herunter, wird der
|
|
Anzeigedienst mit abgeräumt, und das zuletzt gezeichnete Bild bleibt für immer stehen —
|
|
bei der Abnahme stand dort die **Rückfrage** des Tastermenüs, und der ausgeschaltete Pi
|
|
sah aus, als warte er noch auf eine Eingabe.
|
|
|
|
Dafür gibt es zwei systemd-Units, `ninlight-poweroff.service` (`WantedBy=poweroff.target`)
|
|
und `ninlight-reboot.service` (`WantedBy=reboot.target`). Sie hängen am **System**, nicht
|
|
am Tasterdienst: so greifen sie bei jedem Herunterfahren — über das Menü, über `poweroff`
|
|
per SSH, über `reboot` — und nicht nur auf einem von drei Wegen.
|
|
|
|
**Die Texte unterscheiden sich, und das ist der Punkt:**
|
|
|
|
| | Panel |
|
|
|---|---|
|
|
| Herunterfahren | „AUSGESCHALTET — Strom kann getrennt werden" |
|
|
| Neustart | „NEUSTART — Strom NICHT trennen!" plus der Hinweis, dass es rund 2 min dauert |
|
|
|
|
Ein Zero W braucht gut anderthalb Minuten, bis der Anzeigedienst wieder zeichnet. Stünde
|
|
dort währenddessen „Strom kann getrennt werden", wäre das eine Aufforderung zu genau dem
|
|
Griff, der an diesem Pi schon eine SD-Karte gekostet hat.
|
|
|
|
**Reihenfolge:** der Anzeigedienst hält die GPIO-/SPI-Leitungen; zwei Prozesse darauf
|
|
ergeben `lgpio.error: 'GPIO busy'`. Die Units ordnen sich deshalb per
|
|
`After=ninlight-display.service` hinter dessen Stop ein, und `ninlight-station display
|
|
final <poweroff|reboot>` wartet zusätzlich, bis `systemctl is-active ninlight-display`
|
|
nicht mehr anschlägt — dasselbe Muster wie `display_handover()` bei `display test`, nur
|
|
ohne Wiederanfahren. Gibt der Dienst die Leitungen nicht frei, wird **nicht** gezeichnet,
|
|
sondern eine Zeile ins Journal geschrieben: ein Fehler an der Anzeige darf das
|
|
Herunterfahren nie aufhalten.
|
|
|
|
SSID und Passwort stehen in `/etc/ninlight-station.conf` auf dem Pi und müssen zu den
|
|
Build-Parametern `NINLIGHT_RECOVERY_SSID` und `NINLIGHT_RECOVERY_PASSWORD` aus
|
|
`config.local.cmake` passen. Weicht eines ab, findet die Lampe die Station nicht.
|
|
|
|
## Vertrauensgrenze
|
|
|
|
Der Trailer eines Images prüft Gerätetyp und SHA256. Er ist **keine** kryptografische
|
|
Signatur, und der Download läuft über `http://` ohne TLS. Die Station schützt allein ein
|
|
WPA2-Passwort, dessen Default im Repo steht. Wer in Funkreichweite ist und es kennt, kann
|
|
jeder Lampe beim nächsten Boot beliebige Firmware unterschieben.
|
|
|
|
Das ist gewollt: Die Signaturprüfung der alten Firmware auszuhebeln ist der Zweck dieses
|
|
Projekts. Daraus folgen zwei Regeln.
|
|
|
|
**Setz ein eigenes Passwort.** In `config.local.cmake` und in
|
|
`/etc/ninlight-station.conf` — an beiden Stellen denselben Wert.
|
|
|
|
**Lass die Station nur an, solange du sie brauchst.** Nicht nur wegen des Fremdzugriffs:
|
|
Die Firmware löscht den Power-Cycle-Zähler, bevor sie einen Recovery-Versuch startet.
|
|
Solange eine Station in Reichweite ist, funktioniert der Werksreset per fünf Stromzyklen
|
|
bei **keiner** Lampe in Reichweite mehr.
|