feat(station): optionaler Zeitschalter schaltet die Station selbst ab

"on"/"recovery on"/"migrate on" nehmen jetzt "--for <minuten>" (1-240): die
Station schaltet sich danach selbsttaetig ab und kehrt ins Heim-WLAN zurueck.
Das hilft, wenn man ueber WLAN angemeldet ist - "on" macht wlan0 zum
Zugangspunkt und reisst jede SSH-Sitzung ab.

Ausdruecklich optional: ohne "--for" entsteht kein Zeitschalter und alles
verhaelt sich exakt wie bisher. Dazu "renew [<minuten>]" (Uhr zurueckstellen),
"hold" (abbestellen, Station bleibt an) und eine Restzeit in "status"; "off"
raeumt einen laufenden Zeitschalter mit ab.

Umgesetzt als transienter systemd-Timer unter festem Unit-Namen (systemd-run),
Zustand in RUN_DIR/autooff.json. Vor dem Abschalten wird per "ss" geprueft, ob
noch eine Verbindung auf Port 80 aus dem Stationsnetz offen ist - dann wird um
rund eine Minute verschoben, hoechstens zehnmal (Begruendung im Kommentar).
Das Panel zeigt den Zeitschalter als eigene Zeile, aber nur wenn einer laeuft.
This commit is contained in:
Michael Sauer
2026-09-14 19:39:42 +02:00
parent f105a0eb69
commit 1f01b29b8e
5 changed files with 841 additions and 17 deletions
+375 -16
View File
@@ -22,6 +22,56 @@ CONF=${NINLIGHT_STATION_CONF:-/etc/ninlight-station.conf}
RUN_DIR=${NINLIGHT_RUN_DIR:-/run/ninlight-station}
STATION_LIB=${NINLIGHT_STATION_LIB:-/opt/ninlight-station}
# --- Zeitschalter ("Auto-Aus") -----------------------------------------------------
#
# Rein OPTIONAL: nur "--for <minuten>" legt einen an. Ohne die Angabe passiert hier
# nichts, und die Station verhaelt sich exakt wie vorher - der Regelfall ist der Pi am
# Kabel, dem niemand den Zugangspunkt unter der laufenden Arbeit wegschalten soll.
#
# Wozu er dann gut ist: "on" macht wlan0 zum Zugangspunkt und reisst damit JEDE
# SSH-Sitzung ueber WLAN ab. Wer den Pi nicht am Kabel hat, sperrt sich damit aus und
# kommt nur noch ueber Tastatur und Monitor zurueck. Der Zeitschalter schaltet die
# Station nach der angeforderten Zeit selbsttaetig ab (= zurueck ins Heim-WLAN).
#
# Umgesetzt mit einem transienten systemd-Timer unter FESTEM Unit-Namen (systemd-run
# --unit=...): so sind "renew"/"hold"/"off" schlichte systemctl-Aufrufe auf einen
# bekannten Namen, und es braucht keine zusaetzliche Unit-Datei im Repo.
AUTOOFF_UNIT=${NINLIGHT_AUTOOFF_UNIT:-ninlight-station-autooff}
# Eigene Zustandsdatei neben mode/control.json, im selben RUN_DIR und mit denselben
# Rechten (0644): sie ist die Quelle fuer "status", fuer "renew" ohne Argument (welche
# Dauer war zuletzt angefordert?) und fuer die Panelzeile - display.py liest sie. Sie ist
# zugleich die Antwort auf "laeuft ueberhaupt einer?": jeder Weg, der den Timer anlegt
# oder abraeumt, schreibt bzw. loescht sie in derselben Funktion.
AUTOOFF_FILE="$RUN_DIR/autooff.json"
# Abstand zwischen zwei Anlaeufen, wenn gerade ausgeliefert wird ("in etwa einer Minute
# erneut versuchen"). Der Timer bringt ihn selbst mit (--on-unit-inactive), es wird also
# nichts nachgelegt - siehe autooff_arm().
AUTOOFF_RETRY_S=${NINLIGHT_AUTOOFF_RETRY_S:-60}
# Obergrenze fuers Verschieben. 10 Anlaeufe x 1 min = rund 10 Minuten Aufschub.
#
# Begruendung der Zahl: eine Auslieferung ist ein einzelner HTTP-GET ueber ein halbes
# Megabyte im eigenen WLAN - Sekunden, keine Minuten. Eine Verbindung, die nach zehn
# Minuten noch offen steht, liefert nicht mehr aus, sie haengt (Lampe abgestuerzt,
# Funkloch, halboffener Socket, den der Kernel noch als ESTABLISHED fuehrt). Ohne
# Obergrenze wuerde genau so eine haengende Verbindung den Zeitschalter dauerhaft
# aushebeln - und damit ausgerechnet den Fall herbeifuehren, gegen den er angefordert
# wurde: der Nutzer sitzt ohne Kabel da und kommt nicht mehr an den Pi. Zehn Minuten
# sind lang genug, dass keine echte Auslieferung abgeschnitten wird, und kurz genug,
# dass der Zeitschalter seine Zusage noch einloest.
AUTOOFF_MAX_DEFERRALS=${NINLIGHT_AUTOOFF_MAX_DEFERRALS:-10}
# Das Netz des Zugangspunkts (server.py haengt auf 192.168.8.1:80) - daran wird erkannt,
# ob gerade eine Lampe laedt, ohne dafuer etwas am Server zu aendern.
AUTOOFF_STATION_NET=192.168.8.0/24
# Sinnvolle Grenzen fuer --for/renew.
AUTOOFF_MIN_MINUTES=1
AUTOOFF_MAX_MINUTES=240
# Absoluter Pfad auf dieses Skript - der Timer ruft es spaeter ohne unser $PWD auf.
case "$0" in
/*) SELF=$0 ;;
*) SELF=$(cd "$(dirname "$0")" && pwd)/$(basename "$0") ;;
esac
if [ -r "$CONF" ]; then
# shellcheck disable=SC1090
. "$CONF"
@@ -132,6 +182,180 @@ write_control() {
sudo mv -f "$_tmp" "$RUN_DIR/control.json"
}
# --- Zeitschalter: Zustandsdatei, Anlegen, Abraeumen, Pruefung -----------------------
# Prueft eine Minutenangabe, BEVOR irgendetwas eingeschaltet wird (Zusage: eine
# ungueltige Angabe schaltet gar nichts an). Gibt 0 zurueck, wenn sie taugt.
autooff_check_minutes() {
case "${1:-}" in
'' )
echo "Fehler: '--for' braucht eine Angabe in Minuten ($AUTOOFF_MIN_MINUTES-$AUTOOFF_MAX_MINUTES)" >&2
return 1
;;
*[!0-9]* )
echo "Fehler: '$1' ist keine Minutenangabe - erlaubt sind ganze Zahlen von $AUTOOFF_MIN_MINUTES bis $AUTOOFF_MAX_MINUTES" >&2
return 1
;;
esac
if [ "$1" -lt "$AUTOOFF_MIN_MINUTES" ] || [ "$1" -gt "$AUTOOFF_MAX_MINUTES" ]; then
echo "Fehler: $1 Minuten liegen ausserhalb von $AUTOOFF_MIN_MINUTES-$AUTOOFF_MAX_MINUTES" >&2
return 1
fi
}
# Schreibt $AUTOOFF_FILE atomar mit 0644 - dasselbe Muster und dieselben Rechte wie
# write_control(), damit display.py (laeuft als root) und das CLI (laeuft als normaler
# Benutzer) sie beide lesen koennen.
#
# $1 = angeforderte Dauer in Minuten (bleibt ueber Verschiebungen hinweg unveraendert -
# "renew" ohne Argument stellt genau darauf zurueck), $2 = Zeitpunkt des naechsten
# Anlaufs als Unix-Zeit, $3 = Zahl der bisherigen Verschiebungen.
autooff_write() {
sudo mkdir -p "$RUN_DIR"
_tmp="$RUN_DIR/.autooff-$$.json"
_json=$(printf '{"minutes": %s, "deadline": %s, "deferrals": %s}' "$1" "$2" "$3")
printf '%s' "$_json" | sudo tee "$_tmp" >/dev/null && sudo chmod 0644 "$_tmp" || return 1
sudo mv -f "$_tmp" "$RUN_DIR/autooff.json"
}
# Gibt "<minuten> <deadline> <verschiebungen>" aus, oder scheitert (Rueckgabewert 1),
# wenn kein Zeitschalter vorgemerkt ist bzw. die Datei unbrauchbar ist.
autooff_read() {
[ -e "$AUTOOFF_FILE" ] || return 1
python3 - "$AUTOOFF_FILE" <<'PY'
import json
import sys
try:
with open(sys.argv[1]) as f:
d = json.load(f)
except (OSError, ValueError):
sys.exit(1)
try:
print(int(d["minutes"]), int(d["deadline"]), int(d.get("deferrals", 0)))
except (KeyError, TypeError, ValueError):
sys.exit(1)
PY
}
# Haelt NUR den Timer an, nie die gleichnamige .service-Einheit: wenn diese Funktion aus
# dem gefeuerten Timer heraus laeuft (autooff_fire -> full_off), ist genau diese Einheit
# der eigene Prozess - ein "systemctl stop $AUTOOFF_UNIT" ohne Endung loeste sich zur
# .service auf und wuerde sich mitten im Abschalten selbst umbringen.
autooff_stop_unit() {
sudo systemctl stop "$AUTOOFF_UNIT.timer" >/dev/null 2>&1 || true
# Damit ein spaeteres systemd-run denselben Namen wieder vergeben kann, auch wenn ein
# frueherer Lauf im Zustand "failed" haengengeblieben ist.
sudo systemctl reset-failed "$AUTOOFF_UNIT.timer" "$AUTOOFF_UNIT.service" >/dev/null 2>&1 || true
}
# Timer anhalten UND die Vormerkung loeschen - "es laeuft keiner mehr".
autooff_clear() {
autooff_stop_unit
sudo rm -f "$AUTOOFF_FILE"
}
# Legt den Zeitschalter an (oder erneuert ihn).
# $1 = angeforderte Dauer in Minuten, $2 = Verzoegerung des naechsten Anlaufs in
# Sekunden, $3 = Zahl der bisherigen Verschiebungen.
#
# --on-unit-inactive sorgt dafuer, dass der Timer nach JEDEM Lauf der Aktion erneut
# feuert. Das ist die Verschiebung "in etwa einer Minute" (siehe autooff_fire): der
# gefeuerte Lauf kann sie nicht selbst nachlegen, weil er unter demselben Unit-Namen
# laeuft und systemd-run einen belegten Namen ablehnt. Wird nicht verschoben, endet der
# Lauf in full_off -> autooff_clear, und der Timer ist weg, bevor er ein zweites Mal
# feuern koennte.
autooff_arm() {
_req=$1
_delay=$2
_deferrals=$3
autooff_stop_unit
set -- --unit="$AUTOOFF_UNIT" \
--description="ninlight-station: Zeitschalter (Auto-Aus)" \
--on-active="${_delay}s" \
--on-unit-inactive="${AUTOOFF_RETRY_S}s"
# Die Testumgebung (tests/test_cli.sh) laeuft gegen eine Sandbox statt gegen
# /etc und /run; der spaeter gefeuerte Lauf haette davon sonst nichts. Im Betrieb
# sind diese Variablen nicht gesetzt und es wird nichts durchgereicht.
if [ -n "${NINLIGHT_STATION_CONF:-}" ]; then
set -- "$@" "--setenv=NINLIGHT_STATION_CONF=$NINLIGHT_STATION_CONF"
fi
if [ -n "${NINLIGHT_RUN_DIR:-}" ]; then
set -- "$@" "--setenv=NINLIGHT_RUN_DIR=$NINLIGHT_RUN_DIR"
fi
if [ -n "${NINLIGHT_STATION_LIB:-}" ]; then
set -- "$@" "--setenv=NINLIGHT_STATION_LIB=$NINLIGHT_STATION_LIB"
fi
if ! sudo systemd-run "$@" "$SELF" __autooff >/dev/null; then
echo "Fehler: Zeitschalter liess sich nicht anlegen (systemd-run '$AUTOOFF_UNIT') - die Station bleibt AN, 'ninlight-station off' nicht vergessen." >&2
return 1
fi
autooff_write "$_req" "$(( $(date +%s) + _delay ))" "$_deferrals" || {
echo "Fehler: $AUTOOFF_FILE konnte nicht geschrieben werden - Zeitschalter wieder abgeraeumt" >&2
autooff_clear
return 1
}
}
# Laeuft gerade eine Auslieferung? Geprueft an den offenen TCP-Verbindungen auf Port 80
# aus dem Stationsnetz - ohne jede Aenderung an server.py.
#
# "ss" liegt auf Debian in /usr/sbin; der gefeuerte Timer-Lauf bringt nicht zwingend
# denselben $PATH mit wie eine Anmeldesitzung, deshalb die beiden festen Pfade als
# Rueckfall.
autooff_busy() {
_ss=$(command -v ss 2>/dev/null || true)
if [ -z "$_ss" ]; then
for _cand in /usr/sbin/ss /sbin/ss; do
if [ -x "$_cand" ]; then
_ss=$_cand
break
fi
done
fi
if [ -z "$_ss" ]; then
# Nicht pruefbar ist nicht dasselbe wie "es laeuft etwas": waere das hier ein
# "ja, beschaeftigt", haette ein fehlendes ss den Zeitschalter bis zur
# Obergrenze ausgehebelt - und der Zeitschalter ist angefordert worden, WEIL der
# Nutzer sonst nicht mehr an den Pi kommt.
echo "Warnung: 'ss' nicht gefunden - laufende Auslieferungen konnten nicht geprueft werden" >&2
return 1
fi
_conns=$("$_ss" -Htn state established "( sport = :80 )" dst "$AUTOOFF_STATION_NET" 2>/dev/null | grep -c . || true)
[ "${_conns:-0}" -gt 0 ]
}
# Die Aktion des Timers. Absichtlich ein eigener (im Hilfetext nicht gefuehrter)
# Unterbefehl statt schlicht "off": das Verschieben bei laufender Auslieferung gehoert
# NUR hierher. Wer von Hand "off" tippt, will sofort aus - und nicht, dass ihm das CLI
# das Abschalten zehn Minuten lang verweigert. Ausgeschaltet wird am Ende trotzdem
# genau das, was "off" tut (full_off), nicht ein zweiter, aehnlicher Weg.
autooff_fire() {
if ! _info=$(autooff_read); then
# Niemand hat mehr einen Zeitschalter vorgemerkt (z.B. "hold" oder ein
# zwischenzeitliches "off") - dann hier nichts abschalten, nur den eigenen Timer
# abraeumen. Sonst faellt dieser Lauf in eine Station hinein, die laengst aus
# oder aus einem anderen Grund wieder an ist.
autooff_stop_unit
exit 0
fi
# Absichtliches Wortsplitting: autooff_read() liefert drei Zahlen, durch Leerzeichen getrennt.
# shellcheck disable=SC2086
set -- $_info
_req=$1
_deferrals=$3
if autooff_busy; then
if [ "$_deferrals" -lt "$AUTOOFF_MAX_DEFERRALS" ]; then
_deferrals=$((_deferrals + 1))
autooff_write "$_req" "$(( $(date +%s) + AUTOOFF_RETRY_S ))" "$_deferrals"
echo "Zeitschalter verschoben ($_deferrals/$AUTOOFF_MAX_DEFERRALS): eine Auslieferung laeuft noch"
exit 0
fi
echo "Warnung: $AUTOOFF_MAX_DEFERRALS Verschiebungen erreicht - es steht immer noch eine Verbindung offen, sie haengt vermutlich. Station wird jetzt trotzdem abgeschaltet." >&2
fi
full_off
}
# Startet (neu) und wartet kurz auf Lebenszeichen, dann prueft es nach, dass der
# eben gestartete Prozess wirklich den beabsichtigten Modus/die beabsichtigte
# Phase sieht.
@@ -213,7 +437,43 @@ PY
fi
}
# Liest "--for <minuten>" aus den Argumenten und legt die Dauer in $_for_minutes ab
# (leer = kein Zeitschalter, die Vorgabe). Wird VOR jedem Einschalten aufgerufen, damit
# eine ungueltige Angabe nichts anschaltet.
# $1 = das Kommando fuer die Fehlermeldung, Rest = die Argumente.
parse_for_option() {
_for_minutes=""
_for_seen=0
shift
while [ $# -gt 0 ]; do
case "$1" in
--for)
shift
_for_minutes=${1:-}
_for_seen=1
autooff_check_minutes "$_for_minutes" || exit 2
;;
*)
usage
exit 2
;;
esac
[ $# -gt 0 ] && shift || true
done
[ "$_for_seen" -eq 1 ] || _for_minutes=""
}
# Legt den angeforderten Zeitschalter an - NUR wenn "--for" dabei war. Ohne die Angabe
# passiert hier nichts: das ist die Vorgabe und der Grund, warum der Pi am Kabel von
# dieser Funktion nie etwas merkt.
autooff_arm_if_requested() {
[ -n "$_for_minutes" ] || return 0
autooff_arm "$_for_minutes" "$((_for_minutes * 60))" 0 || exit 1
echo "Zeitschalter: Auto-Aus in $_for_minutes min ('ninlight-station renew' verlaengert, 'hold' bestellt ab)"
}
recovery_on() {
parse_for_option recovery "$@"
need_profile "$AP_CONNECTION"
n=$(images | wc -l | tr -d ' ')
[ "$n" -gt 0 ] || echo "Warnung: $IMAGE_DIR enthaelt kein Image - die Station antwortet auf alles mit 404" >&2
@@ -229,17 +489,34 @@ recovery_on() {
echo "Warnung: PSK ist noch der oeffentliche Default 'ninlight-recovery' - eigenes Passwort in /etc/ninlight-station.conf setzen." >&2
fi
echo "Nicht vergessen: 'ninlight-station off', wenn du fertig bist."
autooff_arm_if_requested
}
migrate_on() {
# $1 = "--listen" oder leer.
# Argumente: "--listen" und/oder "--for <minuten>", beide optional und in beliebiger
# Reihenfolge. Beides wird VOLLSTAENDIG geprueft, bevor irgendein Profil hochkommt.
_phase=auto
if [ "${1:-}" = "--listen" ]; then
_phase=listen
elif [ -n "${1:-}" ]; then
usage
exit 2
fi
_for_minutes=""
_for_seen=0
while [ $# -gt 0 ]; do
case "$1" in
--listen)
_phase=listen
;;
--for)
shift
_for_minutes=${1:-}
_for_seen=1
autooff_check_minutes "$_for_minutes" || exit 2
;;
*)
usage
exit 2
;;
esac
[ $# -gt 0 ] && shift || true
done
[ "$_for_seen" -eq 1 ] || _for_minutes=""
need_profile "$MIGRATE_AP_CONNECTION"
n=$(migrate_images | wc -l | tr -d ' ')
[ "$n" -gt 0 ] || echo "Warnung: $IMAGE_DIR/migrate enthaelt keine Migrationsimages - die Station antwortet auf alles mit 404" >&2
@@ -255,6 +532,7 @@ migrate_on() {
echo "Migration an: SSID $MIGRATE_SSID auf 192.168.8.1, Phase $_phase, $n Migrationsimage(s)"
echo "Achtung: das Passwort dieses Zugangspunkts ist aus der alten Firmware oeffentlich bekannt - siehe README.md." >&2
echo "Nicht vergessen: 'ninlight-station off', wenn du fertig bist."
autooff_arm_if_requested
}
# Ruft display.py mit einem Schalter auf. EPD_REVISION steht in $CONF und wird von
@@ -348,6 +626,12 @@ display_off() {
}
full_off() {
# ZUERST: ein noch vorgemerkter Zeitschalter muss mit weg, sonst feuert er spaeter in
# eine Station hinein, die laengst aus ist - oder, schlimmer, in eine, die inzwischen
# jemand anders wieder eingeschaltet hat. Ganz vorne, damit auch ein spaeter
# scheiterndes "off" (haengender NetworkManager, siehe ap_switch) ihn nicht
# stehenlaesst.
autooff_clear
sudo systemctl stop ninlight-station 2>/dev/null || true
# VOR ap_switch, nicht danach: ap_switch "" bricht bei einem haengenden
# NetworkManager mit Fehler ab, und unter "set -e" kaeme danach nichts mehr. Genau
@@ -420,6 +704,55 @@ PY
echo "Freigegeben: $_target"
}
# "renew [<minuten>]" - die Uhr zurueckstellen. Ohne Argument auf dieselbe Dauer, die
# zuletzt angefordert wurde (nicht auf die Restzeit und auch nicht auf eine Vorgabe).
#
# Laeuft keiner, ist das ein Fehler und AUSDRUECKLICH kein stillschweigendes Neuanlegen:
# "renew" auf einem Pi am Kabel wuerde sonst genau den Zeitschalter herbeifuehren, den
# der Nutzer nie wollte.
renew_cmd() {
_arg=${1:-}
if ! _info=$(autooff_read); then
echo "Fehler: kein Zeitschalter aktiv - 'renew' stellt nur einen laufenden zurueck. Einen neuen gibt es nur mit 'ninlight-station on --for <minuten>'." >&2
exit 1
fi
# Absichtliches Wortsplitting: autooff_read() liefert drei Zahlen, durch Leerzeichen getrennt.
# shellcheck disable=SC2086
set -- $_info
_minutes=$1
if [ -n "$_arg" ]; then
autooff_check_minutes "$_arg" || exit 2
_minutes=$_arg
fi
autooff_arm "$_minutes" "$((_minutes * 60))" 0 || exit 1
echo "Zeitschalter zurueckgestellt: Auto-Aus in $_minutes min"
}
# "hold" - den Zeitschalter abbestellen. Die Station bleibt an, bis jemand "off" sagt.
hold_cmd() {
if ! autooff_read >/dev/null; then
echo "Fehler: kein Zeitschalter aktiv - es gibt nichts abzubestellen." >&2
exit 1
fi
autooff_clear
echo "Zeitschalter abbestellt - die Station bleibt an, bis 'ninlight-station off'."
}
# Restzeit fuer "status", oder leer, wenn kein Zeitschalter laeuft (dann steht in der
# Ausgabe gar nichts davon - eine Zeile "kein Zeitschalter aktiv" kostet nur Platz).
autooff_status_line() {
_info=$(autooff_read) || return 0
# Absichtliches Wortsplitting: autooff_read() liefert drei Zahlen, durch Leerzeichen getrennt.
# shellcheck disable=SC2086
set -- $_info
_rest=$(( ($2 - $(date +%s) + 59) / 60 ))
if [ "$_rest" -gt 0 ]; then
printf 'Auto-Aus: in %s min (angefordert: %s min)\n' "$_rest" "$1"
else
printf 'Auto-Aus: steht an (angefordert: %s min)\n' "$1"
fi
}
status_cmd() {
# "systemctl is-active" schreibt den Zustand ("inactive", "failed", ...) auf stdout und
# liefert dabei einen Rueckgabewert ungleich 0, sobald der Dienst nicht "active" ist -
@@ -513,6 +846,7 @@ PY
printf 'Phase : %s\n' "$_phase"
printf 'Erkannt: %s\n' "$_erkannt"
printf 'Dienst : %s\n' "$(systemctl is-active ninlight-station 2>/dev/null || true)"
autooff_status_line
printf 'wlan0 : %s\n' "$(nmcli -t -f GENERAL.CONNECTION device show wlan0 2>/dev/null | cut -d: -f2)"
printf 'Adresse: %s\n' "$(ip -4 -br addr show wlan0 2>/dev/null | awk '{print $3}')"
echo "Images :"
@@ -547,8 +881,16 @@ display_cmd() {
}
usage() {
echo "Aufruf: ninlight-station on|recovery {on|off}|migrate {on [--listen]|off}|arm [<target>]|listen|display {test|clear|off}|off|status" >&2
echo "Aufruf: ninlight-station on [--for <minuten>]|recovery {on [--for <minuten>]|off}|migrate {on [--listen] [--for <minuten>]|off}|arm [<target>]|listen|renew [<minuten>]|hold|display {test|clear|off}|off|status" >&2
echo "" >&2
echo " --for <minuten> Zeitschalter: die Station schaltet sich nach <minuten> selbst" >&2
echo " wieder aus (zurueck ins Heim-WLAN). OPTIONAL - ohne '--for'" >&2
echo " bleibt sie an, bis jemand 'off' sagt. Nuetzlich, wenn du ueber" >&2
echo " WLAN angemeldet bist: 'on' macht wlan0 zum Zugangspunkt und" >&2
echo " reisst die SSH-Sitzung ab. Erlaubt: 1-240." >&2
echo " renew [<min>] Uhr zurueckstellen (ohne Angabe auf die zuletzt angeforderte" >&2
echo " Dauer). Laeuft keiner, ist das ein Fehler." >&2
echo " hold Zeitschalter abbestellen, Station bleibt an." >&2
echo " display test Testbild aufs Panel zeichnen. Das ist der erste Handgriff bei der" >&2
echo " Hardware-Abnahme und die EINZIGE Moeglichkeit, die Panel-Revision" >&2
echo " zu bestimmen: die steht am Geraet nirgends. Bleibt das Bild leer," >&2
@@ -562,32 +904,49 @@ usage() {
echo " display off Anzeigedienst stoppen und das Panel leeren (macht 'off' mit)." >&2
}
case "${1:-}" in
_cmd=${1:-}
[ $# -gt 0 ] && shift || true
case "$_cmd" in
on)
recovery_on
recovery_on "$@"
;;
recovery)
case "${2:-}" in
on) recovery_on ;;
_sub=${1:-}
[ $# -gt 0 ] && shift || true
case "$_sub" in
on) recovery_on "$@" ;;
off) full_off ;;
*) usage; exit 2 ;;
esac
;;
migrate)
case "${2:-}" in
on) migrate_on "${3:-}" ;;
_sub=${1:-}
[ $# -gt 0 ] && shift || true
case "$_sub" in
on) migrate_on "$@" ;;
off) full_off ;;
*) usage; exit 2 ;;
esac
;;
arm)
arm_cmd "${2:-}"
arm_cmd "${1:-}"
;;
listen)
listen_cmd
;;
renew)
renew_cmd "${1:-}"
;;
hold)
hold_cmd
;;
__autooff)
# Nicht im Hilfetext: das ist die Aktion des Zeitschalters, kein Handgriff fuer
# Menschen (siehe autooff_fire).
autooff_fire
;;
display)
display_cmd "${2:-}"
display_cmd "${1:-}"
;;
off)
full_off