diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ab38fe..e3fbd3c 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,9 +58,10 @@ else () WIN32 MACOSX_BUNDLE ${CMAKE_SOURCE_DIR}/main.cpp ${CMAKE_SOURCE_DIR}/Common.cpp - ${CMAKE_SOURCE_DIR}/ResourceManager.cpp ${CMAKE_SOURCE_DIR}/debug/DebugOverlay.cpp ${CMAKE_SOURCE_DIR}/hal/u8x8_hal_sdl.cpp + ${CMAKE_SOURCE_DIR}/manager/PersistenceManager.cpp + ${CMAKE_SOURCE_DIR}/manager/ResourceManager.cpp ${CMAKE_SOURCE_DIR}/model/AppContext.cpp ${CMAKE_SOURCE_DIR}/model/Window.cpp ${CMAKE_SOURCE_DIR}/ui/Device.cpp diff --git a/components/insa/CMakeLists.txt b/components/insa/CMakeLists.txt index e5a8995..69a145f 100644 --- a/components/insa/CMakeLists.txt +++ b/components/insa/CMakeLists.txt @@ -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 ) diff --git a/components/insa/include/MenuOptions.h b/components/insa/include/MenuOptions.h index 49f2836..92ed697 100644 --- a/components/insa/include/MenuOptions.h +++ b/components/insa/include/MenuOptions.h @@ -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)> 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)> 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 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 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 persistenceManager; + } menu_options_t; \ No newline at end of file diff --git a/components/insa/include/common/IPersistenceManager.h b/components/insa/include/common/IPersistenceManager.h new file mode 100644 index 0000000..c49b688 --- /dev/null +++ b/components/insa/include/common/IPersistenceManager.h @@ -0,0 +1,136 @@ +#pragma once + +#include +#include + +/** + * @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 + void SetValue(const std::string& key, const T& value) { + static_assert(std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v, + "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 + T GetValue(const std::string& key, const T& defaultValue = T{}) const { + return GetValueImpl(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 + T GetValueImpl(const std::string& key, const T& defaultValue) const + { + if constexpr (std::is_same_v) { + return GetValueImpl(key, defaultValue); + } else if constexpr (std::is_same_v) { + return GetValueImpl(key, defaultValue); + } else if constexpr (std::is_same_v) { + return GetValueImpl(key, defaultValue); + } else if constexpr (std::is_same_v) { + return GetValueImpl(key, defaultValue); + } else if constexpr (std::is_same_v) { + return GetValueImpl(key, defaultValue); + } else { + static_assert(std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v, + "Unsupported type for IPersistenceManager"); + return defaultValue; // This line will never be reached, but satisfies compiler + } + } +}; \ No newline at end of file diff --git a/components/insa/include/common/PersistenceManager.h b/components/insa/include/common/PersistenceManager.h new file mode 100644 index 0000000..c846264 --- /dev/null +++ b/components/insa/include/common/PersistenceManager.h @@ -0,0 +1,61 @@ +#pragma once + +#include "IPersistenceManager.h" +#include +#include + +#include +#include + +/** + * @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; +}; \ No newline at end of file diff --git a/components/insa/include/ui/LightSettingsMenu.h b/components/insa/include/ui/LightSettingsMenu.h index 6ce5da9..82abe5f 100644 --- a/components/insa/include/ui/LightSettingsMenu.h +++ b/components/insa/include/ui/LightSettingsMenu.h @@ -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 }; \ No newline at end of file diff --git a/components/insa/src/common/Menu.cpp b/components/insa/src/common/Menu.cpp index 2ab63cd..f6d2bfb 100644 --- a/components/insa/src/common/Menu.cpp +++ b/components/insa/src/common/Menu.cpp @@ -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 diff --git a/components/insa/src/common/PersistenceManager.cpp b/components/insa/src/common/PersistenceManager.cpp new file mode 100644 index 0000000..7e0d299 --- /dev/null +++ b/components/insa/src/common/PersistenceManager.cpp @@ -0,0 +1,295 @@ +#ifdef ESP_PLATFORM +#include "common/PersistenceManager.h" +#include +#include + +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(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 \ No newline at end of file diff --git a/components/insa/src/ui/LightMenu.cpp b/components/insa/src/ui/LightMenu.cpp index 0d87848..db271c2 100644 --- a/components/insa/src/ui/LightMenu.cpp +++ b/components/insa/src/ui/LightMenu.cpp @@ -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 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; - + // 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; } diff --git a/components/insa/src/ui/LightSettingsMenu.cpp b/components/insa/src/ui/LightSettingsMenu.cpp index 663a183..029d299 100644 --- a/components/insa/src/ui/LightSettingsMenu.cpp +++ b/components/insa/src/ui/LightSettingsMenu.cpp @@ -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); + } } \ No newline at end of file diff --git a/config.dat b/config.dat new file mode 100644 index 0000000..da898eb Binary files /dev/null and b/config.dat differ diff --git a/main/app_task.cpp b/main/app_task.cpp index 9730708..0805257 100644 --- a/main/app_task.cpp +++ b/main/app_task.cpp @@ -3,11 +3,11 @@ #include "esp_log.h" #include "esp_timer.h" #include "hal/u8g2_esp32_hal.h" -#include "string" #include "u8g2.h" #include "button_handling.h" #include "common/InactivityTracker.h" +#include "common/PersistenceManager.h" #include "ui/ScreenSaver.h" #include "ui/SplashScreen.h" @@ -99,7 +99,9 @@ static void init_ui(void) .pushScreen = [](const std::shared_ptr &screen) { pushScreen(screen); }, .popScreen = []() { popScreen(); }, .onButtonClicked = nullptr, + .persistenceManager = std::make_shared(), }; + options.persistenceManager->Load(); m_widget = std::make_shared(&options); m_inactivityTracker = std::make_unique(60000, []() { auto screensaver = std::make_shared(&options); diff --git a/src/manager/PersistenceManager.cpp b/src/manager/PersistenceManager.cpp new file mode 100644 index 0000000..de1ff22 --- /dev/null +++ b/src/manager/PersistenceManager.cpp @@ -0,0 +1,304 @@ +#include "PersistenceManager.h" +#include + +#include + +#ifndef ESP_PLATFORM +PersistenceManager::PersistenceManager(std::string filename) + : filename_(std::move(filename)) { + // Initialize SDL3 if not already done + if (!SDL_WasInit(SDL_INIT_EVENTS)) { + SDL_Init(SDL_INIT_EVENTS); + } +} + +PersistenceManager::~PersistenceManager() { + Save(); // Automatically save on destruction +} + +bool PersistenceManager::HasKey(const std::string& key) const { + return data_.contains(key); +} + +void PersistenceManager::RemoveKey(const std::string& key) { + data_.erase(key); +} + +void PersistenceManager::Clear() { + data_.clear(); +} + +bool PersistenceManager::Save() { + return SaveToFile(filename_); +} + +bool PersistenceManager::Load() { + return LoadFromFile(filename_); +} + +bool PersistenceManager::SaveToFile(const std::string& filename) { + SDL_IOStream* stream = SDL_IOFromFile(filename.c_str(), "wb"); + if (!stream) { + SDL_Log("Error opening file for writing: %s", SDL_GetError()); + return false; + } + + // Write number of entries + const size_t count = data_.size(); + if (SDL_WriteIO(stream, &count, sizeof(count)) != sizeof(count)) { + SDL_Log("Error writing count: %s", SDL_GetError()); + SDL_CloseIO(stream); + return false; + } + + // Write each entry + for (const auto& [key, value] : data_) { + // Write key length + size_t keyLength = key.length(); + if (SDL_WriteIO(stream, &keyLength, sizeof(keyLength)) != sizeof(keyLength)) { + SDL_Log("Error writing key length: %s", SDL_GetError()); + SDL_CloseIO(stream); + return false; + } + + // Write key + if (SDL_WriteIO(stream, key.c_str(), keyLength) != keyLength) { + SDL_Log("Error writing key: %s", SDL_GetError()); + SDL_CloseIO(stream); + return false; + } + + // Write value + if (!WriteValueToStream(stream, value)) { + SDL_CloseIO(stream); + return false; + } + } + + SDL_CloseIO(stream); + return true; +} + +bool PersistenceManager::LoadFromFile(const std::string& filename) { + SDL_IOStream* stream = SDL_IOFromFile(filename.c_str(), "rb"); + if (!stream) { + SDL_Log("File not found or error opening: %s", SDL_GetError()); + return false; + } + + data_.clear(); + + // Read number of entries + size_t count; + if (SDL_ReadIO(stream, &count, sizeof(count)) != sizeof(count)) { + SDL_Log("Error reading count: %s", SDL_GetError()); + SDL_CloseIO(stream); + return false; + } + + // Read each entry + for (size_t i = 0; i < count; ++i) { + // Read key length + size_t keyLength; + if (SDL_ReadIO(stream, &keyLength, sizeof(keyLength)) != sizeof(keyLength)) { + SDL_Log("Error reading key length: %s", SDL_GetError()); + SDL_CloseIO(stream); + return false; + } + + // Read key + std::string key(keyLength, '\0'); + if (SDL_ReadIO(stream, key.data(), keyLength) != keyLength) { + SDL_Log("Error reading key: %s", SDL_GetError()); + SDL_CloseIO(stream); + return false; + } + + // Read value + ValueType value; + if (!ReadValueFromStream(stream, value)) { + SDL_CloseIO(stream); + return false; + } + + data_[key] = value; + } + + SDL_CloseIO(stream); + return true; +} + +// Template-specific implementations +void PersistenceManager::SetValueImpl(const std::string& key, bool value) { + data_[key] = value; +} + +void PersistenceManager::SetValueImpl(const std::string& key, int value) { + data_[key] = value; +} + +void PersistenceManager::SetValueImpl(const std::string& key, float value) { + data_[key] = value; +} + +void PersistenceManager::SetValueImpl(const std::string& key, double value) { + data_[key] = value; +} + +void PersistenceManager::SetValueImpl(const std::string& key, const std::string& value) { + data_[key] = value; +} + +bool PersistenceManager::GetValueImpl(const std::string& key, bool defaultValue) const { + if (const auto it = data_.find(key); it != data_.end() && std::holds_alternative(it->second)) { + return std::get(it->second); + } + return defaultValue; +} + +int PersistenceManager::GetValueImpl(const std::string& key, int defaultValue) const { + if (const auto it = data_.find(key); it != data_.end() && std::holds_alternative(it->second)) { + return std::get(it->second); + } + return defaultValue; +} + +float PersistenceManager::GetValueImpl(const std::string& key, float defaultValue) const { + if (const auto it = data_.find(key); it != data_.end() && std::holds_alternative(it->second)) { + return std::get(it->second); + } + return defaultValue; +} + +double PersistenceManager::GetValueImpl(const std::string& key, double defaultValue) const { + if (const auto it = data_.find(key); it != data_.end() && std::holds_alternative(it->second)) { + return std::get(it->second); + } + return defaultValue; +} + +std::string PersistenceManager::GetValueImpl(const std::string& key, const std::string& defaultValue) const { + if (const auto it = data_.find(key); it != data_.end() && std::holds_alternative(it->second)) { + return std::get(it->second); + } + return defaultValue; +} + +// Private helper methods +bool PersistenceManager::WriteValueToStream(SDL_IOStream* stream, const ValueType& value) { + const TypeId typeId = GetTypeId(value); + + // Write type ID + if (SDL_WriteIO(stream, &typeId, sizeof(typeId)) != sizeof(typeId)) { + SDL_Log("Error writing type ID: %s", SDL_GetError()); + return false; + } + + // Write value based on type + switch (typeId) { + case TypeId::BOOL: { + const bool val = std::get(value); + return SDL_WriteIO(stream, &val, sizeof(val)) == sizeof(val); + } + case TypeId::INT: { + const int val = std::get(value); + return SDL_WriteIO(stream, &val, sizeof(val)) == sizeof(val); + } + case TypeId::FLOAT: { + const float val = std::get(value); + return SDL_WriteIO(stream, &val, sizeof(val)) == sizeof(val); + } + case TypeId::DOUBLE: { + const double val = std::get(value); + return SDL_WriteIO(stream, &val, sizeof(val)) == sizeof(val); + } + case TypeId::STRING: { + const auto& str = std::get(value); + const size_t length = str.length(); + + // Write string length + if (SDL_WriteIO(stream, &length, sizeof(length)) != sizeof(length)) { + return false; + } + + // Write string data + return SDL_WriteIO(stream, str.c_str(), length) == length; + } + } + return false; +} + +bool PersistenceManager::ReadValueFromStream(SDL_IOStream* stream, ValueType& value) { + TypeId typeId; + + // Read type ID + if (SDL_ReadIO(stream, &typeId, sizeof(typeId)) != sizeof(typeId)) { + SDL_Log("Error reading type ID: %s", SDL_GetError()); + return false; + } + + // Read value based on type + switch (typeId) { + case TypeId::BOOL: { + bool val; + if (SDL_ReadIO(stream, &val, sizeof(val)) == sizeof(val)) { + value = val; + return true; + } + break; + } + case TypeId::INT: { + int val; + if (SDL_ReadIO(stream, &val, sizeof(val)) == sizeof(val)) { + value = val; + return true; + } + break; + } + case TypeId::FLOAT: { + float val; + if (SDL_ReadIO(stream, &val, sizeof(val)) == sizeof(val)) { + value = val; + return true; + } + break; + } + case TypeId::DOUBLE: { + double val; + if (SDL_ReadIO(stream, &val, sizeof(val)) == sizeof(val)) { + value = val; + return true; + } + break; + } + case TypeId::STRING: { + size_t length; + if (SDL_ReadIO(stream, &length, sizeof(length)) != sizeof(length)) { + return false; + } + + std::string str(length, '\0'); + if (SDL_ReadIO(stream, str.data(), length) == length) { + value = str; + return true; + } + break; + } + } + + SDL_Log("Error reading value: %s", SDL_GetError()); + return false; +} + +PersistenceManager::TypeId PersistenceManager::GetTypeId(const ValueType& value) +{ + if (std::holds_alternative(value)) return TypeId::BOOL; + if (std::holds_alternative(value)) return TypeId::INT; + if (std::holds_alternative(value)) return TypeId::FLOAT; + if (std::holds_alternative(value)) return TypeId::DOUBLE; + if (std::holds_alternative(value)) return TypeId::STRING; + + // Should never be reached + return TypeId::BOOL; +} +#endif // !ESP_PLATFORM \ No newline at end of file diff --git a/src/manager/PersistenceManager.h b/src/manager/PersistenceManager.h new file mode 100644 index 0000000..a101514 --- /dev/null +++ b/src/manager/PersistenceManager.h @@ -0,0 +1,67 @@ +#pragma once + +#include "common/IPersistenceManager.h" +#include +#include +#include + +class PersistenceManager final : public IPersistenceManager { +public: + using ValueType = std::variant< + bool, + int, + float, + double, + std::string + >; + +private: + std::unordered_map data_; + std::string filename_; + +public: + explicit PersistenceManager(std::string filename = "settings.dat"); + ~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 { return data_.size(); } + + bool Save() override; + bool Load() override; + + // Erweiterte SDL3-spezifische Methoden + bool SaveToFile(const std::string& filename); + bool LoadFromFile(const std::string& filename); + +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: + // Interne Hilfsmethoden für Serialisierung + static bool WriteValueToStream(SDL_IOStream* stream, const ValueType& value) ; + static bool ReadValueFromStream(SDL_IOStream* stream, ValueType& value) ; + + enum class TypeId : uint8_t { + BOOL = 0, + INT = 1, + FLOAT = 2, + DOUBLE = 3, + STRING = 4 + }; + + static TypeId GetTypeId(const ValueType& value); +}; diff --git a/src/ResourceManager.cpp b/src/manager/ResourceManager.cpp similarity index 100% rename from src/ResourceManager.cpp rename to src/manager/ResourceManager.cpp diff --git a/src/ResourceManager.h b/src/manager/ResourceManager.h similarity index 100% rename from src/ResourceManager.h rename to src/manager/ResourceManager.h diff --git a/src/ui/Device.cpp b/src/ui/Device.cpp index 4d33d6a..7e2d7dd 100644 --- a/src/ui/Device.cpp +++ b/src/ui/Device.cpp @@ -5,6 +5,7 @@ #include "MenuOptions.h" #include "common/InactivityTracker.h" +#include "manager/PersistenceManager.h" #include "ui/ScreenSaver.h" #include "ui/SplashScreen.h" #include "ui/widgets/Button.h" @@ -82,7 +83,10 @@ Device::Device(void *appstate) : UIWidget(appstate) .popScreen = [this]() { this->PopScreen(); }, + .persistenceManager = std::make_shared(), }; + options.persistenceManager->Load(); + m_widget = std::make_shared(&options); m_inactivityTracker = std::make_unique(60000, []() { const auto screensaver = std::make_shared(&options); diff --git a/src/ui/widgets/Button.cpp b/src/ui/widgets/Button.cpp index 97308a9..a284893 100644 --- a/src/ui/widgets/Button.cpp +++ b/src/ui/widgets/Button.cpp @@ -1,6 +1,6 @@ #include "ui/widgets/Button.h" -#include "ResourceManager.h" +#include "../../manager/ResourceManager.h" #include #include diff --git a/src/ui/widgets/D_Pad.cpp b/src/ui/widgets/D_Pad.cpp index 2575cc8..6cc24fb 100644 --- a/src/ui/widgets/D_Pad.cpp +++ b/src/ui/widgets/D_Pad.cpp @@ -1,7 +1,7 @@ #include "D_Pad.h" #include "ui/widgets/D_Pad.h" -#include "ResourceManager.h" +#include "../../manager/ResourceManager.h" D_Pad::D_Pad(void *appState, const float x, const float y, const float width, std::function callback) : UIWidget(appState), m_x(x), m_y(y), m_width(width), m_callback(std::move(callback))