feat: Ring-Log für den Lichtzustand
This commit is contained in:
@@ -0,0 +1,152 @@
|
|||||||
|
#include "config/nl_state_log.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "light/nl_boot_state.h"
|
||||||
|
#include "util/nl_crc.h"
|
||||||
|
|
||||||
|
#define ENTRY_MAGIC 0xA5u
|
||||||
|
|
||||||
|
static uint32_t s_sector[2];
|
||||||
|
static bool s_have;
|
||||||
|
static nl_light_state_t s_latest;
|
||||||
|
static uint32_t s_seq;
|
||||||
|
static uint32_t s_active;
|
||||||
|
static uint32_t s_next; /* next free slot in the active sector */
|
||||||
|
|
||||||
|
static uint32_t slot_addr(uint32_t sec, uint32_t slot)
|
||||||
|
{
|
||||||
|
return s_sector[sec] + slot * NL_STATE_ENTRY_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void encode(const nl_light_state_t *s, uint32_t seq, uint8_t e[NL_STATE_ENTRY_SIZE])
|
||||||
|
{
|
||||||
|
memset(e, 0, NL_STATE_ENTRY_SIZE);
|
||||||
|
e[0] = ENTRY_MAGIC;
|
||||||
|
e[1] = (uint8_t)((s->on ? 1u : 0u) | (s->mode == NL_MODE_WHITE ? 2u : 0u));
|
||||||
|
e[2] = s->brightness;
|
||||||
|
e[3] = s->r;
|
||||||
|
e[4] = s->g;
|
||||||
|
e[5] = s->b;
|
||||||
|
e[6] = (uint8_t)(s->ct & 0xFFu);
|
||||||
|
e[7] = (uint8_t)(s->ct >> 8);
|
||||||
|
e[8] = (uint8_t)seq;
|
||||||
|
e[9] = (uint8_t)(seq >> 8);
|
||||||
|
e[10] = (uint8_t)(seq >> 16);
|
||||||
|
e[11] = (uint8_t)(seq >> 24);
|
||||||
|
e[15] = nl_crc8(e, NL_STATE_ENTRY_SIZE - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool decode(const uint8_t e[NL_STATE_ENTRY_SIZE], nl_light_state_t *s, uint32_t *seq)
|
||||||
|
{
|
||||||
|
if (e[0] != ENTRY_MAGIC || nl_crc8(e, NL_STATE_ENTRY_SIZE - 1) != e[15])
|
||||||
|
return false;
|
||||||
|
s->on = (e[1] & 1u) != 0;
|
||||||
|
s->mode = (e[1] & 2u) ? NL_MODE_WHITE : NL_MODE_COLOR;
|
||||||
|
s->brightness = e[2];
|
||||||
|
s->r = e[3];
|
||||||
|
s->g = e[4];
|
||||||
|
s->b = e[5];
|
||||||
|
s->ct = (uint16_t)(e[6] | (e[7] << 8));
|
||||||
|
*seq = (uint32_t)e[8] | ((uint32_t)e[9] << 8) | ((uint32_t)e[10] << 16) | ((uint32_t)e[11] << 24);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_free(const uint8_t e[NL_STATE_ENTRY_SIZE])
|
||||||
|
{
|
||||||
|
for (uint32_t i = 0; i < NL_STATE_ENTRY_SIZE; i++)
|
||||||
|
if (e[i] != 0xFF)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t next_free_slot(uint32_t sec)
|
||||||
|
{
|
||||||
|
uint8_t e[NL_STATE_ENTRY_SIZE];
|
||||||
|
uint32_t next = 0;
|
||||||
|
for (uint32_t slot = 0; slot < NL_STATE_LOG_SLOTS; slot++) {
|
||||||
|
if (nl_flash_read(slot_addr(sec, slot), e, sizeof e) != 0 || !is_free(e))
|
||||||
|
next = slot + 1;
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nl_state_log_init(uint32_t sector0, uint32_t sector1)
|
||||||
|
{
|
||||||
|
uint8_t e[NL_STATE_ENTRY_SIZE];
|
||||||
|
nl_light_state_t st;
|
||||||
|
uint32_t seq;
|
||||||
|
|
||||||
|
s_sector[0] = sector0;
|
||||||
|
s_sector[1] = sector1;
|
||||||
|
s_have = false;
|
||||||
|
s_seq = 0;
|
||||||
|
s_active = 0;
|
||||||
|
for (uint32_t sec = 0; sec < 2; sec++) {
|
||||||
|
for (uint32_t slot = 0; slot < NL_STATE_LOG_SLOTS; slot++) {
|
||||||
|
if (nl_flash_read(slot_addr(sec, slot), e, sizeof e) != 0)
|
||||||
|
continue;
|
||||||
|
if (decode(e, &st, &seq) && (!s_have || seq > s_seq)) {
|
||||||
|
s_have = true;
|
||||||
|
s_seq = seq;
|
||||||
|
s_latest = st;
|
||||||
|
s_active = sec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s_next = next_free_slot(s_active);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nl_state_log_load(nl_light_state_t *out)
|
||||||
|
{
|
||||||
|
if (!s_have)
|
||||||
|
return false;
|
||||||
|
*out = s_latest;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int nl_state_log_append(const nl_light_state_t *s)
|
||||||
|
{
|
||||||
|
uint8_t e[NL_STATE_ENTRY_SIZE];
|
||||||
|
uint32_t sec = s_active;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
if (s_have && nl_light_state_equal(&s_latest, s))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!s_have) {
|
||||||
|
sec = 0;
|
||||||
|
if (s_active != 0 || s_next != 0) {
|
||||||
|
if (nl_flash_erase_sector(s_sector[0]) != 0)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
s_next = 0;
|
||||||
|
} else if (s_next >= NL_STATE_LOG_SLOTS) {
|
||||||
|
sec = 1u - s_active;
|
||||||
|
if (nl_flash_erase_sector(s_sector[sec]) != 0)
|
||||||
|
return -1;
|
||||||
|
s_next = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
encode(s, s_seq + 1, e);
|
||||||
|
r = nl_flash_write(slot_addr(sec, s_next), e, sizeof e);
|
||||||
|
s_active = sec;
|
||||||
|
s_next++; /* also skip a half written slot */
|
||||||
|
if (r != 0)
|
||||||
|
return -1;
|
||||||
|
s_seq++;
|
||||||
|
s_latest = *s;
|
||||||
|
s_have = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int nl_state_log_erase(void)
|
||||||
|
{
|
||||||
|
int r0 = nl_flash_erase_sector(s_sector[0]);
|
||||||
|
int r1 = nl_flash_erase_sector(s_sector[1]);
|
||||||
|
s_have = false;
|
||||||
|
s_seq = 0;
|
||||||
|
s_active = 0;
|
||||||
|
s_next = 0;
|
||||||
|
return (r0 == 0 && r1 == 0) ? 0 : -1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#ifndef NL_STATE_LOG_H
|
||||||
|
#define NL_STATE_LOG_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "config/nl_flash.h"
|
||||||
|
#include "light/nl_light_types.h"
|
||||||
|
|
||||||
|
#define NL_STATE_ENTRY_SIZE 16u
|
||||||
|
#define NL_STATE_LOG_SLOTS (NL_FLASH_SECTOR_SIZE / NL_STATE_ENTRY_SIZE)
|
||||||
|
|
||||||
|
void nl_state_log_init(uint32_t sector0, uint32_t sector1);
|
||||||
|
bool nl_state_log_load(nl_light_state_t *out);
|
||||||
|
int nl_state_log_append(const nl_light_state_t *s);
|
||||||
|
int nl_state_log_erase(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -31,3 +31,5 @@ nl_test(test_boot_state test_boot_state.c ${NL_SRC}/light/nl_boot_state.c ${NL_S
|
|||||||
nl_test(test_control test_control.c ${NL_SRC}/light/nl_control.c ${NL_SRC}/light/nl_boot_state.c
|
nl_test(test_control test_control.c ${NL_SRC}/light/nl_control.c ${NL_SRC}/light/nl_boot_state.c
|
||||||
${NL_SRC}/light/nl_engine.c ${NL_SRC}/light/nl_color.c ${NL_SRC}/board/nl_board.c)
|
${NL_SRC}/light/nl_engine.c ${NL_SRC}/light/nl_color.c ${NL_SRC}/board/nl_board.c)
|
||||||
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_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)
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include "unity.h"
|
||||||
|
#include "fake_flash.h"
|
||||||
|
#include "config/nl_flash_layout.h"
|
||||||
|
#include "config/nl_state_log.h"
|
||||||
|
#include "light/nl_boot_state.h"
|
||||||
|
|
||||||
|
static nl_light_state_t st(uint8_t bri)
|
||||||
|
{
|
||||||
|
nl_light_state_t s = {true, bri, NL_MODE_COLOR, 10, 20, 30, 3000};
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void reboot(void)
|
||||||
|
{
|
||||||
|
nl_state_log_init(NL_FL_STATE_0, NL_FL_STATE_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setUp(void)
|
||||||
|
{
|
||||||
|
fake_flash_reset();
|
||||||
|
reboot();
|
||||||
|
}
|
||||||
|
|
||||||
|
void tearDown(void)
|
||||||
|
{
|
||||||
|
TEST_ASSERT_EQUAL_UINT(0, fake_flash_dirty_writes);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_empty(void)
|
||||||
|
{
|
||||||
|
nl_light_state_t s;
|
||||||
|
TEST_ASSERT_FALSE(nl_state_log_load(&s));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_append_and_reload(void)
|
||||||
|
{
|
||||||
|
nl_light_state_t in = st(42), out;
|
||||||
|
in.mode = NL_MODE_WHITE;
|
||||||
|
in.on = false;
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, nl_state_log_append(&in));
|
||||||
|
reboot();
|
||||||
|
TEST_ASSERT_TRUE(nl_state_log_load(&out));
|
||||||
|
TEST_ASSERT_TRUE(nl_light_state_equal(&in, &out));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_equal_state_is_not_written_again(void)
|
||||||
|
{
|
||||||
|
nl_light_state_t s = st(42);
|
||||||
|
nl_state_log_append(&s);
|
||||||
|
nl_state_log_append(&s);
|
||||||
|
for (int i = 0; i < 16; i++)
|
||||||
|
TEST_ASSERT_EQUAL_HEX8(0xFF, fake_flash_mem[NL_FL_STATE_0 + 16 + i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_wraps_into_second_sector(void)
|
||||||
|
{
|
||||||
|
nl_light_state_t s, out;
|
||||||
|
for (int i = 0; i < 300; i++) {
|
||||||
|
s = st((uint8_t)(1 + i % 100));
|
||||||
|
s.r = (uint8_t)i; /* make every entry distinct */
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, nl_state_log_append(&s));
|
||||||
|
}
|
||||||
|
TEST_ASSERT_EQUAL_UINT(1, fake_flash_erases[NL_FL_STATE_1 / NL_FLASH_SECTOR_SIZE]);
|
||||||
|
reboot();
|
||||||
|
TEST_ASSERT_TRUE(nl_state_log_load(&out));
|
||||||
|
TEST_ASSERT_TRUE(nl_light_state_equal(&s, &out));
|
||||||
|
/* keep going after the reboot */
|
||||||
|
s = st(77);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, nl_state_log_append(&s));
|
||||||
|
reboot();
|
||||||
|
TEST_ASSERT_TRUE(nl_state_log_load(&out));
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(77, out.brightness);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_wraps_back_to_first_sector(void)
|
||||||
|
{
|
||||||
|
nl_light_state_t s, out;
|
||||||
|
for (int i = 0; i < 600; i++) {
|
||||||
|
s = st(50);
|
||||||
|
s.r = (uint8_t)i;
|
||||||
|
s.g = (uint8_t)(i >> 8);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, nl_state_log_append(&s));
|
||||||
|
}
|
||||||
|
TEST_ASSERT_EQUAL_UINT(1, fake_flash_erases[NL_FL_STATE_0 / NL_FLASH_SECTOR_SIZE]);
|
||||||
|
reboot();
|
||||||
|
TEST_ASSERT_TRUE(nl_state_log_load(&out));
|
||||||
|
TEST_ASSERT_TRUE(nl_light_state_equal(&s, &out));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_power_loss_during_append(void)
|
||||||
|
{
|
||||||
|
nl_light_state_t a = st(10), b = st(20), c = st(30), out;
|
||||||
|
nl_state_log_append(&a);
|
||||||
|
fake_flash_fail_after(5);
|
||||||
|
TEST_ASSERT_NOT_EQUAL(0, nl_state_log_append(&b));
|
||||||
|
fake_flash_fail_after(-1);
|
||||||
|
reboot();
|
||||||
|
TEST_ASSERT_TRUE(nl_state_log_load(&out));
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(10, out.brightness);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, nl_state_log_append(&c));
|
||||||
|
reboot();
|
||||||
|
TEST_ASSERT_TRUE(nl_state_log_load(&out));
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(30, out.brightness);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_garbage_in_empty_log_is_erased_first(void)
|
||||||
|
{
|
||||||
|
nl_light_state_t s = st(60), out;
|
||||||
|
fake_flash_mem[NL_FL_STATE_0 + 3] = 0x12; /* leftovers from the old firmware */
|
||||||
|
reboot();
|
||||||
|
TEST_ASSERT_FALSE(nl_state_log_load(&out));
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, nl_state_log_append(&s));
|
||||||
|
reboot();
|
||||||
|
TEST_ASSERT_TRUE(nl_state_log_load(&out));
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(60, out.brightness);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_erase(void)
|
||||||
|
{
|
||||||
|
nl_light_state_t s = st(60), out;
|
||||||
|
nl_state_log_append(&s);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, nl_state_log_erase());
|
||||||
|
TEST_ASSERT_FALSE(nl_state_log_load(&out));
|
||||||
|
reboot();
|
||||||
|
TEST_ASSERT_FALSE(nl_state_log_load(&out));
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
UNITY_BEGIN();
|
||||||
|
RUN_TEST(test_empty);
|
||||||
|
RUN_TEST(test_append_and_reload);
|
||||||
|
RUN_TEST(test_equal_state_is_not_written_again);
|
||||||
|
RUN_TEST(test_wraps_into_second_sector);
|
||||||
|
RUN_TEST(test_wraps_back_to_first_sector);
|
||||||
|
RUN_TEST(test_power_loss_during_append);
|
||||||
|
RUN_TEST(test_garbage_in_empty_log_is_erased_first);
|
||||||
|
RUN_TEST(test_erase);
|
||||||
|
return UNITY_END();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user