Fixes
This commit is contained in:
parent
27613db808
commit
814d921ba7
Binary file not shown.
Binary file not shown.
BIN
.cache/clangd/index/ChartWidget.cpp.344F8561CCF752B4.idx
Normal file
BIN
.cache/clangd/index/ChartWidget.cpp.344F8561CCF752B4.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/ChartWidget.hpp.D8383FB7050AF787.idx
Normal file
BIN
.cache/clangd/index/ChartWidget.hpp.D8383FB7050AF787.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/ClockWidget.cpp.6053C0339E915CC8.idx
Normal file
BIN
.cache/clangd/index/ClockWidget.cpp.6053C0339E915CC8.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/ClockWidget.hpp.447A48F9D372A75C.idx
Normal file
BIN
.cache/clangd/index/ClockWidget.hpp.447A48F9D372A75C.idx
Normal file
Binary file not shown.
Binary file not shown.
BIN
.cache/clangd/index/HistoryStore.cpp.957860D36959A6A5.idx
Normal file
BIN
.cache/clangd/index/HistoryStore.cpp.957860D36959A6A5.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/HistoryStore.hpp.4381476956370A61.idx
Normal file
BIN
.cache/clangd/index/HistoryStore.hpp.4381476956370A61.idx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.cache/clangd/index/PowerFlowWidget.cpp.8A280FD116CAAFED.idx
Normal file
BIN
.cache/clangd/index/PowerFlowWidget.cpp.8A280FD116CAAFED.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/PowerFlowWidget.hpp.D4F5557650792017.idx
Normal file
BIN
.cache/clangd/index/PowerFlowWidget.hpp.D4F5557650792017.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/PowerLinkWidget.cpp.481D6AFA808A3AE1.idx
Normal file
BIN
.cache/clangd/index/PowerLinkWidget.cpp.481D6AFA808A3AE1.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/PowerLinkWidget.hpp.0B60F3DFD20D3D5C.idx
Normal file
BIN
.cache/clangd/index/PowerLinkWidget.hpp.0B60F3DFD20D3D5C.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/PowerNodeWidget.cpp.D068C7972720D9A3.idx
Normal file
BIN
.cache/clangd/index/PowerNodeWidget.cpp.D068C7972720D9A3.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/PowerNodeWidget.hpp.EFF78DEAFB845F91.idx
Normal file
BIN
.cache/clangd/index/PowerNodeWidget.hpp.EFF78DEAFB845F91.idx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,14 +1,43 @@
|
||||
#include "HistoryStore.hpp"
|
||||
#include "SdCard.hpp"
|
||||
#include "esp_log.h"
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include "esp_timer.h"
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <sys/time.h>
|
||||
|
||||
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.
|
||||
// It now acts as a simple pass-through for the latest value (for real-time chart indication)
|
||||
// but does not record history or write to SD card.
|
||||
#pragma pack(push, 1)
|
||||
struct HistoryFileHeader {
|
||||
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() {
|
||||
static HistoryStore inst;
|
||||
@ -112,7 +141,7 @@ void HistoryStore::configureFromConfig(const GuiConfig& config) {
|
||||
keep[idx] = true;
|
||||
continue;
|
||||
}
|
||||
// Find empty slot
|
||||
|
||||
HistorySeries* slot = nullptr;
|
||||
for (size_t si = 0; si < series_.size(); ++si) {
|
||||
if (!keep[si] && !series_[si].active) {
|
||||
@ -121,10 +150,15 @@ void HistoryStore::configureFromConfig(const GuiConfig& config) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (slot) {
|
||||
slot->key = key;
|
||||
slot->active = true;
|
||||
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_);
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t nowSec = now();
|
||||
series->latestValue = value;
|
||||
series->latestTs = static_cast<int32_t>(nowSec);
|
||||
series->hasLatest = true;
|
||||
xSemaphoreGive(mutex_);
|
||||
return true;
|
||||
@ -159,56 +196,301 @@ int64_t HistoryStore::now() 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,
|
||||
int32_t* outValues, size_t outCount) const {
|
||||
if (!outValues || outCount == 0) return false;
|
||||
if (outCount > CHART_POINT_COUNT) outCount = CHART_POINT_COUNT;
|
||||
for (size_t i = 0; i < outCount; ++i) {
|
||||
outValues[i] = NO_POINT;
|
||||
}
|
||||
|
||||
// Just return the latest value repeated, effectively a flat line
|
||||
xSemaphoreTake(mutex_, portMAX_DELAY);
|
||||
const HistorySeries* series = findSeries(groupAddr, source);
|
||||
int32_t val = NO_POINT;
|
||||
if (series && series->hasLatest) {
|
||||
val = static_cast<int32_t>(lrintf(series->latestValue));
|
||||
if (!series) {
|
||||
xSemaphoreGive(mutex_);
|
||||
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_);
|
||||
|
||||
bool hasData = false;
|
||||
int32_t lastValidValue = NO_POINT;
|
||||
|
||||
for (size_t i = 0; i < outCount; ++i) {
|
||||
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() {
|
||||
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() {
|
||||
// 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) {
|
||||
// Disabled
|
||||
void HistoryStore::updateTimeOfDay(const struct tm& value) {
|
||||
// Handled in WidgetManager
|
||||
}
|
||||
|
||||
void HistoryStore::updateDate(const tm& value) {
|
||||
// Disabled
|
||||
void HistoryStore::updateDate(const struct tm& value) {
|
||||
// Handled in WidgetManager
|
||||
}
|
||||
|
||||
void HistoryStore::updateDateTime(const tm& value) {
|
||||
// Disabled
|
||||
void HistoryStore::updateDateTime(const struct tm& value) {
|
||||
// Handled in WidgetManager
|
||||
}
|
||||
|
||||
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() {
|
||||
// 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() {
|
||||
// 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");
|
||||
}
|
||||
@ -5,6 +5,7 @@
|
||||
#include "esp_lv_adapter.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "cJSON.h"
|
||||
#include <memory>
|
||||
#include <new>
|
||||
@ -351,13 +352,6 @@ void WidgetManager::applyScreenLocked(uint8_t screenId) {
|
||||
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:
|
||||
// 1. Mark all C++ widgets as "LVGL object already gone"
|
||||
for (auto& widget : widgets_) {
|
||||
@ -397,13 +391,6 @@ void WidgetManager::showModalScreenLocked(const ScreenConfig& screen) {
|
||||
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
|
||||
for (auto& widget : widgets_) {
|
||||
if (widget) widget->clearLvglObject();
|
||||
@ -516,13 +503,6 @@ void WidgetManager::closeModalLocked() {
|
||||
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
|
||||
for (auto& widget : widgets_) {
|
||||
if (widget) widget->clearLvglObject();
|
||||
@ -672,40 +652,43 @@ void WidgetManager::enterStandby() {
|
||||
}
|
||||
|
||||
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;
|
||||
if (navPending_) {
|
||||
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_);
|
||||
printf("WM: [TRACE] Nav start\n"); fflush(stdout);
|
||||
if (navAction_ == ButtonAction::JUMP) {
|
||||
showScreenLocked(navTargetScreen_);
|
||||
} else if (navAction_ == ButtonAction::BACK) {
|
||||
goBackLocked();
|
||||
}
|
||||
didUiNav = true;
|
||||
ESP_LOGI(TAG, "Navigation complete");
|
||||
printf("WM: [TRACE] Nav end\n"); fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
if (standbyWakePending_) {
|
||||
standbyWakePending_ = false;
|
||||
if (standbyWakeTarget_ != SCREEN_ID_NONE) {
|
||||
activeScreenId_ = standbyWakeTarget_;
|
||||
applyScreenLocked(activeScreenId_);
|
||||
}
|
||||
didUiNav = true;
|
||||
}
|
||||
|
||||
// printf("WM: [TRACE] Queue start\n"); fflush(stdout);
|
||||
processUiQueue();
|
||||
// printf("WM: [TRACE] Queue end\n"); fflush(stdout);
|
||||
|
||||
// printf("WM: [TRACE] Tick start\n"); fflush(stdout);
|
||||
if (HistoryStore::instance().tick()) {
|
||||
refreshChartWidgets();
|
||||
}
|
||||
// printf("WM: [TRACE] Tick end\n"); fflush(stdout);
|
||||
|
||||
// printf("WM: [TRACE] Time start\n"); fflush(stdout);
|
||||
updateSystemTimeWidgets();
|
||||
// printf("WM: [TRACE] Time end\n"); fflush(stdout);
|
||||
|
||||
if (didUiNav) return;
|
||||
|
||||
|
||||
@ -156,7 +156,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
|
||||
static constexpr int64_t NAV_DELAY_US = 10 * 1000; // 10ms delay (almost immediate)
|
||||
|
||||
GuiConfig config_;
|
||||
uint8_t activeScreenId_ = 0;
|
||||
|
||||
@ -40,8 +40,8 @@ public:
|
||||
|
||||
// Initialize LVGL adapter
|
||||
esp_lv_adapter_config_t cfg = ESP_LV_ADAPTER_DEFAULT_CONFIG();
|
||||
cfg.stack_in_psram = false; // Use internal RAM for stack
|
||||
cfg.task_stack_size = 32 * 1024;
|
||||
cfg.stack_in_psram = true; // Use PSRAM for stack to save internal RAM
|
||||
cfg.task_stack_size = 48 * 1024;
|
||||
ESP_ERROR_CHECK(esp_lv_adapter_init(&cfg));
|
||||
|
||||
// Register display
|
||||
@ -52,7 +52,8 @@ public:
|
||||
1280, // Vertical resolution
|
||||
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);
|
||||
assert(lv_display != NULL);
|
||||
|
||||
@ -97,6 +98,18 @@ public:
|
||||
ESP_LOGI(TAG, "CREATE GUI");
|
||||
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");
|
||||
while (true) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#include "Widget.hpp"
|
||||
#include "../Fonts.hpp"
|
||||
#include "lvgl.h"
|
||||
|
||||
Widget::Widget(const WidgetConfig& config)
|
||||
: config_(config)
|
||||
@ -72,16 +73,40 @@ void Widget::applyCommonStyle() {
|
||||
}
|
||||
|
||||
void Widget::applyShadowStyle() {
|
||||
|
||||
// return;
|
||||
if (obj_ == nullptr || !config_.shadow.enabled) return;
|
||||
|
||||
lv_obj_set_style_shadow_color(obj_, lv_color_make(
|
||||
config_.shadow.color.r, config_.shadow.color.g, config_.shadow.color.b), 0);
|
||||
// Limit shadow values to prevent memory issues on ESP32
|
||||
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_width(obj_, config_.shadow.blur, 0);
|
||||
lv_obj_set_style_shadow_spread(obj_, config_.shadow.spread, 0);
|
||||
lv_obj_set_style_shadow_width(obj_, blur, 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_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) {
|
||||
|
||||
9
sdcard_content/webseite/assets/index-C5enbiYy.js
Normal file
9
sdcard_content/webseite/assets/index-C5enbiYy.js
Normal file
File diff suppressed because one or more lines are too long
1
sdcard_content/webseite/assets/index-CE6v0X8e.css
Normal file
1
sdcard_content/webseite/assets/index-CE6v0X8e.css
Normal file
File diff suppressed because one or more lines are too long
@ -5,8 +5,8 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>web-interface</title>
|
||||
<script type="module" crossorigin src="/assets/index-D_mZROR3.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-D_QgTvEC.css">
|
||||
<script type="module" crossorigin src="/assets/index-C5enbiYy.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CE6v0X8e.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@ -565,7 +565,7 @@ CONFIG_IDF_TOOLCHAIN_GCC=y
|
||||
CONFIG_IDF_TARGET_ARCH_RISCV=y
|
||||
CONFIG_IDF_TARGET_ARCH="riscv"
|
||||
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_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_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_THREAD_STACK_SIZE=32768
|
||||
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_DRAW_SW_COMPLEX=y
|
||||
# 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_ASM_NONE=y
|
||||
# CONFIG_LV_DRAW_SW_ASM_NEON is not set
|
||||
|
||||
@ -14,17 +14,17 @@
|
||||
<span class="text-[13px] font-semibold">Button</span>
|
||||
<span class="text-[11px] text-muted mt-0.5 block">Aktion</span>
|
||||
</button>
|
||||
<button :class="btnClass" @click="store.addWidget('led')">
|
||||
<span class="material-symbols-outlined text-[24px]">light_mode</span>
|
||||
<span class="text-[10px]">LED</span>
|
||||
<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="text-[13px] font-semibold">LED</span>
|
||||
<span class="text-[11px] text-muted mt-0.5 block">Indikator</span>
|
||||
</button>
|
||||
<button :class="btnClass" @click="store.addWidget('clock')">
|
||||
<span class="material-symbols-outlined text-[24px]">schedule</span>
|
||||
<span class="text-[10px]">Uhr</span>
|
||||
<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="text-[13px] font-semibold">Uhr</span>
|
||||
<span class="text-[11px] text-muted mt-0.5 block">Analog</span>
|
||||
</button>
|
||||
<button :class="btnClass" @click="store.addWidget('icon')">
|
||||
<span class="material-symbols-outlined text-[24px]">image</span>
|
||||
<span class="text-[10px]">Icon</span>
|
||||
<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="text-[13px] font-semibold">Icon</span>
|
||||
<span class="text-[11px] text-muted mt-0.5 block">Symbol</span>
|
||||
</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')">
|
||||
<span class="text-[13px] font-semibold">Tabs</span>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user