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:
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user