This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

nRF Mesh persistent data storage. Mesh config entry only?

Good day!

I need to save sensor configs in persistent storage. What is better to use for this purpose: flash manager from nRF Mesh SDK or fstorage from nRF SDK?

I tried flash manager:

Initialization:

static uint8_t bsec_state[FLASH_MANAGER_ENTRY_MAX_SIZE] = {0};
static uint32_t bsec_state_len;
#define FLASH_CUSTOM_DATA_GROUP_ELEMENT 0x1500 // A number in the range 0x0000 - 0x7EFF (flash_manager.h)
#define CUSTOM_DATA_FLASH_PAGE_COUNT (1)
static flash_manager_t m_custom_data_flash_manager;
static fm_entry_t *p_entry;
static bool bsec_mem_allocated = false;
static bool bsec_persistent_flash_init(void)
{
	flash_manager_config_t custom_data_manager_config;
	custom_data_manager_config.write_complete_cb = NULL;
	custom_data_manager_config.invalidate_complete_cb = NULL;
	custom_data_manager_config.remove_complete_cb = NULL;
	custom_data_manager_config.min_available_space = WORD_SIZE;

	// The new instance of flash manager should use an unused region of flash

    mesh_config_backend_flash_usage_t tmp_f_usage;
	mesh_config_backend_flash_usage_get(&tmp_f_usage);
	//custom_data_manager_config.p_area = (const flash_manager_page_t *)((const uint8_t *)flash_manager_recovery_page_get() - (ACCESS_FLASH_PAGE_COUNT * PAGE_SIZE) - (DSM_FLASH_PAGE_COUNT * PAGE_SIZE));
	//custom_data_manager_config.p_area = (const flash_manager_page_t *)flash_manager_recovery_page_get();
	custom_data_manager_config.p_area = (const flash_manager_page_t *)tmp_f_usage.p_start;
	custom_data_manager_config.page_count = CUSTOM_DATA_FLASH_PAGE_COUNT;

	uint32_t ret_code = flash_manager_add(&m_custom_data_flash_manager, &custom_data_manager_config);

	if (NRF_SUCCESS != ret_code) {
		DEBUG_LOG("Flash init error %d: no memory\n",ret_code);
		return false;
	}
	bsec_state_len = sizeof(bsec_state);
	ret_code = flash_manager_entry_read(&m_custom_data_flash_manager, FLASH_CUSTOM_DATA_GROUP_ELEMENT, (void *)bsec_state, &bsec_state_len);

	if (NRF_SUCCESS == ret_code) {
		DEBUG_LOG("Stored data found and succesfully uploaded\n");
                DEBUG_LOG("Test vals %d %d\n", bsec_state[0], bsec_state[1]);
		return true;
	}

	DEBUG_LOG("No previous states found, allocating memory...\n");

	p_entry = flash_manager_entry_alloc(&m_custom_data_flash_manager, FLASH_CUSTOM_DATA_GROUP_ELEMENT, bsec_state_len);
	if (p_entry == NULL) {
		DEBUG_LOG("Flash allocate error\n");
		return false;
	}
	flash_manager_wait();
	bsec_mem_allocated = true;
	return true;
}

int main(void)
{
	initialize();

	start();
	bsec_persistent_flash_init();

	for (;;)
		(void) sd_app_evt_wait();
}

As you can see commented lines 21 and 22 I tried different variants of initialization. Line 21 didn't work at all. Other lines starts fine and crashes while provisioning.

The questions are:

  1. What is better to use for this purpose: flash manager from nRF Mesh SDK or fstorage from nRF SDK?
  2. If Flash manager is prefered how to initialize it?
  3. How to check at startup that data was already stored?

nRF SDK for Mesh v 4.0, nRF SDK 16, nRF52840 (Linux, SeS)

UPD 24/04/20:

I've tried FDS solution. Fail.

