feat: Recovery-Download beim Booten

This commit is contained in:
Michael Sauer
2026-09-12 02:24:58 +02:00
parent 7474cd77e1
commit 8fa4af7eb5
6 changed files with 172 additions and 2 deletions
+16 -2
View File
@@ -38,7 +38,18 @@ string(LENGTH "${NINLIGHT_WIFI_PASSWORD}" _nl_wifi_pass_len)
if(NOT _nl_wifi_pass_len EQUAL 0 AND (_nl_wifi_pass_len LESS 8 OR _nl_wifi_pass_len GREATER 63))
message(FATAL_ERROR "NINLIGHT_WIFI_PASSWORD must be empty or 8..63 characters (WPA2)")
endif()
foreach(_v NINLIGHT_WIFI_SSID NINLIGHT_WIFI_PASSWORD NINLIGHT_AP_PASSWORD NINLIGHT_TOKEN)
if(NOT DEFINED NINLIGHT_RECOVERY_SSID)
set(NINLIGHT_RECOVERY_SSID "ninlight-recovery")
endif()
if(NOT DEFINED NINLIGHT_RECOVERY_PASSWORD)
set(NINLIGHT_RECOVERY_PASSWORD "ninlight-recovery")
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)")
endif()
foreach(_v NINLIGHT_WIFI_SSID NINLIGHT_WIFI_PASSWORD NINLIGHT_AP_PASSWORD NINLIGHT_TOKEN
NINLIGHT_RECOVERY_SSID NINLIGHT_RECOVERY_PASSWORD)
if("${${_v}}" MATCHES "[\"\\\\;]")
message(FATAL_ERROR "${_v} must not contain \", \\ or ;")
endif()
@@ -74,6 +85,7 @@ set(NL_FW_SOURCES
src/net/nl_api.c
src/net/nl_wifi_sm.c
src/net/nl_wifi_w600.c
src/net/nl_recovery.c
src/net/nl_mdns.c
src/net/nl_http_server.c
src/sys/nl_app_api.c
@@ -92,7 +104,9 @@ target_compile_definitions(${NL_FW} PRIVATE
NINLIGHT_TOKEN_DEFAULT="${NINLIGHT_TOKEN}"
NINLIGHT_WIFI_SSID="${NINLIGHT_WIFI_SSID}"
NINLIGHT_WIFI_PASSWORD="${NINLIGHT_WIFI_PASSWORD}"
NINLIGHT_AP_PASSWORD="${NINLIGHT_AP_PASSWORD}")
NINLIGHT_AP_PASSWORD="${NINLIGHT_AP_PASSWORD}"
NINLIGHT_RECOVERY_SSID="${NINLIGHT_RECOVERY_SSID}"
NINLIGHT_RECOVERY_PASSWORD="${NINLIGHT_RECOVERY_PASSWORD}")
target_include_directories(${NL_FW} PRIVATE ${CMAKE_BINARY_DIR}/generated)
add_dependencies(${NL_FW} nl_version)
target_link_options(${NL_FW} PRIVATE
+2
View File
@@ -16,6 +16,7 @@
#include "light/nl_engine.h"
#include "net/nl_http_server.h"
#include "net/nl_mdns.h"
#include "net/nl_recovery.h"
#include "net/nl_wifi.h"
#include "sys/nl_app.h"
#include "sys/nl_app_api.h"
@@ -196,6 +197,7 @@ static void app_task(void *arg)
tls_watchdog_init(20u * 1000u * 1000u); /* 20 s */
nl_ota_init();
nl_recovery_try(); /* may not return: a successful recovery resets the chip */
nl_console_start();
{
+98
View File
@@ -0,0 +1,98 @@
#include "net/nl_recovery.h"
#include <stddef.h>
#include <string.h>
#include "wm_include.h"
#include "wm_watchdog.h"
#include "net/nl_wifi.h"
#include "sys/nl_ota.h"
#define NL_RECOVERY_SCAN_MS 3000u
#define NL_RECOVERY_JOIN_MS 15000u
#define NL_RECOVERY_BUF 2048u
static volatile bool s_scan_done;
/* tls_wifi_scan_result_cb_register() takes a plain void(void) callback (see
sdk/Include/WiFi/wm_wifi.h) - it carries no status/result argument. */
static void scan_done_cb(void)
{
s_scan_done = true;
}
static bool recovery_ap_present(void)
{
char *buf = tls_mem_alloc(NL_RECOVERY_BUF);
struct tls_scan_bss_t *res;
const char *want = NINLIGHT_RECOVERY_SSID;
uint32_t want_len = (uint32_t)strlen(want);
uint32_t waited = 0;
uint32_t max_entries, n;
bool found = false;
u32 i;
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) {
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) {
res = (struct tls_scan_bss_t *)buf;
/* 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
more entries than tls_wifi_get_scan_rslt() copied into this buffer size. */
max_entries = (uint32_t)((NL_RECOVERY_BUF - offsetof(struct tls_scan_bss_t, bss))
/ sizeof(struct tls_bss_info_t));
n = res->count < max_entries ? res->count : max_entries;
for (i = 0; i < n; i++) {
struct tls_bss_info_t *bss = &res->bss[i];
if (bss->ssid_len == want_len && memcmp(bss->ssid, want, want_len) == 0) {
found = true;
break;
}
}
}
tls_wifi_scan_result_cb_register(NULL);
tls_mem_free(buf);
return found;
}
void nl_recovery_try(void)
{
char url[128];
if (!recovery_ap_present())
return;
printf("recovery: station found, joining\r\n");
if (!nl_wifi_join_blocking(NINLIGHT_RECOVERY_SSID, NINLIGHT_RECOVERY_PASSWORD, NL_RECOVERY_JOIN_MS)) {
printf("recovery: join failed\r\n");
nl_ota_set_error("download");
return;
}
snprintf(url, sizeof url, "http://192.168.8.1/ninlight_%s_ota.img", NINLIGHT_DEVICE_ID);
printf("recovery: fetching %s\r\n", url);
if (nl_ota_start(url) != 0) {
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);
}
printf("recovery: update failed (%s), continuing normal boot\r\n",
nl_ota_last_error() ? nl_ota_last_error() : "unknown");
}
+10
View File
@@ -0,0 +1,10 @@
#ifndef NL_RECOVERY_H
#define NL_RECOVERY_H
/* Looks for the recovery station for at most 3 s. When it is there, joins it and
starts the recovery download; the chip resets on success, so this call may not return.
Must run before nl_wifi_start(): it drives the SDK Wi-Fi APIs directly, without the
nl_wifi_sm state machine, and feeds the watchdog itself while it waits. */
void nl_recovery_try(void);
#endif
+6
View File
@@ -21,6 +21,12 @@ extern volatile uint32_t nl_net_beat;
/* Starts the network task. An empty ssid starts the onboarding AP. Strings are copied. */
void nl_wifi_start(const char *ssid, const char *pass, const char *ap_pass);
/* Joins a station directly (no nl_wifi_sm) and blocks until it gets an IP or timeout_ms
* elapses; returns true on success. For use before nl_wifi_start() only - e.g. the boot
* recovery path in nl_recovery.c - since it drives the SDK Wi-Fi calls itself instead of
* going through the state machine, and must not race nl_wifi_start()'s own netif status
* event registration and s_lock/s_queue setup. Feeds the watchdog itself while it waits. */
bool nl_wifi_join_blocking(const char *ssid, const char *pass, uint32_t timeout_ms);
/* Starts an onboarding test with new credentials (from the HTTP task). 0 = queued. */
int nl_wifi_request_onboard(const char *ssid, const char *pass);
/* True while the unconfigured onboarding AP is open (API without token). */
+40
View File
@@ -3,6 +3,7 @@
#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 */
@@ -324,3 +325,42 @@ void nl_wifi_status(nl_wifi_status_t *out)
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;
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;
}
return true;
}