fix(wifi): AP-Daten nach dem Onboarding zuruecksetzen, Netz trotz abgelehntem AP-Aufbau

This commit is contained in:
Michael Sauer
2026-10-02 01:17:00 +02:00
parent 18eb7b5c01
commit 24c4e38e4e
3 changed files with 68 additions and 8 deletions
+9 -1
View File
@@ -10,6 +10,9 @@
#define P_SETTINGS "/api/v1/system/settings" #define P_SETTINGS "/api/v1/system/settings"
#define P_COMMAND "/api/v1/system/command" #define P_COMMAND "/api/v1/system/command"
/* Obergrenze fuer das AP-Passwort des Standalone-Modus (WPA2-ASCII-Passphrase). */
#define NL_AP_PASS_MAX 63
static const char *const POB_NAMES[] = {"last", "on", "off"}; static const char *const POB_NAMES[] = {"last", "on", "off"};
static void reply(nl_api_response_t *r, int status, const char *json) static void reply(nl_api_response_t *r, int status, const char *json)
@@ -370,7 +373,12 @@ static void handle_put_settings(const nl_api_ctx_t *ctx, const nl_http_request_t
will_enable = cJSON_IsBool(en) && cJSON_IsTrue(en); will_enable = cJSON_IsBool(en) && cJSON_IsTrue(en);
asp = get_string(j, "ssid", 1, NL_SSID_LEN - 1, &as); asp = get_string(j, "ssid", 1, NL_SSID_LEN - 1, &as);
app = get_string(j, "password", 8, NL_PASS_LEN - 1, &ap); /* 63, nicht NL_PASS_LEN - 1 (64): eine WPA2-ASCII-Passphrase darf hoechstens 63
* Zeichen haben. Bei 64 schriebe der Treiber keyinfo.key[64] randvoll ohne
* Abschlussbyte, und ob libwlan.a das annimmt, sieht man dem Binaerobjekt nicht an -
* ein abgelehnter AP-Aufbau ist hier aber eine ueber die API erreichbare Sackgasse.
* Der "wifi"-Zweig bleibt bewusst bei 64: dort haengt kein eigener AP daran. */
app = get_string(j, "password", 8, NL_AP_PASS_MAX, &ap);
chp = get_int(j, "channel", 1, 13, &ch); chp = get_int(j, "channel", 1, 13, &ch);
if (!cJSON_IsBool(en)) { if (!cJSON_IsBool(en)) {
+51 -6
View File
@@ -41,6 +41,12 @@ static uint8_t s_ap_channel = 6;
static char s_ap_live_ssid[NL_SSID_LEN], s_ap_live_pass[NL_PASS_LEN]; static char s_ap_live_ssid[NL_SSID_LEN], s_ap_live_pass[NL_PASS_LEN];
static uint8_t s_ap_live_channel; static uint8_t s_ap_live_channel;
static bool s_ap_live; static bool s_ap_live;
/* 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. */ /* 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 char s_req_ap_ssid[NL_SSID_LEN], s_req_ap_pass[NL_PASS_LEN];
static uint8_t s_req_ap_channel; static uint8_t s_req_ap_channel;
@@ -104,6 +110,16 @@ void nl_wifi_disable_sdk_auto_connect(void)
tls_wifi_disconnect(); tls_wifi_disconnect();
} }
/* Setzt Name, Passwort und Kanal des naechsten AP auf die Vorgaben zurueck: "ninlight-<mac4>"
* mit dem einkompilierten Passwort auf Kanal 6. Beruehrt einen LAUFENDEN AP nicht - der
* behaelt seine Daten, bis er abgebaut und neu aufgebaut wird. */
static void reset_ap_defaults(void)
{
s_ap_ssid[0] = '\0';
copy(s_ap_pass, NINLIGHT_AP_PASSWORD, sizeof s_ap_pass);
s_ap_channel = 6;
}
/* Reisst den AP ab und raeumt dabei auch die Buchfuehrung auf. Laeuft nur im Netz-Task /* 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. */ * (oder vor dessen Start) und nie unter s_lock - clear_softap_param() fasst SDK-Zustand an. */
static void destroy_ap(void) static void destroy_ap(void)
@@ -159,7 +175,28 @@ static void start_ap(void)
copy(s_ap_live_ssid, ssid, sizeof s_ap_live_ssid); copy(s_ap_live_ssid, ssid, sizeof s_ap_live_ssid);
copy(s_ap_live_pass, s_ap_pass, sizeof s_ap_live_pass); copy(s_ap_live_pass, s_ap_pass, sizeof s_ap_live_pass);
s_ap_live_channel = s_ap_channel; 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 || strcmp(s_ap_pass, NINLIGHT_AP_PASSWORD) != 0) {
printf("wifi: AP create keeps failing, falling back to %s on ch 6\r\n", s_hostname);
reset_ap_defaults();
}
/* Sonst scheitern schon die Vorgaben - dann bleibt nur, es weiter zu versuchen. */
} }
static void connect_station(bool test) static void connect_station(bool test)
@@ -201,6 +238,12 @@ static void execute(nl_wifi_actions_t a)
/* Sonst stuende die Lampe nach dem naechsten Neustart wieder in ihrem eigenen AP, /* Sonst stuende die Lampe nach dem naechsten Neustart wieder in ihrem eigenen AP,
* obwohl sie gerade erfolgreich ins WLAN gewechselt ist. */ * obwohl sie gerade erfolgreich ins WLAN gewechselt ist. */
printf("wifi: standalone mode cleared\r\n"); 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. Gefahrlos, weil dieses
* Aktionsbuendel kein start_ap enthaelt - der noch laufende Standalone-AP bleibt
* unberuehrt, bis die Zustandsmaschine ihn abbaut. */
reset_ap_defaults();
nl_app_clear_standalone(); nl_app_clear_standalone();
} }
if (a.pulse_on) if (a.pulse_on)
@@ -270,10 +313,10 @@ static void net_task(void *arg)
} else { } else {
/* Zurueck auf die Vorgaben: ein spaeterer Rueckfall- oder Onboarding-AP ist /* Zurueck auf die Vorgaben: ein spaeterer Rueckfall- oder Onboarding-AP ist
* wieder der gewohnte "ninlight-<mac4>" mit dem einkompilierten Passwort, * wieder der gewohnte "ninlight-<mac4>" mit dem einkompilierten Passwort,
* genau wie bei einer Lampe, die nie standalone war. */ * genau wie bei einer Lampe, die nie standalone war. Das greift erst beim
s_ap_ssid[0] = '\0'; * NAECHSTEN Aufbau - der noch laufende AP behaelt Name und Passwort, bis ihn
copy(s_ap_pass, NINLIGHT_AP_PASSWORD, sizeof s_ap_pass); * die Zustandsmaschine abbaut. */
s_ap_channel = 6; reset_ap_defaults();
ev = nl_wifi_sm_leave_standalone(&s_sm, s_ssid[0] != '\0', now); ev = nl_wifi_sm_leave_standalone(&s_sm, s_ssid[0] != '\0', now);
} }
s_req_pending = false; s_req_pending = false;
@@ -327,8 +370,10 @@ void nl_wifi_start(const char *ssid, const char *pass, const char *ap_pass,
nl_names_hostname(s_mac, s_hostname, sizeof s_hostname); nl_names_hostname(s_mac, s_hostname, sizeof s_hostname);
copy(s_ssid, ssid, sizeof s_ssid); copy(s_ssid, ssid, sizeof s_ssid);
copy(s_pass, pass, sizeof s_pass); copy(s_pass, pass, sizeof s_pass);
copy(s_ap_pass, ap_pass, sizeof s_ap_pass); /* Wie in nl_wifi_request_standalone(): NULL ist hier zwar von keinem Aufrufer zu
copy(s_ap_ssid, ap_ssid, sizeof s_ap_ssid); /* leer = Hostname */ * 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; s_ap_channel = ap_channel ? ap_channel : 6;
/* The SDK would otherwise reconnect on its own with credentials from its parameter area. */ /* The SDK would otherwise reconnect on its own with credentials from its parameter area. */
+8 -1
View File
@@ -728,11 +728,18 @@ static void test_standalone_length_limits(void)
{ {
char body[256], pw[80], ss[48]; char body[256], pw[80], ss[48];
memset(pw, 'p', 64); pw[64] = '\0'; /* 63 ist die Obergrenze, nicht 64: eine WPA2-ASCII-Passphrase ist hoechstens 63 Zeichen
* lang, und ein vom Funkteil abgelehnter AP-Aufbau waere hier eine Sackgasse. */
memset(pw, 'p', 63); pw[63] = '\0';
snprintf(body, sizeof body, "{\"type\":\"standalone\",\"enabled\":true,\"password\":\"%s\"}", pw); snprintf(body, sizeof body, "{\"type\":\"standalone\",\"enabled\":true,\"password\":\"%s\"}", pw);
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, body); call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, body);
TEST_ASSERT_EQUAL_INT(202, resp.status); TEST_ASSERT_EQUAL_INT(202, resp.status);
TEST_ASSERT_EQUAL_STRING(pw, fake_standalone_pass); TEST_ASSERT_EQUAL_STRING(pw, fake_standalone_pass);
memset(pw, 'p', 64); pw[64] = '\0';
snprintf(body, sizeof body, "{\"type\":\"standalone\",\"enabled\":true,\"password\":\"%s\"}", pw);
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, body);
TEST_ASSERT_EQUAL_INT(400, resp.status);
TEST_ASSERT_NULL(strstr(resp.body, pw));
memset(pw, 'p', 65); pw[65] = '\0'; memset(pw, 'p', 65); pw[65] = '\0';
snprintf(body, sizeof body, "{\"type\":\"standalone\",\"enabled\":true,\"password\":\"%s\"}", pw); snprintf(body, sizeof body, "{\"type\":\"standalone\",\"enabled\":true,\"password\":\"%s\"}", pw);
call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, body); call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, body);