Commit Graph
19 Commits
Author SHA1 Message Date
Michael SauerandClaude Sonnet 5 3032178247 fix: ninlight-station status gibt Dienstzustand nur einmal aus, Exit 0
"systemctl is-active" schreibt den Zustand ("inactive", ...) auf stdout
und liefert dabei einen Rueckgabewert ungleich 0 - der "|| echo inaktiv"-
Zweig lief also zusaetzlich mit, und beide Zeilen landeten in der
Ausgabe. Ausserdem endete "status" ueber den letzten Befehl im
case-Zweig faktisch mit dessen Exit-Code, der 1 war, sobald noch nie
ausgeliefert wurde (kein /run/ninlight-station/status.json) - das machte
"status" in jedem Skript, das den Exit-Code abfragt, unbrauchbar.

Beide Stellen behoben: der Dienstzustand wird nur noch einmal ausgegeben,
und der Zweig endet mit explizitem "exit 0", sobald die Auskunft erteilt
werden konnte.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 13:13:03 +02:00
Michael SauerandClaude Sonnet 5 4a8a5d658f fix: nl_mkimage wieder Python-3.8-tauglich machen
CMake ruft nl_mkimage.py bei jedem Firmware-Build ueber das plain
"python3" im Docker-Build-Container auf (Python 3.8.10), unabhaengig von
den Werkzeugen auf Mac/Pi, die Python >= 3.11 voraussetzen. Die zuletzt
hinzugekommene Annotation "expect_device: str | None" wird dort zur
Laufzeit ausgewertet und wirft "TypeError: unsupported operand type(s)
for |: 'type' and 'NoneType'", was jeden Firmware-Build abbrechen liess.

"from __future__ import annotations" (wie in tools/station/server.py)
sorgt dafuer, dass Annotationen nur noch als Text existieren und nie
ausgewertet werden - der Rest der Datei kam ohne weitere 3.8-Stolpersteine
aus.

test: zwei Regressionstests ergaenzt, die ohne installiertes Python 3.8
auskommen: einer prueft per AST, dass der future-Import direkt nach dem
Docstring steht, der andere laesst vermin (dev-Dependency) die
Mindestversion aus Syntax/Stdlib-Nutzung ableiten. vermins
"union-types"-Feature wird bewusst nicht aktiviert, weil es "str | None"
unabhaengig vom future-Import als 3.10-Anforderung meldet - das wuerde
ausgerechnet den korrekten Fix durchfallen lassen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 13:12:54 +02:00
Michael SauerandClaude Sonnet 5 d23b0d4e02 fix: Rechte-Bug und Passwort-Leck im NM-Profil, Loesch-Bug in deploy.sh
setup_pi.sh sourcte /etc/ninlight-station.conf (0600 root:root) als
unprivilegierter Benutzer und scheiterte damit bei jedem Lauf, bevor
das NetworkManager-Profil angelegt wurde. Die Konfiguration wird jetzt
per "sudo cat" gelesen; ninlight-station macht denselben Fallback fuer
den dokumentierten Aufruf als normaler Benutzer, ohne die Datei fuer
alle lesbar zu machen.

Das AP-Profil wird nicht mehr per "nmcli ... wifi-sec.psk $PSK"
angelegt (das WLAN-Passwort waere sonst fuer jeden lokalen Prozess in
der Kommandozeile sichtbar), sondern als NetworkManager-Keyfile
(0600 root:root) geschrieben und per "nmcli connection load"
eingelesen.

deploy.sh loeschte bei einem gezielten Deploy eines einzelnen
Geraetetyps per "rsync --delete" alle Images anderer Geraetetypen aus
dem live ausgelieferten Verzeichnis. --delete laeuft jetzt nur noch
beim Vollabgleich (kein target-Argument); ein gezielter Deploy ersetzt
nur die genannten Dateien. Dabei zusaetzlich -c (Checksumme statt
Groesse+Zeitstempel) ergaenzt, sonst uebersieht rsync ein neues Image
mit zufaellig gleicher Byte-Groesse. Ausserdem meldet deploy.sh jetzt
einen fehlgeschlagenen Dienst-Neustart als Fehler statt ihn mit "Dienst
laeuft nicht" zu verwechseln.

