knxdisplay/main/HistoryStore.hpp
2026-01-29 19:33:12 +01:00

128 lines
3.5 KiB
C++

#pragma once
#include "WidgetConfig.hpp"
#include <array>
#include <climits>
#include <cstdint>
#include <ctime>
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();
bool fillChartSeries(uint16_t groupAddr, TextSource source, ChartPeriod period,
int32_t* outValues, size_t outCount) const;
int64_t now() const;
bool isTimeSynced() const { return timeSynced_; }
void updateTimeOfDay(const struct tm& value);
void updateDate(const struct tm& value);
void updateDateTime(const struct tm& value);
void loadFromSdCard();
void saveToSdCard();
static constexpr size_t CHART_POINT_COUNT = 120;
static constexpr int32_t NO_POINT = INT32_MAX;
private:
HistoryStore();
struct SeriesKey {
uint16_t addr = 0;
TextSource source = TextSource::STATIC;
};
struct HistoryPoint {
int32_t ts = 0;
float value = 0.0f;
};
template <size_t N>
struct RingBuffer {
std::array<HistoryPoint, N> 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 <typename Fn>
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 = 12;
static constexpr size_t HISTORY_FINE_CAP = 720;
static constexpr size_t HISTORY_COARSE_CAP = 720;
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<HISTORY_FINE_CAP> fine;
RingBuffer<HISTORY_COARSE_CAP> 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;
int64_t buildEpoch() const;
bool applyTimeSync();
void clearAll();
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<HistorySeries, HISTORY_MAX_SERIES> series_ = {};
size_t seriesCount_ = 0;
bool timeSynced_ = false;
bool hasDate_ = false;
bool hasTime_ = false;
int year_ = 0;
int month_ = 0;
int day_ = 0;
int hour_ = 0;
int minute_ = 0;
int second_ = 0;
int64_t baseEpoch_ = 0;
int64_t baseMono_ = 0;
bool dirty_ = false;
int64_t lastSaveMonoUs_ = 0;
bool dataEpoch_ = false;
};