feat: Flash-HAL-Interface und Settings-Store mit A/B-Slots

Implementiert:
- Flash HAL (nl_flash_read/write/erase_sector) Interfaces
- Flash Layout mit A/B-Settings Slots und weiteren Regions
- Test-Fake für Flash mit Fehlerinjection (Stromausfall-Simulation)
- Settings-Store mit CRC32 Recovery und Slot-Alternierung
- Vollständige Test-Suite mit Corruption/Power-Loss-Tests
This commit is contained in:
Michael Sauer
2026-09-11 13:24:52 +02:00
parent e0bde7bee3
commit 91721ac47d
8 changed files with 360 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
#ifndef NL_FLASH_H
#define NL_FLASH_H
#include <stdint.h>
#define NL_FLASH_SECTOR_SIZE 4096u
/* All functions return 0 on success, < 0 on error. Addresses are absolute. */
int nl_flash_read(uint32_t addr, void *buf, uint32_t len);
/* The target bytes must be erased (0xFF); the stores never rely on implicit erases. */
int nl_flash_write(uint32_t addr, const void *buf, uint32_t len);
int nl_flash_erase_sector(uint32_t addr);
/* Start of the ninlight flash region (USER_ADDR_START + one sector on the W600). */
uint32_t nl_flash_region_base(void);
#endif
+12
View File
@@ -0,0 +1,12 @@
#ifndef NL_FLASH_LAYOUT_H
#define NL_FLASH_LAYOUT_H
/* Offsets inside the ninlight flash region, one 4 KB sector each. */
#define NL_FL_SETTINGS_A 0x0000u
#define NL_FL_SETTINGS_B 0x1000u
#define NL_FL_STATE_0 0x2000u
#define NL_FL_STATE_1 0x3000u
#define NL_FL_RESETCNT 0x4000u
#define NL_FL_REGION_SIZE 0x5000u
#endif
+117
View File
@@ -0,0 +1,117 @@
#include "config/nl_settings.h"
#include <stddef.h>
#include <string.h>
#include "config/nl_flash.h"
#include "light/nl_light_types.h"
#include "util/nl_crc.h"
#ifndef NINLIGHT_TOKEN_DEFAULT
#define NINLIGHT_TOKEN_DEFAULT "aabbccddeeff0011223344"
#endif
#define NL_SETTINGS_MAGIC 0x31534C4Eu /* "NLS1" */
#define NL_SETTINGS_VERSION 1u
typedef struct {
uint32_t magic;
uint16_t version;
uint16_t length;
uint32_t seq;
} rec_hdr_t;
typedef struct {
rec_hdr_t hdr;
nl_settings_t data;
uint32_t crc;
} rec_t;
static uint32_t s_addr[2];
void nl_settings_defaults(nl_settings_t *s)
{
memset(s, 0, sizeof *s);
strncpy(s->token, NINLIGHT_TOKEN_DEFAULT, NL_TOKEN_LEN - 1);
s->power_on_behaviour = NL_POB_LAST;
s->fade_in_ms = 500;
s->fade_out_ms = 500;
}
void nl_settings_init(uint32_t addr_a, uint32_t addr_b)
{
s_addr[0] = addr_a;
s_addr[1] = addr_b;
}
static bool read_rec(int slot, rec_t *r)
{
if (nl_flash_read(s_addr[slot], r, sizeof *r) != 0)
return false;
if (r->hdr.magic != NL_SETTINGS_MAGIC || r->hdr.version != NL_SETTINGS_VERSION ||
r->hdr.length != sizeof(nl_settings_t))
return false;
return nl_crc32(0, r, offsetof(rec_t, crc)) == r->crc;
}
bool nl_settings_load(nl_settings_t *out)
{
rec_t a, b;
bool va = read_rec(0, &a);
bool vb = read_rec(1, &b);
const rec_t *best;
if (!va && !vb) {
nl_settings_defaults(out);
return false;
}
best = (va && (!vb || a.hdr.seq > b.hdr.seq)) ? &a : &b;
*out = best->data;
out->wifi_ssid[NL_SSID_LEN - 1] = '\0';
out->wifi_pass[NL_PASS_LEN - 1] = '\0';
out->token[NL_TOKEN_LEN - 1] = '\0';
out->name[NL_NAME_LEN - 1] = '\0';
return true;
}
int nl_settings_save(const nl_settings_t *s)
{
static rec_t a, b, r; /* keep ~700 bytes off the task stack */
bool va = read_rec(0, &a);
bool vb = read_rec(1, &b);
uint32_t seq;
int slot;
if (va && vb) {
slot = a.hdr.seq <= b.hdr.seq ? 0 : 1;
seq = (a.hdr.seq > b.hdr.seq ? a.hdr.seq : b.hdr.seq) + 1;
} else if (va) {
slot = 1;
seq = a.hdr.seq + 1;
} else if (vb) {
slot = 0;
seq = b.hdr.seq + 1;
} else {
slot = 0;
seq = 1;
}
memset(&r, 0, sizeof r);
r.hdr.magic = NL_SETTINGS_MAGIC;
r.hdr.version = NL_SETTINGS_VERSION;
r.hdr.length = sizeof(nl_settings_t);
r.hdr.seq = seq;
r.data = *s;
r.crc = nl_crc32(0, &r, offsetof(rec_t, crc));
if (nl_flash_erase_sector(s_addr[slot]) != 0)
return -1;
return nl_flash_write(s_addr[slot], &r, sizeof r);
}
int nl_settings_erase(void)
{
int ra = nl_flash_erase_sector(s_addr[0]);
int rb = nl_flash_erase_sector(s_addr[1]);
return (ra == 0 && rb == 0) ? 0 : -1;
}
+30
View File
@@ -0,0 +1,30 @@
#ifndef NL_SETTINGS_H
#define NL_SETTINGS_H
#include <stdbool.h>
#include <stdint.h>
#define NL_SSID_LEN 33
#define NL_PASS_LEN 65
#define NL_TOKEN_LEN 65
#define NL_NAME_LEN 33
typedef struct {
char wifi_ssid[NL_SSID_LEN];
char wifi_pass[NL_PASS_LEN];
char token[NL_TOKEN_LEN];
char name[NL_NAME_LEN];
uint8_t auth_all;
uint8_t power_on_behaviour; /* nl_pob_t */
uint16_t fade_in_ms;
uint16_t fade_out_ms;
} nl_settings_t;
void nl_settings_defaults(nl_settings_t *s);
void nl_settings_init(uint32_t addr_a, uint32_t addr_b);
/* Returns false (and fills defaults) when no valid record exists. */
bool nl_settings_load(nl_settings_t *out);
int nl_settings_save(const nl_settings_t *s);
int nl_settings_erase(void);
#endif