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:
@@ -164,9 +164,31 @@ EOF
|
||||
setup_sandbox() {
|
||||
local sandbox=$1 unitfile=${2:-$REAL_UNIT}
|
||||
rm -rf "$sandbox"
|
||||
mkdir -p "$sandbox/run" "$sandbox/images/migrate" "$sandbox/bin" "$sandbox/nmstate"
|
||||
mkdir -p "$sandbox/run" "$sandbox/images/migrate" "$sandbox/bin" "$sandbox/nmstate" \
|
||||
"$sandbox/lib/display"
|
||||
echo "dummy" >"$sandbox/images/ninlight_foo_ota.img"
|
||||
|
||||
# NINLIGHT_STATION_LIB zeigt auf diese Sandbox statt auf tools/station selbst.
|
||||
# products.py wird gebraucht (arm prueft das Ziel dagegen) und deshalb echt kopiert;
|
||||
# display/display.py ist ein Rekorder, der seine Argumente in DIESELBE cmd.log
|
||||
# schreibt wie die nmcli-/systemctl-Stubs. Nur so laesst sich die REIHENFOLGE
|
||||
# "erst Dienst stoppen, dann Panel leeren" ueberhaupt pruefen - mit zwei getrennten
|
||||
# Logs waere sie nicht rekonstruierbar.
|
||||
#
|
||||
# Der Rekorder ersetzt das echte display.py absichtlich: geprueft wird hier, WAS
|
||||
# ninlight-station aufruft (Pfad, Schalter, Reihenfolge, Umgang mit dem Rueckgabe-
|
||||
# wert), nicht was display.py daraus macht - das deckt tests/test_render.py ab.
|
||||
cp "$STATION_DIR/products.py" "$sandbox/lib/products.py"
|
||||
cat >"$sandbox/lib/display/display.py" <<EOF
|
||||
import os
|
||||
import sys
|
||||
|
||||
with open("$sandbox/cmd.log", "a") as fh:
|
||||
fh.write("STUB display.py %s (EPD_REVISION=%s)\n"
|
||||
% (" ".join(sys.argv[1:]), os.environ.get("EPD_REVISION", "<unset>")))
|
||||
sys.exit(int(os.environ.get("FAKE_DISPLAY_EXIT", "0")))
|
||||
EOF
|
||||
|
||||
cat >"$sandbox/station.conf" <<EOF
|
||||
SSID='test-recovery'
|
||||
PSK='test-psk'
|
||||
@@ -192,7 +214,7 @@ run_cli() {
|
||||
PATH="$sandbox/bin:$PATH" \
|
||||
NINLIGHT_STATION_CONF="$sandbox/station.conf" \
|
||||
NINLIGHT_RUN_DIR="$sandbox/run" \
|
||||
NINLIGHT_STATION_LIB="$STATION_DIR" \
|
||||
NINLIGHT_STATION_LIB="$sandbox/lib" \
|
||||
"$CLI" "$@"
|
||||
}
|
||||
|
||||
@@ -549,6 +571,157 @@ echo "$OUT" | grep -qE "Modus[[:space:]]*: -" && pass "Modus zeigt '-' (keine st
|
||||
echo "$OUT" | grep -qi "armed" && fail "status zeigt 'armed', obwohl der Modus gar nicht bekannt ist: $OUT" || pass "keine 'armed'-Phase bei unbekanntem Modus"
|
||||
echo "$OUT" | grep -qE "Phase[[:space:]]*: -" && pass "Phase zeigt '-' statt einer Vermutung" || fail "Phase zeigt keinen neutralen Platzhalter: $OUT"
|
||||
|
||||
echo
|
||||
echo "=== Fall D1 (Task 6b, Punkt 3): 'display test' ruft display.py mit --test und der konfigurierten Revision auf ==="
|
||||
SBX="$TMPROOT/tD1"
|
||||
setup_sandbox "$SBX"
|
||||
: >"$SBX/cmd.log"
|
||||
set +e
|
||||
OUT=$(run_cli "$SBX" display test 2>&1)
|
||||
RC=$?
|
||||
set -e
|
||||
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
||||
grep -Fq "STUB display.py --test" "$SBX/cmd.log" && pass "display.py wurde mit --test aufgerufen" || fail "kein '--test'-Aufruf im Log: $(cat "$SBX/cmd.log")"
|
||||
# Der Testbild-Aufruf ist die einzige Moeglichkeit, die Panel-Revision zu bestimmen -
|
||||
# er ist wertlos, wenn er nicht die Revision aus der Konfiguration durchreicht:
|
||||
# EPD_REVISION wird per ". $CONF" nur als Shell-Variable gesetzt, nicht exportiert.
|
||||
grep -Fq "STUB display.py --test (EPD_REVISION=V3)" "$SBX/cmd.log" && pass "EPD_REVISION aus der Konfiguration erreicht den Python-Prozess" || fail "EPD_REVISION kam nicht durch: $(cat "$SBX/cmd.log")"
|
||||
# Nicht gleichzeitig den Anzeigedienst umbringen: 'display test' ist ein Handgriff bei
|
||||
# der Abnahme, kein Ausschalten.
|
||||
grep -Fq "systemctl stop ninlight-display" "$SBX/cmd.log" && fail "'display test' stoppt den Anzeigedienst - das soll nur 'display off'/'off'" || pass "'display test' laesst den Anzeigedienst in Ruhe"
|
||||
|
||||
echo
|
||||
echo "=== Fall D2: 'display clear' ruft display.py mit --clear auf ==="
|
||||
SBX="$TMPROOT/tD2"
|
||||
setup_sandbox "$SBX"
|
||||
: >"$SBX/cmd.log"
|
||||
set +e
|
||||
OUT=$(run_cli "$SBX" display clear 2>&1)
|
||||
RC=$?
|
||||
set -e
|
||||
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
||||
grep -Fq "STUB display.py --clear" "$SBX/cmd.log" && pass "display.py wurde mit --clear aufgerufen" || fail "kein '--clear'-Aufruf im Log: $(cat "$SBX/cmd.log")"
|
||||
grep -Fq "STUB display.py --test" "$SBX/cmd.log" && fail "'display clear' hat --test aufgerufen" || pass "kein --test bei 'display clear'"
|
||||
|
||||
echo
|
||||
echo "=== Fall D3 (Task 6b, Punkt 4): 'display off' stoppt den Dienst VOR dem Leeren des Panels ==="
|
||||
# Umgekehrte Reihenfolge waere wirkungslos: der Daemon fragt alle 5 s nach und
|
||||
# schriebe sein altes Bild binnen dieser Zeit wieder aufs Panel.
|
||||
SBX="$TMPROOT/tD3"
|
||||
setup_sandbox "$SBX"
|
||||
: >"$SBX/cmd.log"
|
||||
set +e
|
||||
OUT=$(run_cli "$SBX" display off 2>&1)
|
||||
RC=$?
|
||||
set -e
|
||||
[ "$RC" -eq 0 ] && pass "Exit-Code 0" || fail "Exit-Code $RC, Ausgabe: $OUT"
|
||||
STOP_LINE=$(grep -n "STUB systemctl stop ninlight-display" "$SBX/cmd.log" | head -n1 | cut -d: -f1)
|
||||
CLEAR_LINE=$(grep -n "STUB display.py --clear" "$SBX/cmd.log" | head -n1 | cut -d: -f1)
|
||||
if [ -n "$STOP_LINE" ] && [ -n "$CLEAR_LINE" ] && [ "$STOP_LINE" -lt "$CLEAR_LINE" ]; then
|
||||
pass "Anzeigedienst wird gestoppt, BEVOR das Panel geleert wird (stop=$STOP_LINE, clear=$CLEAR_LINE)"
|
||||
else
|
||||
fail "Reihenfolge falsch oder Aufruf fehlt (stop=$STOP_LINE, clear=$CLEAR_LINE): $(cat "$SBX/cmd.log")"
|
||||
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.
|
||||
SBX="$TMPROOT/tD4"
|
||||
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=$(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)
|
||||
CLEAR_LINE=$(grep -n "STUB display.py --clear" "$SBX/cmd.log" | head -n1 | cut -d: -f1)
|
||||
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)
|
||||
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
|
||||
|
||||
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"
|
||||
setup_sandbox "$SBX"
|
||||
: >"$SBX/cmd.log"
|
||||
set +e
|
||||
OUT=$(FAKE_DISPLAY_EXIT=1 run_cli "$SBX" off 2>"$SBX/stderr.log")
|
||||
RC=$?
|
||||
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")"
|
||||
|
||||
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"
|
||||
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=$?
|
||||
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")"
|
||||
|
||||
echo
|
||||
echo "=== Fall D7: 'display' ohne/mit unbekanntem Unterbefehl -> Hilfetext, Exit 2 ==="
|
||||
SBX="$TMPROOT/tD7"
|
||||
setup_sandbox "$SBX"
|
||||
# Anlegen, bevor geprueft wird: eine fehlende Logdatei wuerde die "kein Aufruf"-Zusage
|
||||
# unten sonst durch ein grep-Versagen erfuellen statt durch das Verhalten des Skripts.
|
||||
: >"$SBX/cmd.log"
|
||||
for ARG in "" "quatsch"; do
|
||||
set +e
|
||||
# shellcheck disable=SC2086
|
||||
OUT=$(run_cli "$SBX" display $ARG 2>&1)
|
||||
RC=$?
|
||||
set -e
|
||||
[ "$RC" -eq 2 ] && pass "Exit-Code 2 fuer 'display ${ARG:-<leer>}'" || fail "Exit-Code $RC fuer 'display ${ARG:-<leer>}': $OUT"
|
||||
echo "$OUT" | grep -q "display {test|clear|off}" && pass "Hilfetext nennt die Unterbefehle ('display ${ARG:-<leer>}')" || fail "Hilfetext fehlt: $OUT"
|
||||
done
|
||||
grep -Fq "STUB display.py" "$SBX/cmd.log" && fail "ein unbekannter Unterbefehl hat display.py trotzdem aufgerufen" || pass "kein display.py-Aufruf bei unbekanntem Unterbefehl"
|
||||
|
||||
echo
|
||||
echo "=== Fall D8 (Task 6b, Punkt 3): der Hilfetext erklaert die Revisionsbestimmung ==="
|
||||
# Die Panel-Revision steht am Geraet nirgends; 'display test' ist die einzige
|
||||
# Moeglichkeit, sie zu bestimmen, und das muss dort stehen, wo man beim ersten
|
||||
# Handgriff der Hardware-Abnahme hinsieht.
|
||||
SBX="$TMPROOT/tD8"
|
||||
setup_sandbox "$SBX"
|
||||
set +e
|
||||
HELP=$(run_cli "$SBX" quatsch 2>&1)
|
||||
set -e
|
||||
echo "$HELP" | grep -q "EPD_REVISION" && pass "Hilfetext nennt EPD_REVISION" || fail "Hilfetext nennt EPD_REVISION nicht: $HELP"
|
||||
echo "$HELP" | grep -q "ninlight-station.conf" && pass "Hilfetext nennt die Datei, in der die Revision steht" || fail "Hilfetext nennt /etc/ninlight-station.conf nicht: $HELP"
|
||||
echo "$HELP" | grep -qE "V2|V3" && pass "Hilfetext nennt die beiden moeglichen Revisionen" || fail "Hilfetext nennt V2/V3 nicht: $HELP"
|
||||
|
||||
echo
|
||||
if [ "$FAILED" -eq 0 ]; then
|
||||
echo "Alle Tests bestanden."
|
||||
|
||||
Reference in New Issue
Block a user