Commit Graph
78 Commits
Author SHA1 Message Date
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 76a3253322 docs: Recovery am EVK gegen die Pi-Station verifiziert
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 11:24:18 +02:00
Michael SauerandClaude Opus 5 37a5fe2ca9 fix: uebersprungenes Recovery-Image ist kein Fehler mehr
Lehnte die Recovery ein Image ab, weil es bereits die laufende Version
traegt, meldete die Firmware das als last_ota_error=trailer. Wer eine
Lampe im Feld ausliest, haette dort einen Trailer-Fehler gesehen, wo in
Wahrheit nichts passiert ist - und die Meldung in der Konsole lautete
"update failed". Das bewusste Ueberspringen hat jetzt einen eigenen
Zustand (nl_ota_skipped()), setzt keinen Fehlerwert und wird als
"image already up to date" protokolliert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
v0.3.4
2026-09-12 11:22:17 +02:00
Michael SauerandClaude Opus 5 c5d76efb25 docs: auth_all-Ergebnis und die beiden offenen Punkte der EVK-Runde vermerken
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 11:00:50 +02:00
Michael SauerandClaude Opus 5 5e2dff375e fix: abgewiesener fw_update verdirbt last_ota_error des laufenden Updates nicht mehr
Ein zweiter fw_update waehrend eines laufenden Downloads setzte s_error
auf "busy". Da ota_task() nur bei leerem Fehlerfeld auf "download"
zurueckfaellt, wurde ein spaeter gescheiterter Transfer danach als
"busy" gemeldet statt als "download". Die Abweisung hat kein eigenes
Ergebnis - sie wird der API mit 409 gemeldet, last_ota_error bleibt beim
Ergebnis des laufenden bzw. letzten Updates.

Dazu die Doku auf das am EVK gemessene Verhalten korrigiert: stirbt die
Gegenstelle mitten im Download, haengt der Lesevorgang rund sieben
Minuten in einem TCP-Timeout und bricht ohne Range-Resume ab.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
v0.3.3
2026-09-12 10:58:09 +02:00
Michael SauerandClaude Opus 5 6adf22fb52 docs: EVK-Ergebnisse der OTA-Runde eintragen
Sieben Punkte bestanden. Der zweite fw_update nach einer Ablehnung ging
erst nach dem SHA256-Kontext-Fix. Die einmalige lwIP-pbuf-Assertion beim
verfälschten Image ist als offene Beobachtung vermerkt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 10:42:09 +02:00
Michael SauerandClaude Opus 5 d56f1d25d2 fix: SHA256-Kontext pro Image initialisieren statt einmal beim Task-Start
Der fwup-Task überlebt jede Update-Sitzung. Weil nl_sha256_init() am
Taskanfang stand, benutzte ab dem zweiten Update-Versuch pro Boot ein
bereits finalisierter Kontext weiter - der Trailer-Hash konnte dann gar
nicht mehr passen und jedes weitere Update scheiterte mit
last_ota_error=hash. Am EVK gefunden: nach einer Ablehnung war ein
Reboot nötig, bevor überhaupt wieder ein Update gelingen konnte.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
v0.3.2
2026-09-12 10:38:01 +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 3841a3473c docs: Recovery-Zeitobergrenze, aktiven Boot-Scan und offene Pruefpunkte nachziehen
sdk/PATCHES.md, README.md und docs/hardware-checklist.md beschrieben teils
einen ueberholten Stand: die Watchdog-Einschaetzung in PATCHES.md Punkt 4
stammte noch von vor dem Drei-Heartbeat-Gate in app_main.c und vor der
Zeitobergrenze in nl_recovery.c; README behauptete eine harte Obergrenze
ueber dem gesamten Download statt nur ueber einem echten Stillstand; der
Boot-Scan ist aktiv (~3 s), nicht passiv wie an einer Stelle der Spec
beschrieben (Spec bleibt unveraendert). PATCHES.md Punkt 1 ergaenzt den
sys/nl_ota.h-Include in wm_fwup.c, Punkt 5 den fls_lock-printf.

