From 663825c16599e4676147683e5910cdb7ecf73e04 Mon Sep 17 00:00:00 2001 From: Michael Sauer Date: Fri, 11 Sep 2026 13:39:01 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Power-Cycle-Z=C3=A4hler=20f=C3=BCr=20de?= =?UTF-8?q?n=20Werksreset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/nl_reset_counter.c | 58 +++++++++++++++++++++++++++ src/config/nl_reset_counter.h | 13 ++++++ test/CMakeLists.txt | 1 + test/test_reset_counter.c | 75 +++++++++++++++++++++++++++++++++++ 4 files changed, 147 insertions(+) create mode 100644 src/config/nl_reset_counter.c create mode 100644 src/config/nl_reset_counter.h create mode 100644 test/test_reset_counter.c diff --git a/src/config/nl_reset_counter.c b/src/config/nl_reset_counter.c new file mode 100644 index 0000000..6ea2f35 --- /dev/null +++ b/src/config/nl_reset_counter.c @@ -0,0 +1,58 @@ +#include "config/nl_reset_counter.h" + +#include "config/nl_flash.h" + +#define REC_TAG 0x5A000000u +#define REC_MASK 0xFF000000u +#define REC_FREE 0xFFFFFFFFu +#define REC_SLOTS (NL_FLASH_SECTOR_SIZE / 4u) + +static uint32_t s_sector; +static uint32_t s_next; +static uint8_t s_count; + +void nl_resetcnt_init(uint32_t sector) +{ + uint32_t w; + + s_sector = sector; + s_next = 0; + s_count = 0; + for (uint32_t i = 0; i < REC_SLOTS; i++) { + if (nl_flash_read(sector + i * 4u, &w, 4) != 0 || w == REC_FREE) + continue; + s_next = i + 1; + s_count = ((w & REC_MASK) == REC_TAG) ? (uint8_t)(w & 0xFFu) : 0; + } +} + +static int put(uint8_t count) +{ + uint32_t w = REC_TAG | count; + int r; + + if (s_next >= REC_SLOTS) { + if (nl_flash_erase_sector(s_sector) != 0) + return -1; + s_next = 0; + } + r = nl_flash_write(s_sector + s_next * 4u, &w, 4); + s_next++; + if (r == 0) + s_count = count; + return r; +} + +uint8_t nl_resetcnt_boot(void) +{ + uint8_t c = s_count < 255 ? (uint8_t)(s_count + 1) : 255; + put(c); + return c; +} + +int nl_resetcnt_clear(void) +{ + if (s_count == 0) + return 0; + return put(0); +} diff --git a/src/config/nl_reset_counter.h b/src/config/nl_reset_counter.h new file mode 100644 index 0000000..9845687 --- /dev/null +++ b/src/config/nl_reset_counter.h @@ -0,0 +1,13 @@ +#ifndef NL_RESET_COUNTER_H +#define NL_RESET_COUNTER_H + +#include + +#define NL_FACTORY_RESET_BOOTS 5u +#define NL_RESETCNT_CLEAR_MS 5000u + +void nl_resetcnt_init(uint32_t sector); +uint8_t nl_resetcnt_boot(void); +int nl_resetcnt_clear(void); + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a377612..e2416fa 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -33,3 +33,4 @@ nl_test(test_control test_control.c ${NL_SRC}/light/nl_control.c ${NL_SRC}/light nl_test(test_settings test_settings.c fake_flash.c ${NL_SRC}/config/nl_settings.c ${NL_SRC}/util/nl_crc.c) nl_test(test_state_log test_state_log.c fake_flash.c ${NL_SRC}/config/nl_state_log.c ${NL_SRC}/light/nl_boot_state.c ${NL_SRC}/util/nl_crc.c) +nl_test(test_reset_counter test_reset_counter.c fake_flash.c ${NL_SRC}/config/nl_reset_counter.c) diff --git a/test/test_reset_counter.c b/test/test_reset_counter.c new file mode 100644 index 0000000..b3ad9d3 --- /dev/null +++ b/test/test_reset_counter.c @@ -0,0 +1,75 @@ +#include "unity.h" +#include "fake_flash.h" +#include "config/nl_flash_layout.h" +#include "config/nl_reset_counter.h" + +static uint8_t boot(void) +{ + nl_resetcnt_init(NL_FL_RESETCNT); + return nl_resetcnt_boot(); +} + +void setUp(void) { fake_flash_reset(); } + +void tearDown(void) +{ + TEST_ASSERT_EQUAL_UINT(0, fake_flash_dirty_writes); +} + +static void test_first_boot_is_one(void) +{ + TEST_ASSERT_EQUAL_UINT8(1, boot()); +} + +static void test_fast_boots_count_up(void) +{ + for (uint8_t i = 1; i <= 5; i++) + TEST_ASSERT_EQUAL_UINT8(i, boot()); +} + +static void test_clear_resets(void) +{ + boot(); + boot(); + TEST_ASSERT_EQUAL_INT(0, nl_resetcnt_clear()); + TEST_ASSERT_EQUAL_UINT8(1, boot()); +} + +static void test_clear_twice_writes_once(void) +{ + boot(); + nl_resetcnt_clear(); + nl_resetcnt_clear(); + /* slot 0: count 1, slot 1: count 0, slot 2 still free */ + TEST_ASSERT_EQUAL_HEX8(0xFF, fake_flash_mem[NL_FL_RESETCNT + 8]); +} + +static void test_wraps_after_full_sector(void) +{ + for (int i = 0; i < 700; i++) { + boot(); + nl_resetcnt_clear(); + } + TEST_ASSERT_EQUAL_UINT(1, fake_flash_erases[NL_FL_RESETCNT / NL_FLASH_SECTOR_SIZE]); + TEST_ASSERT_EQUAL_UINT8(1, boot()); + TEST_ASSERT_EQUAL_UINT8(2, boot()); +} + +static void test_garbage_counts_as_zero(void) +{ + fake_flash_mem[NL_FL_RESETCNT + 0] = 0x12; + fake_flash_mem[NL_FL_RESETCNT + 3] = 0x00; + TEST_ASSERT_EQUAL_UINT8(1, boot()); +} + +int main(void) +{ + UNITY_BEGIN(); + RUN_TEST(test_first_boot_is_one); + RUN_TEST(test_fast_boots_count_up); + RUN_TEST(test_clear_resets); + RUN_TEST(test_clear_twice_writes_once); + RUN_TEST(test_wraps_after_full_sector); + RUN_TEST(test_garbage_counts_as_zero); + return UNITY_END(); +}