#include #include #include "wm_include.h" #include "wm_efuse.h" #include "wm_watchdog.h" #include "lwip/ip_addr.h" #include "lwip/netif.h" /* struct netif is only forward-declared by wm_netif2.0.3.h */ #include "light/nl_engine.h" #include "net/nl_mdns.h" #include "net/nl_names.h" #include "net/nl_wifi.h" #include "net/nl_wifi_sm.h" #include "sys/nl_app.h" #define NL_NET_PRIO 35 #define NL_NET_QUEUE 8 #define MSG_NETIF 0x100u /* | netif status */ #define MSG_ONBOARD 0x200u /* 0x400, nicht 0x300: die netif-Meldungen werden weiter unten mit (m & MSG_NETIF) erkannt, * und 0x300 traegt das 0x100-Bit mit - die Standalone-Meldung waere als netif-Ereignis * protokolliert worden. */ #define MSG_STANDALONE 0x400u static u32 s_stack[768]; static tls_os_queue_t *s_queue; static tls_os_mutex_t *s_lock; /* guards s_sm and the credential buffers */ static nl_wifi_sm_t s_sm; static bool s_started; static uint8_t s_mac[6]; static char s_hostname[24]; static char s_ssid[NL_SSID_LEN], s_pass[NL_PASS_LEN]; static char s_test_ssid[NL_SSID_LEN], s_test_pass[NL_PASS_LEN]; static char s_ap_pass[NL_PASS_LEN]; static char s_ap_ssid[NL_SSID_LEN]; /* leer = Hostname */ static uint8_t s_ap_channel = 6; /* Die Daten, mit denen der laufende AP tatsaechlich aufgebaut wurde. Nur so ist zu sehen, * ob ein Wunsch aus der API den AP wirklich veraendert - die Zustandsmaschine liefert kein * start_ap, wenn der AP schon steht, und die alten Daten blieben sonst still in Kraft. */ static char s_ap_live_ssid[NL_SSID_LEN], s_ap_live_pass[NL_PASS_LEN]; static uint8_t s_ap_live_channel; static bool s_ap_live; /* mDNS im eigenen AP haengt am ZUSTAND (Standalone + AP steht), nicht am Erzeugungsereignis: * betritt die Lampe Standalone auf einem schon laufenden, unveraenderten AP, entsteht kein * SOFTAP_SUCCESS mehr. Beides nur im Netz-Task angefasst. */ static bool s_ap_event_seen; /* zum aktuell stehenden AP kam SOFTAP_SUCCESS an */ static bool s_ap_mdns_done; /* fuer den aktuell stehenden AP steht der mDNS-Dienst wirklich */ /* I5: s_ap_mdns_done haengt am ERFOLG, nicht am Versuch. nl_mdns_on_ip_up() reicht die Arbeit * nur an den tcpip-Thread weiter; findet announce() dort noch kein Netif mit Adresse, gibt es * auf. Bis der Dienst steht, wird alle NL_AP_MDNS_RETRY_MS erneut angestossen. */ #define NL_AP_MDNS_RETRY_MS 5000u static uint32_t s_ap_mdns_retry_at; /* Drei Versuche, dann der Rueckfall auf Daten, die schon einmal getragen haben: einer koennte * ein Zufall sein (das Funkteil raeumt gerade den alten AP ab), aber drei hintereinander * heissen, dass das Funkteil genau diese Daten nicht annimmt. Und 3 x 5 s hoechstens * 15 s ohne AP ist kuerzer als jede Fehlersuche vor Ort. */ #define NL_AP_CREATE_TRIES 3 static uint8_t s_ap_create_fails; /* Vom HTTP-Task gefuellt, vom Netz-Task uebernommen; beides unter s_lock. */ static char s_req_ap_ssid[NL_SSID_LEN], s_req_ap_pass[NL_PASS_LEN]; static uint8_t s_req_ap_channel; static bool s_req_enabled, s_req_pulse, s_req_pending; static bool s_ap_create_pending; /* M3: tls_wifi_softap_create() failed, retry it */ static uint32_t s_ap_create_retry_at; volatile uint32_t nl_net_beat; static void lock(void) { tls_os_mutex_acquire(s_lock, 0); } static void unlock(void) { tls_os_mutex_release(s_lock); } static void copy(char *dst, const char *src, size_t size) { strncpy(dst, src, size - 1); dst[size - 1] = '\0'; } static bool due_now(uint32_t now, uint32_t at) { return (int32_t)(now - at) >= 0; } /* SDK system task context: only hand the event over. */ static void netif_event(u8 status) { tls_os_queue_send(s_queue, (void *)(MSG_NETIF | status), 0); } /* C2: the SDK sets the SOFTAP bit in TLS_PARAM_ID_WPROTOCOL when tls_wifi_softap_create() * runs and persists it to flash; tls_wifi_softap_destroy() only sends a supplicant event * and never clears it. As long as the bit stays set, sys_net_status_changed() in * sdk/Platform/Sys/tls_sys.c (NETIF_IP_NET_UP case) recreates the AP with the SDK's own * (public default) softap params on every station IP-up - independent of our auto-connect * flag. Clear the bit (and persist that) whenever we know the AP should be down. */ static void clear_softap_param(void) { u8 proto = 0; tls_param_get(TLS_PARAM_ID_WPROTOCOL, &proto, FALSE); if (proto & IEEE80211_MODE_AP) { proto &= (u8)~IEEE80211_MODE_AP; tls_param_set(TLS_PARAM_ID_WPROTOCOL, &proto, TRUE); printf("wifi: cleared stale SDK softap param (wprotocol now %u)\r\n", proto); } } /* I3: tls_sys_auto_mode_run() (sdk/Platform/Sys/tls_sys.c) kicks off an SDK-driven join right * after UserMain() whenever the persisted auto-connect flag is on - and tls_sys.c:264-267 turns a * temporary WIFI_AUTO_CNT_TMP_OFF back into WIFI_AUTO_CNT_ON and persists that, so the factory * default "off" is not something we may rely on. Any such join is indistinguishable from our own * in the netif status events, so switch it off (and drop a connection it already made) before the * recovery scan, not only in nl_wifi_start(). */ void nl_wifi_disable_sdk_auto_connect(void) { u8 auto_mode = WIFI_AUTO_CNT_OFF; tls_wifi_auto_connect_flag(WIFI_AUTO_CNT_FLAG_GET, &auto_mode); if (auto_mode != WIFI_AUTO_CNT_OFF) { printf("wifi: SDK auto-connect was %u, switching it off\r\n", auto_mode); auto_mode = WIFI_AUTO_CNT_OFF; tls_wifi_auto_connect_flag(WIFI_AUTO_CNT_FLAG_SET, &auto_mode); } tls_wifi_disconnect(); } /* Setzt NUR Name und Kanal des naechsten AP auf die Vorgaben zurueck: "ninlight-" auf * Kanal 6. Das Passwort bleibt stehen - I1: ein Rueckfall darf das Passwort des Nutzers nicht * durch das einkompilierte ersetzen, das in der README oeffentlich dokumentiert ist. Beruehrt * einen LAUFENDEN AP nicht - der behaelt seine Daten, bis er abgebaut und neu aufgebaut wird. */ static void reset_ap_identity(void) { s_ap_ssid[0] = '\0'; s_ap_channel = 6; } /* I2: Vergisst, mit welchen Daten der zuletzt stehende AP aufgebaut wurde. Verlaesst die Lampe * den Standalone-Modus, duerfen Name und Passwort des Nutzers nicht als "hat schon einmal * getragen" liegenbleiben: der erste Rueckfallzweig in start_ap() holte sie sonst Minuten * spaeter zurueck (etwa am Rueckfall-AP nach fuenf Minuten ohne Router), und die Lampe boete * periodisch einen AP an, den der Nutzer laengst abgeschaltet hat. */ static void forget_ap_live(void) { s_ap_live_ssid[0] = s_ap_live_pass[0] = '\0'; s_ap_live_channel = 0; } /* Vollstaendige Rueckstellung auf die Vorgaben, einschliesslich des einkompilierten Passworts. * Nur fuer den Weg AUS dem Standalone-Modus heraus, wo der Wunsch des Nutzers endet. */ static void reset_ap_defaults(void) { reset_ap_identity(); copy(s_ap_pass, NINLIGHT_AP_PASSWORD, sizeof s_ap_pass); } /* Reisst den AP ab und raeumt dabei auch die Buchfuehrung auf. Laeuft nur im Netz-Task * (oder vor dessen Start) und nie unter s_lock - clear_softap_param() fasst SDK-Zustand an. */ static void destroy_ap(void) { tls_wifi_softap_destroy(); s_ap_create_pending = false; s_ap_live = false; s_ap_event_seen = s_ap_mdns_done = false; /* I3: Der Zaehler gehoert zu EINEM Datensatz. Ohne diese Nullung addieren sich die * Fehlschlaege mit den alten Daten zu denen mit den neuen, und nach einer Korrektur * (Kanal 13 -> Kanal 6) loeste schon ein einziger Fehlschlag den Rueckfall aus. */ s_ap_create_fails = 0; clear_softap_param(); /* C2: every softap teardown clears the persisted SOFTAP bit */ } static void start_ap(void) { struct tls_softap_info_t ap; struct tls_ip_info_t ip; const char *ssid = s_ap_ssid[0] ? s_ap_ssid : s_hostname; size_t n = strlen(s_ap_pass); int r; /* Steht derselbe AP schon, bleibt er stehen: die Zustandsmaschine verlangt start_ap * auch dann, wenn sie den AP gar nicht erst abgebaut hat (fehlgeschlagener * Onboarding-Versuch aus dem Standalone-Modus), und ein Neuaufbau wuerfe dort alle * Clients ohne Not heraus. */ if (s_ap_live && strcmp(s_ap_live_ssid, ssid) == 0 && strcmp(s_ap_live_pass, s_ap_pass) == 0 && s_ap_live_channel == s_ap_channel) return; /* Umgekehrt: Name, Passwort oder Kanal haben sich geaendert, waehrend der AP steht. * Ohne eigenen Abriss liefe er mit den alten Daten weiter. */ if (s_ap_live) { printf("wifi: AP data changed, rebuilding\r\n"); destroy_ap(); } memset(&ap, 0, sizeof ap); memset(&ip, 0, sizeof ip); copy((char *)ap.ssid, ssid, sizeof ap.ssid); ap.encrypt = IEEE80211_ENCRYT_CCMP_WPA2; ap.channel = s_ap_channel; ap.keyinfo.format = 1; /* ascii */ ap.keyinfo.index = 1; ap.keyinfo.key_len = (u8)n; memcpy(ap.keyinfo.key, s_ap_pass, n); ip.ip_addr[0] = 192; ip.ip_addr[1] = 168; ip.ip_addr[2] = 4; ip.ip_addr[3] = 1; ip.netmask[0] = 255; ip.netmask[1] = 255; ip.netmask[2] = 255; ip.netmask[3] = 0; memcpy(ip.dnsname, "local", 6); r = tls_wifi_softap_create(&ap, &ip); printf("wifi: AP %s (ch %u) %s\r\n", ssid, (unsigned)s_ap_channel, r == WM_SUCCESS ? "up" : "failed"); /* M3: retry a failed AP create instead of leaving onboarding/fallback without an AP. */ s_ap_create_pending = r != WM_SUCCESS; s_ap_create_retry_at = nl_app_now_ms() + 5000u; s_ap_live = r == WM_SUCCESS; if (s_ap_live) { /* Neuer AP: erst das Ereignis abwarten, dann bekanntmachen. */ s_ap_event_seen = s_ap_mdns_done = false; copy(s_ap_live_ssid, ssid, sizeof s_ap_live_ssid); copy(s_ap_live_pass, s_ap_pass, sizeof s_ap_live_pass); s_ap_live_channel = s_ap_channel; s_ap_create_fails = 0; return; } /* Nimmt das Funkteil diese Daten dauerhaft nicht an, steht die Lampe ohne AP da - und mit * in den Einstellungen gespeicherten Wunschdaten auch nach einem Neustart wieder. Dann * lieber ein AP unter unerwartetem Namen: der ist sichtbar und reparierbar. */ if (++s_ap_create_fails < NL_AP_CREATE_TRIES) return; s_ap_create_fails = 0; if (s_ap_live_ssid[0] && (strcmp(s_ap_live_ssid, ssid) != 0 || strcmp(s_ap_live_pass, s_ap_pass) != 0 || s_ap_live_channel != s_ap_channel)) { printf("wifi: AP create keeps failing, falling back to the last working data (%s, ch %u)\r\n", s_ap_live_ssid, (unsigned)s_ap_live_channel); copy(s_ap_ssid, s_ap_live_ssid, sizeof s_ap_ssid); copy(s_ap_pass, s_ap_live_pass, sizeof s_ap_pass); s_ap_channel = s_ap_live_channel; } else if (s_ap_ssid[0] || s_ap_channel != 6) { /* I1: nur Name und Kanal. Das Passwort ist der unwahrscheinlichste Grund fuer ein * abgelehntes tls_wifi_softap_create() - die Laenge prueft die API selbst, Kanal und * SSID sind die plausiblen Kandidaten. Ein Rueckfall auf NINLIGHT_AP_PASSWORD stellte * eine Standalone-Lampe mit einem oeffentlich dokumentierten Passwort ins Funkfeld, * und sACN hoert dort unauthentifiziert mit. */ printf("wifi: AP create keeps failing, falling back to %s on ch 6\r\n", s_hostname); reset_ap_identity(); } /* Sonst scheitern schon die Vorgaben - dann bleibt nur, es weiter zu versuchen. */ } static void connect_station(bool test) { char ssid[NL_SSID_LEN], pass[NL_PASS_LEN]; int r; lock(); copy(ssid, test ? s_test_ssid : s_ssid, sizeof ssid); copy(pass, test ? s_test_pass : s_pass, sizeof pass); unlock(); printf("wifi: connecting to %s\r\n", ssid); r = tls_wifi_connect((u8 *)ssid, (u8)strlen(ssid), (u8 *)pass, (u8)strlen(pass)); if (r != WM_SUCCESS) /* M2: surface connect-call failures, not just later join events */ printf("wifi: tls_wifi_connect failed, err=%d\r\n", r); } static void pulse(bool on) { nl_app_lock(); nl_engine_pulse(on); nl_app_unlock(); } static void execute(nl_wifi_actions_t a) { if (a.save_test_creds) { char ssid[NL_SSID_LEN], pass[NL_PASS_LEN]; lock(); copy(s_ssid, s_test_ssid, sizeof s_ssid); copy(s_pass, s_test_pass, sizeof s_pass); copy(ssid, s_ssid, sizeof ssid); copy(pass, s_pass, sizeof pass); unlock(); printf("wifi: onboarding ok, saving credentials for %s\r\n", ssid); nl_app_set_wifi_credentials(ssid, pass); } if (a.clear_standalone) { /* Sonst stuende die Lampe nach dem naechsten Neustart wieder in ihrem eigenen AP, * obwohl sie gerade erfolgreich ins WLAN gewechselt ist. */ printf("wifi: standalone mode cleared\r\n"); /* Dieselbe Rueckstellung wie beim Zurueckschalten ueber die API: sonst kaeme ein * spaeterer Rueckfall-AP (5 min ohne Station) mit Namen und Passwort des Nutzers * hoch, obwohl standalone in den Einstellungen schon 0 ist. forget_ap_live() raeumt * dazu die Buchfuehrung ueber den zuletzt tragenden AP weg (I2) - sonst holte ihn * der erste Rueckfallzweig in start_ap() nach drei abgelehnten Aufbauten zurueck. * Der noch laufende Standalone-AP bleibt stehen, bis die Zustandsmaschine ihn * abbaut: dieses Aktionsbuendel enthaelt kein start_ap. */ reset_ap_defaults(); forget_ap_live(); /* Schlaegt das Speichern fehl, steht die Lampe jetzt im WLAN, nach dem naechsten * Neustart aber wieder in ihrem eigenen AP - ohne jede Spur. Wenigstens melden. */ if (nl_app_clear_standalone() != 0) printf("wifi: could not persist the cleared standalone flag\r\n"); } if (a.pulse_on) pulse(true); if (a.pulse_off) pulse(false); if (a.start_ap) start_ap(); if (a.stop_ap) { printf("wifi: AP down\r\n"); destroy_ap(); } if (a.disconnect) { printf("wifi: dropping stray/failed station attempt\r\n"); tls_wifi_disconnect(); } if (a.connect) connect_station(a.use_test_creds); } static nl_wifi_error_t join_error(void) { switch (tls_wifi_get_errno()) { case WM_WIFI_EKEY: return NL_WIFI_ERR_AUTH; case WM_WIFI_ENOAP: return NL_WIFI_ERR_NOT_FOUND; default: return NL_WIFI_ERR_TIMEOUT; } } static void net_task(void *arg) { (void)arg; for (;;) { void *msg = NULL; nl_wifi_actions_t ev, tick; uint32_t m = 0, now; bool got_ip_event = false, connected_now, ap_wanted, unexpected_softap, standalone_now, open_now; bool recheck_ap = false, rebuild_ap = false, ignored_off = false; memset(&ev, 0, sizeof ev); if (tls_os_queue_receive(s_queue, &msg, 0, HZ / 10) == TLS_OS_SUCCESS) m = (uint32_t)msg; now = nl_app_now_ms(); lock(); if (m == (MSG_NETIF | NETIF_IP_NET_UP)) { ev = nl_wifi_sm_event(&s_sm, NL_WIFI_EV_CONNECTED, NL_WIFI_ERR_NONE, now); got_ip_event = true; } else if (m == (MSG_NETIF | NETIF_WIFI_JOIN_FAILED)) { ev = nl_wifi_sm_event(&s_sm, NL_WIFI_EV_JOIN_FAILED, join_error(), now); } else if (m == (MSG_NETIF | NETIF_WIFI_DISCONNECTED)) { ev = nl_wifi_sm_event(&s_sm, NL_WIFI_EV_DISCONNECTED, NL_WIFI_ERR_NONE, now); } else if (m == MSG_ONBOARD) { ev = nl_wifi_sm_onboard(&s_sm, now); } else if (m == MSG_STANDALONE) { /* Die Wunschwerte erst hier uebernehmen: der HTTP-Task hat sie abgelegt, der * Netz-Task ist der einzige, der s_ap_* schreibt und start_ap() aufruft. */ if (s_req_enabled) { copy(s_ap_ssid, s_req_ap_ssid, sizeof s_ap_ssid); copy(s_ap_pass, s_req_ap_pass[0] ? s_req_ap_pass : NINLIGHT_AP_PASSWORD, sizeof s_ap_pass); s_ap_channel = s_req_ap_channel ? s_req_ap_channel : 6; ev = nl_wifi_sm_standalone(&s_sm, s_req_pulse, now); /* Steht der AP schon, liefert die Zustandsmaschine kein start_ap. Ob die * neuen Daten einen Neuaufbau verlangen, entscheidet start_ap() selbst. */ recheck_ap = true; } else if (!s_sm.standalone) { /* War die Lampe gar nicht standalone, gibt es nichts abzuschalten. Ohne diese * Vorpruefung riefe leave_standalone() auf einer gewoehnlichen Stationslampe * begin_connecting() und erzwaenge eine Neuverbindung ohne Anlass. Die Antwort * bleibt 202: der gewuenschte Zustand gilt ja bereits. (standalone bleibt auch * waehrend eines Onboarding-Tests gesetzt, der Test faellt also nicht hierher.) */ ignored_off = true; } else { /* Zurueck auf die Vorgaben: ein spaeterer Rueckfall- oder Onboarding-AP ist * wieder der gewohnte "ninlight-" mit dem einkompilierten Passwort, * genau wie bei einer Lampe, die nie standalone war. */ reset_ap_defaults(); forget_ap_live(); ev = nl_wifi_sm_leave_standalone(&s_sm, s_ssid[0] != '\0', now); rebuild_ap = true; } /* I3: Ein neuer Wunsch ist ein neuer Datensatz - die Fehlschlaege des alten * duerfen nicht mitzaehlen. */ s_ap_create_fails = 0; s_req_pending = false; } tick = nl_wifi_sm_tick(&s_sm, now); /* I1: a late/stray IP-up must not announce mDNS unless the SM actually settled on STA_CONNECTED. */ connected_now = s_sm.state == NL_WIFI_ST_STA_CONNECTED; ap_wanted = s_sm.ap_up; /* C2 backstop: the SDK brought its own softap up behind our back (e.g. the clear in * nl_wifi_start raced a pending sys_net_status_changed()) while our state machine * does not want an AP. Only latch the fact here; tls_wifi_softap_destroy() and * clear_softap_param() touch the SDK's own state and must not run while s_lock * (which only guards s_sm and the credential buffers) is held. */ unexpected_softap = m == (MSG_NETIF | NETIF_WIFI_SOFTAP_SUCCESS) && !ap_wanted; /* Nur die Lage merken; der Aufruf nach aussen folgt hinter der Entsperrung. */ standalone_now = s_sm.state == NL_WIFI_ST_STANDALONE; open_now = nl_wifi_sm_open_access(&s_sm); unlock(); if (unexpected_softap) { printf("wifi: unexpected SDK softap while AP should be down, destroying it\r\n"); destroy_ap(); } if (m & MSG_NETIF) printf("wifi: netif event %u\r\n", (unsigned)(m & 0xFFu)); /* I6: Die 202 auf den Standalone-Wunsch schreibt der HTTP-Task erst, nachdem * nl_api_handle() zurueck ist - die Warteschlangennachricht geht aber schon darin * raus, und dieser Task hat die hoehere Prioritaet (35 gegen 42, kleinere Zahl heisst * hoeher). Er verdraengt also sofort und risse das Netz ab, bevor send_all() lief. * Beim Wechsel AUS dem WLAN ging das bisher gut, weil tls_wifi_disconnect() asynchron * arbeitet; ueber den eigenen AP ist tls_wifi_softap_destroy() der haertere Abriss. * Die Pause ist billige Versicherung - gemessen ist der AP-Fall damit nicht, er steht * als offener Punkt in der Abnahmeliste. */ if (m == MSG_STANDALONE) tls_os_time_delay(HZ / 4); /* ~250 ms */ if (ignored_off) printf("wifi: standalone was already off, nothing to do\r\n"); /* C1: Beim Verlassen des Standalone-Modus OHNE gespeicherte WLAN-Daten geht die * Zustandsmaschine in den Onboarding-AP - dort liefert open_access() true, waehrend * der AP noch unveraendert mit Name und Passwort des Nutzers steht. Wer das * AP-Passwort kennt, koennte dann ohne Token system/settings und system/command * benutzen. Also hier hart abreissen und mit den Vorgabedaten neu aufbauen; damit * stimmt auch die README-Zusage wieder, der AP heisse danach "ninlight-". * Nicht ueber die Aktionsstruktur: execute() fuehrt start_ap VOR stop_ap aus, ein * Buendel mit beiden Flags baute erst auf und risse dann ab. Und VOR execute(ev), * damit dessen start_ap in die Gleichheitspruefung von start_ap() faellt, statt den * eben gebauten AP ein zweites Mal abzureissen. */ if (rebuild_ap && ap_wanted && open_now) { printf("wifi: left standalone without credentials, rebuilding the AP with the defaults\r\n"); destroy_ap(); start_ap(); } execute(ev); /* Nach einem Wechsel in den Standalone-Modus: start_ap() baut den AP neu auf, wenn * Name, Passwort oder Kanal sich geaendert haben, und tut sonst nichts. */ if (recheck_ap && ap_wanted) start_ap(); execute(tick); if (got_ip_event && connected_now) nl_mdns_on_ip_up(); /* Im eigenen AP gibt es kein Stationsereignis, an dem mDNS haengen koennte. * Ohne diesen Aufruf findet die App ein Standalone-Geraet nicht - am * 2026-10-01 am EVK gemessen: REST auf der AP-IP lief, ninlight-681f.local * war weder aufloesbar noch sichtbar. */ if (m == (MSG_NETIF | NETIF_WIFI_SOFTAP_SUCCESS)) s_ap_event_seen = true; /* Zustandsgebunden: auch ein Wechsel auf einen unveraendert stehenden AP loest es aus. * Mehrfaches Rufen waere harmlos, s_ap_mdns_done spart es trotzdem. Faellt der Zustand * weg, gilt die Bekanntmachung beim naechsten Eintritt neu. */ if (!standalone_now) { s_ap_mdns_done = false; s_ap_mdns_retry_at = now; /* beim Eintritt sofort anstossen */ } else if (s_ap_live && s_ap_event_seen && !s_ap_mdns_done) { /* I5: Erst der Erfolg beendet die Sache. Wer den blossen Versuch als erledigt * verbucht, laesst die Lampe fuer die ganze Standzeit des AP unauffindbar, * sobald announce() einmal "no usable netif yet" gemeldet hat. Deshalb immer * erst anstossen und dann den gemeldeten Zustand abfragen - nl_mdns_service_up() * ist erst wahr, wenn die Bekanntmachung im tcpip-Thread auch gelaufen ist. */ if (due_now(now, s_ap_mdns_retry_at)) { s_ap_mdns_retry_at = now + NL_AP_MDNS_RETRY_MS; nl_mdns_on_ip_up(); } else s_ap_mdns_done = nl_mdns_service_up(); } /* M3: retry a softap create that failed, as long as the SM still wants the AP up. */ if (s_ap_create_pending && ap_wanted && due_now(now, s_ap_create_retry_at)) { printf("wifi: retrying AP create\r\n"); start_ap(); } nl_net_beat++; } } void nl_wifi_start(const char *ssid, const char *pass, const char *ap_pass, const char *ap_ssid, uint8_t ap_channel, bool standalone, bool pulse) { nl_wifi_actions_t a; struct netif *nif; clear_softap_param(); /* C2: drop any softap bit left over from a previous onboarding/fallback */ tls_get_mac_addr(s_mac); nl_names_hostname(s_mac, s_hostname, sizeof s_hostname); copy(s_ssid, ssid, sizeof s_ssid); copy(s_pass, pass, sizeof s_pass); /* Wie in nl_wifi_request_standalone(): NULL ist hier zwar von keinem Aufrufer zu * erwarten, aber copy() wuerde darueber stolpern. */ copy(s_ap_pass, ap_pass && ap_pass[0] ? ap_pass : NINLIGHT_AP_PASSWORD, sizeof s_ap_pass); copy(s_ap_ssid, ap_ssid ? ap_ssid : "", sizeof s_ap_ssid); /* leer = Hostname */ s_ap_channel = ap_channel ? ap_channel : 6; /* The SDK would otherwise reconnect on its own with credentials from its parameter area. */ nl_wifi_disable_sdk_auto_connect(); if (tls_wifi_softap_get_state() != WM_WIFI_DISCONNECTED) { /* C2: the SDK already brought up its own softap (e.g. from NETIF_IP_NET_UP before * we got a chance to run) - tear it down, our state machine owns AP lifecycle. */ printf("wifi: destroying SDK-created softap left over from boot\r\n"); destroy_ap(); } nif = tls_get_netif(); if (nif) nif->hostname = s_hostname; /* DHCP host name */ tls_os_mutex_create(0, &s_lock); tls_os_queue_create(&s_queue, NL_NET_QUEUE); tls_netif_add_status_event(netif_event); lock(); a = nl_wifi_sm_start(&s_sm, s_ssid[0] != '\0', standalone, pulse, nl_app_now_ms()); unlock(); s_started = true; printf("wifi: %s, hostname %s\r\n", standalone ? "standalone AP" : (s_ssid[0] ? "station" : "onboarding AP"), s_hostname); execute(a); tls_os_task_create(NULL, "nl_net", net_task, NULL, (u8 *)s_stack, sizeof(s_stack), NL_NET_PRIO, 0); } int nl_wifi_request_onboard(const char *ssid, const char *pass) { bool busy; if (!s_started) return -1; lock(); busy = s_sm.state == NL_WIFI_ST_ONBOARD_TEST; if (!busy) { copy(s_test_ssid, ssid, sizeof s_test_ssid); copy(s_test_pass, pass, sizeof s_test_pass); } unlock(); if (busy) /* M1: API returns 503 "wifi busy" instead of queuing on top of a running test */ return -1; return tls_os_queue_send(s_queue, (void *)MSG_ONBOARD, 0) == TLS_OS_SUCCESS ? 0 : -1; } int nl_wifi_request_standalone(bool enabled, const char *ssid, const char *pass, uint8_t channel, bool pulse) { bool busy; if (!s_started) return -1; lock(); /* Wie beim Onboarding: nicht auf einen laufenden Test draufsatteln, und keinen zweiten * Wunsch annehmen, solange der erste noch in der Warteschlange steht - er ueberschriebe * dessen Werte, die Antwort haette aber schon 202 gesagt. */ busy = s_sm.state == NL_WIFI_ST_ONBOARD_TEST || s_req_pending; if (!busy) { copy(s_req_ap_ssid, ssid ? ssid : "", sizeof s_req_ap_ssid); copy(s_req_ap_pass, pass ? pass : "", sizeof s_req_ap_pass); s_req_ap_channel = channel; s_req_enabled = enabled; s_req_pulse = pulse; s_req_pending = true; } unlock(); if (busy) return -1; if (tls_os_queue_send(s_queue, (void *)MSG_STANDALONE, 0) != TLS_OS_SUCCESS) { lock(); s_req_pending = false; unlock(); return -1; } return 0; } bool nl_wifi_is_standalone(void) { bool r; if (!s_started) return false; lock(); r = s_sm.state == NL_WIFI_ST_STANDALONE; unlock(); return r; } bool nl_wifi_open_access(void) { bool r; if (!s_started) return false; lock(); r = nl_wifi_sm_open_access(&s_sm); unlock(); return r; } void nl_wifi_status(nl_wifi_status_t *out) { struct tls_ethif *e = tls_netif_get_ethif(); bool ap_up = false; memset(out, 0, sizeof *out); out->mode = "sta"; if (s_started) { lock(); out->mode = nl_wifi_sm_mode(&s_sm); out->error = nl_wifi_error_name(s_sm.error); /* I4: Im eigenen AP (Standalone oder Onboarding) ist die Stations-SSID nicht der Name * des Netzes, auf dem die Lampe antwortet - und ein Heimnetzname hat in einer * tokenfrei lesbaren Antwort ohnehin nichts verloren. Gemeldet wird der WIRKSAME * AP-Name; damit wird auch ein Rueckfall auf andere AP-Daten ueber die API sichtbar, * ohne ein neues Feld. "apsta" bleibt bei der Station: dort ist sie die Hauptsache. */ if (strcmp(nl_wifi_sm_mode(&s_sm), "ap") == 0) copy(out->ssid, s_ap_live_ssid[0] ? s_ap_live_ssid : (s_ap_ssid[0] ? s_ap_ssid : s_hostname), sizeof out->ssid); else copy(out->ssid, s_ssid, sizeof out->ssid); ap_up = s_sm.ap_up || s_sm.state == NL_WIFI_ST_AP_ONBOARDING; unlock(); } if (e && e->status) { struct tls_curr_bss_t bss; ip4addr_ntoa_r(ip_2_ip4(&e->ip_addr), out->sta_ip, sizeof out->sta_ip); memset(&bss, 0, sizeof bss); tls_wifi_get_current_bss(&bss); /* W600 SDK reports RSSI magnitude as unsigned; EVK ninIOT measured ~-50 dBm as 50. * Report negative for 0 < bss.rssi <= 127, unchanged for > 127 (already two's complement). */ out->rssi = (bss.rssi > 0 && bss.rssi <= 127) ? -(int)bss.rssi : (int)(int8_t)bss.rssi; } if (out->sta_ip[0]) copy(out->ip, out->sta_ip, sizeof out->ip); else if (ap_up) copy(out->ip, "192.168.4.1", sizeof out->ip); } const char *nl_wifi_hostname(void) { return s_hostname; } void nl_wifi_mac(uint8_t mac[6]) { memcpy(mac, s_mac, 6); } /* --- boot recovery: join a station directly, ahead of nl_wifi_start()/nl_wifi_sm --- */ static volatile bool s_recovery_join_done; static volatile bool s_recovery_join_ok; static void recovery_join_event(u8 status) { if (status == NETIF_IP_NET_UP) { s_recovery_join_ok = true; s_recovery_join_done = true; } else if (status == NETIF_WIFI_JOIN_FAILED) { s_recovery_join_done = true; } } bool nl_wifi_join_blocking(const char *ssid, const char *pass, uint32_t timeout_ms) { uint32_t waited = 0; /* C2: if a stale SDK softap bit from a previous onboarding/fallback boot is still set * in TLS_PARAM_ID_WPROTOCOL, sys_net_status_changed() (sdk/Platform/Sys/tls_sys.c) * recreates the SDK's own default-params softap the moment this join reaches * NETIF_IP_NET_UP - exactly the "lamp stuck in onboarding" case recovery exists for. * Clear it once, up front, same as nl_wifi_start() does. */ clear_softap_param(); s_recovery_join_done = false; s_recovery_join_ok = false; tls_netif_add_status_event(recovery_join_event); if (tls_wifi_connect((u8 *)ssid, (u8)strlen(ssid), (u8 *)pass, (u8)strlen(pass)) != WM_SUCCESS) { tls_netif_remove_status_event(recovery_join_event); return false; } while (!s_recovery_join_done && waited < timeout_ms) { tls_watchdog_clr(); /* boot phase: called before app_task's own heartbeat gate exists */ tls_os_time_delay(HZ / 10); waited += 100; } tls_netif_remove_status_event(recovery_join_event); if (!s_recovery_join_ok) { tls_wifi_disconnect(); /* cancel a stuck/timed-out join attempt */ return false; } /* I3 backstop: NETIF_IP_NET_UP carries no SSID, so an SDK-initiated join to its own stored AP * would look exactly like ours - and the lamp would then fetch http://192.168.8.1/... from a * foreign network. tls_wifi_get_current_bss() does report the SSID actually associated with * (sdk/Include/WiFi/wm_wifi.h:190), so verify it before the caller starts a download. */ { struct tls_curr_bss_t bss; size_t n = strlen(ssid); memset(&bss, 0, sizeof bss); tls_wifi_get_current_bss(&bss); if (bss.ssid_len != (u8)n || memcmp(bss.ssid, ssid, n) != 0) { printf("wifi: joined a different network than %s, dropping the connection\r\n", ssid); tls_wifi_disconnect(); return false; } } return true; }