feat(examples/storage): Add NVS iteration example

Add a new example showcasing iterating over NVS entries
of a specific (or any) type, and the info to be obtained from therein.
This commit is contained in:
Martin Havlik
2025-10-06 14:56:56 +02:00
parent 7d756ebd0a
commit 42056cca1d
8 changed files with 330 additions and 8 deletions
+1 -1
View File
@@ -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.
+13 -7
View File
@@ -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
@@ -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)
@@ -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()
...
```
@@ -0,0 +1,3 @@
idf_component_register(SRCS "nvs_iteration_example.c"
PRIV_REQUIRES nvs_flash
INCLUDE_DIRS ".")
@@ -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 <stdio.h>
#include <inttypes.h>
#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().");
}
@@ -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)