fix(station): Anzeigedienst bleibt ueber on/off hinweg durchgehend an

'off' stoppte bisher den Anzeigedienst und leerte das Panel, aber kein
'on'-Pfad hat ihn je wieder gestartet - ab dem zweiten Ein/Aus blieb das
Panel bis zum naechsten Pi-Neustart dunkel. Die Begruendung dafuer trug
nicht: der Anzeigedienst erkennt den ausgeschalteten Zustand selbst
(systemctl is-active ninlight-station) und zeichnet von sich aus
"Station aus".

'off' fasst den Anzeigedienst jetzt nicht mehr an; er laeuft durchgehend
(wie seit dem Booten ohnehin) und zeigt den jeweils aktuellen Zustand.
display_off() bleibt fuer den expliziten Diagnosebefehl
'ninlight-station display off' erhalten. Enthaelt zusaetzlich die schon
im Arbeitsbaum liegende PHASE_LABELS-Korrektur in render.py
(HORCHT -> LISTEN).
This commit is contained in:
Michael Sauer
2026-09-14 21:09:50 +02:00
parent 5f3cf25e41
commit d1535433ce
3 changed files with 93 additions and 63 deletions
+54 -43
View File
@@ -140,6 +140,16 @@ case "\$1" in
exit "\$code"
fi
;;
start)
# Testschalter fuer die Resilienz-Faelle: bildet nach, dass "ninlight-display"
# gar nicht installiert oder absichtlich maskiert ist (systemctl gibt dann einen
# Fehler zurueck statt still zu tun, als waere alles gut).
if [ "\$2" = "ninlight-display" ] && [ -n "\${FAKE_DISPLAY_SERVICE_MISSING:-}" ]; then
echo "Failed to start ninlight-display.service: Unit not found." >&2
exit 5
fi
exit 0
;;
restart|stop)
# Bildet systemds RuntimeDirectory-Verhalten nach (Fixrunde 1, C1): ohne
# RuntimeDirectoryPreserve=restart in der Unit leert systemd das
@@ -157,6 +167,10 @@ case "\$1" in
rm -rf "\$RUN_DIR"
mkdir -p "\$RUN_DIR"
fi
if [ "\$2" = "ninlight-display" ] && [ -n "\${FAKE_DISPLAY_SERVICE_MISSING:-}" ]; then
echo "Failed to stop ninlight-display.service: Unit not found." >&2
exit 5
fi
if [ "\$1" = "restart" ]; then
exit "\${FAKE_SYSTEMCTL_RESTART_EXIT:-0}"
fi
@@ -795,10 +809,14 @@ else
fi
echo
echo "=== Fall D4 (Task 6b, Punkt 4): 'off' nimmt die Anzeige mit - Dienst gestoppt, Panel geleert ==="
# Ohne das behauptet das stehengebliebene Bild einen Betriebszustand ("MIGRATION"),
# den es nicht mehr gibt - bei einer Station, die Lampen ungefragt Firmware
# unterschieben kann, die gefaehrliche Richtung.
echo "=== Fall D4 (Befund Task 7f): 'off' fasst den Anzeigedienst nicht mehr an ==="
# Frueher stoppte 'off' den Anzeigedienst und leerte das Panel - mit der Begruendung,
# ein stehengebliebenes "MIGRATION"-Bild wuerde einen Betriebszustand behaupten, den es
# nicht mehr gibt. Die Begruendung trug nicht: der Anzeigedienst erkennt den
# ausgeschalteten Zustand selbst (systemctl is-active ninlight-station) und zeichnet dann
# "Station aus". Das eigentliche Ergebnis des Stoppens war ein anderes: kein "on"-Pfad hat
# den Dienst je wieder gestartet, und das Panel blieb ab dem zweiten Durchgang bis zum
# naechsten Pi-Neustart dunkel. Jetzt laesst 'off' den Dienst laufen.
SBX="$TMPROOT/tD4"
setup_sandbox "$SBX"
echo "dummy" >"$SBX/images/migrate/gu10_rgb_prod.img"
@@ -809,57 +827,50 @@ OUT=$(run_cli "$SBX" off 2>&1)
RC=$?
set -e
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
grep -Fq "STUB systemctl stop ninlight-display" "$SBX/cmd.log" && pass "'off' stoppt den Anzeigedienst" || fail "'off' stoppt den Anzeigedienst nicht: $(cat "$SBX/cmd.log")"
grep -Fq "STUB display.py --clear" "$SBX/cmd.log" && pass "'off' leert das Panel" || fail "'off' leert das Panel nicht: $(cat "$SBX/cmd.log")"
STOP_LINE=$(grep -n "STUB systemctl stop ninlight-display" "$SBX/cmd.log" | head -n1 | cut -d: -f1 || true)
CLEAR_LINE=$(grep -n "STUB display.py --clear" "$SBX/cmd.log" | head -n1 | cut -d: -f1 || true)
if [ -n "$STOP_LINE" ] && [ -n "$CLEAR_LINE" ] && [ "$STOP_LINE" -lt "$CLEAR_LINE" ]; then
pass "auch bei 'off': erst stoppen, dann leeren"
else
fail "Reihenfolge bei 'off' falsch (stop=$STOP_LINE, clear=$CLEAR_LINE): $(cat "$SBX/cmd.log")"
fi
# Das Panel muss geleert sein, BEVOR ap_switch das Ergebnis von "off" verkuenden kann -
# und vor allem, bevor ap_switch bei haengendem NetworkManager abbricht (Fall D6).
UP_LINE=$(grep -n "nmcli con up home-wifi" "$SBX/cmd.log" | head -n1 | cut -d: -f1 || true)
if [ -n "$UP_LINE" ] && [ "$CLEAR_LINE" -lt "$UP_LINE" ]; then
pass "Panel ist geleert, bevor wlan0 ins Heim-WLAN zurueckgeschaltet wird"
else
fail "Panel wurde erst nach dem Zurueckschalten geleert (clear=$CLEAR_LINE, up=$UP_LINE)"
fi
grep -Fq "STUB systemctl stop ninlight-display" "$SBX/cmd.log" && fail "'off' stoppt den Anzeigedienst weiterhin: $(cat "$SBX/cmd.log")" || pass "'off' stoppt den Anzeigedienst nicht mehr"
grep -Fq "STUB display.py --clear" "$SBX/cmd.log" && fail "'off' leert weiterhin das Panel: $(cat "$SBX/cmd.log")" || pass "'off' leert das Panel nicht mehr (der Dienst zeichnet 'Station aus' selbst)"
echo
echo "=== Fall D5: ein Fehler beim Leeren des Panels haelt 'off' nicht auf ==="
# Ein fehlendes/kaputtes Panel darf das Ausschalten der Station nie blockieren -
# sonst bliebe ausgerechnet der Zugangspunkt an, weil die Anzeige klemmt.
SBX="$TMPROOT/tD5"
echo "=== Fall D4b (die Zusage aus Task 7f): 'off' und ein erneutes 'on' lassen den Anzeigedienst durchgehend an ==="
# Genau das ist die Zusage: nach 'off' und einem erneuten 'on' ist die Anzeige wieder in
# Betrieb - und zwar OHNE dass irgendwer den Dienst zwischendurch neu gestartet haben
# muesste, weil er nie gestoppt wurde. Ein/Aus/Ein durchspielen und ueber die gesamte
# Sequenz pruefen, dass 'ninlight-display' kein einziges Mal gestoppt wird.
SBX="$TMPROOT/tD4b"
setup_sandbox "$SBX"
: >"$SBX/cmd.log"
set +e
OUT=$(FAKE_DISPLAY_EXIT=1 run_cli "$SBX" off 2>"$SBX/stderr.log")
RC=$?
run_cli "$SBX" on >/dev/null 2>&1
RC1=$?
run_cli "$SBX" off >/dev/null 2>&1
RC2=$?
run_cli "$SBX" on >/dev/null 2>&1
RC3=$?
set -e
ERR=$(cat "$SBX/stderr.log")
[ "$RC" -eq 0 ] && pass "Exit-Code 0 trotz fehlgeschlagenem Leeren" || fail "Exit-Code $RC, stderr: $ERR"
echo "$ERR" | grep -q "Warnung" && pass "Warnung ueber das nicht geleerte Panel steht auf stderr" || fail "keine Warnung auf stderr: $ERR"
grep -Fq "nmcli con up home-wifi" "$SBX/cmd.log" && pass "'off' hat trotzdem bis zum Zurueckschalten ins Heim-WLAN durchgezogen" || fail "'off' ist am Anzeigefehler haengengeblieben: $(cat "$SBX/cmd.log")"
[ "$RC1" -eq 0 ] && [ "$RC2" -eq 0 ] && [ "$RC3" -eq 0 ] \
&& pass "on/off/on liefern alle Exit-Code 0" \
|| fail "on/off/on: Exit-Codes $RC1/$RC2/$RC3, Log: $(cat "$SBX/cmd.log")"
grep -Fq "STUB systemctl stop ninlight-display" "$SBX/cmd.log" \
&& fail "der Anzeigedienst wurde waehrend on/off/on gestoppt - nach 'off' bliebe das Panel dunkel: $(cat "$SBX/cmd.log")" \
|| pass "der Anzeigedienst blieb ueber on/off/on hinweg durchgehend an"
echo
echo "=== Fall D6: haengender NetworkManager - das Panel wird trotzdem noch geleert ==="
# ap_switch "" bricht hier mit Fehler ab; unter "set -e" laeuft danach nichts mehr.
# Genau deshalb steht display_off VOR ap_switch: sonst bliebe ein "MIGRATION"-Bild
# stehen, waehrend der Zugangspunkt noch sendet und niemand mehr etwas abschaltet.
SBX="$TMPROOT/tD6"
echo "=== Fall D9: ein fehlender/scheiternder Anzeigedienst haelt weder 'on' noch 'off' auf ==="
# FAKE_DISPLAY_SERVICE_MISSING bildet nach, dass 'ninlight-display' gar nicht installiert
# oder absichtlich deaktiviert ist (systemctl meldet dann einen Fehler statt still
# mitzuspielen). Das darf 'on' und 'off' nicht scheitern lassen - eine tragende Zusage
# dieses Projekts.
SBX="$TMPROOT/tD9"
setup_sandbox "$SBX"
echo "dummy" >"$SBX/images/migrate/gu10_rgb_prod.img"
run_cli "$SBX" migrate on >/dev/null 2>&1
: >"$SBX/cmd.log"
set +e
OUT=$(FAKE_NMCLI_STUCK=1 run_cli "$SBX" off 2>"$SBX/stderr.log")
RC=$?
OUT_ON=$(FAKE_DISPLAY_SERVICE_MISSING=1 FAKE_DISPLAY_SERVICE_ACTIVE_EXIT=4 run_cli "$SBX" on 2>&1)
RC_ON=$?
OUT_OFF=$(FAKE_DISPLAY_SERVICE_MISSING=1 FAKE_DISPLAY_SERVICE_ACTIVE_EXIT=4 run_cli "$SBX" off 2>&1)
RC_OFF=$?
set -e
ERR=$(cat "$SBX/stderr.log")
[ "$RC" -ne 0 ] && pass "Exit-Code != 0 (der Zugangspunkt liess sich nicht abschalten)" || fail "Exit-Code 0, obwohl der Zugangspunkt noch aktiv ist: $OUT"
grep -Fq "STUB display.py --clear" "$SBX/cmd.log" && pass "Panel wurde trotz haengendem NetworkManager geleert" || fail "Panel blieb stehen, waehrend der Zugangspunkt noch sendet: $(cat "$SBX/cmd.log")"
[ "$RC_ON" -eq 0 ] && pass "'on' gelingt trotz fehlendem Anzeigedienst" || fail "'on' scheitert am fehlenden Anzeigedienst, Exit $RC_ON: $OUT_ON"
[ "$RC_OFF" -eq 0 ] && pass "'off' gelingt trotz fehlendem Anzeigedienst" || fail "'off' scheitert am fehlenden Anzeigedienst, Exit $RC_OFF: $OUT_OFF"
echo
echo "=== Fall D7: 'display' ohne/mit unbekanntem Unterbefehl -> Hilfetext, Exit 2 ==="