feat(station): Anzeige ausliefern, Migrationsprofil, display-Befehle

Schritt 8 des Plans: die fertige E-Paper-Anzeige wird endlich ausgeliefert
und verdrahtet, dazu zwei liegengebliebene Dinge.

- deploy.sh/setup_pi.sh uebertragen display/ (mit Panel-Treibern und
  HERKUNFT.md) und ninlight-display.service; setup_pi.sh installiert und
  aktiviert die Unit und zieht python3-pil nach - ohne Pillow beendet sich
  der Anzeigedienst still mit 0, das faellt sonst nirgends auf.
- setup_pi.sh legt auch das Profil fuer den Migrations-Zugangspunkt an
  (qconnex-firmware, von der alten Klyqa-Firmware fest vorgegeben, gleiche
  Adresse wie das Rettungsprofil). Ohne das scheiterte "migrate on" auf
  einem frisch eingerichteten Pi an need_profile. Bestehende Profile
  bleiben unangetastet.
- "ninlight-station display test|clear|off"; der Hilfetext erklaert die
  Revisionsbestimmung, denn EPD_REVISION laesst sich nur am Testbild
  bestimmen.
- "off" nimmt die Anzeige mit: erst Dienst stoppen, dann Panel leeren
  (sonst schreibt der Daemon binnen 5 s sein altes Bild zurueck) und beides
  vor ap_switch, das bei haengendem NetworkManager abbricht. Ein
  stehengebliebenes "MIGRATION" wuerde einen Betriebszustand behaupten, den
  es nicht mehr gibt.
- test_host.sh sammelt tools/mkimage als Ganzes ein: die 19 Tests in
  test_nl_mkimage.py liefen in keiner Suite mehr mit, darunter die Wache
  fuer die Python-3.8-Randbedingung des Build-Containers. Ein neuer Test
  haelt die Regel fest, dass keine Testdatei mehr herausfallen kann.
- Nebenbei: die Temp-Datei mit dem WLAN-Passwort wird wieder zuverlaessig
  aufgeraeumt, und leere Konfigurationswerte legen kein kaputtes Profil an.