hardware-checklist.md: Recovery-Zeile auf "Gegenstelle sendet nichts mehr"
praezisiert; die beiden Werksreset-Haken aus Plan 1 zurueckgesetzt, weil der
vorgelagerte Recovery-Scan das Boot-Timing verschiebt und sie neu geprueft
werden muessen; acht neue Pruefpunkte fuer Plan 3 ergaenzt (Zweitversuch
nach Ablehnung, REST-Erreichbarkeit waehrend/nach Ablehnung, Lichtverhalten
ueber OTA mit pob=off/on, fw_version gegen Trailer, echter Rettungsfall,
Rettungsstation nach Erfolg angelassen, AP-Modus + auth_all) sowie ein
Betriebshinweis zu NINLIGHT_RECOVERY_PASSWORD/NINLIGHT_TOKEN fuer den
Flottenbau.
v0.3.1
2026-09-12 03:27:33 +02:00
Michael Sauer 49d6b329b0 fix: printf statt wegkompilierter TLS_DBGPRT_ERR-Meldung bei fls_lock-Fehler
Wie beim s_fls2_lock-Fehlerpfad (T1, Plan 2) blieb auch ein Fehlschlag beim
Anlegen des primaeren fls_lock-Semaphors unsichtbar, weil TLS_DBGPRT_ERR in
diesem Build wegkompiliert ist. tls_fls_init() haette TLS_FLS_STATUS_ENOMEM
stumm zurueckgegeben. Ergaenzt außerdem den Zeitobergrenzen-Kommentar in
nl_recovery.c: die Obergrenze schuetzt nur gegen einen echten Stillstand,
nicht gegen einen langsamen, aber laufenden Download - wm_fwup.c bedient den
Watchdog bei jeder empfangenen Nachricht selbst.
2026-09-12 03:27:13 +02:00
Michael Sauer f434c013e7 build: Build-Datum zusammen mit der Version bei jedem Build erzeugen
NINLIGHT_BUILD_DATE entstand als string(TIMESTAMP) zur Konfigurationszeit,
während die Version seit bf8f76e bei jedem Build neu geschrieben wird -
system/info.build_date konnte dadurch tagealt sein. Das Datum wandert in
cmake/write_version.cmake und damit in denselben generierten Header wie die
Version.
2026-09-12 03:15:01 +02:00
Michael Sauer a400cc1649 fix: CRC im Settings-Record dort schreiben, wo der Leser sie erwartet
nl_settings_save() rechnete die CRC über [0, offsetof(rec_t, crc)) und legte sie
dort ab, read_rec() liest sie bei sizeof(rec_hdr_t) + hdr.length. Beide Offsets
stimmen nur überein, solange sizeof(nl_settings_t) ein Vielfaches von 4 ist
(aktuell 204). Ein zusätzliches uint16_t - in Plan 4 (Migration) sehr
wahrscheinlich - macht 206, und jeder gespeicherte Record fiele beim nächsten
Boot still durch die CRC: WLAN, Token und Name wären auf allen Lampen weg.

Geschrieben wird jetzt explizit bei NL_SETTINGS_CRC_OFF(length), also exakt an
der Leseposition; dazu eine Übersetzungszeit-Zusicherung, dass die Nutzdaten
direkt hinter dem Header beginnen (die CRC läuft auf beiden Seiten über einen
zusammenhängenden Block).

Test: test_crc_offset_matches_reader prüft die Symmetrie unabhängig von der
Strukturgröße. Gegenprobe mit einem zusätzlichen uint16_t im Struct: mit der
alten Schreibposition fallen vier Settings-Tests, mit der neuen keiner.
2026-09-12 03:14:31 +02:00
Michael Sauer 8af58d0124 fix: HTTP-Server-Heartbeat auch im Fehlerpfad ticken
Schlägt socket(), bind() oder listen() fehl, schlief die äußere Schleife eine
Sekunde und versuchte es erneut, ohne nl_http_beat zu erhöhen. Da app_task den
Watchdog nur füttert, wenn alle drei Heartbeats vorankommen, resettete der Chip
nach 20 s, bootete, scheiterte wieder - dauerhaft. Der Retry selbst ist die
Fehlerbehandlung, nicht der Reboot.
2026-09-12 03:14:31 +02:00
Michael Sauer cf1e6a351c fix: Recovery-Rettungsweg gegen Endlosschleife, Werksreset und Fremdnetz absichern
C1: nl_recovery_try() verglich nichts mit der laufenden Firmware. Vor einer
laufenden Rettungsstation - laut Spec 9 der Normalfall, solange eine Charge
migriert - lud die Lampe bei jedem Boot dasselbe Image erneut und startete neu.
nl_ota_start_recovery() setzt jetzt ein Flag, das nl_fwup_verify_tail() nur im
Recovery-Pfad auswerten lässt: Trägt der Trailer dieselbe Version wie die
laufende Firmware (nl_trailer_version(), bisher ungenutzt), wird das Image
abgelehnt und der Boot läuft normal weiter. Ein absichtliches Neuflashen
derselben Version über die REST-API bleibt möglich. Kein neuer Wert im
last_ota_error-Vokabular (Spec 8.2); unterschieden wird über die Konsole.