Stub-Tests unter tools/station/tests/test_deploy.sh (ssh/rsync im PATH
ersetzt, Installationsschritt laeuft gegen ein Sandbox-Verzeichnis)
decken beide Bugfixes sowie die bereits bestehenden Faelle ab: Image
besteht die Trailer-Pruefung nicht, keine Images gefunden.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 12:52:44 +02:00
Michael SauerandClaude Sonnet 5 fda732d6c4 feat: Aufsetz- und Ausliefer-Skripte der Recovery-Station
setup_pi.sh ist idempotent und laesst eine vorhandene Konfiguration in
Ruhe; deploy.sh prueft jedes Image mit nl_mkimage --verify, bevor es
etwas uebertraegt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 12:36:03 +02:00
Michael SauerandClaude Haiku 4.5 df8e796f1e fix: nl_mkimage Verifikation robuster gegen Edge Cases und Misuse
Critical 1: path.read_bytes() fiel auf nonexistent file oder Directory mit
Traceback statt einer Fehlerzeile. Jetzt OSError-Familie abgefangen.

Critical 2: --device/--version mit Leerzeichen wurden stillschweigend
akzeptiert; das zerlegt das Ausgabeformat und bricht Task 4s Skript.
Jetzt whitespace beim Bauen abgelehnt.

Important 1: Gerätefeld muss in C wie in Python null-terminiert sein.
Die Firmware checkt das unabhängig von --expect-device; Python nur mit
Flag. Jetzt Terminierung immer verlangt.

Important 2: Modi waren nicht exklusiv; --verify mit Baumodus-Argumenten
wurde stillschweigend ignoriert. Jetzt Fehler statt falschem Vertrauen.

Neue Tests für alle vier Szenarien + nonexistent file, directory, empty
file, Trailer ohne Payload, Mode-Mischung.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-09-12 12:30:42 +02:00
Michael SauerandClaude Haiku 4.5 0ba269a39a feat: nl_mkimage prüft Images mit --verify
deploy.sh soll kein Image auf die Station schieben, das jede Lampe
ablehnen würde. Die Prüfung sitzt in nl_mkimage, weil dort das
Trailer-Layout ohnehin steht - eine zweite Kopie derselben Offsets
wäre genau die Doppelung, die beim Abweichen jedes Update killt.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-09-12 12:25:02 +02:00
Michael SauerandClaude Sonnet 5 cb0a4b035d fix: Startaufruf absichern und Neustartschleife der Station deckeln
- 'on' fing bisher nur einen bereits laufenden, aber toten Dienst ab. Scheiterte
  schon der Aufruf "systemctl start" selbst (z.B. start-limit-hit nach einer
  Absturzserie durch Restart=always), beendete set -eu das Skript dort, bevor
  die Lebendigkeitspruefung je erreicht wurde - der eben hochgefahrene
  Zugangspunkt blieb offen ohne Server dahinter stehen. Der Startaufruf wird
  jetzt selbst abgefangen und faehrt im Fehlerfall dieselbe Rueckabwicklung wie
  die is-active-Pruefung. "systemctl reset-failed" vor dem Start verhindert,
  dass eine fruehere Absturzserie den naechsten ehrlichen Versuch blockiert.
- Stirbt der Dienst nach einem erfolgreichen Start, stoppt das Skript ihn jetzt
  explizit, bevor es den Zugangspunkt herunterfaehrt und "abgeschaltet" meldet -
  sonst haette ein mit Restart=always weiterlaufender Dienst im Hintergrund
  einen saubereren Zustand suggeriert, als tatsaechlich vorlag.
