checking persistence on desktop and esp32

Signed-off-by: Peter Siegmund <developer@mars3142.org>
This commit is contained in:
2025-06-20 23:29:00 +02:00
parent 26723db8d8
commit 72fd0bdf1a
19 changed files with 969 additions and 84 deletions

View File

@@ -16,7 +16,10 @@ set(SOURCE_FILES
if (DEFINED ENV{IDF_PATH})
idf_component_register(SRCS
${SOURCE_FILES}
src/common/PersistenceManager.cpp
INCLUDE_DIRS "include"
REQUIRES
nvs_flash
PRIV_REQUIRES
u8g2
)

View File

@@ -17,6 +17,7 @@
// Project-specific headers
#include "common/Widget.h"
#include "common/IPersistenceManager.h"
#include "u8g2.h"
class MenuItem;
@@ -25,102 +26,49 @@ class MenuItem;
* @struct menu_options_t
* @brief Configuration structure for menu widgets containing display context and callbacks
* @details This structure serves as a configuration container that provides menu widgets
* with access to the display system, screen management functions, and input
* handling callbacks. It acts as the bridge between individual menu widgets
* and the broader application framework.
*
* The structure contains:
* - Display context for rendering operations
* - Screen management callbacks for navigation
* - Input handling callback for button events
*
* All callback functions use std::function for type safety and flexibility,
* allowing both function pointers and lambda expressions to be used.
*
* @note This structure should be initialized by the application framework
* and passed to menu widgets during construction.
* with access to the display system, screen management functions, input
* handling callbacks, and persistent storage.
*
* @see Widget
* @see ButtonType
* @see IPersistenceManager
*/
typedef struct
{
/**
* @brief Pointer to u8g2 display context for graphics output operations
* @details This pointer provides access to the u8g2 graphics library functions
* for rendering text, shapes, and other visual elements. It must be
* initialized and ready for drawing operations before being passed
* to menu widgets.
*
* @note The menu widgets do not take ownership of this pointer and assume
* it remains valid throughout their lifetime. Ensure the u8g2 context
* is properly managed by the application framework.
*
* @warning Must not be nullptr when passed to menu widgets
*/
u8g2_t *u8g2;
/**
* @brief Callback function to set the current active screen
* @param screen Smart pointer to the Widget that should become the active screen
*
* @details This callback replaces the currently active screen with the provided
* widget. It is typically used for direct screen transitions where the
* previous screen should be completely replaced rather than stacked.
*
* @note The callback takes ownership of the provided Widget through the shared_ptr.
* The previous screen will be destroyed unless other references exist.
*
* @see pushScreen for adding screens to a navigation stack
*/
std::function<void(std::shared_ptr<Widget>)> setScreen;
/**
* @brief Callback function to add a new screen to the navigation stack
* @param screen Smart pointer to the Widget that should be pushed onto the screen stack
*
* @details This callback adds a new screen on top of the current screen stack,
* allowing for hierarchical navigation where users can return to
* previous screens. Commonly used for sub-menus, settings screens,
* or modal dialogs.
*
* @note The callback takes ownership of the provided Widget through the shared_ptr.
* The current screen remains in memory and can be returned to via popScreen().
*
* @see popScreen for removing screens from the navigation stack
* @see setScreen for direct screen replacement
*/
std::function<void(std::shared_ptr<Widget>)> pushScreen;
/**
* @brief Callback function to remove the top screen from the navigation stack
* @details This callback removes the currently active screen and returns to the
* previous screen in the navigation stack. It is typically used for
* "back" or "cancel" operations in hierarchical menu systems.
*
* @note If the navigation stack is empty or contains only one screen, the
* behavior is implementation-dependent and should be handled gracefully
* by the application framework.
*
* @see pushScreen for adding screens to the navigation stack
*/
std::function<void()> popScreen;
/**
* @brief Callback function to handle button press events
* @param button The type of button that was pressed
*
* @details This callback is invoked when a button press event occurs that is
* not handled directly by the menu widget. It allows the application
* framework to implement global button handling logic, such as
* system-wide shortcuts or fallback behavior.
*
* @note This callback is typically used for buttons that have application-wide
* meaning (e.g., home button, menu button) rather than widget-specific
* navigation which is handled internally by the widgets.
*
* @see ButtonType for available button types
* @see Widget::onButtonClicked for widget-specific button handling
*/
std::function<void(ButtonType button)> onButtonClicked;
/**
* @brief Shared pointer to platform-independent persistence manager
* @details This provides access to persistent key-value storage across different
* platforms. The actual implementation (SDL3 or ESP32/NVS) is determined
* at compile time based on the target platform.
*
* @note The persistence manager is shared across all menu widgets and maintains
* its state throughout the application lifecycle.
*/
std::shared_ptr<IPersistenceManager> persistenceManager;
} menu_options_t;

View File

@@ -0,0 +1,136 @@
#pragma once
#include <string>
#include <type_traits>
/**
* @interface IPersistenceManager
* @brief Abstract interface for platform-independent persistence management
* @details This interface defines the contract for key-value storage and retrieval
* systems across different platforms (Desktop/SDL3 and ESP32).
*/
class IPersistenceManager
{
public:
virtual ~IPersistenceManager() = default;
/**
* @brief Template methods for type-safe setting and retrieving of values
* @tparam T The type of value to set (must be one of: bool, int, float, double, std::string)
* @param key The key to associate with the value
* @param value The value to store
*/
template<typename T>
void SetValue(const std::string& key, const T& value) {
static_assert(std::is_same_v<T, bool> ||
std::is_same_v<T, int> ||
std::is_same_v<T, float> ||
std::is_same_v<T, double> ||
std::is_same_v<T, std::string>,
"Unsupported type for IPersistenceManager");
SetValueImpl(key, value);
}
/**
* @brief Template method for type-safe retrieval of values
* @tparam T The type of value to retrieve
* @param key The key to look up
* @param defaultValue The default value to return if key is not found
* @return The stored value or default value if key doesn't exist
*/
template<typename T>
T GetValue(const std::string& key, const T& defaultValue = T{}) const {
return GetValueImpl<T>(key, defaultValue);
}
/**
* @brief Convenience methods for setting specific types
*/
void SetBool(const std::string& key, bool value) { SetValue(key, value); }
void SetInt(const std::string& key, int value) { SetValue(key, value); }
void SetFloat(const std::string& key, float value) { SetValue(key, value); }
void SetDouble(const std::string& key, double value) { SetValue(key, value); }
void SetString(const std::string& key, const std::string& value) { SetValue(key, value); }
/**
* @brief Convenience methods for getting specific types with default values
*/
bool GetBool(const std::string& key, bool defaultValue = false) const {
return GetValue(key, defaultValue);
}
int GetInt(const std::string& key, int defaultValue = 0) const {
return GetValue(key, defaultValue);
}
float GetFloat(const std::string& key, float defaultValue = 0.0f) const {
return GetValue(key, defaultValue);
}
double GetDouble(const std::string& key, double defaultValue = 0.0) const {
return GetValue(key, defaultValue);
}
std::string GetString(const std::string& key, const std::string& defaultValue = "") const {
return GetValue(key, defaultValue);
}
/**
* @brief Utility methods for key management
*/
virtual bool HasKey(const std::string& key) const = 0; ///< Check if a key exists
virtual void RemoveKey(const std::string& key) = 0; ///< Remove a key-value pair
virtual void Clear() = 0; ///< Clear all stored data
virtual size_t GetKeyCount() const = 0; ///< Get the number of stored keys
/**
* @brief Persistence operations
*/
virtual bool Save() = 0; ///< Save data to persistent storage
virtual bool Load() = 0; ///< Load data from persistent storage
protected:
/**
* @brief Template-specific implementations that must be overridden by derived classes
* @details These methods handle the actual storage and retrieval of different data types
*/
virtual void SetValueImpl(const std::string& key, bool value) = 0;
virtual void SetValueImpl(const std::string& key, int value) = 0;
virtual void SetValueImpl(const std::string& key, float value) = 0;
virtual void SetValueImpl(const std::string& key, double value) = 0;
virtual void SetValueImpl(const std::string& key, const std::string& value) = 0;
virtual bool GetValueImpl(const std::string& key, bool defaultValue) const = 0;
virtual int GetValueImpl(const std::string& key, int defaultValue) const = 0;
virtual float GetValueImpl(const std::string& key, float defaultValue) const = 0;
virtual double GetValueImpl(const std::string& key, double defaultValue) const = 0;
virtual std::string GetValueImpl(const std::string& key, const std::string& defaultValue) const = 0;
private:
/**
* @brief Template dispatch methods for type-safe value retrieval
* @tparam T The type to retrieve
* @param key The key to look up
* @param defaultValue The default value to return
* @return The retrieved value or default if not found
*/
template<typename T>
T GetValueImpl(const std::string& key, const T& defaultValue) const
{
if constexpr (std::is_same_v<T, bool>) {
return GetValueImpl(key, defaultValue);
} else if constexpr (std::is_same_v<T, int>) {
return GetValueImpl(key, defaultValue);
} else if constexpr (std::is_same_v<T, float>) {
return GetValueImpl(key, defaultValue);
} else if constexpr (std::is_same_v<T, double>) {
return GetValueImpl(key, defaultValue);
} else if constexpr (std::is_same_v<T, std::string>) {
return GetValueImpl(key, defaultValue);
} else {
static_assert(std::is_same_v<T, bool> ||
std::is_same_v<T, int> ||
std::is_same_v<T, float> ||
std::is_same_v<T, double> ||
std::is_same_v<T, std::string>,
"Unsupported type for IPersistenceManager");
return defaultValue; // This line will never be reached, but satisfies compiler
}
}
};

View File

@@ -0,0 +1,61 @@
#pragma once
#include "IPersistenceManager.h"
#include <string>
#include <unordered_map>
#include <nvs.h>
#include <nvs_flash.h>
/**
* @class PersistenceManager
* @brief ESP32-specific implementation using NVS (Non-Volatile Storage)
* @details This implementation uses ESP32's NVS API for persistent storage
* in flash memory, providing a platform-optimized solution for
* embedded systems.
*/
class PersistenceManager : public IPersistenceManager
{
private:
nvs_handle_t nvs_handle_;
std::string namespace_;
bool initialized_;
public:
explicit PersistenceManager(const std::string &nvs_namespace = "config");
~PersistenceManager() override;
// IPersistenceManager implementation
bool HasKey(const std::string &key) const override;
void RemoveKey(const std::string &key) override;
void Clear() override;
size_t GetKeyCount() const override;
bool Save() override;
bool Load() override;
// ESP32-specific methods
bool Initialize();
void Deinitialize();
bool IsInitialized() const
{
return initialized_;
}
protected:
// Template-spezifische Implementierungen
void SetValueImpl(const std::string &key, bool value) override;
void SetValueImpl(const std::string &key, int value) override;
void SetValueImpl(const std::string &key, float value) override;
void SetValueImpl(const std::string &key, double value) override;
void SetValueImpl(const std::string &key, const std::string &value) override;
bool GetValueImpl(const std::string &key, bool defaultValue) const override;
int GetValueImpl(const std::string &key, int defaultValue) const override;
float GetValueImpl(const std::string &key, float defaultValue) const override;
double GetValueImpl(const std::string &key, double defaultValue) const override;
std::string GetValueImpl(const std::string &key, const std::string &defaultValue) const override;
private:
bool EnsureInitialized() const;
};

View File

@@ -29,5 +29,7 @@ private:
*/
void onButtonPressed(const MenuItem& menuItem, ButtonType button) override;
static std::string CreateKey(int index);
menu_options_t *m_options; ///< Pointer to menu configuration options
};

