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?