C1/I2: boot_ms wurde erst nach nl_recovery_try() gesetzt und der
Power-Cycle-Zähler erst 5 s danach gelöscht - bei erfolgreicher Recovery also
nie. Nach NL_FACTORY_RESET_BOOTS Runden folgte ein Werksreset. boot_ms startet
jetzt am Anfang von app_task (damit gilt wieder das in Spec 3.4/5.5 zugesagte
5-s-Fenster statt faktisch ~8 s), und nl_app_clear_reset_counter() löscht den
Zähler zusätzlich, sobald die Rettungsstation gefunden ist.

I3: tls_sys_auto_mode_run() stößt direkt nach UserMain() einen SDK-eigenen
Verbindungsversuch an; das dabei entstehende NETIF_IP_NET_UP war in
recovery_join_event() nicht vom eigenen Join zu unterscheiden, die Lampe hätte
im falschen Netz geladen. nl_wifi_disable_sdk_auto_connect() schaltet das Flag
ab und kappt eine bestehende Verbindung jetzt am Anfang von nl_recovery_try();
zusätzlich prüft nl_wifi_join_blocking() über tls_wifi_get_current_bss(), dass
die Verbindung wirklich zur erwarteten SSID gehört.
2026-09-12 03:14:16 +02:00
Michael Sauer 1822014e48 feat: Licht nach einem OTA wie bei power_on_behaviour=last wiederherstellen
Spec 8.2 verlangt, dass ein durch ein Update ausgelöster Neustart nicht wie ein
Stromausfall wirkt: Das Flag ota_pending wurde bisher nur geschrieben und beim
nächsten Boot wieder gelöscht, nl_boot_state() wendet aber weiterhin das normale
power_on_behaviour an. Eine eingeschaltete Lampe mit pob=off kam nach einem Update
aus zurück, mit pob=on umgekehrt.

nl_boot_state() bekommt dafür ein Argument after_ota; ist ein gespeicherter
Zustand vorhanden, wird er unverändert übernommen. Ohne gespeicherten Zustand
gelten weiterhin die normalen Regeln.

