diff --git a/components/nvs_flash/include/nvs.h b/components/nvs_flash/include/nvs.h index 0cc3ba09a..1d88217ee 100644 --- a/components/nvs_flash/include/nvs.h +++ b/components/nvs_flash/include/nvs.h @@ -65,6 +65,20 @@ typedef enum { NVS_READWRITE /*!< Read and write */ } nvs_open_mode; +typedef enum { + NVS_TYPE_U8 = 0x01, + NVS_TYPE_I8 = 0x11, + NVS_TYPE_U16 = 0x02, + NVS_TYPE_I16 = 0x12, + NVS_TYPE_U32 = 0x04, + NVS_TYPE_I32 = 0x14, + NVS_TYPE_U64 = 0x08, + NVS_TYPE_I64 = 0x18, + NVS_TYPE_STR = 0x21, + NVS_TYPE_BLOB = 0x42, + NVS_TYPE_ANY = 0xff // Must be last +} nvs_type_t; + /** * @brief Open non-volatile storage with a given namespace from the default NVS partition * diff --git a/examples/system/console/components/cmd_nvs/CMakeLists.txt b/examples/system/console/components/cmd_nvs/CMakeLists.txt new file mode 100644 index 000000000..7c2d3c7d0 --- /dev/null +++ b/examples/system/console/components/cmd_nvs/CMakeLists.txt @@ -0,0 +1,7 @@ +set(COMPONENT_ADD_INCLUDEDIRS .) + +set(COMPONENT_SRCS "cmd_nvs.c") + +set(COMPONENT_REQUIRES console nvs_flash) + +register_component() diff --git a/examples/system/console/components/cmd_nvs/cmd_nvs.c b/examples/system/console/components/cmd_nvs/cmd_nvs.c new file mode 100644 index 000000000..a7e4319c4 --- /dev/null +++ b/examples/system/console/components/cmd_nvs/cmd_nvs.c @@ -0,0 +1,518 @@ +/* Console example — NVS commands + + 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 +#include +#include +#include "esp_log.h" +#include "esp_console.h" +#include "argtable3/argtable3.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "esp_err.h" +#include "cmd_nvs.h" +#include "nvs.h" + +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 *ARG_TYPE_STR = "type can be: i8, u8, i16, u16 i32, u32 i64, u64, str, blob"; +static char current_namespace[16] = "storage"; +static const char *TAG = "cmd_nvs"; + +static struct { + struct arg_str *key; + struct arg_str *type; + struct arg_str *value; + struct arg_end *end; +} set_args; + +static struct { + struct arg_str *key; + struct arg_str *type; + struct arg_end *end; +} get_args; + +static struct { + struct arg_str *key; + struct arg_end *end; +} erase_args; + +static struct { + struct arg_str *namespace; + struct arg_end *end; +} erase_all_args; + +static struct { + struct arg_str *namespace; + struct arg_end *end; +} namespace_args; + + +static nvs_type_t str_to_type(const char *type) +{ + for (int i = 0; i < TYPE_STR_PAIR_SIZE; i++) { + const type_str_pair_t *p = &type_str_pair[i]; + if (strncmp(type, p->str, strlen(p->str)) == 0) { + return p->type; + } + } + + return NVS_TYPE_ANY; +} + +static esp_err_t store_blob(nvs_handle nvs, const char *key, const char *str_values) +{ + uint8_t value; + size_t str_len = strlen(str_values); + size_t blob_len = str_len / 2; + + if (str_len % 2) { + ESP_LOGE(TAG, "Blob data must contain even number of characters"); + return ESP_ERR_NVS_TYPE_MISMATCH; + } + + char *blob = (char *)malloc(blob_len); + if (blob == NULL) { + return ESP_ERR_NO_MEM; + } + + for (int i = 0, j = 0; i < str_len; i++) { + char ch = str_values[i]; + if (ch >= '0' && ch <= '9') { + value = ch - '0'; + } else if (ch >= 'A' && ch <= 'F') { + value = ch - 'A' + 10; + } else if (ch >= 'a' && ch <= 'f') { + value = ch - 'a' + 10; + } else { + ESP_LOGE(TAG, "Blob data contain invalid character"); + free(blob); + return ESP_ERR_NVS_TYPE_MISMATCH; + } + + if (i & 1) { + blob[j++] += value; + } else { + blob[j] = value << 4; + } + } + + esp_err_t err = nvs_set_blob(nvs, key, blob, blob_len); + free(blob); + + if (err == ESP_OK) { + err = nvs_commit(nvs); + } + + return err; +} + +static void print_blob(const char *blob, size_t len) +{ + for (int i = 0; i < len; i++) { + printf("%02x", blob[i]); + } + printf("\n"); +} + +static esp_err_t set_value_in_nvs(const char *key, const char *str_type, const char *str_value) +{ + esp_err_t err; + nvs_handle nvs; + bool range_error = false; + + nvs_type_t type = str_to_type(str_type); + + if (type == NVS_TYPE_ANY) { + return ESP_ERR_NVS_TYPE_MISMATCH; + } + + err = nvs_open(current_namespace, NVS_READWRITE, &nvs); + if (err != ESP_OK) { + return err; + } + + if (type == NVS_TYPE_I8) { + int32_t value = strtol(str_value, NULL, 0); + if (value < INT8_MIN || value > INT8_MAX || errno == ERANGE) { + range_error = true; + } else { + err = nvs_set_i8(nvs, key, (int8_t)value); + } + } else if (type == NVS_TYPE_U8) { + uint32_t value = strtoul(str_value, NULL, 0); + if (value > UINT8_MAX || errno == ERANGE) { + range_error = true; + } else { + err = nvs_set_u8(nvs, key, (uint8_t)value); + } + } else if (type == NVS_TYPE_I16) { + int32_t value = strtol(str_value, NULL, 0); + if (value < INT16_MIN || value > INT16_MAX || errno == ERANGE) { + range_error = true; + } else { + err = nvs_set_i16(nvs, key, (int16_t)value); + } + } else if (type == NVS_TYPE_U16) { + uint32_t value = strtoul(str_value, NULL, 0); + if (value > UINT16_MAX || errno == ERANGE) { + range_error = true; + } else { + err = nvs_set_u16(nvs, key, (uint16_t)value); + } + } else if (type == NVS_TYPE_I32) { + int32_t value = strtol(str_value, NULL, 0); + if (errno != ERANGE) { + err = nvs_set_i32(nvs, key, value); + } + } else if (type == NVS_TYPE_U32) { + uint32_t value = strtoul(str_value, NULL, 0); + if (errno != ERANGE) { + err = nvs_set_u32(nvs, key, value); + } + } else if (type == NVS_TYPE_I64) { + int64_t value = strtoll(str_value, NULL, 0); + if (errno != ERANGE) { + err = nvs_set_i64(nvs, key, value); + } + } else if (type == NVS_TYPE_U64) { + uint64_t value = strtoull(str_value, NULL, 0); + if (errno != ERANGE) { + err = nvs_set_u64(nvs, key, value); + } + } else if (type == NVS_TYPE_STR) { + err = nvs_set_str(nvs, key, str_value); + } else if (type == NVS_TYPE_BLOB) { + err = store_blob(nvs, key, str_value); + } + + if (range_error || errno == ERANGE) { + nvs_close(nvs); + return ESP_ERR_NVS_VALUE_TOO_LONG; + } + + if (err == ESP_OK) { + err = nvs_commit(nvs); + if (err == ESP_OK) { + ESP_LOGI(TAG, "Value stored under key '%s'", key); + } + } + + nvs_close(nvs); + return err; +} + +static esp_err_t get_value_from_nvs(const char *key, const char *str_type) +{ + nvs_handle nvs; + esp_err_t err; + + nvs_type_t type = str_to_type(str_type); + + if (type == NVS_TYPE_ANY) { + return ESP_ERR_NVS_TYPE_MISMATCH; + } + + err = nvs_open(current_namespace, NVS_READONLY, &nvs); + if (err != ESP_OK) { + return err; + } + + if (type == NVS_TYPE_I8) { + int8_t value; + err = nvs_get_i8(nvs, key, &value); + if (err == ESP_OK) { + printf("Value associated with key '%s' is %d \n", key, value); + } + } else if (type == NVS_TYPE_U8) { + uint8_t value; + err = nvs_get_u8(nvs, key, &value); + if (err == ESP_OK) { + printf("Value associated with key '%s' is %u \n", key, value); + } + } else if (type == NVS_TYPE_I16) { + int16_t value; + err = nvs_get_i16(nvs, key, &value); + if (err == ESP_OK) { + printf("Value associated with key '%s' is %d \n", key, value); + } + } else if (type == NVS_TYPE_U16) { + uint16_t value; + if ((err = nvs_get_u16(nvs, key, &value)) == ESP_OK) { + printf("Value associated with key '%s' is %u", key, value); + } + } else if (type == NVS_TYPE_I32) { + int32_t value; + if ((err = nvs_get_i32(nvs, key, &value)) == ESP_OK) { + printf("Value associated with key '%s' is %d \n", key, value); + } + } else if (type == NVS_TYPE_U32) { + uint32_t value; + if ((err = nvs_get_u32(nvs, key, &value)) == ESP_OK) { + printf("Value associated with key '%s' is %u \n", key, value); + } + } else if (type == NVS_TYPE_I64) { + int64_t value; + if ((err = nvs_get_i64(nvs, key, &value)) == ESP_OK) { + printf("Value associated with key '%s' is %lld \n", key, value); + } + } else if (type == NVS_TYPE_U64) { + uint64_t value; + if ( (err = nvs_get_u64(nvs, key, &value)) == ESP_OK) { + printf("Value associated with key '%s' is %llu \n", key, value); + } + } else if (type == NVS_TYPE_STR) { + size_t len; + if ( (err = nvs_get_str(nvs, key, NULL, &len)) == ESP_OK) { + char *str = (char *)malloc(len); + if ( (err = nvs_get_str(nvs, key, str, &len)) == ESP_OK) { + printf("String associated with key '%s' is %s \n", key, str); + } + free(str); + } + } else if (type == NVS_TYPE_BLOB) { + size_t len; + if ( (err = nvs_get_blob(nvs, key, NULL, &len)) == ESP_OK) { + char *blob = (char *)malloc(len); + if ( (err = nvs_get_blob(nvs, key, blob, &len)) == ESP_OK) { + printf("Blob associated with key '%s' is %d bytes long: \n", key, len); + print_blob(blob, len); + } + free(blob); + } + } + + nvs_close(nvs); + return err; +} + +static esp_err_t erase(const char *key) +{ + nvs_handle nvs; + + esp_err_t err = nvs_open(current_namespace, NVS_READWRITE, &nvs); + if (err == ESP_OK) { + err = nvs_erase_key(nvs, key); + if (err == ESP_OK) { + err = nvs_commit(nvs); + if (err == ESP_OK) { + ESP_LOGI(TAG, "Value with key '%s' erased", key); + } + } + nvs_close(nvs); + } + + return err; +} + +static esp_err_t erase_all(const char *name) +{ + nvs_handle nvs; + + esp_err_t err = nvs_open(current_namespace, NVS_READWRITE, &nvs); + if (err == ESP_OK) { + err = nvs_erase_all(nvs); + if (err == ESP_OK) { + err = nvs_commit(nvs); + } + } + + ESP_LOGI(TAG, "Namespace '%s' was %s erased", name, (err == ESP_OK) ? "" : "not"); + nvs_close(nvs); + return ESP_OK; +} + +static int set_value(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **) &set_args); + if (nerrors != 0) { + arg_print_errors(stderr, set_args.end, argv[0]); + return 1; + } + + const char *key = set_args.key->sval[0]; + const char *type = set_args.type->sval[0]; + const char *values = set_args.value->sval[0]; + + esp_err_t err = set_value_in_nvs(key, type, values); + + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s", esp_err_to_name(err)); + return 1; + } + + return 0; + +} + +static int get_value(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **) &get_args); + if (nerrors != 0) { + arg_print_errors(stderr, get_args.end, argv[0]); + return 1; + } + + const char *key = get_args.key->sval[0]; + const char *type = get_args.type->sval[0]; + + esp_err_t err = get_value_from_nvs(key, type); + + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s", esp_err_to_name(err)); + return 1; + } + + return 0; +} + +static int erase_value(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **) &erase_args); + if (nerrors != 0) { + arg_print_errors(stderr, erase_args.end, argv[0]); + return 1; + } + + const char *key = erase_args.key->sval[0]; + + esp_err_t err = erase(key); + + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s", esp_err_to_name(err)); + return 1; + } + + return 0; +} + +static int erase_namespace(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **) &erase_all_args); + if (nerrors != 0) { + arg_print_errors(stderr, erase_all_args.end, argv[0]); + return 1; + } + + const char *name = erase_all_args.namespace->sval[0]; + + esp_err_t err = erase_all(name); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s", esp_err_to_name(err)); + return 1; + } + + return 0; +} + +static int set_namespace(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **) &namespace_args); + if (nerrors != 0) { + arg_print_errors(stderr, namespace_args.end, argv[0]); + return 1; + } + + const char *namespace = namespace_args.namespace->sval[0]; + strlcpy(current_namespace, namespace, sizeof(current_namespace)); + ESP_LOGI(TAG, "Namespace set to '%s'", current_namespace); + return 0; +} + +void register_nvs() +{ + set_args.key = arg_str1(NULL, NULL, "", "key of the value to be set"); + set_args.type = arg_str1(NULL, NULL, "", ARG_TYPE_STR); + set_args.value = arg_str1("v", "value", "", "value to be stored"); + set_args.end = arg_end(2); + + get_args.key = arg_str1(NULL, NULL, "", "key of the value to be read"); + get_args.type = arg_str1(NULL, NULL, "", ARG_TYPE_STR); + get_args.end = arg_end(2); + + erase_args.key = arg_str1(NULL, NULL, "", "key of the value to be erased"); + erase_args.end = arg_end(2); + + erase_all_args.namespace = arg_str1(NULL, NULL, "", "namespace to be erased"); + erase_all_args.end = arg_end(2); + + namespace_args.namespace = arg_str1(NULL, NULL, "", "namespace of the partition to be selected"); + namespace_args.end = arg_end(2); + + const esp_console_cmd_t set_cmd = { + .command = "nvs_set", + .help = "Set variable in selected namespace. Blob type must be comma separated list of hex values. \n" + "Examples:\n" + " nvs_set VarName i32 -v 123 \n" + " nvs_set VarName srt -v YourString \n" + " nvs_set VarName blob -v 0123456789abcdef \n", + .hint = NULL, + .func = &set_value, + .argtable = &set_args + }; + + const esp_console_cmd_t get_cmd = { + .command = "nvs_get", + .help = "Get variable from selected namespace. \n" + "Example: nvs_get VarName i32", + .hint = NULL, + .func = &get_value, + .argtable = &get_args + }; + + const esp_console_cmd_t erase_cmd = { + .command = "nvs_erase", + .help = "Erase variable from current namespace", + .hint = NULL, + .func = &erase_value, + .argtable = &erase_args + }; + + const esp_console_cmd_t erase_namespace_cmd = { + .command = "nvs_erase_namespace", + .help = "Erases specified namespace", + .hint = NULL, + .func = &erase_namespace, + .argtable = &erase_all_args + }; + + const esp_console_cmd_t namespace_cmd = { + .command = "nvs_namespace", + .help = "Set current namespace", + .hint = NULL, + .func = &set_namespace, + .argtable = &namespace_args + }; + + ESP_ERROR_CHECK(esp_console_cmd_register(&set_cmd)); + ESP_ERROR_CHECK(esp_console_cmd_register(&get_cmd)); + ESP_ERROR_CHECK(esp_console_cmd_register(&erase_cmd)); + ESP_ERROR_CHECK(esp_console_cmd_register(&namespace_cmd)); + ESP_ERROR_CHECK(esp_console_cmd_register(&erase_namespace_cmd)); +} diff --git a/examples/system/console/components/cmd_nvs/cmd_nvs.h b/examples/system/console/components/cmd_nvs/cmd_nvs.h new file mode 100644 index 000000000..6d0de50dc --- /dev/null +++ b/examples/system/console/components/cmd_nvs/cmd_nvs.h @@ -0,0 +1,21 @@ +/* Console example — declarations of command registration functions. + + 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. +*/ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +// Register NVS functions +void register_nvs(); + +#ifdef __cplusplus +} +#endif + diff --git a/examples/system/console/components/cmd_nvs/component.mk b/examples/system/console/components/cmd_nvs/component.mk new file mode 100644 index 000000000..e0e9f4c12 --- /dev/null +++ b/examples/system/console/components/cmd_nvs/component.mk @@ -0,0 +1,10 @@ +# +# Component Makefile +# +# This Makefile should, at the very least, just include $(SDK_PATH)/Makefile. By default, +# this will take the sources in the src/ directory, compile them and link them into +# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, +# please read the SDK documents if you need to do this. +# + +COMPONENT_ADD_INCLUDEDIRS := . diff --git a/examples/system/console/main/cmd_decl.h b/examples/system/console/main/cmd_decl.h index efdda4e25..983b6e974 100644 --- a/examples/system/console/main/cmd_decl.h +++ b/examples/system/console/main/cmd_decl.h @@ -14,6 +14,7 @@ extern "C" { #include "cmd_system.h" #include "cmd_wifi.h" +#include "cmd_nvs.h" #ifdef __cplusplus } diff --git a/examples/system/console/main/console_example_main.c b/examples/system/console/main/console_example_main.c index f56563f6c..23d1cf2c9 100644 --- a/examples/system/console/main/console_example_main.c +++ b/examples/system/console/main/console_example_main.c @@ -130,6 +130,7 @@ void app_main() esp_console_register_help_command(); register_system(); register_wifi(); + register_nvs(); /* Prompt to be printed before each line. * This can be customized, made dynamic, etc.