When starting up the device with the nRF5 SDK 3.2.0 for mesh, even though the model and subscription list state is loaded from flash, it is stored again right afterwards.
I think the problem is in the access_model_add and the mesh_stack_init functions:
The access_model_add function allocates a new model in case it is not recovered from flash and sets it as outdated, so its state is eventually written to flash. The function fails, if m_access_model_config_frozen is true.
uint32_t access_model_add(const access_model_add_params_t *p_model_params,
access_model_handle_t *p_model_handle) {
/* ... */
if (0 == p_model_params->opcode_count && NULL != p_model_params->p_opcode_handlers) {
return NRF_ERROR_INVALID_LENGTH;
} else if (p_model_params->element_index >= ACCESS_ELEMENT_COUNT) {
return NRF_ERROR_NOT_FOUND;
} else if (m_access_model_config_frozen || (element_has_model_id(p_model_params->element_index, p_model_params->model_id, p_model_handle) && ACCESS_INTERNAL_STATE_IS_ALLOCATED(m_model_pool[*p_model_handle].internal_state))) {
return NRF_ERROR_FORBIDDEN;
} else if (!opcodes_are_valid(p_model_params->p_opcode_handlers, p_model_params->opcode_count)) {
return NRF_ERROR_INVALID_PARAM;
} else if (*p_model_handle == ACCESS_HANDLE_INVALID) /* The model was not recovered from the flash */
{
*p_model_handle = find_available_model();
if (ACCESS_HANDLE_INVALID == *p_model_handle) {
return NRF_ERROR_NO_MEM;
}
m_model_pool[*p_model_handle].model_info.publish_address_handle = DSM_HANDLE_INVALID;
m_model_pool[*p_model_handle].model_info.publish_appkey_handle = DSM_HANDLE_INVALID;
m_model_pool[*p_model_handle].model_info.element_index = p_model_params->element_index;
m_model_pool[*p_model_handle].model_info.model_id.model_id = p_model_params->model_id.model_id;
m_model_pool[*p_model_handle].model_info.model_id.company_id = p_model_params->model_id.company_id;
m_model_pool[*p_model_handle].model_info.publish_ttl = ACCESS_TTL_USE_DEFAULT;
increment_model_count(p_model_params->element_index, p_model_params->model_id.company_id);
ACCESS_INTERNAL_STATE_OUTDATED_SET(m_model_pool[*p_model_handle].internal_state);
}
}
However, in the mesh_stack_init function, the models are first initialized (which call access_model_add internally) and then the state is recovered from flash:
uint32_t mesh_stack_init(const mesh_stack_init_params_t * p_init_params,
bool * p_device_provisioned)
{
/* ... */
/* Initialize the access layer */
dsm_init();
access_init();
/* Initialize the configuration server */
status = config_server_init(p_init_params->models.config_server_cb);
if (status != NRF_SUCCESS)
{
return status;
}
/* Initialize the health server for the primary element */
status = health_server_init(&m_health_server, 0, DEVICE_COMPANY_ID,
p_init_params->models.health_server_attention_cb,
p_init_params->models.p_health_server_selftest_array,
p_init_params->models.health_server_num_selftests);
if (status != NRF_SUCCESS)
{
return status;
}
/* Give application opportunity to initialize application specific models */
if (p_init_params->models.models_init_cb != NULL)
{
p_init_params->models.models_init_cb();
}
/* Load configuration, and check if the device has already been provisioned */
mesh_config_load();
(void) dsm_flash_config_load();
if (access_flash_config_load())
{
access_flash_config_store();
}
/* ... */
return NRF_SUCCESS;
}
The access_flash_config_load function will set m_access_model_config_frozen to true, which means, it has to be called after the models were initialized.
In consequence, access_model_add will always allocate a new model and assume its state has not been recovered from flash. It will then set the outdated flag which stores the model to flash again when access_flash_config_store is called. The internal restore_acquired_model function only sets the restored flag, but does not remove the outdated flag, which was set before.
I guess the current implementation is wrongly assuming that access_model_add could be called after the model state has been restored, which can never be the case as with the current implementation of mesh_stack_init. I think this can be fixed in SDK version 3.2.0 in the restore_acquired_model, restore_acquired_element, restore_acquired_subscription_list functions by calling ACCESS_INTERNAL_STATE_OUTDATED_CLR in each of the functions.
I did also take a quick look a the current nRF5 Mesh SDK 4.0.0 where the above code has been refactored. I guess the issue is even worse for this version of the SDK as the model state is stored directly in the access_model_add function which definitely happens before the mesh_stack_init function could load model data from flash.
Is this a bug in the SDKs or am I doing something wrong here?