diff --git a/CMakeLists.txt b/CMakeLists.txt index 0fae211..1d9a88e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/app_main.c b/src/app_main.c index b497394..a26409e 100644 --- a/src/app_main.c +++ b/src/app_main.c @@ -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(); { diff --git a/src/net/nl_recovery.c b/src/net/nl_recovery.c new file mode 100644 index 0000000..73a1176 --- /dev/null +++ b/src/net/nl_recovery.c @@ -0,0 +1,98 @@ +#include "net/nl_recovery.h" + +#include +#include + +#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"); +} diff --git a/src/net/nl_recovery.h b/src/net/nl_recovery.h new file mode 100644 index 0000000..bcedfa8 --- /dev/null +++ b/src/net/nl_recovery.h @@ -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 diff --git a/src/net/nl_wifi.h b/src/net/nl_wifi.h index 6b3a11e..2b0a61b 100644 --- a/src/net/nl_wifi.h +++ b/src/net/nl_wifi.h @@ -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). */ diff --git a/src/net/nl_wifi_w600.c b/src/net/nl_wifi_w600.c index 4ccf356..454a342 100644 --- a/src/net/nl_wifi_w600.c +++ b/src/net/nl_wifi_w600.c @@ -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; +}