This commit is contained in:
Thomas Peterson 2026-01-30 23:06:38 +01:00
parent 27613db808
commit 814d921ba7
53 changed files with 401 additions and 88 deletions

View File

@ -1,14 +1,43 @@
#include "HistoryStore.hpp" #include "HistoryStore.hpp"
#include "SdCard.hpp"
#include "esp_log.h" #include "esp_log.h"
#include <cstdio> #include "esp_timer.h"
#include <ctime>
#include <cmath> #include <cmath>
#include <cstdio>
#include <cstring>
#include <sys/time.h>
static const char* TAG = "HistoryStore"; static const char* TAG = "HistoryStore";
static constexpr uint32_t HISTORY_MAGIC = 0x4B584831; // KXH1
static constexpr uint16_t HISTORY_VERSION = 1;
static constexpr const char* HISTORY_FILE = "/sdcard/knx_history.bin";
static constexpr int64_t HISTORY_SAVE_INTERVAL_US = 300000000; // 5 minutes
// HistoryStore is effectively disabled to save resources and prevent hangs. #pragma pack(push, 1)
// It now acts as a simple pass-through for the latest value (for real-time chart indication) struct HistoryFileHeader {
// but does not record history or write to SD card. uint32_t magic;
uint16_t version;
uint16_t seriesCount;
uint16_t fineCapacity;
uint16_t coarseCapacity;
uint32_t fineInterval;
uint32_t coarseInterval;
};
struct HistoryFileSeriesHeader {
uint16_t groupAddr;
uint8_t source;
uint8_t reserved;
uint16_t fineCount;
uint16_t fineHead;
uint16_t coarseCount;
uint16_t coarseHead;
uint8_t hasLatest;
uint8_t reserved2[3];
int32_t latestTs;
float latestValue;
};
#pragma pack(pop)
HistoryStore& HistoryStore::instance() { HistoryStore& HistoryStore::instance() {
static HistoryStore inst; static HistoryStore inst;
@ -112,7 +141,7 @@ void HistoryStore::configureFromConfig(const GuiConfig& config) {
keep[idx] = true; keep[idx] = true;
continue; continue;
} }
// Find empty slot
HistorySeries* slot = nullptr; HistorySeries* slot = nullptr;
for (size_t si = 0; si < series_.size(); ++si) { for (size_t si = 0; si < series_.size(); ++si) {
if (!keep[si] && !series_[si].active) { if (!keep[si] && !series_[si].active) {
@ -121,10 +150,15 @@ void HistoryStore::configureFromConfig(const GuiConfig& config) {
break; break;
} }
} }
if (slot) { if (slot) {
slot->key = key; slot->key = key;
slot->active = true; slot->active = true;
slot->hasLatest = false; slot->hasLatest = false;
slot->fine.clear();
slot->coarse.clear();
slot->lastFineSampleTs = 0;
slot->lastCoarseSampleTs = 0;
} }
} }
@ -148,7 +182,10 @@ bool HistoryStore::updateLatest(uint16_t groupAddr, TextSource source, float val
xSemaphoreGive(mutex_); xSemaphoreGive(mutex_);
return false; return false;
} }
int64_t nowSec = now();
series->latestValue = value; series->latestValue = value;
series->latestTs = static_cast<int32_t>(nowSec);
series->hasLatest = true; series->hasLatest = true;
xSemaphoreGive(mutex_); xSemaphoreGive(mutex_);
return true; return true;
@ -159,56 +196,301 @@ int64_t HistoryStore::now() const {
} }
int32_t HistoryStore::periodSeconds(ChartPeriod period) const { int32_t HistoryStore::periodSeconds(ChartPeriod period) const {
return 3600; // Dummy switch (period) {
case ChartPeriod::HOUR_1: return 3600;
case ChartPeriod::HOUR_3: return 3 * 3600;
case ChartPeriod::HOUR_5: return 5 * 3600;
case ChartPeriod::HOUR_12: return 12 * 3600;
case ChartPeriod::HOUR_24: return 24 * 3600;
case ChartPeriod::MONTH_1: return HISTORY_MONTH_SECONDS;
default: return 3600;
}
} }
bool HistoryStore::fillChartSeries(uint16_t groupAddr, TextSource source, ChartPeriod period, bool HistoryStore::fillChartSeries(uint16_t groupAddr, TextSource source, ChartPeriod period,
int32_t* outValues, size_t outCount) const { int32_t* outValues, size_t outCount) const {
if (!outValues || outCount == 0) return false; if (!outValues || outCount == 0) return false;
if (outCount > CHART_POINT_COUNT) outCount = CHART_POINT_COUNT;
// Just return the latest value repeated, effectively a flat line for (size_t i = 0; i < outCount; ++i) {
outValues[i] = NO_POINT;
}
xSemaphoreTake(mutex_, portMAX_DELAY); xSemaphoreTake(mutex_, portMAX_DELAY);
const HistorySeries* series = findSeries(groupAddr, source); const HistorySeries* series = findSeries(groupAddr, source);
int32_t val = NO_POINT; if (!series) {
if (series && series->hasLatest) { xSemaphoreGive(mutex_);
val = static_cast<int32_t>(lrintf(series->latestValue)); return false;
}
int64_t nowSec = now();
int32_t window = periodSeconds(period);
if (window <= 0) {
xSemaphoreGive(mutex_);
return false;
}
int64_t start = nowSec - window;
std::array<float, CHART_POINT_COUNT> sums = {};
std::array<uint16_t, CHART_POINT_COUNT> counts = {};
auto accumulate = [&](const HistoryPoint& p) {
if (p.ts < start || p.ts > nowSec) return;
size_t bucket = static_cast<size_t>(((p.ts - start) * outCount) / window);
if (bucket >= outCount) bucket = outCount - 1;
sums[bucket] += p.value;
counts[bucket]++;
};
if (period == ChartPeriod::MONTH_1) {
series->coarse.forEach(accumulate);
} else {
series->fine.forEach(accumulate);
}
if (series->hasLatest) {
HistoryPoint latest{series->latestTs, series->latestValue};
accumulate(latest);
} }
xSemaphoreGive(mutex_); xSemaphoreGive(mutex_);
bool hasData = false;
int32_t lastValidValue = NO_POINT;
for (size_t i = 0; i < outCount; ++i) { for (size_t i = 0; i < outCount; ++i) {
outValues[i] = val; if (counts[i] > 0) {
float avg = sums[i] / counts[i];
int32_t val = static_cast<int32_t>(lrintf(avg));
outValues[i] = val;
lastValidValue = val;
hasData = true;
} else {
outValues[i] = lastValidValue;
if (lastValidValue != NO_POINT) {
hasData = true;
}
}
} }
return (val != NO_POINT);
return hasData;
} }
bool HistoryStore::tick() { bool HistoryStore::tick() {
return false; // Disabled xSemaphoreTake(mutex_, portMAX_DELAY);
int64_t nowSec = now();
// Only collect data if time is roughly synced (after 2020)
if (nowSec < 1577836800LL) {
xSemaphoreGive(mutex_);
return false;
}
bool added = false;
for (size_t i = 0; i < series_.size(); ++i) {
HistorySeries& series = series_[i];
if (!series.active || !series.hasLatest) continue;
if (series.fine.count == 0 || nowSec - series.lastFineSampleTs >= HISTORY_FINE_INTERVAL) {
series.fine.push({static_cast<int32_t>(nowSec), series.latestValue});
series.lastFineSampleTs = static_cast<int32_t>(nowSec);
added = true;
}
if (series.coarse.count == 0 || nowSec - series.lastCoarseSampleTs >= HISTORY_COARSE_INTERVAL) {
series.coarse.push({static_cast<int32_t>(nowSec), series.latestValue});
series.lastCoarseSampleTs = static_cast<int32_t>(nowSec);
added = true;
}
}
if (added) {
dirty_ = true;
}
xSemaphoreGive(mutex_);
return added;
} }
void HistoryStore::performAutoSave() { void HistoryStore::performAutoSave() {
// Disabled xSemaphoreTake(mutex_, portMAX_DELAY);
bool shouldSave = false;
int64_t nowSec = now();
int64_t monoUs = esp_timer_get_time();
if (dirty_ && nowSec > 1577836800LL && SdCard::instance().isMounted()) {
if (monoUs - lastSaveMonoUs_ >= HISTORY_SAVE_INTERVAL_US) {
shouldSave = true;
lastSaveMonoUs_ = monoUs;
}
}
xSemaphoreGive(mutex_);
if (shouldSave) {
saveToSdCard();
}
} }
void HistoryStore::updateTimeOfDay(const tm& value) { void HistoryStore::updateTimeOfDay(const struct tm& value) {
// Disabled // Handled in WidgetManager
} }
void HistoryStore::updateDate(const tm& value) { void HistoryStore::updateDate(const struct tm& value) {
// Disabled // Handled in WidgetManager
} }
void HistoryStore::updateDateTime(const tm& value) { void HistoryStore::updateDateTime(const struct tm& value) {
// Disabled // Handled in WidgetManager
} }
void HistoryStore::clearAll() { void HistoryStore::clearAll() {
// Disabled xSemaphoreTake(mutex_, portMAX_DELAY);
for (size_t i = 0; i < series_.size(); ++i) {
HistorySeries& series = series_[i];
if (!series.active) continue;
series.fine.clear();
series.coarse.clear();
series.hasLatest = false;
series.latestValue = 0.0f;
series.latestTs = 0;
series.lastFineSampleTs = 0;
series.lastCoarseSampleTs = 0;
}
xSemaphoreGive(mutex_);
} }
void HistoryStore::saveToSdCard() { void HistoryStore::saveToSdCard() {
// Disabled if (!SdCard::instance().isMounted()) return;
if (now() < 1577836800LL) return;
FILE* f = fopen(HISTORY_FILE, "wb");
if (!f) {
ESP_LOGW(TAG, "Failed to open history file for writing");
return;
}
xSemaphoreTake(mutex_, portMAX_DELAY);
HistoryFileHeader header = {};
header.magic = HISTORY_MAGIC;
header.version = HISTORY_VERSION;
uint16_t activeCount = 0;
for (const auto& series : series_) {
if (series.active) activeCount++;
}
header.seriesCount = activeCount;
header.fineCapacity = HISTORY_FINE_CAP;
header.coarseCapacity = HISTORY_COARSE_CAP;
header.fineInterval = HISTORY_FINE_INTERVAL;
header.coarseInterval = HISTORY_COARSE_INTERVAL;
xSemaphoreGive(mutex_);
if (fwrite(&header, sizeof(header), 1, f) != 1) {
fclose(f);
ESP_LOGW(TAG, "Failed to write history header");
return;
}
size_t seriesDataSize = sizeof(HistoryFileSeriesHeader) +
sizeof(HistoryPoint) * (HISTORY_FINE_CAP + HISTORY_COARSE_CAP);
uint8_t* tempBuf = (uint8_t*)malloc(seriesDataSize);
if (!tempBuf) {
fclose(f);
ESP_LOGE(TAG, "Failed to allocate temp buffer for save");
return;
}
for (size_t i = 0; i < series_.size(); ++i) {
xSemaphoreTake(mutex_, portMAX_DELAY);
const HistorySeries& series = series_[i];
if (!series.active) {
xSemaphoreGive(mutex_);
continue;
}
HistoryFileSeriesHeader* sh = (HistoryFileSeriesHeader*)tempBuf;
sh->groupAddr = series.key.addr;
sh->source = static_cast<uint8_t>(series.key.source);
sh->fineCount = static_cast<uint16_t>(series.fine.count);
sh->fineHead = static_cast<uint16_t>(series.fine.head);
sh->coarseCount = static_cast<uint16_t>(series.coarse.count);
sh->coarseHead = static_cast<uint16_t>(series.coarse.head);
sh->hasLatest = series.hasLatest ? 1 : 0;
sh->latestTs = series.latestTs;
sh->latestValue = series.latestValue;
uint8_t* dataPtr = tempBuf + sizeof(HistoryFileSeriesHeader);
memcpy(dataPtr, series.fine.points.data(), sizeof(HistoryPoint) * HISTORY_FINE_CAP);
dataPtr += sizeof(HistoryPoint) * HISTORY_FINE_CAP;
memcpy(dataPtr, series.coarse.points.data(), sizeof(HistoryPoint) * HISTORY_COARSE_CAP);
xSemaphoreGive(mutex_);
if (fwrite(tempBuf, 1, seriesDataSize, f) != seriesDataSize) {
ESP_LOGW(TAG, "Failed to write series data");
break;
}
}
free(tempBuf);
xSemaphoreTake(mutex_, portMAX_DELAY);
dirty_ = false;
xSemaphoreGive(mutex_);
fclose(f);
ESP_LOGI(TAG, "History saved (%d series)", static_cast<int>(activeCount));
} }
void HistoryStore::loadFromSdCard() { void HistoryStore::loadFromSdCard() {
// Disabled if (!SdCard::instance().isMounted()) return;
}
FILE* f = fopen(HISTORY_FILE, "rb");
if (!f) return;
HistoryFileHeader header = {};
if (fread(&header, sizeof(header), 1, f) != 1) {
fclose(f);
return;
}
if (header.magic != HISTORY_MAGIC || header.version != HISTORY_VERSION) {
fclose(f);
ESP_LOGW(TAG, "History header mismatch");
return;
}
if (header.fineCapacity != HISTORY_FINE_CAP ||
header.coarseCapacity != HISTORY_COARSE_CAP ||
header.fineInterval != HISTORY_FINE_INTERVAL ||
header.coarseInterval != HISTORY_COARSE_INTERVAL) {
fclose(f);
ESP_LOGW(TAG, "History config mismatch");
return;
}
xSemaphoreTake(mutex_, portMAX_DELAY);
for (uint16_t i = 0; i < header.seriesCount; ++i) {
HistoryFileSeriesHeader sh = {};
if (fread(&sh, sizeof(sh), 1, f) != 1) break;
HistorySeries* series = findSeries(sh.groupAddr, static_cast<TextSource>(sh.source));
if (series) {
series->fine.count = sh.fineCount > HISTORY_FINE_CAP ? HISTORY_FINE_CAP : sh.fineCount;
series->fine.head = sh.fineHead >= HISTORY_FINE_CAP ? 0 : sh.fineHead;
series->coarse.count = sh.coarseCount > HISTORY_COARSE_CAP ? HISTORY_COARSE_CAP : sh.coarseCount;
series->coarse.head = sh.coarseHead >= HISTORY_COARSE_CAP ? 0 : sh.coarseHead;
series->hasLatest = sh.hasLatest != 0;
series->latestTs = sh.latestTs;
series->latestValue = sh.latestValue;
if (fread(series->fine.points.data(), sizeof(HistoryPoint), HISTORY_FINE_CAP, f) != HISTORY_FINE_CAP) break;
if (fread(series->coarse.points.data(), sizeof(HistoryPoint), HISTORY_COARSE_CAP, f) != HISTORY_COARSE_CAP) break;
} else {
fseek(f, sizeof(HistoryPoint) * (HISTORY_FINE_CAP + HISTORY_COARSE_CAP), SEEK_CUR);
}
}
dirty_ = false;
xSemaphoreGive(mutex_);
fclose(f);
ESP_LOGI(TAG, "History loaded");
}

