refactor(examples/storage): add nvs_console example

This commit is contained in:
sonika.rathi
2025-03-26 08:53:49 +01:00
parent 24065c6bbd
commit f1ff9e1afc
9 changed files with 480 additions and 0 deletions

View File

@@ -9,6 +9,14 @@ examples/storage/nvs/nvs_bootloader:
- 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

View File

@@ -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)

View File

@@ -0,0 +1,111 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- |
# NVS Console Example
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.
## Overview
The example implements a command-line interface with the following features:
- Interactive console with command history
- Command auto-completion
- Command hints
- Persistent command history (optional)
- Color-coded output (on supported terminals)
## 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"`
- `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.
- 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 version of chip and SDK
- `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>
...
```

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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."
Can't render this file because it has a wrong number of fields in line 2.

View File

@@ -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')