fds_record_update() creates duplicate records in SDK 13.0.0 - Critical data corruption bug

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):

  1. New record is written to flash

  2. Old record is marked as dirty (deleted)

  3. Result: ONE valid record (new), ONE dirty record (old)

  4. Valid record count stays constant


Actual Behavior

When calling fds_record_update(&desc, &record):

  1. New record is written to flash

  2. Old record delete FAILS silently

  3. Result: TWO valid records (old + new)

  4. Valid record count INCREASES on every update

  5. 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"

Parents Reply Children
Related