feat(station): Abschlussbild beim Herunterfahren und beim Neustart

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.
This commit is contained in:
Michael Sauer
2026-09-15 03:02:21 +02:00
parent 300edafc3e
commit 4e1a965cbb
12 changed files with 572 additions and 4 deletions
+58 -2
View File
@@ -674,7 +674,11 @@ migrate_on() {
# an den Python-Prozess durch. "sudo", weil /dev/spidev0.0 und die GPIO-Leitungen root
# gehoeren (der Anzeigedienst laeuft aus demselben Grund als root).
display_run() {
sudo env EPD_REVISION="$EPD_REVISION" python3 "$STATION_LIB/display/display.py" "$1"
# "$@", nicht "$1": das Abschlussbild wird mit ZWEI Argumenten gerufen
# ("--final poweroff"), und ein verschlucktes zweites waere hier besonders teuer -
# argparse erzwingt bei --final eine Auswahl, der Aufruf scheiterte also, und auf dem
# Panel bliebe die letzte Rueckfrage stehen.
sudo env EPD_REVISION="$EPD_REVISION" python3 "$STATION_LIB/display/display.py" "$@"
}
# Handbetrieb am Panel ("display test"/"display clear"), waehrend der Anzeigedienst
@@ -779,6 +783,44 @@ display_off() {
display_run --clear || echo "Warnung: Panel konnte nicht geleert werden - Anzeige pruefen" >&2
}
# --- Abschlussbildschirm beim Herunterfahren und beim Neustart ----------------------
#
# 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, und der ausgeschaltete Pi sah aus, als warte
# er noch auf eine Eingabe.
#
# Dieser Handgriff gehoert deshalb ins System (ninlight-poweroff.service /
# ninlight-reboot.service), nicht in den Tasterdienst: so greift er bei JEDEM
# Herunterfahren - ueber das Menue, ueber "poweroff" per SSH, ueber "reboot" - und nicht
# nur auf einem von drei Wegen.
#
# Wie lange auf die Freigabe der GPIO-/SPI-Leitungen gewartet wird. Die Units ordnen sich
# per "After=ninlight-display.service" HINTER dessen Stop ein; systemd erledigt das also
# schon. Die Wartezeit ist das Netz darunter (und der Weg fuer einen Handaufruf zum
# Ausprobieren): zwei Prozesse auf denselben Leitungen enden in "lgpio.error: 'GPIO busy'",
# und das ist in diesem Projekt schon zweimal passiert.
DISPLAY_RELEASE_WAIT_S=${NINLIGHT_DISPLAY_RELEASE_WAIT_S:-10}
# $1 = poweroff|reboot. Beendet IMMER mit 0: ein Fehler an der Anzeige darf das
# Herunterfahren nie aufhalten (dieselbe Zusage wie ueberall sonst).
display_final() {
_waited=0
while systemctl is-active --quiet ninlight-display 2>/dev/null; do
if [ "$_waited" -ge "$DISPLAY_RELEASE_WAIT_S" ]; then
# Bewusst KEIN "systemctl stop" von hier aus: das liefe mitten in der
# Abschalt-Transaktion und koennte sich mit ihr verhaken. Lieber eine ehrliche
# Zeile im Journal als ein haengendes Herunterfahren.
echo "Warnung: Anzeigedienst laeuft nach ${DISPLAY_RELEASE_WAIT_S}s noch - kein Abschlussbild (GPIO belegt)" >&2
return 0
fi
sleep 1
_waited=$((_waited + 1))
done
display_run --final "$1" || echo "Warnung: Abschlussbild konnte nicht gezeichnet werden - Anzeige pruefen" >&2
return 0
}
full_off() {
# ZUERST: ein noch vorgemerkter Zeitschalter muss mit weg, sonst feuert er spaeter in
# eine Station hinein, die laengst aus ist - oder, schlimmer, in eine, die inzwischen
@@ -1028,6 +1070,12 @@ display_cmd() {
test) display_handover --test ;;
clear) display_handover --clear ;;
off) display_off ;;
final)
case "${2:-}" in
poweroff|reboot) display_final "$2" ;;
*) echo "Fehler: 'display final' braucht 'poweroff' oder 'reboot'" >&2; exit 2 ;;
esac
;;
*) usage; exit 2 ;;
esac
}
@@ -1061,6 +1109,14 @@ usage() {
echo " Ein laufender Anzeigedienst wird dafuer kurz gestoppt und" >&2
echo " danach wieder gestartet (er haelt sonst die GPIO-Leitungen)." >&2
echo " display clear Panel leeren (ebenfalls mit kurzer Unterbrechung des Dienstes)." >&2
echo " display final {poweroff|reboot}" >&2
echo " Abschlussbild aufs Panel. Kein Handgriff fuer Menschen: das" >&2
echo " rufen ninlight-poweroff.service/ninlight-reboot.service beim" >&2
echo " Herunterfahren bzw. Neustart auf, nachdem der Anzeigedienst" >&2
echo " die GPIO-Leitungen freigegeben hat. E-Paper behaelt sein Bild" >&2
echo " dauerhaft - ohne diesen Schritt bliebe die letzte Rueckfrage" >&2
echo " auf dem Panel stehen. Die Texte unterscheiden sich: beim" >&2
echo " Neustart darf man den Stecker gerade NICHT ziehen." >&2
echo " display off Anzeigedienst dauerhaft stoppen und das Panel leeren. 'on'/'off'" >&2
echo " der Station brauchen das nicht - der Anzeigedienst laeuft" >&2
echo " unabhaengig davon durch und zeichnet den Zustand selbst." >&2
@@ -1114,7 +1170,7 @@ case "$_cmd" in
autooff_fire
;;
display)
display_cmd "${1:-}"
display_cmd "${1:-}" "${2:-}"
;;
off)
full_off