static uint8_t bsec_state[FLASH_MANAGER_ENTRY_MAX_SIZE] = {0};
static uint32_t bsec_state_len = sizeof(bsec_state);
#define FLASH_CUSTOM_DATA_GROUP_ELEMENT 0x1500 // A number in the range 0x0000 - 0x7EFF (flash_manager.h)
#define CUSTOM_DATA_FLASH_PAGE_COUNT (1)
//static flash_manager_t m_custom_data_flash_manager;
//static fm_entry_t *p_entry;
static bool bsec_mem_allocated = false;
static uint8_t write_flag = 0;
static void my_fds_evt_handler(fds_evt_t const * const p_fds_evt)
{
    switch (p_fds_evt->id)
    {
        case FDS_EVT_INIT:
            if (p_fds_evt->result != NRF_SUCCESS)
            {
                // Initialization failed.
            }
            break;
				case FDS_EVT_WRITE:
						if (p_fds_evt->result == NRF_SUCCESS)
						{
							write_flag=1;
						}
						break;
        default:
            break;
    }
}
static ret_code_t fds_test_write(void)
{
		#define FILE_ID     0x1111
		#define REC_KEY     0x2222
		static uint32_t const m_deadbeef[2] = {0xDEADBEEF,0xBAADF00D};
		fds_record_t        record;
		fds_record_desc_t   record_desc;
		//fds_record_chunk_t  record_chunk;
		// Set up data.
		//record_chunk.p_data         = m_deadbeef;
		//record_chunk.length_words   = 2;
		// Set up record.
		record.file_id              = FILE_ID;
		record.key              		= REC_KEY;
		record.data.p_data       = bsec_state;
		record.data.length_words   = (sizeof(bsec_state)/WORD_SIZE);

		ret_code_t ret = fds_record_write(&record_desc, &record);
		if (ret != NRF_SUCCESS)
		{
				return ret;
		}
		 DEBUG_LOG("Writing Record ID = %d \r\n",record_desc.record_id);
		return NRF_SUCCESS;
}

static ret_code_t fds_read(void)
{
		#define FILE_ID     0x1111
		#define REC_KEY     0x2222
		fds_flash_record_t  flash_record;
		fds_record_desc_t   record_desc;
		fds_find_token_t    ftok ={0};//Important, make sure you zero init the ftok token
		uint8_t *data;
		uint32_t err_code;

		DEBUG_LOG("Start searching... \r\n");
		// Loop until all records with the given key and file ID have been found.
		while (fds_record_find(FILE_ID, REC_KEY, &record_desc, &ftok) == NRF_SUCCESS)
		{
				err_code = fds_record_open(&record_desc, &flash_record);
				if ( err_code != NRF_SUCCESS)
				{
					return err_code;
				}

				DEBUG_LOG("Found Record ID = %d\r\n",record_desc.record_id);
				DEBUG_LOG("Data = ");
				data = (uint8_t *) flash_record.p_data;
				for (uint8_t i=0;i<bsec_state_len;i++)
				{
					DEBUG_LOG("0x%8x \n",data[i]);
				}
				// Access the record through the flash_record structure.
				// Close the record when done.
				err_code = fds_record_close(&record_desc);
				if (err_code != NRF_SUCCESS)
				{
					return err_code;
				}
		}
		return NRF_SUCCESS;

}

static ret_code_t fds_test_find_and_delete (void)
{
	#define FILE_ID     0x1111
		#define REC_KEY     0x2222
		fds_record_desc_t   record_desc;
		fds_find_token_t    ftok;

		ftok.page=0;
		ftok.p_addr=NULL;
		// Loop and find records with same ID and rec key and mark them as deleted.
		while (fds_record_find(FILE_ID, REC_KEY, &record_desc, &ftok) == NRF_SUCCESS)
		{
			fds_record_delete(&record_desc);
			DEBUG_LOG("Deleted record ID: %d \r\n",record_desc.record_id);
		}
		// call the garbage collector to empty them, don't need to do this all the time, this is just for demonstration
		ret_code_t ret = fds_gc();
		if (ret != NRF_SUCCESS)
		{
				return ret;
		}
		return NRF_SUCCESS;
}

static ret_code_t fds_test_init (void)
{

		ret_code_t ret = fds_register(my_fds_evt_handler);
		if (ret != NRF_SUCCESS)
		{
					return ret;

		}
		ret = fds_init();
		if (ret != NRF_SUCCESS)
		{
				return ret;
		}

		return NRF_SUCCESS;

}

UPD 24/04/20

I increased this value in sdk_config.h #define FDS_VIRTUAL_PAGES 8 and application started... and crashed after about 15 minutes.

So question is still opened

UPD 25/05/20

Solution based on Mesh config entry is working! That's great! But now I have new question:

what is the proper way to store kind of blob, 512bytes for example represented as array?

My solution:

