Files
ninlight-w600/src/app_main.c
T
Michael Sauer 4f270ad79b fix: Watchdog wirksam machen, Factory-Reset im App-Task, Resetzähler-Retry
- vApplicationIdleHook() füttert den Watchdog nicht mehr (sdk/Platform/Sys/wm_main.c,
  dokumentiert als Patch 4 in sdk/PATCHES.md). Bisher fütterte der Idle-Task den
  Watchdog bei jedem Leerlauf, auch wenn ein Task deadlockt (z.B. nl_app_lock() hält
  und nie freigibt) - der Watchdog konnte so nie greifen.
- Neues Konsolenkommando `wdtest` (Parser in nl_console_cmd.c/.h, Ausführung in
  nl_console.c, Host-Test in test_console_cmd.c): hält nl_app_lock() für immer, damit
  light_task und app_task blockieren und nur der Watchdog-Reset den Chip retten kann.
- nl_app_request_factory_reset() (nl_app.h) setzt nur ein volatile Flag; app_task
  prüft es in der Hauptschleife und ruft nl_app_factory_reset() selbst auf, da nur der
  App-Task die Flash-Stores besitzt. Das Konsolenkommando `factory` ruft jetzt diese
  Funktion statt nl_app_factory_reset() direkt aufzurufen, um die Race mit
  nl_state_log_append()/nl_resetcnt_clear() im App-Task zu vermeiden. Der Bootpfad
  (Bootzähler >= 5) ruft weiterhin direkt nl_app_factory_reset() auf.
- nl_resetcnt_clear() wird nur noch als erledigt markiert, wenn es 0 zurückgibt, damit
  ein fehlgeschlagener Clear in der nächsten Schleifeniteration erneut versucht wird.
2026-09-11 20:53:25 +02:00

168 lines
4.7 KiB
C

#include <stdio.h>
#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_engine.h"
#include "sys/nl_app.h"
#include "sys/nl_console.h"
#define NL_LIGHT_PRIO 30
#define NL_APP_PRIO 40
static tls_os_mutex_t *s_lock;
static nl_settings_t s_settings;
static volatile uint32_t s_light_beat;
static volatile bool s_factory_reset_requested;
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); }
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)
{
int r;
nl_app_lock();
r = nl_settings_save(&s_settings);
nl_app_unlock();
return r;
}
void nl_app_request_factory_reset(void) { s_factory_reset_requested = true; }
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 */
nl_settings_erase();
nl_state_log_erase();
nl_resetcnt_clear();
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_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, counter_cleared = false;
uint8_t boots;
uint32_t boot_ms, beat_seen;
(void)arg;
nl_flash_init();
nl_led_init(board); /* hardware init first (spec 3.4): LEDs dark before any flash/state scan */
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);
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);
nl_app_lock();
nl_engine_init(&cfg);
nl_ctrl_init(board, s_settings.fade_in_ms, s_settings.fade_out_ms);
nl_ctrl_boot(&initial);
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_console_start();
boot_ms = nl_app_now_ms();
beat_seen = s_light_beat;
for (;;) {
uint32_t now;
bool due;
tls_os_time_delay(HZ / 10); /* 100 ms */
now = nl_app_now_ms();
if (!counter_cleared && now - boot_ms >= NL_RESETCNT_CLEAR_MS)
counter_cleared = nl_resetcnt_clear() == 0;
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 && nl_state_log_append(&pending) == 0) {
nl_app_lock();
nl_ctrl_persisted(&pending);
nl_app_unlock();
}
if (s_light_beat != beat_seen) {
beat_seen = s_light_beat;
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;
}
tls_os_mutex_create(0, &s_lock);
tls_os_task_create(NULL, "nl_app", app_task, NULL, (u8 *)s_app_stack, sizeof(s_app_stack), NL_APP_PRIO, 0);
}