Hosttests: vier neue Fälle in test_boot_state.c (pob=off/on über ein OTA,
Sanitisierung bleibt aktiv, kein gespeicherter Zustand).
2026-09-12 03:13:36 +02:00
Michael Sauer b601ddeb9b fix: Fix-Runde 1 für Task 7 - busy-Beschreibung korrigieren, fw_update-Fehlerantwort dokumentieren 2026-09-12 02:51:08 +02:00
Michael Sauer 682235f3ab docs: OTA und Recovery dokumentieren 2026-09-12 02:45:27 +02:00
Michael Sauer 247d610fe8 fix: Recovery-Rettungsweg härten (Watchdog-Cap, Konsole, Softap-Bit, Scan-Retry) 2026-09-12 02:38:02 +02:00
Michael Sauer 8fa4af7eb5 feat: Recovery-Download beim Booten 2026-09-12 02:24:58 +02:00
Michael Sauer 7474cd77e1 fix: Fix-Runde 3 für Task 4 - WLAN-Aussetzer bleibt resumable statt sofortigem Abbruch 2026-09-12 02:11:56 +02:00
Michael Sauer 2d5095574d fix: Fix-Runde 2 für Task 4 - Ablehnung sofort statt nach voller Resume-Schleife, Reset-Guard auch bei OOM greifen lassen 2026-09-12 02:04:58 +02:00
Michael Sauer 37c05d2f90 fix: Fix-Runde 1 für Task 4 - Reset nur bei akzeptiertem OTA-Image, hängende Downloads/Sessions begrenzen 2026-09-12 01:56:58 +02:00
Michael Sauer aa8151c3d6 feat: OTA über HTTP mit Trailer-Prüfung 2026-09-12 01:36:57 +02:00
Michael Sauer ebee9e8e61 fix: Versions-Regex in run_mkimage.cmake an NINLIGHT_FW_VERSION verankern
Vorher traf die Regex das erste Stringliteral im generierten Header, unabhängig
vom Makronamen. Zusätzlich bricht ein leerer Regex-Treffer jetzt hörbar ab,
statt eine leere Version stillschweigend in den OTA-Trailer zu schreiben.
2026-09-12 01:28:27 +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 e3be3e84c1 feat: OTA-Trailer bauen und prüfen 2026-09-12 01:16:04 +02:00
Michael Sauer c96e0442c0 fix: fehlende Felder in gekürzten Settings-Records auf echte Defaults setzen
read_rec() füllte den Puffer bisher mit memset(0) statt mit
nl_settings_defaults() vor, bevor die tatsächlich vorhandenen hdr.length
Bytes darübergeschrieben werden. Ein gekürzter Record, dessen Länge vor
fade_in_ms/fade_out_ms endet, aber token noch enthält, lief damit mit
fade_in_ms=0/fade_out_ms=0 statt der spezifizierten 500 ms - genau der Fall,
für den die Längentoleranz gedacht war. Die davor eingebaute "leerer Record"
Sonderbehandlung in nl_settings_load() (fade_in_ms==0 && fade_out_ms==0 &&
token[0]=='\0' -> volle Defaults) ist dadurch überflüssig geworden und
entfernt.

test_load_accepts_shorter_record kürzt jetzt bis vor fade_in_ms und prüft
explizit auf die echten Defaults (500/500/0) statt nur auf Felder vor dem
gekürzten Bereich. test_load_rejects_longer_record verzichtet auf die tote
CRC-Neuberechnung, da read_rec() zu lange Records schon anhand der Länge
verwirft, bevor Payload oder CRC gelesen werden.
2026-09-12 01:11:18 +02:00
Michael Sauer 8805aa764d fix: Härtungen aus dem Plan-2-Review
- wm_internal_fls.c: TLS_DBGPRT_ERR ist in diesem Build wegkompiliert, daher
  printf() für die fehlgeschlagene s_fls2_lock-Anlage; PATCHES.md korrigiert.
- nl_wifi_w600.c: der SoftAP-Backstop (Zerstören eines unerwarteten SDK-Softap)
  lief bisher innerhalb von lock()/unlock() - tls_wifi_softap_destroy() und
  clear_softap_param() greifen aber auf SDK-Zustand zu, nicht auf s_sm/die
  Credential-Puffer, die s_lock eigentlich schützt. Jetzt wird im Lock nur noch
  ein lokales Flag gesetzt, die SDK-Aufrufe laufen danach.
- clear_softap_param() liest TLS_PARAM_ID_WPROTOCOL jetzt aus SRAM (FALSE)
  statt Flash (TRUE) - das Schreiben bleibt weiterhin geflasht.
- app_main.c füttert den Watchdog erst, wenn Licht-, Netz- und HTTP-Task
  jeweils seit dem letzten Feed einen Fortschritt gemeldet haben. Dafür
  bekommen nl_wifi_w600.c (nl_net_beat) und nl_http_server.c (nl_http_beat)
  je einen volatile Heartbeat-Zähler; der HTTP-Listen-Socket bekommt ein
  SO_RCVTIMEO von 2 s auf accept(), damit er auch ohne Anfragen tickt.