/***	Save \ load state functions	***/
static uint32_t state_setter(mesh_config_entry_id_t id, const void * p_entry);
static void state_getter(mesh_config_entry_id_t id, void * p_entry);

#define LIB_STATE_FILE_ID 0x0220

MESH_CONFIG_FILE(m_state_file, LIB_STATE_FILE_ID, MESH_CONFIG_STRATEGY_CONTINUOUS);

enum
{
	LIB_STATE_RECORD_1 = 0x0001,
	LIB_STATE_RECORD_2,
	LIB_STATE_RECORD_3,
	LIB_STATE_RECORD_4,
	LIB_STATE_RECORD_5,
	LIB_STATE_RECORD_6,
	LIB_STATE_RECORD_7,
	LIB_STATE_RECORD_END
};

#define LIB_STATE_C1_ENTRY_ID MESH_CONFIG_ENTRY_ID(LIB_STATE_FILE_ID, LIB_STATE_RECORD_1)
#define LIB_STATE_C2_ENTRY_ID MESH_CONFIG_ENTRY_ID(LIB_STATE_FILE_ID, LIB_STATE_RECORD_2)
#define LIB_STATE_C3_ENTRY_ID MESH_CONFIG_ENTRY_ID(LIB_STATE_FILE_ID, LIB_STATE_RECORD_3)
#define LIB_STATE_C4_ENTRY_ID MESH_CONFIG_ENTRY_ID(LIB_STATE_FILE_ID, LIB_STATE_RECORD_4)
#define LIB_STATE_C5_ENTRY_ID MESH_CONFIG_ENTRY_ID(LIB_STATE_FILE_ID, LIB_STATE_RECORD_5)
#define LIB_STATE_C6_ENTRY_ID MESH_CONFIG_ENTRY_ID(LIB_STATE_FILE_ID, LIB_STATE_RECORD_6)
#define LIB_STATE_C7_ENTRY_ID MESH_CONFIG_ENTRY_ID(LIB_STATE_FILE_ID, LIB_STATE_RECORD_7)
#define LIB_STATE_C8_ENTRY_ID MESH_CONFIG_ENTRY_ID(LIB_STATE_FILE_ID, LIB_STATE_RECORD_END)

MESH_CONFIG_ENTRY(m_lib_state_1,
			LIB_STATE_C1_ENTRY_ID,
			1,
			STATE_CHUNK_SIZE,
			state_setter,
			state_getter,
			NULL, //deleter is unnecessary
			false);

/** SAME FOR ALL OTHERS **/

MESH_CONFIG_ENTRY(m_lib_state_8,
			LIB_STATE_C8_ENTRY_ID,
			1,
			LAST_CHUNK_SIZE,
			state_setter,
			state_getter,
			NULL, //deleter is unnecessary
			false);

static uint32_t state_setter(mesh_config_entry_id_t id, const void * p_entry)
{
	NRF_MESH_ASSERT_DEBUG(LIB_STATE_FILE_ID == id.file);

	uint8_t *tmp_p = (uint8_t *)p_entry;
	uint8_t i = (id.record - 1);
	if((LIB_STATE_RECORD_END-1) != i) { //all chunks are full size except last one
		if (memcmp((p_state+i*STATE_CHUNK_SIZE), tmp_p, STATE_CHUNK_SIZE) != 0) {
			memcpy((p_state+i*STATE_CHUNK_SIZE), tmp_p, STATE_CHUNK_SIZE);
		}
	} else {
		if (memcmp((p_state+i*STATE_CHUNK_SIZE), tmp_p, LAST_CHUNK_SIZE) != 0) {
			memcpy((p_state+i*STATE_CHUNK_SIZE), tmp_p, LAST_CHUNK_SIZE);
		}
	}

	return NRF_SUCCESS;
}

uint32_t state_load(uint8_t *state_buffer)
{
	mesh_config_entry_id_t id;
	id.file = LIB_STATE_FILE_ID;
	for (uint8_t i = 0; i < LIB_STATE_RECORD_END; i++) {
	    id.record = (i+1);
	    mesh_config_entry_get(id, (state_buffer + STATE_CHUNK_SIZE * i));
	}

    /* Return array length */
	if (state[0] == 0)  // if first byte is empty - whole buffer is empty < app specific
		return 0;      

	return (i*STATE_CHUNK_SIZE + LAST_CHUNK_SIZE);
}

Related