#include #include #include "wm_include.h" #include "wm_watchdog.h" #include "board/nl_board.h" #include "config/nl_flash.h" #include "config/nl_flash_layout.h" #include "config/nl_reset_counter.h" #include "config/nl_settings.h" #include "config/nl_state_log.h" #include "drivers/nl_led.h" #include "light/nl_boot_state.h" #include "light/nl_control.h" #include "light/nl_dmx.h" #include "light/nl_engine.h" #include "net/nl_http_server.h" #include "net/nl_mdns.h" #include "net/nl_recovery.h" #include "net/nl_sacn.h" #include "net/nl_wifi.h" #include "sys/nl_app.h" #include "sys/nl_app_api.h" #include "sys/nl_console.h" #include "sys/nl_ota.h" #include "nl_version.h" #define NL_LIGHT_PRIO 30 #define NL_APP_PRIO 40 static tls_os_mutex_t *s_lock; static tls_os_mutex_t *s_store_lock; /* serialises all nl_settings_* / nl_state_log_* / nl_resetcnt_* calls */ static nl_settings_t s_settings; static volatile uint32_t s_light_beat; static volatile bool s_factory_reset_requested; static volatile bool s_counter_cleared; static u32 s_app_stack[1024]; static u32 s_light_stack[512]; void nl_app_lock(void) { tls_os_mutex_acquire(s_lock, 0); } void nl_app_unlock(void) { tls_os_mutex_release(s_lock); } static void store_lock(void) { tls_os_mutex_acquire(s_store_lock, 0); } static void store_unlock(void) { tls_os_mutex_release(s_store_lock); } uint32_t nl_app_now_ms(void) { return (uint32_t)((uint64_t)tls_os_get_time() * 1000u / HZ); } nl_settings_t *nl_app_settings(void) { return &s_settings; } int nl_app_save_settings(void) { static nl_settings_t copy; /* guarded by the store lock */ int r; store_lock(); nl_app_lock(); copy = s_settings; nl_app_unlock(); r = nl_settings_save(©); store_unlock(); return r; } void nl_app_flush(void) { nl_light_state_t st; store_lock(); nl_app_lock(); nl_ctrl_get(&st); nl_app_unlock(); if (nl_state_log_append(&st) == 0) { /* no write when unchanged */ nl_app_lock(); nl_ctrl_persisted(&st); nl_app_unlock(); } store_unlock(); } void nl_app_reboot(void) { printf("reboot\r\n"); nl_app_flush(); tls_sys_reset(); } void nl_app_request_factory_reset(void) { s_factory_reset_requested = true; } void nl_app_clear_reset_counter(void) { if (s_counter_cleared) return; store_lock(); s_counter_cleared = nl_resetcnt_clear() == 0; store_unlock(); } 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"); nl_app_lock(); nl_engine_blink(3); nl_app_unlock(); tls_os_time_delay(2 * HZ); /* 3 blinks = 1.8 s, the light task keeps running */ store_lock(); nl_settings_erase(); nl_state_log_erase(); nl_resetcnt_clear(); store_unlock(); /* C2: also wipe the SDK's own Wi-Fi params (SSID/key/WPROTOCOL, incl. any leftover * softap bit) - verified in sdk/Platform/Common/Params/wm_param.c that * tls_param_to_default() (tls_param_load_factory_default() + param_to_flash()) only * touches struct tls_sys_param (Wi-Fi/network config); it never touches the MAC * address or RF calibration data, which live in a separate efuse-backed FT_PARAM_ST * area (sdk/Platform/Drivers/efuse/wm_efuse.c) that this call never reads or writes. */ tls_param_to_default(); tls_sys_reset(); } static void led_out(const uint8_t ch[NL_CH_COUNT], void *ctx) { (void)ctx; nl_led_set(ch); } static void light_task(void *arg) { uint32_t last = nl_app_now_ms(); (void)arg; for (;;) { uint32_t now; tls_os_time_delay(HZ / 100 ? HZ / 100 : 1); /* ~10 ms */ now = nl_app_now_ms(); nl_app_lock(); nl_engine_tick(now - last); nl_dmx_tick(now); nl_app_unlock(); last = now; s_light_beat++; } } static void app_task(void *arg) { const nl_board_t *board = nl_board_get(); uint32_t base = nl_flash_region_base(); nl_engine_cfg_t cfg; nl_light_state_t last, initial, pending; bool have_last, after_ota; bool light_alive = false, net_alive = false, http_alive = false, sacn_alive = false; bool sacn_required; uint8_t boots; uint32_t boot_ms, light_seen, net_seen, http_seen, sacn_seen; (void)arg; /* I2: the 5 s of spec 3.4 step 7 / 5.5 count from power-up, not from "after the recovery * scan" - otherwise the documented "< 5 s per power cycle" window silently becomes ~8 s. */ boot_ms = nl_app_now_ms(); nl_flash_init(); nl_led_init(board); /* hardware init first (spec 3.4): LEDs dark before any flash/state scan */ store_lock(); nl_resetcnt_init(base + NL_FL_RESETCNT); boots = nl_resetcnt_boot(); nl_settings_init(base + NL_FL_SETTINGS_A, base + NL_FL_SETTINGS_B); nl_settings_load(&s_settings); nl_state_log_init(base + NL_FL_STATE_0, base + NL_FL_STATE_1); have_last = nl_state_log_load(&last); store_unlock(); after_ota = s_settings.ota_pending != 0; if (after_ota) { /* We came back from an update attempt (accepted or rejected). Clear the flag so it does * not stick in flash forever; nl_boot_state() below uses `after_ota` to restore the light * effectively as power_on_behaviour = last (spec 8.2: a reboot caused by an update is not * a power cut). */ s_settings.ota_pending = 0; printf("boot after ota attempt\r\n"); store_lock(); (void)nl_settings_save(&s_settings); store_unlock(); } printf("boot #%u, settings: pob=%u fade=%u/%u, last state: %s\r\n", boots, s_settings.power_on_behaviour, s_settings.fade_in_ms, s_settings.fade_out_ms, have_last ? "yes" : "no"); cfg.ct_min = board->ct_min; cfg.ct_max = board->ct_max; cfg.output = led_out; cfg.ctx = NULL; initial = nl_boot_state((nl_pob_t)s_settings.power_on_behaviour, have_last ? &last : NULL, board, after_ota); nl_app_lock(); nl_engine_init(&cfg); nl_ctrl_init(board, s_settings.fade_in_ms, s_settings.fade_out_ms, s_settings.transition_ms, s_settings.wake_on_change != 0); nl_ctrl_boot(&initial); nl_dmx_init(board); nl_app_unlock(); tls_os_task_create(NULL, "nl_light", light_task, NULL, (u8 *)s_light_stack, sizeof(s_light_stack), NL_LIGHT_PRIO, 0); if (boots >= NL_FACTORY_RESET_BOOTS) nl_app_factory_reset(); /* does not return */ 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 */ { 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); } nl_http_server_start(nl_app_api_ctx()); { static nl_settings_t sacn_boot; /* copy: the network task may change s_settings later */ nl_app_lock(); sacn_boot = s_settings; nl_app_unlock(); nl_sacn_apply_settings(&sacn_boot); } nl_sacn_start(); /* Nur ein tatsaechlich gestarteter Task darf den Wachhund mitbestimmen: ohne Task * bewegt sich nl_sacn_beat nie, die Bedingung waere nie erfuellt und die Lampe liefe * in eine endlose Reset-Schleife. Ein Zusatzdienst faellt dann eben aus. */ sacn_required = nl_sacn_running(); light_seen = s_light_beat; net_seen = nl_net_beat; http_seen = nl_http_beat; sacn_seen = nl_sacn_beat; for (;;) { uint32_t now; bool due; tls_os_time_delay(HZ / 10); /* 100 ms */ now = nl_app_now_ms(); if (!s_counter_cleared && now - boot_ms >= NL_RESETCNT_CLEAR_MS) nl_app_clear_reset_counter(); if (s_factory_reset_requested) nl_app_factory_reset(); /* does not return */ nl_app_lock(); due = nl_ctrl_persist_due(now, &pending); nl_app_unlock(); if (due) { int r; store_lock(); r = nl_state_log_append(&pending); store_unlock(); if (r == 0) { nl_app_lock(); nl_ctrl_persisted(&pending); nl_app_unlock(); } } /* Feed the watchdog only once the light, network, HTTP and sACN tasks have each * made progress since the last feed - a single stuck task (e.g. nl_app_lock() held * forever) must still starve the watchdog, not just the light task. Ausnahme: der * sACN-Task, wenn er gar nicht erst angelegt werden konnte (sacn_required). */ if (s_light_beat != light_seen) { light_seen = s_light_beat; light_alive = true; } if (nl_net_beat != net_seen) { net_seen = nl_net_beat; net_alive = true; } if (nl_http_beat != http_seen) { http_seen = nl_http_beat; http_alive = true; } if (nl_sacn_beat != sacn_seen) { sacn_seen = nl_sacn_beat; sacn_alive = true; } if (light_alive && net_alive && http_alive && (sacn_alive || !sacn_required)) { light_alive = net_alive = http_alive = sacn_alive = false; tls_watchdog_clr(); } } } void UserMain(void) { printf("\r\nninlight %s for %s (built %s)\r\n", NINLIGHT_FW_VERSION, NINLIGHT_DEVICE_ID, NINLIGHT_BUILD_DATE); if (nl_board_get() == NULL) { printf("FATAL: unknown board %s\r\n", NINLIGHT_DEVICE_ID); return; } if (tls_os_mutex_create(0, &s_lock) != TLS_OS_SUCCESS) { printf("FATAL: cannot create app lock\r\n"); return; } if (tls_os_mutex_create(0, &s_store_lock) != TLS_OS_SUCCESS) { printf("FATAL: cannot create store lock\r\n"); return; } tls_os_task_create(NULL, "nl_app", app_task, NULL, (u8 *)s_app_stack, sizeof(s_app_stack), NL_APP_PRIO, 0); }