diff --git a/docs/en/api-guides/file-system-considerations.rst b/docs/en/api-guides/file-system-considerations.rst index ff5bac6756..fbd8bcee4a 100644 --- a/docs/en/api-guides/file-system-considerations.rst +++ b/docs/en/api-guides/file-system-considerations.rst @@ -190,6 +190,8 @@ Points to keep in mind when developing NVS related code: - :example:`storage/nvs/nvs_rw_value` demonstrates how to use NVS to write and read a single integer value. - :example:`storage/nvs/nvs_rw_blob` demonstrates how to use NVS to write and read a blob. +- :example:`storage/nvs/nvs_statistics` demonstrates how to obtain and interpret NVS usage statistics: free/used/available/total number of entries and number of namespaces in given NVS partition. +- :example:`storage/nvs/nvs_iteration` demonstrates how to iterate over entries of specific (or any) NVS data type and how to obtain info about such entries. - :example:`security/nvs_encryption_hmac` demonstrates NVS encryption using the HMAC peripheral, where the encryption keys are derived from the HMAC key burnt in eFuse. - :example:`security/flash_encryption` demonstrates the flash encryption workflow including NVS partition creation and usage. diff --git a/docs/en/api-reference/storage/index.rst b/docs/en/api-reference/storage/index.rst index af8361a002..f268e3e11f 100644 --- a/docs/en/api-reference/storage/index.rst +++ b/docs/en/api-reference/storage/index.rst @@ -53,6 +53,10 @@ Examples - Shows the use of the C-style API to read and write integer data types in NVS flash. * - :example:`nvs_rw_value_cxx ` - Shows the use of the C++-style API to read and write integer data types in NVS flash. + * - :example:`nvs_statistics ` + - Shows the use of the C-style API to obtain NVS usage statistics: free/used/available/total number of entries and number of namespaces in given NVS partition. + * - :example:`nvs_iteration ` + - Shows the use of the C-style API to iterate over entries of specific (or any) NVS data type and how to obtain info about such entries. * - :example:`nvs_bootloader ` - Shows the use of the API available to the bootloader code to read NVS data. * - :example:`nvsgen ` diff --git a/docs/en/api-reference/storage/nvs_flash.rst b/docs/en/api-reference/storage/nvs_flash.rst index 4824a0bef9..47a2bdf45f 100644 --- a/docs/en/api-reference/storage/nvs_flash.rst +++ b/docs/en/api-reference/storage/nvs_flash.rst @@ -177,6 +177,22 @@ You can find code examples in the :example:`storage/nvs` directory of ESP-IDF ex This example does exactly the same as :example:`storage/nvs/nvs_rw_value`, except that it uses the C++ NVS handle class. +:example:`storage/nvs/nvs_statistics` + + This example demonstrates how to obtain and interpret NVS usage statistics: free/used/available/total number of entries and number of namespaces in given NVS partition. + + Default NVS partition is erased for a clean run of this example. Then mock data string values are written. + + Usage statistics are obtained prior to and post writing, with the differences being compared to expected values of newly used entries. + +:example:`storage/nvs/nvs_iteration` + + This example demonstrates how to iterate over entries of specific (or any) NVS data type and how to obtain info about such entries. + + Default NVS partition is erased for a clean run of this example. Then mock data consisting of different NVS integer data types are written. + + After that, the example iterates over each individual data type as well as the generic ``NVS_TYPE_ANY`` type, and logs the information obtained from each iteration. + Internals --------- diff --git a/docs/zh_CN/api-guides/file-system-considerations.rst b/docs/zh_CN/api-guides/file-system-considerations.rst index 979e41f02f..529b34efc7 100644 --- a/docs/zh_CN/api-guides/file-system-considerations.rst +++ b/docs/zh_CN/api-guides/file-system-considerations.rst @@ -190,6 +190,8 @@ NVS 具有如下特性: - :example:`storage/nvs/nvs_rw_value` 演示了如何写入和读取一个整数值。 - :example:`storage/nvs/nvs_rw_blob` 演示如何写入和读取一个 blob。 +- :example:`storage/nvs/nvs_statistics` 演示了如何获取并解读 NVS 使用情况统计信息:包括指定 NVS 分区中的空闲、已用、可用、总条目数、以及命名空间数量。 +- :example:`storage/nvs/nvs_iteration` 演示了如何遍历特定(或任意)NVS 数据类型的条目,以及如何获取这些条目的相关信息。 - :example:`security/nvs_encryption_hmac` 演示了如何用 HMAC 外设进行 NVS 加密,并通过 efuse 中的 HMAC 密钥生成加密密钥。 - :example:`security/flash_encryption` 演示了如何进行 flash 加密,包括创建和使用 NVS 分区。 diff --git a/docs/zh_CN/api-reference/storage/index.rst b/docs/zh_CN/api-reference/storage/index.rst index e5b8d0c067..3145750668 100644 --- a/docs/zh_CN/api-reference/storage/index.rst +++ b/docs/zh_CN/api-reference/storage/index.rst @@ -53,6 +53,10 @@ - 演示了如何在 NVS flash 中使用 C 语言 API 读写整数数据类型。 * - :example:`nvs_rw_value ` - 演示了如何在 NVS flash 中使用 C++ 语言 API 读写整数数据类型。 + * - :example:`nvs_statistics ` + - 演示了如何使用 C 风格 API 获取 NVS 使用情况统计信息,包括指定 NVS 分区中的空闲、已用、可用、总条目数、以及命名空间数量。 + * - :example:`nvs_iteration ` + - 演示了如何使用 C 风格 API 遍历特定(或任意)NVS 数据类型的条目,以及如何获取这些条目的相关信息。 * - :example:`nvs_bootloader ` - 演示了如何使用引导加载程序代码中可用的 API 来读取 NVS 数据。 * - :example:`nvsgen ` diff --git a/docs/zh_CN/api-reference/storage/nvs_flash.rst b/docs/zh_CN/api-reference/storage/nvs_flash.rst index 4aacccb751..1c5847312d 100644 --- a/docs/zh_CN/api-reference/storage/nvs_flash.rst +++ b/docs/zh_CN/api-reference/storage/nvs_flash.rst @@ -177,6 +177,22 @@ ESP-IDF :example:`storage/nvs` 目录下提供了数个代码示例: 这个例子与 :example:`storage/nvs/nvs_rw_value` 完全一样,只是使用了 C++ 的 NVS 句柄类。 +:example:`storage/nvs/nvs_statistics` + + 该示例演示了如何获取并解读 NVS 使用情况统计信息:包括指定 NVS 分区中的空闲、已用、可用、总条目数、以及命名空间数量。 + + 默认的 NVS 分区会在运行本示例前被擦除,以确保干净的运行环境。随后,会写入模拟的字符串类型数据。 + + 在写入数据前后分别获取使用情况统计信息,并将两者的差异与新占用条目的预期值进行比较。 + +:example:`storage/nvs/nvs_iteration` + + 该示例演示了如何遍历特定(或任意)NVS 数据类型的条目,以及如何获取这些条目的相关信息。 + + 默认的 NVS 分区会在运行本示例前被擦除,以确保干净的运行环境。随后,会写入包含不同 NVS 整型数据类型的模拟数据。 + + 之后,本示例会遍历各个数据类型以及通用的 ``NVS_TYPE_ANY`` 类型,并记录在每次遍历过程中获取到的信息。 + 内部实现 --------- diff --git a/examples/storage/README.md b/examples/storage/README.md index 36b74acd12..6a29811042 100644 --- a/examples/storage/README.md +++ b/examples/storage/README.md @@ -18,6 +18,8 @@ The examples are grouped into sub-directories by category. Each category directo * `nvs_rw_value` example demonstrates how to read and write a single integer value using NVS. * `nvs_rw_value_cxx` example demonstrates how to read and write a single integer value using NVS (it uses the C++ NVS handle API). * `nvs_console` example demonstrates how to use NVS through an interactive console interface. +* `nvs_statistics` example demonstrates how to obtain and interpret stats about used/available NVS storage entries in given NVS partition. +* `nvs_iteration` example demonstrates iterating over entries of specific (or any) data type in given namespace, and the info to be obtained about the entries while doing so. * `partition_api` examples demonstrate how to use different partition APIs. * `parttool` example demonstrates common operations the partitions tool allows the user to perform. * `sd_card` examples demonstrate how to use an SD card with an ESP device. diff --git a/examples/storage/nvs/.build-test-rules.yml b/examples/storage/nvs/.build-test-rules.yml index d4173e46ea..d28db77c11 100644 --- a/examples/storage/nvs/.build-test-rules.yml +++ b/examples/storage/nvs/.build-test-rules.yml @@ -17,6 +17,12 @@ examples/storage/nvs/nvs_console: disable_test: - if: IDF_TARGET not in ["esp32", "esp32c3"] reason: only one target per arch needed +examples/storage/nvs/nvs_iteration: + depends_components: + - nvs_flash + disable_test: + - if: IDF_TARGET not in ["esp32", "esp32c3"] + reason: only one target per arch needed examples/storage/nvs/nvs_rw_blob: depends_components: - nvs_flash @@ -38,6 +44,13 @@ examples/storage/nvs/nvs_rw_value_cxx: - if: IDF_TARGET not in ["esp32", "esp32c3"] reason: only one target per arch needed +examples/storage/nvs/nvs_statistics: + depends_components: + - nvs_flash + disable_test: + - if: IDF_TARGET not in ["esp32", "esp32c3"] + reason: only one target per arch needed + examples/storage/nvs/nvsgen: depends_components: - nvs_flash diff --git a/examples/storage/nvs/nvs_iteration/CMakeLists.txt b/examples/storage/nvs/nvs_iteration/CMakeLists.txt new file mode 100644 index 0000000000..f362aeaef2 --- /dev/null +++ b/examples/storage/nvs/nvs_iteration/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +idf_build_set_property(MINIMAL_BUILD ON) +project(nvs_iteration) diff --git a/examples/storage/nvs/nvs_iteration/README.md b/examples/storage/nvs/nvs_iteration/README.md new file mode 100644 index 0000000000..a88f9aa2ec --- /dev/null +++ b/examples/storage/nvs/nvs_iteration/README.md @@ -0,0 +1,69 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | + +# Non-Volatile Storage (NVS) Iteration Example + +This example showcases how to iterate and obtain info about NVS entries of a specific (or any) NVS datatype. + +Default "nvs" partition is first erased to allow for clean example run, followed by writing 2 sets of key value pairs of different types to NVS storage. +After that, iteration is performed over the individual data types, as well as the generic `NVS_TYPE_ANY`, and relevant entry info gained from iteration is verbosely logged. + +Iterator creation in this example is performed by [nvs_entry_find()](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/storage/nvs_flash.html#_CPPv414nvs_entry_findPKcPKc10nvs_type_tP14nvs_iterator_t) and accessing entry info by [nvs_entry_info()](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/storage/nvs_flash.html#_CPPv414nvs_entry_infoK14nvs_iterator_tP16nvs_entry_info_t). + + +Detailed functional description of NVS and API is provided in [documentation](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/storage/nvs_flash.html). + +## How to use example + +### Hardware required + +This example does not require any special hardware, and can be run on any common development board. + +### Build and flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +``` +... +I (265) nvs_iteration_example: Erasing the contents of the default NVS partition... +I (595) nvs_iteration_example: Opening Non-Volatile Storage (NVS) handle for namespace '_mock_data'... +I (605) nvs_iteration_example: Writing u32 mock data key-value pairs to NVS namespace '_mock_data'... +I (605) nvs_iteration_example: Wrote 7 u32 mock data key-value pairs to NVS namespace '_mock_data'. +I (615) nvs_iteration_example: Writing i8 mock data key-value pairs to NVS namespace '_mock_data'... +I (625) nvs_iteration_example: Wrote 7 i8 mock data key-value pairs to NVS namespace '_mock_data'. +I (625) nvs_iteration_example: Committing data in NVS namespace '_mock_data'... +I (635) nvs_iteration_example: NVS handle for namespace '_mock_data' closed. +I (645) nvs_iteration_example: Iterating over NVS_TYPE_U32 data in namespace '_mock_data'... +I (655) nvs_iteration_example: Entry info: key 'system_startup' for data type type NVS_TYPE_U32 in namespace '_mock_data' +I (665) nvs_iteration_example: Entry info: key 'user_login' for data type type NVS_TYPE_U32 in namespace '_mock_data' +I (675) nvs_iteration_example: Entry info: key 'data_backup' for data type type NVS_TYPE_U32 in namespace '_mock_data' +I (685) nvs_iteration_example: Entry info: key 'sched_update' for data type type NVS_TYPE_U32 in namespace '_mock_data' +I (695) nvs_iteration_example: Entry info: key 'db_cleanup' for data type type NVS_TYPE_U32 in namespace '_mock_data' +I (705) nvs_iteration_example: Entry info: key 'security_scan' for data type type NVS_TYPE_U32 in namespace '_mock_data' +I (715) nvs_iteration_example: Entry info: key 'system_shutdown' for data type type NVS_TYPE_U32 in namespace '_mock_data' +I (725) nvs_iteration_example: Iterated over 7 entries. +I (725) nvs_iteration_example: Iterating over NVS_TYPE_I8 data in namespace '_mock_data'... +I (735) nvs_iteration_example: Entry info: key 'temperature' for data type type NVS_TYPE_I8 in namespace '_mock_data' +I (745) nvs_iteration_example: Entry info: key 'humidity' for data type type NVS_TYPE_I8 in namespace '_mock_data' +I (755) nvs_iteration_example: Entry info: key 'light_level' for data type type NVS_TYPE_I8 in namespace '_mock_data' +I (765) nvs_iteration_example: Entry info: key 'motion_detected' for data type type NVS_TYPE_I8 in namespace '_mock_data' +I (775) nvs_iteration_example: Entry info: key 'battery_level' for data type type NVS_TYPE_I8 in namespace '_mock_data' +I (785) nvs_iteration_example: Entry info: key 'signal_strength' for data type type NVS_TYPE_I8 in namespace '_mock_data' +I (795) nvs_iteration_example: Entry info: key 'error_code' for data type type NVS_TYPE_I8 in namespace '_mock_data' +I (805) nvs_iteration_example: Iterated over 7 entries. +I (815) nvs_iteration_example: Iterating over NVS_TYPE_ANY data in namespace '_mock_data'... +I (825) nvs_iteration_example: Iterated over 14 entries. +I (825) nvs_iteration_example: Returning from app_main(). +I (835) main_task: Returned from app_main() +... +``` \ No newline at end of file diff --git a/examples/storage/nvs/nvs_iteration/main/CMakeLists.txt b/examples/storage/nvs/nvs_iteration/main/CMakeLists.txt new file mode 100644 index 0000000000..b7ab6c1dd4 --- /dev/null +++ b/examples/storage/nvs/nvs_iteration/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "nvs_iteration_example.c" + PRIV_REQUIRES nvs_flash + INCLUDE_DIRS ".") diff --git a/examples/storage/nvs/nvs_iteration/main/nvs_iteration_example.c b/examples/storage/nvs/nvs_iteration/main/nvs_iteration_example.c new file mode 100644 index 0000000000..6fb8a924af --- /dev/null +++ b/examples/storage/nvs/nvs_iteration/main/nvs_iteration_example.c @@ -0,0 +1,220 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* Non-Volatile Storage (NVS) Iteration Example + + For other examples please check: + https://github.com/espressif/esp-idf/tree/master/examples + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_check.h" +#include "esp_system.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "nvs.h" + +#define MOCK_DATA_NAMESPACE "_mock_data" + +static const char *TAG = "nvs_iteration_example"; + +static const char *u32_mock_data_keys[] = { + "system_startup", + "user_login", + "data_backup", + "sched_update", + "db_cleanup", + "security_scan", + "system_shutdown" +}; +// Unix timestamps +static const uint32_t u32_mock_data_values[] = { + 1704067200, + 1704067260, + 1704067320, + 1704067440, + 1704067500, + 1704067560, + 1704067620 +}; + +static const size_t u32_mock_data_count = sizeof(u32_mock_data_keys) / sizeof(u32_mock_data_keys[0]); + +static const char *i8_mock_data_keys[] = { + "temperature", + "humidity", + "light_level", + "motion_detected", + "battery_level", + "signal_strength", + "error_code" +}; + +static const int8_t i8_mock_data_values[] = { + 22, + 45, + -5, + 1, + 95, + -10, + -42 +}; + +static const size_t i8_mock_data_count = sizeof(i8_mock_data_keys) / sizeof(i8_mock_data_keys[0]); + +static esp_err_t save_mock_data() +{ + + ESP_LOGI(TAG, "Opening Non-Volatile Storage (NVS) handle for namespace '%s'...", MOCK_DATA_NAMESPACE); + nvs_handle_t my_handle; + esp_err_t ret = nvs_open(MOCK_DATA_NAMESPACE, NVS_READWRITE, &my_handle); + ESP_RETURN_ON_ERROR(ret, TAG, "Error (%s) opening NVS handle for namespace '%s'!", esp_err_to_name(ret), MOCK_DATA_NAMESPACE); + + size_t successfully_written_entries = 0; + + ESP_LOGI(TAG, "Writing u32 mock data key-value pairs to NVS namespace '%s'...", MOCK_DATA_NAMESPACE); + for (int i = 0; i < u32_mock_data_count; i++) { + // Write uint32_t values + ret = nvs_set_u32(my_handle, u32_mock_data_keys[i], u32_mock_data_values[i]); + // Don't break/abort on error, try to continue writing + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) writing '%s':'%u' pair!", esp_err_to_name(ret), u32_mock_data_keys[i], u32_mock_data_values[i]); + } else { + successfully_written_entries++; + } + } + ESP_LOGI(TAG, "Wrote %u u32 mock data key-value pairs to NVS namespace '%s'.", successfully_written_entries, MOCK_DATA_NAMESPACE); + + successfully_written_entries = 0; + + ESP_LOGI(TAG, "Writing i8 mock data key-value pairs to NVS namespace '%s'...", MOCK_DATA_NAMESPACE); + for (int i = 0; i < i8_mock_data_count; i++) { + // Write int8_t values + ret = nvs_set_i8(my_handle, i8_mock_data_keys[i], i8_mock_data_values[i]); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) writing '%s':'%d' pair!", esp_err_to_name(ret), i8_mock_data_keys[i], i8_mock_data_values[i]); + } else { + successfully_written_entries++; + } + } + ESP_LOGI(TAG, "Wrote %u i8 mock data key-value pairs to NVS namespace '%s'.", successfully_written_entries, MOCK_DATA_NAMESPACE); + + ESP_LOGI(TAG, "Committing data in NVS namespace '%s'...", MOCK_DATA_NAMESPACE); + ret = nvs_commit(my_handle); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) committing data for namespace '%s'!", esp_err_to_name(ret), MOCK_DATA_NAMESPACE); + } + + // Close the storage handle, freeing allocated resources + nvs_close(my_handle); + ESP_LOGI(TAG, "NVS handle for namespace '%s' closed.", MOCK_DATA_NAMESPACE); + + return ESP_OK; +} + +static esp_err_t iterate_over_mock_data() +{ + ESP_LOGI(TAG, "Iterating over NVS_TYPE_U32 data in namespace '%s'...", MOCK_DATA_NAMESPACE); + nvs_iterator_t it = NULL; + size_t entries_iterated = 0; + // Find only entries of type NVS_TYPE_U32 and iterate over them + esp_err_t ret = nvs_entry_find(NVS_DEFAULT_PART_NAME, MOCK_DATA_NAMESPACE, NVS_TYPE_U32, &it); + while (ret == ESP_OK) { + nvs_entry_info_t info; + ret = nvs_entry_info(it, &info); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) obtaining entry info in namespace '%s'!", esp_err_to_name(ret), MOCK_DATA_NAMESPACE); + } else { + ESP_LOGI(TAG, "Entry info: key '%s' for data type type %s in namespace '%s'", info.key, info.type == NVS_TYPE_U32 ? "NVS_TYPE_U32" : "Error: not the expected NVS_TYPE_U32!", info.namespace_name); + } + entries_iterated++; + // ret gets checked in the while() condition, terminating the loop if it's not ESP_OK + ret = nvs_entry_next(&it); + // ESP_ERR_NVS_NOT_FOUND is expected to be returned when there is no next entry to iterate over (valid return value for last entry) + if (ret != ESP_OK && ret != ESP_ERR_NVS_NOT_FOUND) { + ESP_LOGE(TAG, "Error (%s) obtaining next entry in namespace '%s'!", esp_err_to_name(ret), MOCK_DATA_NAMESPACE); + break; + } + } + nvs_release_iterator(it); + ESP_LOGI(TAG, "Iterated over %u entries.", entries_iterated); + + ESP_LOGI(TAG, "Iterating over NVS_TYPE_I8 data in namespace '%s'...", MOCK_DATA_NAMESPACE); + it = NULL; + entries_iterated = 0; + // Find only entries of type NVS_TYPE_I8 and iterate over them + ret = nvs_entry_find(NVS_DEFAULT_PART_NAME, MOCK_DATA_NAMESPACE, NVS_TYPE_I8, &it); + while (ret == ESP_OK) { + nvs_entry_info_t info; + ret = nvs_entry_info(it, &info); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) obtaining entry info in namespace '%s'!", esp_err_to_name(ret), MOCK_DATA_NAMESPACE); + } else { + ESP_LOGI(TAG, "Entry info: key '%s' for data type type %s in namespace '%s'", info.key, info.type == NVS_TYPE_I8 ? "NVS_TYPE_I8" : "Error: not the expected NVS_TYPE_I8!", info.namespace_name); + } + entries_iterated++; + ret = nvs_entry_next(&it); + if (ret != ESP_OK && ret != ESP_ERR_NVS_NOT_FOUND) { + ESP_LOGE(TAG, "Error (%s) obtaining next entry in namespace '%s'!", esp_err_to_name(ret), MOCK_DATA_NAMESPACE); + break; + } + } + + nvs_release_iterator(it); + ESP_LOGI(TAG, "Iterated over %u entries.", entries_iterated); + + ESP_LOGI(TAG, "Iterating over NVS_TYPE_ANY data in namespace '%s'...", MOCK_DATA_NAMESPACE); + it = NULL; + entries_iterated = 0; + // Iterate over NVS_TYPE_ANY entries only to count them + ret = nvs_entry_find(NVS_DEFAULT_PART_NAME, MOCK_DATA_NAMESPACE, NVS_TYPE_ANY, &it); + while (ret == ESP_OK) { + entries_iterated++; + ret = nvs_entry_next(&it); + if (ret != ESP_OK && ret != ESP_ERR_NVS_NOT_FOUND) { + ESP_LOGE(TAG, "Error (%s) obtaining next entry in namespace '%s'!", esp_err_to_name(ret), MOCK_DATA_NAMESPACE); + break; + } + } + nvs_release_iterator(it); + ESP_LOGI(TAG, "Iterated over %u entries.", entries_iterated); + + return ESP_OK; +} + +void app_main(void) +{ + ESP_LOGI(TAG, "Erasing the contents of the default NVS partition..."); + ESP_ERROR_CHECK(nvs_flash_erase()); + + // Initialize NVS on default "nvs" partition + esp_err_t ret = nvs_flash_init(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) initializing NVS on default 'nvs' partition!", esp_err_to_name(ret)); + return; + } + + // Write mock data + ret = save_mock_data(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) saving mock data to namespace '%s'!", esp_err_to_name(ret), MOCK_DATA_NAMESPACE); + } + + // Iterate over mock data + ret = iterate_over_mock_data(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) iterating over namespace '%s'!", esp_err_to_name(ret), MOCK_DATA_NAMESPACE); + } + + ESP_LOGI(TAG, "Returning from app_main()."); +} diff --git a/examples/storage/nvs/nvs_iteration/pytest_nvs_iteration.py b/examples/storage/nvs/nvs_iteration/pytest_nvs_iteration.py new file mode 100644 index 0000000000..71293cf472 --- /dev/null +++ b/examples/storage/nvs/nvs_iteration/pytest_nvs_iteration.py @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.generic +@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target']) +def test_examples_nvs_iteration(dut: Dut) -> None: + dut.expect("Iterating over NVS_TYPE_U32 data in namespace '_mock_data'...", timeout=5) + dut.expect('Iterated over 7 entries.', timeout=5) + dut.expect("Iterating over NVS_TYPE_I8 data in namespace '_mock_data'...", timeout=5) + dut.expect('Iterated over 7 entries.', timeout=5) + dut.expect("Iterating over NVS_TYPE_ANY data in namespace '_mock_data'...", timeout=5) + dut.expect('Iterated over 14 entries.', timeout=5) diff --git a/examples/storage/nvs/nvs_iteration/sdkconfig.ci b/examples/storage/nvs/nvs_iteration/sdkconfig.ci new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/storage/nvs/nvs_statistics/CMakeLists.txt b/examples/storage/nvs/nvs_statistics/CMakeLists.txt new file mode 100644 index 0000000000..daddbbc620 --- /dev/null +++ b/examples/storage/nvs/nvs_statistics/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +idf_build_set_property(MINIMAL_BUILD ON) +project(nvs_statistics) diff --git a/examples/storage/nvs/nvs_statistics/README.md b/examples/storage/nvs/nvs_statistics/README.md new file mode 100644 index 0000000000..6991746e29 --- /dev/null +++ b/examples/storage/nvs/nvs_statistics/README.md @@ -0,0 +1,119 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | + +# Non-Volatile Storage (NVS) Statistics Example + +This example demonstrates the usage of obtaining and interpreting statistics about the a given NVS partition, namely free/used/available/total entries and namespace count. + +The default "nvs" partition is first erased, then a mock string data configuration is written to 2 different namespaces, followed by checking the changed statistics and mainly the number of newly used NVS entries. + +Statistics obtained via [nvs_get_stats()](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/storage/nvs_flash.html#_CPPv413nvs_get_statsPKcP11nvs_stats_t) are the following: +| | | +| -------------- | ------------------- | +| `available entries` | Essential statistic, specifies free non-reserved entries available for data storage. | +| `used entries` | Includes 1 overhead entry per namespace + actual data entries used by data storage. | +| `free entries` | Free entries are both the free available to the user entries, and free but internally reserved (not available to the user). | +| `total entries` | Number of all entries (free + used). | + + +Detailed functional description of NVS and API is provided in [documentation](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/storage/nvs_flash.html). + +## How to use example + +### Hardware required + +This example does not require any special hardware, and can be run on any common development board. + +### Build and flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +``` +... +I (265) nvs_statistics_example: Erasing the contents of the default NVS partition... +I (475) nvs_statistics_example: Opening Non-Volatile Storage (NVS) handle for namespace '_mock_data'... +I (485) nvs_statistics_example: Getting NVS statistics... +I (485) nvs_statistics_example: NVS statistics: +I (485) nvs_statistics_example: Used NVS entries: 1 +I (485) nvs_statistics_example: Free NVS entries: 755 +I (495) nvs_statistics_example: Available NVS entries: 629 +I (495) nvs_statistics_example: Total NVS entries: 756 +I (505) nvs_statistics_example: Namespace count: 1 +I (505) nvs_statistics_example: Writing mock data key-value pairs to NVS namespace '_mock_data'... +I (525) nvs_statistics_example: Committing data in NVS namespace '_mock_data'... +I (525) nvs_statistics_example: Getting post-commit NVS statistics... +I (525) nvs_statistics_example: NVS statistics: +I (535) nvs_statistics_example: Used NVS entries: 30 +I (535) nvs_statistics_example: Free NVS entries: 726 +I (545) nvs_statistics_example: Available NVS entries: 600 +I (545) nvs_statistics_example: Total NVS entries: 756 +I (555) nvs_statistics_example: Namespace count: 1 +I (555) nvs_statistics_example: Newly used entries match expectation. +I (565) nvs_statistics_example: Newly used entries: 29, expected: 29. +I (575) nvs_statistics_example: NVS handle for namespace '_mock_data' closed. +I (575) nvs_statistics_example: Opening Non-Volatile Storage (NVS) handle for namespace '_mock_data'... +I (585) nvs_statistics_example: Reading stored data from NVS namespace '_mock_data'... +I (595) nvs_statistics_example: Read key-value pair from NVS: 'wifi_ssid':'HomeNetwork' +I (605) nvs_statistics_example: Read key-value pair from NVS: 'wifi_pass':'MySecretPass' +I (605) nvs_statistics_example: Read key-value pair from NVS: 'dev_name':'LivingRoomThermostat' +I (615) nvs_statistics_example: Read key-value pair from NVS: 'temp_unit':'Celsius' +I (625) nvs_statistics_example: Read key-value pair from NVS: 'target_temp':'22' +I (635) nvs_statistics_example: Read key-value pair from NVS: 'eco_mode':'false' +I (635) nvs_statistics_example: Read key-value pair from NVS: 'fw_version':'1.2.3' +I (645) nvs_statistics_example: Read key-value pair from NVS: 'led_bright':'80' +I (655) nvs_statistics_example: Read key-value pair from NVS: 'auto_update':'true' +I (665) nvs_statistics_example: Read key-value pair from NVS: 'last_sync':'2025-01-01T08:00:00Z' +I (665) nvs_statistics_example: Read key-value pair from NVS: 'user_lang':'en' +I (675) nvs_statistics_example: Read key-value pair from NVS: 'long_token':'2f8c1e7b5a4d9c6e3b0f1a8e5d7c2b6f4e1a9c7b' +I (685) nvs_statistics_example: Read key-value pair from NVS: 'very_long_token':'7e2b1c9f5a4d8e3b0f1a6c7e2d9b5a4c8e1f7b2d6c3a9e5b0f1a8c7e2d9b5a4c8e1f7b2d6c3a9e5b' +I (705) nvs_statistics_example: NVS handle for namespace '_mock_data' closed. +I (705) nvs_statistics_example: Opening Non-Volatile Storage (NVS) handle for namespace '_mock_backup'... +I (715) nvs_statistics_example: Getting NVS statistics... +I (725) nvs_statistics_example: NVS statistics: +I (725) nvs_statistics_example: Used NVS entries: 31 +I (735) nvs_statistics_example: Free NVS entries: 725 +I (735) nvs_statistics_example: Available NVS entries: 599 +I (745) nvs_statistics_example: Total NVS entries: 756 +I (745) nvs_statistics_example: Namespace count: 2 +I (755) nvs_statistics_example: Writing mock data key-value pairs to NVS namespace '_mock_backup'... +I (765) nvs_statistics_example: Committing data in NVS namespace '_mock_backup'... +I (765) nvs_statistics_example: Getting post-commit NVS statistics... +I (775) nvs_statistics_example: NVS statistics: +I (775) nvs_statistics_example: Used NVS entries: 60 +I (785) nvs_statistics_example: Free NVS entries: 696 +I (785) nvs_statistics_example: Available NVS entries: 570 +I (795) nvs_statistics_example: Total NVS entries: 756 +I (795) nvs_statistics_example: Namespace count: 2 +I (805) nvs_statistics_example: Newly used entries match expectation. +I (805) nvs_statistics_example: Newly used entries: 29, expected: 29. +I (815) nvs_statistics_example: NVS handle for namespace '_mock_backup' closed. +I (825) nvs_statistics_example: Opening Non-Volatile Storage (NVS) handle for namespace '_mock_backup'... +I (835) nvs_statistics_example: Reading stored data from NVS namespace '_mock_backup'... +I (835) nvs_statistics_example: Read key-value pair from NVS: 'wifi_ssid':'HomeNetwork' +I (845) nvs_statistics_example: Read key-value pair from NVS: 'wifi_pass':'MySecretPass' +I (855) nvs_statistics_example: Read key-value pair from NVS: 'dev_name':'LivingRoomThermostat' +I (865) nvs_statistics_example: Read key-value pair from NVS: 'temp_unit':'Celsius' +I (865) nvs_statistics_example: Read key-value pair from NVS: 'target_temp':'22' +I (875) nvs_statistics_example: Read key-value pair from NVS: 'eco_mode':'false' +I (885) nvs_statistics_example: Read key-value pair from NVS: 'fw_version':'1.2.3' +I (895) nvs_statistics_example: Read key-value pair from NVS: 'led_bright':'80' +I (895) nvs_statistics_example: Read key-value pair from NVS: 'auto_update':'true' +I (905) nvs_statistics_example: Read key-value pair from NVS: 'last_sync':'2025-01-01T08:00:00Z' +I (915) nvs_statistics_example: Read key-value pair from NVS: 'user_lang':'en' +I (925) nvs_statistics_example: Read key-value pair from NVS: 'long_token':'2f8c1e7b5a4d9c6e3b0f1a8e5d7c2b6f4e1a9c7b' +I (935) nvs_statistics_example: Read key-value pair from NVS: 'very_long_token':'7e2b1c9f5a4d8e3b0f1a6c7e2d9b5a4c8e1f7b2d6c3a9e5b0f1a8c7e2d9b5a4c8e1f7b2d6c3a9e5b' +I (945) nvs_statistics_example: NVS handle for namespace '_mock_backup' closed. +I (955) nvs_statistics_example: Returning from app_main(). +I (955) main_task: Returned from app_main() +... +``` \ No newline at end of file diff --git a/examples/storage/nvs/nvs_statistics/main/CMakeLists.txt b/examples/storage/nvs/nvs_statistics/main/CMakeLists.txt new file mode 100644 index 0000000000..46493172d9 --- /dev/null +++ b/examples/storage/nvs/nvs_statistics/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "nvs_statistics_example.c" + PRIV_REQUIRES nvs_flash + INCLUDE_DIRS ".") diff --git a/examples/storage/nvs/nvs_statistics/main/nvs_statistics_example.c b/examples/storage/nvs/nvs_statistics/main/nvs_statistics_example.c new file mode 100644 index 0000000000..9b74d99d78 --- /dev/null +++ b/examples/storage/nvs/nvs_statistics/main/nvs_statistics_example.c @@ -0,0 +1,234 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* Non-Volatile Storage (NVS) Statistics Example + + For other examples please check: + https://github.com/espressif/esp-idf/tree/master/examples + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_check.h" +#include "esp_system.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "nvs.h" + +#define MOCK_DATA_NAMESPACE "_mock_data" +#define MOCK_DATA_BACKUP_NAMESPACE "_mock_backup" + +static const char *TAG = "nvs_statistics_example"; + +// Maximum key character length is 15 (NVS_KEY_NAME_MAX_SIZE-1) +static const char* mock_data_keys[] = { + "wifi_ssid", + "wifi_pass", + "dev_name", + "temp_unit", + "target_temp", + "eco_mode", + "fw_version", + "led_bright", + "auto_update", + "last_sync", + "user_lang", + "long_token", + "very_long_token" +}; + +// String values are limited in length by NVS implementation to 4000 bytes (including null character) +// Each string value occupies 1 overhead NVS entry + 1 NVS entry per each 32 characters including null character +#define CHARS_PER_STRING_VALUE_ENTRY 32 +static const char* mock_data_values[] = { + "HomeNetwork", + "MySecretPass", + "LivingRoomThermostat", + "Celsius", + "22", + "false", + "1.2.3", + "80", + "true", + "2025-01-01T08:00:00Z", + "en", + "2f8c1e7b5a4d9c6e3b0f1a8e5d7c2b6f4e1a9c7b", + "7e2b1c9f5a4d8e3b0f1a6c7e2d9b5a4c8e1f7b2d6c3a9e5b0f1a8c7e2d9b5a4c8e1f7b2d6c3a9e5b" +}; + +static const size_t mock_data_count = sizeof(mock_data_keys) / sizeof(mock_data_keys[0]); + +static void print_nvs_stats(nvs_stats_t *nvs_stats) +{ + ESP_LOGI(TAG, "NVS statistics:"); + ESP_LOGI(TAG, "Used NVS entries: %u", nvs_stats->used_entries); + ESP_LOGI(TAG, "Free NVS entries: %u", nvs_stats->free_entries); + ESP_LOGI(TAG, "Available NVS entries: %u", nvs_stats->available_entries); + ESP_LOGI(TAG, "Total NVS entries: %u", nvs_stats->total_entries); + ESP_LOGI(TAG, "Namespace count: %u", nvs_stats->namespace_count); +} + +static size_t calculate_expected_entries_for_string_array(const char** string_array, size_t array_size) +{ + size_t expected_newly_used_entries = 0; + for (int i = 0; i < array_size; i++) { + // Each string occupies 1 overhead entry + 1 entry per each 32 characters including null character + size_t str_len = strlen(string_array[i]); + size_t span = 1 + ((str_len + CHARS_PER_STRING_VALUE_ENTRY - 1) / CHARS_PER_STRING_VALUE_ENTRY); // 1 overhead + integer division rounding up + expected_newly_used_entries += span; + } + return expected_newly_used_entries; +} + +static esp_err_t save_mock_data_to_namespace(const char* namespace_name) +{ + ESP_LOGI(TAG, "Opening Non-Volatile Storage (NVS) handle for namespace '%s'...", namespace_name); + nvs_handle_t my_handle; + // Opening NVS storage handle uses up 1 entry for the namespace + esp_err_t ret = nvs_open(namespace_name, NVS_READWRITE, &my_handle); + ESP_RETURN_ON_ERROR(ret, TAG, "Error (%s) opening NVS handle for namespace '%s'!", esp_err_to_name(ret), namespace_name); + + ESP_LOGI(TAG, "Getting NVS statistics..."); + nvs_stats_t nvs_stats; + ret = nvs_get_stats(NVS_DEFAULT_PART_NAME, &nvs_stats); + ESP_GOTO_ON_ERROR(ret, nvs_close_label, TAG, "Error (%s) getting NVS statistics!", esp_err_to_name(ret)); + + // Print the pre-write NVS statistics + print_nvs_stats(&nvs_stats); + + ESP_LOGI(TAG, "Writing mock data key-value pairs to NVS namespace '%s'...", namespace_name); + for (int i = 0; i < mock_data_count; i++) { + // Write string values + ret = nvs_set_str(my_handle, mock_data_keys[i], mock_data_values[i]); + // Don't break/abort on error, try to continue writing + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) writing '%s':'%s' pair!", esp_err_to_name(ret), mock_data_keys[i], mock_data_values[i]); + } + } + + ESP_LOGI(TAG, "Committing data in NVS namespace '%s'...", namespace_name); + ret = nvs_commit(my_handle); + ESP_GOTO_ON_ERROR(ret, nvs_close_label, TAG, "Error (%s) committing data for namespace '%s'!", esp_err_to_name(ret), namespace_name); + + ESP_LOGI(TAG, "Getting post-commit NVS statistics..."); + nvs_stats_t new_nvs_stats; + ret = nvs_get_stats(NVS_DEFAULT_PART_NAME, &new_nvs_stats); + ESP_GOTO_ON_ERROR(ret, nvs_close_label, TAG, "Error (%s) getting NVS statistics!", esp_err_to_name(ret)); + + // Print the post-write NVS statistics + print_nvs_stats(&new_nvs_stats); + + size_t expected_newly_used_entries = calculate_expected_entries_for_string_array(mock_data_values, mock_data_count); + + if (new_nvs_stats.used_entries - nvs_stats.used_entries != expected_newly_used_entries) { + ESP_LOGE(TAG, "Newly used entries: %u, expected: %u.", + new_nvs_stats.used_entries - nvs_stats.used_entries, + expected_newly_used_entries); + } else { + ESP_LOGI(TAG, "Newly used entries match expectation."); + ESP_LOGI(TAG, "Newly used entries: %u, expected: %u.", + new_nvs_stats.used_entries - nvs_stats.used_entries, + expected_newly_used_entries); + } + + nvs_close_label: + // Close the storage handle, freeing allocated resources + nvs_close(my_handle); + ESP_LOGI(TAG, "NVS handle for namespace '%s' closed.", namespace_name); + + return ESP_OK; +} + +static esp_err_t read_mock_data_from_namespace(const char* namespace_name) +{ + ESP_LOGI(TAG, "Opening Non-Volatile Storage (NVS) handle for namespace '%s'...", namespace_name); + nvs_handle_t my_handle; + esp_err_t ret = nvs_open(namespace_name, NVS_READONLY, &my_handle); + ESP_RETURN_ON_ERROR(ret, TAG, "Error (%s) opening NVS handle for namespace '%s'!", esp_err_to_name(ret), namespace_name); + + ESP_LOGI(TAG, "Reading stored data from NVS namespace '%s'...", namespace_name); + for (int i = 0; i < mock_data_count; i++) { + size_t required_size = 0; + // Obtain required size of the string value including null character + ret = nvs_get_str(my_handle, mock_data_keys[i], NULL, &required_size); + if (ret != ESP_OK && ret != ESP_ERR_NVS_NOT_FOUND) { + ESP_LOGE(TAG, "Error (%s) getting required size for key '%s'!", esp_err_to_name(ret), mock_data_keys[i]); + continue; + } + if (ret == ESP_ERR_NVS_NOT_FOUND) { + ESP_LOGW(TAG, "Key '%s' not found in namespace '%s'!", mock_data_keys[i], namespace_name); + continue; + } + + // Allocate memory for the string value + char* value = malloc(required_size); + if (value == NULL) { + ESP_LOGE(TAG, "Failed to allocate memory for key '%s'!", mock_data_keys[i]); + continue; + } + + // Read the string value + ret = nvs_get_str(my_handle, mock_data_keys[i], value, &required_size); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) reading key '%s'!", esp_err_to_name(ret), mock_data_keys[i]); + free(value); + continue; + } + + ESP_LOGI(TAG, "Read key-value pair from NVS: '%s':'%s'", mock_data_keys[i], value); + free(value); + } + + // Close the storage handle, freeing allocated resources + nvs_close(my_handle); + ESP_LOGI(TAG, "NVS handle for namespace '%s' closed.", namespace_name); + + return ESP_OK; +} + +void app_main(void) +{ + // Erase the contents of the default NVS partition for clean run of this example + ESP_LOGI(TAG, "Erasing the contents of the default NVS partition..."); + ESP_ERROR_CHECK(nvs_flash_erase()); + + // Initialize NVS on default "nvs" partition + esp_err_t ret = nvs_flash_init(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) initializing NVS on default 'nvs' partition!", esp_err_to_name(ret)); + return; + } + + // Write and read mock data + ret = save_mock_data_to_namespace(MOCK_DATA_NAMESPACE); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) saving mock data data to namespace '%s'!", esp_err_to_name(ret), MOCK_DATA_NAMESPACE); + } + + ret = read_mock_data_from_namespace(MOCK_DATA_NAMESPACE); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) reading back stored data from namespace '%s'!", esp_err_to_name(ret), MOCK_DATA_NAMESPACE); + } + + // Write and read mock data "backup" + ret = save_mock_data_to_namespace(MOCK_DATA_BACKUP_NAMESPACE); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) saving mock data to namespace '%s'!", esp_err_to_name(ret), MOCK_DATA_BACKUP_NAMESPACE); + } + + ret = read_mock_data_from_namespace(MOCK_DATA_BACKUP_NAMESPACE); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error (%s) reading back stored data from namespace '%s'!", esp_err_to_name(ret), MOCK_DATA_BACKUP_NAMESPACE); + } + + ESP_LOGI(TAG, "Returning from app_main()."); +} diff --git a/examples/storage/nvs/nvs_statistics/pytest_nvs_statistics.py b/examples/storage/nvs/nvs_statistics/pytest_nvs_statistics.py new file mode 100644 index 0000000000..17fdfe738b --- /dev/null +++ b/examples/storage/nvs/nvs_statistics/pytest_nvs_statistics.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.generic +@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target']) +def test_examples_nvs_statistics(dut: Dut) -> None: + dut.expect('Getting post-commit NVS statistics...', timeout=5) + dut.expect('Newly used entries match expectation.', timeout=5) + + dut.expect('Getting post-commit NVS statistics...', timeout=5) + dut.expect('Newly used entries match expectation.', timeout=5) + + dut.expect('Returning from app_main().', timeout=5) diff --git a/examples/storage/nvs/nvs_statistics/sdkconfig.ci b/examples/storage/nvs/nvs_statistics/sdkconfig.ci new file mode 100644 index 0000000000..e69de29bb2