View File

@@ -58,7 +58,15 @@ void Menu::setItemSize(const size_t size)
for (size_t i = m_items.size() - 1; i < size; i++)
{
auto caption = std::string("Bereich ") + std::to_string(i + 1);
addSelection(i + 1, caption, m_items.at(0).getValues(), 0);
auto index = 0;
if (m_options && m_options->persistenceManager)
{
constexpr int key_length = 20;
char key[key_length] = "";
snprintf(key, key_length, "section_%zu", i + 1);
index = m_options->persistenceManager->GetValue(key, index);
}
addSelection(i + 1, caption, m_items.at(0).getValues(), index);
}
}
else

View File

@@ -0,0 +1,295 @@
#ifdef ESP_PLATFORM
#include "common/PersistenceManager.h"
#include <cstring>
#include <esp_log.h>
static const char *TAG = "PersistenceManager";
PersistenceManager::PersistenceManager(const std::string &nvs_namespace)
: namespace_(nvs_namespace), initialized_(false)
{
Initialize();
}
PersistenceManager::~PersistenceManager()
{
Deinitialize();
}
bool PersistenceManager::Initialize()
{
if (initialized_)
{
return true;
}
// 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();
}
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to initialize NVS flash: %s", esp_err_to_name(err));
return false;
}
// Open NVS handle
err = nvs_open(namespace_.c_str(), NVS_READWRITE, &nvs_handle_);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to open NVS handle: %s", esp_err_to_name(err));
return false;
}
initialized_ = true;
ESP_LOGI(TAG, "PersistenceManager initialized with namespace: %s", namespace_.c_str());
return true;
}
void PersistenceManager::Deinitialize()
{
if (initialized_)
{
nvs_close(nvs_handle_);
initialized_ = false;
}
}
bool PersistenceManager::EnsureInitialized() const
{
if (!initialized_)
{
ESP_LOGE(TAG, "PersistenceManager not initialized");
return false;
}
return true;
}
bool PersistenceManager::HasKey(const std::string &key) const
{
if (!EnsureInitialized())
return false;
size_t required_size = 0;
esp_err_t err = nvs_get_blob(nvs_handle_, key.c_str(), nullptr, &required_size);
return err == ESP_OK;
}
void PersistenceManager::RemoveKey(const std::string &key)
{
if (!EnsureInitialized())
return;
esp_err_t err = nvs_erase_key(nvs_handle_, key.c_str());
if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND)
{
ESP_LOGE(TAG, "Failed to remove key '%s': %s", key.c_str(), esp_err_to_name(err));
}
}
void PersistenceManager::Clear()
{
if (!EnsureInitialized())
return;
esp_err_t err = nvs_erase_all(nvs_handle_);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to clear all keys: %s", esp_err_to_name(err));
}
}
size_t PersistenceManager::GetKeyCount() const
{
if (!EnsureInitialized())
return 0;
nvs_iterator_t it = nullptr;
esp_err_t err = nvs_entry_find(NVS_DEFAULT_PART_NAME, namespace_.c_str(), NVS_TYPE_ANY, &it);
if (err != ESP_OK || it == nullptr)
{
return 0;
}
size_t count = 0;
while (it != nullptr)
{
count++;
err = nvs_entry_next(&it);
if (err != ESP_OK)
{
break;
}
}
nvs_release_iterator(it);
return count;
}
bool PersistenceManager::Save()
{
if (!EnsureInitialized())
return false;
esp_err_t err = nvs_commit(nvs_handle_);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to commit NVS: %s", esp_err_to_name(err));
return false;
}
return true;
}
bool PersistenceManager::Load()
{
return EnsureInitialized();
}
void PersistenceManager::SetValueImpl(const std::string &key, bool value)
{
if (!EnsureInitialized())
return;
uint8_t val = value ? 1 : 0;
esp_err_t err = nvs_set_u8(nvs_handle_, key.c_str(), val);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to set bool key '%s': %s", key.c_str(), esp_err_to_name(err));
}
}
void PersistenceManager::SetValueImpl(const std::string &key, int value)
{
if (!EnsureInitialized())
return;
esp_err_t err = nvs_set_i32(nvs_handle_, key.c_str(), value);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to set int key '%s': %s", key.c_str(), esp_err_to_name(err));
}
}
void PersistenceManager::SetValueImpl(const std::string &key, float value)
{
if (!EnsureInitialized())
return;
esp_err_t err = nvs_set_blob(nvs_handle_, key.c_str(), &value, sizeof(float));
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to set float key '%s': %s", key.c_str(), esp_err_to_name(err));
}
}
void PersistenceManager::SetValueImpl(const std::string &key, double value)
{
if (!EnsureInitialized())
return;
esp_err_t err = nvs_set_blob(nvs_handle_, key.c_str(), &value, sizeof(double));
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to set double key '%s': %s", key.c_str(), esp_err_to_name(err));
}
}
void PersistenceManager::SetValueImpl(const std::string &key, const std::string &value)
{
if (!EnsureInitialized())
return;
esp_err_t err = nvs_set_str(nvs_handle_, key.c_str(), value.c_str());
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to set string key '%s': %s", key.c_str(), esp_err_to_name(err));
}
}
bool PersistenceManager::GetValueImpl(const std::string &key, bool defaultValue) const
{
if (!EnsureInitialized())
return defaultValue;
uint8_t value;
esp_err_t err = nvs_get_u8(nvs_handle_, key.c_str(), &value);
if (err != ESP_OK)
{
return defaultValue;
}
return value != 0;
}
int PersistenceManager::GetValueImpl(const std::string &key, int defaultValue) const
{
if (!EnsureInitialized())
return defaultValue;
int32_t value;
esp_err_t err = nvs_get_i32(nvs_handle_, key.c_str(), &value);
if (err != ESP_OK)
{
return defaultValue;
}
return static_cast<int>(value);
}
float PersistenceManager::GetValueImpl(const std::string &key, float defaultValue) const
{
if (!EnsureInitialized())
return defaultValue;
float value;
size_t required_size = sizeof(float);
esp_err_t err = nvs_get_blob(nvs_handle_, key.c_str(), &value, &required_size);
if (err != ESP_OK || required_size != sizeof(float))
{
return defaultValue;
}
return value;
}
double PersistenceManager::GetValueImpl(const std::string &key, double defaultValue) const
{
if (!EnsureInitialized())
return defaultValue;
double value;
size_t required_size = sizeof(double);
esp_err_t err = nvs_get_blob(nvs_handle_, key.c_str(), &value, &required_size);
if (err != ESP_OK || required_size != sizeof(double))
{
return defaultValue;
}
return value;
}
std::string PersistenceManager::GetValueImpl(const std::string &key, const std::string &defaultValue) const
{
if (!EnsureInitialized())
return defaultValue;
size_t required_size = 0;
esp_err_t err = nvs_get_str(nvs_handle_, key.c_str(), nullptr, &required_size);
if (err != ESP_OK)
{
return defaultValue;
}
std::string value(required_size - 1, '\0'); // -1 for null terminator
err = nvs_get_str(nvs_handle_, key.c_str(), value.data(), &required_size);
if (err != ESP_OK)
{
return defaultValue;
}
return value;
}
#endif // ESP32

