mirror of
https://github.com/espressif/esp-idf.git
synced 2026-04-27 19:13:21 +00:00
Merge branch 'update/nvs_example' into 'master'
refactor(nvs_examples): refactor nvs storage examples and add nvs_console example See merge request espressif/esp-idf!37978
This commit is contained in:
@@ -16,44 +16,6 @@ examples/storage/emmc:
|
||||
- if: IDF_TARGET == "esp32s3"
|
||||
reason: only support on esp32s3
|
||||
|
||||
examples/storage/nvs_bootloader:
|
||||
depends_components:
|
||||
- nvs_flash
|
||||
- nvs_sec_provider
|
||||
disable:
|
||||
- if: CONFIG_NAME == "nvs_enc_flash_enc" and (SOC_AES_SUPPORTED != 1 and ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB != 1)
|
||||
- if: CONFIG_NAME == "nvs_enc_hmac" and (SOC_HMAC_SUPPORTED != 1 or (SOC_HMAC_SUPPORTED == 1 and (SOC_AES_SUPPORTED != 1 and ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB != 1)))
|
||||
reason: As of now in such cases, we do not have any way to perform AES operations in the bootloader build
|
||||
|
||||
examples/storage/nvs_rw_blob:
|
||||
depends_components:
|
||||
- nvs_flash
|
||||
- driver
|
||||
disable_test:
|
||||
- if: IDF_TARGET not in ["esp32", "esp32c3"]
|
||||
reason: only one target per arch needed
|
||||
|
||||
examples/storage/nvs_rw_value:
|
||||
depends_components:
|
||||
- nvs_flash
|
||||
disable_test:
|
||||
- if: IDF_TARGET not in ["esp32", "esp32c3"]
|
||||
reason: only one target per arch needed
|
||||
|
||||
examples/storage/nvs_rw_value_cxx:
|
||||
depends_components:
|
||||
- nvs_flash
|
||||
disable_test:
|
||||
- if: IDF_TARGET not in ["esp32", "esp32c3"]
|
||||
reason: only one target per arch needed
|
||||
|
||||
examples/storage/nvsgen:
|
||||
depends_components:
|
||||
- nvs_flash
|
||||
disable_test:
|
||||
- if: IDF_TARGET != "esp32"
|
||||
reason: only one target needed
|
||||
|
||||
examples/storage/partition_api/partition_find:
|
||||
depends_components:
|
||||
- esp_partition
|
||||
|
||||
@@ -17,6 +17,7 @@ The examples are grouped into sub-directories by category. Each category directo
|
||||
* `nvs_rw_blob` example demonstrates how to read and write a single integer value and a blob (binary large object) using NVS to preserve them between ESP module restarts.
|
||||
* `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.
|
||||
* `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.
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
|
||||
|
||||
examples/storage/nvs/nvs_bootloader:
|
||||
depends_components:
|
||||
- nvs_flash
|
||||
- nvs_sec_provider
|
||||
disable:
|
||||
- if: CONFIG_NAME == "nvs_enc_flash_enc" and (SOC_AES_SUPPORTED != 1 and ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB != 1)
|
||||
- if: CONFIG_NAME == "nvs_enc_hmac" and (SOC_HMAC_SUPPORTED != 1 or (SOC_HMAC_SUPPORTED == 1 and (SOC_AES_SUPPORTED != 1 and ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB != 1)))
|
||||
reason: As of now in such cases, we do not have any way to perform AES operations in the bootloader build
|
||||
|
||||
examples/storage/nvs/nvs_console:
|
||||
depends_components:
|
||||
- nvs_flash
|
||||
- console
|
||||
- vfs
|
||||
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
|
||||
- driver
|
||||
disable_test:
|
||||
- if: IDF_TARGET not in ["esp32", "esp32c3"]
|
||||
reason: only one target per arch needed
|
||||
|
||||
examples/storage/nvs/nvs_rw_value:
|
||||
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_value_cxx:
|
||||
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
|
||||
+3
-3
@@ -137,7 +137,7 @@ Enable NVS encryption using your preferred scheme. Please find more details rega
|
||||
(Note: In case you select the `HMAC based NVS encryption scheme`, make sure that you burn the below mentioned [HMAC key](./main/nvs_enc_hmac_key.bin) in the efuses.)
|
||||
|
||||
For generating the encrypted NVS partitions, we shall use [NVS partition generator](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/storage/nvs_partition_gen.html#nvs-partition-generator-utility).
|
||||
We shall use the [nvs_partition_gen.py](../../../components/nvs_flash/nvs_partition_generator/nvs_partition_gen.py) script for the operations.
|
||||
We shall use the [nvs_partition_gen.py](../../../../components/nvs_flash/nvs_partition_generator/nvs_partition_gen.py) script for the operations.
|
||||
|
||||
Along with the above mentioned file structure, the project folder also contains pre-generated encrypted partitions and the partition corresponding to the selected NVS encryption scheme is flashed along with the build artefacts using the `main/CMakeLists.txt`.
|
||||
|
||||
@@ -146,13 +146,13 @@ In case the data in `nvs_data.csv` is modified, these encrypted NVS partitions c
|
||||
1. NVS Encryption using the flash encryption scheme
|
||||
|
||||
```
|
||||
python nvs_partition_gen.py encrypt $IDF_PATH/examples/storage/nvs_bootloader/nvs_data.csv $IDF_PATH/examples/storage/nvs_bootloader/main/nvs_encrypted.bin 0x6000 --inputkey $IDF_PATH/examples/storage/nvs_bootloader/main/encryption_keys.bin
|
||||
python nvs_partition_gen.py encrypt $IDF_PATH/examples/storage/nvs/nvs_bootloader/nvs_data.csv $IDF_PATH/examples/storage/nvs/nvs_bootloader/main/nvs_encrypted.bin 0x6000 --inputkey $IDF_PATH/examples/storage/nvs/nvs_bootloader/main/encryption_keys.bin
|
||||
```
|
||||
|
||||
2. NVS Encryption using the HMAC scheme
|
||||
|
||||
```
|
||||
python nvs_partition_gen.py encrypt $IDF_PATH/examples/storage/nvs_bootloader/nvs_data.csv $IDF_PATH/examples/storage/nvs_bootloader/main/nvs_encrypted_hmac.bin 0x6000 --keygen --key_protect_hmac --kp_hmac_inputkey $IDF_PATH/examples/storage/nvs_bootloader/main/nvs_enc_hmac_key.bin
|
||||
python nvs_partition_gen.py encrypt $IDF_PATH/examples/storage/nvs/nvs_bootloader/nvs_data.csv $IDF_PATH/examples/storage/nvs/nvs_bootloader/main/nvs_encrypted_hmac.bin 0x6000 --keygen --key_protect_hmac --kp_hmac_inputkey $IDF_PATH/examples/storage/nvs/nvs_bootloader/main/nvs_enc_hmac_key.bin
|
||||
```
|
||||
|
||||
Build the application using configurations corresponding to the NVS encryption scheme that you have selected:
|
||||
@@ -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.16)
|
||||
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/system/console/advanced/components")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(nvs_console_example)
|
||||
@@ -0,0 +1,104 @@
|
||||
| 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 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- |
|
||||
|
||||
## Overview
|
||||
|
||||
This example demonstrates how to use Non-Volatile Storage (NVS) through an interactive console interface. It provides a set of commands to read, write, and manage data in NVS.
|
||||
|
||||
## Hardware Required
|
||||
|
||||
This example can run on any ESP32 family development board.
|
||||
|
||||
## Configuration
|
||||
|
||||
The example can be configured through `menuconfig`:
|
||||
1. Enable/disable command history storage (`CONFIG_CONSOLE_STORE_HISTORY`)
|
||||
2. Configure UART parameters
|
||||
3. Configure console prompt color settings
|
||||
|
||||
## How to Use
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```bash
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
### Console Commands
|
||||
|
||||
The following commands are available:
|
||||
|
||||
1. NVS Operations:
|
||||
- `nvs_namespace <namespace>` - Set current namespace
|
||||
- Example: `nvs_namespace storage`
|
||||
|
||||
- `nvs_set <key> <type> -v <value>` - Set a value in NVS
|
||||
- type can be: i8, u8, i16, u16 i32, u32 i64, u64, str, blob
|
||||
- Example: `nvs_set counter i32 -v 42`
|
||||
- Example: `nvs_set name str -v "esp"`
|
||||
- Example: `nvs_set blob_val blob -v "657370"` - To set a blob value, provide the hex representation of the data you want to store. For example, 65 (e), 73 (s), 70 (p).
|
||||
|
||||
- `nvs_get <key>` - Get a value from NVS
|
||||
- Example: `nvs_get counter`
|
||||
|
||||
- `nvs_erase <key>` - Erase a key from NVS
|
||||
- Example: `nvs_erase counter`
|
||||
|
||||
- `nvs_list <partition> [-n <namespace>] [-t <type>]` - List stored key-value pairs stored in NVS. Use default partition name 'nvs' for listing the stored data.
|
||||
- Example: `nvs_list nvs` - This command lists all namespaces and their stored key-value pairs in the 'nvs' partition.
|
||||
- Example: `nvs_list nvs -n storage -t i8`
|
||||
|
||||
- `nvs_erase_namespace <namespace>` - Erases specified namespace
|
||||
- Example: `nvs_erase_namespace storage`
|
||||
|
||||
2. System Commands:
|
||||
- `help` - List all commands
|
||||
- `free` - Get the current size of free heap memory
|
||||
- `restart` - Software reset of the chip
|
||||
- `version` - Get the chip info together with ESP-IDF version used in the application
|
||||
- `heap` - Get minimum size of free heap memory that was available during program execution
|
||||
|
||||
### Example Output
|
||||
|
||||
``` bash
|
||||
...
|
||||
NVS Console Example
|
||||
-------------------
|
||||
Type 'help' to get the list of commands.
|
||||
Use UP/DOWN arrows to navigate through command history.
|
||||
Press TAB when typing command name to auto-complete.
|
||||
Press Ctrl+C to exit the console.
|
||||
|
||||
nvs> version
|
||||
IDF Version:v5.5-dev-2627-g2cbfce97768-dirt
|
||||
Chip info:
|
||||
model:ESP32
|
||||
cores:2
|
||||
feature:/802.11bgn/BLE/BT/External-Flash:2 MB
|
||||
revision number:0
|
||||
nvs> free
|
||||
298172
|
||||
nvs> heap
|
||||
min heap size: 298156
|
||||
nvs> nvs_list nvs
|
||||
namespace 'storage_1', key 'u8_key', type 'u8'
|
||||
namespace 'storage_1', key 'i8_key', type 'i8'
|
||||
namespace 'storage_1', key 'u16_key', type 'u16'
|
||||
namespace 'storage_2', key 'u32_key', type 'u32'
|
||||
namespace 'storage_2', key 'i32_key', type 'i32'
|
||||
namespace 'storage_2', key 'str_key', type 'str'
|
||||
nvs> nvs_namespace storage_1
|
||||
I (85497) cmd_nvs: Namespace set to 'storage_1'
|
||||
nvs> nvs_get i8_key i8
|
||||
-128
|
||||
nvs> nvs_set i8_key i8 -v -50
|
||||
I (233297) cmd_nvs: Value stored under key 'i8_key'
|
||||
nvs> nvs_get i8_key i8
|
||||
-50
|
||||
nvs>
|
||||
...
|
||||
```
|
||||
@@ -0,0 +1,10 @@
|
||||
idf_component_register(SRCS "nvs_console_main.c"
|
||||
"console_settings.c"
|
||||
REQUIRES cmd_nvs cmd_system esp_driver_uart fatfs
|
||||
INCLUDE_DIRS ".")
|
||||
|
||||
# Create a NVS image from the contents of the `nvs_data` CSV file
|
||||
# that fits the partition named 'nvs'. FLASH_IN_PROJECT indicates that
|
||||
# the generated image should be flashed when the entire project is flashed to
|
||||
# the target with 'idf.py -p PORT flash'.
|
||||
nvs_create_partition_image(nvs ../nvs_data.csv FLASH_IN_PROJECT)
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_console.h"
|
||||
#include "esp_vfs_dev.h"
|
||||
#include "driver/uart.h"
|
||||
#include "driver/uart_vfs.h"
|
||||
#include "linenoise/linenoise.h"
|
||||
#include "argtable3/argtable3.h"
|
||||
#include "console_settings.h"
|
||||
|
||||
#define CONSOLE_MAX_CMDLINE_ARGS 8
|
||||
#define CONSOLE_MAX_CMDLINE_LENGTH 256
|
||||
#define CONSOLE_PROMPT_MAX_LEN (32)
|
||||
|
||||
static const char *TAG = "console_settings";
|
||||
static char prompt[CONSOLE_PROMPT_MAX_LEN];
|
||||
|
||||
void initialize_console_peripheral(void)
|
||||
{
|
||||
/* Drain stdout before reconfiguring it */
|
||||
fflush(stdout);
|
||||
fsync(fileno(stdout));
|
||||
|
||||
/* Disable buffering on stdin */
|
||||
setvbuf(stdin, NULL, _IONBF, 0);
|
||||
|
||||
/* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
|
||||
uart_vfs_dev_port_set_rx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CR);
|
||||
/* Move the caret to the beginning of the next line on '\n' */
|
||||
uart_vfs_dev_port_set_tx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CRLF);
|
||||
|
||||
/* Configure UART */
|
||||
const uart_config_t uart_config = {
|
||||
.baud_rate = CONFIG_ESP_CONSOLE_UART_BAUDRATE,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2
|
||||
.source_clk = UART_SCLK_REF_TICK,
|
||||
#else
|
||||
.source_clk = UART_SCLK_XTAL,
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Install UART driver for interrupt-driven reads and writes */
|
||||
ESP_ERROR_CHECK(uart_driver_install(CONFIG_ESP_CONSOLE_UART_NUM,
|
||||
256, 0, 0, NULL, 0));
|
||||
ESP_ERROR_CHECK(uart_param_config(CONFIG_ESP_CONSOLE_UART_NUM, &uart_config));
|
||||
|
||||
/* Tell VFS to use UART driver */
|
||||
uart_vfs_dev_use_driver(CONFIG_ESP_CONSOLE_UART_NUM);
|
||||
}
|
||||
|
||||
void initialize_console_library(const char *history_path)
|
||||
{
|
||||
/* Initialize the console */
|
||||
esp_console_config_t console_config = {
|
||||
.max_cmdline_args = CONSOLE_MAX_CMDLINE_ARGS,
|
||||
.max_cmdline_length = CONSOLE_MAX_CMDLINE_LENGTH,
|
||||
#if CONFIG_LOG_COLORS
|
||||
.hint_color = atoi(LOG_COLOR_CYAN)
|
||||
#endif
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_init(&console_config));
|
||||
|
||||
/* Configure linenoise line completion library */
|
||||
linenoiseSetMultiLine(1);
|
||||
|
||||
/* Tell linenoise where to get command completions and hints */
|
||||
linenoiseSetCompletionCallback(&esp_console_get_completion);
|
||||
linenoiseSetHintsCallback((linenoiseHintsCallback*) &esp_console_get_hint);
|
||||
|
||||
/* Set command history size */
|
||||
linenoiseHistorySetMaxLen(100);
|
||||
|
||||
/* Set command maximum length */
|
||||
linenoiseSetMaxLineLen(console_config.max_cmdline_length);
|
||||
|
||||
/* Don't return empty lines */
|
||||
linenoiseAllowEmpty(false);
|
||||
|
||||
#if CONFIG_CONSOLE_STORE_HISTORY
|
||||
/* Load command history from filesystem */
|
||||
if (history_path) {
|
||||
linenoiseHistoryLoad(history_path);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Figure out if the terminal supports escape sequences */
|
||||
int probe_status = linenoiseProbe();
|
||||
if (probe_status) { /* zero indicates success */
|
||||
ESP_LOGW(TAG, "Your terminal application does not support escape sequences.\n"
|
||||
"Line editing and history features are disabled.\n"
|
||||
"On Windows, try using Windows Terminal or Putty instead.");
|
||||
linenoiseSetDumbMode(1);
|
||||
}
|
||||
}
|
||||
|
||||
char *setup_prompt(const char *prompt_str)
|
||||
{
|
||||
const char *prompt_temp = "esp>";
|
||||
if (prompt_str) {
|
||||
prompt_temp = prompt_str;
|
||||
}
|
||||
|
||||
/* Set command line prompt */
|
||||
if (linenoiseIsDumbMode()) {
|
||||
/* Since the terminal doesn't support escape sequences,
|
||||
* don't use color codes in the prompt */
|
||||
snprintf(prompt, CONSOLE_PROMPT_MAX_LEN - 1, "%s ", prompt_temp);
|
||||
} else {
|
||||
snprintf(prompt, CONSOLE_PROMPT_MAX_LEN - 1, LOG_COLOR_I "%s " LOG_RESET_COLOR, prompt_temp);
|
||||
}
|
||||
|
||||
return prompt;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initialize console peripheral type
|
||||
*
|
||||
* Console peripheral is based on sdkconfig settings
|
||||
*
|
||||
* UART CONFIG_ESP_CONSOLE_UART_DEFAULT
|
||||
* USB_OTG CONFIG_ESP_CONSOLE_USB_CDC
|
||||
* USB_SERIAL_JTAG CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
|
||||
*/
|
||||
void initialize_console_peripheral(void);
|
||||
|
||||
/**
|
||||
* @brief Initialize linenoise and esp console
|
||||
*
|
||||
* This function initialize linenoise library and esp_console component,
|
||||
* also checks if the terminal supports escape sequences
|
||||
*
|
||||
* @param history_path Path to store command history
|
||||
*/
|
||||
void initialize_console_library(const char *history_path);
|
||||
|
||||
/**
|
||||
* @brief Initialize console prompt
|
||||
*
|
||||
* This function adds color code to the prompt (if the console supports escape sequences)
|
||||
*
|
||||
* @param prompt_str Prompt in form of string eg esp32s3>
|
||||
*
|
||||
* @return
|
||||
* - pointer to initialized prompt
|
||||
*/
|
||||
char *setup_prompt(const char *prompt_str);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_console.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "linenoise/linenoise.h"
|
||||
#include "argtable3/argtable3.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "cmd_nvs.h"
|
||||
#include "cmd_system.h"
|
||||
#include "console_settings.h"
|
||||
|
||||
static const char* TAG = "nvs_console";
|
||||
#define PROMPT_STR "nvs"
|
||||
|
||||
/* Console command history can be stored to and loaded from a file.
|
||||
* The easiest way to do this is to use FATFS filesystem on top of
|
||||
* wear_levelling library.
|
||||
*/
|
||||
#if CONFIG_CONSOLE_STORE_HISTORY
|
||||
#define MOUNT_PATH "/data"
|
||||
#define HISTORY_PATH MOUNT_PATH "/history.txt"
|
||||
|
||||
static void initialize_filesystem(void)
|
||||
{
|
||||
static wl_handle_t wl_handle;
|
||||
const esp_vfs_fat_mount_config_t mount_config = {
|
||||
.max_files = 4,
|
||||
.format_if_mount_failed = true
|
||||
};
|
||||
esp_err_t err = esp_vfs_fat_spiflash_mount_rw_wl(MOUNT_PATH, "storage", &mount_config, &wl_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to mount FATFS (%s)", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
}
|
||||
#else
|
||||
#define HISTORY_PATH NULL
|
||||
#endif // CONFIG_CONSOLE_STORE_HISTORY
|
||||
|
||||
static void initialize_nvs(void)
|
||||
{
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
err = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(err);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
initialize_nvs();
|
||||
|
||||
#if CONFIG_CONSOLE_STORE_HISTORY
|
||||
initialize_filesystem();
|
||||
ESP_LOGI(TAG, "Command history enabled");
|
||||
#else
|
||||
ESP_LOGI(TAG, "Command history disabled");
|
||||
#endif
|
||||
|
||||
/* Initialize console peripheral and library */
|
||||
initialize_console_peripheral();
|
||||
initialize_console_library(HISTORY_PATH);
|
||||
|
||||
/* Register commands */
|
||||
esp_console_register_help_command();
|
||||
register_system_common();
|
||||
register_nvs();
|
||||
|
||||
/* Set up prompt */
|
||||
const char* prompt = setup_prompt(PROMPT_STR ">");
|
||||
|
||||
/* Clear screen */
|
||||
printf("\033[2J\033[1;1H");
|
||||
|
||||
printf("\n"
|
||||
"NVS Console Example\n"
|
||||
"-------------------\n"
|
||||
"Type 'help' to get the list of commands.\n"
|
||||
"Use UP/DOWN arrows to navigate through command history.\n"
|
||||
"Press TAB when typing command name to auto-complete.\n"
|
||||
"Press Ctrl+C to exit the console.\n\n");
|
||||
|
||||
if (linenoiseIsDumbMode()) {
|
||||
printf("\n"
|
||||
"Your terminal application does not support escape sequences.\n"
|
||||
"Line editing and history features are disabled.\n"
|
||||
"On Windows, try using Windows Terminal or Putty instead.\n\n");
|
||||
}
|
||||
|
||||
/* Main loop */
|
||||
while(true) {
|
||||
char* line = linenoise(prompt);
|
||||
if (line == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Add the command to the history if not empty */
|
||||
if (strlen(line) > 0) {
|
||||
linenoiseHistoryAdd(line);
|
||||
#if CONFIG_CONSOLE_STORE_HISTORY
|
||||
linenoiseHistorySave(HISTORY_PATH);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Try to run the command */
|
||||
int ret;
|
||||
esp_err_t err = esp_console_run(line, &ret);
|
||||
if (err == ESP_ERR_NOT_FOUND) {
|
||||
printf("Unrecognized command\n");
|
||||
} else if (err == ESP_ERR_INVALID_ARG) {
|
||||
// command was empty
|
||||
} else if (err == ESP_OK && ret != ESP_OK) {
|
||||
printf("Command returned non-zero error code: 0x%x (%s)\n", ret, esp_err_to_name(ret));
|
||||
} else if (err != ESP_OK) {
|
||||
printf("Internal error: %s\n", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
linenoiseFree(line);
|
||||
}
|
||||
|
||||
ESP_LOGE(TAG, "Error or end-of-input, terminating console");
|
||||
esp_console_deinit();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
# Sample csv file
|
||||
key,type,encoding,value
|
||||
storage_1,namespace,,
|
||||
u8_key,data,u8,255
|
||||
i8_key,data,i8,-128
|
||||
u16_key,data,u16,65535
|
||||
|
||||
storage_2,namespace,,
|
||||
u32_key,data,u32,4294967295
|
||||
i32_key,data,i32,-2147483648
|
||||
str_key,data,string,"Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
Fusce quis risus justo.
|
||||
Suspendisse egestas in nisi sit amet auctor.
|
||||
Pellentesque rhoncus dictum sodales.
|
||||
In justo erat, viverra at interdum eget, interdum vel dui."
|
||||
|
@@ -0,0 +1,22 @@
|
||||
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
from time import sleep
|
||||
|
||||
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_nvs_console(dut: Dut) -> None:
|
||||
# Wait until the console prompt appears
|
||||
dut.expect('nvs> ')
|
||||
|
||||
# Write CLI command "version"
|
||||
dut.write('version')
|
||||
sleep(0.5)
|
||||
|
||||
# Check if following strings are present in the "version" command output
|
||||
dut.expect('IDF Version')
|
||||
dut.expect('Chip info')
|
||||
+22
-29
@@ -14,7 +14,7 @@ Example also shows how to implement diagnostics if read / write operation was su
|
||||
|
||||
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).
|
||||
|
||||
If not done already, consider checking simpler example *storage/nvs_rw_value*, that has been used as a starting point for preparing this one.
|
||||
If not done already, consider checking simpler example *storage/nvs/nvs_rw_value*, that has been used as a starting point for preparing this one.
|
||||
|
||||
## How to use example
|
||||
|
||||
@@ -36,34 +36,27 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui
|
||||
|
||||
## Example Output
|
||||
|
||||
First run:
|
||||
```
|
||||
Restart counter = 0
|
||||
Run time:
|
||||
Nothing saved yet!
|
||||
...
|
||||
I (288) main_task: Calling app_main()
|
||||
I (298) nvs_blob_example: Saving test data blob...
|
||||
I (308) nvs_blob_example:
|
||||
Reading updated blob data:
|
||||
I (308) nvs_blob_example: Reading test data blob:
|
||||
I (308) nvs_blob_example: ID: 123
|
||||
I (308) nvs_blob_example: Name: Test Sample
|
||||
I (308) nvs_blob_example: Values: 3.140, 2.718, -0.000, 0.000
|
||||
I (318) nvs_blob_example: Flags: 0xABCD1234
|
||||
I (318) nvs_blob_example: Counts: -100, 100
|
||||
I (328) nvs_blob_example: Active: true
|
||||
I (328) nvs_blob_example:
|
||||
Reading array data blob:
|
||||
I (338) nvs_blob_example: Array[0] = 30
|
||||
I (338) nvs_blob_example: Array[1] = 20
|
||||
I (338) nvs_blob_example:
|
||||
Blob operations completed. Monitoring GPIO for reset...
|
||||
I (348) gpio: GPIO[0]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
...
|
||||
```
|
||||
|
||||
At this point, press "Boot" button and hold it for a second. The board will perform software restart, printing:
|
||||
|
||||
```
|
||||
Restarting...
|
||||
```
|
||||
|
||||
After booting again, restart counter and run time array will be printed:
|
||||
|
||||
```
|
||||
Restart counter = 1
|
||||
Run time:
|
||||
1: 5110
|
||||
```
|
||||
|
||||
After pressing "Boot" once more:
|
||||
|
||||
```
|
||||
Restart counter = 2
|
||||
Run time:
|
||||
1: 5110
|
||||
2: 5860
|
||||
```
|
||||
|
||||
To reset the counter and run time array, erase the contents of flash memory using `idf.py erase-flash`, then upload the program again as described above.
|
||||
To reset nvs data, erase the contents of flash memory using `idf.py erase-flash`, then upload the program again as described above.
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
/* Non-Volatile Storage (NVS) Read and Write a Blob - 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_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "nvs.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
#define STORAGE_NAMESPACE "storage"
|
||||
|
||||
static const char *TAG = "nvs_blob_example";
|
||||
|
||||
/* Test data structure to demonstrate different data types in blob */
|
||||
typedef struct {
|
||||
uint8_t id;
|
||||
char name[32];
|
||||
float values[2];
|
||||
uint32_t flags;
|
||||
int16_t counts[2];
|
||||
bool active;
|
||||
} test_data_t;
|
||||
|
||||
/* Save test data as a blob in NVS */
|
||||
esp_err_t save_test_data(void)
|
||||
{
|
||||
nvs_handle_t my_handle;
|
||||
esp_err_t err;
|
||||
|
||||
// Create sample test data
|
||||
test_data_t test_data = {
|
||||
.id = 123,
|
||||
.name = "Test Sample",
|
||||
.values = {3.14f, 2.718f},
|
||||
.flags = 0xABCD1234,
|
||||
.counts = {-100, 100},
|
||||
.active = true
|
||||
};
|
||||
|
||||
// Open NVS handle
|
||||
err = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error (%s) opening NVS handle!", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
// Write blob
|
||||
ESP_LOGI(TAG, "Saving test data blob...");
|
||||
err = nvs_set_blob(my_handle, "test_data", &test_data, sizeof(test_data_t));
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to write test data blob!");
|
||||
nvs_close(my_handle);
|
||||
return err;
|
||||
}
|
||||
|
||||
// Commit
|
||||
err = nvs_commit(my_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to commit data");
|
||||
}
|
||||
|
||||
nvs_close(my_handle);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Example of storing and appending array data as a blob */
|
||||
esp_err_t save_array_data(void)
|
||||
{
|
||||
nvs_handle_t my_handle;
|
||||
esp_err_t err;
|
||||
|
||||
// Open
|
||||
err = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
// First, get the size of existing data (if any)
|
||||
size_t required_size = 0;
|
||||
err = nvs_get_blob(my_handle, "array_data", NULL, &required_size);
|
||||
if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) {
|
||||
ESP_LOGE(TAG, "Error (%s) reading array size!", esp_err_to_name(err));
|
||||
nvs_close(my_handle);
|
||||
return err;
|
||||
}
|
||||
|
||||
// Allocate memory and read existing data
|
||||
uint32_t* array_data = malloc(required_size + sizeof(uint32_t));
|
||||
if (required_size > 0) {
|
||||
err = nvs_get_blob(my_handle, "array_data", array_data, &required_size);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error (%s) reading array data!", esp_err_to_name(err));
|
||||
free(array_data);
|
||||
nvs_close(my_handle);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// Append new value
|
||||
required_size += sizeof(uint32_t);
|
||||
array_data[required_size / sizeof(uint32_t) - 1] = xTaskGetTickCount() * portTICK_PERIOD_MS;
|
||||
|
||||
// Save updated array
|
||||
err = nvs_set_blob(my_handle, "array_data", array_data, required_size);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error (%s) saving array data!", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
free(array_data);
|
||||
|
||||
// Commit
|
||||
err = nvs_commit(my_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error (%s) committing data!", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
nvs_close(my_handle);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Read and display all saved blobs */
|
||||
esp_err_t read_stored_blobs(void)
|
||||
{
|
||||
nvs_handle_t my_handle;
|
||||
esp_err_t err;
|
||||
|
||||
err = nvs_open(STORAGE_NAMESPACE, NVS_READONLY, &my_handle);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
// 1. Read test data blob
|
||||
ESP_LOGI(TAG, "Reading test data blob:");
|
||||
test_data_t test_data;
|
||||
size_t test_data_size = sizeof(test_data_t);
|
||||
err = nvs_get_blob(my_handle, "test_data", &test_data, &test_data_size);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "ID: %d", test_data.id);
|
||||
ESP_LOGI(TAG, "Name: %s", test_data.name);
|
||||
ESP_LOGI(TAG, "Values: %.3f, %.3f, %.3f, %.3f",
|
||||
test_data.values[0], test_data.values[1],
|
||||
test_data.values[2], test_data.values[3]);
|
||||
ESP_LOGI(TAG, "Flags: 0x%08" PRIX32, test_data.flags);
|
||||
ESP_LOGI(TAG, "Counts: %d, %d", test_data.counts[0], test_data.counts[1]);
|
||||
ESP_LOGI(TAG, "Active: %s", test_data.active ? "true" : "false");
|
||||
} else if (err == ESP_ERR_NVS_NOT_FOUND) {
|
||||
ESP_LOGW(TAG, "Test data not found!");
|
||||
}
|
||||
|
||||
// 2. Read array data blob
|
||||
ESP_LOGI(TAG, "\nReading array data blob:");
|
||||
size_t required_size = 0;
|
||||
err = nvs_get_blob(my_handle, "array_data", NULL, &required_size);
|
||||
if (err == ESP_OK) {
|
||||
uint32_t* array_data = malloc(required_size);
|
||||
err = nvs_get_blob(my_handle, "array_data", array_data, &required_size);
|
||||
if (err == ESP_OK) {
|
||||
for (int i = 0; i < required_size / sizeof(uint32_t); i++) {
|
||||
ESP_LOGI(TAG, "Array[%d] = %" PRIu32, i, array_data[i]);
|
||||
}
|
||||
}
|
||||
free(array_data);
|
||||
} else if (err == ESP_ERR_NVS_NOT_FOUND) {
|
||||
ESP_LOGW(TAG, "Array data not found!");
|
||||
}
|
||||
|
||||
nvs_close(my_handle);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
// Initialize NVS
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
err = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
// Save new test data
|
||||
err = save_test_data();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error (%s) saving test data!", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// Save new array data
|
||||
err = save_array_data();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error (%s) saving array data!", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// Read updated data
|
||||
ESP_LOGI(TAG, "\nReading updated blob data:");
|
||||
err = read_stored_blobs();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error (%s) reading updated data!", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "\nBlob operations completed.");
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
# SPDX-FileCopyrightText: 2022-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_rw_blob(dut: Dut) -> None:
|
||||
# Save and read test data
|
||||
dut.expect('Saving test data blob...', timeout=20)
|
||||
# Save array data
|
||||
# Read updated data
|
||||
dut.expect('Reading updated blob data:', timeout=20)
|
||||
dut.expect('Reading test data blob:', timeout=20)
|
||||
# Verify array data reading
|
||||
dut.expect('Reading array data blob:', timeout=20)
|
||||
dut.expect('Blob operations completed.', timeout=20)
|
||||
+27
-45
@@ -13,7 +13,7 @@ Example also shows how to check if read / write operation was successful, or cer
|
||||
|
||||
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).
|
||||
|
||||
Check another example *storage/nvs_rw_blob*, which shows how to read and write variable length binary data (blob).
|
||||
Check another example *storage/nvs/nvs_rw_blob*, which shows how to read and write variable length binary data (blob).
|
||||
|
||||
## How to use example
|
||||
|
||||
@@ -35,50 +35,32 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui
|
||||
|
||||
## Example Output
|
||||
|
||||
First run:
|
||||
```
|
||||
Opening Non-Volatile Storage (NVS) handle... Done
|
||||
Reading restart counter from NVS ... The value is not initialized yet!
|
||||
Updating restart counter in NVS ... Done
|
||||
Committing updates in NVS ... Done
|
||||
|
||||
Restarting in 10 seconds...
|
||||
Restarting in 9 seconds...
|
||||
Restarting in 8 seconds...
|
||||
Restarting in 7 seconds...
|
||||
Restarting in 6 seconds...
|
||||
Restarting in 5 seconds...
|
||||
Restarting in 4 seconds...
|
||||
Restarting in 3 seconds...
|
||||
Restarting in 2 seconds...
|
||||
Restarting in 1 seconds...
|
||||
Restarting in 0 seconds...
|
||||
Restarting now.
|
||||
...
|
||||
I (296) nvs_example:
|
||||
Opening Non-Volatile Storage (NVS) handle...
|
||||
I (296) nvs_example:
|
||||
Writing counter to NVS...
|
||||
I (306) nvs_example:
|
||||
Reading counter from NVS...
|
||||
I (306) nvs_example: Read counter = 42
|
||||
I (306) nvs_example:
|
||||
Writing string to NVS...
|
||||
I (306) nvs_example:
|
||||
Reading string from NVS...
|
||||
I (306) nvs_example: Read string: Hello from NVS!
|
||||
I (316) nvs_example:
|
||||
Finding keys in NVS...
|
||||
I (316) nvs_example: Key: 'message', Type: str
|
||||
I (316) nvs_example: Key: 'counter', Type: i32
|
||||
I (326) nvs_example:
|
||||
Deleting key from NVS...
|
||||
I (336) nvs_example:
|
||||
Committing updates in NVS...
|
||||
I (336) nvs_example: NVS handle closed.
|
||||
I (336) nvs_example: Returned to app_main
|
||||
I (346) main_task: Returned from app_main()
|
||||
...
|
||||
```
|
||||
|
||||
Subsequent runs:
|
||||
|
||||
```
|
||||
Opening Non-Volatile Storage (NVS) handle... Done
|
||||
Reading restart counter from NVS ... Done
|
||||
Restart counter = 1
|
||||
Updating restart counter in NVS ... Done
|
||||
Committing updates in NVS ... Done
|
||||
|
||||
Restarting in 10 seconds...
|
||||
Restarting in 9 seconds...
|
||||
Restarting in 8 seconds...
|
||||
Restarting in 7 seconds...
|
||||
Restarting in 6 seconds...
|
||||
Restarting in 5 seconds...
|
||||
Restarting in 4 seconds...
|
||||
Restarting in 3 seconds...
|
||||
Restarting in 2 seconds...
|
||||
Restarting in 1 seconds...
|
||||
Restarting in 0 seconds...
|
||||
Restarting now.
|
||||
```
|
||||
|
||||
Restart counter will increment on each run.
|
||||
|
||||
To reset the counter, erase the contents of flash memory using `idf.py erase-flash`, then upload the program again as described above.
|
||||
To reset nvs data, erase the contents of flash memory using `idf.py erase-flash`, then upload the program again as described above.
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
/* Non-Volatile Storage (NVS) Read and Write a Value - 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_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "nvs.h"
|
||||
|
||||
static const char *TAG = "nvs_example";
|
||||
|
||||
typedef struct {
|
||||
nvs_type_t type;
|
||||
const char *str;
|
||||
} type_str_pair_t;
|
||||
|
||||
static const type_str_pair_t type_str_pair[] = {
|
||||
{ NVS_TYPE_I8, "i8" },
|
||||
{ NVS_TYPE_U8, "u8" },
|
||||
{ NVS_TYPE_U16, "u16" },
|
||||
{ NVS_TYPE_I16, "i16" },
|
||||
{ NVS_TYPE_U32, "u32" },
|
||||
{ NVS_TYPE_I32, "i32" },
|
||||
{ NVS_TYPE_U64, "u64" },
|
||||
{ NVS_TYPE_I64, "i64" },
|
||||
{ NVS_TYPE_STR, "str" },
|
||||
{ NVS_TYPE_BLOB, "blob" },
|
||||
{ NVS_TYPE_ANY, "any" },
|
||||
};
|
||||
|
||||
static const size_t TYPE_STR_PAIR_SIZE = sizeof(type_str_pair) / sizeof(type_str_pair[0]);
|
||||
|
||||
static const char *type_to_str(nvs_type_t type)
|
||||
{
|
||||
for (int i = 0; i < TYPE_STR_PAIR_SIZE; i++) {
|
||||
const type_str_pair_t *p = &type_str_pair[i];
|
||||
if (p->type == type) {
|
||||
return p->str;
|
||||
}
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
// Initialize NVS
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
// NVS partition was truncated and needs to be erased
|
||||
// Retry nvs_flash_init
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
err = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
// Open NVS handle
|
||||
ESP_LOGI(TAG, "\nOpening Non-Volatile Storage (NVS) handle...");
|
||||
nvs_handle_t my_handle;
|
||||
err = nvs_open("storage", NVS_READWRITE, &my_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error (%s) opening NVS handle!", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
// Store and read an integer value
|
||||
int32_t counter = 42;
|
||||
ESP_LOGI(TAG, "\nWriting counter to NVS...");
|
||||
err = nvs_set_i32(my_handle, "counter", counter);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to write counter!");
|
||||
}
|
||||
|
||||
// Read back the value
|
||||
int32_t read_counter = 0;
|
||||
ESP_LOGI(TAG, "\nReading counter from NVS...");
|
||||
err = nvs_get_i32(my_handle, "counter", &read_counter);
|
||||
switch (err) {
|
||||
case ESP_OK:
|
||||
ESP_LOGI(TAG, "Read counter = %" PRIu32, read_counter);
|
||||
break;
|
||||
case ESP_ERR_NVS_NOT_FOUND:
|
||||
ESP_LOGW(TAG, "The value is not initialized yet!");
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Error (%s) reading!", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// Store and read a string
|
||||
ESP_LOGI(TAG, "\nWriting string to NVS...");
|
||||
err = nvs_set_str(my_handle, "message", "Hello from NVS!");
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to write string!");
|
||||
}
|
||||
|
||||
// Read back the string
|
||||
size_t required_size = 0;
|
||||
ESP_LOGI(TAG, "\nReading string from NVS...");
|
||||
err = nvs_get_str(my_handle, "message", NULL, &required_size);
|
||||
if (err == ESP_OK) {
|
||||
char* message = malloc(required_size);
|
||||
err = nvs_get_str(my_handle, "message", message, &required_size);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Read string: %s", message);
|
||||
}
|
||||
free(message);
|
||||
}
|
||||
|
||||
// Find keys in NVS
|
||||
ESP_LOGI(TAG, "\nFinding keys in NVS...");
|
||||
nvs_iterator_t it = NULL;
|
||||
esp_err_t res = nvs_entry_find("nvs", "storage", NVS_TYPE_ANY, &it);
|
||||
while(res == ESP_OK) {
|
||||
nvs_entry_info_t info;
|
||||
nvs_entry_info(it, &info);
|
||||
const char *type_str = type_to_str(info.type);
|
||||
ESP_LOGI(TAG, "Key: '%s', Type: %s", info.key, type_str);
|
||||
res = nvs_entry_next(&it);
|
||||
}
|
||||
nvs_release_iterator(it);
|
||||
|
||||
// Delete a key from NVS
|
||||
ESP_LOGI(TAG, "\nDeleting key from NVS...");
|
||||
err = nvs_erase_key(my_handle, "counter");
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to erase key!");
|
||||
}
|
||||
|
||||
// Commit changes
|
||||
// After setting any values, nvs_commit() must be called to ensure changes are written
|
||||
// to flash storage. Implementations may write to storage at other times,
|
||||
// but this is not guaranteed.
|
||||
ESP_LOGI(TAG, "\nCommitting updates in NVS...");
|
||||
err = nvs_commit(my_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to commit NVS changes!");
|
||||
}
|
||||
|
||||
// Close
|
||||
nvs_close(my_handle);
|
||||
ESP_LOGI(TAG, "NVS handle closed.");
|
||||
|
||||
ESP_LOGI(TAG, "Returned to app_main");
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
# SPDX-FileCopyrightText: 2022-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_rw_value(dut: Dut) -> None:
|
||||
dut.serial.erase_flash()
|
||||
dut.serial.flash()
|
||||
|
||||
dut.expect('Opening Non-Volatile Storage \\(NVS\\) handle...', timeout=20)
|
||||
dut.expect('Writing counter to NVS...', timeout=20)
|
||||
dut.expect('Reading counter from NVS...', timeout=20)
|
||||
dut.expect('Writing string to NVS...', timeout=20)
|
||||
dut.expect('Reading string from NVS...', timeout=20)
|
||||
dut.expect('Finding keys in NVS...', timeout=20)
|
||||
dut.expect('Deleting key from NVS...', timeout=20)
|
||||
dut.expect('Committing updates in NVS...', timeout=20)
|
||||
dut.expect('NVS handle closed.', timeout=20)
|
||||
dut.expect('Returned to app_main', timeout=20)
|
||||
+5
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
/* Non-Volatile Storage (NVS) Read and Write a Value - Example
|
||||
|
||||
For other examples please check:
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example demonstrates how to use the NVS image generation tool [nvs_partition_gen.py](../../../components/nvs_flash/nvs_partition_generator/nvs_partition_gen.py) to automatically create a NVS partition image from the contents of a CSV file during build, with an option of automatically flashing the created image on invocation of `idf.py -p PORT flash`. For more information, see description of `nvs_partition_gen.py` on the ESP-IDF Programming Guide under API Reference > Storage API > NVS Partition Generator Utility.
|
||||
This example demonstrates how to use the NVS image generation tool [nvs_partition_gen.py](../../../../components/nvs_flash/nvs_partition_generator/nvs_partition_gen.py) to automatically create a NVS partition image from the contents of a CSV file during build, with an option of automatically flashing the created image on invocation of `idf.py -p PORT flash`. For more information, see description of `nvs_partition_gen.py` on the ESP-IDF Programming Guide under API Reference > Storage API > NVS Partition Generator Utility.
|
||||
|
||||
The following gives an overview of the example:
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
/* Non-Volatile Storage (NVS) Read and Write a Blob - 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_system.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "nvs.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
#define STORAGE_NAMESPACE "storage"
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32C3
|
||||
#define BOOT_MODE_PIN GPIO_NUM_9
|
||||
#else
|
||||
#define BOOT_MODE_PIN GPIO_NUM_0
|
||||
#endif //CONFIG_IDF_TARGET_ESP32C3
|
||||
|
||||
/* Save the number of module restarts in NVS
|
||||
by first reading and then incrementing
|
||||
the number that has been saved previously.
|
||||
Return an error if anything goes wrong
|
||||
during this process.
|
||||
*/
|
||||
esp_err_t save_restart_counter(void)
|
||||
{
|
||||
nvs_handle_t my_handle;
|
||||
esp_err_t err;
|
||||
|
||||
// Open
|
||||
err = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
// Read
|
||||
int32_t restart_counter = 0; // value will default to 0, if not set yet in NVS
|
||||
err = nvs_get_i32(my_handle, "restart_conter", &restart_counter);
|
||||
if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err;
|
||||
|
||||
// Write
|
||||
restart_counter++;
|
||||
err = nvs_set_i32(my_handle, "restart_conter", restart_counter);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
// Commit written value.
|
||||
// After setting any values, nvs_commit() must be called to ensure changes are written
|
||||
// to flash storage. Implementations may write to storage at other times,
|
||||
// but this is not guaranteed.
|
||||
err = nvs_commit(my_handle);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
// Close
|
||||
nvs_close(my_handle);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* Save new run time value in NVS
|
||||
by first reading a table of previously saved values
|
||||
and then adding the new value at the end of the table.
|
||||
Return an error if anything goes wrong
|
||||
during this process.
|
||||
*/
|
||||
esp_err_t save_run_time(void)
|
||||
{
|
||||
nvs_handle_t my_handle;
|
||||
esp_err_t err;
|
||||
|
||||
// Open
|
||||
err = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
// Read the size of memory space required for blob
|
||||
size_t required_size = 0; // value will default to 0, if not set yet in NVS
|
||||
err = nvs_get_blob(my_handle, "run_time", NULL, &required_size);
|
||||
if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err;
|
||||
|
||||
// Read previously saved blob if available
|
||||
uint32_t* run_time = malloc(required_size + sizeof(uint32_t));
|
||||
if (required_size > 0) {
|
||||
err = nvs_get_blob(my_handle, "run_time", run_time, &required_size);
|
||||
if (err != ESP_OK) {
|
||||
free(run_time);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// Write value including previously saved blob if available
|
||||
required_size += sizeof(uint32_t);
|
||||
run_time[required_size / sizeof(uint32_t) - 1] = xTaskGetTickCount() * portTICK_PERIOD_MS;
|
||||
err = nvs_set_blob(my_handle, "run_time", run_time, required_size);
|
||||
free(run_time);
|
||||
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
// Commit
|
||||
err = nvs_commit(my_handle);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
// Close
|
||||
nvs_close(my_handle);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* Read from NVS and print restart counter
|
||||
and the table with run times.
|
||||
Return an error if anything goes wrong
|
||||
during this process.
|
||||
*/
|
||||
esp_err_t print_what_saved(void)
|
||||
{
|
||||
nvs_handle_t my_handle;
|
||||
esp_err_t err;
|
||||
|
||||
// Open
|
||||
err = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle);
|
||||
if (err != ESP_OK) return err;
|
||||
|
||||
// Read restart counter
|
||||
int32_t restart_counter = 0; // value will default to 0, if not set yet in NVS
|
||||
err = nvs_get_i32(my_handle, "restart_conter", &restart_counter);
|
||||
if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err;
|
||||
printf("Restart counter = %" PRIu32 "\n", restart_counter);
|
||||
|
||||
// Read run time blob
|
||||
size_t required_size = 0; // value will default to 0, if not set yet in NVS
|
||||
// obtain required memory space to store blob being read from NVS
|
||||
err = nvs_get_blob(my_handle, "run_time", NULL, &required_size);
|
||||
if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err;
|
||||
printf("Run time:\n");
|
||||
if (required_size == 0) {
|
||||
printf("Nothing saved yet!\n");
|
||||
} else {
|
||||
uint32_t* run_time = malloc(required_size);
|
||||
err = nvs_get_blob(my_handle, "run_time", run_time, &required_size);
|
||||
if (err != ESP_OK) {
|
||||
free(run_time);
|
||||
return err;
|
||||
}
|
||||
for (int i = 0; i < required_size / sizeof(uint32_t); i++) {
|
||||
printf("%d: %" PRIu32 "\n", i + 1, run_time[i]);
|
||||
}
|
||||
free(run_time);
|
||||
}
|
||||
|
||||
// Close
|
||||
nvs_close(my_handle);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
// NVS partition was truncated and needs to be erased
|
||||
// Retry nvs_flash_init
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
err = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK( err );
|
||||
|
||||
err = print_what_saved();
|
||||
if (err != ESP_OK) printf("Error (%s) reading data from NVS!\n", esp_err_to_name(err));
|
||||
|
||||
err = save_restart_counter();
|
||||
if (err != ESP_OK) printf("Error (%s) saving restart counter to NVS!\n", esp_err_to_name(err));
|
||||
|
||||
gpio_reset_pin(BOOT_MODE_PIN);
|
||||
gpio_set_direction(BOOT_MODE_PIN, GPIO_MODE_INPUT);
|
||||
|
||||
/* Read the status of GPIO0. If GPIO0 is LOW for longer than 1000 ms,
|
||||
then save module's run time and restart it
|
||||
*/
|
||||
while (1) {
|
||||
if (gpio_get_level(BOOT_MODE_PIN) == 0) {
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
if(gpio_get_level(BOOT_MODE_PIN) == 0) {
|
||||
err = save_run_time();
|
||||
if (err != ESP_OK) printf("Error (%s) saving run time blob to NVS!\n", esp_err_to_name(err));
|
||||
printf("Restarting...\n");
|
||||
fflush(stdout);
|
||||
esp_restart();
|
||||
}
|
||||
}
|
||||
vTaskDelay(200 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
import logging
|
||||
import random
|
||||
import re
|
||||
import time
|
||||
from typing import List
|
||||
|
||||
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_rw_blob(dut: Dut) -> None:
|
||||
def expect_start_msg(index: int) -> None:
|
||||
dut.expect('Restart counter = {}'.format(index), timeout=10)
|
||||
dut.expect('Run time:', timeout=10)
|
||||
|
||||
expect_start_msg(0)
|
||||
dut.expect('Nothing saved yet!', timeout=5)
|
||||
nvs_store: List[str] = []
|
||||
for i in range(1, 10):
|
||||
time.sleep(random.uniform(0.1, 2)) # in order to randomize the runtimes stored in NVS
|
||||
try:
|
||||
# Pulling pin low using DTR
|
||||
dut.serial.proc.setDTR(True)
|
||||
dut.expect('Restarting...', timeout=5) # the application waits for a second
|
||||
finally:
|
||||
dut.serial.proc.setDTR(False)
|
||||
|
||||
expect_start_msg(i)
|
||||
|
||||
for store_item in nvs_store:
|
||||
dut.expect(store_item.encode(), timeout=10)
|
||||
|
||||
logging.info('Received: {}'.format(', '.join(nvs_store)))
|
||||
new_runtime = (dut.expect(re.compile(str.encode('{}: (\\d+)'.format(i))), timeout=10)[0]).decode()
|
||||
nvs_store.append(new_runtime)
|
||||
logging.info('loop {} has finished with runtime {}'.format(i, new_runtime))
|
||||
@@ -1,86 +0,0 @@
|
||||
/* Non-Volatile Storage (NVS) Read and Write a Value - 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_system.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "nvs.h"
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
// Initialize NVS
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
// NVS partition was truncated and needs to be erased
|
||||
// Retry nvs_flash_init
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
err = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK( err );
|
||||
|
||||
// Open
|
||||
printf("\n");
|
||||
printf("Opening Non-Volatile Storage (NVS) handle... ");
|
||||
nvs_handle_t my_handle;
|
||||
err = nvs_open("storage", NVS_READWRITE, &my_handle);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error (%s) opening NVS handle!\n", esp_err_to_name(err));
|
||||
} else {
|
||||
printf("Done\n");
|
||||
|
||||
// Read
|
||||
printf("Reading restart counter from NVS ... ");
|
||||
int32_t restart_counter = 0; // value will default to 0, if not set yet in NVS
|
||||
err = nvs_get_i32(my_handle, "restart_counter", &restart_counter);
|
||||
switch (err) {
|
||||
case ESP_OK:
|
||||
printf("Done\n");
|
||||
printf("Restart counter = %" PRIu32 "\n", restart_counter);
|
||||
break;
|
||||
case ESP_ERR_NVS_NOT_FOUND:
|
||||
printf("The value is not initialized yet!\n");
|
||||
break;
|
||||
default :
|
||||
printf("Error (%s) reading!\n", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// Write
|
||||
printf("Updating restart counter in NVS ... ");
|
||||
restart_counter++;
|
||||
err = nvs_set_i32(my_handle, "restart_counter", restart_counter);
|
||||
printf((err != ESP_OK) ? "Failed!\n" : "Done\n");
|
||||
|
||||
// Commit written value.
|
||||
// After setting any values, nvs_commit() must be called to ensure changes are written
|
||||
// to flash storage. Implementations may write to storage at other times,
|
||||
// but this is not guaranteed.
|
||||
printf("Committing updates in NVS ... ");
|
||||
err = nvs_commit(my_handle);
|
||||
printf((err != ESP_OK) ? "Failed!\n" : "Done\n");
|
||||
|
||||
// Close
|
||||
nvs_close(my_handle);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
// Restart module
|
||||
for (int i = 10; i >= 0; i--) {
|
||||
printf("Restarting in %d seconds...\n", i);
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
printf("Restarting now.\n");
|
||||
fflush(stdout);
|
||||
esp_restart();
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
import logging
|
||||
from itertools import zip_longest
|
||||
|
||||
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_rw_value(dut: Dut) -> None:
|
||||
dut.serial.erase_flash()
|
||||
dut.serial.flash()
|
||||
for i, counter_state in zip_longest(range(4), ('The value is not initialized yet!',), fillvalue='Done'):
|
||||
dut.expect('Opening Non-Volatile Storage \\(NVS\\) handle... Done', timeout=20)
|
||||
dut.expect('Reading restart counter from NVS ... {}'.format(counter_state), timeout=20)
|
||||
dut.expect('Restart counter = {}'.format(i) if int(i) > 0 else '', timeout=20)
|
||||
dut.expect('Updating restart counter in NVS ... Done', timeout=20)
|
||||
dut.expect('Committing updates in NVS ... Done', timeout=20)
|
||||
dut.expect('Restarting in 10 seconds...', timeout=20)
|
||||
logging.info('loop {} has finished'.format(i))
|
||||
Reference in New Issue
Block a user