From f34eb810da19de9b60e99fb77af7182d7504c091 Mon Sep 17 00:00:00 2001 From: Thomas Peterson Date: Sat, 24 Jan 2026 09:25:30 +0100 Subject: [PATCH] Webserver --- main/Touch.cpp | 2 + main/WidgetConfig.hpp | 36 +- main/WidgetManager.cpp | 823 ++++++++++--- main/WidgetManager.hpp | 40 +- main/main.cpp | 2 + main/webserver/ConfigHandlers.cpp | 6 +- sdcard_content/webseite/index.html | 1736 +++++++++++++++++++++++----- 7 files changed, 2139 insertions(+), 506 deletions(-) diff --git a/main/Touch.cpp b/main/Touch.cpp index ff733c2..ef5836d 100644 --- a/main/Touch.cpp +++ b/main/Touch.cpp @@ -2,6 +2,7 @@ #include "driver/i2c_master.h" #include "esp_log.h" #include "esp_lcd_touch_gt911.h" +#include "WidgetManager.hpp" // Common display resolutions, used for touch dimensions #define LCD_H_RES 800 @@ -75,6 +76,7 @@ void Touch::lv_indev_read_cb(lv_indev_t *indev, lv_indev_data_t *data) data->point.x = x[0]; data->point.y = y[0]; data->state = LV_INDEV_STATE_PRESSED; + WidgetManager::instance().onUserActivity(); } else { data->state = LV_INDEV_STATE_RELEASED; } diff --git a/main/WidgetConfig.hpp b/main/WidgetConfig.hpp index d1551c9..d0987ee 100644 --- a/main/WidgetConfig.hpp +++ b/main/WidgetConfig.hpp @@ -6,14 +6,28 @@ // Maximum number of widgets static constexpr size_t MAX_WIDGETS = 32; +static constexpr size_t MAX_SCREENS = 8; static constexpr size_t MAX_TEXT_LEN = 32; +static constexpr size_t MAX_SCREEN_NAME_LEN = 24; enum class WidgetType : uint8_t { LABEL = 0, BUTTON = 1, + LED = 2, // Future: GAUGE, IMAGE, ARC, etc. }; +enum class ScreenMode : uint8_t { + FULLSCREEN = 0, + MODAL = 1, +}; + +enum class ButtonAction : uint8_t { + KNX = 0, + JUMP = 1, + BACK = 2, +}; + // Text source: static text or KNX group address enum class TextSource : uint8_t { STATIC = 0, // Static text @@ -77,9 +91,11 @@ struct WidgetConfig { // Button specific bool isToggle; // For buttons: toggle mode uint16_t knxAddressWrite; // KNX address to write on click + ButtonAction action; // Button action (KNX, Jump, Back) + uint8_t targetScreen; // Target screen ID for jump // Serialization size (fixed for NVS storage) - static constexpr size_t SERIALIZED_SIZE = 64; + static constexpr size_t SERIALIZED_SIZE = 68; void serialize(uint8_t* buf) const; void deserialize(const uint8_t* buf); @@ -98,13 +114,29 @@ struct WidgetConfig { // Screen configuration (holds all widgets) struct ScreenConfig { + uint8_t id; + char name[MAX_SCREEN_NAME_LEN]; + ScreenMode mode; Color backgroundColor; uint8_t widgetCount; WidgetConfig widgets[MAX_WIDGETS]; - void clear(); + void clear(uint8_t newId = 0, const char* newName = nullptr); int addWidget(const WidgetConfig& widget); // Returns widget ID or -1 bool removeWidget(uint8_t id); WidgetConfig* findWidget(uint8_t id); const WidgetConfig* findWidget(uint8_t id) const; }; + +struct GuiConfig { + uint8_t screenCount; + ScreenConfig screens[MAX_SCREENS]; + uint8_t startScreenId; + bool standbyEnabled; + uint8_t standbyScreenId; + uint16_t standbyMinutes; + + void clear(); + ScreenConfig* findScreen(uint8_t id); + const ScreenConfig* findScreen(uint8_t id) const; +}; diff --git a/main/WidgetManager.cpp b/main/WidgetManager.cpp index 8f56fc7..28871f9 100644 --- a/main/WidgetManager.cpp +++ b/main/WidgetManager.cpp @@ -2,25 +2,24 @@ #include "SdCard.hpp" #include "esp_lv_adapter.h" #include "esp_log.h" +#include "esp_timer.h" #include "Gui.hpp" #include "cJSON.h" +#include +#include #include #include #include static const char* TAG = "WidgetMgr"; +static constexpr uint8_t SCREEN_ID_NONE = 0xFF; // Button click callback static void button_click_cb(lv_event_t* e) { WidgetConfig* cfg = static_cast(lv_event_get_user_data(e)); - if (cfg && cfg->knxAddressWrite > 0) { - lv_obj_t* target = static_cast(lv_event_get_target(e)); - bool state = (lv_obj_get_state(target) & LV_STATE_CHECKED) != 0; - ESP_LOGI(TAG, "Button %d clicked, KNX write to %d, state=%d", - cfg->id, cfg->knxAddressWrite, state); - // TODO: Send KNX telegram - // Gui::knxWorker.writeSwitch(cfg->knxAddressWrite, state); - } + if (!cfg) return; + lv_obj_t* target = static_cast(lv_event_get_target(e)); + WidgetManager::instance().handleButtonAction(*cfg, target); } // WidgetConfig implementation @@ -55,6 +54,8 @@ void WidgetConfig::serialize(uint8_t* buf) const { buf[pos++] = isToggle ? 1 : 0; buf[pos++] = knxAddressWrite & 0xFF; buf[pos++] = (knxAddressWrite >> 8) & 0xFF; + buf[pos++] = static_cast(action); + buf[pos++] = targetScreen; } void WidgetConfig::deserialize(const uint8_t* buf) { @@ -88,6 +89,9 @@ void WidgetConfig::deserialize(const uint8_t* buf) { isToggle = buf[pos++] != 0; knxAddressWrite = buf[pos] | (buf[pos+1] << 8); + pos += 2; + action = static_cast(buf[pos++]); + targetScreen = buf[pos++]; } WidgetConfig WidgetConfig::createLabel(uint8_t id, int16_t x, int16_t y, const char* labelText) { @@ -143,14 +147,22 @@ WidgetConfig WidgetConfig::createButton(uint8_t id, int16_t x, int16_t y, cfg.shadow.color = {0, 0, 0}; cfg.isToggle = toggle; cfg.knxAddressWrite = knxAddrWrite; + cfg.action = ButtonAction::KNX; + cfg.targetScreen = 0; return cfg; } // ScreenConfig implementation -void ScreenConfig::clear() { +void ScreenConfig::clear(uint8_t newId, const char* newName) { + id = newId; + mode = ScreenMode::FULLSCREEN; backgroundColor = {26, 26, 46}; // Dark blue background widgetCount = 0; memset(widgets, 0, sizeof(widgets)); + memset(name, 0, sizeof(name)); + if (newName && newName[0] != '\0') { + strncpy(name, newName, sizeof(name) - 1); + } } int ScreenConfig::addWidget(const WidgetConfig& widget) { @@ -196,6 +208,32 @@ const WidgetConfig* ScreenConfig::findWidget(uint8_t id) const { return nullptr; } +// GuiConfig implementation +void GuiConfig::clear() { + screenCount = 0; + startScreenId = 0; + standbyEnabled = false; + standbyScreenId = 0xFF; + standbyMinutes = 0; + for (size_t i = 0; i < MAX_SCREENS; i++) { + screens[i].clear(static_cast(i), nullptr); + } +} + +ScreenConfig* GuiConfig::findScreen(uint8_t id) { + for (uint8_t i = 0; i < screenCount; i++) { + if (screens[i].id == id) return &screens[i]; + } + return nullptr; +} + +const ScreenConfig* GuiConfig::findScreen(uint8_t id) const { + for (uint8_t i = 0; i < screenCount; i++) { + if (screens[i].id == id) return &screens[i]; + } + return nullptr; +} + // WidgetManager implementation WidgetManager& WidgetManager::instance() { static WidgetManager inst; @@ -205,26 +243,45 @@ WidgetManager& WidgetManager::instance() { WidgetManager::WidgetManager() { widgetObjects_.fill(nullptr); createDefaultConfig(); + activeScreenId_ = config_.startScreenId; + lastActivityUs_ = esp_timer_get_time(); } void WidgetManager::createDefaultConfig() { config_.clear(); + config_.screenCount = 1; + ScreenConfig& screen = config_.screens[0]; + screen.clear(0, "Screen 1"); // Default: Temperature label auto tempLabel = WidgetConfig::createKnxLabel(0, 50, 20, TextSource::KNX_DPT_TEMP, 1, "%.1f °C"); tempLabel.fontSize = 3; // 28pt - config_.addWidget(tempLabel); + screen.addWidget(tempLabel); // Default: KNX Prog button auto progBtn = WidgetConfig::createButton(1, 50, 100, "KNX Prog", 0, true); progBtn.bgColor = {200, 50, 50}; // Red - config_.addWidget(progBtn); + screen.addWidget(progBtn); + + config_.startScreenId = screen.id; + config_.standbyEnabled = false; + config_.standbyScreenId = 0xFF; + config_.standbyMinutes = 0; + activeScreenId_ = screen.id; } void WidgetManager::init() { loadFromSdCard(); - ESP_LOGI(TAG, "WidgetManager initialized with %d widgets", config_.widgetCount); + if (config_.findScreen(config_.startScreenId)) { + activeScreenId_ = config_.startScreenId; + } else if (config_.screenCount > 0) { + activeScreenId_ = config_.screens[0].id; + } else { + activeScreenId_ = 0; + } + lastActivityUs_ = esp_timer_get_time(); + ESP_LOGI(TAG, "WidgetManager initialized with %d screens", config_.screenCount); } void WidgetManager::loadFromSdCard() { @@ -267,7 +324,7 @@ void WidgetManager::loadFromSdCard() { delete[] json; if (success) { - ESP_LOGI(TAG, "Loaded %d widgets from SD card", config_.widgetCount); + ESP_LOGI(TAG, "Loaded %d screens from SD card", config_.screenCount); } else { ESP_LOGE(TAG, "Failed to parse config file"); } @@ -280,13 +337,13 @@ void WidgetManager::saveToSdCard() { } // Generate JSON using cJSON - char* json = new char[8192]; + char* json = new char[32768]; if (!json) { ESP_LOGE(TAG, "Failed to allocate memory for JSON"); return; } - getConfigJson(json, 8192); + getConfigJson(json, 32768); // Write to file FILE* f = fopen(CONFIG_FILE, "w"); @@ -301,18 +358,21 @@ void WidgetManager::saveToSdCard() { delete[] json; if (written > 0) { - ESP_LOGI(TAG, "Saved %d widgets to SD card", config_.widgetCount); + ESP_LOGI(TAG, "Saved %d screens to SD card", config_.screenCount); } else { ESP_LOGE(TAG, "Failed to write config file"); } } void WidgetManager::applyConfig() { - if (esp_lv_adapter_lock(-1) == ESP_OK) { - destroyAllWidgets(); - createAllWidgets(); - esp_lv_adapter_unlock(); + if (!config_.findScreen(activeScreenId_)) { + if (config_.findScreen(config_.startScreenId)) { + activeScreenId_ = config_.startScreenId; + } else if (config_.screenCount > 0) { + activeScreenId_ = config_.screens[0].id; + } } + applyScreen(activeScreenId_); } void WidgetManager::saveAndApply() { @@ -322,11 +382,209 @@ void WidgetManager::saveAndApply() { } void WidgetManager::resetToDefaults() { + closeModal(); + standbyActive_ = false; + standbyWakePending_ = false; + standbyReturnScreenId_ = SCREEN_ID_NONE; + previousScreenId_ = SCREEN_ID_NONE; createDefaultConfig(); saveAndApply(); ESP_LOGI(TAG, "Reset to defaults"); } +ScreenConfig* WidgetManager::activeScreen() { + if (modalContainer_ && modalScreenId_ != SCREEN_ID_NONE) { + return config_.findScreen(modalScreenId_); + } + return config_.findScreen(activeScreenId_); +} + +const ScreenConfig* WidgetManager::activeScreen() const { + if (modalContainer_ && modalScreenId_ != SCREEN_ID_NONE) { + return config_.findScreen(modalScreenId_); + } + return config_.findScreen(activeScreenId_); +} + +void WidgetManager::applyScreen(uint8_t screenId) { + ScreenConfig* screen = config_.findScreen(screenId); + if (!screen) { + ESP_LOGW(TAG, "Screen %d not found", screenId); + return; + } + + if (modalContainer_) { + closeModal(); + } + + if (esp_lv_adapter_lock(-1) == ESP_OK) { + lv_obj_t* root = lv_scr_act(); + lv_obj_clean(root); + widgetObjects_.fill(nullptr); + createAllWidgets(*screen, root); + esp_lv_adapter_unlock(); + } +} + +void WidgetManager::showModalScreen(const ScreenConfig& screen) { + if (modalContainer_) { + closeModal(); + } + + if (esp_lv_adapter_lock(-1) != ESP_OK) return; + + lv_obj_t* overlay = lv_obj_create(lv_layer_top()); + lv_obj_set_style_bg_opa(overlay, LV_OPA_COVER, 0); + lv_obj_clear_flag(overlay, LV_OBJ_FLAG_SCROLLABLE); + + lv_disp_t* disp = lv_disp_get_default(); + int32_t hor = disp ? lv_disp_get_hor_res(disp) : 1280; + int32_t ver = disp ? lv_disp_get_ver_res(disp) : 800; + lv_obj_set_size(overlay, hor, ver); + + modalContainer_ = overlay; + modalScreenId_ = screen.id; + createAllWidgets(screen, modalContainer_); + + esp_lv_adapter_unlock(); +} + +void WidgetManager::closeModal() { + if (!modalContainer_) return; + + if (esp_lv_adapter_lock(-1) == ESP_OK) { + lv_obj_delete(modalContainer_); + esp_lv_adapter_unlock(); + } + modalContainer_ = nullptr; + modalScreenId_ = SCREEN_ID_NONE; + widgetObjects_.fill(nullptr); +} + +void WidgetManager::showScreen(uint8_t screenId) { + ScreenConfig* screen = config_.findScreen(screenId); + if (!screen) { + ESP_LOGW(TAG, "Screen %d not found", screenId); + return; + } + + if (screen->mode == ScreenMode::MODAL) { + showModalScreen(*screen); + return; + } + + previousScreenId_ = activeScreenId_; + activeScreenId_ = screen->id; + standbyActive_ = false; + applyScreen(activeScreenId_); +} + +void WidgetManager::handleButtonAction(const WidgetConfig& cfg, lv_obj_t* target) { + if (cfg.type != WidgetType::BUTTON) return; + + onUserActivity(); + + switch (cfg.action) { + case ButtonAction::JUMP: + navPending_ = true; + navAction_ = ButtonAction::JUMP; + navTargetScreen_ = cfg.targetScreen; + break; + case ButtonAction::BACK: + navPending_ = true; + navAction_ = ButtonAction::BACK; + navTargetScreen_ = SCREEN_ID_NONE; + break; + case ButtonAction::KNX: + default: { + if (cfg.knxAddressWrite > 0) { + bool state = false; + if (target) { + state = (lv_obj_get_state(target) & LV_STATE_CHECKED) != 0; + } + ESP_LOGI(TAG, "Button %d clicked, KNX write to %d, state=%d", + cfg.id, cfg.knxAddressWrite, state); + // TODO: Send KNX telegram + // Gui::knxWorker.writeSwitch(cfg.knxAddressWrite, state); + } + break; + } + } +} + +void WidgetManager::goBack() { + if (modalContainer_) { + closeModal(); + applyScreen(activeScreenId_); + return; + } + + if (previousScreenId_ != SCREEN_ID_NONE && previousScreenId_ != activeScreenId_) { + activeScreenId_ = previousScreenId_; + previousScreenId_ = SCREEN_ID_NONE; + applyScreen(activeScreenId_); + } +} + +void WidgetManager::enterStandby() { + if (!config_.standbyEnabled || config_.standbyMinutes == 0) return; + if (standbyActive_) return; + if (config_.standbyScreenId == SCREEN_ID_NONE) return; + + ScreenConfig* standbyScreen = config_.findScreen(config_.standbyScreenId); + if (!standbyScreen) return; + + standbyReturnScreenId_ = activeScreenId_; + standbyActive_ = true; + activeScreenId_ = standbyScreen->id; + applyScreen(activeScreenId_); +} + +void WidgetManager::loop() { + if (navPending_) { + navPending_ = false; + if (navAction_ == ButtonAction::JUMP) { + showScreen(navTargetScreen_); + } else if (navAction_ == ButtonAction::BACK) { + goBack(); + } + return; + } + + if (standbyWakePending_) { + standbyWakePending_ = false; + if (standbyWakeTarget_ != SCREEN_ID_NONE) { + activeScreenId_ = standbyWakeTarget_; + applyScreen(activeScreenId_); + } + return; + } + + if (!config_.standbyEnabled || config_.standbyMinutes == 0) return; + if (standbyActive_) return; + if (config_.standbyScreenId == SCREEN_ID_NONE) return; + + int64_t now = esp_timer_get_time(); + int64_t idleUs = now - lastActivityUs_; + int64_t timeoutUs = static_cast(config_.standbyMinutes) * 60 * 1000000LL; + if (idleUs >= timeoutUs) { + enterStandby(); + } +} + +void WidgetManager::onUserActivity() { + lastActivityUs_ = esp_timer_get_time(); + if (standbyActive_) { + standbyActive_ = false; + uint8_t returnId = standbyReturnScreenId_; + if (returnId == SCREEN_ID_NONE) { + returnId = config_.startScreenId; + } + standbyWakeTarget_ = returnId; + standbyWakePending_ = true; + } +} + void WidgetManager::destroyAllWidgets() { for (auto& obj : widgetObjects_) { if (obj != nullptr) { @@ -336,38 +594,40 @@ void WidgetManager::destroyAllWidgets() { } } -void WidgetManager::createAllWidgets() { - screen_ = lv_scr_act(); +void WidgetManager::createAllWidgets(const ScreenConfig& screen, lv_obj_t* parent) { + screen_ = parent; + widgetObjects_.fill(nullptr); // Set background color - lv_obj_set_style_bg_color(screen_, lv_color_make( - config_.backgroundColor.r, - config_.backgroundColor.g, - config_.backgroundColor.b), 0); + lv_obj_set_style_bg_color(parent, lv_color_make( + screen.backgroundColor.r, + screen.backgroundColor.g, + screen.backgroundColor.b), 0); + lv_obj_set_style_bg_opa(parent, LV_OPA_COVER, 0); // Create all widgets - for (uint8_t i = 0; i < config_.widgetCount; i++) { - WidgetConfig& cfg = config_.widgets[i]; - lv_obj_t* obj = createWidget(cfg); + for (uint8_t i = 0; i < screen.widgetCount; i++) { + const WidgetConfig& cfg = screen.widgets[i]; + lv_obj_t* obj = createWidget(cfg, parent); if (obj != nullptr && cfg.id < MAX_WIDGETS) { widgetObjects_[cfg.id] = obj; } } } -lv_obj_t* WidgetManager::createWidget(const WidgetConfig& cfg) { +lv_obj_t* WidgetManager::createWidget(const WidgetConfig& cfg, lv_obj_t* parent) { if (!cfg.visible) return nullptr; lv_obj_t* obj = nullptr; switch (cfg.type) { case WidgetType::LABEL: { - obj = lv_label_create(screen_); + obj = lv_label_create(parent); lv_label_set_text(obj, cfg.text); break; } case WidgetType::BUTTON: { - obj = lv_btn_create(screen_); + obj = lv_btn_create(parent); if (cfg.isToggle) { lv_obj_add_flag(obj, LV_OBJ_FLAG_CHECKABLE); } @@ -377,9 +637,16 @@ lv_obj_t* WidgetManager::createWidget(const WidgetConfig& cfg) { // Create label inside button lv_obj_t* label = lv_label_create(obj); lv_label_set_text(label, cfg.text); + lv_obj_set_style_text_color(label, lv_color_make( + cfg.textColor.r, cfg.textColor.g, cfg.textColor.b), 0); + lv_obj_set_style_text_font(label, getFontBySize(cfg.fontSize), 0); lv_obj_center(label); break; } + case WidgetType::LED: { + obj = lv_led_create(parent); + break; + } } if (obj != nullptr) { @@ -387,7 +654,11 @@ lv_obj_t* WidgetManager::createWidget(const WidgetConfig& cfg) { if (cfg.width > 0 && cfg.height > 0) { lv_obj_set_size(obj, cfg.width, cfg.height); } - applyStyle(obj, cfg); + if (cfg.type == WidgetType::LED) { + applyLedStyle(obj, cfg); + } else { + applyStyle(obj, cfg); + } } return obj; @@ -425,6 +696,23 @@ void WidgetManager::applyStyle(lv_obj_t* obj, const WidgetConfig& cfg) { } } +void WidgetManager::applyLedStyle(lv_obj_t* obj, const WidgetConfig& cfg) { + lv_obj_set_style_radius(obj, LV_RADIUS_CIRCLE, 0); + lv_led_set_color(obj, lv_color_make( + cfg.bgColor.r, cfg.bgColor.g, cfg.bgColor.b)); + lv_led_set_brightness(obj, cfg.bgOpacity); + + if (cfg.shadow.enabled) { + lv_obj_set_style_shadow_color(obj, lv_color_make( + cfg.shadow.color.r, cfg.shadow.color.g, cfg.shadow.color.b), 0); + lv_obj_set_style_shadow_opa(obj, 180, 0); + lv_obj_set_style_shadow_width(obj, cfg.shadow.blur, 0); + lv_obj_set_style_shadow_spread(obj, cfg.shadow.spread, 0); + lv_obj_set_style_shadow_offset_x(obj, cfg.shadow.offsetX, 0); + lv_obj_set_style_shadow_offset_y(obj, cfg.shadow.offsetY, 0); + } +} + const lv_font_t* WidgetManager::getFontBySize(uint8_t sizeIndex) { // Font sizes: 0=14, 1=18, 2=22, 3=28, 4=36, 5=48 // These must be enabled in sdkconfig (CONFIG_LV_FONT_MONTSERRAT_*) @@ -452,8 +740,14 @@ const lv_font_t* WidgetManager::getFontBySize(uint8_t sizeIndex) { void WidgetManager::onKnxValue(uint16_t groupAddr, float value) { if (esp_lv_adapter_lock(100) != ESP_OK) return; - for (uint8_t i = 0; i < config_.widgetCount; i++) { - const WidgetConfig& cfg = config_.widgets[i]; + const ScreenConfig* screen = activeScreen(); + if (!screen) { + esp_lv_adapter_unlock(); + return; + } + + for (uint8_t i = 0; i < screen->widgetCount; i++) { + const WidgetConfig& cfg = screen->widgets[i]; if (cfg.knxAddress == groupAddr && cfg.textSource == TextSource::KNX_DPT_TEMP) { lv_obj_t* obj = widgetObjects_[cfg.id]; if (obj != nullptr) { @@ -470,12 +764,23 @@ void WidgetManager::onKnxValue(uint16_t groupAddr, float value) { void WidgetManager::onKnxSwitch(uint16_t groupAddr, bool value) { if (esp_lv_adapter_lock(100) != ESP_OK) return; - for (uint8_t i = 0; i < config_.widgetCount; i++) { - const WidgetConfig& cfg = config_.widgets[i]; + const ScreenConfig* screen = activeScreen(); + if (!screen) { + esp_lv_adapter_unlock(); + return; + } + + for (uint8_t i = 0; i < screen->widgetCount; i++) { + const WidgetConfig& cfg = screen->widgets[i]; if (cfg.knxAddress == groupAddr && cfg.textSource == TextSource::KNX_DPT_SWITCH) { lv_obj_t* obj = widgetObjects_[cfg.id]; - if (obj != nullptr) { + if (obj == nullptr) continue; + + if (cfg.type == WidgetType::LABEL) { lv_label_set_text(obj, value ? "EIN" : "AUS"); + } else if (cfg.type == WidgetType::LED) { + uint8_t brightness = value ? (cfg.bgOpacity > 0 ? cfg.bgOpacity : 255) : 0; + lv_led_set_brightness(obj, brightness); } } } @@ -486,8 +791,14 @@ void WidgetManager::onKnxSwitch(uint16_t groupAddr, bool value) { void WidgetManager::onKnxText(uint16_t groupAddr, const char* text) { if (esp_lv_adapter_lock(100) != ESP_OK) return; - for (uint8_t i = 0; i < config_.widgetCount; i++) { - const WidgetConfig& cfg = config_.widgets[i]; + const ScreenConfig* screen = activeScreen(); + if (!screen) { + esp_lv_adapter_unlock(); + return; + } + + for (uint8_t i = 0; i < screen->widgetCount; i++) { + const WidgetConfig& cfg = screen->widgets[i]; if (cfg.knxAddress == groupAddr && cfg.textSource == TextSource::KNX_DPT_TEXT) { lv_obj_t* obj = widgetObjects_[cfg.id]; if (obj != nullptr) { @@ -506,62 +817,82 @@ void WidgetManager::getConfigJson(char* buf, size_t bufSize) const { return; } - // Add background color - char bgColorStr[8]; - snprintf(bgColorStr, sizeof(bgColorStr), "#%02X%02X%02X", - config_.backgroundColor.r, config_.backgroundColor.g, config_.backgroundColor.b); - cJSON_AddStringToObject(root, "bgColor", bgColorStr); + cJSON_AddNumberToObject(root, "startScreen", config_.startScreenId); - // Add widgets array - cJSON* widgets = cJSON_AddArrayToObject(root, "widgets"); + cJSON* standby = cJSON_AddObjectToObject(root, "standby"); + cJSON_AddBoolToObject(standby, "enabled", config_.standbyEnabled); + cJSON_AddNumberToObject(standby, "screen", config_.standbyScreenId); + cJSON_AddNumberToObject(standby, "minutes", config_.standbyMinutes); - for (uint8_t i = 0; i < config_.widgetCount; i++) { - const WidgetConfig& w = config_.widgets[i]; - cJSON* widget = cJSON_CreateObject(); + // Add screens array + cJSON* screens = cJSON_AddArrayToObject(root, "screens"); - cJSON_AddNumberToObject(widget, "id", w.id); - cJSON_AddNumberToObject(widget, "type", static_cast(w.type)); - cJSON_AddNumberToObject(widget, "x", w.x); - cJSON_AddNumberToObject(widget, "y", w.y); - cJSON_AddNumberToObject(widget, "w", w.width); - cJSON_AddNumberToObject(widget, "h", w.height); - cJSON_AddBoolToObject(widget, "visible", w.visible); - cJSON_AddNumberToObject(widget, "textSrc", static_cast(w.textSource)); - cJSON_AddStringToObject(widget, "text", w.text); - cJSON_AddNumberToObject(widget, "knxAddr", w.knxAddress); - cJSON_AddNumberToObject(widget, "fontSize", w.fontSize); + for (uint8_t s = 0; s < config_.screenCount; s++) { + const ScreenConfig& screen = config_.screens[s]; + cJSON* screenJson = cJSON_CreateObject(); - // Text color - char textColorStr[8]; - snprintf(textColorStr, sizeof(textColorStr), "#%02X%02X%02X", - w.textColor.r, w.textColor.g, w.textColor.b); - cJSON_AddStringToObject(widget, "textColor", textColorStr); + cJSON_AddNumberToObject(screenJson, "id", screen.id); + cJSON_AddStringToObject(screenJson, "name", screen.name); + cJSON_AddNumberToObject(screenJson, "mode", static_cast(screen.mode)); - // Background color - char widgetBgColorStr[8]; - snprintf(widgetBgColorStr, sizeof(widgetBgColorStr), "#%02X%02X%02X", - w.bgColor.r, w.bgColor.g, w.bgColor.b); - cJSON_AddStringToObject(widget, "bgColor", widgetBgColorStr); + char bgColorStr[8]; + snprintf(bgColorStr, sizeof(bgColorStr), "#%02X%02X%02X", + screen.backgroundColor.r, screen.backgroundColor.g, screen.backgroundColor.b); + cJSON_AddStringToObject(screenJson, "bgColor", bgColorStr); - cJSON_AddNumberToObject(widget, "bgOpacity", w.bgOpacity); - cJSON_AddNumberToObject(widget, "radius", w.borderRadius); + cJSON* widgets = cJSON_AddArrayToObject(screenJson, "widgets"); + for (uint8_t i = 0; i < screen.widgetCount; i++) { + const WidgetConfig& w = screen.widgets[i]; + cJSON* widget = cJSON_CreateObject(); - // Shadow object - cJSON* shadow = cJSON_AddObjectToObject(widget, "shadow"); - cJSON_AddBoolToObject(shadow, "enabled", w.shadow.enabled); - cJSON_AddNumberToObject(shadow, "x", w.shadow.offsetX); - cJSON_AddNumberToObject(shadow, "y", w.shadow.offsetY); - cJSON_AddNumberToObject(shadow, "blur", w.shadow.blur); - cJSON_AddNumberToObject(shadow, "spread", w.shadow.spread); - char shadowColorStr[8]; - snprintf(shadowColorStr, sizeof(shadowColorStr), "#%02X%02X%02X", - w.shadow.color.r, w.shadow.color.g, w.shadow.color.b); - cJSON_AddStringToObject(shadow, "color", shadowColorStr); + cJSON_AddNumberToObject(widget, "id", w.id); + cJSON_AddNumberToObject(widget, "type", static_cast(w.type)); + cJSON_AddNumberToObject(widget, "x", w.x); + cJSON_AddNumberToObject(widget, "y", w.y); + cJSON_AddNumberToObject(widget, "w", w.width); + cJSON_AddNumberToObject(widget, "h", w.height); + cJSON_AddBoolToObject(widget, "visible", w.visible); + cJSON_AddNumberToObject(widget, "textSrc", static_cast(w.textSource)); + cJSON_AddStringToObject(widget, "text", w.text); + cJSON_AddNumberToObject(widget, "knxAddr", w.knxAddress); + cJSON_AddNumberToObject(widget, "fontSize", w.fontSize); - cJSON_AddBoolToObject(widget, "isToggle", w.isToggle); - cJSON_AddNumberToObject(widget, "knxAddrWrite", w.knxAddressWrite); + // Text color + char textColorStr[8]; + snprintf(textColorStr, sizeof(textColorStr), "#%02X%02X%02X", + w.textColor.r, w.textColor.g, w.textColor.b); + cJSON_AddStringToObject(widget, "textColor", textColorStr); - cJSON_AddItemToArray(widgets, widget); + // Background color + char widgetBgColorStr[8]; + snprintf(widgetBgColorStr, sizeof(widgetBgColorStr), "#%02X%02X%02X", + w.bgColor.r, w.bgColor.g, w.bgColor.b); + cJSON_AddStringToObject(widget, "bgColor", widgetBgColorStr); + + cJSON_AddNumberToObject(widget, "bgOpacity", w.bgOpacity); + cJSON_AddNumberToObject(widget, "radius", w.borderRadius); + + // Shadow object + cJSON* shadow = cJSON_AddObjectToObject(widget, "shadow"); + cJSON_AddBoolToObject(shadow, "enabled", w.shadow.enabled); + cJSON_AddNumberToObject(shadow, "x", w.shadow.offsetX); + cJSON_AddNumberToObject(shadow, "y", w.shadow.offsetY); + cJSON_AddNumberToObject(shadow, "blur", w.shadow.blur); + cJSON_AddNumberToObject(shadow, "spread", w.shadow.spread); + char shadowColorStr[8]; + snprintf(shadowColorStr, sizeof(shadowColorStr), "#%02X%02X%02X", + w.shadow.color.r, w.shadow.color.g, w.shadow.color.b); + cJSON_AddStringToObject(shadow, "color", shadowColorStr); + + cJSON_AddBoolToObject(widget, "isToggle", w.isToggle); + cJSON_AddNumberToObject(widget, "knxAddrWrite", w.knxAddressWrite); + cJSON_AddNumberToObject(widget, "action", static_cast(w.action)); + cJSON_AddNumberToObject(widget, "targetScreen", w.targetScreen); + + cJSON_AddItemToArray(widgets, widget); + } + + cJSON_AddItemToArray(screens, screenJson); } // Print to buffer @@ -590,116 +921,230 @@ bool WidgetManager::updateConfigFromJson(const char* json) { return false; } - // Parse background color - cJSON* bgColor = cJSON_GetObjectItem(root, "bgColor"); - if (cJSON_IsString(bgColor)) { - config_.backgroundColor = Color::fromHex(parseHexColor(bgColor->valuestring)); + std::unique_ptr newConfig(new (std::nothrow) GuiConfig()); + if (!newConfig) { + ESP_LOGE(TAG, "Out of memory for config"); + cJSON_Delete(root); + return false; + } + newConfig->clear(); + + auto parseWidgets = [&](cJSON* widgets, ScreenConfig& screen) -> bool { + if (!cJSON_IsArray(widgets)) return false; + + screen.widgetCount = 0; + cJSON* widget = nullptr; + cJSON_ArrayForEach(widget, widgets) { + if (screen.widgetCount >= MAX_WIDGETS) break; + + WidgetConfig& w = screen.widgets[screen.widgetCount]; + memset(&w, 0, sizeof(w)); + w.visible = true; + w.action = ButtonAction::KNX; + w.targetScreen = 0; + + // Parse basic properties + cJSON* id = cJSON_GetObjectItem(widget, "id"); + if (cJSON_IsNumber(id)) w.id = id->valueint; + + cJSON* type = cJSON_GetObjectItem(widget, "type"); + if (cJSON_IsNumber(type)) w.type = static_cast(type->valueint); + + cJSON* x = cJSON_GetObjectItem(widget, "x"); + if (cJSON_IsNumber(x)) w.x = x->valueint; + + cJSON* y = cJSON_GetObjectItem(widget, "y"); + if (cJSON_IsNumber(y)) w.y = y->valueint; + + cJSON* width = cJSON_GetObjectItem(widget, "w"); + if (cJSON_IsNumber(width)) w.width = width->valueint; + + cJSON* height = cJSON_GetObjectItem(widget, "h"); + if (cJSON_IsNumber(height)) w.height = height->valueint; + + cJSON* visible = cJSON_GetObjectItem(widget, "visible"); + if (cJSON_IsBool(visible)) w.visible = cJSON_IsTrue(visible); + + cJSON* textSrc = cJSON_GetObjectItem(widget, "textSrc"); + if (cJSON_IsNumber(textSrc)) w.textSource = static_cast(textSrc->valueint); + + cJSON* text = cJSON_GetObjectItem(widget, "text"); + if (cJSON_IsString(text)) { + strncpy(w.text, text->valuestring, MAX_TEXT_LEN - 1); + w.text[MAX_TEXT_LEN - 1] = '\0'; + } + + cJSON* knxAddr = cJSON_GetObjectItem(widget, "knxAddr"); + if (cJSON_IsNumber(knxAddr)) w.knxAddress = knxAddr->valueint; + + cJSON* fontSize = cJSON_GetObjectItem(widget, "fontSize"); + if (cJSON_IsNumber(fontSize)) w.fontSize = fontSize->valueint; + + // Parse colors + cJSON* textColor = cJSON_GetObjectItem(widget, "textColor"); + if (cJSON_IsString(textColor)) { + w.textColor = Color::fromHex(parseHexColor(textColor->valuestring)); + } + + cJSON* widgetBgColor = cJSON_GetObjectItem(widget, "bgColor"); + if (cJSON_IsString(widgetBgColor)) { + w.bgColor = Color::fromHex(parseHexColor(widgetBgColor->valuestring)); + } + + cJSON* bgOpacity = cJSON_GetObjectItem(widget, "bgOpacity"); + if (cJSON_IsNumber(bgOpacity)) w.bgOpacity = bgOpacity->valueint; + + cJSON* radius = cJSON_GetObjectItem(widget, "radius"); + if (cJSON_IsNumber(radius)) w.borderRadius = radius->valueint; + + // Parse shadow + cJSON* shadow = cJSON_GetObjectItem(widget, "shadow"); + if (cJSON_IsObject(shadow)) { + cJSON* enabled = cJSON_GetObjectItem(shadow, "enabled"); + if (cJSON_IsBool(enabled)) w.shadow.enabled = cJSON_IsTrue(enabled); + + cJSON* sx = cJSON_GetObjectItem(shadow, "x"); + if (cJSON_IsNumber(sx)) w.shadow.offsetX = sx->valueint; + + cJSON* sy = cJSON_GetObjectItem(shadow, "y"); + if (cJSON_IsNumber(sy)) w.shadow.offsetY = sy->valueint; + + cJSON* blur = cJSON_GetObjectItem(shadow, "blur"); + if (cJSON_IsNumber(blur)) w.shadow.blur = blur->valueint; + + cJSON* spread = cJSON_GetObjectItem(shadow, "spread"); + if (cJSON_IsNumber(spread)) w.shadow.spread = spread->valueint; + + cJSON* shadowColor = cJSON_GetObjectItem(shadow, "color"); + if (cJSON_IsString(shadowColor)) { + w.shadow.color = Color::fromHex(parseHexColor(shadowColor->valuestring)); + } + } + + cJSON* isToggle = cJSON_GetObjectItem(widget, "isToggle"); + if (cJSON_IsBool(isToggle)) w.isToggle = cJSON_IsTrue(isToggle); + + cJSON* knxAddrWrite = cJSON_GetObjectItem(widget, "knxAddrWrite"); + if (cJSON_IsNumber(knxAddrWrite)) w.knxAddressWrite = knxAddrWrite->valueint; + + cJSON* action = cJSON_GetObjectItem(widget, "action"); + if (cJSON_IsNumber(action)) w.action = static_cast(action->valueint); + + cJSON* targetScreen = cJSON_GetObjectItem(widget, "targetScreen"); + if (cJSON_IsNumber(targetScreen)) w.targetScreen = targetScreen->valueint; + + screen.widgetCount++; + } + + return true; + }; + + cJSON* screens = cJSON_GetObjectItem(root, "screens"); + if (cJSON_IsArray(screens)) { + cJSON* screenJson = nullptr; + cJSON_ArrayForEach(screenJson, screens) { + if (newConfig->screenCount >= MAX_SCREENS) break; + + uint8_t screenId = newConfig->screenCount; + const char* screenName = nullptr; + + cJSON* id = cJSON_GetObjectItem(screenJson, "id"); + if (cJSON_IsNumber(id)) { + int idVal = id->valueint; + if (idVal < 0) idVal = 0; + screenId = static_cast(idVal); + } + + cJSON* name = cJSON_GetObjectItem(screenJson, "name"); + if (cJSON_IsString(name)) screenName = name->valuestring; + + ScreenConfig& screen = newConfig->screens[newConfig->screenCount]; + screen.clear(screenId, screenName); + + if (!screen.name[0]) { + char fallback[16]; + snprintf(fallback, sizeof(fallback), "Screen %d", screenId); + strncpy(screen.name, fallback, sizeof(screen.name) - 1); + } + + cJSON* mode = cJSON_GetObjectItem(screenJson, "mode"); + if (cJSON_IsNumber(mode)) screen.mode = static_cast(mode->valueint); + + cJSON* bgColor = cJSON_GetObjectItem(screenJson, "bgColor"); + if (cJSON_IsString(bgColor)) { + screen.backgroundColor = Color::fromHex(parseHexColor(bgColor->valuestring)); + } + + cJSON* widgets = cJSON_GetObjectItem(screenJson, "widgets"); + if (!parseWidgets(widgets, screen)) { + screen.widgetCount = 0; + } + + newConfig->screenCount++; + } + } else { + newConfig->screenCount = 1; + ScreenConfig& screen = newConfig->screens[0]; + screen.clear(0, "Screen 1"); + + cJSON* bgColor = cJSON_GetObjectItem(root, "bgColor"); + if (cJSON_IsString(bgColor)) { + screen.backgroundColor = Color::fromHex(parseHexColor(bgColor->valuestring)); + } + + cJSON* widgets = cJSON_GetObjectItem(root, "widgets"); + if (!parseWidgets(widgets, screen)) { + cJSON_Delete(root); + return false; + } } - // Parse widgets array - cJSON* widgets = cJSON_GetObjectItem(root, "widgets"); - if (!cJSON_IsArray(widgets)) { + cJSON* startScreen = cJSON_GetObjectItem(root, "startScreen"); + if (cJSON_IsNumber(startScreen)) { + int val = startScreen->valueint; + if (val < 0) val = 0; + newConfig->startScreenId = static_cast(val); + } + + cJSON* standby = cJSON_GetObjectItem(root, "standby"); + if (cJSON_IsObject(standby)) { + cJSON* enabled = cJSON_GetObjectItem(standby, "enabled"); + if (cJSON_IsBool(enabled)) newConfig->standbyEnabled = cJSON_IsTrue(enabled); + + cJSON* screen = cJSON_GetObjectItem(standby, "screen"); + if (cJSON_IsNumber(screen)) { + int val = screen->valueint; + if (val < 0) { + newConfig->standbyScreenId = SCREEN_ID_NONE; + } else { + newConfig->standbyScreenId = static_cast(val); + } + } + + cJSON* minutes = cJSON_GetObjectItem(standby, "minutes"); + if (cJSON_IsNumber(minutes)) { + int val = minutes->valueint; + if (val < 0) val = 0; + newConfig->standbyMinutes = static_cast(val); + } + } + + if (newConfig->screenCount == 0) { cJSON_Delete(root); return false; } - config_.widgetCount = 0; - - cJSON* widget = nullptr; - cJSON_ArrayForEach(widget, widgets) { - if (config_.widgetCount >= MAX_WIDGETS) break; - - WidgetConfig& w = config_.widgets[config_.widgetCount]; - memset(&w, 0, sizeof(w)); - - // Parse basic properties - cJSON* id = cJSON_GetObjectItem(widget, "id"); - if (cJSON_IsNumber(id)) w.id = id->valueint; - - cJSON* type = cJSON_GetObjectItem(widget, "type"); - if (cJSON_IsNumber(type)) w.type = static_cast(type->valueint); - - cJSON* x = cJSON_GetObjectItem(widget, "x"); - if (cJSON_IsNumber(x)) w.x = x->valueint; - - cJSON* y = cJSON_GetObjectItem(widget, "y"); - if (cJSON_IsNumber(y)) w.y = y->valueint; - - cJSON* width = cJSON_GetObjectItem(widget, "w"); - if (cJSON_IsNumber(width)) w.width = width->valueint; - - cJSON* height = cJSON_GetObjectItem(widget, "h"); - if (cJSON_IsNumber(height)) w.height = height->valueint; - - cJSON* visible = cJSON_GetObjectItem(widget, "visible"); - if (cJSON_IsBool(visible)) w.visible = cJSON_IsTrue(visible); - - cJSON* textSrc = cJSON_GetObjectItem(widget, "textSrc"); - if (cJSON_IsNumber(textSrc)) w.textSource = static_cast(textSrc->valueint); - - cJSON* text = cJSON_GetObjectItem(widget, "text"); - if (cJSON_IsString(text)) { - strncpy(w.text, text->valuestring, MAX_TEXT_LEN - 1); - w.text[MAX_TEXT_LEN - 1] = '\0'; - } - - cJSON* knxAddr = cJSON_GetObjectItem(widget, "knxAddr"); - if (cJSON_IsNumber(knxAddr)) w.knxAddress = knxAddr->valueint; - - cJSON* fontSize = cJSON_GetObjectItem(widget, "fontSize"); - if (cJSON_IsNumber(fontSize)) w.fontSize = fontSize->valueint; - - // Parse colors - cJSON* textColor = cJSON_GetObjectItem(widget, "textColor"); - if (cJSON_IsString(textColor)) { - w.textColor = Color::fromHex(parseHexColor(textColor->valuestring)); - } - - cJSON* widgetBgColor = cJSON_GetObjectItem(widget, "bgColor"); - if (cJSON_IsString(widgetBgColor)) { - w.bgColor = Color::fromHex(parseHexColor(widgetBgColor->valuestring)); - } - - cJSON* bgOpacity = cJSON_GetObjectItem(widget, "bgOpacity"); - if (cJSON_IsNumber(bgOpacity)) w.bgOpacity = bgOpacity->valueint; - - cJSON* radius = cJSON_GetObjectItem(widget, "radius"); - if (cJSON_IsNumber(radius)) w.borderRadius = radius->valueint; - - // Parse shadow - cJSON* shadow = cJSON_GetObjectItem(widget, "shadow"); - if (cJSON_IsObject(shadow)) { - cJSON* enabled = cJSON_GetObjectItem(shadow, "enabled"); - if (cJSON_IsBool(enabled)) w.shadow.enabled = cJSON_IsTrue(enabled); - - cJSON* sx = cJSON_GetObjectItem(shadow, "x"); - if (cJSON_IsNumber(sx)) w.shadow.offsetX = sx->valueint; - - cJSON* sy = cJSON_GetObjectItem(shadow, "y"); - if (cJSON_IsNumber(sy)) w.shadow.offsetY = sy->valueint; - - cJSON* blur = cJSON_GetObjectItem(shadow, "blur"); - if (cJSON_IsNumber(blur)) w.shadow.blur = blur->valueint; - - cJSON* spread = cJSON_GetObjectItem(shadow, "spread"); - if (cJSON_IsNumber(spread)) w.shadow.spread = spread->valueint; - - cJSON* shadowColor = cJSON_GetObjectItem(shadow, "color"); - if (cJSON_IsString(shadowColor)) { - w.shadow.color = Color::fromHex(parseHexColor(shadowColor->valuestring)); - } - } - - cJSON* isToggle = cJSON_GetObjectItem(widget, "isToggle"); - if (cJSON_IsBool(isToggle)) w.isToggle = cJSON_IsTrue(isToggle); - - cJSON* knxAddrWrite = cJSON_GetObjectItem(widget, "knxAddrWrite"); - if (cJSON_IsNumber(knxAddrWrite)) w.knxAddressWrite = knxAddrWrite->valueint; - - config_.widgetCount++; + if (!newConfig->findScreen(newConfig->startScreenId)) { + newConfig->startScreenId = newConfig->screens[0].id; } + if (!newConfig->findScreen(newConfig->standbyScreenId)) { + newConfig->standbyEnabled = false; + newConfig->standbyScreenId = SCREEN_ID_NONE; + } + + config_ = *newConfig; cJSON_Delete(root); - ESP_LOGI(TAG, "Parsed %d widgets from JSON", config_.widgetCount); + ESP_LOGI(TAG, "Parsed %d screens from JSON", config_.screenCount); return true; } diff --git a/main/WidgetManager.hpp b/main/WidgetManager.hpp index 0de53bf..944d539 100644 --- a/main/WidgetManager.hpp +++ b/main/WidgetManager.hpp @@ -27,14 +27,24 @@ public: // Reset to factory defaults void resetToDefaults(); + // Periodic tasks (standby handling) + void loop(); + + // User activity (resets standby timer) + void onUserActivity(); + // KNX value update (called from KnxWorker) 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 - ScreenConfig& getConfig() { return config_; } - const ScreenConfig& getConfig() const { return config_; } + GuiConfig& getConfig() { return config_; } + const GuiConfig& getConfig() const { return config_; } private: WidgetManager(); @@ -45,18 +55,38 @@ private: void loadFromSdCard(); void saveToSdCard(); void destroyAllWidgets(); - void createAllWidgets(); - lv_obj_t* createWidget(const WidgetConfig& cfg); + void createAllWidgets(const ScreenConfig& screen, lv_obj_t* parent); + lv_obj_t* createWidget(const WidgetConfig& cfg, lv_obj_t* parent); void applyStyle(lv_obj_t* obj, const WidgetConfig& cfg); + void applyLedStyle(lv_obj_t* obj, const WidgetConfig& cfg); const lv_font_t* getFontBySize(uint8_t sizeIndex); 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"; - ScreenConfig config_; + 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 lastActivityUs_ = 0; // Runtime widget references (indexed by widget ID) std::array widgetObjects_; lv_obj_t* screen_ = nullptr; + lv_obj_t* modalContainer_ = nullptr; }; diff --git a/main/main.cpp b/main/main.cpp index ba2c0ba..3e9a689 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -7,6 +7,7 @@ #include "Display.hpp" #include "Touch.hpp" #include "Gui.hpp" +#include "WidgetManager.hpp" #include "Nvs.hpp" #include "KnxWorker.hpp" #include "Wifi.hpp" @@ -72,6 +73,7 @@ public: while (true) { vTaskDelay(pdMS_TO_TICKS(10)); knxWorker.loop(); + WidgetManager::instance().loop(); } } diff --git a/main/webserver/ConfigHandlers.cpp b/main/webserver/ConfigHandlers.cpp index c9657b4..40ead9e 100644 --- a/main/webserver/ConfigHandlers.cpp +++ b/main/webserver/ConfigHandlers.cpp @@ -6,13 +6,13 @@ static const char* TAG = "WebServer"; esp_err_t WebServer::getConfigHandler(httpd_req_t* req) { - char* buf = new char[8192]; + char* buf = new char[32768]; if (buf == nullptr) { httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Out of memory"); return ESP_FAIL; } - WidgetManager::instance().getConfigJson(buf, 8192); + WidgetManager::instance().getConfigJson(buf, 32768); httpd_resp_set_type(req, "application/json"); httpd_resp_send(req, buf, strlen(buf)); @@ -23,7 +23,7 @@ esp_err_t WebServer::getConfigHandler(httpd_req_t* req) { esp_err_t WebServer::postConfigHandler(httpd_req_t* req) { int total_len = req->content_len; - if (total_len > 8192) { + if (total_len > 32768) { httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Content too large"); return ESP_FAIL; } diff --git a/sdcard_content/webseite/index.html b/sdcard_content/webseite/index.html index d8dd0cd..fafdc27 100644 --- a/sdcard_content/webseite/index.html +++ b/sdcard_content/webseite/index.html @@ -3,252 +3,751 @@ - GUI Editor - KNX Display + GUI Designer - KNX Display -
-

GUI Editor

- KNX Display Konfiguration -
- - - -
-
- -
-