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:
@@ -560,6 +560,24 @@ def run_test(revision: str) -> None:
|
||||
log.info("Testbild gezeichnet (EPD_REVISION=%s)", revision)
|
||||
|
||||
|
||||
def run_final(revision: str, kind: str) -> None:
|
||||
"""Zeichnet den Abschlussbildschirm ("poweroff"/"reboot") und legt das Panel schlafen.
|
||||
|
||||
Das ist das Bild, das nach dem Herunterfahren DAUERHAFT stehen bleibt - E-Paper ist
|
||||
bistabil, es haelt sein Bild auch ohne Strom. Bewusst ein VOLLAUFBAU: ein Teilaufbau
|
||||
liesse Reste des vorigen Bildes stehen, und dieses eine Bild ueberdauert alle anderen.
|
||||
|
||||
Aufgerufen wird das ausschliesslich ueber "ninlight-station display final <kind>" aus
|
||||
einer systemd-Unit heraus, und zwar ERST, nachdem der Anzeigedienst gestoppt ist - er
|
||||
haelt sonst die GPIO-/SPI-Leitungen ("lgpio.error: 'GPIO busy'"). Diese Uebergabe
|
||||
macht das CLI (display_final()), nicht diese Funktion."""
|
||||
panel = Panel(revision)
|
||||
panel.clear()
|
||||
panel.draw_full(render.compose({"mode": "final", "final": kind}))
|
||||
panel.sleep()
|
||||
log.info("Abschlussbild gezeichnet (%s, EPD_REVISION=%s)", kind, revision)
|
||||
|
||||
|
||||
def run_clear(revision: str) -> None:
|
||||
panel = Panel(revision)
|
||||
panel.clear()
|
||||
@@ -634,6 +652,9 @@ def main() -> None:
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
group.add_argument("--test", action="store_true", help="Testbild zeichnen und beenden")
|
||||
group.add_argument("--clear", action="store_true", help="Panel leeren und beenden")
|
||||
group.add_argument("--final", choices=sorted(render.FINAL_LABELS),
|
||||
help="Abschlussbild zeichnen und beenden (beim Herunterfahren "
|
||||
"bzw. Neustart; die Texte unterscheiden sich)")
|
||||
args = parser.parse_args()
|
||||
|
||||
revision = os.environ.get("EPD_REVISION", "V3")
|
||||
@@ -643,6 +664,8 @@ def main() -> None:
|
||||
run_test(revision)
|
||||
elif args.clear:
|
||||
run_clear(revision)
|
||||
elif args.final:
|
||||
run_final(revision, args.final)
|
||||
else:
|
||||
run_daemon(revision)
|
||||
except (DisplayUnavailable, ImportError) as e:
|
||||
|
||||
@@ -200,6 +200,23 @@ IMAGESET_LABELS = {
|
||||
}
|
||||
IMAGESET_MISSING_SUFFIX = " - SATZ FEHLT"
|
||||
|
||||
# Der Abschlussbildschirm: was auf dem Panel stehen bleibt, wenn der Pi heruntergefaehrt
|
||||
# oder neu gestartet wird. E-Paper ist bistabil - das zuletzt gezeichnete Bild bleibt fuer
|
||||
# immer stehen, auch stromlos. Ohne diesen Schritt behauptet das Panel nach dem
|
||||
# Ausschalten weiter den Zustand von vorher (bei der Abnahme stand dort die RUECKFRAGE,
|
||||
# und der Pi sah aus, als warte er noch auf eine Eingabe).
|
||||
#
|
||||
# Die beiden Texte unterscheiden sich, und das ist der Punkt: beim Ausschalten DARF man
|
||||
# den Stecker ziehen, beim Neustart NICHT - ein Zero W braucht gut anderthalb Minuten, bis
|
||||
# der Anzeigedienst wieder zeichnet, und an genau diesem Pi hat ein Stromzug in dieser
|
||||
# Wartezeit schon eine SD-Karte gekostet.
|
||||
FINAL_LABELS = {
|
||||
"poweroff": ("AUSGESCHALTET", "Strom kann getrennt werden", []),
|
||||
"reboot": ("NEUSTART", "Strom NICHT trennen!",
|
||||
["Der Pi kommt in rund 2 min wieder.",
|
||||
"Solange bleibt dieses Bild stehen."]),
|
||||
}
|
||||
|
||||
# Woraus die Station die Signiervariante abgeleitet hat (products.classify_variant):
|
||||
# "-dev" kann in der Produkt-ID stehen, in der Version, oder in beiden. Die Quelle steht
|
||||
# mit auf dem Panel, weil genau hier am 2026-09-15 ein falsch signiertes Image
|
||||
@@ -250,6 +267,20 @@ def blocks(state: dict) -> list[tuple[str, str]]:
|
||||
|
||||
mode = state.get("mode")
|
||||
out: list[tuple[str, str]] = []
|
||||
|
||||
if mode == "final":
|
||||
# Abschlussbildschirm - KEINE Fusszeile und keine weiteren Zeilen: die Station ist
|
||||
# im Begriff zu verschwinden, und die einzige Auskunft, auf die es jetzt ankommt,
|
||||
# ist "darf ich den Stecker ziehen?".
|
||||
label = FINAL_LABELS.get(state.get("final"))
|
||||
if label is None:
|
||||
# Unbekannte Spielart - lieber ehrlich nichts behaupten als "ausgeschaltet"
|
||||
# zu sagen, wenn gleich wieder gebootet wird.
|
||||
return [("Faehrt herunter", ROLE_BIG)]
|
||||
title, hero, details = label
|
||||
return ([(title, ROLE_HEADER), (hero, ROLE_HERO)]
|
||||
+ [(d, ROLE_DETAIL) for d in details])
|
||||
|
||||
# Die Fusszeile haengt an JEDEM Statusbild - auch an "Station aus" und an "Status
|
||||
# unbekannt", die beide sofort zurueckkehren. Sie wird deshalb hier gebaut und unten
|
||||
# bei jedem Rueckgabepunkt angehaengt.
|
||||
|
||||
Reference in New Issue
Block a user