2026-09-12 00:59:40 +02:00
Michael Sauer e5145626b0 fix: reset_reason aus system/info entfernen
Das Feld lieferte ohnehin nur den festen String "unknown" - kein SDK-Aufruf
lieferte je einen echten Reset-Grund. README/hardware-checklist erwähnten
es nicht.
2026-09-12 00:57:08 +02:00
Michael Sauer bf8f76ecc3 build: Firmware-Version bei jedem Build neu erzeugen
NINLIGHT_FW_VERSION kam bisher aus einer configure-Zeit-Variable, die nur bei
erneutem CMake-Lauf aktualisiert wurde - nach einem Commit ohne Re-Konfiguration
zeigte Banner/system/info also eine veraltete Git-Version. cmake/write_version.cmake
schreibt build/generated/nl_version.h bei jedem Build frisch (add_custom_target ALL),
app_main.c/nl_app_api.c/nl_mdns.c inkludieren das Makro jetzt von dort statt aus
target_compile_definitions.
2026-09-12 00:56:38 +02:00
Michael Sauer 6e9fb09455 test: Settings-Format längentolerant machen, ota_pending ergänzen
read_rec() akzeptiert jetzt Records mit hdr.length <= sizeof(nl_settings_t):
CRC wird nur über die tatsächlich geschriebenen Bytes geprüft, fehlende Felder
bleiben auf 0 (Default). Ein zu langer (unbekannter) Record wird weiter
abgelehnt. Ein durch massive Kürzung "leer" gelesener Record (fade_in_ms,
fade_out_ms und token allesamt 0) fällt komplett auf die Defaults zurück.
Neues Feld ota_pending (nach fade_out_ms) für den kommenden OTA-Ablauf.
2026-09-12 00:55:20 +02:00
Michael Sauer ed374fc490 docs: Netzwerk-Checkliste nach EVK-Session 2026-09-12 00:32:42 +02:00
Michael Sauer 6754cdfb66 fix: RSSI mit Vorzeichen melden 2026-09-12 00:32:37 +02:00
Michael Sauer aae7a7462d docs: Checkliste Netzwerk ergänzt
M9: Hardware-Checkliste um Einträge für C1 (verschachteltes JSON, 400,
Lampe läuft weiter), C2 (Onboarding, 60 s warten, Reboot + Router-Neustart
-> AP darf nicht wiederkommen) und einen Hinweis auf den Build ohne
NINLIGHT_WIFI_SSID für Onboarding-Tests ergänzt. `bri 30`-Eintrag nennt
jetzt explizit den REST-Weg (`{"brightness":30}`) neben der Konsole.

README: klargestellt, dass der Onboarding-Aufruf ohne Authorization-Header
nur im unkonfigurierten AP funktioniert - im Fallback-AP (Zugangsdaten
bereits vorhanden) ist der Token-Header wie bei jedem anderen Endpunkt
erforderlich.
2026-09-11 23:52:18 +02:00
Michael Sauer 2e90b8fd06 fix: kleinere Netzwerk-Härtungen
M2: WLAN-Passwort muss leer (offenes Netz) oder 8..64 Zeichen lang sein
(400 "invalid password" bei 1..7 Zeichen). CMake prüft NINLIGHT_WIFI_PASSWORD
ebenso (0 oder 8..63 Zeichen) und der Zeichen-Blacklist-Check bekommt `;`
dazu.

M4: Ein Authorization-Wert, der länger als NL_TOKEN_LEN-1 (64) ist, wird
von nl_http_parse() nicht mehr stillschweigend abgeschnitten, sondern setzt
`auth_too_long` in nl_http_request_t; nl_api_handle() behandelt eine solche
Anfrage als unauthentifiziert (außer bei offenem Zugriff), damit ein
abgeschnittener Wert nie zufällig auf ein kürzeres Token passt.

M5: get_int() prüfte die (long)-Cast-Gleichheit vor dem Bereichscheck -
bei sehr großen oder NaN-Werten ist der Cast von double nach long
undefiniertes Verhalten. Bereichs- und NaN-Check laufen jetzt vor dem Cast.

T1: sdk/Platform/Drivers/internalflash/wm_internal_fls.c: tls_fls_init()
ignorierte den Rückgabewert von tls_os_sem_create(&s_fls2_lock, 1) - jetzt
eine TLS_DBGPRT_ERR-Meldung, falls das fehlschlägt (dokumentiert in
sdk/PATCHES.md).

