#include "HistoryStore.hpp" #include "esp_log.h" #include #include #include static const char* TAG = "HistoryStore"; // 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. HistoryStore& HistoryStore::instance() { static HistoryStore inst; return inst; } HistoryStore::HistoryStore() { mutex_ = xSemaphoreCreateMutex(); } HistoryStore::~HistoryStore() { if (mutex_) { vSemaphoreDelete(mutex_); } } bool HistoryStore::isNumericSource(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; } bool HistoryStore::keysEqual(const SeriesKey& a, const SeriesKey& b) { return a.addr == b.addr && a.source == b.source; } HistoryStore::HistorySeries* HistoryStore::findSeries(uint16_t groupAddr, TextSource source) { SeriesKey key{groupAddr, source}; for (size_t i = 0; i < series_.size(); ++i) { if (series_[i].active && keysEqual(series_[i].key, key)) { return &series_[i]; } } return nullptr; } const HistoryStore::HistorySeries* HistoryStore::findSeries(uint16_t groupAddr, TextSource source) const { SeriesKey key{groupAddr, source}; for (size_t i = 0; i < series_.size(); ++i) { if (series_[i].active && keysEqual(series_[i].key, key)) { return &series_[i]; } } return nullptr; } HistoryStore::HistorySeries* HistoryStore::findSeriesByKey(const SeriesKey& key) { for (size_t i = 0; i < series_.size(); ++i) { if (keysEqual(series_[i].key, key)) { return &series_[i]; } } return nullptr; } const HistoryStore::HistorySeries* HistoryStore::findSeriesByKey(const SeriesKey& key) const { for (size_t i = 0; i < series_.size(); ++i) { if (keysEqual(series_[i].key, key)) { return &series_[i]; } } return nullptr; } void HistoryStore::configureFromConfig(const GuiConfig& config) { xSemaphoreTake(mutex_, portMAX_DELAY); std::array needed = {}; size_t neededCount = 0; auto addSeries = [&](uint16_t addr, TextSource source) { if (addr == 0 || !isNumericSource(source)) return; SeriesKey key{addr, source}; for (size_t i = 0; i < neededCount; ++i) { if (keysEqual(needed[i], key)) return; } if (neededCount >= HISTORY_MAX_SERIES) return; needed[neededCount++] = key; }; for (size_t s = 0; s < config.screenCount; ++s) { const ScreenConfig& screen = config.screens[s]; for (size_t i = 0; i < screen.widgetCount; ++i) { const WidgetConfig& w = screen.widgets[i]; if (w.type != WidgetType::CHART) continue; uint8_t count = w.chartSeriesCount; if (count > CHART_MAX_SERIES) count = CHART_MAX_SERIES; for (uint8_t si = 0; si < count; ++si) { addSeries(w.chartKnxAddress[si], w.chartTextSource[si]); } } } std::array keep = {}; for (size_t i = 0; i < neededCount; ++i) { const SeriesKey& key = needed[i]; HistorySeries* existing = findSeriesByKey(key); if (existing) { size_t idx = static_cast(existing - series_.data()); 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) { slot = &series_[si]; keep[si] = true; break; } } if (slot) { slot->key = key; slot->active = true; slot->hasLatest = false; } } for (size_t i = 0; i < series_.size(); ++i) { series_[i].active = keep[i]; } xSemaphoreGive(mutex_); } bool HistoryStore::isTracked(uint16_t groupAddr, TextSource source) const { xSemaphoreTake(mutex_, portMAX_DELAY); bool tracked = findSeries(groupAddr, source) != nullptr; xSemaphoreGive(mutex_); return tracked; } bool HistoryStore::updateLatest(uint16_t groupAddr, TextSource source, float value) { xSemaphoreTake(mutex_, portMAX_DELAY); HistorySeries* series = findSeries(groupAddr, source); if (!series) { xSemaphoreGive(mutex_); return false; } series->latestValue = value; series->hasLatest = true; xSemaphoreGive(mutex_); return true; } int64_t HistoryStore::now() const { return (int64_t)time(nullptr); } int32_t HistoryStore::periodSeconds(ChartPeriod period) const { return 3600; // Dummy } bool HistoryStore::fillChartSeries(uint16_t groupAddr, TextSource source, ChartPeriod period, int32_t* outValues, size_t outCount) const { if (!outValues || outCount == 0) return false; // 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(lrintf(series->latestValue)); } xSemaphoreGive(mutex_); for (size_t i = 0; i < outCount; ++i) { outValues[i] = val; } return (val != NO_POINT); } bool HistoryStore::tick() { return false; // Disabled } void HistoryStore::performAutoSave() { // Disabled } void HistoryStore::updateTimeOfDay(const tm& value) { // Disabled } void HistoryStore::updateDate(const tm& value) { // Disabled } void HistoryStore::updateDateTime(const tm& value) { // Disabled } void HistoryStore::clearAll() { // Disabled } void HistoryStore::saveToSdCard() { // Disabled } void HistoryStore::loadFromSdCard() { // Disabled }