diff --git a/.cache/clangd/index/EthSetting.cpp.4B55389D58A8AEB7.idx b/.cache/clangd/index/EthSetting.cpp.4B55389D58A8AEB7.idx index d99c21a..b04cf21 100644 Binary files a/.cache/clangd/index/EthSetting.cpp.4B55389D58A8AEB7.idx and b/.cache/clangd/index/EthSetting.cpp.4B55389D58A8AEB7.idx differ diff --git a/.cache/clangd/index/Gui.cpp.BDF53DB313293DD9.idx b/.cache/clangd/index/Gui.cpp.BDF53DB313293DD9.idx index ec388b8..39a484e 100644 Binary files a/.cache/clangd/index/Gui.cpp.BDF53DB313293DD9.idx and b/.cache/clangd/index/Gui.cpp.BDF53DB313293DD9.idx differ diff --git a/.cache/clangd/index/WidgetConfig.cpp.FD56F9F36C29A5DA.idx b/.cache/clangd/index/WidgetConfig.cpp.FD56F9F36C29A5DA.idx index d246069..2637e0a 100644 Binary files a/.cache/clangd/index/WidgetConfig.cpp.FD56F9F36C29A5DA.idx and b/.cache/clangd/index/WidgetConfig.cpp.FD56F9F36C29A5DA.idx differ diff --git a/.cache/clangd/index/WidgetManager.cpp.D8CE609DC911F13E.idx b/.cache/clangd/index/WidgetManager.cpp.D8CE609DC911F13E.idx index b204108..51c5a03 100644 Binary files a/.cache/clangd/index/WidgetManager.cpp.D8CE609DC911F13E.idx and b/.cache/clangd/index/WidgetManager.cpp.D8CE609DC911F13E.idx differ diff --git a/.cache/clangd/index/WidgetManager.hpp.8559171B7B8A010C.idx b/.cache/clangd/index/WidgetManager.hpp.8559171B7B8A010C.idx index c2e8389..bc686a7 100644 Binary files a/.cache/clangd/index/WidgetManager.hpp.8559171B7B8A010C.idx and b/.cache/clangd/index/WidgetManager.hpp.8559171B7B8A010C.idx differ diff --git a/.cache/clangd/index/main.cpp.7C677863E2582AB3.idx b/.cache/clangd/index/main.cpp.7C677863E2582AB3.idx index 7dcb366..f919c2c 100644 Binary files a/.cache/clangd/index/main.cpp.7C677863E2582AB3.idx and b/.cache/clangd/index/main.cpp.7C677863E2582AB3.idx differ diff --git a/dependencies.lock b/dependencies.lock index 9d3cef8..9b88a30 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -42,7 +42,7 @@ dependencies: version: 1.1.4 espressif/esp_hosted: component_hash: - b6422e810fe97acd87ac184f7da1653e69e885fd209e13c5b1ae20c5787d914d + 49424510d8cf3659aa4bcf787e7b4abbf11848a25e7e5f133cf7f3324860d066 dependencies: - name: idf require: private @@ -50,7 +50,7 @@ dependencies: source: registry_url: https://components.espressif.com type: service - version: 2.11.1 + version: 2.11.3 espressif/esp_lcd_touch: component_hash: 3f85a7d95af876f1a6ecca8eb90a81614890d0f03a038390804e5a77e2caf862 @@ -377,7 +377,7 @@ dependencies: 17e68bfd21f0edf4c3ee838e2273da840bf3930e5dbc3bfa6c1190c3aed41f9f dependencies: [] source: - registry_url: https://components.espressif.com/ + registry_url: https://components.espressif.com type: service version: 9.4.0 waveshare/esp_lcd_jd9365_10_1: @@ -408,8 +408,7 @@ direct_dependencies: - espressif/esp_tinyusb - espressif/esp_wifi_remote - idf -- lvgl/lvgl - waveshare/esp_lcd_jd9365_10_1 -manifest_hash: 77a45d81439c2b8bff313cb08784154731b4451f54eecd31bcd4c2fc83b0f096 +manifest_hash: 4224a9627ef40f4e86cf66ea3d5037d4b4dbfca46c368078b42e6988300c7f2f target: esp32p4 version: 2.0.0 diff --git a/main/Gui.cpp b/main/Gui.cpp index 57e3afe..c9c86ab 100644 --- a/main/Gui.cpp +++ b/main/Gui.cpp @@ -1,5 +1,6 @@ #include "Gui.hpp" #include "esp_lv_adapter.h" +#include "esp_log.h" #include "Gui/WifiSetting.hpp" #include "Gui/EthSetting.hpp" #include "WidgetManager.hpp" @@ -14,15 +15,27 @@ static void screen_long_press_handler(lv_event_t * e) } } +static void widget_manager_timer_cb(lv_timer_t* timer) +{ + (void)timer; + // Debug: Log every 100th call to verify timer is running + static uint32_t callCount = 0; + if (++callCount % 100 == 0) { + ESP_LOGI("Gui", "Timer tick %lu", callCount); + } + WidgetManager::instance().loop(); +} + void Gui::create() { // Initialize WidgetManager (loads config from SD card) WidgetManager::instance().init(); if (esp_lv_adapter_lock(-1) == ESP_OK) { - // Add long press handler for settings - lv_obj_add_event_cb(lv_scr_act(), screen_long_press_handler, - LV_EVENT_LONG_PRESSED, NULL); + // TEMP: Disabled long press handler for testing + // lv_obj_add_event_cb(lv_scr_act(), screen_long_press_handler, + // LV_EVENT_LONG_PRESSED, NULL); + lv_timer_create(widget_manager_timer_cb, 10, nullptr); esp_lv_adapter_unlock(); } diff --git a/main/Gui/EthSetting.cpp b/main/Gui/EthSetting.cpp index 504cb3c..fd1d650 100644 --- a/main/Gui/EthSetting.cpp +++ b/main/Gui/EthSetting.cpp @@ -20,6 +20,7 @@ void EthSetting::show() { if (esp_lv_adapter_lock(-1) == ESP_OK) { createUI(); visible_ = true; + esp_lv_adapter_unlock(); } } @@ -64,7 +65,7 @@ void EthSetting::createUI() { lv_obj_set_size(closeBtn, 40, 40); lv_obj_align(closeBtn, LV_ALIGN_LEFT_MID, 0, 0); lv_obj_set_style_bg_color(closeBtn, lv_color_hex(0x804040), 0); - lv_obj_add_event_cb(closeBtn, onCloseClick, LV_EVENT_CLICKED, nullptr); + //lv_obj_add_event_cb(closeBtn, onCloseClick, LV_EVENT_CLICKED, nullptr); lv_obj_t* closeLbl = lv_label_create(closeBtn); lv_label_set_text(closeLbl, "X"); diff --git a/main/Gui/WifiSetting.cpp b/main/Gui/WifiSetting.cpp index a5d44e2..9d9c890 100644 --- a/main/Gui/WifiSetting.cpp +++ b/main/Gui/WifiSetting.cpp @@ -86,7 +86,7 @@ void WifiSetting::createUI() { lv_obj_set_size(closeBtn, 40, 40); lv_obj_align(closeBtn, LV_ALIGN_LEFT_MID, 0, 0); lv_obj_set_style_bg_color(closeBtn, lv_color_hex(0x804040), 0); - lv_obj_add_event_cb(closeBtn, onCloseClick, LV_EVENT_CLICKED, nullptr); + //lv_obj_add_event_cb(closeBtn, onCloseClick, LV_EVENT_CLICKED, nullptr); lv_obj_t* closeLbl = lv_label_create(closeBtn); lv_label_set_text(closeLbl, "X"); @@ -111,7 +111,7 @@ void WifiSetting::createUI() { disconnectBtn_ = lv_btn_create(statusSection); lv_obj_set_size(disconnectBtn_, 100, 40); lv_obj_align(disconnectBtn_, LV_ALIGN_RIGHT_MID, 0, 0); - lv_obj_add_event_cb(disconnectBtn_, onDisconnectClick, LV_EVENT_CLICKED, nullptr); + //lv_obj_add_event_cb(disconnectBtn_, onDisconnectClick, LV_EVENT_CLICKED, nullptr); lv_obj_add_flag(disconnectBtn_, LV_OBJ_FLAG_HIDDEN); lv_obj_t* disconnectLbl = lv_label_create(disconnectBtn_); @@ -140,7 +140,7 @@ void WifiSetting::createUI() { lv_obj_t* scanBtn = lv_btn_create(availableHeader); lv_obj_set_size(scanBtn, 100, 35); lv_obj_align(scanBtn, LV_ALIGN_RIGHT_MID, 0, 0); - lv_obj_add_event_cb(scanBtn, onScanClick, LV_EVENT_CLICKED, nullptr); + //lv_obj_add_event_cb(scanBtn, onScanClick, LV_EVENT_CLICKED, nullptr); lv_obj_t* scanLbl = lv_label_create(scanBtn); lv_label_set_text(scanLbl, "Scannen"); @@ -198,7 +198,7 @@ void WifiSetting::refreshNetworkList() { (const char*)ap.ssid, barSymbols[bars - 1], ap.rssi); lv_obj_t* btn = lv_list_add_btn(settings.networkList_, LV_SYMBOL_WIFI, signalStr); - lv_obj_add_event_cb(btn, onNetworkSelect, LV_EVENT_CLICKED, nullptr); + //lv_obj_add_event_cb(btn, onNetworkSelect, LV_EVENT_CLICKED, nullptr); size_t ssidLen = strlen((const char*)ap.ssid); char* ssidCopy = (char*)lv_malloc(ssidLen + 1); @@ -237,7 +237,7 @@ void WifiSetting::refreshSavedNetworks() { lv_obj_set_size(deleteBtn, 80, 30); lv_obj_align(deleteBtn, LV_ALIGN_RIGHT_MID, 0, 0); lv_obj_set_style_bg_color(deleteBtn, lv_color_hex(0x804040), 0); - lv_obj_add_event_cb(deleteBtn, onDeleteSavedClick, LV_EVENT_CLICKED, nullptr); + //lv_obj_add_event_cb(deleteBtn, onDeleteSavedClick, LV_EVENT_CLICKED, nullptr); char* ssidCopy = (char*)lv_malloc(ssid.size() + 1); strcpy(ssidCopy, ssid.c_str()); @@ -312,7 +312,7 @@ void WifiSetting::createPasswordDialogUI() { lv_obj_set_size(cancelBtn, 150, 50); lv_obj_align(cancelBtn, LV_ALIGN_TOP_LEFT, 20, 150); lv_obj_set_style_bg_color(cancelBtn, lv_color_hex(0x606060), 0); - lv_obj_add_event_cb(cancelBtn, onCancelClick, LV_EVENT_CLICKED, nullptr); + //lv_obj_add_event_cb(cancelBtn, onCancelClick, LV_EVENT_CLICKED, nullptr); lv_obj_t* cancelLbl = lv_label_create(cancelBtn); lv_label_set_text(cancelLbl, "Abbrechen"); @@ -322,7 +322,7 @@ void WifiSetting::createPasswordDialogUI() { lv_obj_set_size(connectBtn, 150, 50); lv_obj_align(connectBtn, LV_ALIGN_TOP_RIGHT, -20, 150); lv_obj_set_style_bg_color(connectBtn, lv_color_hex(0x408040), 0); - lv_obj_add_event_cb(connectBtn, onConnectClick, LV_EVENT_CLICKED, nullptr); + //lv_obj_add_event_cb(connectBtn, onConnectClick, LV_EVENT_CLICKED, nullptr); lv_obj_t* connectLbl = lv_label_create(connectBtn); lv_label_set_text(connectLbl, "Verbinden"); @@ -332,7 +332,7 @@ void WifiSetting::createPasswordDialogUI() { lv_obj_set_size(keyboard_, LV_PCT(100), 300); lv_obj_align(keyboard_, LV_ALIGN_BOTTOM_MID, 0, 0); lv_keyboard_set_textarea(keyboard_, passwordInput_); - lv_obj_add_event_cb(keyboard_, onKeyboardReady, LV_EVENT_READY, nullptr); + //lv_obj_add_event_cb(keyboard_, onKeyboardReady, LV_EVENT_READY, nullptr); ESP_LOGI(TAG, "Password dialog created"); } diff --git a/main/Touch.cpp b/main/Touch.cpp index ef5836d..9d86415 100644 --- a/main/Touch.cpp +++ b/main/Touch.cpp @@ -62,6 +62,7 @@ esp_lcd_touch_handle_t Touch::getTouchHandle() const { void Touch::lv_indev_read_cb(lv_indev_t *indev, lv_indev_data_t *data) { + Touch* self = static_cast(lv_indev_get_user_data(indev)); if (!self) { data->state = LV_INDEV_STATE_RELEASED; @@ -76,7 +77,8 @@ 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(); + // TEMP: Disabled to test if this causes the freeze + // WidgetManager::instance().onUserActivity(); } else { data->state = LV_INDEV_STATE_RELEASED; } diff --git a/main/WidgetManager.cpp b/main/WidgetManager.cpp index 5610379..4ce50a8 100644 --- a/main/WidgetManager.cpp +++ b/main/WidgetManager.cpp @@ -22,6 +22,10 @@ WidgetManager& WidgetManager::instance() { WidgetManager::WidgetManager() { // widgets_ is default-initialized to nullptr + uiQueue_ = xQueueCreate(UI_EVENT_QUEUE_LEN, sizeof(UiEvent)); + if (!uiQueue_) { + ESP_LOGE(TAG, "Failed to create UI event queue"); + } createDefaultConfig(); activeScreenId_ = config_.startScreenId; lastActivityUs_ = esp_timer_get_time(); @@ -182,6 +186,8 @@ const ScreenConfig* WidgetManager::activeScreen() const { } void WidgetManager::applyScreen(uint8_t screenId) { + ESP_LOGI(TAG, "applyScreen(%d) - start", screenId); + ScreenConfig* screen = config_.findScreen(screenId); if (!screen) { ESP_LOGW(TAG, "Screen %d not found", screenId); @@ -189,17 +195,40 @@ void WidgetManager::applyScreen(uint8_t screenId) { } if (modalContainer_) { + ESP_LOGI(TAG, "Closing modal first"); closeModal(); } - // First destroy C++ widgets (which deletes LVGL objects) - destroyAllWidgets(); - - if (esp_lv_adapter_lock(-1) == ESP_OK) { - lv_obj_t* root = lv_scr_act(); - createAllWidgets(*screen, root); - esp_lv_adapter_unlock(); + ESP_LOGI(TAG, "Acquiring LVGL lock..."); + if (esp_lv_adapter_lock(-1) != ESP_OK) { + ESP_LOGE(TAG, "Failed to acquire LVGL lock!"); + return; } + ESP_LOGI(TAG, "LVGL lock acquired"); + + // Reset all input devices BEFORE destroying widgets to clear any + // pending input state and prevent use-after-free on widget objects + ESP_LOGI(TAG, "Resetting input devices..."); + lv_indev_t* indev = lv_indev_get_next(nullptr); + while (indev) { + lv_indev_reset(indev, nullptr); + indev = lv_indev_get_next(indev); + } + ESP_LOGI(TAG, "Input devices reset"); + + // Now destroy C++ widgets (which deletes LVGL objects) under LVGL lock + ESP_LOGI(TAG, "Destroying widgets..."); + destroyAllWidgets(); + ESP_LOGI(TAG, "Widgets destroyed"); + + ESP_LOGI(TAG, "Creating new widgets for screen '%s' (%d widgets)...", + screen->name, screen->widgetCount); + lv_obj_t* root = lv_scr_act(); + createAllWidgets(*screen, root); + ESP_LOGI(TAG, "Widgets created"); + + esp_lv_adapter_unlock(); + ESP_LOGI(TAG, "applyScreen(%d) - complete", screenId); } void WidgetManager::showModalScreen(const ScreenConfig& screen) { @@ -207,11 +236,18 @@ void WidgetManager::showModalScreen(const ScreenConfig& screen) { closeModal(); } + if (esp_lv_adapter_lock(-1) != ESP_OK) return; + + // Reset all input devices BEFORE destroying widgets + lv_indev_t* indev = lv_indev_get_next(nullptr); + while (indev) { + lv_indev_reset(indev, nullptr); + indev = lv_indev_get_next(indev); + } + // Destroy any existing widgets before creating modal widgets destroyAllWidgets(); - if (esp_lv_adapter_lock(-1) != ESP_OK) return; - lv_disp_t* disp = lv_disp_get_default(); int32_t dispWidth = disp ? lv_disp_get_hor_res(disp) : 1280; int32_t dispHeight = disp ? lv_disp_get_ver_res(disp) : 800; @@ -299,28 +335,40 @@ void WidgetManager::showModalScreen(const ScreenConfig& screen) { void WidgetManager::closeModal() { if (!modalContainer_) return; + if (esp_lv_adapter_lock(-1) != ESP_OK) return; + + // Reset all input devices BEFORE destroying widgets + lv_indev_t* indev = lv_indev_get_next(nullptr); + while (indev) { + lv_indev_reset(indev, nullptr); + indev = lv_indev_get_next(indev); + } + // First destroy C++ widgets (which deletes their LVGL objects) destroyAllWidgets(); - if (esp_lv_adapter_lock(-1) == ESP_OK) { - if (modalDimmer_) { - lv_obj_delete(modalDimmer_); - } - lv_obj_delete(modalContainer_); - esp_lv_adapter_unlock(); + if (modalDimmer_) { + lv_obj_delete(modalDimmer_); } + lv_obj_delete(modalContainer_); + esp_lv_adapter_unlock(); modalContainer_ = nullptr; modalDimmer_ = nullptr; modalScreenId_ = SCREEN_ID_NONE; } void WidgetManager::showScreen(uint8_t screenId) { + ESP_LOGI(TAG, "showScreen(%d) called", screenId); + ScreenConfig* screen = config_.findScreen(screenId); if (!screen) { ESP_LOGW(TAG, "Screen %d not found", screenId); return; } + ESP_LOGI(TAG, "Found screen '%s', mode=%d", screen->name, + static_cast(screen->mode)); + if (screen->mode == ScreenMode::MODAL) { showModalScreen(*screen); return; @@ -335,18 +383,25 @@ void WidgetManager::showScreen(uint8_t screenId) { void WidgetManager::handleButtonAction(const WidgetConfig& cfg, lv_obj_t* target) { if (cfg.type != WidgetType::BUTTON) return; + ESP_LOGI(TAG, "handleButtonAction: button=%d action=%d targetScreen=%d", + cfg.id, static_cast(cfg.action), cfg.targetScreen); + onUserActivity(); switch (cfg.action) { case ButtonAction::JUMP: - navPending_ = true; + ESP_LOGI(TAG, "JUMP action: scheduling navigation to screen %d", cfg.targetScreen); navAction_ = ButtonAction::JUMP; navTargetScreen_ = cfg.targetScreen; + navPending_ = true; + navRequestUs_ = esp_timer_get_time(); break; case ButtonAction::BACK: - navPending_ = true; + ESP_LOGI(TAG, "BACK action: scheduling navigation back"); navAction_ = ButtonAction::BACK; navTargetScreen_ = SCREEN_ID_NONE; + navPending_ = true; + navRequestUs_ = esp_timer_get_time(); break; case ButtonAction::KNX: default: { @@ -393,14 +448,22 @@ void WidgetManager::enterStandby() { } void WidgetManager::loop() { + bool didUiNav = false; if (navPending_) { - navPending_ = false; - if (navAction_ == ButtonAction::JUMP) { - showScreen(navTargetScreen_); - } else if (navAction_ == ButtonAction::BACK) { - goBack(); + int64_t now = esp_timer_get_time(); + // Increased delay to ensure touch events are fully processed + if (now - navRequestUs_ >= NAV_DELAY_US) { + navPending_ = false; + ESP_LOGI(TAG, "Executing navigation: action=%d target=%d", + static_cast(navAction_), navTargetScreen_); + if (navAction_ == ButtonAction::JUMP) { + showScreen(navTargetScreen_); + } else if (navAction_ == ButtonAction::BACK) { + goBack(); + } + didUiNav = true; + ESP_LOGI(TAG, "Navigation complete"); } - return; } if (standbyWakePending_) { @@ -409,9 +472,13 @@ void WidgetManager::loop() { activeScreenId_ = standbyWakeTarget_; applyScreen(activeScreenId_); } - return; + didUiNav = true; } + processUiQueue(); + + if (didUiNav) return; + if (!config_.standbyEnabled || config_.standbyMinutes == 0) return; if (standbyActive_) return; if (config_.standbyScreenId == SCREEN_ID_NONE) return; @@ -464,39 +531,88 @@ void WidgetManager::createAllWidgets(const ScreenConfig& screen, lv_obj_t* paren } void WidgetManager::onKnxValue(uint16_t groupAddr, float value) { - if (esp_lv_adapter_lock(100) != ESP_OK) return; + UiEvent event = {}; + event.type = UiEventType::KNX_VALUE; + event.groupAddr = groupAddr; + event.value = value; + enqueueUiEvent(event); +} +void WidgetManager::onKnxSwitch(uint16_t groupAddr, bool value) { + UiEvent event = {}; + event.type = UiEventType::KNX_SWITCH; + event.groupAddr = groupAddr; + event.state = value; + enqueueUiEvent(event); +} + +void WidgetManager::onKnxText(uint16_t groupAddr, const char* text) { + UiEvent event = {}; + event.type = UiEventType::KNX_TEXT; + event.groupAddr = groupAddr; + if (text) { + strncpy(event.text, text, sizeof(event.text) - 1); + } else { + event.text[0] = '\0'; + } + enqueueUiEvent(event); +} + +bool WidgetManager::enqueueUiEvent(const UiEvent& event) { + if (!uiQueue_) return false; + return xQueueSend(uiQueue_, &event, 0) == pdTRUE; +} + +void WidgetManager::processUiQueue() { + if (!uiQueue_) return; + if (uxQueueMessagesWaiting(uiQueue_) == 0) return; + + if (esp_lv_adapter_lock(-1) != ESP_OK) return; + + UiEvent event = {}; + static constexpr size_t kMaxEventsPerLoop = 8; + size_t processed = 0; + while (processed < kMaxEventsPerLoop && + xQueueReceive(uiQueue_, &event, 0) == pdTRUE) { + switch (event.type) { + case UiEventType::KNX_VALUE: + applyKnxValue(event.groupAddr, event.value); + break; + case UiEventType::KNX_SWITCH: + applyKnxSwitch(event.groupAddr, event.state); + break; + case UiEventType::KNX_TEXT: + applyKnxText(event.groupAddr, event.text); + break; + } + processed++; + } + + esp_lv_adapter_unlock(); +} + +void WidgetManager::applyKnxValue(uint16_t groupAddr, float value) { for (auto& widget : widgets_) { if (widget && widget->getKnxAddress() == groupAddr) { widget->onKnxValue(value); } } - - esp_lv_adapter_unlock(); } -void WidgetManager::onKnxSwitch(uint16_t groupAddr, bool value) { - if (esp_lv_adapter_lock(100) != ESP_OK) return; - +void WidgetManager::applyKnxSwitch(uint16_t groupAddr, bool value) { for (auto& widget : widgets_) { if (widget && widget->getKnxAddress() == groupAddr) { widget->onKnxSwitch(value); } } - - esp_lv_adapter_unlock(); } -void WidgetManager::onKnxText(uint16_t groupAddr, const char* text) { - if (esp_lv_adapter_lock(100) != ESP_OK) return; - +void WidgetManager::applyKnxText(uint16_t groupAddr, const char* text) { for (auto& widget : widgets_) { if (widget && widget->getKnxAddress() == groupAddr) { widget->onKnxText(text); } } - - esp_lv_adapter_unlock(); } // Helper function to parse hex color string diff --git a/main/WidgetManager.hpp b/main/WidgetManager.hpp index 278117b..c30f8e7 100644 --- a/main/WidgetManager.hpp +++ b/main/WidgetManager.hpp @@ -3,6 +3,8 @@ #include "WidgetConfig.hpp" #include "widgets/Widget.hpp" #include "lvgl.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" #include #include @@ -35,7 +37,7 @@ public: // User activity (resets standby timer) void onUserActivity(); - // KNX value update (called from KnxWorker) + // 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); @@ -54,10 +56,32 @@ private: 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); @@ -69,6 +93,7 @@ private: 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; @@ -81,6 +106,7 @@ private: 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) @@ -88,4 +114,5 @@ private: lv_obj_t* screen_ = nullptr; lv_obj_t* modalContainer_ = nullptr; lv_obj_t* modalDimmer_ = nullptr; + QueueHandle_t uiQueue_ = nullptr; }; diff --git a/main/idf_component.yml b/main/idf_component.yml index ac6a67f..1f26b62 100644 --- a/main/idf_component.yml +++ b/main/idf_component.yml @@ -15,8 +15,7 @@ dependencies: # # All dependencies of `main` are public by default. # public: true waveshare/esp_lcd_jd9365_10_1: '*' - lvgl/lvgl: ^9.4.0 - espressif/esp_lvgl_port: ^2.3.0 + espressif/esp_lvgl_port: ^2.7.0 espressif/esp_lcd_touch_gt911: '*' espressif/esp_lvgl_adapter: '*' espressif/esp_wifi_remote: '*' diff --git a/main/main.cpp b/main/main.cpp index 3e9a689..b6641c2 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -17,6 +17,14 @@ #define TAG "App" +static void knx_task(void* arg) { + KnxWorker* worker = static_cast(arg); + worker->init(); + while (true) { + worker->loop(); + vTaskDelay(pdMS_TO_TICKS(10)); + } +} // This is a simple wrapper for the application logic class Application { @@ -63,7 +71,11 @@ public: } ESP_LOGI(TAG, "Creating UI"); - knxWorker.init(); + BaseType_t knx_ok = xTaskCreatePinnedToCore( + knx_task, "knx", 4096, &Gui::knxWorker, 5, nullptr, 1); + if (knx_ok != pdPASS) { + ESP_LOGE(TAG, "Failed to start KNX task"); + } gui.create(); // Start WebServer for widget configuration @@ -72,8 +84,6 @@ public: ESP_LOGI(TAG, "Application running"); while (true) { vTaskDelay(pdMS_TO_TICKS(10)); - knxWorker.loop(); - WidgetManager::instance().loop(); } } @@ -82,7 +92,6 @@ private: Touch touch; Gui gui; Nvs nvs; - KnxWorker knxWorker; }; extern "C" void app_main(void) diff --git a/main/widgets/ButtonWidget.cpp b/main/widgets/ButtonWidget.cpp index a1bbdcb..a63ab4a 100644 --- a/main/widgets/ButtonWidget.cpp +++ b/main/widgets/ButtonWidget.cpp @@ -1,5 +1,13 @@ #include "ButtonWidget.hpp" #include "../WidgetManager.hpp" +#include "esp_log.h" + +static const char* TAG = "ButtonWidget"; + +// Free function instead of static member +static void button_event_cb(lv_event_t* e) { + ESP_LOGI(TAG, "button_event_cb called"); +} ButtonWidget::ButtonWidget(const WidgetConfig& config) : Widget(config) @@ -7,32 +15,33 @@ ButtonWidget::ButtonWidget(const WidgetConfig& config) { } +ButtonWidget::~ButtonWidget() { + // Remove event callback BEFORE the base class destructor deletes the object + if (obj_) { + lv_obj_remove_event_cb(obj_, button_event_cb); + } + label_ = nullptr; +} + void ButtonWidget::clickCallback(lv_event_t* e) { - ButtonWidget* widget = static_cast(lv_event_get_user_data(e)); - if (!widget) return; - lv_obj_t* target = static_cast(lv_event_get_target(e)); - WidgetManager::instance().handleButtonAction(widget->getConfig(), target); + // Not used currently + (void)e; } lv_obj_t* ButtonWidget::create(lv_obj_t* parent) { obj_ = lv_btn_create(parent); + lv_obj_set_pos(obj_, config_.x, config_.y); + lv_obj_set_size(obj_, config_.width > 0 ? config_.width : 100, + config_.height > 0 ? config_.height : 50); - if (config_.isToggle) { - lv_obj_add_flag(obj_, LV_OBJ_FLAG_CHECKABLE); - } + // Test: free function callback + lv_obj_add_event_cb(obj_, button_event_cb, LV_EVENT_CLICKED, NULL); - lv_obj_add_event_cb(obj_, clickCallback, LV_EVENT_CLICKED, this); - - // Create label inside button label_ = lv_label_create(obj_); lv_label_set_text(label_, config_.text); lv_obj_center(label_); - lv_obj_set_pos(obj_, config_.x, config_.y); - if (config_.width > 0 && config_.height > 0) { - lv_obj_set_size(obj_, config_.width, config_.height); - } - + ESP_LOGI(TAG, "Created button '%s' at %d,%d", config_.text, config_.x, config_.y); return obj_; } diff --git a/main/widgets/ButtonWidget.hpp b/main/widgets/ButtonWidget.hpp index 638a444..6f8b322 100644 --- a/main/widgets/ButtonWidget.hpp +++ b/main/widgets/ButtonWidget.hpp @@ -5,6 +5,7 @@ class ButtonWidget : public Widget { public: explicit ButtonWidget(const WidgetConfig& config); + ~ButtonWidget() override; lv_obj_t* create(lv_obj_t* parent) override; void applyStyle() override; diff --git a/sdcard_content/webseite/index.html b/sdcard_content/webseite/index.html index fafdc27..cd00114 100644 --- a/sdcard_content/webseite/index.html +++ b/sdcard_content/webseite/index.html @@ -908,6 +908,18 @@ if (!screen.bgColor) screen.bgColor = '#1A1A2E'; if (!Array.isArray(screen.widgets)) screen.widgets = []; + // Modal defaults + if (!screen.modal) { + screen.modal = { x: 0, y: 0, w: 0, h: 0, radius: 12, dim: true }; + } else { + if (screen.modal.x === undefined) screen.modal.x = 0; + if (screen.modal.y === undefined) screen.modal.y = 0; + if (screen.modal.w === undefined) screen.modal.w = 0; + if (screen.modal.h === undefined) screen.modal.h = 0; + if (screen.modal.radius === undefined) screen.modal.radius = 12; + if (screen.modal.dim === undefined) screen.modal.dim = true; + } + screen.widgets.forEach(normalizeWidget); }