fix: Recovery-Rettungsweg härten (Watchdog-Cap, Konsole, Softap-Bit, Scan-Retry)

This commit is contained in:
Michael Sauer
2026-09-12 02:38:02 +02:00
parent 8fa4af7eb5
commit 247d610fe8
5 changed files with 85 additions and 16 deletions
+9 -2
View File
@@ -44,9 +44,16 @@ endif()
if(NOT DEFINED NINLIGHT_RECOVERY_PASSWORD)
set(NINLIGHT_RECOVERY_PASSWORD "ninlight-recovery")
endif()
# Unlike NINLIGHT_WIFI_SSID, the recovery SSID is compared byte-for-byte against scan
# results (nl_recovery.c) and must fit struct tls_bss_info_t::ssid[32]; empty would match
# any hidden/empty-SSID AP in the scan, so it must not be empty either.
string(LENGTH "${NINLIGHT_RECOVERY_SSID}" _nl_rec_ssid_len)
if(_nl_rec_ssid_len EQUAL 0 OR _nl_rec_ssid_len GREATER 32)
message(FATAL_ERROR "NINLIGHT_RECOVERY_SSID must be 1..32 characters")
endif()
string(LENGTH "${NINLIGHT_RECOVERY_PASSWORD}" _nl_rec_len)
if(_nl_rec_len LESS 8)
message(FATAL_ERROR "NINLIGHT_RECOVERY_PASSWORD needs at least 8 characters (WPA2)")
if(_nl_rec_len LESS 8 OR _nl_rec_len GREATER 63)
message(FATAL_ERROR "NINLIGHT_RECOVERY_PASSWORD must be 8..63 characters (WPA2)")
endif()
foreach(_v NINLIGHT_WIFI_SSID NINLIGHT_WIFI_PASSWORD NINLIGHT_AP_PASSWORD NINLIGHT_TOKEN
NINLIGHT_RECOVERY_SSID NINLIGHT_RECOVERY_PASSWORD)
+3
View File
@@ -4,5 +4,8 @@
# set(NINLIGHT_WIFI_SSID "MyWifi")
# set(NINLIGHT_WIFI_PASSWORD "secret")
# set(NINLIGHT_AP_PASSWORD "ninlight")
# Boot recovery station (plan 3): SSID 1..32 chars, password 8..63 chars (WPA2)
# set(NINLIGHT_RECOVERY_SSID "ninlight-recovery")
# set(NINLIGHT_RECOVERY_PASSWORD "ninlight-recovery")
# Used from plan 3/4 on (path to the old RSA signing key):
# set(NINLIGHT_MIGRATION_KEY "/path/to/fw_sign.pem")
+1 -1
View File
@@ -197,8 +197,8 @@ static void app_task(void *arg)
tls_watchdog_init(20u * 1000u * 1000u); /* 20 s */
nl_ota_init();
nl_console_start(); /* keep the serial fallback alive during nl_recovery_try() below */
nl_recovery_try(); /* may not return: a successful recovery resets the chip */
nl_console_start();
{
static nl_settings_t boot; /* copy: the network task may change s_settings later */
+65 -13
View File
@@ -12,6 +12,15 @@
#define NL_RECOVERY_SCAN_MS 3000u
#define NL_RECOVERY_JOIN_MS 15000u
#define NL_RECOVERY_BUF 2048u
/* Generous cap on how long the download itself may run. wm_http_fwup.c's own resume loop
can already take up to ~20 minutes on a slow/aborted transfer (200 rounds x up to 5 s
receive timeout / 2 s retry delay), and a real hang - e.g. its unbounded
"while (!p) { tls_os_time_delay(1); pbuf_alloc(...) }" retry on allocation failure
(wm_http_fwup.c:178) - never returns from t_http_fwup() at all. Past this cap we stop
feeding the watchdog (see below) instead of waiting indefinitely: a watchdog reset is
strictly better than a silent hang with no Wi-Fi and no console, and a fresh boot simply
retries recovery from scratch. */
#define NL_RECOVERY_DOWNLOAD_MAX_MS (10u * 60u * 1000u)
static volatile bool s_scan_done;
@@ -32,24 +41,50 @@ static bool recovery_ap_present(void)
uint32_t max_entries, n;
bool found = false;
u32 i;
int scan_rc;
if (buf == NULL)
return false;
s_scan_done = false;
tls_wifi_scan_result_cb_register(scan_done_cb);
if (tls_wifi_scan() != WM_SUCCESS) {
tls_wifi_scan_result_cb_register(NULL);
tls_mem_free(buf);
return false;
}
while (!s_scan_done && waited < NL_RECOVERY_SCAN_MS) {
/* wm_wifi.h: "If not SUCCESS, user needs to call this function again to trigger scan" -
retry a transient failure (e.g. WM_WIFI_SCANNING_BUSY) instead of silently skipping
the whole recovery attempt, but stay inside the shared 3 s budget. */
for (;;) {
scan_rc = tls_wifi_scan();
if (scan_rc == WM_SUCCESS)
break;
if (waited >= NL_RECOVERY_SCAN_MS) {
printf("recovery: tls_wifi_scan() failed (rc=%d), giving up\r\n", scan_rc);
tls_wifi_scan_result_cb_register(NULL);
tls_mem_free(buf);
return false;
}
tls_watchdog_clr(); /* boot phase: only this path runs, the light_task heartbeat
gate in app_task() does not watch us yet */
tls_os_time_delay(HZ / 10);
waited += 100;
}
if (s_scan_done && tls_wifi_get_scan_rslt((u8 *)buf, NL_RECOVERY_BUF) == WM_SUCCESS) {
while (!s_scan_done && waited < NL_RECOVERY_SCAN_MS) {
tls_watchdog_clr();
tls_os_time_delay(HZ / 10);
waited += 100;
}
if (!s_scan_done) {
/* Without this line, a scan whose callback simply never fired in time is
indistinguishable in the log from "no AP found" - the EVK verification needs
exactly that distinction. */
printf("recovery: scan result not ready after %u ms, giving up\r\n", (unsigned)waited);
} else if (tls_wifi_get_scan_rslt((u8 *)buf, NL_RECOVERY_BUF) != WM_SUCCESS) {
printf("recovery: scan done after %u ms, but tls_wifi_get_scan_rslt() failed\r\n",
(unsigned)waited);
} else {
res = (struct tls_scan_bss_t *)buf;
printf("recovery: scan done after %u ms, %u AP(s)\r\n", (unsigned)waited,
(unsigned)res->count);
/* struct tls_bss_info_t has no length field - res->bss is a fixed-size-element
array (struct tls_scan_bss_t::bss[1] is a flexible-array-style head), so index
it directly. Cap by what actually fits in buf in case res->count ever reports
@@ -88,11 +123,28 @@ void nl_recovery_try(void)
printf("recovery: cannot start download\r\n");
return;
}
/* Give the download task the boot phase; on success the SDK resets the chip. */
while (nl_ota_busy()) {
tls_watchdog_clr();
tls_os_time_delay(HZ / 2);
/* Give the download task the boot phase; on success the SDK resets the chip. Past
NL_RECOVERY_DOWNLOAD_MAX_MS, stop feeding the watchdog instead of continuing to wait -
see the comment on that constant. */
{
uint32_t waited = 0;
bool feeding = true;
while (nl_ota_busy()) {
if (feeding) {
if (waited >= NL_RECOVERY_DOWNLOAD_MAX_MS) {
printf("recovery: download still running after %u ms, letting the watchdog reset\r\n",
(unsigned)NL_RECOVERY_DOWNLOAD_MAX_MS);
feeding = false;
} else {
tls_watchdog_clr();
}
}
tls_os_time_delay(HZ / 2);
waited += 500;
}
if (feeding)
printf("recovery: update failed (%s), continuing normal boot\r\n",
nl_ota_last_error() ? nl_ota_last_error() : "unknown");
}
printf("recovery: update failed (%s), continuing normal boot\r\n",
nl_ota_last_error() ? nl_ota_last_error() : "unknown");
}
+7
View File
@@ -345,6 +345,13 @@ bool nl_wifi_join_blocking(const char *ssid, const char *pass, uint32_t timeout_
{
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);