View File

@@ -8,21 +8,37 @@
*/
namespace LightMenuItem
{
constexpr uint8_t ACTIVATE = 0; ///< ID for the light activation toggle
constexpr uint8_t MODE = 1; ///< ID for the light mode selection
constexpr uint8_t LED_SETTINGS = 2; ///< ID for the LED settings menu item
constexpr uint8_t ACTIVATE = 0; ///< ID for the light activation toggle
constexpr uint8_t MODE = 1; ///< ID for the light mode selection
constexpr uint8_t LED_SETTINGS = 2; ///< ID for the LED settings menu item
} // namespace LightMenuItem
namespace LightMenuOptions
{
constexpr std::string LIGHT_ACTIVE = "light_active";
constexpr std::string LIGHT_MODE = "light_mode";
}
LightMenu::LightMenu(menu_options_t *options) : Menu(options), m_options(options)
{
// Add toggle for enabling/disabling the light system
addToggle(LightMenuItem::ACTIVATE, "Einschalten", true);
bool active = false;
if (m_options && m_options->persistenceManager)
{
active = m_options->persistenceManager->GetValue(LightMenuOptions::LIGHT_ACTIVE, active);
}
addToggle(LightMenuItem::ACTIVATE, "Einschalten", active);
// Create mode selection options (Day/Night modes)
std::vector<std::string> values;
values.emplace_back("Tag"); // Day mode
values.emplace_back("Nacht"); // Night mode
addSelection(LightMenuItem::MODE, "Modus", values, 0);
values.emplace_back("Tag"); // Day mode
values.emplace_back("Nacht"); // Night mode
int mode_value = 0;
if (m_options && m_options->persistenceManager)
{
mode_value = m_options->persistenceManager->GetValue(LightMenuOptions::LIGHT_MODE, mode_value);
}
addSelection(LightMenuItem::MODE, "Modus", values, mode_value);
// Add menu item for accessing LED settings submenu
addText(LightMenuItem::LED_SETTINGS, "Einstellungen");
@@ -31,7 +47,7 @@ LightMenu::LightMenu(menu_options_t *options) : Menu(options), m_options(options
void LightMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType button)
{
std::shared_ptr<Widget> widget;
// Handle different menu items based on their ID
switch (menuItem.getId())
{
@@ -40,13 +56,28 @@ void LightMenu::onButtonPressed(const MenuItem &menuItem, const ButtonType butto
if (button == ButtonType::SELECT)
{
toggle(menuItem);
if (m_options && m_options->persistenceManager)
{
const auto value = getItem(menuItem.getId()).getValue() == "1";
m_options->persistenceManager->SetValue(LightMenuOptions::LIGHT_ACTIVE, value);
m_options->persistenceManager->Save();
}
}
break;
}
case LightMenuItem::MODE: {
// Switch between day/night modes using left/right buttons
switchValue(menuItem, button);
const auto item = switchValue(menuItem, button);
if (button == ButtonType::LEFT || button == ButtonType::RIGHT)
{
if (m_options && m_options->persistenceManager)
{
const auto value = getItem(item.getId()).getIndex();
m_options->persistenceManager->SetValue(LightMenuOptions::LIGHT_MODE, value);
m_options->persistenceManager->Save();
}
}
break;
}

View File

@@ -9,6 +9,14 @@ namespace LightSettingsMenuItem
constexpr uint8_t SECTION_COUNTER = 0; ///< ID for the section counter menu item
}
std::string LightSettingsMenu::CreateKey(const int index)
{
constexpr int key_length = 20;
char key[key_length] = "";
snprintf(key, key_length, "section_%d", index);
return key;
}
LightSettingsMenu::LightSettingsMenu(menu_options_t *options) : Menu(options), m_options(options)
{
// Create values vector for section counts (1-99)
@@ -19,7 +27,12 @@ LightSettingsMenu::LightSettingsMenu(menu_options_t *options) : Menu(options), m
}
// Add section counter selection (allows choosing number of sections)
addSelection(LightSettingsMenuItem::SECTION_COUNTER, "Sektionen", values, 7);
auto value = 7;
if (m_options && m_options->persistenceManager)
{
value = m_options->persistenceManager->GetValue(CreateKey(0), value);
}
addSelection(LightSettingsMenuItem::SECTION_COUNTER, "Sektionen", values, value);
setItemSize(std::stoull(getItem(0).getValue()));
}
@@ -30,5 +43,15 @@ void LightSettingsMenu::onButtonPressed(const MenuItem &menuItem, const ButtonTy
switchValue(menuItem, button);
// Update the section list size based on the section counter value
setItemSize(std::stoull(getItem(0).getValue()));
if (menuItem.getId() == 0)
{
setItemSize(std::stoull(getItem(0).getValue()));
}
// Persist the changed section values if persistence is available
if (m_options && m_options->persistenceManager)
{
const auto value = getItem(menuItem.getId()).getIndex();
m_options->persistenceManager->SetValue(CreateKey(menuItem.getId()), value);
}
}