View File

@ -5,6 +5,7 @@
#include "esp_lv_adapter.h" #include "esp_lv_adapter.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_timer.h" #include "esp_timer.h"
#include "esp_heap_caps.h"
#include "cJSON.h" #include "cJSON.h"
#include <memory> #include <memory>
#include <new> #include <new>
@ -351,13 +352,6 @@ void WidgetManager::applyScreenLocked(uint8_t screenId) {
lv_display_enable_invalidation(disp, false); lv_display_enable_invalidation(disp, false);
} }
// Reset all input devices
lv_indev_t* indev = lv_indev_get_next(nullptr);
while (indev) {
lv_indev_reset(indev, nullptr);
indev = lv_indev_get_next(indev);
}
// SAFE DESTRUCTION: // SAFE DESTRUCTION:
// 1. Mark all C++ widgets as "LVGL object already gone" // 1. Mark all C++ widgets as "LVGL object already gone"
for (auto& widget : widgets_) { for (auto& widget : widgets_) {
@ -397,13 +391,6 @@ void WidgetManager::showModalScreenLocked(const ScreenConfig& screen) {
closeModalLocked(); closeModalLocked();
} }
// Reset all input devices
lv_indev_t* indev = lv_indev_get_next(nullptr);
while (indev) {
lv_indev_reset(indev, nullptr);
indev = lv_indev_get_next(indev);
}
// SAFE DESTRUCTION // SAFE DESTRUCTION
for (auto& widget : widgets_) { for (auto& widget : widgets_) {
if (widget) widget->clearLvglObject(); if (widget) widget->clearLvglObject();
@ -516,13 +503,6 @@ void WidgetManager::closeModalLocked() {
return; return;
} }
// Reset input devices
lv_indev_t* indev = lv_indev_get_next(nullptr);
while (indev) {
lv_indev_reset(indev, nullptr);
indev = lv_indev_get_next(indev);
}
// SAFE DESTRUCTION // SAFE DESTRUCTION
for (auto& widget : widgets_) { for (auto& widget : widgets_) {
if (widget) widget->clearLvglObject(); if (widget) widget->clearLvglObject();
@ -672,40 +652,43 @@ void WidgetManager::enterStandby() {
} }
void WidgetManager::loop() { void WidgetManager::loop() {
static uint32_t loopCount = 0;
loopCount++;
if (loopCount % 40 == 0) {
ESP_LOGI(TAG, "Heap: %lu | Internal: %lu",
esp_get_free_heap_size(),
heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
}
bool didUiNav = false; bool didUiNav = false;
if (navPending_) { if (navPending_) {
int64_t now = esp_timer_get_time(); int64_t now = esp_timer_get_time();
// Increased delay to ensure touch events are fully processed
if (now - navRequestUs_ >= NAV_DELAY_US) { if (now - navRequestUs_ >= NAV_DELAY_US) {
navPending_ = false; navPending_ = false;
ESP_LOGI(TAG, "Executing navigation: action=%d target=%d", printf("WM: [TRACE] Nav start\n"); fflush(stdout);
static_cast<int>(navAction_), navTargetScreen_);
if (navAction_ == ButtonAction::JUMP) { if (navAction_ == ButtonAction::JUMP) {
showScreenLocked(navTargetScreen_); showScreenLocked(navTargetScreen_);
} else if (navAction_ == ButtonAction::BACK) { } else if (navAction_ == ButtonAction::BACK) {
goBackLocked(); goBackLocked();
} }
didUiNav = true; didUiNav = true;
ESP_LOGI(TAG, "Navigation complete"); printf("WM: [TRACE] Nav end\n"); fflush(stdout);
} }
} }
if (standbyWakePending_) { // printf("WM: [TRACE] Queue start\n"); fflush(stdout);
standbyWakePending_ = false;
if (standbyWakeTarget_ != SCREEN_ID_NONE) {
activeScreenId_ = standbyWakeTarget_;
applyScreenLocked(activeScreenId_);
}
didUiNav = true;
}
processUiQueue(); processUiQueue();
// printf("WM: [TRACE] Queue end\n"); fflush(stdout);
// printf("WM: [TRACE] Tick start\n"); fflush(stdout);
if (HistoryStore::instance().tick()) { if (HistoryStore::instance().tick()) {
refreshChartWidgets(); refreshChartWidgets();
} }
// printf("WM: [TRACE] Tick end\n"); fflush(stdout);
// printf("WM: [TRACE] Time start\n"); fflush(stdout);
updateSystemTimeWidgets(); updateSystemTimeWidgets();
// printf("WM: [TRACE] Time end\n"); fflush(stdout);
if (didUiNav) return; if (didUiNav) return;

View File

@ -156,7 +156,7 @@ private:
const ScreenConfig* activeScreen() const; const ScreenConfig* activeScreen() const;
static constexpr const char* CONFIG_FILE = "/sdcard/lvgl.json"; static constexpr const char* CONFIG_FILE = "/sdcard/lvgl.json";
static constexpr int64_t NAV_DELAY_US = 200 * 1000; // 200ms delay for touch release static constexpr int64_t NAV_DELAY_US = 10 * 1000; // 10ms delay (almost immediate)
GuiConfig config_; GuiConfig config_;
uint8_t activeScreenId_ = 0; uint8_t activeScreenId_ = 0;

View File

@ -40,8 +40,8 @@ public:
// Initialize LVGL adapter // Initialize LVGL adapter
esp_lv_adapter_config_t cfg = ESP_LV_ADAPTER_DEFAULT_CONFIG(); esp_lv_adapter_config_t cfg = ESP_LV_ADAPTER_DEFAULT_CONFIG();
cfg.stack_in_psram = false; // Use internal RAM for stack cfg.stack_in_psram = true; // Use PSRAM for stack to save internal RAM
cfg.task_stack_size = 32 * 1024; cfg.task_stack_size = 48 * 1024;
ESP_ERROR_CHECK(esp_lv_adapter_init(&cfg)); ESP_ERROR_CHECK(esp_lv_adapter_init(&cfg));
// Register display // Register display
@ -52,7 +52,8 @@ public:
1280, // Vertical resolution 1280, // Vertical resolution
ESP_LV_ADAPTER_ROTATE_90 // Rotation ESP_LV_ADAPTER_ROTATE_90 // Rotation
); );
disp_cfg.profile.buffer_height = 34; // Reduced to 20 to fit in RAM (32KB) disp_cfg.profile.buffer_height = 34; // Reduced to 10 (~25KB) to fit in Internal RAM
disp_cfg.profile.use_psram = true;
lv_disp_t* lv_display = esp_lv_adapter_register_display(&disp_cfg); lv_disp_t* lv_display = esp_lv_adapter_register_display(&disp_cfg);
assert(lv_display != NULL); assert(lv_display != NULL);
@ -96,7 +97,19 @@ public:
Fonts::init(); Fonts::init();
ESP_LOGI(TAG, "CREATE GUI"); ESP_LOGI(TAG, "CREATE GUI");
gui.create(); gui.create();
// Force full screen redraw to initialize DMA2D hardware resources at startup
// This prevents lazy initialization during first button press which can cause freezes
ESP_LOGI(TAG, "Warming up display hardware...");
vTaskDelay(pdMS_TO_TICKS(100));
if (esp_lv_adapter_lock(1000) == ESP_OK) {
lv_obj_invalidate(lv_scr_act());
lv_refr_now(lv_display_get_default());
esp_lv_adapter_unlock();
}
vTaskDelay(pdMS_TO_TICKS(200)); // Let the refresh complete
ESP_LOGI(TAG, "Display hardware ready");
ESP_LOGI(TAG, "Application running"); ESP_LOGI(TAG, "Application running");
while (true) { while (true) {
vTaskDelay(pdMS_TO_TICKS(10)); vTaskDelay(pdMS_TO_TICKS(10));

View File

@ -1,5 +1,6 @@
#include "Widget.hpp" #include "Widget.hpp"
#include "../Fonts.hpp" #include "../Fonts.hpp"
#include "lvgl.h"
Widget::Widget(const WidgetConfig& config) Widget::Widget(const WidgetConfig& config)
: config_(config) : config_(config)
@ -72,16 +73,40 @@ void Widget::applyCommonStyle() {
} }
void Widget::applyShadowStyle() { void Widget::applyShadowStyle() {
// return;
if (obj_ == nullptr || !config_.shadow.enabled) return; if (obj_ == nullptr || !config_.shadow.enabled) return;
lv_obj_set_style_shadow_color(obj_, lv_color_make( // Limit shadow values to prevent memory issues on ESP32
config_.shadow.color.r, config_.shadow.color.g, config_.shadow.color.b), 0); constexpr int16_t MAX_SHADOW_BLUR = 15;
constexpr int16_t MAX_SHADOW_SPREAD = 8;
int16_t blur = config_.shadow.blur;
int16_t spread = config_.shadow.spread;
if (blur > MAX_SHADOW_BLUR) blur = MAX_SHADOW_BLUR;
if (blur < 0) blur = 0;
if (spread > MAX_SHADOW_SPREAD) spread = MAX_SHADOW_SPREAD;
if (spread < 0) spread = 0;
if (blur == 0) return;
lv_color_t shadowColor = lv_color_make(
config_.shadow.color.r, config_.shadow.color.g, config_.shadow.color.b);
// Default state shadow
lv_obj_set_style_shadow_color(obj_, shadowColor, 0);
lv_obj_set_style_shadow_opa(obj_, 180, 0); lv_obj_set_style_shadow_opa(obj_, 180, 0);
lv_obj_set_style_shadow_width(obj_, config_.shadow.blur, 0); lv_obj_set_style_shadow_width(obj_, blur, 0);
lv_obj_set_style_shadow_spread(obj_, config_.shadow.spread, 0); lv_obj_set_style_shadow_spread(obj_, spread, 0);
lv_obj_set_style_shadow_offset_x(obj_, config_.shadow.offsetX, 0); lv_obj_set_style_shadow_offset_x(obj_, config_.shadow.offsetX, 0);
lv_obj_set_style_shadow_offset_y(obj_, config_.shadow.offsetY, 0); lv_obj_set_style_shadow_offset_y(obj_, config_.shadow.offsetY, 0);
// For clickable widgets: explicitly define PRESSED state with no shadow
// This prevents PPA/DMA2D freeze when shadow needs recalculation during state change
if (lv_obj_has_flag(obj_, LV_OBJ_FLAG_CLICKABLE)) {
lv_obj_set_style_shadow_width(obj_, 0, LV_STATE_PRESSED);
lv_obj_set_style_shadow_spread(obj_, 0, LV_STATE_PRESSED);
lv_obj_set_style_shadow_opa(obj_, 0, LV_STATE_PRESSED);
}
} }
const lv_font_t* Widget::getFontBySize(uint8_t sizeIndex) { const lv_font_t* Widget::getFontBySize(uint8_t sizeIndex) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,8 +5,8 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>web-interface</title> <title>web-interface</title>
<script type="module" crossorigin src="/assets/index-D_mZROR3.js"></script> <script type="module" crossorigin src="/assets/index-C5enbiYy.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D_QgTvEC.css"> <link rel="stylesheet" crossorigin href="/assets/index-CE6v0X8e.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@ -565,7 +565,7 @@ CONFIG_IDF_TOOLCHAIN_GCC=y
CONFIG_IDF_TARGET_ARCH_RISCV=y CONFIG_IDF_TARGET_ARCH_RISCV=y
CONFIG_IDF_TARGET_ARCH="riscv" CONFIG_IDF_TARGET_ARCH="riscv"
CONFIG_IDF_TARGET="esp32p4" CONFIG_IDF_TARGET="esp32p4"
CONFIG_IDF_INIT_VERSION="5.5.2" CONFIG_IDF_INIT_VERSION="$IDF_INIT_VERSION"
CONFIG_IDF_TARGET_ESP32P4=y CONFIG_IDF_TARGET_ESP32P4=y
CONFIG_IDF_FIRMWARE_CHIP_ID=0x0012 CONFIG_IDF_FIRMWARE_CHIP_ID=0x0012
@ -2888,7 +2888,7 @@ CONFIG_LV_USE_FREERTOS_TASK_NOTIFY=y
# #
CONFIG_LV_DRAW_BUF_STRIDE_ALIGN=1 CONFIG_LV_DRAW_BUF_STRIDE_ALIGN=1
CONFIG_LV_DRAW_BUF_ALIGN=64 CONFIG_LV_DRAW_BUF_ALIGN=64
CONFIG_LV_DRAW_LAYER_SIMPLE_BUF_SIZE=24576 CONFIG_LV_DRAW_LAYER_SIMPLE_BUF_SIZE=43000
CONFIG_LV_DRAW_LAYER_MAX_MEMORY=0 CONFIG_LV_DRAW_LAYER_MAX_MEMORY=0
CONFIG_LV_DRAW_THREAD_STACK_SIZE=32768 CONFIG_LV_DRAW_THREAD_STACK_SIZE=32768
CONFIG_LV_DRAW_THREAD_PRIO=3 CONFIG_LV_DRAW_THREAD_PRIO=3
@ -2909,7 +2909,7 @@ CONFIG_LV_DRAW_SW_DRAW_UNIT_CNT=1
# CONFIG_LV_USE_NATIVE_HELIUM_ASM is not set # CONFIG_LV_USE_NATIVE_HELIUM_ASM is not set
CONFIG_LV_DRAW_SW_COMPLEX=y CONFIG_LV_DRAW_SW_COMPLEX=y
# CONFIG_LV_USE_DRAW_SW_COMPLEX_GRADIENTS is not set # CONFIG_LV_USE_DRAW_SW_COMPLEX_GRADIENTS is not set
CONFIG_LV_DRAW_SW_SHADOW_CACHE_SIZE=0 CONFIG_LV_DRAW_SW_SHADOW_CACHE_SIZE=20
CONFIG_LV_DRAW_SW_CIRCLE_CACHE_SIZE=4 CONFIG_LV_DRAW_SW_CIRCLE_CACHE_SIZE=4
CONFIG_LV_DRAW_SW_ASM_NONE=y CONFIG_LV_DRAW_SW_ASM_NONE=y
# CONFIG_LV_DRAW_SW_ASM_NEON is not set # CONFIG_LV_DRAW_SW_ASM_NEON is not set

View File

@ -14,18 +14,18 @@
<span class="text-[13px] font-semibold">Button</span> <span class="text-[13px] font-semibold">Button</span>
<span class="text-[11px] text-muted mt-0.5 block">Aktion</span> <span class="text-[11px] text-muted mt-0.5 block">Aktion</span>
</button> </button>
<button :class="btnClass" @click="store.addWidget('led')"> <button class="bg-panel-2 border border-border rounded-xl p-2.5 text-left transition hover:-translate-y-0.5 hover:border-accent" @click="store.addWidget('led')">
<span class="material-symbols-outlined text-[24px]">light_mode</span> <span class="text-[13px] font-semibold">LED</span>
<span class="text-[10px]">LED</span> <span class="text-[11px] text-muted mt-0.5 block">Indikator</span>
</button> </button>
<button :class="btnClass" @click="store.addWidget('clock')"> <button class="bg-panel-2 border border-border rounded-xl p-2.5 text-left transition hover:-translate-y-0.5 hover:border-accent" @click="store.addWidget('clock')">
<span class="material-symbols-outlined text-[24px]">schedule</span> <span class="text-[13px] font-semibold">Uhr</span>
<span class="text-[10px]">Uhr</span> <span class="text-[11px] text-muted mt-0.5 block">Analog</span>
</button> </button>
<button :class="btnClass" @click="store.addWidget('icon')"> <button class="bg-panel-2 border border-border rounded-xl p-2.5 text-left transition hover:-translate-y-0.5 hover:border-accent" @click="store.addWidget('icon')">
<span class="material-symbols-outlined text-[24px]">image</span> <span class="text-[13px] font-semibold">Icon</span>
<span class="text-[10px]">Icon</span> <span class="text-[11px] text-muted mt-0.5 block">Symbol</span>
</button> </button>
<button class="bg-panel-2 border border-border rounded-xl p-2.5 text-left transition hover:-translate-y-0.5 hover:border-accent" @click="store.addWidget('tabview')"> <button class="bg-panel-2 border border-border rounded-xl p-2.5 text-left transition hover:-translate-y-0.5 hover:border-accent" @click="store.addWidget('tabview')">
<span class="text-[13px] font-semibold">Tabs</span> <span class="text-[13px] font-semibold">Tabs</span>
<span class="text-[11px] text-muted mt-0.5 block">Container</span> <span class="text-[11px] text-muted mt-0.5 block">Container</span>