Tests: test_api.c (Passwortlängen, auth_too_long, 1e300-Brightness),
test_http.c (auth_too_long wird gesetzt). Für M5 zeigt der Host-Test kein
sichtbares RED, weil der (double)->(long)-Cast auf dieser Plattform/diesem
Compiler keinen Crash auslöst (implementation-defined statt UB-Crash) - der
Fix wird trotzdem angewendet, da er auf dem Zielcompiler (arm-none-eabi 4.9)
nicht garantiert sicher ist.
2026-09-11 23:52:03 +02:00
Michael Sauer d098623121 fix: SDK-SoftAP-Parameter löschen
C2: tls_wifi_softap_create() setzt das SOFTAP-Bit (IEEE80211_MODE_AP = 4)
in TLS_PARAM_ID_WPROTOCOL und schreibt es in den Flash; tls_wifi_softap_destroy()
löscht es nie wieder. Solange das Bit gesetzt bleibt, erstellt
sys_net_status_changed() (sdk/Platform/Sys/tls_sys.c:519-525) bei jedem
NETIF_IP_NET_UP über tls_auto_reconnect_softap() erneut den Onboarding-AP
mit den SDK-eigenen (öffentlichen Default-)Parametern - unabhängig vom
Auto-Connect-Flag. Nach jedem Onboarding/Fallback käme der AP
`ninlight-<mac4>` mit dem öffentlichen Default-Passwort bei jedem
Reconnect/Reboot zurück, offen fürs LAN (SDK-NAPT).

Verifiziert in sdk/Platform/Sys/tls_sys.c, sdk/Include/Platform/wm_params.h
und sdk/Platform/Common/Params/wm_param.c:
- TLS_PARAM_ID_WPROTOCOL (10) ist ein u8, TLS_PARAM_IEEE80211_SOFTAP == 4 ==
  IEEE80211_MODE_AP (sdk/Include/WiFi/wm_wifi.h).
- tls_param_get()/tls_param_set(id, argv, to_flash) - genau die Signaturen
  aus dem Finding.
- tls_param_to_default() (tls_param_load_factory_default() + param_to_flash())
  arbeitet ausschließlich auf `struct tls_sys_param` (Wi-Fi/Netzwerk-Konfig);
  es liest/schreibt nirgends die MAC-Adresse oder RF-Kalibrierdaten - die
  liegen in einer komplett getrennten, efuse-gestützten FT_PARAM_ST-Struktur
  in sdk/Platform/Drivers/efuse/wm_efuse.c. tls_param_to_default() ist damit
  für den Werksreset sicher.

Fix (nl_wifi_w600.c):
1. clear_softap_param(): liest TLS_PARAM_ID_WPROTOCOL, löscht bei gesetztem
   IEEE80211_MODE_AP-Bit und persistiert das (to_flash=TRUE). Aufgerufen in
   nl_wifi_start() (vor allem anderen) und nach jedem tls_wifi_softap_destroy().
2. nl_wifi_start(): zerstört einen bereits vom SDK selbst erzeugten SoftAP
   (tls_wifi_softap_get_state() != WM_WIFI_DISCONNECTED), bevor unsere eigene
   State Machine startet.
3. Backstop im Netzwerk-Task: NETIF_WIFI_SOFTAP_SUCCESS, während die State
   Machine sagt, der AP solle unten sein (s_sm.ap_up == false) -> AP wieder
   zerstören und Param erneut löschen (geloggt).
4. Werksreset (nl_app_factory_reset, src/app_main.c): setzt zusätzlich
   tls_param_to_default() (siehe Verifikation oben) und dokumentiert das
   inline.

Nebenbei im selben Treiber erledigt, weil sie denselben Codepfad betreffen:
- M2: connect_station() loggt den Rückgabewert von tls_wifi_connect(), wenn
  er nicht WM_SUCCESS ist.
- M3: start_ap() merkt sich einen fehlgeschlagenen tls_wifi_softap_create()
  (`s_ap_create_pending`) und wiederholt ihn alle 5 s, solange die State
  Machine den AP oben haben will.

Nicht host-testbar (bindet SDK-Header ein); über den Firmware-Build
verifiziert (baut sauber für evk, keine neuen Warnungen).
2026-09-11 23:51:38 +02:00
Michael Sauer 5f45b9e996 fix: Gesamt-Timeout für HTTP-Anfragen
I2: SO_RCVTIMEO=5s war ein Timeout pro recv(), nicht für die gesamte
Anfrage. Ein Client, der 1 Byte alle 4,9 s schickt, konnte den einzigen
HTTP-Server-Task damit stundenlang belegen (Spec §7.2 will 5 s pro Header).

serve() rechnet jetzt eine absolute Deadline (nl_app_now_ms() + 5000 ms)
und setzt SO_RCVTIMEO vor jedem recv() auf die verbleibende Zeit (mindestens
1 ms); ist die Deadline überschritten, wird die Verbindung sofort verworfen.

