Issue Summary
fds_record_update() in SDK 13.0.0 creates permanent duplicate records instead of replacing old records. This causes:
-
Valid record count to grow indefinitely
-
Flash to fill up with duplicates
-
Application data corruption
-
Eventually FDS_ERR_NO_SPACE_IN_FLASH errors
Expected Behavior
When calling fds_record_update(&desc, &record):
-
New record is written to flash
-
Old record is marked as dirty (deleted)
-
Result: ONE valid record (new), ONE dirty record (old)
-
Valid record count stays constant
Actual Behavior
When calling fds_record_update(&desc, &record):
-
New record is written to flash
-
Old record delete FAILS silently
-
Result: TWO valid records (old + new)
-
Valid record count INCREASES on every update
-
Duplicate records accumulate indefinitely
typedef struct {
uint32_t time; // unix timestamp
int16_t temperature[2]; // AVG, MAX
uint16_t rh[2];
uint16_t pm1[2];
uint16_t pm2_5[2];
uint16_t pm4[2];
uint16_t pm10[2];
uint16_t co2[2];
uint16_t voc[2];
uint16_t co[2];
uint16_t nox[2];
uint16_t no2[2];
uint16_t tvoc[2];
} aqi_avg_min_max_data_t; // 52 bytes
typedef struct {
uint32_t time;
uint8_t node_state; // 1-5
uint8_t _pad[3]; // → 8 bytes
} node_state_t; // 8 bytes
typedef struct {
uint32_t time;
uint8_t motion_value; // 0/1
uint8_t _pad[3];
} motion_value_t; // 8 bytes
typedef struct {
uint32_t last_update_time;
uint16_t motion_count;
uint8_t _pad[2];
} motion_count_t; // 8 bytes
typedef struct {
uint32_t time;
uint8_t LSD1[2];
uint8_t LSD2[2];
} lsd_data_t; // 8 bytes
typedef struct {
uint32_t time;
uint8_t micro_post; // 0/1
uint8_t _pad[3];
} micro_post_t; // 8 bytes
/* --------------------------------------------------------------------
* 2. Motion queue entry – bit-fields isolated
* -------------------------------------------------------------------- */
typedef struct {
uint16_t payload_index;
uint8_t retries : 4;
uint8_t is_motion_cnt: 1;
uint8_t _pad_bits : 3;
uint8_t _pad_byte;
union {
motion_value_t motion_value; // 8 bytes
motion_count_t motion_count; // 8 bytes
} payload;
} motion_data_queue_t; // 12 bytes
typedef struct {
uint32_t valid_signature;
uint32_t aqi_idx;
uint32_t aqi_duration;
uint32_t last_aqi_timestamp;
aqi_avg_min_max_data_t aqi_data[MAX_AQI_DATA];
} aqi_part_t;
// --- NODE PART (with array) ---
typedef struct {
uint32_t valid_signature;
uint32_t node_idx;
uint32_t node_duration;
uint32_t last_node_timestamp;
node_state_t node_state[MAX_NODE_DATA];
node_state_t last_node_state;
} node_part_t;
// --- LSD PART (with array) ---
typedef struct {
uint32_t valid_signature;
uint32_t lsd_idx;
uint32_t lsd_duration;
uint32_t last_lsd_timestamp;
lsd_data_t lsd_data[MAX_LSD_DATA];
lsd_data_t last_lsd_data;
} lsd_part_t;
// --- POST PART (with array) ---
typedef struct {
uint32_t valid_signature;
uint32_t post_idx;
uint32_t post_duration;
uint32_t seconds_since_last_micro_post;
uint32_t last_post_timestamp;
uint32_t last_last_mpost_timestamp;
micro_post_t post_data[MAX_POST_DATA];
micro_post_t last_post_data;
} post_part_t;
// --- MOTION PART ---
typedef struct {
uint32_t valid_signature;
uint32_t motion_cnt_idx;
uint32_t motion_duration;
uint32_t queue_head;
uint32_t queue_tail;
uint32_t queue_count;
uint32_t payload_index_counter;
uint32_t QC_TEST_DATA;
uint32_t last_motion_timestamp;
motion_count_t motion_cnt[MAX_MOTION_DATA];
motion_data_queue_t motion_data_queue[MAX_EIS_QUEUE_SIZE];
} motion_part_t;
typedef struct{
uint32_t valid_signature;
uint8_t isAqiThresholdCrossed; //1-byte
uint8_t isMicroPostFailed; //1-byte
uint8_t isOnBattery; //1-byte
uint8_t motion_event; //1-byte
uint16_t aqi_interval; //2-bytes
uint16_t lsd_interval; //2-bytes
uint16_t motion_interval; //2-bytes
uint16_t node_state_interval; //2-bytes
time_t aqi_threshold_exceed_time; //4-bytes
uint16_t gw_node_id;
}
eis_command_t;
// --- FULL EIS DATA ---
typedef struct {
aqi_part_t aqi_part;
node_part_t node_part;
lsd_part_t lsd_part;
post_part_t post_part;
motion_part_t motion_part;
eis_command_t comman_data;
} eis_data_t;
/* --------------------------------------------------------------------
* Global instances
* -------------------------------------------------------------------- */
extern __ALIGN(4) eis_data_t exit_sign_data;
//another file fds operations
extern void watchdog_feed(void);
// Watchdog feed macro
#define FEED_WATCHDOG() watchdog_feed()
/* --------------------------------------------------------------------
* CONFIGURATION
* -------------------------------------------------------------------- */
#define GC_DIRTY_THRESHOLD 35
#define FLASH_CRITICAL_THRESHOLD 85
#define FLASH_WARNING_THRESHOLD 75
#define FDS_OPERATION_TIMEOUT_MS 30000
// Custom error codes
#ifndef FDS_ERR_TIMEOUT
#define FDS_ERR_TIMEOUT 0xBAAD0001
#endif
/* --------------------------------------------------------------------
* FDS EVENT FLAGS
* -------------------------------------------------------------------- */
volatile bool is_fds_initialized = false;
volatile bool is_fds_write_complete = false;
volatile bool is_fds_update_complete = false;
volatile bool is_fds_record_delete_complete = false;
volatile bool is_fds_file_delete_complete = false;
volatile bool is_fds_gc_complete = false;
/* --------------------------------------------------------------------
* STATIC FDS BUFFER - Thread-safe
* -------------------------------------------------------------------- */
static __ALIGN(4) uint8_t fds_buffer[1024];
static volatile bool fds_buffer_in_use = false;
/* --------------------------------------------------------------------
* DEBUG: Track FDS operations
* -------------------------------------------------------------------- */
static uint32_t fds_write_count = 0;
static uint32_t fds_update_count = 0;
static uint32_t fds_delete_count = 0;
static uint32_t fds_gc_count = 0;
static uint32_t fds_error_count = 0;
/* --------------------------------------------------------------------
* FDS EVENT HANDLER (WITH DEBUG LOGS)
* -------------------------------------------------------------------- */
static void user_data_event_handler(fds_evt_t const * p_evt)
{
switch (p_evt->id) {
case FDS_EVT_INIT:
if (p_evt->result == FDS_SUCCESS) {
is_fds_initialized = true;
fds_logf("[FDS_EVT] INIT: Success\r\n");
} else {
fds_logf("[FDS_EVT] INIT: FAILED (0x%x)\r\n", p_evt->result);
}
break;
case FDS_EVT_WRITE:
fds_write_count++;
is_fds_write_complete = true;
if (p_evt->result == FDS_SUCCESS) {
fds_logf("[FDS_EVT] WRITE: Success (file=0x%04X, key=0x%04X, total=%u)\r\n",
p_evt->write.file_id, p_evt->write.record_key, fds_write_count);
} else {
fds_error_count++;
fds_logf("[FDS_EVT] WRITE: FAILED (0x%x, file=0x%04X, key=0x%04X)\r\n",
p_evt->result, p_evt->write.file_id, p_evt->write.record_key);
}
break;
case FDS_EVT_UPDATE:
fds_update_count++;
is_fds_update_complete = true;
if (p_evt->result == FDS_SUCCESS) {
fds_logf("[FDS_EVT] UPDATE: Success (file=0x%04X, key=0x%04X, total=%u)\r\n",
p_evt->write.file_id, p_evt->write.record_key, fds_update_count);
} else {
fds_error_count++;
fds_logf("[FDS_EVT] UPDATE: FAILED (0x%x, file=0x%04X, key=0x%04X)\r\n",
p_evt->result, p_evt->write.file_id, p_evt->write.record_key);
}
break;
case FDS_EVT_DEL_RECORD:
fds_delete_count++;
is_fds_record_delete_complete = true;
if (p_evt->result == FDS_SUCCESS) {
fds_logf("[FDS_EVT] DELETE: Success (file=0x%04X, key=0x%04X, total=%u)\r\n",
p_evt->del.file_id, p_evt->del.record_key, fds_delete_count);
} else {
fds_error_count++;
fds_logf("[FDS_EVT] DELETE: FAILED (0x%x)\r\n", p_evt->result);
}
break;
case FDS_EVT_DEL_FILE:
is_fds_file_delete_complete = true;
fds_logf("[FDS_EVT] DEL_FILE: file=0x%04X\r\n", p_evt->del.file_id);
break;
case FDS_EVT_GC:
fds_gc_count++;
is_fds_gc_complete = true;
if (p_evt->result == FDS_SUCCESS) {
fds_logf("[FDS_EVT] GC: Success (total GCs=%u)\r\n", fds_gc_count);
} else {
fds_error_count++;
fds_logf("[FDS_EVT] GC: FAILED (0x%x)\r\n", p_evt->result);
}
break;
default:
fds_logf("[FDS_EVT] Unknown event: %d\r\n", p_evt->id);
break;
}
}
/* --------------------------------------------------------------------
* SAFE WAIT HELPER (WITH DEBUG)
* -------------------------------------------------------------------- */
static bool wait_for_fds_event(volatile bool *flag, uint32_t timeout_ms)
{
uint32_t elapsed = 0;
const uint32_t check_interval_ms = 50;
fds_logf("[FDS_WAIT] Waiting for event (timeout: %u ms)...\r\n", timeout_ms);
while (!(*flag) && elapsed < timeout_ms) {
FEED_WATCHDOG();
sd_app_evt_wait();
nrf_delay_ms(check_interval_ms);
elapsed += check_interval_ms;
// Log every second
if (elapsed % 1000 == 0) {
FEED_WATCHDOG();
fds_logf("[FDS_WAIT] Still waiting... %u ms\r\n", elapsed);
}
}
if (!(*flag)) {
fds_logf("[FDS_WAIT] TIMEOUT after %u ms\r\n", elapsed);
return false;
}
fds_logf("[FDS_WAIT] Event received after %u ms\r\n", elapsed);
return true;
}
/* --------------------------------------------------------------------
* FLASH STORAGE INIT (WITH DEBUG)
* -------------------------------------------------------------------- */
void flash_storage_init(void)
{
uint32_t err;
fds_logf("[FDS_INIT] Starting FDS initialization...\r\n");
fds_logf("[FDS_INIT] Buffer size: %u bytes\r\n", sizeof(fds_buffer));
fds_logf("[FDS_INIT] Virtual pages: %u\r\n", FDS_VIRTUAL_PAGES);
fds_logf("[FDS_INIT] Page size: %u words (%u bytes)\r\n",
FDS_VIRTUAL_PAGE_SIZE, FDS_VIRTUAL_PAGE_SIZE * 4);
memset(&nv_user_data, 0, sizeof(nv_user_data));
memset(&exit_sign_data, 0, sizeof(exit_sign_data));
err = fds_register(user_data_event_handler);
uart_logf("[FDS_INIT] Register: %s (err=0x%x)\r\n",
(err == FDS_SUCCESS) ? "OK" : "FAIL", err);
err = fds_init();
uart_logf("[FDS_INIT] Init: %s (err=0x%x)\r\n",
(err == FDS_SUCCESS) ? "OK" : "FAIL", err);
if (!wait_for_fds_event(&is_fds_initialized, FDS_OPERATION_TIMEOUT_MS)) {
uart_logf("[FDS_INIT] Init timeout!\r\n");
return;
}
uart_logf("[FDS_INIT] FDS ready\r\n");
nrf_delay_ms(1000);
print_flash_percentage();
check_and_gc_if_needed();
}
/* --------------------------------------------------------------------
* HELPER: WRITE ONE RECORD (WITH DEBUG)
* -------------------------------------------------------------------- */
static uint32_t fds_write_one(uint16_t file_id, uint16_t rec_key,
const void *src, uint16_t src_bytes)
{
uint32_t err;
uint16_t words = (src_bytes + 3) / 4;
fds_record_desc_t desc = {0};
fds_logf("[FDS_WRITE] file=0x%04X, key=0x%04X, size=%u bytes (%u words)\r\n",
file_id, rec_key, src_bytes, words);
if (src_bytes > sizeof(fds_buffer)) {
fds_logf("[FDS_WRITE] ERROR: Record too large (%u > %u)\r\n",
src_bytes, sizeof(fds_buffer));
return FDS_ERR_RECORD_TOO_LARGE;
}
__disable_irq();
if (fds_buffer_in_use) {
__enable_irq();
fds_logf("[FDS_WRITE] ERROR: Buffer busy\r\n");
return FDS_ERR_BUSY;
}
fds_buffer_in_use = true;
__enable_irq();
// Copy to buffer
memcpy(fds_buffer, src, src_bytes);
fds_logf("[FDS_WRITE] Data copied to buffer\r\n");
// Create chunk
fds_record_chunk_t chunk;
chunk.p_data = fds_buffer;
chunk.length_words = words;
// Setup record
fds_record_t rec;
rec.file_id = file_id;
rec.key = rec_key;
rec.data.p_chunks = &chunk;
rec.data.num_chunks = 1;
is_fds_write_complete = false;
fds_logf("[FDS_WRITE] Calling fds_record_write()...\r\n");
err = fds_record_write(&desc, &rec);
if (err != FDS_SUCCESS) {
uart_logf("[FDS_WRITE] fds_record_write FAILED: 0x%x\r\n", err);
__disable_irq();
fds_buffer_in_use = false;
__enable_irq();
return err;
}
fds_logf("[FDS_WRITE] Write queued, waiting for event...\r\n");
if (!wait_for_fds_event(&is_fds_write_complete, FDS_OPERATION_TIMEOUT_MS)) {
fds_logf("[FDS_WRITE] Write timeout!\r\n");
__disable_irq();
fds_buffer_in_use = false;
__enable_irq();
return FDS_ERR_TIMEOUT;
}
__disable_irq();
fds_buffer_in_use = false;
__enable_irq();
fds_logf("[FDS_WRITE] Write complete\r\n");
return FDS_SUCCESS;
}
/* --------------------------------------------------------------------
* HELPER: LOAD ONE RECORD (WITH DEBUG)
* -------------------------------------------------------------------- */
static uint32_t fds_load_one(uint16_t file_id, uint16_t rec_key,
void *dst, uint16_t dst_bytes)
{
fds_record_desc_t desc;
fds_find_token_t tok = {0};
fds_logf("[FDS_LOAD] file=0x%04X, key=0x%04X\r\n", file_id, rec_key);
uint32_t err = fds_record_find(file_id, rec_key, &desc, &tok);
if (err != FDS_SUCCESS) {
uart_logf("[FDS_LOAD] Record not found: 0x%x\r\n", err);
return err;
}
fds_flash_record_t flash;
err = fds_record_open(&desc, &flash);
if (err != FDS_SUCCESS) {
uart_logf("[FDS_LOAD] Record open failed: 0x%x\r\n", err);
return err;
}
memcpy(dst, flash.p_data, dst_bytes);
(void)fds_record_close(&desc);
fds_logf("[FDS_LOAD] Loaded %u bytes\r\n", dst_bytes);
return FDS_SUCCESS;
}
/* --------------------------------------------------------------------
* UPDATE FDS DATA (WITH DEBUG)
* -------------------------------------------------------------------- */
uint32_t update_fds_data(uint16_t file_id, uint16_t record_key)
{
fds_record_desc_t desc;
fds_find_token_t tok = {0};
fds_logf("[FDS_UPDATE] file=0x%04X, key=0x%04X\r\n", file_id, record_key);
uint32_t err = fds_record_find(file_id, record_key, &desc, &tok);
if (err != FDS_SUCCESS) {
uart_logf("[FDS_UPDATE] Record find failed: 0x%x (NOT_FOUND=%d)\r\n",
err, FDS_ERR_NOT_FOUND);
return err;
}
const void *src = NULL;
uint16_t size = 0;
// Get src and size
if (file_id == USER_DATA_FILE_ID && record_key == USER_DATA_RECORD_KEY) {
src = &nv_user_data;
size = sizeof(nv_user_data);
}
else if (file_id == EIS_FILE_ID) {
switch (record_key) {
case EIS_AQI_KEY:
src = &exit_sign_data.aqi_part;
size = sizeof(exit_sign_data.aqi_part);
break;
case EIS_NODE_KEY:
src = &exit_sign_data.node_part;
size = sizeof(exit_sign_data.node_part);
break;
case EIS_LSD_KEY:
src = &exit_sign_data.lsd_part;
size = sizeof(exit_sign_data.lsd_part);
break;
case EIS_POST_KEY:
src = &exit_sign_data.post_part;
size = sizeof(exit_sign_data.post_part);
break;
case EIS_MOTION_KEY:
src = &exit_sign_data.motion_part;
size = sizeof(exit_sign_data.motion_part);
break;
case EIS_COMMAN_KEY:
src = &exit_sign_data.comman_data;
size = sizeof(exit_sign_data.comman_data);
break;
default:
uart_logf("[FDS_UPDATE] Invalid key: 0x%04X\r\n", record_key);
return FDS_ERR_INVALID_ARG;
}
}
else {
uart_logf("[FDS_UPDATE] Invalid file_id: 0x%04X\r\n", file_id);
return FDS_ERR_INVALID_ARG;
}
fds_logf("[FDS_UPDATE] Data size: %u bytes\r\n", size);
if (size > sizeof(fds_buffer)) {
uart_logf("[FDS_UPDATE] ERROR: Record too large (%u > %u)\r\n",
size, sizeof(fds_buffer));
return FDS_ERR_RECORD_TOO_LARGE;
}
__disable_irq();
if (fds_buffer_in_use) {
__enable_irq();
uart_logf("[FDS_UPDATE] ERROR: Buffer busy\r\n");
return FDS_ERR_BUSY;
}
fds_buffer_in_use = true;
__enable_irq();
uint16_t words = (size + 3) / 4;
memcpy(fds_buffer, src, size);
// Create chunk
fds_record_chunk_t chunk;
chunk.p_data = fds_buffer;
chunk.length_words = words;
// Setup record
fds_record_t rec;
rec.file_id = file_id;
rec.key = record_key;
rec.data.p_chunks = &chunk;
rec.data.num_chunks = 1;
is_fds_update_complete = false;
fds_logf("[FDS_UPDATE] Calling fds_record_update()...\r\n");
err = fds_record_update(&desc, &rec);
if (err != FDS_SUCCESS) {
uart_logf("[FDS_UPDATE] fds_record_update FAILED: 0x%x\r\n", err);
__disable_irq();
fds_buffer_in_use = false;
__enable_irq();
return err;
}
fds_logf("[FDS_UPDATE] Update queued, waiting for event...\r\n");
if (!wait_for_fds_event(&is_fds_update_complete, FDS_OPERATION_TIMEOUT_MS)) {
uart_logf("[FDS_UPDATE] Update timeout!\r\n");
__disable_irq();
fds_buffer_in_use = false;
__enable_irq();
return FDS_ERR_TIMEOUT;
}
__disable_irq();
fds_buffer_in_use = false;
__enable_irq();
fds_logf("[FDS_UPDATE] Update complete\r\n");
return FDS_SUCCESS;
}
/* --------------------------------------------------------------------
* SAFE GC RUNNER (WITH DEBUG)
* -------------------------------------------------------------------- */
void run_fds_gc(void){
fds_logf("[FDS_GC] Starting garbage collection...\r\n");
is_fds_gc_complete = false;
uint32_t err = fds_gc();
if (err != FDS_SUCCESS) {
uart_logf("[FDS_GC] GC start failed: 0x%x\r\n", err);
return;
}
fds_logf("[FDS_GC] GC queued, waiting...\r\n");
if (!wait_for_fds_event(&is_fds_gc_complete, FDS_OPERATION_TIMEOUT_MS)) {
uart_logf("[FDS_GC] GC timeout!\r\n");
return;
}
fds_logf("[FDS_GC] GC completed successfully\r\n");
}
/* --------------------------------------------------------------------
* WRITE OR UPDATE WITH RETRY (WITH DEBUG)
* -------------------------------------------------------------------- */
uint32_t write_or_update_fds_data(uint16_t file_id, uint16_t record_key)
{
uint32_t err = FDS_SUCCESS;
int retry = 3;
fds_logf("[FDS_SAVE] file=0x%04X, key=0x%04X, retries=%d\r\n",
file_id, record_key, retry);
check_and_gc_if_needed();
while (retry--) {
fds_logf("[FDS_SAVE] Attempt %d/%d\r\n", 3 - retry, 3);
// Always try update first
err = update_fds_data(file_id, record_key);
// If record doesn't exist, create it
if (err == FDS_ERR_NOT_FOUND) {
uart_logf("[FDS_SAVE] Record not found, creating new...\r\n");
err = fds_write_one(file_id, record_key,
(file_id == USER_DATA_FILE_ID) ?
(void*)&nv_user_data :
(void*)get_eis_data_ptr(record_key),
get_eis_data_size(record_key));
}
if (err == FDS_SUCCESS) {
fds_logf("[FDS_SAVE] SUCCESS\r\n");
return err;
}
// Handle specific errors
if (err == FDS_ERR_NO_SPACE_IN_FLASH) {
uart_logf("[FDS_SAVE] No flash space (0x%x) - running GC\r\n", err);
run_fds_gc();
} else if (err == FDS_ERR_NO_SPACE_IN_QUEUES) {
uart_logf("[FDS_SAVE] Queue full (0x%x) - waiting 1 sec\r\n", err);
nrf_delay_ms(1000);
FEED_WATCHDOG();
} else if (err == FDS_ERR_TIMEOUT) {
uart_logf("[FDS_SAVE] Timeout (0x%x), retry=%d\r\n", err, retry);
} else if (err == FDS_ERR_BUSY) {
uart_logf("[FDS_SAVE] Busy (0x%x), retry=%d\r\n", err, retry);
nrf_delay_ms(500);
} else {
uart_logf("[FDS_SAVE] Error 0x%x, retry=%d\r\n", err, retry);
}
nrf_delay_ms(100);
FEED_WATCHDOG();
}
fds_logf("[FDS_SAVE] FAILED after 3 retries, final error: 0x%x\r\n", err);
return err;
}
/* --------------------------------------------------------------------
* HELPER: Get EIS data pointer
* -------------------------------------------------------------------- */
void* get_eis_data_ptr(uint16_t record_key)
{
switch (record_key) {
case EIS_AQI_KEY: return &exit_sign_data.aqi_part;
case EIS_NODE_KEY: return &exit_sign_data.node_part;
case EIS_LSD_KEY: return &exit_sign_data.lsd_part;
case EIS_POST_KEY: return &exit_sign_data.post_part;
case EIS_MOTION_KEY: return &exit_sign_data.motion_part;
case EIS_COMMAN_KEY: return &exit_sign_data.comman_data;
default: return NULL;
}
}
/* --------------------------------------------------------------------
* HELPER: Get EIS data size
* -------------------------------------------------------------------- */
uint16_t get_eis_data_size(uint16_t record_key)
{
switch (record_key) {
case EIS_AQI_KEY: return sizeof(exit_sign_data.aqi_part);
case EIS_NODE_KEY: return sizeof(exit_sign_data.node_part);
case EIS_LSD_KEY: return sizeof(exit_sign_data.lsd_part);
case EIS_POST_KEY: return sizeof(exit_sign_data.post_part);
case EIS_MOTION_KEY: return sizeof(exit_sign_data.motion_part);
case EIS_COMMAN_KEY: return sizeof(exit_sign_data.comman_data);
default: return 0;
}
}
/* --------------------------------------------------------------------
* SAVE FDS DATA (Only for initialization)
* -------------------------------------------------------------------- */
uint32_t save_fds_data(uint16_t file_id, uint16_t record_key)
{
const void *src = NULL;
uint16_t size = 0;
fds_logf("[FDS_INIT_SAVE] file=0x%04X, key=0x%04X (using defaults)\r\n",
file_id, record_key);
check_and_gc_if_needed();
// Use DEFAULTS only for initial creation
if (file_id == USER_DATA_FILE_ID && record_key == USER_DATA_RECORD_KEY) {
src = &default_nv_user_data;
size = sizeof(default_nv_user_data);
}
else if (file_id == EIS_FILE_ID) {
switch (record_key) {
case EIS_AQI_KEY:
src = &default_exit_sign_data.aqi_part;
size = sizeof(default_exit_sign_data.aqi_part);
break;
case EIS_NODE_KEY:
src = &default_exit_sign_data.node_part;
size = sizeof(default_exit_sign_data.node_part);
break;
case EIS_LSD_KEY:
src = &default_exit_sign_data.lsd_part;
size = sizeof(default_exit_sign_data.lsd_part);
break;
case EIS_POST_KEY:
src = &default_exit_sign_data.post_part;
size = sizeof(default_exit_sign_data.post_part);
break;
case EIS_MOTION_KEY:
src = &default_exit_sign_data.motion_part;
size = sizeof(default_exit_sign_data.motion_part);
break;
case EIS_COMMAN_KEY:
src = &default_exit_sign_data.comman_data;
size = sizeof(default_exit_sign_data.comman_data);
break;
default:
return FDS_ERR_INVALID_ARG;
}
}
else {
return FDS_ERR_INVALID_ARG;
}
return fds_write_one(file_id, record_key, src, size);
}
/* --------------------------------------------------------------------
* LOAD FDS DATA
* -------------------------------------------------------------------- */
uint32_t load_fds_data(uint16_t file_id, uint16_t record_key)
{
void *dst = NULL;
uint16_t size = 0;
if (file_id == USER_DATA_FILE_ID && record_key == USER_DATA_RECORD_KEY) {
dst = &nv_user_data;
size = sizeof(nv_user_data);
}
else if (file_id == EIS_FILE_ID) {
dst = get_eis_data_ptr(record_key);
size = get_eis_data_size(record_key);
}
else {
return FDS_ERR_INVALID_ARG;
}
return fds_load_one(file_id, record_key, dst, size);
}
/* --------------------------------------------------------------------
* RECORD AVAILABILITY CHECK
* -------------------------------------------------------------------- */
bool is_record_available(uint16_t file_id, uint16_t record_key)
{
fds_record_desc_t desc;
fds_find_token_t tok = {0};
bool available = (fds_record_find(file_id, record_key, &desc, &tok) == FDS_SUCCESS);
fds_logf("[FDS_CHECK] file=0x%04X, key=0x%04X: %s\r\n",
file_id, record_key, available ? "EXISTS" : "NOT_FOUND");
return available;
}
/* --------------------------------------------------------------------
* LOAD ALL FDS DATA (WITH DEBUG)
* -------------------------------------------------------------------- */
void load_all_fds_data(void)
{
uint32_t err;
fds_logf("[FDS_LOAD_ALL] Starting...\r\n");
// USER DATA
FEED_WATCHDOG();
uart_logf("[FDS_LOAD_ALL] Loading user data, size = %u\r\n", sizeof(ilumi_user_data));
if (!is_record_available(USER_DATA_FILE_ID, USER_DATA_RECORD_KEY)) {
uart_logf("Creating USER_DATA...\r\n");
memcpy(&nv_user_data, &default_nv_user_data, sizeof(nv_user_data));
err = save_fds_data(USER_DATA_FILE_ID, USER_DATA_RECORD_KEY);
if (err) uart_logf("USER_DATA create failed: 0x%x\r\n", err);
} else {
uart_logf("Loading USER_DATA...\r\n");
err = load_fds_data(USER_DATA_FILE_ID, USER_DATA_RECORD_KEY);
if (err) uart_logf("USER_DATA load failed: 0x%x\r\n", err);
}
// EXIT SIGN DATA
uart_logf("[FDS_LOAD_ALL] Loading EIS data, size = %u\r\n", sizeof(eis_data_t));
uart_logf("Loading EXIT_SIGN data...\r\n");
for (uint16_t k = EIS_AQI_KEY; k <= EIS_COMMAN_KEY; ++k) {
FEED_WATCHDOG();
if (!is_record_available(EIS_FILE_ID, k)) {
uart_logf("Creating EIS record 0x%04X...\r\n", k);
// Initialize from defaults
void *dst = get_eis_data_ptr(k);
const void *src_default = NULL;
switch (k) {
case EIS_AQI_KEY: src_default = &default_exit_sign_data.aqi_part; break;
case EIS_NODE_KEY: src_default = &default_exit_sign_data.node_part; break;
case EIS_LSD_KEY: src_default = &default_exit_sign_data.lsd_part; break;
case EIS_POST_KEY: src_default = &default_exit_sign_data.post_part; break;
case EIS_MOTION_KEY: src_default = &default_exit_sign_data.motion_part; break;
case EIS_COMMAN_KEY: src_default = &default_exit_sign_data.comman_data; break;
}
if (dst && src_default) {
memcpy(dst, src_default, get_eis_data_size(k));
}
err = save_fds_data(EIS_FILE_ID, k);
if (err) uart_logf("EIS create 0x%04X failed: 0x%x\r\n", k, err);
} else {
uart_logf("Loading EIS record 0x%04X...\r\n", k);
err = load_fds_data(EIS_FILE_ID, k);
if (err) {
uart_logf("EIS load 0x%04X failed: 0x%x\r\n", k, err);
continue;
}
// Validate signature
bool invalid = false;
switch (k) {
case EIS_AQI_KEY:
invalid = (exit_sign_data.aqi_part.valid_signature != FLASH_PAGE_VALID);
break;
case EIS_NODE_KEY:
invalid = (exit_sign_data.node_part.valid_signature != FLASH_PAGE_VALID);
break;
case EIS_LSD_KEY:
invalid = (exit_sign_data.lsd_part.valid_signature != FLASH_PAGE_VALID);
break;
case EIS_POST_KEY:
invalid = (exit_sign_data.post_part.valid_signature != FLASH_PAGE_VALID);
break;
case EIS_MOTION_KEY:
invalid = (exit_sign_data.motion_part.valid_signature != FLASH_PAGE_VALID);
break;
case EIS_COMMAN_KEY:
invalid = (exit_sign_data.comman_data.valid_signature != FLASH_PAGE_VALID);
break;
}
if (invalid) {
uart_logf("Invalid signature for 0x%04X - recreating...\r\n", k);
fds_record_desc_t desc;
fds_find_token_t tok = {0};
if (fds_record_find(EIS_FILE_ID, k, &desc, &tok) == FDS_SUCCESS) {
is_fds_record_delete_complete = false;
fds_record_delete(&desc);
if (!wait_for_fds_event(&is_fds_record_delete_complete,
FDS_OPERATION_TIMEOUT_MS)) {
uart_logf("Delete timeout for 0x%04X\r\n", k);
}
}
// Restore defaults
void *dst = get_eis_data_ptr(k);
const void *src_default = NULL;
switch (k) {
case EIS_AQI_KEY: src_default = &default_exit_sign_data.aqi_part; break;
case EIS_NODE_KEY: src_default = &default_exit_sign_data.node_part; break;
case EIS_LSD_KEY: src_default = &default_exit_sign_data.lsd_part; break;
case EIS_POST_KEY: src_default = &default_exit_sign_data.post_part; break;
case EIS_MOTION_KEY: src_default = &default_exit_sign_data.motion_part; break;
case EIS_COMMAN_KEY: src_default = &default_exit_sign_data.comman_data; break;
}
if (dst && src_default) {
memcpy(dst, src_default, get_eis_data_size(k));
}
save_fds_data(EIS_FILE_ID, k);
}
}
nrf_delay_ms(50);
}
fds_logf("[FDS_LOAD_ALL] Complete\r\n");
uart_logf("All FDS data loaded\r\n");
}
/* --------------------------------------------------------------------
* CHECK AND RUN GC IF NEEDED (WITH DEBUG)
* -------------------------------------------------------------------- */
void check_and_gc_if_needed(void)
{
fds_stat_t stat = {0};
if (fds_stat(&stat) != FDS_SUCCESS) {
fds_logf("[FDS_GC_CHECK] fds_stat() failed\r\n");
return;
}
uint32_t num_pages = FDS_VIRTUAL_PAGES - 1;
uint32_t total_words = num_pages * FDS_VIRTUAL_PAGE_SIZE;
uint32_t usage_percent = (stat.words_used * 100) / total_words;
fds_logf("[FDS_GC_CHECK] Dirty: %u, usage: %u%%, open_records: %u\r\n",
stat.dirty_records, usage_percent, stat.open_records);
if (stat.dirty_records >= GC_DIRTY_THRESHOLD) {
uart_logf("[FDS_GC_CHECK] Threshold exceeded (%u >= %u)\r\n",
stat.dirty_records, GC_DIRTY_THRESHOLD);
run_fds_gc();
if (fds_stat(&stat) == FDS_SUCCESS) {
usage_percent = (stat.words_used * 100) / total_words;
uart_logf("After GC: dirty: %u, usage: %u%%\r\n",
stat.dirty_records, usage_percent);
}
} else {
fds_logf("[FDS_GC_CHECK] No GC needed\r\n");
}
if (usage_percent >= FLASH_CRITICAL_THRESHOLD) {
uart_logf("CRITICAL: Flash %u%% full!\r\n", usage_percent);
} else if (usage_percent >= FLASH_WARNING_THRESHOLD) {
uart_logf("WARNING: Flash %u%% full\r\n", usage_percent);
}
}
/* --------------------------------------------------------------------
* PRINT FLASH PERCENTAGE (WITH DEBUG)
* -------------------------------------------------------------------- */
void print_flash_percentage(void)
{
fds_stat_t stat = {0};
if (fds_stat(&stat) == FDS_SUCCESS) {
uint32_t num_pages = FDS_VIRTUAL_PAGES - 1;
uint32_t total_words = num_pages * FDS_VIRTUAL_PAGE_SIZE;
uint32_t available_words = total_words - stat.words_used;
uint32_t usage_percent = (stat.words_used * 100) / total_words;
uint32_t avail_percent = 100 - usage_percent;
uint32_t total_kb = (total_words * 4) / 1024;
uint32_t used_kb = (stat.words_used * 4) / 1024;
uart_logf("Flash: %u/%u KB (%u%% used, %u%% free), Dirty records: %u\r\n",
used_kb, total_kb, usage_percent, avail_percent, stat.dirty_records);
fds_logf("[FDS_STATS] Words: %u/%u, Open: %u, Freeable: %u\r\n",
stat.words_used, total_words, stat.open_records);
fds_logf("[FDS_STATS] Total ops: W=%u, U=%u, D=%u, GC=%u, Errors=%u\r\n",
fds_write_count, fds_update_count, fds_delete_count,
fds_gc_count, fds_error_count);
} else {
fds_logf("[FDS_STATS] fds_stat() FAILED\r\n");
}
}
/* --------------------------------------------------------------------
* DEBUG: Print FDS operation summary
* -------------------------------------------------------------------- */
void print_fds_debug_summary(void)
{
uart_logf("\r\n========== FDS DEBUG SUMMARY ==========\r\n");
uart_logf("Write operations: %u\r\n", fds_write_count);
uart_logf("Update operations: %u\r\n", fds_update_count);
uart_logf("Delete operations: %u\r\n", fds_delete_count);
uart_logf("GC runs: %u\r\n", fds_gc_count);
uart_logf("Errors: %u\r\n", fds_error_count);
uart_logf("Buffer in use: %s\r\n", fds_buffer_in_use ? "YES" : "NO");
print_flash_percentage();
uart_logf("======================================\r\n\r\n");
}
/* --------------------------------------------------------------------
* SEQUENTIAL SAVE QUEUE SYSTEM
* -------------------------------------------------------------------- */
#define MAX_SAVE_QUEUE_SIZE 7 // USER_DATA + 6 EIS records
typedef struct {
uint16_t file_id;
uint16_t record_key;
} save_queue_item_t;
static save_queue_item_t save_queue[MAX_SAVE_QUEUE_SIZE];
static uint8_t save_queue_head = 0;
static uint8_t save_queue_tail = 0;
static uint8_t save_queue_count = 0;
static bool save_in_progress = false;
static app_timer_id_t sequential_save_timer_id = APP_TIMER_ID_UNINIT;
static app_timer_id_t write_to_flash_timer_id = APP_TIMER_ID_UNINIT;
static bool write_into_flash = false;
static bool write_to_flash_timer_running = false;
static bool eis_save_timer_running = false;
/* --------------------------------------------------------------------
* EIS UPDATE FLAGS AND TIMER
* -------------------------------------------------------------------- */
static uint16_t eis_update_flags = 0; // Bit flags for each EIS record
static app_timer_id_t eis_save_timer_id = APP_TIMER_ID_UNINIT;
/* --------------------------------------------------------------------
* SEQUENTIAL SAVE TIMER HANDLER
* -------------------------------------------------------------------- */
static void sequential_save_timer_handler(void *context)
{
if (save_queue_count == 0) {
fds_logf("[SEQ_SAVE] Queue empty, stopping\r\n");
save_in_progress = false;
return;
}
// Get next item from queue
__disable_irq();
save_queue_item_t item = save_queue[save_queue_head];
save_queue_head = (save_queue_head + 1) % MAX_SAVE_QUEUE_SIZE;
save_queue_count--;
uint8_t remaining = save_queue_count;
__enable_irq();
fds_logf("[SEQ_SAVE] Processing: file=0x%04X, key=0x%04X (remaining: %u)\r\n",
item.file_id, item.record_key, remaining);
// Perform the save
uint32_t err = write_or_update_fds_data(item.file_id, item.record_key);
if (err == FDS_SUCCESS) {
uart_logf("[FDS_INFO] %s data saved: 0x%x\r\n",(item.record_key == USER_DATA_RECORD_KEY ? "USER" : "EIS") ,item.record_key);
} else {
uart_logf("[FDS_ERROR] Save failed: file=0x%04X, key=0x%04X, err=0x%x\r\n",
item.file_id, item.record_key, err);
}
// If more items in queue, schedule next save
if (remaining > 0) {
fds_logf("[SEQ_SAVE] Scheduling next save in 2 seconds...\r\n");
app_timer_start(sequential_save_timer_id,
APP_TIMER_TICKS(2000, APP_TIMER_PRESCALER), // 2 second delay
NULL);
} else {
fds_logf("[SEQ_SAVE] All saves complete\r\n");
save_in_progress = false;
}
}
/* --------------------------------------------------------------------
* ADD ITEM TO SAVE QUEUE
* -------------------------------------------------------------------- */
static bool add_to_save_queue(uint16_t file_id, uint16_t record_key)
{
__disable_irq();
// Check if already in queue
for (uint8_t i = 0; i < save_queue_count; i++) {
uint8_t idx = (save_queue_head + i) % MAX_SAVE_QUEUE_SIZE;
if (save_queue[idx].file_id == file_id &&
save_queue[idx].record_key == record_key) {
__enable_irq();
fds_logf("[QUEUE] Already queued: file=0x%04X, key=0x%04X\r\n",
file_id, record_key);
return false; // Already queued
}
}
// Check if queue is full
if (save_queue_count >= MAX_SAVE_QUEUE_SIZE) {
__enable_irq();
fds_logf("[QUEUE] FULL! Cannot add file=0x%04X, key=0x%04X\r\n",
file_id, record_key);
return false;
}
// Add to queue
save_queue[save_queue_tail].file_id = file_id;
save_queue[save_queue_tail].record_key = record_key;
save_queue_tail = (save_queue_tail + 1) % MAX_SAVE_QUEUE_SIZE;
save_queue_count++;
uint8_t count = save_queue_count;
__enable_irq();
fds_logf("[QUEUE] Added: file=0x%04X, key=0x%04X (count: %u)\r\n",
file_id, record_key, count);
return true;
}
/* --------------------------------------------------------------------
* START SEQUENTIAL SAVE PROCESSING
* -------------------------------------------------------------------- */
static void start_sequential_save(void)
{
// Create timer if not already created
if (sequential_save_timer_id == APP_TIMER_ID_UNINIT) {
uint32_t err = app_timer_create(&sequential_save_timer_id,
APP_TIMER_MODE_SINGLE_SHOT,
sequential_save_timer_handler);
if (err != NRF_SUCCESS) {
fds_logf("[SEQ_SAVE] Timer create failed: 0x%x\r\n", err);
return;
}
}
// Only start if not already processing
if (!save_in_progress && save_queue_count > 0) {
save_in_progress = true;
fds_logf("[SEQ_SAVE] Starting sequential save (%u items)...\r\n", save_queue_count);
// Start immediately (no delay for first item)
app_timer_start(sequential_save_timer_id,
APP_TIMER_TICKS(2000, APP_TIMER_PRESCALER), // 100ms delay
NULL);
}
}
uint8_t fds_save_timer_runnig(void){
return save_in_progress;
}
/* --------------------------------------------------------------------
* SAVE USER DATA (IMMEDIATE - QUEUED)
* -------------------------------------------------------------------- */
void save_user_data(void)
{
fds_logf("[SAVE] Queueing user data save...\r\n");
nv_user_data.valid_signature = FLASH_PAGE_VALID;
if (add_to_save_queue(USER_DATA_FILE_ID, USER_DATA_RECORD_KEY)) {
start_sequential_save();
}
write_into_flash = false;
}
/* --------------------------------------------------------------------
* SAVE EXIT SIGN DATA (IMMEDIATE - QUEUED)
* -------------------------------------------------------------------- */
void save_exit_sign_data(uint16_t key_id)
{
fds_logf("[SAVE] Queueing EIS data save: 0x%04X...\r\n", key_id);
// Set valid signature based on key
switch (key_id) {
case EIS_AQI_KEY:
exit_sign_data.aqi_part.valid_signature = FLASH_PAGE_VALID;
break;
case EIS_NODE_KEY:
exit_sign_data.node_part.valid_signature = FLASH_PAGE_VALID;
break;
case EIS_LSD_KEY:
exit_sign_data.lsd_part.valid_signature = FLASH_PAGE_VALID;
break;
case EIS_POST_KEY:
exit_sign_data.post_part.valid_signature = FLASH_PAGE_VALID;
break;
case EIS_MOTION_KEY:
exit_sign_data.motion_part.valid_signature = FLASH_PAGE_VALID;
break;
case EIS_COMMAN_KEY:
exit_sign_data.comman_data.valid_signature = FLASH_PAGE_VALID;
break;
}
if (add_to_save_queue(EIS_FILE_ID, key_id)) {
start_sequential_save();
}
}
/* --------------------------------------------------------------------
* 15-MINUTE DELAYED SAVE TIMER (FIXED)
* -------------------------------------------------------------------- */
static void write_to_flash_timer(void *pContext)
{
fds_logf("[DELAYED_SAVE] 15-minute timer expired, saving user data...\r\n");
write_to_flash_timer_running = false; // ADD THIS: Clear running flag
// Queue the save (will be processed sequentially)
nv_user_data.valid_signature = FLASH_PAGE_VALID;
if (add_to_save_queue(USER_DATA_FILE_ID, USER_DATA_RECORD_KEY)) {
start_sequential_save();
}
write_into_flash = false;
}
void start_write_to_flash_timer(void)
{
// FIXED: Don't restart if already running
if (write_to_flash_timer_running) {
fds_logf("[DELAYED_SAVE] Timer already running, not restarting\r\n");
return;
}
if (write_to_flash_timer_id == APP_TIMER_ID_UNINIT) {
app_timer_create(&write_to_flash_timer_id,
APP_TIMER_MODE_SINGLE_SHOT,
write_to_flash_timer);
}
fds_logf("[DELAYED_SAVE] Starting 15-minute timer...\r\n");
uint32_t err = app_timer_start(write_to_flash_timer_id,
APP_TIMER_TICKS((15 * 60 * 1000), APP_TIMER_PRESCALER),
NULL);
if (err == NRF_SUCCESS) {
write_to_flash_timer_running = true; // ADD THIS: Set running flag
} else {
fds_logf("[DELAYED_SAVE] Timer start failed: 0x%x\r\n", err);
}
}
void update_user_data(void)
{
// Always set valid signature
nv_user_data.valid_signature = FLASH_PAGE_VALID;
if (!write_into_flash) {
write_into_flash = true;
start_write_to_flash_timer(); // FIXED: Removed stop() call
fds_logf("[UPDATE] User data update triggered\r\n");
}
}
/* --------------------------------------------------------------------
* BATCH SAVE FUNCTION (FOR YOUR USE CASE)
* -------------------------------------------------------------------- */
void save_all_data_sequentially(void)
{
fds_logf("[BATCH_SAVE] Queueing all data for sequential save...\r\n");
// Add all items to queue
add_to_save_queue(USER_DATA_FILE_ID, USER_DATA_RECORD_KEY);
add_to_save_queue(EIS_FILE_ID, EIS_AQI_KEY);
add_to_save_queue(EIS_FILE_ID, EIS_LSD_KEY);
add_to_save_queue(EIS_FILE_ID, EIS_NODE_KEY);
add_to_save_queue(EIS_FILE_ID, EIS_MOTION_KEY);
add_to_save_queue(EIS_FILE_ID, EIS_POST_KEY);
add_to_save_queue(EIS_FILE_ID, EIS_COMMAN_KEY);
// Start processing
start_sequential_save();
}
/* --------------------------------------------------------------------
* DEBUG: Print save queue status
* -------------------------------------------------------------------- */
void print_save_queue_status(void)
{
uart_logf("\r\n========== SAVE QUEUE STATUS ==========\r\n");
uart_logf("Queue count: %u/%u\r\n", save_queue_count, MAX_SAVE_QUEUE_SIZE);
uart_logf("Save in progress: %s\r\n", save_in_progress ? "YES" : "NO");
uart_logf("Head: %u, Tail: %u\r\n", save_queue_head, save_queue_tail);
if (save_queue_count > 0) {
uart_logf("Queued items:\r\n");
for (uint8_t i = 0; i < save_queue_count; i++) {
uint8_t idx = (save_queue_head + i) % MAX_SAVE_QUEUE_SIZE;
uart_logf(" [%u] file=0x%04X, key=0x%04X\r\n",
i, save_queue[idx].file_id, save_queue[idx].record_key);
}
}
uart_logf("======================================\r\n\r\n");
}
/* --------------------------------------------------------------------
* EIS SAVE TIMER HANDLER (Triggered after delay)
* -------------------------------------------------------------------- */
static void eis_save_timer_handler(void *context)
{
fds_logf("[EIS_DELAYED] Timer expired, processing pending saves...\r\n");
eis_save_timer_running = false; // ADD THIS: Clear running flag
// Check each flag and queue for save
if (IS_SET(eis_update_flags, EIS_AQI_BIT_POS)) {
fds_logf("[EIS_DELAYED] Queueing AQI save\r\n");
add_to_save_queue(EIS_FILE_ID, EIS_AQI_KEY);
CLR_BIT(eis_update_flags, EIS_AQI_BIT_POS);
}
if (IS_SET(eis_update_flags, EIS_NODE_BIT_POS)) {
fds_logf("[EIS_DELAYED] Queueing NODE save\r\n");
add_to_save_queue(EIS_FILE_ID, EIS_NODE_KEY);
CLR_BIT(eis_update_flags, EIS_NODE_BIT_POS);
}
if (IS_SET(eis_update_flags, EIS_LSD_BIT_POS)) {
fds_logf("[EIS_DELAYED] Queueing LSD save\r\n");
add_to_save_queue(EIS_FILE_ID, EIS_LSD_KEY);
CLR_BIT(eis_update_flags, EIS_LSD_BIT_POS);
}
if (IS_SET(eis_update_flags, EIS_POST_BIT_POS)) {
fds_logf("[EIS_DELAYED] Queueing POST save\r\n");
add_to_save_queue(EIS_FILE_ID, EIS_POST_KEY);
CLR_BIT(eis_update_flags, EIS_POST_BIT_POS);
}
if (IS_SET(eis_update_flags, EIS_MOTION_BIT_POS)) {
fds_logf("[EIS_DELAYED] Queueing MOTION save\r\n");
add_to_save_queue(EIS_FILE_ID, EIS_MOTION_KEY);
CLR_BIT(eis_update_flags, EIS_MOTION_BIT_POS);
}
if (IS_SET(eis_update_flags, EIS_COMMAN_BIT_POS)) {
fds_logf("[EIS_DELAYED] Queueing COMMAN save\r\n");
add_to_save_queue(EIS_FILE_ID, EIS_COMMAN_KEY);
CLR_BIT(eis_update_flags, EIS_COMMAN_BIT_POS);
}
// Start sequential save processing
start_sequential_save();
}
/* --------------------------------------------------------------------
* START EIS SAVE TIMER (FIXED - Don't restart if already running)
* -------------------------------------------------------------------- */
static void start_eis_save_timer(void)
{
// FIXED: Don't restart if already running
if (eis_save_timer_running) {
fds_logf("[EIS_DELAYED] Timer already running, not restarting\r\n");
return;
}
// Create timer if not already created
if (eis_save_timer_id == APP_TIMER_ID_UNINIT) {
uint32_t err = app_timer_create(&eis_save_timer_id,
APP_TIMER_MODE_SINGLE_SHOT,
eis_save_timer_handler);
if (err != NRF_SUCCESS) {
fds_logf("[EIS_DELAYED] Timer create failed: 0x%x\r\n", err);
return;
}
}
fds_logf("[EIS_DELAYED] Starting 15-minute delay timer...\r\n");
// Start 15-minute timer
uint32_t err = app_timer_start(eis_save_timer_id,
APP_TIMER_TICKS((15 * 60 * 1000), APP_TIMER_PRESCALER),
NULL);
if (err == NRF_SUCCESS) {
eis_save_timer_running = true; // ADD THIS: Set running flag
} else {
fds_logf("[EIS_DELAYED] Timer start failed: 0x%x\r\n", err);
}
}
/* --------------------------------------------------------------------
* UPDATE EXIT SIGN DATA (FIXED - Set flag only)
* -------------------------------------------------------------------- */
void update_exit_sign_data(uint16_t key_id)
{
uint8_t bit_pos = 0xFF;
// Map key to bit position
switch (key_id) {
case EIS_AQI_KEY:
bit_pos = EIS_AQI_BIT_POS;
exit_sign_data.aqi_part.valid_signature = FLASH_PAGE_VALID;
break;
case EIS_NODE_KEY:
bit_pos = EIS_NODE_BIT_POS;
exit_sign_data.node_part.valid_signature = FLASH_PAGE_VALID;
break;
case EIS_LSD_KEY:
bit_pos = EIS_LSD_BIT_POS;
exit_sign_data.lsd_part.valid_signature = FLASH_PAGE_VALID;
break;
case EIS_POST_KEY:
bit_pos = EIS_POST_BIT_POS;
exit_sign_data.post_part.valid_signature = FLASH_PAGE_VALID;
break;
case EIS_MOTION_KEY:
bit_pos = EIS_MOTION_BIT_POS;
exit_sign_data.motion_part.valid_signature = FLASH_PAGE_VALID;
break;
case EIS_COMMAN_KEY:
bit_pos = EIS_COMMAN_BIT_POS;
exit_sign_data.comman_data.valid_signature = FLASH_PAGE_VALID;
break;
default:
uart_logf("[EIS_UPDATE] Invalid key: 0x%04X\r\n", key_id);
return;
}
// Set the update flag
SET_BIT(eis_update_flags, bit_pos);
fds_logf("[EIS_UPDATE] Updated: 0x%04X (flags=0x%04X)\r\n", key_id, eis_update_flags);
// FIXED: Only start timer if not already running
start_eis_save_timer();
}
//fds.c
FS_REGISTER_CFG(fs_config_t fs_config) = {
.callback = fs_event_handler,
.num_pages = 38, // Changed from: 38
.priority = 0xFF,
.p_start_addr = (uint32_t*)0x53000, // Changed from: 0x60000
.p_end_addr = (uint32_t*)0x79000 // Unchanged
};
//flashplacement.ld
/* Linker script to configure memory regions. */
SEARCH_DIR(.)
GROUP(-lgcc -lc -lnosys)
MEMORY
{
FLASH (rx) : ORIGIN = 0x0001F000, LENGTH = 0x26000
FDS (rw!x) : ORIGIN = 0x00053000, LENGTH = 0x26000 /* Changed from: 0x60000, 0x19000 */
RAM (rwx) : ORIGIN = 0x20003250, LENGTH = 0xC7B0 /* 0x20004000 ~ 0x2000FA00-1 */
BOOTLOADER_SETTINGS (rw) : ORIGIN = 0x0007F000, LENGTH = 4K
READBACK_PORTECTION (rw) : ORIGIN = 0x10001208, LENGTH = 0x004 /* 0x10001008 ~ 0x1000120c */
NFCPIN (rw) : ORIGIN = 0x1000120c, LENGTH = 0x004 /* 0x1000120c ~ 0x1000120f */
MBR_PARAM_ADDRESS (rw) : ORIGIN = 0x10001018, LENGTH = 0x004
/* dont forget to change pstorage_platform PSTORAGE_DATA_START_ADDR address */
/* #define APP_VERSION_INFO_MEM_ADDRESS 0x2000FA00 */
/* #define BL_VERSION_INFO_MEM_ADDRESS 0x2000FC00 */
}
SECTIONS
{
/* placing my named section at given address: */
.fs_data :
{
__start_fs_data = .;
KEEP(*(.fs_data))
__stop_fs_data = .;
} > FDS
.bootloaderSettings 0x0007F000 :
{
}
.noinit 0x2000FC80 :
{
PROVIDE (__noinit_start = .) ;
*(.noinit*)
PROVIDE (__noinit_end = .) ;
}
/* readback protection */
.readbackSettings 0x10001208 :
{
KEEP(*(.readbackSettings))
} > READBACK_PORTECTION
.nfcpinSettings 0x1000120C :
{
KEEP(*(.nfcpinSettings))
} > NFCPIN
.mbrParamAddress 0x10001018 :
{
KEEP(*(.mbrParamAddress))
} > MBR_PARAM_ADDRESS
/* section for nvm protection config */
/*
.mpuprotenset0 0x40000600 :
{
KEEP(*(.mpuprotenset0))
} > MPU
.mpuprotenset1 0x40000604 :
{
KEEP(*(.mpuprotenset1))
} > MPU
*/
}
INCLUDE "../gcc/app_common.ld"