diff --git a/examples/storage/README.md b/examples/storage/README.md index f3f1e7c351..6a29811042 100644 --- a/examples/storage/README.md +++ b/examples/storage/README.md @@ -19,7 +19,7 @@ The examples are grouped into sub-directories by category. Each category directo * `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` TODO +* `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 b490dbdc93..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,16 +44,16 @@ 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 disable_test: - if: IDF_TARGET != "esp32" reason: only one target 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 \ No newline at end of file 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