#pragma once #include "WidgetConfig.hpp" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include #include #include #include class HistoryStore { public: static HistoryStore& instance(); void configureFromConfig(const GuiConfig& config); bool updateLatest(uint16_t groupAddr, TextSource source, float value); bool isTracked(uint16_t groupAddr, TextSource source) const; bool tick(); void performAutoSave(); bool fillChartSeries(uint16_t groupAddr, TextSource source, ChartPeriod period, int32_t* outValues, size_t outCount) const; int64_t now() const; bool isTimeSynced() const { return now() > 1577836800LL; } void updateTimeOfDay(const tm& value); void updateDate(const tm& value); void updateDateTime(const tm& value); void clearAll(); void loadFromSdCard(); void saveToSdCard(); static constexpr size_t CHART_POINT_COUNT = 120; static constexpr int32_t NO_POINT = INT32_MAX; private: HistoryStore(); ~HistoryStore(); struct SeriesKey { uint16_t addr = 0; TextSource source = TextSource::STATIC; }; struct HistoryPoint { int32_t ts = 0; float value = 0.0f; }; template struct RingBuffer { std::array points = {}; size_t count = 0; size_t head = 0; void clear() { count = 0; head = 0; } void push(const HistoryPoint& point) { points[head] = point; head = (head + 1) % N; if (count < N) { count++; } } template void forEach(Fn&& fn) const { if (count == 0) return; size_t start = (head + N - count) % N; for (size_t i = 0; i < count; ++i) { const HistoryPoint& p = points[(start + i) % N]; fn(p); } } }; static constexpr size_t HISTORY_MAX_SERIES = 4; static constexpr size_t HISTORY_FINE_CAP = 360; static constexpr size_t HISTORY_COARSE_CAP = 360; static constexpr int32_t HISTORY_FINE_INTERVAL = 120; static constexpr int32_t HISTORY_COARSE_INTERVAL = 3600; static constexpr int32_t HISTORY_MONTH_SECONDS = 30 * 24 * 3600; struct HistorySeries { SeriesKey key; bool active = false; bool hasLatest = false; float latestValue = 0.0f; int32_t latestTs = 0; RingBuffer fine; RingBuffer coarse; int32_t lastFineSampleTs = 0; int32_t lastCoarseSampleTs = 0; }; static bool isNumericSource(TextSource source); static bool keysEqual(const SeriesKey& a, const SeriesKey& b); int32_t periodSeconds(ChartPeriod period) const; HistorySeries* findSeries(uint16_t groupAddr, TextSource source); const HistorySeries* findSeries(uint16_t groupAddr, TextSource source) const; HistorySeries* findSeriesByKey(const SeriesKey& key); const HistorySeries* findSeriesByKey(const SeriesKey& key) const; std::array series_ = {}; size_t seriesCount_ = 0; bool dirty_ = false; int64_t lastSaveMonoUs_ = 0; mutable SemaphoreHandle_t mutex_ = nullptr; };