diff --git a/components/nvs/example/README.md b/components/nvs/example/README.md index e8955303c..722661be4 100644 --- a/components/nvs/example/README.md +++ b/components/nvs/example/README.md @@ -22,8 +22,59 @@ idf.py -p PORT flash monitor See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. -## Example Output +## NVS Example Output ![CleanShot 2024-05-18 at 12 54 46](https://github.com/esp-cpp/espp/assets/213467/60b2db2f-8796-4ae3-9a8c-51f69fa21911) ![CleanShot 2024-05-18 at 12 54 54](https://github.com/esp-cpp/espp/assets/213467/ddceddbf-0690-4590-93b6-66cf91ad1898) ![CleanShot 2024-05-18 at 12 55 02](https://github.com/esp-cpp/espp/assets/213467/1181fc79-f7bd-4b1d-b351-e5ca24ee7c55) + +## NVSHandle Example Output + +First run: +``` +Opening Non-Volatile Storage (NVS) handle... Done +Reading restart counter from NVS ... [NVSHandle/E][0.061]: The value is not initialized in NVS, key = 'restart_counter' +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. +``` + +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. diff --git a/components/nvs/example/main/nvs_example.cpp b/components/nvs/example/main/nvs_example.cpp index 2bc26ec5c..8fe04095f 100644 --- a/components/nvs/example/main/nvs_example.cpp +++ b/components/nvs/example/main/nvs_example.cpp @@ -5,6 +5,7 @@ #include "format.hpp" #include "nvs.hpp" +#include "nvs_handle_espp.hpp" using namespace std::chrono_literals; @@ -12,80 +13,136 @@ extern "C" void app_main(void) { std::this_thread::sleep_for(200ms); fmt::print("Starting NVS example!\n"); - //! [nvs example] - std::error_code ec; - uint8_t counter = 0; - espp::Nvs nvs; - nvs.init(ec); - ec.clear(); - // note that the namespace and key strings must be <= 15 characters - nvs.get_or_set_var("system", "reset_counter", counter, counter, ec); - ec.clear(); - fmt::print("Reset Counter = {}\n", counter); - - bool flag = false; - nvs.get_or_set_var("other-namespace", "flag", flag, flag, ec); - ec.clear(); - fmt::print("Got / Set Flag = {}\n", flag); - // now toggle the flag - flag = !flag; - nvs.set_var("other-namespace", "flag", flag, ec); - ec.clear(); - fmt::print("Toggled Flag = {}\n", flag); - - // test protection against really long namespace names (length > 15) - std::string long_ns(16, 'a'); - nvs.get_or_set_var(long_ns, "flag", flag, flag, ec); - if (ec) { - fmt::print("Expected error: {}\n", ec.message()); - } else { - fmt::print("Unexpected success\n"); - } - ec.clear(); - - // test getting a non-existent key - nvs.get_var("system", "not-here", counter, ec); - if (ec) { - fmt::print("Expected error: {}\n", ec.message()); - } else { - fmt::print("Unexpected success\n"); - } - ec.clear(); - - // test getting a string value - std::string str; - nvs.get_or_set_var("system", "string", str, std::string("default"), ec); - if (ec) { - fmt::print("Error: {}\n", ec.message()); - } else { - fmt::print("String = {}\n", str); - } - ec.clear(); - - // test setting a string value - str = "hello"; - nvs.set_var("system", "string", str, ec); - if (ec) { - fmt::print("Error: {}\n", ec.message()); - } else { - fmt::print("String set to '{}'\n", str); - } - ec.clear(); + { + //! [nvs example] + std::error_code ec; + uint8_t counter = 0; + espp::Nvs nvs; + nvs.init(ec); + ec.clear(); + // note that the namespace and key strings must be <= 15 characters + nvs.get_or_set_var("system", "reset_counter", counter, counter, ec); + ec.clear(); + fmt::print("Reset Counter = {}\n", counter); + + bool flag = false; + nvs.get_or_set_var("other-namespace", "flag", flag, flag, ec); + ec.clear(); + fmt::print("Got / Set Flag = {}\n", flag); + // now toggle the flag + flag = !flag; + nvs.set_var("other-namespace", "flag", flag, ec); + ec.clear(); + fmt::print("Toggled Flag = {}\n", flag); - counter++; + // test protection against really long namespace names (length > 15) + std::string long_ns(16, 'a'); + nvs.get_or_set_var(long_ns, "flag", flag, flag, ec); + if (ec) { + fmt::print("Expected error: {}\n", ec.message()); + } else { + fmt::print("Unexpected success\n"); + } + ec.clear(); - if (counter > 10) { - nvs.erase(ec); - nvs.init(ec); - counter = 0; - fmt::print("NVS erased, Reset Counter set to 0\n"); + // test getting a non-existent key + nvs.get_var("system", "not-here", counter, ec); + if (ec) { + fmt::print("Expected error: {}\n", ec.message()); + } else { + fmt::print("Unexpected success\n"); + } + ec.clear(); + + // test getting a string value + std::string str; + nvs.get_or_set_var("system", "string", str, std::string("default"), ec); + if (ec) { + fmt::print("Error: {}\n", ec.message()); + } else { + fmt::print("String = {}\n", str); + } + ec.clear(); + + // test setting a string value + str = "hello"; + nvs.set_var("system", "string", str, ec); + if (ec) { + fmt::print("Error: {}\n", ec.message()); + } else { + fmt::print("String set to '{}'\n", str); + } ec.clear(); + + counter++; + + if (counter > 10) { + nvs.erase(ec); + nvs.init(ec); + counter = 0; + fmt::print("NVS erased, Reset Counter set to 0\n"); + ec.clear(); + } + + nvs.set_var("system", "reset_counter", counter, ec); + fmt::print("Next Reset Counter will be = {}\n", counter); + fmt::print("NVS example complete!\n"); + //! [nvs example] } - nvs.set_var("system", "reset_counter", counter, ec); - fmt::print("Next Reset Counter will be = {}\n", counter); - fmt::print("NVS example complete!\n"); - //! [nvs example] + { + //! [nvshandle example] + // Init NVS + std::error_code ec; + espp::Nvs nvs; + nvs.init(ec); + ec.clear(); + + // Open + fmt::print("\nOpening Non-Volatile Storage (NVS) handle... "); + // Handle will automatically close when going out of scope or when it's reset. + espp::NVSHandle storage("storage", ec); + fmt::print("Done\n"); + ec.clear(); + + // Read + fmt::print("Reading restart counter from NVS ... "); + int32_t restart_counter = 0; + storage.get("restart_counter", restart_counter, ec); + if (ec) { + if (ec.value() == static_cast(NvsErrc::Key_Not_Found)) { + fmt::print("The value is not initialized yet!\n"); + } else if (ec.value() == static_cast(NvsErrc::Read_NVS_Failed)) { + fmt::print("Failed to read from NVS: {}\n", ec.message().c_str()); + } else { + fmt::print("An error occurred: {}\n", ec.message().c_str()); + } + } else { + fmt::print("Done\n"); + fmt::print("Restart counter = {}\n", restart_counter); + } + ec.clear(); + + // Write + fmt::print("Updating restart counter in NVS ... "); + restart_counter++; + storage.set("restart_counter", restart_counter, ec); + fmt::print("Done\n"); + ec.clear(); + + // 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. + fmt::print("Committing updates in NVS ... "); + storage.commit(ec); + fmt::print("Done\n"); + ec.clear(); + + fmt::print("\n"); + fflush(stdout); + //! [nvshandle example] + } while (true) { std::this_thread::sleep_for(1s); diff --git a/components/nvs/include/nvs.hpp b/components/nvs/include/nvs.hpp index 866af1ac5..2a366336c 100644 --- a/components/nvs/include/nvs.hpp +++ b/components/nvs/include/nvs.hpp @@ -23,6 +23,7 @@ enum class NvsErrc { Key_Not_Found, Init_NVS_Failed, Erase_NVS_Failed, + Handle_Uninitialized, }; struct NvsErrCategory : std::error_category { @@ -61,6 +62,9 @@ std::string NvsErrCategory::message(int ev) const { case NvsErrc::Erase_NVS_Failed: return "Failed to erase NVS"; + case NvsErrc::Handle_Uninitialized: + return "Handle not initialized"; + default: return "(unrecognized error)"; } diff --git a/components/nvs/include/nvs_handle_espp.hpp b/components/nvs/include/nvs_handle_espp.hpp new file mode 100644 index 000000000..1e2014347 --- /dev/null +++ b/components/nvs/include/nvs_handle_espp.hpp @@ -0,0 +1,254 @@ +#pragma once + +#include + +#include "nvs.h" +#include "nvs.hpp" +#include "nvs_flash.h" +#include "nvs_handle.hpp" + +#include "base_component.hpp" + +namespace espp { +/** + * @brief Class to manage NVS handles. + * @details This class provides an interface for managing specific ESP NVS namespaces, + * enabling operations like reading, writing, and committing key-value pairs. It + * encapsulates all direct interactions with the NVS to ensure proper error handling + * and namespace management. + * + * @section nvshandle_ex1 NVSHandle Example + * @snippet nvs_example.cpp nvshandle example + */ +class NVSHandle : public BaseComponent { +public: + /// @brief Construct a new NVSHandle object + /// @param[in] ns_name Namespace for NVS + /// @param[out] ec Saves a std::error_code representing success or failure + /// @details Create an NVSHandle object for the key-value pairs in the ns_name namespace + explicit NVSHandle(const char *ns_name, std::error_code &ec) + : BaseComponent("NVSHandle", espp::Logger::Verbosity::WARN) { + if (strlen(ns_name) > 15) { + logger_.error("Namespace too long, must be <= 15 characters: {}", ns_name); + ec = make_error_code(NvsErrc::Namespace_Length_Too_Long); + return; + } + + esp_err_t err; + handle = nvs::open_nvs_handle(ns_name, NVS_READWRITE, &err); + if (err != ESP_OK) { + logger_.error("Error {} opening NVS handle for namespace '{}'!", esp_err_to_name(err), + ns_name); + ec = make_error_code(NvsErrc::Open_NVS_Handle_Failed); + } + } + + /// @brief Reads a variable from the NVS + /// @param[in] key NVS Key of the variable to read + /// @param[in] value Variable to read + /// @param[out] ec Saves a std::error_code representing success or failure + /// @details Reads the value of key into value, if key exists + template void get(const char *key, T &value, std::error_code &ec) { + check_key_length(key, ec); + if (ec) + return; + + if (!check_handle_initialized(ec)) + return; + + esp_err_t err; + T readvalue; + err = handle->get_item(key, readvalue); + switch (err) { + case ESP_OK: + value = readvalue; + break; + case ESP_ERR_NVS_NOT_FOUND: + ec = make_error_code(NvsErrc::Key_Not_Found); + logger_.error("The value is not initialized in NVS, key = '{}'", key); + break; + default: + logger_.error("Error {} reading!", esp_err_to_name(err)); + ec = make_error_code(NvsErrc::Read_NVS_Failed); + } + return; + } + + /// @brief Reads a variable from the NVS + /// @param[in] key NVS Key of the variable to read + /// @param[in] value Variable to read + /// @param[out] ec Saves a std::error_code representing success or failure + template void get(std::string_view key, T &value, std::error_code &ec) { + get(key.data(), value, ec); + } + + /// @brief Reads a bool from the NVS + /// @param[in] key NVS Key of the bool to read + /// @param[in] value bool to read + /// @param[out] ec Saves a std::error_code representing success or failure + /// @details Read the key/variable pair + void get(const char *key, bool &value, std::error_code &ec) { + uint8_t u8 = static_cast(value); + get(key, u8, ec); + if (!ec) + value = static_cast(u8); + } + + /// @brief Reads a bool from the NVS + /// @param[in] key NVS Key of the bool to read + /// @param[in] value bool to read + /// @param[out] ec Saves a std::error_code representing success or failure + void get(std::string_view key, bool &value, std::error_code &ec) { get(key.data(), value, ec); } + + /// @brief Reads a string from the NVS + /// @param[in] key NVS Key of the string to read + /// @param[in] value string to read + /// @param[out] ec Saves a std::error_code representing success or failure + void get(std::string_view key, std::string &value, std::error_code &ec) { + get(key.data(), value, ec); + } + + /// @brief Reads a string from the NVS + /// @param[in] key NVS Key of the string to read + /// @param[in] value string to read + /// @param[out] ec Saves a std::error_code representing success or failure + void get(const char *key, std::string &value, std::error_code &ec) { + check_key_length(key, ec); + if (ec) + return; + + if (!check_handle_initialized(ec)) + return; + + esp_err_t err; + std::size_t len = 0; + err = handle->get_item_size(nvs::ItemType::SZ, key, len); + if (err != ESP_OK) { + logger_.error("Error {} reading!", esp_err_to_name(err)); + ec = make_error_code(NvsErrc::Read_NVS_Failed); + return; + } + value.resize(len); + err = handle->get_string(key, value.data(), len); + if (err != ESP_OK) { + ec = make_error_code(NvsErrc::Read_NVS_Failed); + logger_.error("Error {} reading from NVS!", esp_err_to_name(err)); + return; + } + return; + } + + /// @brief Save a variable in the NVS + /// @param[in] key NVS Key of the variable to read + /// @param[in] value Variable to read + /// @param[out] ec Saves a std::error_code representing success or failure + /// @details Saves the key/variable pair without committing the NVS. + template void set(const char *key, T value, std::error_code &ec) { + check_key_length(key, ec); + if (ec) + return; + + if (!check_handle_initialized(ec)) + return; + + esp_err_t err; + err = handle->set_item(key, value); + if (err != ESP_OK) { + ec = make_error_code(NvsErrc::Write_NVS_Failed); + logger_.error("Error {} writing to NVS!", esp_err_to_name(err)); + } + return; + } + + /// @brief Save a variable in the NVS + /// @param[in] key NVS Key of the variable to save + /// @param[in] value Variable to save + /// @param[out] ec Saves a std::error_code representing success or failure + template void set(std::string_view key, T value, std::error_code &ec) { + set(key.data(), value, ec); + } + + /// @brief Set a bool in the NVS + /// @param[in] key NVS Key of the bool to set + /// @param[in] value bool to set + /// @param[out] ec Saves a std::error_code representing success or failure + void set(std::string_view key, bool value, std::error_code &ec) { set(key.data(), value, ec); } + + /// @brief Set a bool in the NVS + /// @param[in] key NVS Key of the bool to set + /// @param[in] value bool to set + /// @param[out] ec Saves a std::error_code representing success or failure + void set(const char *key, bool value, std::error_code &ec) { + uint8_t u8 = static_cast(value); + set(key, u8, ec); + } + + /// @brief Set a string in the NVS + /// @param[in] key NVS Key of the string to set + /// @param[in] value string to set + /// @param[out] ec Saves a std::error_code representing success or failure + void set(std::string_view key, const std::string &value, std::error_code &ec) { + set(key.data(), value, ec); + } + + /// @brief Set a string in the NVS + /// @param[in] key NVS Key of the string to set + /// @param[in] value string to set + /// @param[out] ec Saves a std::error_code representing success or failure + void set(const char *key, const std::string &value, std::error_code &ec) { + check_key_length(key, ec); + if (ec) + return; + + if (!check_handle_initialized(ec)) + return; + + esp_err_t err; + err = handle->set_string(key, value.data()); + if (err != ESP_OK) { + ec = make_error_code(NvsErrc::Write_NVS_Failed); + logger_.error("Error {} writing to NVS!", esp_err_to_name(err)); + return; + } + return; + } + + /// @brief Commit changes + /// @param[out] ec Saves a std::error_code representing success or failure + /// @details Commits changes to the NVS + void commit(std::error_code &ec) { + esp_err_t err = handle->commit(); + if (err != ESP_OK) { + logger_.error("Error {} committing to NVS!", esp_err_to_name(err)); + ec = make_error_code(NvsErrc::Commit_NVS_Failed); + } + return; + } + +protected: + std::unique_ptr handle; + + void check_key_length(const char *key, std::error_code &ec) { + if (strlen(key) > 15) { + logger_.error("Key too long, must be <= 15 characters: {}", key); + ec = make_error_code(NvsErrc::Key_Length_Too_Long); + return; + } + } + + bool check_handle_initialized(std::error_code &ec) { + if (!handle) { + ec = make_error_code(NvsErrc::Handle_Uninitialized); + logger_.error("NVS Handle not initialized!"); + return false; + } + return true; + } + + /** + * @brief overload of std::make_error_code used by custom error codes. + */ + std::error_code make_error_code(NvsErrc e) { return {static_cast(e), theNvsErrCategory}; } + +}; // Class NVSHandle +} // namespace espp diff --git a/doc/Doxyfile b/doc/Doxyfile index 7eee02001..653fc6efc 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -160,6 +160,7 @@ INPUT += $(PROJECT_PATH)/components/math/include/vector2d.hpp INPUT += $(PROJECT_PATH)/components/max1704x/include/max1704x.hpp INPUT += $(PROJECT_PATH)/components/ndef/include/ndef.hpp INPUT += $(PROJECT_PATH)/components/nvs/include/nvs.hpp +INPUT += $(PROJECT_PATH)/components/nvs/include/nvs_handle_espp.hpp INPUT += $(PROJECT_PATH)/components/mcp23x17/include/mcp23x17.hpp INPUT += $(PROJECT_PATH)/components/mt6701/include/mt6701.hpp INPUT += $(PROJECT_PATH)/components/pid/include/pid.hpp diff --git a/doc/en/nvs.rst b/doc/en/nvs.rst index ce0867a0a..9c74be875 100644 --- a/doc/en/nvs.rst +++ b/doc/en/nvs.rst @@ -6,7 +6,14 @@ NVS The `NVS` component provides a simple class representing an NVS controller. -Code examples for the task API are provided in the `nvs` example folder. +NVSHandle +--------- + +The `NVSHandle` class manages individual NVS storage handles, allowing for scoped +control over specific NVS namespaces. It simplifies operations like reading, +writing, and committing key-value pairs within these namespaces. + +Code examples for the NVS and NVSHandle API are provided in the `nvs` example folder. .. ---------------------------- API Reference ---------------------------------- @@ -14,3 +21,4 @@ API Reference ------------- .. include-build-file:: inc/nvs.inc +.. include-build-file:: inc/nvs_handle_espp.inc \ No newline at end of file