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:
+142
-47
@@ -77,8 +77,31 @@ fi
|
||||
rm -f "$TEMPLATE"
|
||||
REMOTE
|
||||
|
||||
echo "== Server, CLI und Unit installieren =="
|
||||
"$SCP_BIN" -q "$HERE/server.py" "$HERE/products.py" "$HERE/ninlight-station" "$HERE/ninlight-station.service" "$HOST:$STAGE_DIR/"
|
||||
echo "== Pakete =="
|
||||
# Pillow kommt auf dem Pi bewusst aus dem Distributionspaket (python3-pil), nicht per
|
||||
# pip: tools/station/ laeuft dort unter dem System-Python 3.13, und Debian 13 laesst ein
|
||||
# "pip install" ins System-Python gar nicht mehr zu (PEP 668). Ohne Pillow zeichnet die
|
||||
# Anzeige nicht - display.py faengt den ImportError ab und beendet sich still mit 0
|
||||
# (Absicht, damit ein fehlendes Panel nie eine Migration verhindert). Genau deshalb muss
|
||||
# das Paket HIER kommen: sonst faellt es nirgends auf.
|
||||
"$SSH_BIN" "$HOST" bash -s <<'REMOTE'
|
||||
set -e
|
||||
if python3 -c 'import PIL' 2>/dev/null; then
|
||||
echo " python3-pil ist bereits vorhanden"
|
||||
else
|
||||
sudo apt-get install -y python3-pil
|
||||
echo " python3-pil installiert"
|
||||
fi
|
||||
REMOTE
|
||||
|
||||
echo "== Server, CLI und Units installieren =="
|
||||
"$SCP_BIN" -q "$HERE/server.py" "$HERE/products.py" "$HERE/ninlight-station" \
|
||||
"$HERE/ninlight-station.service" "$HERE/ninlight-display.service" "$HOST:$STAGE_DIR/"
|
||||
# Das Anzeigeverzeichnis als Ganzes: render.py, display.py, epdconfig.py, die beiden
|
||||
# uebernommenen Panel-Treiber und HERKUNFT.md (der Herkunftsnachweis gehoert mit auf den
|
||||
# Pi). "scp -r" statt rsync, weil setup_pi.sh - anders als deploy.sh - ausdruecklich nur
|
||||
# ssh/scp voraussetzt; __pycache__ wird auf der Gegenseite weggeraeumt (unten).
|
||||
"$SCP_BIN" -q -r "$HERE/display" "$HOST:$STAGE_DIR/ninlight-display"
|
||||
# Pfade als Variablen mit Default (wie in deploy.sh): so kann ein Test die
|
||||
# Installation gegen ein Sandbox-Verzeichnis statt gegen echte Systempfade laufen
|
||||
# lassen.
|
||||
@@ -88,17 +111,40 @@ STAGE_DIR=${NINLIGHT_STAGE_DIR:-/tmp}
|
||||
ROOT_DIR=${NINLIGHT_STATION_ROOT:-/opt/ninlight-station}
|
||||
BIN_PATH=${NINLIGHT_STATION_BIN:-/usr/local/bin/ninlight-station}
|
||||
UNIT_PATH=${NINLIGHT_STATION_UNIT:-/etc/systemd/system/ninlight-station.service}
|
||||
sudo install -d -m 0755 "$ROOT_DIR" "$ROOT_DIR/images"
|
||||
DISPLAY_UNIT_PATH=${NINLIGHT_DISPLAY_UNIT:-/etc/systemd/system/ninlight-display.service}
|
||||
sudo install -d -m 0755 "$ROOT_DIR" "$ROOT_DIR/images" "$ROOT_DIR/display"
|
||||
sudo install -m 0755 "$STAGE_DIR/server.py" "$ROOT_DIR/server.py"
|
||||
sudo install -m 0644 "$STAGE_DIR/products.py" "$ROOT_DIR/products.py"
|
||||
sudo install -m 0755 "$STAGE_DIR/ninlight-station" "$BIN_PATH"
|
||||
sudo install -m 0644 "$STAGE_DIR/ninlight-station.service" "$UNIT_PATH"
|
||||
rm -f "$STAGE_DIR/server.py" "$STAGE_DIR/products.py" "$STAGE_DIR/ninlight-station" "$STAGE_DIR/ninlight-station.service"
|
||||
# Anzeige: display.py ist ausfuehrbar (die Unit ruft es zwar ueber /usr/bin/python3 auf,
|
||||
# "ninlight-station display test" aber auch von Hand), der Rest ist importierter Code.
|
||||
rm -rf "$STAGE_DIR/ninlight-display/__pycache__"
|
||||
for f in "$STAGE_DIR"/ninlight-display/*; do
|
||||
[ -f "$f" ] || continue
|
||||
case "$(basename "$f")" in
|
||||
display.py) sudo install -m 0755 "$f" "$ROOT_DIR/display/$(basename "$f")" ;;
|
||||
*) sudo install -m 0644 "$f" "$ROOT_DIR/display/$(basename "$f")" ;;
|
||||
esac
|
||||
done
|
||||
sudo install -m 0644 "$STAGE_DIR/ninlight-display.service" "$DISPLAY_UNIT_PATH"
|
||||
rm -rf "$STAGE_DIR/server.py" "$STAGE_DIR/products.py" "$STAGE_DIR/ninlight-station" \
|
||||
"$STAGE_DIR/ninlight-station.service" "$STAGE_DIR/ninlight-display.service" \
|
||||
"$STAGE_DIR/ninlight-display"
|
||||
sudo systemctl daemon-reload
|
||||
echo " installiert"
|
||||
# Die Anzeige wird aktiviert (startet also beim Booten mit) UND jetzt gestartet - anders
|
||||
# als die Station selbst, die absichtlich aus bleibt: die Anzeige sendet nichts, sie
|
||||
# zeigt nur an, und "Station aus" ist genau das, was nach dem Einrichten auf dem Panel
|
||||
# stehen soll. Fehlt Panel oder Pillow, beendet sich der Dienst still mit 0 (siehe
|
||||
# display.py) - deshalb "|| true": ein fehlendes Panel darf setup_pi.sh nicht abbrechen.
|
||||
sudo systemctl enable ninlight-display.service >/dev/null 2>&1 || \
|
||||
echo " Warnung: 'systemctl enable ninlight-display' fehlgeschlagen" >&2
|
||||
sudo systemctl restart ninlight-display.service >/dev/null 2>&1 || \
|
||||
echo " Warnung: 'systemctl restart ninlight-display' fehlgeschlagen" >&2
|
||||
echo " installiert (Anzeigedienst aktiviert)"
|
||||
REMOTE
|
||||
|
||||
echo "== NetworkManager-Profil =="
|
||||
echo "== NetworkManager-Profile =="
|
||||
# Zwei Dinge, die man an einer echten Lampenstation nicht falsch machen darf:
|
||||
# - /etc/ninlight-station.conf ist 0600 root:root (das Passwort soll nicht
|
||||
# jeder lesen koennen). Der SSH-Benutzer ist aber nicht root, direktes
|
||||
@@ -116,47 +162,91 @@ set -e
|
||||
CONF=${NINLIGHT_STATION_CONF:-/etc/ninlight-station.conf}
|
||||
NM_DIR=${NINLIGHT_NM_DIR:-/etc/NetworkManager/system-connections}
|
||||
eval "$(sudo cat "$CONF")"
|
||||
CONN_FILE="$NM_DIR/${AP_CONNECTION}.nmconnection"
|
||||
if sudo test -f "$CONN_FILE"; then
|
||||
UUID=$(sudo sed -n 's/^uuid=//p' "$CONN_FILE" | head -n1)
|
||||
ACTION="aktualisiert"
|
||||
else
|
||||
# /proc/sys/kernel/random/uuid gibt es nur unter Linux (dem Pi) - der Fallback
|
||||
# auf uuidgen ist es, was einen Testlauf gegen den lokalen Stub (macOS) ueberhaupt
|
||||
# ermoeglicht, aendert aber nichts am Verhalten auf dem Pi selbst.
|
||||
UUID=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || uuidgen | tr '[:upper:]' '[:lower:]')
|
||||
ACTION="angelegt"
|
||||
fi
|
||||
TMP=$(mktemp)
|
||||
trap 'rm -f "$TMP"' EXIT
|
||||
{
|
||||
printf '%s\n' '[connection]'
|
||||
printf 'id=%s\n' "$AP_CONNECTION"
|
||||
printf 'uuid=%s\n' "$UUID"
|
||||
printf '%s\n' 'type=wifi'
|
||||
printf '%s\n' 'interface-name=wlan0'
|
||||
printf '%s\n' 'autoconnect=false'
|
||||
printf '\n%s\n' '[wifi]'
|
||||
printf '%s\n' 'mode=ap'
|
||||
printf 'ssid=%s\n' "$SSID"
|
||||
printf '%s\n' 'band=bg'
|
||||
printf '%s\n' 'channel=6'
|
||||
printf '\n%s\n' '[wifi-security]'
|
||||
printf '%s\n' 'key-mgmt=wpa-psk'
|
||||
printf '%s\n' 'proto=rsn'
|
||||
printf '%s\n' 'pairwise=ccmp;'
|
||||
printf '%s\n' 'group=ccmp;'
|
||||
printf 'psk=%s\n' "$PSK"
|
||||
printf '\n%s\n' '[ipv4]'
|
||||
printf '%s\n' 'method=shared'
|
||||
printf '%s\n' 'address1=192.168.8.1/24'
|
||||
printf '\n%s\n' '[ipv6]'
|
||||
printf '%s\n' 'method=disabled'
|
||||
} >"$TMP"
|
||||
sudo install -o root -g root -m 0600 "$TMP" "$CONN_FILE"
|
||||
rm -f "$TMP"
|
||||
sudo nmcli connection load "$CONN_FILE" >/dev/null
|
||||
echo " Profil $AP_CONNECTION $ACTION"
|
||||
|
||||
# Die Keyfiles werden erst in einer Temp-Datei aufgebaut und dann per "install"
|
||||
# an ihren Platz gelegt - dazwischen liegt das WLAN-Passwort in einer Datei, die
|
||||
# noch nicht 0600 root:root ist. Ein Abbruch (set -e) genau dort darf sie nicht
|
||||
# liegen lassen. Ein EXIT-Trap in write_profile() selbst waere dafuer untauglich:
|
||||
# die Funktion laeuft zweimal, der zweite trap ueberschriebe den ersten, und
|
||||
# "$tmp" ist "local" - beim Auswerten des Traps am Skriptende gaebe es die
|
||||
# Variable gar nicht mehr. Deshalb ein gemeinsames Verzeichnis mit genau einem
|
||||
# Trap darum herum (0700, mktemp -d legt es so an).
|
||||
PROFILE_TMPDIR=$(mktemp -d)
|
||||
trap 'rm -rf "$PROFILE_TMPDIR"' EXIT
|
||||
|
||||
# $1 = Profilname, $2 = SSID, $3 = PSK, $4 = "update" (bestehendes Profil neu schreiben,
|
||||
# UUID behalten) oder "keep" (bestehendes Profil unangetastet lassen).
|
||||
#
|
||||
# Beide Profile bekommen dieselbe Adresse 192.168.8.1/24 auf wlan0 - genau deshalb
|
||||
# schliessen sie sich gegenseitig aus, und genau deshalb prueft ninlight-station's
|
||||
# ap_switch() nach jedem Umschalten nach, dass wirklich nur eines aktiv ist.
|
||||
write_profile() {
|
||||
local id=$1 ssid=$2 psk=$3 mode=$4
|
||||
# Ein leerer Profilname wuerde eine Datei ".nmconnection" anlegen, eine leere SSID
|
||||
# oder ein leeres PSK ein Profil, das NetworkManager beim Hochfahren stumm ablehnt -
|
||||
# in beiden Faellen faellt es erst auf, wenn jemand mit einer toten Lampe vor der
|
||||
# Station steht. Der Konfigurationsschritt oben ergaenzt fehlende Schluessel aus der
|
||||
# Vorlage; leer sein koennen sie trotzdem (von Hand geleert).
|
||||
if [ -z "$id" ] || [ -z "$ssid" ] || [ -z "$psk" ]; then
|
||||
echo "Fehler: Profilname, SSID oder PSK ist leer - $CONF pruefen (Profil '$id')" >&2
|
||||
return 1
|
||||
fi
|
||||
local conn_file="$NM_DIR/${id}.nmconnection"
|
||||
local uuid action tmp
|
||||
if sudo test -f "$conn_file"; then
|
||||
if [ "$mode" = "keep" ]; then
|
||||
# Der Migrations-AP ist auf manchen Pis von Hand angelegt worden (SSID/PSK sind
|
||||
# von der alten Klyqa-Firmware fest vorgegeben, es gibt also nichts zu variieren).
|
||||
# Ein bestehendes Profil wird deshalb nicht ueberschrieben - es koennte
|
||||
# handjustierte Felder (Kanal, Band, Regulierungsdomaene) tragen, die dieses
|
||||
# Skript gar nicht kennt.
|
||||
echo " Profil $id existiert bereits - unveraendert gelassen"
|
||||
return 0
|
||||
fi
|
||||
uuid=$(sudo sed -n 's/^uuid=//p' "$conn_file" | head -n1)
|
||||
action="aktualisiert"
|
||||
else
|
||||
# /proc/sys/kernel/random/uuid gibt es nur unter Linux (dem Pi) - der Fallback
|
||||
# auf uuidgen ist es, was einen Testlauf gegen den lokalen Stub (macOS) ueberhaupt
|
||||
# ermoeglicht, aendert aber nichts am Verhalten auf dem Pi selbst.
|
||||
uuid=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || uuidgen | tr '[:upper:]' '[:lower:]')
|
||||
action="angelegt"
|
||||
fi
|
||||
tmp=$(mktemp "$PROFILE_TMPDIR/profile.XXXXXX")
|
||||
{
|
||||
printf '%s\n' '[connection]'
|
||||
printf 'id=%s\n' "$id"
|
||||
printf 'uuid=%s\n' "$uuid"
|
||||
printf '%s\n' 'type=wifi'
|
||||
printf '%s\n' 'interface-name=wlan0'
|
||||
printf '%s\n' 'autoconnect=false'
|
||||
printf '\n%s\n' '[wifi]'
|
||||
printf '%s\n' 'mode=ap'
|
||||
printf 'ssid=%s\n' "$ssid"
|
||||
printf '%s\n' 'band=bg'
|
||||
printf '%s\n' 'channel=6'
|
||||
printf '\n%s\n' '[wifi-security]'
|
||||
printf '%s\n' 'key-mgmt=wpa-psk'
|
||||
printf '%s\n' 'proto=rsn'
|
||||
printf '%s\n' 'pairwise=ccmp;'
|
||||
printf '%s\n' 'group=ccmp;'
|
||||
printf 'psk=%s\n' "$psk"
|
||||
printf '\n%s\n' '[ipv4]'
|
||||
printf '%s\n' 'method=shared'
|
||||
printf '%s\n' 'address1=192.168.8.1/24'
|
||||
printf '\n%s\n' '[ipv6]'
|
||||
printf '%s\n' 'method=disabled'
|
||||
} >"$tmp"
|
||||
sudo install -o root -g root -m 0600 "$tmp" "$conn_file"
|
||||
rm -f "$tmp"
|
||||
sudo nmcli connection load "$conn_file" >/dev/null
|
||||
echo " Profil $id $action"
|
||||
}
|
||||
|
||||
write_profile "$AP_CONNECTION" "$SSID" "$PSK" update
|
||||
# Ohne dieses zweite Profil scheitert "ninlight-station migrate on" auf einem frisch
|
||||
# eingerichteten Pi an need_profile "$MIGRATE_AP_CONNECTION".
|
||||
write_profile "$MIGRATE_AP_CONNECTION" "$MIGRATE_SSID" "$MIGRATE_PSK" keep
|
||||
REMOTE
|
||||
|
||||
cat <<'EOF'
|
||||
@@ -164,6 +254,11 @@ cat <<'EOF'
|
||||
Fertig. Die Station ist absichtlich noch AUS.
|
||||
|
||||
Naechste Schritte:
|
||||
ssh <pi-host> ninlight-station display test
|
||||
Testbild aufs E-Paper - bleibt es leer oder
|
||||
verzerrt, steht EPD_REVISION in
|
||||
/etc/ninlight-station.conf auf der falschen
|
||||
Panel-Revision (V2 statt V3 oder umgekehrt)
|
||||
tools/station/deploy.sh <pi-host> Images ausliefern
|
||||
ssh <pi-host> ninlight-station on Station einschalten
|
||||
ssh <pi-host> ninlight-station off wieder ausschalten (nicht vergessen)
|
||||
|
||||
Reference in New Issue
Block a user