#pragma once #include "WidgetConfig.hpp" #include "widgets/Widget.hpp" #include "lvgl.h" #include "freertos/FreeRTOS.h" #include "freertos/queue.h" #include #include class WidgetManager { public: static WidgetManager& instance(); // Initialize (load config from SD card) void init(); // Build/rebuild all widgets from current config // Call this after loading or when user clicks "Save" in web editor void applyConfig(); // Get current config for web editor (JSON) void getConfigJson(char* buf, size_t bufSize) const; // Update config from web editor (JSON) - does NOT apply immediately bool updateConfigFromJson(const char* json); // Save current config to SD card and apply to display void saveAndApply(); // Reset to factory defaults void resetToDefaults(); // Periodic tasks (standby handling) void loop(); // User activity (resets standby timer) void onUserActivity(); // Thread-safe KNX updates (queued to UI thread) void onKnxValue(uint16_t groupAddr, float value); void onKnxSwitch(uint16_t groupAddr, bool value); void onKnxText(uint16_t groupAddr, const char* text); // Button action handler void handleButtonAction(const WidgetConfig& cfg, lv_obj_t* target); void goBack(); // Direct config access GuiConfig& getConfig() { return config_; } const GuiConfig& getConfig() const { return config_; } const ScreenConfig* currentScreen() const; private: WidgetManager(); ~WidgetManager() = default; WidgetManager(const WidgetManager&) = delete; WidgetManager& operator=(const WidgetManager&) = delete; static constexpr size_t UI_EVENT_QUEUE_LEN = 16; static constexpr size_t UI_EVENT_TEXT_LEN = MAX_TEXT_LEN; enum class UiEventType : uint8_t { KNX_VALUE = 0, KNX_SWITCH = 1, KNX_TEXT = 2, }; struct UiEvent { UiEventType type; uint16_t groupAddr; float value; bool state; char text[UI_EVENT_TEXT_LEN]; }; void loadFromSdCard(); void saveToSdCard(); void destroyAllWidgets(); void createAllWidgets(const ScreenConfig& screen, lv_obj_t* parent); bool enqueueUiEvent(const UiEvent& event); void processUiQueue(); void applyKnxValue(uint16_t groupAddr, float value); void applyKnxSwitch(uint16_t groupAddr, bool value); void applyKnxText(uint16_t groupAddr, const char* text); void createDefaultConfig(); void applyScreen(uint8_t screenId); void showScreen(uint8_t screenId); void showModalScreen(const ScreenConfig& screen); void closeModal(); void enterStandby(); ScreenConfig* activeScreen(); const ScreenConfig* activeScreen() const; static constexpr const char* CONFIG_FILE = "/sdcard/lvgl.json"; static constexpr int64_t NAV_DELAY_US = 200 * 1000; // 200ms delay for touch release GuiConfig config_; uint8_t activeScreenId_ = 0; uint8_t previousScreenId_ = 0xFF; uint8_t standbyReturnScreenId_ = 0xFF; uint8_t modalScreenId_ = 0xFF; bool standbyActive_ = false; bool standbyWakePending_ = false; uint8_t standbyWakeTarget_ = 0xFF; bool navPending_ = false; ButtonAction navAction_ = ButtonAction::KNX; uint8_t navTargetScreen_ = 0xFF; int64_t navRequestUs_ = 0; int64_t lastActivityUs_ = 0; // Runtime widget instances (indexed by widget ID) std::array, MAX_WIDGETS> widgets_; lv_obj_t* screen_ = nullptr; lv_obj_t* modalContainer_ = nullptr; lv_obj_t* modalDimmer_ = nullptr; QueueHandle_t uiQueue_ = nullptr; };