- ninlight-station.service setzt StartLimitIntervalSec/StartLimitBurst jetzt
  explizit statt sich stillschweigend auf systemds Vorgabewerte zu verlassen,
  mit Begruendung als Kommentar: ohne Zugangspunkt kann server.py nicht binden
  und stirbt sofort, Restart=always soll das nach kurzer Zeit ehrlich aufgeben
  statt das Journal zu fluten.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 12:21:26 +02:00
Michael SauerandClaude Sonnet 5 10cc67d490 fix: Fehlerbehandlung und Quoting der Recovery-Station nach Review
- 'on' prueft nach dem Start wirklich, ob der Dienst aktiv ist (Type=simple
  meldet sonst schon Erfolg, wenn der Prozess nur gestartet wurde), und faehrt
  den Zugangspunkt bei einem toten Server wieder herunter statt ihn offen mit
  bekanntem Passwort ohne Server dahinter stehen zu lassen.
- 'off' faengt einen fehlschlagenden Wechsel zurueck ins Heim-WLAN ab statt
  unter set -eu mit nmclis Rohausgabe abzubrechen.
- Fehlende Schluessel in der Konfiguration werden nach dem Sourcen explizit
  geprueft und mit einem Verweis auf setup_pi.sh gemeldet statt mit
  "parameter not set" abzubrechen.
