feat(station): Umschalter fuer Migrationsimages mit/ohne WLAN-Daten
Die WLAN-Zugangsdaten stecken zur Bauzeit in der Firmware, es gibt also zwei Abzuege je Ziel. Die Station haelt jetzt beide vor - images/ migrate-nocreds (aus tools/build_nocreds.sh, Lampe startet im Onboarding-AP) und images/migrate-wlan (aus build/, Lampe haengt sofort im eigenen WLAN) - und der Bediener waehlt per Umschalter, welcher ausgeliefert wird: "ninlight-station bildsatz" oder der Menueeintrag am Taster. Der Zustand liegt in /var/lib/ninlight-station/imageset, ueberlebt Neustarts und steht dauerhaft in der Fusszeile des Panels - in jedem Modus, auch bei ausgeschalteter Station: man soll vor dem Einschalten sehen, was gilt. Vorgabe und Rueckfallwert in JEDEM Zweifelsfall ist "ohne-wlan". Im Automatikmodus wird jede Klyqa-Lampe in Reichweite migriert, auch fremde; ein Abzug ohne Zugangsdaten ist dann ein harmloser Fehlgriff, einer mit Zugangsdaten traegt das eigene WLAN-Passwort in ein fremdes Geraet. Fehlt der gewaehlte Satz (Verzeichnis weg oder leer), wird NICHTS ausgeliefert - kein stilles Ausweichen auf den anderen Satz. Der Server antwortet 404 mit Status "noimageset", CLI und Panel sagen "FEHLT". server.py und die Stations-Unit muessen mit: die Auswahl der ausgelieferten Datei ist genau die Aufgabe des Servers, und ein Symlink-Tausch haette "gewaehlt, aber nicht vorhanden" von "dieses Modell fehlt" nicht mehr unterscheidbar gemacht.
This commit is contained in:
@@ -58,6 +58,18 @@ SERVICE_NAME = os.environ.get("NINLIGHT_STATION_SERVICE", "ninlight-station")
|
||||
# RuntimeDirectory der Hauptunit und verschwindet beim regulaeren "off" - also
|
||||
# ausgerechnet dann, wenn das Menue "Rettung starten"/"Migration starten" anbietet.
|
||||
UI_FILE = Path(os.environ.get("NINLIGHT_UI_FILE", "/run/ninlight-button/ui.json"))
|
||||
# Der Umschalter "Migrationsimages mit oder ohne WLAN-Zugangsdaten" (siehe
|
||||
# ninlight-station, bildsatz_cmd, und server.py:read_image_set). NUR GELESEN, und
|
||||
# absichtlich aus derselben Datei wie der Server - nicht aus status.json: der Zustand
|
||||
# muss auch dann auf dem Panel stehen, wenn die Station AUS ist. Genau davor soll er
|
||||
# schuetzen ("man soll vor jedem Einschalten sehen, was gilt"), und ausgeschaltet
|
||||
# schreibt server.py nichts mehr.
|
||||
IMAGESET_FILE = Path(os.environ.get("NINLIGHT_IMAGESET_FILE",
|
||||
"/var/lib/ninlight-station/imageset"))
|
||||
# Dieselben Namen und dieselbe Vorgabe wie in server.py und im CLI - im Zweifel der
|
||||
# Satz OHNE Zugangsdaten (dort steht die Begruendung).
|
||||
IMAGESET_DIRS = {"ohne-wlan": "migrate-nocreds", "mit-wlan": "migrate-wlan"}
|
||||
IMAGESET_DEFAULT = "ohne-wlan"
|
||||
|
||||
POLL_INTERVAL_S = 5
|
||||
# Solange eine Bedienung laeuft, waere ein 5-Sekunden-Takt unbenutzbar (ein Tastendruck
|
||||
@@ -197,8 +209,37 @@ def _service_active(service_name: str) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def _read_imageset(imageset_path: Path, conf: dict[str, str]) -> tuple[str, bool]:
|
||||
"""(Name des eingestellten Bildsatzes, liegt er ueberhaupt auf dem Pi?).
|
||||
|
||||
Fehlt die Datei, ist sie unlesbar oder steht etwas Unbekanntes darin, gilt
|
||||
IMAGESET_DEFAULT - dieselbe Vorgabe wie in server.py und im CLI, sonst wuerde das
|
||||
Panel etwas anderes behaupten als der Server tut.
|
||||
|
||||
`ok` ist False, wenn der GEWAEHLTE Satz nicht auf dem Pi liegt - nach derselben
|
||||
Regel wie server.py:migrate_set_present(): Verzeichnis vorhanden UND mindestens ein
|
||||
Image darin. Dann liefert server.py nichts aus (Status "noimageset"), und genau das
|
||||
muss am Panel stehen, statt dass der Bediener vor einer Lampe steht, die nichts
|
||||
bekommt. Ohne bekanntes IMAGE_DIR (Konfigurationsdatei nicht lesbar) wird nichts
|
||||
behauptet: dann gilt "ok", denn eine unbelegte Warnung ist schlechter als keine."""
|
||||
try:
|
||||
value = imageset_path.read_text().strip()
|
||||
except OSError:
|
||||
value = ""
|
||||
name = value if value in IMAGESET_DIRS else IMAGESET_DEFAULT
|
||||
image_dir = conf.get("IMAGE_DIR")
|
||||
if not image_dir:
|
||||
return name, True
|
||||
set_dir = Path(image_dir) / IMAGESET_DIRS[name]
|
||||
try:
|
||||
return name, any(p.name.endswith(".img") for p in set_dir.iterdir())
|
||||
except OSError:
|
||||
return name, False
|
||||
|
||||
|
||||
def build_state(run_dir: Path = RUN_DIR, conf_path: Path = CONF_PATH,
|
||||
service_name: str = SERVICE_NAME) -> dict:
|
||||
service_name: str = SERVICE_NAME,
|
||||
imageset_path: Path = IMAGESET_FILE) -> dict:
|
||||
"""Baut den zusammengefuehrten Zustand, den render.lines()/compose() erwarten.
|
||||
|
||||
Modus kommt vorrangig aus RUN_DIR/mode (Vorgabe "recovery", wie
|
||||
@@ -211,14 +252,22 @@ def build_state(run_dir: Path = RUN_DIR, conf_path: Path = CONF_PATH,
|
||||
|
||||
Phase/armed_target kommen aus control.json, nur im Migrationsmodus massgeblich - der
|
||||
Rettungsmodus ignoriert control.json ebenso wie server.py das tut. "off" ueberschreibt
|
||||
alles, wenn der Hauptdienst gar nicht laeuft."""
|
||||
alles, wenn der Hauptdienst gar nicht laeuft.
|
||||
|
||||
Der Bildsatz ("imageset"/"imageset_ok") kommt in JEDEN Zustand, auch in "off" und in
|
||||
den Rettungsmodus: er ist ein dauerhafter Umschalter, und man soll ihn VOR dem
|
||||
Einschalten sehen - eine Einstellung von vorletzter Woche, die erst beim Blaettern im
|
||||
Menue auffaellt, hat genau den Zweck verfehlt."""
|
||||
conf = _read_conf(conf_path)
|
||||
imageset, imageset_ok = _read_imageset(imageset_path, conf)
|
||||
|
||||
if not _service_active(service_name):
|
||||
return {"mode": "off"}
|
||||
return {"mode": "off", "imageset": imageset, "imageset_ok": imageset_ok}
|
||||
|
||||
mode = _read_mode_file(run_dir)
|
||||
status = _read_json(run_dir / "status.json")
|
||||
conf = _read_conf(conf_path)
|
||||
state: dict = {"mode": mode, "served": status.get("served"), "recent": status.get("recent")}
|
||||
state: dict = {"mode": mode, "served": status.get("served"), "recent": status.get("recent"),
|
||||
"imageset": imageset, "imageset_ok": imageset_ok}
|
||||
|
||||
if mode == "recovery":
|
||||
state["ssid"] = conf.get("SSID")
|
||||
|
||||
Reference in New Issue
Block a user