diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d9a88e..f500947 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/config.local.cmake.example b/config.local.cmake.example index f16aa0e..db9207b 100644 --- a/config.local.cmake.example +++ b/config.local.cmake.example @@ -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") diff --git a/src/app_main.c b/src/app_main.c index a26409e..e17b9e8 100644 --- a/src/app_main.c +++ b/src/app_main.c @@ -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 */ diff --git a/src/net/nl_recovery.c b/src/net/nl_recovery.c index 73a1176..65271aa 100644 --- a/src/net/nl_recovery.c +++ b/src/net/nl_recovery.c @@ -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"); } diff --git a/src/net/nl_wifi_w600.c b/src/net/nl_wifi_w600.c index 454a342..f3201a7 100644 --- a/src/net/nl_wifi_w600.c +++ b/src/net/nl_wifi_w600.c @@ -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);