Fixes
This commit is contained in:
parent
a4cefd7c09
commit
df29f89977
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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
|
||||
|
||||
19
main/Gui.cpp
19
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();
|
||||
}
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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");
|
||||
}
|
||||
|
||||
@ -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<Touch*>(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;
|
||||
}
|
||||
|
||||
@ -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<int>(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<int>(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<int>(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
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
#include "WidgetConfig.hpp"
|
||||
#include "widgets/Widget.hpp"
|
||||
#include "lvgl.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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: '*'
|
||||
|
||||
@ -17,6 +17,14 @@
|
||||
|
||||
#define TAG "App"
|
||||
|
||||
static void knx_task(void* arg) {
|
||||
KnxWorker* worker = static_cast<KnxWorker*>(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)
|
||||
|
||||
@ -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<ButtonWidget*>(lv_event_get_user_data(e));
|
||||
if (!widget) return;
|
||||
lv_obj_t* target = static_cast<lv_obj_t*>(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_;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user