This commit is contained in:
Michael Sauer
2026-09-13 23:19:14 +02:00
parent 22f92988dd
commit cc398ef57a
8 changed files with 795 additions and 54 deletions
+108
View File
@@ -51,6 +51,17 @@ setup_sandbox() {
echo "# dummy" >"$sandbox/tools/station/products.py"
echo "# dummy" >"$sandbox/tools/station/ninlight-station"
echo "# dummy" >"$sandbox/tools/station/ninlight-station.service"
echo "# dummy" >"$sandbox/tools/station/ninlight-display.service"
# display/ wird ECHT kopiert, nicht durch Attrappen ersetzt: die Zusage aus Task 6b
# ist, dass die beiden uebernommenen Panel-Treiber UND der Herkunftsnachweis
# HERKUNFT.md mit auf den Pi gehen. Mit selbst erfundenen Dateinamen im Sandbox-Repo
# wuerde der Test genau das nicht mehr zeigen - er pruefte nur noch sich selbst.
cp -R "$STATION_DIR/display" "$sandbox/tools/station/display"
rm -rf "$sandbox/tools/station/display/__pycache__"
# Ein __pycache__ mit .pyc dieses Rechners (andere Python-Version als der Pi) - es
# darf nicht mit uebertragen werden.
mkdir -p "$sandbox/tools/station/display/__pycache__"
echo "pyc-muell" >"$sandbox/tools/station/display/__pycache__/render.cpython-999.pyc"
# Fake nl_mkimage.py --verify: verhaelt sich wie das echte Tool (eine Zeile
# "<geraet> <version> <bytes>" auf stdout bei Erfolg, Exit 0; eine Zeile auf
@@ -111,9 +122,22 @@ echo "STUB sudo \$*" >>"$log"
exec "\$@"
EOF
# Der Stub unterscheidet nach Unit: deploy.sh behandelt den Anzeigedienst
# ausdruecklich anders als den Hauptdienst (ein fehlgeschlagener Neustart der
# Anzeige ist KEIN Deploy-Fehler). Mit einem Schalter fuer beide Units zusammen
# liesse sich genau dieser Unterschied nicht pruefen.
cat >"$bindir/systemctl" <<EOF
#!/usr/bin/env bash
echo "STUB systemctl \$*" >>"$log"
case "\$*" in
*ninlight-display*)
case "\$1" in
is-active) exit "\${FAKE_DISPLAY_ACTIVE_EXIT:-1}" ;;
restart) exit "\${FAKE_DISPLAY_RESTART_EXIT:-0}" ;;
*) exit 0 ;;
esac
;;
esac
case "\$1" in
is-active) exit "\${FAKE_SYSTEMCTL_ACTIVE_EXIT:-1}" ;;
restart) exit "\${FAKE_SYSTEMCTL_RESTART_EXIT:-0}" ;;
@@ -146,6 +170,7 @@ run_deploy() {
NINLIGHT_STATION_ROOT="$SANDBOX_STATE/opt/ninlight-station" \
NINLIGHT_STATION_BIN="$SANDBOX_STATE/usr/local/bin/ninlight-station" \
NINLIGHT_STATION_UNIT="$SANDBOX_STATE/etc/systemd/system/ninlight-station.service" \
NINLIGHT_DISPLAY_UNIT="$SANDBOX_STATE/etc/systemd/system/ninlight-display.service" \
NINLIGHT_SIGN_DIR="$sign_dir" \
"$sandbox/tools/station/deploy.sh" "$@"
}
@@ -948,6 +973,89 @@ for label in truncated empty array null string null-entry; do
fi
done
echo
echo "=== Szenario 22 (Task 6b, Punkt 1): deploy.sh liefert Anzeigeverzeichnis und Anzeige-Unit mit aus ==="
SANDBOX="$TMPROOT/s22"; STUBBIN="$TMPROOT/bin22"; LOG="$TMPROOT/s22.log"; SANDBOX_STATE="$TMPROOT/s22-state"
setup_sandbox "$SANDBOX"
setup_stubbin "$STUBBIN" "$LOG"
mkdir -p "$SANDBOX/build/foo"
echo "foo-image" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
# Bestand auf dem "Pi": eine Datei, die es im Repo nicht mehr gibt. display/ ist reiner
# Code aus dem Repo - was dort verschwindet, muss auch hier verschwinden, sonst laedt
# ein zurueckgebliebenes altes Modul weiter mit.
mkdir -p "$SANDBOX_STATE/opt/ninlight-station/display"
echo "alt" >"$SANDBOX_STATE/opt/ninlight-station/display/veraltet.py"
: >"$LOG"
set +e
OUT=$(run_deploy "$SANDBOX" pi-test 2>&1)
RC=$?
set -e
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
DISP="$SANDBOX_STATE/opt/ninlight-station/display"
for F in display.py render.py epdconfig.py epd2in13_V2.py epd2in13_V3.py HERKUNFT.md; do
[ -f "$DISP/$F" ] && pass "display/$F ausgeliefert" || fail "display/$F fehlt unter $DISP"
done
[ -f "$SANDBOX_STATE/etc/systemd/system/ninlight-display.service" ] && pass "ninlight-display.service ausgeliefert" || fail "Anzeige-Unit fehlt - sie waere weiter toter Text im Repo"
[ ! -f "$DISP/veraltet.py" ] && pass "im Repo geloeschte Datei verschwindet auch auf dem Pi (--delete)" || fail "veraltet.py blieb im Zielverzeichnis liegen"
[ ! -d "$DISP/__pycache__" ] && pass "kein __pycache__ dieses Rechners auf dem Pi gelandet" || fail "__pycache__ wurde mit uebertragen: $(ls "$DISP/__pycache__")"
# Die Anzeige lief nicht (Stub-Default) - dann wird sie auch nicht neu gestartet, aber
# der Deploy sagt es.
echo "$OUT" | grep -q "Anzeigedienst laeuft nicht" && pass "Deploy meldet, dass der Anzeigedienst nicht laeuft" || fail "Ausgabe erwaehnt den Anzeigedienst nicht: $OUT"
# Die fluechtige Ablage wird wieder aufgeraeumt (sie liegt, wie bei server.py & Co.,
# im echten /tmp - deploy.sh's REMOTE-Block kennt dafuer keine Sandbox-Variable).
[ ! -d /tmp/ninlight-display ] && pass "die fluechtige Ablage /tmp/ninlight-display wurde aufgeraeumt" || fail "/tmp/ninlight-display blieb liegen"
echo
echo "=== Szenario 23: laeuft die Anzeige, wird sie nach dem Deploy neu gestartet ==="
# Ohne Neustart liefe sie mit dem gerade ersetzten Code weiter.
SANDBOX="$TMPROOT/s23"; STUBBIN="$TMPROOT/bin23"; LOG="$TMPROOT/s23.log"; SANDBOX_STATE="$TMPROOT/s23-state"
setup_sandbox "$SANDBOX"
setup_stubbin "$STUBBIN" "$LOG"
mkdir -p "$SANDBOX/build/foo"
echo "foo-image" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
: >"$LOG"
set +e
OUT=$(FAKE_DISPLAY_ACTIVE_EXIT=0 run_deploy "$SANDBOX" pi-test 2>&1)
RC=$?
set -e
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
grep -Fq "systemctl restart ninlight-display" "$LOG" && pass "Anzeigedienst wurde neu gestartet" || fail "kein 'systemctl restart ninlight-display' im Log: $(cat "$LOG")"
echo "$OUT" | grep -q "Anzeigedienst neu gestartet" && pass "Ausgabe meldet den Neustart" || fail "Ausgabe meldet den Neustart nicht: $OUT"
echo
echo "=== Szenario 24: ein fehlgeschlagener Neustart der ANZEIGE ist kein Deploy-Fehler ==="
# Die Anzeige ist nirgends Voraussetzung fuer eine Migration. Ein Deploy, der wegen des
# Panels rot wird, verleitet nur dazu, einen echten Fehler zu uebersehen. Beim
# HAUPTdienst ist es umgekehrt - das prueft das Szenario darunter zur Abgrenzung.
SANDBOX="$TMPROOT/s24"; STUBBIN="$TMPROOT/bin24"; LOG="$TMPROOT/s24.log"; SANDBOX_STATE="$TMPROOT/s24-state"
setup_sandbox "$SANDBOX"
setup_stubbin "$STUBBIN" "$LOG"
mkdir -p "$SANDBOX/build/foo"
echo "foo-image" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
: >"$LOG"
set +e
OUT=$(FAKE_DISPLAY_ACTIVE_EXIT=0 FAKE_DISPLAY_RESTART_EXIT=1 run_deploy "$SANDBOX" pi-test 2>"$TMPROOT/s24.err")
RC=$?
set -e
ERR=$(cat "$TMPROOT/s24.err")
[ "$RC" -eq 0 ] && pass "Exit-Code 0 trotz fehlgeschlagenem Anzeige-Neustart" || fail "Exit-Code $RC - der Deploy wurde wegen der Anzeige rot: $ERR"
echo "$ERR" | grep -q "Warnung" && pass "Warnung (keine Fehlermeldung) auf stderr" || fail "keine Warnung auf stderr: $ERR"
[ -f "$SANDBOX_STATE/opt/ninlight-station/server.py" ] && pass "der Deploy selbst lief vollstaendig durch" || fail "server.py fehlt - der Deploy brach ab"
echo
echo "=== Szenario 25 (Abgrenzung): ein fehlgeschlagener Neustart des HAUPTdienstes ist sehr wohl ein Deploy-Fehler ==="
SANDBOX="$TMPROOT/s25"; STUBBIN="$TMPROOT/bin25"; LOG="$TMPROOT/s25.log"; SANDBOX_STATE="$TMPROOT/s25-state"
setup_sandbox "$SANDBOX"
setup_stubbin "$STUBBIN" "$LOG"
mkdir -p "$SANDBOX/build/foo"
echo "foo-image" >"$SANDBOX/build/foo/ninlight_foo_ota.img"
: >"$LOG"
set +e
OUT=$(FAKE_SYSTEMCTL_ACTIVE_EXIT=0 FAKE_SYSTEMCTL_RESTART_EXIT=1 run_deploy "$SANDBOX" pi-test 2>&1)
RC=$?
set -e
[ "$RC" -ne 0 ] && pass "Exit-Code != 0 beim Hauptdienst" || fail "Exit-Code 0 - ein toter Hauptdienst ginge unbemerkt durch: $OUT"
echo
if [ "$FAILED" -eq 0 ]; then
echo "Alle Tests bestanden."