From 37a5fe2ca9da00dc6ac3353e748c28af6502e2cb Mon Sep 17 00:00:00 2001 From: Michael Sauer Date: Sat, 12 Sep 2026 11:22:17 +0200 Subject: [PATCH] fix: uebersprungenes Recovery-Image ist kein Fehler mehr Lehnte die Recovery ein Image ab, weil es bereits die laufende Version traegt, meldete die Firmware das als last_ota_error=trailer. Wer eine Lampe im Feld ausliest, haette dort einen Trailer-Fehler gesehen, wo in Wahrheit nichts passiert ist - und die Meldung in der Konsole lautete "update failed". Das bewusste Ueberspringen hat jetzt einen eigenen Zustand (nl_ota_skipped()), setzt keinen Fehlerwert und wird als "image already up to date" protokolliert. Co-Authored-By: Claude Opus 5 --- src/net/nl_recovery.c | 12 +++++++++--- src/sys/nl_ota.c | 10 ++++++++-- src/sys/nl_ota.h | 3 +++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/net/nl_recovery.c b/src/net/nl_recovery.c index 64a9c4d..f691665 100644 --- a/src/net/nl_recovery.c +++ b/src/net/nl_recovery.c @@ -162,8 +162,14 @@ void nl_recovery_try(void) tls_os_time_delay(HZ / 2); waited += 500; } - if (feeding) - printf("recovery: update failed (%s), continuing normal boot\r\n", - nl_ota_last_error() ? nl_ota_last_error() : "unknown"); + if (feeding) { + /* A skipped image (same version as the running firmware) is not a failure - saying so + would leave a healthy lamp looking like it had rejected a broken image. */ + if (nl_ota_skipped()) + printf("recovery: image already up to date, continuing normal boot\r\n"); + else + printf("recovery: update failed (%s), continuing normal boot\r\n", + nl_ota_last_error() ? nl_ota_last_error() : "unknown"); + } } } diff --git a/src/sys/nl_ota.c b/src/sys/nl_ota.c index 3025587..6935946 100644 --- a/src/sys/nl_ota.c +++ b/src/sys/nl_ota.c @@ -23,6 +23,7 @@ static char s_url[NL_OTA_URL_MAX]; static volatile bool s_busy; static volatile bool s_reject_running_version; /* recovery path only, see nl_ota_start_recovery() */ static const char * volatile s_error; +static volatile bool s_skipped; static u32 s_stack[NL_OTA_STACK]; static char s_tail_version[NL_TRAILER_VERSION_LEN]; /* used by the hook, kept off its 2 KB stack */ @@ -33,6 +34,7 @@ void nl_ota_init(void) } bool nl_ota_busy(void) { return s_busy; } +bool nl_ota_skipped(void) { return s_skipped; } const char *nl_ota_last_error(void) { return s_error; } void nl_ota_set_error(const char *e) { s_error = e; } @@ -61,7 +63,10 @@ bool nl_fwup_verify_tail(const uint8_t sha256[32], const uint8_t *tail, uint32_t if (strcmp(s_tail_version, NINLIGHT_FW_VERSION) == 0) { printf("ota: recovery image already carries the running version %s, not flashing\r\n", s_tail_version); - s_error = "trailer"; + /* Deliberately skipped, not failed: leave last_ota_error alone. Reporting "trailer" + * here used to make a perfectly healthy lamp look like it had rejected a malformed + * image, which is exactly the wrong thing to read off a lamp in the field. */ + s_skipped = true; return false; } printf("ota: recovery image version %s (running %s)\r\n", s_tail_version, NINLIGHT_FW_VERSION); @@ -90,7 +95,7 @@ static void ota_task(void *arg) log_url(s_url); rc = t_http_fwup(s_url); /* Only reached on failure - the SDK resets the chip itself after a good image. */ - if (s_error == NULL) + if (s_error == NULL && !s_skipped) s_error = "download"; printf("ota: failed (rc=%d, %s)\r\n", rc, s_error); { @@ -125,6 +130,7 @@ static int ota_start(const char *url, bool reject_running_version) } s_busy = true; s_error = NULL; + s_skipped = false; s_reject_running_version = reject_running_version; memcpy(s_url, url, n + 1); diff --git a/src/sys/nl_ota.h b/src/sys/nl_ota.h index 62d0d6b..603297e 100644 --- a/src/sys/nl_ota.h +++ b/src/sys/nl_ota.h @@ -12,6 +12,9 @@ int nl_ota_start(const char *url); * forever. A deliberate re-flash of the same version stays possible over the REST API. */ int nl_ota_start_recovery(const char *url); bool nl_ota_busy(void); +/* True when the last recovery attempt deliberately skipped an image whose version already + matches the running firmware - that is not a failure and sets no last_ota_error. */ +bool nl_ota_skipped(void); /* NULL, or one of: download, crc, trailer, device_mismatch, hash, busy */ const char *nl_ota_last_error(void); void nl_ota_set_error(const char *e);