Implemented history writer

This commit is contained in:
Martino Ferrari
2026-06-14 14:09:51 +02:00
parent f25bd7f08e
commit dd7dd22cb0
27 changed files with 1537 additions and 104 deletions
+38
View File
@@ -503,6 +503,8 @@ void App::handleJSON(const std::string& json) {
else if (type == "stats") { onStats(json); }
else if (type == "triggerState") { onTriggerState(json); }
else if (type == "zoom") { onZoom(json); }
else if (type == "historyZoom") { onHistoryZoom(json); }
else if (type == "historyInfo") { onHistoryInfo(json); }
else if (type == "maxPointsUpdated") { onMaxPointsUpdated(json); }
/* pong: ignore */
}
@@ -705,4 +707,40 @@ void App::onMaxPointsUpdated(const std::string& json) {
}
}
/* ---- History ------------------------------------------------------------ */
void App::onHistoryZoom(const std::string& json) {
ZoomResponse resp;
if (!ParseZoom(json, resp)) { return; }
/* Route the reply to the plot that requested it (via histZoomCache_) */
for (int i = 0; i < kMaxPlotSlots; i++) {
auto& hc = histZoomCache_[i];
if (hc.pending && hc.reqId == resp.reqId) {
hc.signals = std::move(resp.signals);
hc.t0 = hc.reqT0;
hc.t1 = hc.reqT1;
hc.valid = true;
hc.pending = false;
return;
}
}
}
void App::onHistoryInfo(const std::string& json) {
ParseHistoryInfo(json, historyInfo_);
}
void App::requestHistoryZoom(int plotIdx, double t0, double t1,
const std::string& signalsCsv) {
if (plotIdx < 0 || plotIdx >= kMaxPlotSlots) { return; }
if (!ws_.isConnected() || signalsCsv.empty()) { return; }
auto& hc = histZoomCache_[plotIdx];
hc.reqId = nextZoomReqId_++;
hc.reqT0 = t0;
hc.reqT1 = t1;
hc.pending = true;
ws_.sendText(BuildHistoryZoom(hc.reqId, t0, t1, 2400, signalsCsv));
}
} /* namespace StreamHubClient */
+18
View File
@@ -232,6 +232,18 @@ public:
/** @brief Full key "src:sig" for an assignment (empty if invalid). */
std::string slotKey(const PlotAssignment& a) const;
/* ---- History ------------------------------------------------------------ */
/** @brief History info received from the hub. */
const HistoryInfoMsg& historyInfo() const { return historyInfo_; }
/** @brief Send a historyZoom request. */
void requestHistoryZoom(int plotIdx, double t0, double t1,
const std::string& signalsCsv);
/** @brief Hi-res history zoom cache (per plot, parallel to zoomCache_). */
PlotZoomCache& histZoomCache(int i) { return histZoomCache_[i]; }
/** @brief True if the stats panel is open. */
bool& showStats() { return showStats_; }
bool& showAddSrc() { return showAddSrc_; }
@@ -253,6 +265,8 @@ private:
void onStats(const std::string& json);
void onTriggerState(const std::string& json);
void onZoom(const std::string& json);
void onHistoryZoom(const std::string& json);
void onHistoryInfo(const std::string& json);
void onMaxPointsUpdated(const std::string& json);
/* ---- Rendering ------------------------------------------------------ */
@@ -296,8 +310,12 @@ private:
/* Zoom history + hi-res zoom cache */
std::vector<std::pair<double,double>> zoomHist_[kMaxPlotSlots];
PlotZoomCache zoomCache_[kMaxPlotSlots];
PlotZoomCache histZoomCache_[kMaxPlotSlots];
uint32_t nextZoomReqId_ = 1;
/* History info from the hub */
HistoryInfoMsg historyInfo_;
/* UI state */
bool showStats_ = false;
bool showAddSrc_ = false;
+2 -1
View File
@@ -30,5 +30,6 @@
#define ICON_FA_CHEVRON_RIGHT ">"
#define ICON_FA_ARROW_TREND_UP "/\\"
#define ICON_FA_ARROW_TREND_DOWN "\\/"
#define ICON_FA_ARROWS_UP_DOWN "/\\\\/"
#define ICON_FA_ARROWS_UP_DOWN "/\\\\/"
#define ICON_FA_CLOCK_ROTATE_LEFT "<-"
#endif
+40 -4
View File
@@ -204,6 +204,13 @@ void RenderPlotPanel(App& app, int plotIdx, bool& paused) {
zc.t0 <= app.plotXMin(plotIdx) + 1e-9 &&
zc.t1 >= app.plotXMax(plotIdx) - 1e-9));
/* History zoom cache (disk-backed, for ranges beyond in-memory ring) */
auto& hc = app.histZoomCache(plotIdx);
const bool useHistData = !trigView && !live && !useZoomData &&
hc.valid &&
hc.t0 <= app.plotXMin(plotIdx) + 1e-9 &&
hc.t1 >= app.plotXMax(plotIdx) - 1e-9;
/* Fill tStore/vStore[si] from the frozen view (paused) or the live ring */
auto readBase = [&](size_t si, const Signal& sig, const std::string& key) {
if (paused && snap.valid) {
@@ -249,6 +256,18 @@ void RenderPlotPanel(App& app, int plotIdx, bool& paused) {
if (!found) {
readBase(si, sig, key);
}
} else if (useHistData) {
bool found = false;
for (const auto& hs : hc.signals) {
if (hs.name != key) continue;
tStore[si] = hs.t;
vStore[si] = hs.v;
found = true;
break;
}
if (!found) {
readBase(si, sig, key);
}
} else {
readBase(si, sig, key);
}
@@ -375,6 +394,13 @@ void RenderPlotPanel(App& app, int plotIdx, bool& paused) {
}
}
/* History indicator */
if (useHistData) {
ImGui::SameLine();
ImGui::TextColored(ImVec4(0.980f,0.702f,0.529f,1.f),
ICON_FA_CLOCK_ROTATE_LEFT " HIST");
}
/* Norm mode + cursors toggles */
ImGui::SameLine();
{
@@ -659,7 +685,9 @@ void RenderPlotPanel(App& app, int plotIdx, bool& paused) {
}
} else if (!live) {
/* Non-live: debounced — every zoom/pan settles into one
* request, so each zoom level gets fresh resolution. */
* request, so each zoom level gets fresh resolution.
* If the range is covered by the in-memory ring, use regular
* zoom; otherwise try historyZoom (disk-backed). */
static double rangeChangedAt[kPlotSlotsMax] = {};
static double lastT0[kPlotSlotsMax] = {}, lastT1[kPlotSlotsMax] = {};
const double now = ImGui::GetTime();
@@ -670,9 +698,17 @@ void RenderPlotPanel(App& app, int plotIdx, bool& paused) {
rangeChangedAt[plotIdx] = now;
} else if (rangeChangedAt[plotIdx] > 0.0 &&
now - rangeChangedAt[plotIdx] > 0.35 &&
!zc.pending &&
!(zc.valid && zc.t0 == t0 && zc.t1 == t1)) {
if (!csv.empty()) { app.requestZoom(plotIdx, t0, t1, csv); }
!csv.empty()) {
/* Try regular zoom first */
if (!zc.pending && !(zc.valid && zc.t0 == t0 && zc.t1 == t1)) {
app.requestZoom(plotIdx, t0, t1, csv);
}
/* Also try history zoom if history is available */
const auto& hi = app.historyInfo();
if (hi.enabled && !hc.pending &&
!(hc.valid && hc.t0 == t0 && hc.t1 == t1)) {
app.requestHistoryZoom(plotIdx, t0, t1, csv);
}
rangeChangedAt[plotIdx] = 0.0;
}
}
+73
View File
@@ -253,6 +253,23 @@ std::string BuildZoom(uint32_t reqId, double t0, double t1, int n,
return out;
}
std::string BuildHistoryZoom(uint32_t reqId, double t0, double t1, int n,
const std::string& signalsCsv) {
char head[256];
snprintf(head, sizeof(head),
"{\"type\":\"historyZoom\",\"reqId\":%u,"
"\"t0\":%.17g,\"t1\":%.17g,\"n\":%d,\"signals\":\"",
static_cast<unsigned>(reqId), t0, t1, n);
std::string out(head);
out += signalsCsv;
out += "\"}";
return out;
}
std::string BuildHistoryInfo() {
return "{\"type\":\"historyInfo\"}";
}
std::string BuildSetMaxPoints(uint32_t n) {
char buf[128];
snprintf(buf, sizeof(buf), "{\"type\":\"setMaxPoints\",\"maxPoints\":%u}",
@@ -530,4 +547,60 @@ bool ParseMaxPointsUpdated(const std::string& json, uint32_t& maxPoints) {
return true;
}
/* ---- historyInfo -------------------------------------------------------- */
bool ParseHistoryInfo(const std::string& json, HistoryInfoMsg& out) {
out.signals.clear();
out.enabled = false;
char tmp[16] = "";
jsonGetStr(json.c_str(), "enabled", tmp, sizeof(tmp));
out.enabled = (strcmp(tmp, "true") == 0);
double df = 0.0;
jsonGetDouble(json.c_str(), "durationHours", df);
out.durationHours = df;
double decF = 0.0;
jsonGetDouble(json.c_str(), "decimation", decF);
out.decimation = static_cast<uint32_t>(decF);
/* Parse "signals":{...} block — same key iteration as zoom */
const char *sig = strstr(json.c_str(), "\"signals\":{");
if (!sig) { return out.enabled; }
sig += 11; /* past '{"signals":{' */
while (*sig) {
/* Find next "key":{ */
const char *q = strchr(sig, '"');
if (!q) { break; }
q++;
const char *qEnd = strchr(q, '"');
if (!qEnd) { break; }
HistorySignalRange hr;
hr.key = std::string(q, qEnd);
/* Find the nested object */
const char *brace = strchr(qEnd, '{');
if (!brace) { break; }
jsonGetDouble(brace, "t0", hr.t0);
jsonGetDouble(brace, "t1", hr.t1);
double cntF = 0.0, capF = 0.0;
jsonGetDouble(brace, "count", cntF);
jsonGetDouble(brace, "capacity", capF);
hr.count = static_cast<uint32_t>(cntF);
hr.capacity = static_cast<uint32_t>(capF);
out.signals.push_back(hr);
/* Advance past the closing brace */
const char *end = strchr(brace, '}');
if (!end) { break; }
sig = end + 1;
}
return true;
}
} /* namespace StreamHubClient */
+20
View File
@@ -161,6 +161,10 @@ std::string BuildSetTrigger(const std::string& signalKey, const std::string& edg
/** signalsCsv: comma-separated full keys "src:sig,src:sig2"; n<=0 → raw. */
std::string BuildZoom(uint32_t reqId, double t0, double t1, int n,
const std::string& signalsCsv);
/** History zoom: same shape as zoom but reads from disk history. */
std::string BuildHistoryZoom(uint32_t reqId, double t0, double t1, int n,
const std::string& signalsCsv);
std::string BuildHistoryInfo();
std::string BuildSetMaxPoints(uint32_t n);
/*---------------------------------------------------------------------------*/
@@ -187,6 +191,22 @@ bool ParseTriggerState(const std::string& json, TriggerStateMsg& out);
/** @brief Parse a "zoom" response. */
bool ParseZoom(const std::string& json, ZoomResponse& out);
/** @brief Parse a "historyInfo" event. */
struct HistorySignalRange {
std::string key;
double t0 = 0.0, t1 = 0.0;
uint32_t count = 0, capacity = 0;
};
struct HistoryInfoMsg {
bool enabled = false;
double durationHours = 0.0;
uint32_t decimation = 0;
std::vector<HistorySignalRange> signals;
};
bool ParseHistoryInfo(const std::string& json, HistoryInfoMsg& out);
/** @brief Parse a "maxPointsUpdated" event. */
bool ParseMaxPointsUpdated(const std::string& json, uint32_t& maxPoints);
@@ -209,7 +209,7 @@ StreamHubClient: CMakeFiles/StreamHubClient.dir/StatsPanel.cpp.o
StreamHubClient: CMakeFiles/StreamHubClient.dir/build.make
StreamHubClient: CMakeFiles/StreamHubClient.dir/compiler_depend.ts
StreamHubClient: libimgui_lib.a
StreamHubClient: /usr/lib/libSDL2-2.0.so.0.3200.68
StreamHubClient: /usr/lib/libSDL2-2.0.so.0.3200.70
StreamHubClient: /usr/lib/libGLX.so
StreamHubClient: /usr/lib/libOpenGL.so
StreamHubClient: CMakeFiles/StreamHubClient.dir/link.txt
@@ -2451,7 +2451,7 @@ StreamHubClient
/usr/lib/libGLX.so
/usr/lib/libGLdispatch.so.0
/usr/lib/libOpenGL.so
/usr/lib/libSDL2-2.0.so.0.3200.68
/usr/lib/libSDL2-2.0.so.0.3200.70
/usr/lib/libX11.so.6
/usr/lib/libXau.so.6
/usr/lib/libXdmcp.so.6
@@ -2442,7 +2442,7 @@ StreamHubClient: /usr/lib/Scrt1.o \
/usr/lib/libGLX.so \
/usr/lib/libGLdispatch.so.0 \
/usr/lib/libOpenGL.so \
/usr/lib/libSDL2-2.0.so.0.3200.68 \
/usr/lib/libSDL2-2.0.so.0.3200.70 \
/usr/lib/libX11.so.6 \
/usr/lib/libXau.so.6 \
/usr/lib/libXdmcp.so.6 \
@@ -2487,7 +2487,7 @@ CMakeFiles/StreamHubClient.dir/StatsPanel.cpp.o:
/usr/lib/libXau.so.6:
/usr/lib/libSDL2-2.0.so.0.3200.68:
/usr/lib/libSDL2-2.0.so.0.3200.70:
/usr/lib/libGLdispatch.so.0:
@@ -1 +1 @@
/usr/bin/c++ -O3 -DNDEBUG -Wl,--dependency-file=CMakeFiles/StreamHubClient.dir/link.d CMakeFiles/StreamHubClient.dir/main.cpp.o CMakeFiles/StreamHubClient.dir/App.cpp.o CMakeFiles/StreamHubClient.dir/WSClient.cpp.o CMakeFiles/StreamHubClient.dir/Protocol.cpp.o CMakeFiles/StreamHubClient.dir/SourcePanel.cpp.o CMakeFiles/StreamHubClient.dir/PlotPanel.cpp.o CMakeFiles/StreamHubClient.dir/TriggerPanel.cpp.o CMakeFiles/StreamHubClient.dir/StatsPanel.cpp.o -o StreamHubClient libimgui_lib.a /usr/lib/libSDL2-2.0.so.0.3200.68 -lpthread /usr/lib/libGLX.so /usr/lib/libOpenGL.so
/usr/bin/c++ -O3 -DNDEBUG -Wl,--dependency-file=CMakeFiles/StreamHubClient.dir/link.d CMakeFiles/StreamHubClient.dir/main.cpp.o CMakeFiles/StreamHubClient.dir/App.cpp.o CMakeFiles/StreamHubClient.dir/WSClient.cpp.o CMakeFiles/StreamHubClient.dir/Protocol.cpp.o CMakeFiles/StreamHubClient.dir/SourcePanel.cpp.o CMakeFiles/StreamHubClient.dir/PlotPanel.cpp.o CMakeFiles/StreamHubClient.dir/TriggerPanel.cpp.o CMakeFiles/StreamHubClient.dir/StatsPanel.cpp.o -o StreamHubClient libimgui_lib.a /usr/lib/libSDL2-2.0.so.0.3200.70 -lpthread /usr/lib/libGLX.so /usr/lib/libOpenGL.so
Binary file not shown.