Files
ninlight-w600/tools/station/ninlight-display.service
T
Michael Sauer eb9efa12f6 fix(station): Panel schlaeft nicht mehr nach jedem Bild, Breitenkuerzung nach Pixeln
Fixrunde 2 zu Task 6a: der sleep()-Einbau aus Fixrunde 1 hatte drei neue Critical
erzeugt. epd.sleep() schliesst ueber epdconfig.module_exit() die GPIO-Pins; ein
Teilaufbau direkt danach fasste bei V3 eine bereits geschlossene Pin-Referenz an
(AttributeError, ungefangen, Neustartschleife - binnen Sekunden nach dem Start, V3 ist
die Vorgaberevision) und war bei V2 nach Deep Sleep ohnehin keine belastbare Sequenz
(init(PART_UPDATE) allein setzt nicht die Register, die nur der FULL_UPDATE-Zweig setzt).
Ausserdem setzte der V3-Vollaufbau nie den Referenzpuffer, den ein Teilaufbau zum Diffen
braucht (displayPartBaseImage() statt display()).

Panel bekommt ein internes _awake-Flag: draw_partial() wertet sich selbst zu einem
Vollaufbau auf, wenn das Panel nicht wach ist (frisch erzeugt oder seit dem letzten
Aufbau geschlafen), unabhaengig von der Aufrufreihenfolge. sleep() wird nur noch nach
laengerer Ruhe (10 Minuten ohne Aenderung) gerufen, nicht mehr nach jedem Bild, und ist
selbst idempotent. Dazu: epdconfig faengt jetzt gpiozero.GPIOZeroError statt nur
GPIODeviceError (die PinError-Familie haengt nicht darunter), EnvironmentFile= in der
Unit bekommt ein "-"-Praefix, und die Zeilenkuerzung wandert von einer Zeichenzahl-Grenze
(ungueltig bei einer Proportionalschrift) zu einer echten Pixelbreitenmessung in
compose() - lines() bleibt dafuer ungekuerzt und weiterhin ohne Pillow testbar.
2026-09-13 21:24:42 +02:00

69 lines
3.8 KiB
Desktop File

[Unit]
Description=ninlight E-Paper-Anzeige (Status der Migrations-/Rettungsstation)
# Nur eine Anzeige fuer den Hauptdienst - Wants=/After=, NICHT Requires=/BindsTo=: ein
# fehlendes oder abgestuerztes Panel darf server.py niemals mitreissen, und server.py
# darf umgekehrt starten, auch wenn dieser Dienst fehlt oder deaktiviert ist. Siehe
# display.py's Modul-Docstring: die Anzeige ist nirgends Voraussetzung fuer die
# Migration.
After=ninlight-station.service
Wants=ninlight-station.service
# Gehoeren seit systemd 229 ins [Unit], nicht ins [Service] (dort standen sie zuerst -
# Fixrunde 1, I1: systemd ignoriert sie dort mit "Unknown key name", die Vorgabe (5
# Starts/10s) greift dann bei RestartSec=5 nie, und genau das sollte die Deckelung unten
# eigentlich verhindern). ninlight-station.service (dieselbe Sektion) macht es richtig.
StartLimitIntervalSec=60
StartLimitBurst=5
[Service]
Type=simple
# Reihenfolge wichtig: systemd wendet Environment=/EnvironmentFile= in der Reihenfolge
# an, in der sie hier stehen, und eine spaetere Zuweisung ueberschreibt eine gleichnamige
# fruehere. EPD_REVISION=V3 hier ist nur der Vorgabewert, falls die Konfigurationsdatei
# (unten, per EnvironmentFile=) den Schluessel (noch) nicht enthaelt - z.B. auf einem Pi,
# der vor Task 6 aufgesetzt wurde. Steht er dort, gewinnt er.
Environment=EPD_REVISION=V3
Environment=NINLIGHT_RUN_DIR=/run/ninlight-station
Environment=NINLIGHT_STATION_CONF=/etc/ninlight-station.conf
# "-"-Praefix: ohne die Konfigurationsdatei soll die Anzeige trotzdem starten (mit den
# Environment=-Vorgaben oben) statt mit einem harten Fehler - "die Anzeige darf nirgends
# Voraussetzung sein" gilt auch fuer ihre eigene Konfiguration (Fixrunde 1/2, Hinweis).
-EnvironmentFile=/etc/ninlight-station.conf
ExecStart=/usr/bin/python3 /opt/ninlight-station/display/display.py
# display.py faengt ein fehlendes/abgestuerztes Panel als DisplayUnavailable ab,
# protokolliert eine Zeile und beendet sich mit sys.exit(0) - das ist also nie ein
# "Fehlschlag" im systemd-Sinne und loest kein Restart= aus. Restart=on-failure (nicht
# always) greift nur fuer echte Abstuerze (z.B. ein Python-Traceback aus einem
# Programmierfehler); ohne den Deckel wuerde ein dauerhaft fehlendes Panel sonst NICHT
# staendig neu versucht (gut so), waehrend ein echter Bug endlos neu starten wuerde und
# so das Journal fluten koennte.
Restart=on-failure
RestartSec=5
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
# KEIN ReadOnlyPaths=/run/ninlight-station hier (stand hier zuerst, Fixrunde 1, C3):
# server.py's Unit traegt RuntimeDirectory=ninlight-station, und "ninlight-station off"
# stoppt den Hauptdienst regulaer - dabei raeumt systemd dessen RuntimeDirectory auf,
# sofern nicht gerade ein Neustart lief (RuntimeDirectoryPreserve=restart deckt nur den
# Neustart-Fall ab, nicht den regulaeren Stop). Ein nicht mit "-" prefigierter Pfad in
# ReadOnlyPaths=/ReadWritePaths=/BindPaths=, der beim Start fehlt, laesst systemd die
# Namensraum-Einrichtung mit 226/NAMESPACE abbrechen - ausgerechnet dann, wenn die
# Anzeige "Station aus" zeigen soll, waere sie selbst nicht mehr startbar gewesen. Die
# Zeile war obendrein wirkungslos: ProtectSystem=strict macht "/" (und damit "/run",
# soweit nicht per ReadWritePaths= explizit freigegeben) bereits schreibgeschuetzt.
RestrictAddressFamilies=AF_UNIX AF_NETLINK
MemoryMax=64M
# Laeuft als root (wie der Hauptdienst) fuer /dev/spidev0.0 und die GPIO-Zeichenleitung -
# kein SupplementaryGroups=spi gpio hier (stand hier zuerst, Fixrunde 1, I2): auf reinem
# Debian (im Unterschied zu Raspberry Pi OS) gibt es diese Gruppen oft gar nicht, und
# eine fehlende Gruppe ist fuer systemd ein harter Startfehler (216/GROUP) - ausgerechnet
# in der Unit, die nie stoeren soll, wenn etwas an der Anzeige fehlt. Als root sind die
# Gruppen fuer den Geraetezugriff ohnehin nicht noetig.
[Install]
WantedBy=multi-user.target