From 4c2f9e537485c3f0ae2bd533fcdb30ac0355c6a9 Mon Sep 17 00:00:00 2001 From: Michael Sauer Date: Fri, 11 Sep 2026 22:50:43 +0200 Subject: [PATCH] feat: WLAN-Treiber mit Onboarding-AP, Build-Credentials und Befehl net --- CMakeLists.txt | 26 +++- config.local.cmake.example | 2 +- src/app_main.c | 26 ++++ src/net/nl_mdns.c | 5 + src/net/nl_mdns.h | 9 ++ src/net/nl_wifi.h | 29 +++++ src/net/nl_wifi_w600.c | 250 +++++++++++++++++++++++++++++++++++++ src/sys/nl_app.h | 3 + src/sys/nl_console.c | 8 ++ src/sys/nl_console_cmd.c | 4 +- src/sys/nl_console_cmd.h | 1 + test/test_console_cmd.c | 2 + 12 files changed, 361 insertions(+), 4 deletions(-) create mode 100644 src/net/nl_mdns.c create mode 100644 src/net/nl_mdns.h create mode 100644 src/net/nl_wifi.h create mode 100644 src/net/nl_wifi_w600.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 64c799e..26e4fe7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,25 @@ if(NOT DEFINED NINLIGHT_TOKEN) set(NINLIGHT_TOKEN "aabbccddeeff0011223344") endif() +if(NOT DEFINED NINLIGHT_WIFI_SSID) + set(NINLIGHT_WIFI_SSID "") +endif() +if(NOT DEFINED NINLIGHT_WIFI_PASSWORD) + set(NINLIGHT_WIFI_PASSWORD "") +endif() +if(NOT DEFINED NINLIGHT_AP_PASSWORD) + set(NINLIGHT_AP_PASSWORD "ninlight") +endif() +string(LENGTH "${NINLIGHT_AP_PASSWORD}" _nl_ap_len) +if(_nl_ap_len LESS 8) + message(FATAL_ERROR "NINLIGHT_AP_PASSWORD needs at least 8 characters (WPA2)") +endif() +foreach(_v NINLIGHT_WIFI_SSID NINLIGHT_WIFI_PASSWORD NINLIGHT_AP_PASSWORD NINLIGHT_TOKEN) + if("${${_v}}" MATCHES "[\"\\\\]") + message(FATAL_ERROR "${_v} must not contain \" or \\") + endif() +endforeach() + include(cmake/version.cmake) include(cmake/sdk.cmake) include(cmake/image.cmake) @@ -48,6 +67,8 @@ set(NL_FW_SOURCES src/net/nl_http.c src/net/nl_api.c src/net/nl_wifi_sm.c + src/net/nl_wifi_w600.c + src/net/nl_mdns.c third_party/cJSON/cJSON.c) set(NL_FW ninlight_${NINLIGHT_DEVICE}) @@ -57,7 +78,10 @@ target_compile_definitions(${NL_FW} PRIVATE NINLIGHT_DEVICE_ID="${NINLIGHT_DEVICE}" NINLIGHT_FW_VERSION="${NINLIGHT_FW_VERSION}" NINLIGHT_BUILD_DATE="${NINLIGHT_BUILD_DATE}" - NINLIGHT_TOKEN_DEFAULT="${NINLIGHT_TOKEN}") + NINLIGHT_TOKEN_DEFAULT="${NINLIGHT_TOKEN}" + NINLIGHT_WIFI_SSID="${NINLIGHT_WIFI_SSID}" + NINLIGHT_WIFI_PASSWORD="${NINLIGHT_WIFI_PASSWORD}" + NINLIGHT_AP_PASSWORD="${NINLIGHT_AP_PASSWORD}") target_link_options(${NL_FW} PRIVATE -static -nostartfiles -T${SDK_DIR}/ld/link_w600_2m.ld -Wl,-Map=${CMAKE_BINARY_DIR}/${NL_FW}.map) diff --git a/config.local.cmake.example b/config.local.cmake.example index 3265eb4..f16aa0e 100644 --- a/config.local.cmake.example +++ b/config.local.cmake.example @@ -1,6 +1,6 @@ # Copy to config.local.cmake (gitignored) and adjust. # set(NINLIGHT_TOKEN "change-me-please") -# Used from plan 2 on: +# WLAN credentials compiled into the image (stored settings win); AP password >= 8 chars # set(NINLIGHT_WIFI_SSID "MyWifi") # set(NINLIGHT_WIFI_PASSWORD "secret") # set(NINLIGHT_AP_PASSWORD "ninlight") diff --git a/src/app_main.c b/src/app_main.c index bf1f023..2c2337a 100644 --- a/src/app_main.c +++ b/src/app_main.c @@ -1,4 +1,5 @@ #include +#include #include "wm_include.h" #include "wm_watchdog.h" @@ -13,6 +14,8 @@ #include "light/nl_boot_state.h" #include "light/nl_control.h" #include "light/nl_engine.h" +#include "net/nl_mdns.h" +#include "net/nl_wifi.h" #include "sys/nl_app.h" #include "sys/nl_console.h" @@ -79,6 +82,17 @@ void nl_app_reboot(void) void nl_app_request_factory_reset(void) { s_factory_reset_requested = true; } +int nl_app_set_wifi_credentials(const char *ssid, const char *pass) +{ + nl_app_lock(); + strncpy(s_settings.wifi_ssid, ssid, NL_SSID_LEN - 1); + s_settings.wifi_ssid[NL_SSID_LEN - 1] = '\0'; + strncpy(s_settings.wifi_pass, pass, NL_PASS_LEN - 1); + s_settings.wifi_pass[NL_PASS_LEN - 1] = '\0'; + nl_app_unlock(); + return nl_app_save_settings(); +} + void nl_app_factory_reset(void) { printf("factory reset\r\n"); @@ -160,6 +174,18 @@ static void app_task(void *arg) tls_watchdog_init(20u * 1000u * 1000u); /* 20 s */ nl_console_start(); + { + static nl_settings_t boot; /* copy: the network task may change s_settings later */ + nl_app_lock(); + boot = s_settings; + nl_app_unlock(); + nl_mdns_set_name(boot.name); + if (boot.wifi_ssid[0]) + nl_wifi_start(boot.wifi_ssid, boot.wifi_pass, NINLIGHT_AP_PASSWORD); + else + nl_wifi_start(NINLIGHT_WIFI_SSID, NINLIGHT_WIFI_PASSWORD, NINLIGHT_AP_PASSWORD); + } + boot_ms = nl_app_now_ms(); beat_seen = s_light_beat; for (;;) { diff --git a/src/net/nl_mdns.c b/src/net/nl_mdns.c new file mode 100644 index 0000000..e4e2004 --- /dev/null +++ b/src/net/nl_mdns.c @@ -0,0 +1,5 @@ +#include "net/nl_mdns.h" + +/* Placeholder until the mDNS responder lands (plan 2, task 7). */ +void nl_mdns_on_ip_up(void) {} +void nl_mdns_set_name(const char *name) { (void)name; } diff --git a/src/net/nl_mdns.h b/src/net/nl_mdns.h new file mode 100644 index 0000000..7380eac --- /dev/null +++ b/src/net/nl_mdns.h @@ -0,0 +1,9 @@ +#ifndef NL_MDNS_H +#define NL_MDNS_H + +/* Called by the network task once the station got an IP (again). */ +void nl_mdns_on_ip_up(void); +/* Device name for the TXT record; re-announces when mDNS is running. */ +void nl_mdns_set_name(const char *name); + +#endif diff --git a/src/net/nl_wifi.h b/src/net/nl_wifi.h new file mode 100644 index 0000000..e64a30d --- /dev/null +++ b/src/net/nl_wifi.h @@ -0,0 +1,29 @@ +#ifndef NL_WIFI_H +#define NL_WIFI_H + +#include +#include + +#include "config/nl_settings.h" + +typedef struct { + const char *mode; /* "sta" | "ap" | "apsta" */ + char ssid[NL_SSID_LEN]; /* configured station SSID */ + int rssi; /* dBm, 0 when not connected */ + char ip[16]; /* station IP, else the AP IP when the AP is up, else "" */ + char sta_ip[16]; /* "" when the station has no IP */ + const char *error; /* result of the last onboarding test or NULL */ +} nl_wifi_status_t; + +/* 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); +/* 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). */ +bool nl_wifi_open_access(void); +void nl_wifi_status(nl_wifi_status_t *out); +/* "ninlight-", valid after nl_wifi_start(). */ +const char *nl_wifi_hostname(void); +void nl_wifi_mac(uint8_t mac[6]); + +#endif diff --git a/src/net/nl_wifi_w600.c b/src/net/nl_wifi_w600.c new file mode 100644 index 0000000..4f0ac7d --- /dev/null +++ b/src/net/nl_wifi_w600.c @@ -0,0 +1,250 @@ +#include +#include + +#include "wm_include.h" +#include "wm_efuse.h" +#include "lwip/ip_addr.h" +#include "lwip/netif.h" /* struct netif is only forward-declared by wm_netif2.0.3.h */ + +#include "light/nl_engine.h" +#include "net/nl_mdns.h" +#include "net/nl_names.h" +#include "net/nl_wifi.h" +#include "net/nl_wifi_sm.h" +#include "sys/nl_app.h" + +#define NL_NET_PRIO 35 +#define NL_NET_QUEUE 8 +#define MSG_NETIF 0x100u /* | netif status */ +#define MSG_ONBOARD 0x200u + +static u32 s_stack[768]; +static tls_os_queue_t *s_queue; +static tls_os_mutex_t *s_lock; /* guards s_sm and the credential buffers */ +static nl_wifi_sm_t s_sm; +static bool s_started; +static uint8_t s_mac[6]; +static char s_hostname[24]; +static char s_ssid[NL_SSID_LEN], s_pass[NL_PASS_LEN]; +static char s_test_ssid[NL_SSID_LEN], s_test_pass[NL_PASS_LEN]; +static char s_ap_pass[NL_PASS_LEN]; + +static void lock(void) { tls_os_mutex_acquire(s_lock, 0); } +static void unlock(void) { tls_os_mutex_release(s_lock); } + +static void copy(char *dst, const char *src, size_t size) +{ + strncpy(dst, src, size - 1); + dst[size - 1] = '\0'; +} + +/* SDK system task context: only hand the event over. */ +static void netif_event(u8 status) +{ + tls_os_queue_send(s_queue, (void *)(MSG_NETIF | status), 0); +} + +static void start_ap(void) +{ + struct tls_softap_info_t ap; + struct tls_ip_info_t ip; + size_t n = strlen(s_ap_pass); + int r; + + memset(&ap, 0, sizeof ap); + memset(&ip, 0, sizeof ip); + copy((char *)ap.ssid, s_hostname, sizeof ap.ssid); + ap.encrypt = IEEE80211_ENCRYT_CCMP_WPA2; + ap.channel = 6; + ap.keyinfo.format = 1; /* ascii */ + ap.keyinfo.index = 1; + ap.keyinfo.key_len = (u8)n; + memcpy(ap.keyinfo.key, s_ap_pass, n); + ip.ip_addr[0] = 192; ip.ip_addr[1] = 168; ip.ip_addr[2] = 4; ip.ip_addr[3] = 1; + ip.netmask[0] = 255; ip.netmask[1] = 255; ip.netmask[2] = 255; ip.netmask[3] = 0; + memcpy(ip.dnsname, "local", 6); + r = tls_wifi_softap_create(&ap, &ip); + printf("wifi: AP %s %s\r\n", s_hostname, r == WM_SUCCESS ? "up" : "failed"); +} + +static void connect_station(bool test) +{ + char ssid[NL_SSID_LEN], pass[NL_PASS_LEN]; + + lock(); + copy(ssid, test ? s_test_ssid : s_ssid, sizeof ssid); + copy(pass, test ? s_test_pass : s_pass, sizeof pass); + unlock(); + printf("wifi: connecting to %s\r\n", ssid); + tls_wifi_connect((u8 *)ssid, (u8)strlen(ssid), (u8 *)pass, (u8)strlen(pass)); +} + +static void pulse(bool on) +{ + nl_app_lock(); + nl_engine_pulse(on); + nl_app_unlock(); +} + +static void execute(nl_wifi_actions_t a) +{ + if (a.save_test_creds) { + char ssid[NL_SSID_LEN], pass[NL_PASS_LEN]; + lock(); + copy(s_ssid, s_test_ssid, sizeof s_ssid); + copy(s_pass, s_test_pass, sizeof s_pass); + copy(ssid, s_ssid, sizeof ssid); + copy(pass, s_pass, sizeof pass); + unlock(); + printf("wifi: onboarding ok, saving credentials for %s\r\n", ssid); + nl_app_set_wifi_credentials(ssid, pass); + } + if (a.pulse_on) + pulse(true); + if (a.pulse_off) + pulse(false); + if (a.start_ap) + start_ap(); + if (a.stop_ap) { + printf("wifi: AP down\r\n"); + tls_wifi_softap_destroy(); + } + if (a.connect) + connect_station(a.use_test_creds); +} + +static nl_wifi_error_t join_error(void) +{ + switch (tls_wifi_get_errno()) { + case WM_WIFI_EKEY: return NL_WIFI_ERR_AUTH; + case WM_WIFI_ENOAP: return NL_WIFI_ERR_NOT_FOUND; + default: return NL_WIFI_ERR_TIMEOUT; + } +} + +static void net_task(void *arg) +{ + (void)arg; + for (;;) { + void *msg = NULL; + nl_wifi_actions_t ev, tick; + uint32_t m = 0, now; + bool ip_up = false; + + memset(&ev, 0, sizeof ev); + if (tls_os_queue_receive(s_queue, &msg, 0, HZ / 10) == TLS_OS_SUCCESS) + m = (uint32_t)msg; + now = nl_app_now_ms(); + + lock(); + if (m == (MSG_NETIF | NETIF_IP_NET_UP)) { + ev = nl_wifi_sm_event(&s_sm, NL_WIFI_EV_CONNECTED, NL_WIFI_ERR_NONE, now); + ip_up = true; + } else if (m == (MSG_NETIF | NETIF_WIFI_JOIN_FAILED)) { + ev = nl_wifi_sm_event(&s_sm, NL_WIFI_EV_JOIN_FAILED, join_error(), now); + } else if (m == (MSG_NETIF | NETIF_WIFI_DISCONNECTED)) { + ev = nl_wifi_sm_event(&s_sm, NL_WIFI_EV_DISCONNECTED, NL_WIFI_ERR_NONE, now); + } else if (m == MSG_ONBOARD) { + ev = nl_wifi_sm_onboard(&s_sm, now); + } + tick = nl_wifi_sm_tick(&s_sm, now); + unlock(); + + if (m & MSG_NETIF) + printf("wifi: netif event %u\r\n", (unsigned)(m & 0xFFu)); + execute(ev); + execute(tick); + if (ip_up) + nl_mdns_on_ip_up(); + } +} + +void nl_wifi_start(const char *ssid, const char *pass, const char *ap_pass) +{ + u8 auto_mode = WIFI_AUTO_CNT_OFF; + nl_wifi_actions_t a; + struct netif *nif; + + tls_get_mac_addr(s_mac); + nl_names_hostname(s_mac, s_hostname, sizeof s_hostname); + copy(s_ssid, ssid, sizeof s_ssid); + copy(s_pass, pass, sizeof s_pass); + copy(s_ap_pass, ap_pass, sizeof s_ap_pass); + + /* The SDK would otherwise reconnect on its own with credentials from its parameter area. */ + tls_wifi_auto_connect_flag(WIFI_AUTO_CNT_FLAG_GET, &auto_mode); + if (auto_mode != WIFI_AUTO_CNT_OFF) { + auto_mode = WIFI_AUTO_CNT_OFF; + tls_wifi_auto_connect_flag(WIFI_AUTO_CNT_FLAG_SET, &auto_mode); + } + tls_wifi_disconnect(); + nif = tls_get_netif(); + if (nif) + nif->hostname = s_hostname; /* DHCP host name */ + + tls_os_mutex_create(0, &s_lock); + tls_os_queue_create(&s_queue, NL_NET_QUEUE); + tls_netif_add_status_event(netif_event); + + lock(); + a = nl_wifi_sm_start(&s_sm, s_ssid[0] != '\0', nl_app_now_ms()); + unlock(); + s_started = true; + printf("wifi: %s, hostname %s\r\n", s_ssid[0] ? "station" : "onboarding AP", s_hostname); + execute(a); + tls_os_task_create(NULL, "nl_net", net_task, NULL, (u8 *)s_stack, sizeof(s_stack), NL_NET_PRIO, 0); +} + +int nl_wifi_request_onboard(const char *ssid, const char *pass) +{ + if (!s_started) + return -1; + lock(); + copy(s_test_ssid, ssid, sizeof s_test_ssid); + copy(s_test_pass, pass, sizeof s_test_pass); + unlock(); + return tls_os_queue_send(s_queue, (void *)MSG_ONBOARD, 0) == TLS_OS_SUCCESS ? 0 : -1; +} + +bool nl_wifi_open_access(void) +{ + bool r; + if (!s_started) + return false; + lock(); + r = nl_wifi_sm_open_access(&s_sm); + unlock(); + return r; +} + +void nl_wifi_status(nl_wifi_status_t *out) +{ + struct tls_ethif *e = tls_netif_get_ethif(); + bool ap_up = false; + + memset(out, 0, sizeof *out); + out->mode = "sta"; + if (s_started) { + lock(); + out->mode = nl_wifi_sm_mode(&s_sm); + out->error = nl_wifi_error_name(s_sm.error); + copy(out->ssid, s_ssid, sizeof out->ssid); + ap_up = s_sm.ap_up || s_sm.state == NL_WIFI_ST_AP_ONBOARDING; + unlock(); + } + if (e && e->status) { + struct tls_curr_bss_t bss; + ip4addr_ntoa_r(ip_2_ip4(&e->ip_addr), out->sta_ip, sizeof out->sta_ip); + memset(&bss, 0, sizeof bss); + tls_wifi_get_current_bss(&bss); + out->rssi = (int)(int8_t)bss.rssi; + } + if (out->sta_ip[0]) + copy(out->ip, out->sta_ip, sizeof out->ip); + else if (ap_up) + copy(out->ip, "192.168.4.1", sizeof out->ip); +} + +const char *nl_wifi_hostname(void) { return s_hostname; } + +void nl_wifi_mac(uint8_t mac[6]) { memcpy(mac, s_mac, 6); } diff --git a/src/sys/nl_app.h b/src/sys/nl_app.h index c9e18a9..0f454af 100644 --- a/src/sys/nl_app.h +++ b/src/sys/nl_app.h @@ -25,4 +25,7 @@ void nl_app_factory_reset(void); * function instead of calling nl_app_factory_reset() directly from another task. */ void nl_app_request_factory_reset(void); +/* Stores new station credentials (from a successful onboarding test) and persists them. */ +int nl_app_set_wifi_credentials(const char *ssid, const char *pass); + #endif diff --git a/src/sys/nl_console.c b/src/sys/nl_console.c index 2dc074e..8317b4f 100644 --- a/src/sys/nl_console.c +++ b/src/sys/nl_console.c @@ -6,6 +6,7 @@ #include "light/nl_control.h" #include "light/nl_engine.h" +#include "net/nl_wifi.h" #include "sys/nl_app.h" #include "sys/nl_console.h" #include "sys/nl_console_cmd.h" @@ -101,6 +102,13 @@ static void exec(const nl_cmd_t *c) case NL_CMD_REBOOT: nl_app_reboot(); return; + case NL_CMD_NET: { + nl_wifi_status_t w; + nl_wifi_status(&w); + printf("net: mode=%s ssid=%s ip=%s sta_ip=%s rssi=%d error=%s host=%s\r\n", w.mode, w.ssid, w.ip, + w.sta_ip[0] ? w.sta_ip : "-", w.rssi, w.error ? w.error : "-", nl_wifi_hostname()); + return; + } case NL_CMD_WDTEST: printf("wdtest: holding app lock forever\r\n"); nl_app_lock(); /* deliberately never released: light_task and app_task stall on it, diff --git a/src/sys/nl_console_cmd.c b/src/sys/nl_console_cmd.c index 42106fb..fcee502 100644 --- a/src/sys/nl_console_cmd.c +++ b/src/sys/nl_console_cmd.c @@ -9,7 +9,7 @@ const char nl_console_help[] = "on [ms] | off [ms] | bri <1-100> [ms] | rgb [ms] | ct [ms]\r\n" "pob last|on|off | fade | blink [n] | pulse on|off\r\n" - "state | factory | reboot | wdtest | help\r\n"; + "state | net | factory | reboot | wdtest | help\r\n"; static bool num(const char *s, int32_t lo, int32_t hi, int32_t *out) { @@ -105,7 +105,7 @@ nl_cmd_t nl_console_parse(const char *line) } else { static const struct { const char *name; nl_cmd_type_t type; } SIMPLE[] = { {"state", NL_CMD_STATE}, {"factory", NL_CMD_FACTORY}, {"reboot", NL_CMD_REBOOT}, - {"help", NL_CMD_HELP}, {"wdtest", NL_CMD_WDTEST}}; + {"help", NL_CMD_HELP}, {"wdtest", NL_CMD_WDTEST}, {"net", NL_CMD_NET}}; ok = false; for (size_t i = 0; i < sizeof SIMPLE / sizeof SIMPLE[0]; i++) if (!strcmp(tok[0], SIMPLE[i].name)) { diff --git a/src/sys/nl_console_cmd.h b/src/sys/nl_console_cmd.h index 8f00bf3..0684d1c 100644 --- a/src/sys/nl_console_cmd.h +++ b/src/sys/nl_console_cmd.h @@ -19,6 +19,7 @@ typedef enum { NL_CMD_REBOOT, NL_CMD_HELP, NL_CMD_WDTEST, + NL_CMD_NET, NL_CMD_ERROR } nl_cmd_type_t; diff --git a/test/test_console_cmd.c b/test/test_console_cmd.c index aafa921..d744ab8 100644 --- a/test/test_console_cmd.c +++ b/test/test_console_cmd.c @@ -82,8 +82,10 @@ static void test_simple_commands(void) TEST_ASSERT_EQUAL(NL_CMD_REBOOT, nl_console_parse("reboot").type); TEST_ASSERT_EQUAL(NL_CMD_HELP, nl_console_parse("help").type); TEST_ASSERT_EQUAL(NL_CMD_WDTEST, nl_console_parse("wdtest").type); + TEST_ASSERT_EQUAL(NL_CMD_NET, nl_console_parse("net").type); TEST_ASSERT_EQUAL(NL_CMD_ERROR, nl_console_parse("state now").type); TEST_ASSERT_EQUAL(NL_CMD_ERROR, nl_console_parse("wdtest now").type); + TEST_ASSERT_EQUAL(NL_CMD_ERROR, nl_console_parse("net now").type); } int main(void)