- station.conf.example verwendet einfache statt doppelter Anfuehrungszeichen:
  POSIX ". datei" wertet $ und ` auch in doppelten Anfuehrungszeichen aus,
  ein Passwort mit "$1" waere sonst lautlos durch ein Shell-Argument ersetzt
  worden. systemds EnvironmentFile= fuehrt ohnehin nie eine Shell aus und
  entfernt einfache wie doppelte Anfuehrungszeichen gleichermassen.
- ninlight-station.service: BindsTo=NetworkManager.service entfernt (ein
  dadurch ausgeloester Stop gilt fuer systemd als sauber und wird von keiner
  Restart=-Politik erneut gestartet - ein NetworkManager-Neustart haette den
  Server sonst still und dauerhaft mitgerissen); stattdessen nur
  After=NetworkManager.service fuer die Startreihenfolge und Restart=always,
  damit der Prozess nach einem Absturz (z.B. Bind-Fehler) automatisch
  wiederkommt.
- Trailer-Reader im 'status'-Zweig nutzt jetzt errors="replace" wie
  trailer_version() in server.py, statt bei nicht-ASCII-Bytes im
  Versionsfeld abzustuerzen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 12:14:49 +02:00
Michael SauerandClaude Sonnet 5 d41c385375 feat: Konfiguration, systemd-Unit und CLI der Recovery-Station
Die Station ist bewusst nicht dauerhaft an - 'ninlight-station on'
schaltet Zugangspunkt und Dienst gemeinsam ein, 'off' schaltet wlan0
zurueck ins Heim-WLAN. Der Dienst laeuft als root (Port 80 und die
DHCP-Leases fuer 4c) und bekommt dafuer systemd-Einschraenkungen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 12:02:32 +02:00
Michael SauerandClaude Sonnet 5 a2e87b8855 fix: Nebenläufigkeit und verfrühtes "ok" in der Recovery-Station beheben
Fix-Runde 1 nach Review: Station.served und Station.recent wurden ohne
Lock von mehreren Handler-Threads (ein Thread pro Verbindung unter
ThreadingHTTPServer) mutiert - der Normalfall, wenn mehrere Lampen
gleichzeitig laden. Ein threading.RLock schuetzt jetzt Mutation und
Serialisierung nach status.json als eine Einheit.

Ausserdem hatte mein vorheriger Fix fuer das Status-Race einen neuen
Fehler eingefuehrt: "ok" wurde vor dem letzten wfile.write() vermerkt,
sodass ein danach scheiternder Schreibvorgang trotzdem als Erfolg in
status.json stand. record() laeuft jetzt erst, nachdem die Uebertragung
tatsaechlich abgeschlossen oder gescheitert ist; die betroffenen Tests
warten stattdessen auf den Eintrag statt sofortige Konsistenz
vorauszusetzen.

test_stall_after_stops_sending_but_keeps_the_connection prüfte bisher
nur die gesendete Bytezahl und haette einen faelschlich schliessenden
Server nicht bemerkt - ergaenzt um eine explizite Pruefung, dass ein
weiterer recv() in einen Timeout laeuft statt EOF zu liefern.

Neuer Test test_concurrent_downloads_are_all_recorded faehrt 20
gleichzeitige Downloads und prueft served/recent auf Vollstaendigkeit;
er schlaegt zuverlaessig fehl, wenn das Lock entfernt wird (lokal
gegengeprueft: 3 von 10 Laeufen ohne Lock verloren Eintraege, 10 von 10
mit Lock bestanden).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 11:58:18 +02:00
Michael SauerandClaude Sonnet 5 37f4dd425e feat: HTTP-Server der Recovery-Station
Liefert ausschliesslich Images aus, deren Name dem Muster
ninlight_<target>_ota.img entspricht, unterstuetzt Range-Anfragen fuer
den Wiederaufnahmeversuch der Firmware und schreibt seinen Zustand nach
status.json - die Schnittstelle fuer die E-Paper-Anzeige in 4c.

Behebt zwei Races aus der Referenzimplementierung des Task-Briefs: der
Server trug Erfolg/404 erst nach dem letzten gesendeten Byte in
status.json ein, wodurch ein Client, der die Antwort vollstaendig
gelesen hat, noch den alten Zustand sehen konnte. Ausserdem wurde ein
ganzer 32-KiB-Chunk auch dann komplett gesendet, wenn --stall-after
schon ueberschritten war; die Lesegroesse wird jetzt an die Stall-Grenze
gekappt, und ein Verbindungsabbruch waehrend des Stalls wird per
select/MSG_PEEK erkannt und als "aborted" vermerkt statt den Thread
endlos zu blockieren.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 11:45:05 +02:00
Michael SauerandClaude Opus 5 6588085679 fix: flash.sh akzeptiert den Port auch mit /dev/-Praefix
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 10:23:54 +02:00
Michael Sauer 4cd7fde0cd fix: ls-Zeile in build_all.sh statt Duplikat um ota.img erweitern 2026-09-12 01:23:33 +02:00
Michael Sauer fabca1a6b4 feat: OTA-Image mit Trailer im Build erzeugen 2026-09-12 01:23:03 +02:00
Michael Sauer 76b9c3fef1 test: nl_mkimage für OTA-Trailer-Erzeugung mit Tests anlegen
feat: nl_mkimage.py implementieren (Gegenstück zu nl_fwup_trailer.c aus Task 2)
2026-09-12 01:21:42 +02:00
Michael Sauer 64acc6b1d9 fix: Flash-Skript an EVK-Befund angepasst, Checkliste abgehakt
- Flash-Skript flasht jetzt das App-Image (ninlight_<dev>_gz.img) mit 460800 baud
- -rs none statt -rs rts, Reset-Taste manuell nötig
- Optionales Argument 'full' für Vollbild-Flash (blank chips nur)
- Checkliste Light und Persistenz als bestanden markiert, Testdatum notiert
2026-09-11 16:45:15 +02:00
Michael Sauer b986bb10b9 build: Build aller Geräte und README 2026-09-11 16:30:47 +02:00
Michael Sauer 1b0501179d build: W600-SDK importiert, Firmware-Build und Hello am EVK
Docker-Build mit gcc-arm-none-eabi 4.9-2014q4 (statt Homebrew-Cask) verwendet,
Dockerfile/tools/docker.sh dafür aus Task 13 vorgezogen. cmake/sdk.cmake schont
gcc 4.9 (kennt -fcommon/-Wno-error=... nicht). Zwei zusätzliche SDK-Stubs
(EarlyInit, pre_gpio_config) in src/sys/sdk_stubs.c ergänzt, siehe sdk/PATCHES.md.
2026-09-11 12:52:26 +02:00
Michael Sauer 4e7ff78fc6 test: Host-Testumgebung mit Unity, SHA256 und CRC 2026-09-11 12:23:04 +02:00