Backup
This commit is contained in:
parent
8e90872c75
commit
d24507263f
@ -132,6 +132,7 @@ WidgetManager& WidgetManager::instance() {
|
||||
|
||||
WidgetManager::WidgetManager() {
|
||||
// widgets_ is default-initialized to nullptr
|
||||
portMUX_INITIALIZE(&knxCacheMux_);
|
||||
uiQueue_ = xQueueCreate(UI_EVENT_QUEUE_LEN, sizeof(UiEvent));
|
||||
if (!uiQueue_) {
|
||||
ESP_LOGE(TAG, "Failed to create UI event queue");
|
||||
@ -324,6 +325,13 @@ void WidgetManager::applyScreen(uint8_t screenId) {
|
||||
}
|
||||
ESP_LOGI(TAG, "LVGL lock acquired");
|
||||
|
||||
lv_display_t* disp = lv_display_get_default();
|
||||
bool invEnabled = true;
|
||||
if (disp) {
|
||||
invEnabled = lv_display_is_invalidation_enabled(disp);
|
||||
lv_display_enable_invalidation(disp, false);
|
||||
}
|
||||
|
||||
// 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...");
|
||||
@ -337,6 +345,7 @@ void WidgetManager::applyScreen(uint8_t screenId) {
|
||||
// Now destroy C++ widgets (which deletes LVGL objects) under LVGL lock
|
||||
ESP_LOGI(TAG, "Destroying widgets...");
|
||||
destroyAllWidgets();
|
||||
lv_obj_clean(lv_scr_act());
|
||||
ESP_LOGI(TAG, "Widgets destroyed");
|
||||
|
||||
ESP_LOGI(TAG, "Creating new widgets for screen '%s' (%d widgets)...",
|
||||
@ -344,6 +353,12 @@ void WidgetManager::applyScreen(uint8_t screenId) {
|
||||
lv_obj_t* root = lv_scr_act();
|
||||
createAllWidgets(*screen, root);
|
||||
ESP_LOGI(TAG, "Widgets created");
|
||||
applyCachedValuesToWidgets();
|
||||
|
||||
if (disp) {
|
||||
lv_display_enable_invalidation(disp, invEnabled);
|
||||
}
|
||||
lv_obj_invalidate(lv_scr_act());
|
||||
|
||||
esp_lv_adapter_unlock();
|
||||
ESP_LOGI(TAG, "applyScreen(%d) - complete", screenId);
|
||||
@ -448,6 +463,8 @@ void WidgetManager::showModalScreen(const ScreenConfig& screen) {
|
||||
}
|
||||
}
|
||||
|
||||
applyCachedValuesToWidgets();
|
||||
|
||||
esp_lv_adapter_unlock();
|
||||
ESP_LOGI(TAG, "Modal screen %d opened (%ldx%ld)", screen.id, modalWidth, modalHeight);
|
||||
}
|
||||
@ -751,6 +768,7 @@ void WidgetManager::onKnxValue(uint16_t groupAddr, float value, TextSource sourc
|
||||
event.groupAddr = groupAddr;
|
||||
event.textSource = source;
|
||||
event.value = value;
|
||||
cacheKnxValue(groupAddr, source, value);
|
||||
enqueueUiEvent(event);
|
||||
}
|
||||
|
||||
@ -763,6 +781,7 @@ void WidgetManager::onKnxSwitch(uint16_t groupAddr, bool value) {
|
||||
event.type = UiEventType::KNX_SWITCH;
|
||||
event.groupAddr = groupAddr;
|
||||
event.state = value;
|
||||
cacheKnxSwitch(groupAddr, value);
|
||||
enqueueUiEvent(event);
|
||||
}
|
||||
|
||||
@ -782,6 +801,7 @@ void WidgetManager::onKnxText(uint16_t groupAddr, const char* text) {
|
||||
} else {
|
||||
event.text[0] = '\0';
|
||||
}
|
||||
cacheKnxText(groupAddr, event.text);
|
||||
enqueueUiEvent(event);
|
||||
}
|
||||
|
||||
@ -818,6 +838,41 @@ void WidgetManager::processUiQueue() {
|
||||
esp_lv_adapter_unlock();
|
||||
}
|
||||
|
||||
void WidgetManager::applyCachedValuesToWidgets() {
|
||||
for (auto& widget : widgets_) {
|
||||
if (!widget) continue;
|
||||
|
||||
uint16_t addr = widget->getKnxAddress();
|
||||
if (addr == 0) continue;
|
||||
|
||||
TextSource source = widget->getTextSource();
|
||||
if (source == TextSource::STATIC) continue;
|
||||
|
||||
if (source == TextSource::KNX_DPT_SWITCH) {
|
||||
bool state = false;
|
||||
if (getCachedKnxSwitch(addr, &state)) {
|
||||
widget->onKnxSwitch(state);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (source == TextSource::KNX_DPT_TEXT) {
|
||||
char text[MAX_TEXT_LEN] = {};
|
||||
if (getCachedKnxText(addr, text, sizeof(text))) {
|
||||
widget->onKnxText(text);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isNumericTextSource(source)) {
|
||||
float value = 0.0f;
|
||||
if (getCachedKnxValue(addr, source, &value)) {
|
||||
widget->onKnxValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WidgetManager::applyKnxValue(uint16_t groupAddr, float value, TextSource source) {
|
||||
for (auto& widget : widgets_) {
|
||||
if (widget && widget->getKnxAddress() == groupAddr &&
|
||||
@ -843,6 +898,161 @@ void WidgetManager::applyKnxText(uint16_t groupAddr, const char* text) {
|
||||
}
|
||||
}
|
||||
|
||||
void WidgetManager::cacheKnxValue(uint16_t groupAddr, TextSource source, float value) {
|
||||
if (groupAddr == 0) return;
|
||||
portENTER_CRITICAL(&knxCacheMux_);
|
||||
|
||||
size_t freeIndex = KNX_CACHE_SIZE;
|
||||
for (size_t i = 0; i < KNX_CACHE_SIZE; ++i) {
|
||||
auto& entry = knxNumericCache_[i];
|
||||
if (entry.valid) {
|
||||
if (entry.groupAddr == groupAddr && entry.source == source) {
|
||||
entry.value = value;
|
||||
portEXIT_CRITICAL(&knxCacheMux_);
|
||||
return;
|
||||
}
|
||||
} else if (freeIndex == KNX_CACHE_SIZE) {
|
||||
freeIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
size_t index = freeIndex;
|
||||
if (index == KNX_CACHE_SIZE) {
|
||||
index = knxNumericCacheNext_;
|
||||
knxNumericCacheNext_ = (knxNumericCacheNext_ + 1) % KNX_CACHE_SIZE;
|
||||
}
|
||||
auto& entry = knxNumericCache_[index];
|
||||
entry.groupAddr = groupAddr;
|
||||
entry.source = source;
|
||||
entry.value = value;
|
||||
entry.valid = true;
|
||||
portEXIT_CRITICAL(&knxCacheMux_);
|
||||
}
|
||||
|
||||
void WidgetManager::cacheKnxSwitch(uint16_t groupAddr, bool value) {
|
||||
if (groupAddr == 0) return;
|
||||
portENTER_CRITICAL(&knxCacheMux_);
|
||||
|
||||
size_t freeIndex = KNX_CACHE_SIZE;
|
||||
for (size_t i = 0; i < KNX_CACHE_SIZE; ++i) {
|
||||
auto& entry = knxSwitchCache_[i];
|
||||
if (entry.valid) {
|
||||
if (entry.groupAddr == groupAddr) {
|
||||
entry.value = value;
|
||||
portEXIT_CRITICAL(&knxCacheMux_);
|
||||
return;
|
||||
}
|
||||
} else if (freeIndex == KNX_CACHE_SIZE) {
|
||||
freeIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
size_t index = freeIndex;
|
||||
if (index == KNX_CACHE_SIZE) {
|
||||
index = knxSwitchCacheNext_;
|
||||
knxSwitchCacheNext_ = (knxSwitchCacheNext_ + 1) % KNX_CACHE_SIZE;
|
||||
}
|
||||
auto& entry = knxSwitchCache_[index];
|
||||
entry.groupAddr = groupAddr;
|
||||
entry.value = value;
|
||||
entry.valid = true;
|
||||
portEXIT_CRITICAL(&knxCacheMux_);
|
||||
}
|
||||
|
||||
void WidgetManager::cacheKnxText(uint16_t groupAddr, const char* text) {
|
||||
if (groupAddr == 0) return;
|
||||
portENTER_CRITICAL(&knxCacheMux_);
|
||||
|
||||
size_t freeIndex = KNX_CACHE_SIZE;
|
||||
for (size_t i = 0; i < KNX_CACHE_SIZE; ++i) {
|
||||
auto& entry = knxTextCache_[i];
|
||||
if (entry.valid) {
|
||||
if (entry.groupAddr == groupAddr) {
|
||||
if (text) {
|
||||
strncpy(entry.text, text, MAX_TEXT_LEN - 1);
|
||||
entry.text[MAX_TEXT_LEN - 1] = '\0';
|
||||
} else {
|
||||
entry.text[0] = '\0';
|
||||
}
|
||||
portEXIT_CRITICAL(&knxCacheMux_);
|
||||
return;
|
||||
}
|
||||
} else if (freeIndex == KNX_CACHE_SIZE) {
|
||||
freeIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
size_t index = freeIndex;
|
||||
if (index == KNX_CACHE_SIZE) {
|
||||
index = knxTextCacheNext_;
|
||||
knxTextCacheNext_ = (knxTextCacheNext_ + 1) % KNX_CACHE_SIZE;
|
||||
}
|
||||
auto& entry = knxTextCache_[index];
|
||||
entry.groupAddr = groupAddr;
|
||||
if (text) {
|
||||
strncpy(entry.text, text, MAX_TEXT_LEN - 1);
|
||||
entry.text[MAX_TEXT_LEN - 1] = '\0';
|
||||
} else {
|
||||
entry.text[0] = '\0';
|
||||
}
|
||||
entry.valid = true;
|
||||
portEXIT_CRITICAL(&knxCacheMux_);
|
||||
}
|
||||
|
||||
bool WidgetManager::getCachedKnxValue(uint16_t groupAddr, TextSource source, float* out) const {
|
||||
if (groupAddr == 0 || out == nullptr) return false;
|
||||
bool found = false;
|
||||
portENTER_CRITICAL(&knxCacheMux_);
|
||||
for (const auto& entry : knxNumericCache_) {
|
||||
if (entry.valid && entry.groupAddr == groupAddr && entry.source == source) {
|
||||
*out = entry.value;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL(&knxCacheMux_);
|
||||
return found;
|
||||
}
|
||||
|
||||
bool WidgetManager::getCachedKnxSwitch(uint16_t groupAddr, bool* out) const {
|
||||
if (groupAddr == 0 || out == nullptr) return false;
|
||||
bool found = false;
|
||||
portENTER_CRITICAL(&knxCacheMux_);
|
||||
for (const auto& entry : knxSwitchCache_) {
|
||||
if (entry.valid && entry.groupAddr == groupAddr) {
|
||||
*out = entry.value;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL(&knxCacheMux_);
|
||||
return found;
|
||||
}
|
||||
|
||||
bool WidgetManager::getCachedKnxText(uint16_t groupAddr, char* out, size_t outSize) const {
|
||||
if (groupAddr == 0 || out == nullptr || outSize == 0) return false;
|
||||
bool found = false;
|
||||
portENTER_CRITICAL(&knxCacheMux_);
|
||||
for (const auto& entry : knxTextCache_) {
|
||||
if (entry.valid && entry.groupAddr == groupAddr) {
|
||||
strncpy(out, entry.text, outSize - 1);
|
||||
out[outSize - 1] = '\0';
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL(&knxCacheMux_);
|
||||
return found;
|
||||
}
|
||||
|
||||
bool WidgetManager::isNumericTextSource(TextSource source) {
|
||||
return source == TextSource::KNX_DPT_TEMP ||
|
||||
source == TextSource::KNX_DPT_PERCENT ||
|
||||
source == TextSource::KNX_DPT_POWER ||
|
||||
source == TextSource::KNX_DPT_ENERGY ||
|
||||
source == TextSource::KNX_DPT_DECIMALFACTOR;
|
||||
}
|
||||
|
||||
// Helper function to parse hex color string
|
||||
static uint32_t parseHexColor(const char* colorStr) {
|
||||
if (!colorStr || colorStr[0] != '#') return 0;
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include "lvgl.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/portmacro.h"
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
@ -76,15 +77,44 @@ private:
|
||||
char text[UI_EVENT_TEXT_LEN];
|
||||
};
|
||||
|
||||
static constexpr size_t KNX_CACHE_SIZE = MAX_WIDGETS * MAX_SCREENS;
|
||||
|
||||
struct KnxNumericCacheEntry {
|
||||
uint16_t groupAddr = 0;
|
||||
TextSource source = TextSource::STATIC;
|
||||
float value = 0.0f;
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
struct KnxSwitchCacheEntry {
|
||||
uint16_t groupAddr = 0;
|
||||
bool value = false;
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
struct KnxTextCacheEntry {
|
||||
uint16_t groupAddr = 0;
|
||||
char text[MAX_TEXT_LEN] = {};
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
void loadFromSdCard();
|
||||
void saveToSdCard();
|
||||
void destroyAllWidgets();
|
||||
void createAllWidgets(const ScreenConfig& screen, lv_obj_t* parent);
|
||||
bool enqueueUiEvent(const UiEvent& event);
|
||||
void processUiQueue();
|
||||
void applyCachedValuesToWidgets();
|
||||
void applyKnxValue(uint16_t groupAddr, float value, TextSource source);
|
||||
void applyKnxSwitch(uint16_t groupAddr, bool value);
|
||||
void applyKnxText(uint16_t groupAddr, const char* text);
|
||||
void cacheKnxValue(uint16_t groupAddr, TextSource source, float value);
|
||||
void cacheKnxSwitch(uint16_t groupAddr, bool value);
|
||||
void cacheKnxText(uint16_t groupAddr, const char* text);
|
||||
bool getCachedKnxValue(uint16_t groupAddr, TextSource source, float* out) const;
|
||||
bool getCachedKnxSwitch(uint16_t groupAddr, bool* out) const;
|
||||
bool getCachedKnxText(uint16_t groupAddr, char* out, size_t outSize) const;
|
||||
static bool isNumericTextSource(TextSource source);
|
||||
|
||||
void createDefaultConfig();
|
||||
void applyScreen(uint8_t screenId);
|
||||
@ -118,4 +148,12 @@ private:
|
||||
lv_obj_t* modalContainer_ = nullptr;
|
||||
lv_obj_t* modalDimmer_ = nullptr;
|
||||
QueueHandle_t uiQueue_ = nullptr;
|
||||
|
||||
std::array<KnxNumericCacheEntry, KNX_CACHE_SIZE> knxNumericCache_ = {};
|
||||
std::array<KnxSwitchCacheEntry, KNX_CACHE_SIZE> knxSwitchCache_ = {};
|
||||
std::array<KnxTextCacheEntry, KNX_CACHE_SIZE> knxTextCache_ = {};
|
||||
size_t knxNumericCacheNext_ = 0;
|
||||
size_t knxSwitchCacheNext_ = 0;
|
||||
size_t knxTextCacheNext_ = 0;
|
||||
mutable portMUX_TYPE knxCacheMux_ = {};
|
||||
};
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#include "LabelWidget.hpp"
|
||||
#include "../Fonts.hpp"
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
LabelWidget::LabelWidget(const WidgetConfig& config)
|
||||
: Widget(config)
|
||||
@ -22,6 +23,13 @@ static lv_flex_align_t toFlexAlign(uint8_t align) {
|
||||
return LV_FLEX_ALIGN_CENTER;
|
||||
}
|
||||
|
||||
static void set_label_text_if_changed(lv_obj_t* label, const char* text) {
|
||||
if (!label || !text) return;
|
||||
const char* current = lv_label_get_text(label);
|
||||
if (current && strcmp(current, text) == 0) return;
|
||||
lv_label_set_text(label, text);
|
||||
}
|
||||
|
||||
int LabelWidget::encodeUtf8(uint32_t codepoint, char* buf) {
|
||||
if (codepoint < 0x80) {
|
||||
buf[0] = static_cast<char>(codepoint);
|
||||
@ -203,7 +211,7 @@ void LabelWidget::onKnxValue(float value) {
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), config_.text, value);
|
||||
}
|
||||
lv_label_set_text(label, buf);
|
||||
set_label_text_if_changed(label, buf);
|
||||
}
|
||||
|
||||
void LabelWidget::onKnxSwitch(bool value) {
|
||||
@ -211,7 +219,7 @@ void LabelWidget::onKnxSwitch(bool value) {
|
||||
if (label == nullptr) return;
|
||||
if (config_.textSource != TextSource::KNX_DPT_SWITCH) return;
|
||||
|
||||
lv_label_set_text(label, value ? "EIN" : "AUS");
|
||||
set_label_text_if_changed(label, value ? "EIN" : "AUS");
|
||||
}
|
||||
|
||||
void LabelWidget::onKnxText(const char* text) {
|
||||
@ -219,5 +227,5 @@ void LabelWidget::onKnxText(const char* text) {
|
||||
if (label == nullptr) return;
|
||||
if (config_.textSource != TextSource::KNX_DPT_TEXT) return;
|
||||
|
||||
lv_label_set_text(label, text);
|
||||
set_label_text_if_changed(label, text);
|
||||
}
|
||||
|
||||
@ -216,12 +216,18 @@ void PowerLinkWidget::dotAnimExec(void* var, int32_t value) {
|
||||
|
||||
void PowerLinkWidget::updateAnimation(float speed, bool reverse) {
|
||||
if (dot_ == nullptr || obj_ == nullptr) return;
|
||||
bool shouldHide = (speed <= 0.01f || pathLen_ <= 0.5f);
|
||||
bool isHidden = lv_obj_has_flag(dot_, LV_OBJ_FLAG_HIDDEN);
|
||||
if (std::fabs(speed - speed_) < 0.01f && reverse == reverse_ && isHidden == shouldHide) {
|
||||
return;
|
||||
}
|
||||
|
||||
speed_ = speed;
|
||||
reverse_ = reverse;
|
||||
|
||||
lv_anim_del(this, nullptr);
|
||||
|
||||
if (speed_ <= 0.01f || pathLen_ <= 0.5f) {
|
||||
if (shouldHide) {
|
||||
lv_obj_add_flag(dot_, LV_OBJ_FLAG_HIDDEN);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -10,6 +10,13 @@ PowerNodeWidget::PowerNodeWidget(const WidgetConfig& config)
|
||||
valueFormat_[0] = '\0';
|
||||
}
|
||||
|
||||
static void set_label_text_if_changed(lv_obj_t* label, const char* text) {
|
||||
if (!label || !text) return;
|
||||
const char* current = lv_label_get_text(label);
|
||||
if (current && strcmp(current, text) == 0) return;
|
||||
lv_label_set_text(label, text);
|
||||
}
|
||||
|
||||
int PowerNodeWidget::encodeUtf8(uint32_t codepoint, char* buf) {
|
||||
if (codepoint < 0x80) {
|
||||
buf[0] = static_cast<char>(codepoint);
|
||||
@ -152,7 +159,7 @@ void PowerNodeWidget::applyStyle() {
|
||||
|
||||
void PowerNodeWidget::updateValueText(const char* text) {
|
||||
if (valueLabel_ == nullptr || text == nullptr) return;
|
||||
lv_label_set_text(valueLabel_, text);
|
||||
set_label_text_if_changed(valueLabel_, text);
|
||||
}
|
||||
|
||||
void PowerNodeWidget::onKnxValue(float value) {
|
||||
|
||||
@ -10,6 +10,9 @@ CONFIG_LV_FONT_MONTSERRAT_48=y
|
||||
# Keep LVGL draw thread stack reasonable to avoid xTaskCreate failures
|
||||
CONFIG_LV_DRAW_THREAD_STACK_SIZE=32768
|
||||
|
||||
# Increase LVGL heap to avoid draw task OOM with dynamic UI
|
||||
CONFIG_LV_MEM_SIZE_KILOBYTES=128
|
||||
|
||||
# Enable FreeType fonts for extended glyph coverage (e.g. umlauts)
|
||||
CONFIG_LV_USE_FREETYPE=y
|
||||
CONFIG_ESP_LVGL_ADAPTER_ENABLE_FREETYPE=y
|
||||
|
||||
Loading…
Reference in New Issue
Block a user