Nicht host-testbar (nl_http_server.c bindet SDK-Sockets ein); über den
Firmware-Build verifiziert.
2026-09-11 23:51:11 +02:00
Michael Sauer 3250b89986 fix: Onboarding-Test trennt die Verbindung
I1: fail_test() kehrte ohne gespeicherte Zugangsdaten stillschweigend nach
AP_ONBOARDING zurück, ohne den Stationsversuch zu trennen; ein danach doch
noch eintreffendes CONNECTED wurde in AP_ONBOARDING ignoriert, aber der
Treiber rief nl_mdns_on_ip_up() unabhängig vom Zustand auf - die Lampe
landete offen im LAN, ohne dass Zugangsdaten gespeichert wurden.

nl_wifi_actions_t bekommt ein neues Feld `disconnect`:
- fail_test() ohne Zugangsdaten setzt es (Timeout/Fehler beendet den
  Stationsversuch).
- Ein CONNECTED-Event in AP_ONBOARDING gilt als Ausreißer eines bereits
  gescheiterten Versuchs und wird ebenfalls getrennt (Zustand bleibt
  unverändert).
- M1: nl_wifi_sm_onboard() trennt jetzt immer vor dem Testverbindungsaufbau,
  damit veraltete Events eines vorherigen Versuchs nicht mehr zählen; der
  Treiber (nl_wifi_request_onboard) meldet zusätzlich "wifi busy" (503),
  solange bereits ein Onboarding-Test läuft.

Der Treiber (src/net/nl_wifi_w600.c) führt `disconnect` vor `connect` aus
und ruft nl_mdns_on_ip_up() nur noch, wenn der Zustand nach der Verarbeitung
tatsächlich STA_CONNECTED ist.

Tests (test_wifi_sm.c): Timeout/Fehler ohne Zugangsdaten -> disconnect;
CONNECTED während AP_ONBOARDING -> disconnect, Zustand unverändert;
erneutes Onboarding während laufendem Test -> disconnect vor dem neuen
connect.
2026-09-11 23:51:03 +02:00
Michael Sauer 89c764514a fix: cJSON-Verschachtelung begrenzen
C1: Ein POST an /api/v1/device/state mit tief verschachteltem JSON (z.B.
20 Ebenen "[[[[...") lässt cJSONs rekursiven Parser den 6-KB-Stack des
HTTP-Tasks überlaufen - CJSON_NESTING_LIMIT war mit 1000 viel zu hoch und
FreeRTOS' Stack-Overflow-Checking ist aus. Begrenzt auf 8 Ebenen, sowohl
für die Firmware (third_party/cJSON/cJSON.c) als auch für die
Host-Tests (nl_cjson), damit beide dasselbe Verhalten zeigen.

Test: test_post_validation deckt jetzt einen Body mit 20 verschachtelten
Arrays ab (400 "invalid json").
2026-09-11 23:50:45 +02:00
Michael Sauer de49647f9d docs: Bruno-Collection, REST-Doku und Netzwerk-Checkliste 2026-09-11 23:14:15 +02:00
Michael Sauer f11ac8d9cc fix: mDNS-Init nur einmal, Teilfehler wiederholbar 2026-09-11 23:10:51 +02:00
Michael Sauer 27a6d6caf2 feat: mDNS-Responder für _ninlight._tcp 2026-09-11 23:06:04 +02:00
Michael Sauer c068156e6c fix: Send-Timeout für den HTTP-Server 2026-09-11 23:03:01 +02:00
Michael Sauer 15f709594b feat: HTTP-Server auf Port 3333 mit App-API-Kontext 2026-09-11 22:59:34 +02:00
Michael Sauer 4c2f9e5374 feat: WLAN-Treiber mit Onboarding-AP, Build-Credentials und Befehl net 2026-09-11 22:50:43 +02:00
Michael Sauer 2f7cadaeee fix: Verbindungs-Timeout im WLAN-Zustandsautomaten 2026-09-11 22:42:49 +02:00
Michael Sauer 2bdeaed98d feat: WLAN-Zustandsautomat mit Onboarding, Backoff und AP-Fallback 2026-09-11 22:35:30 +02:00
Michael Sauer dc23d3d887 feat: REST-API mit Routing, Auth und Handlern 2026-09-11 22:24:24 +02:00