Files
2026-06-26 09:11:10 +02:00

300 lines
11 KiB
C++

/**
* @file Hub.cpp
*/
#include "Hub.h"
#include "Theme.h"
#include <QDateTime>
#include <algorithm>
using namespace StreamHubClient;
namespace shq {
Hub::Hub(QObject* parent) : QObject(parent) {
connect(&ws_, &WsClient::connectedChanged, this, &Hub::onConnected);
connect(&ws_, &WsClient::textReceived, this, &Hub::onText);
connect(&ws_, &WsClient::binaryReceived, this, &Hub::onBinary);
}
void Hub::start(const QString& host, uint16_t port) {
ws_.connectTo(host, port);
}
void Hub::reconnect(const QString& host, uint16_t port) {
ws_.reconnectTo(host, port);
}
/* ── connection ──────────────────────────────────────────────────────────── */
void Hub::onConnected(bool connected) {
if (connected) {
sendGetSources();
sendGetStats();
sendHistoryInfo();
}
Q_EMIT connectedChanged(connected);
}
/* ── dispatch ────────────────────────────────────────────────────────────── */
void Hub::onText(const QString& jsonQ) {
const std::string json = jsonQ.toStdString();
const std::string type = ParseType(json);
if (type == "sources") { onSources(json); }
else if (type == "config") { onConfig(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 */
}
void Hub::onBinary(const QByteArray& bytes) {
if (bytes.isEmpty()) { return; }
const uint8_t* data = reinterpret_cast<const uint8_t*>(bytes.constData());
const size_t len = static_cast<size_t>(bytes.size());
if (data[0] == 2u) { /* trigger capture frame */
CaptureFrame cf;
if (ParseCaptureFrame(data, len, cf)) {
capture_ = std::move(cf);
hasCapture_ = true;
trigger_.status = "triggered";
trigger_.trigTime = capture_.trigTime;
trigger_.hasTrigTime = true;
Q_EMIT captureReceived();
}
return;
}
DataFrame frame; /* v1 live push */
if (!ParseBinaryFrame(data, len, frame)) { return; }
int srcIdx = findSource(frame.sourceId);
if (srcIdx < 0) { return; }
Source& src = sources_[srcIdx];
for (const auto& fs : frame.signals) {
int sigIdx = findSignal(src, fs.name);
if (sigIdx < 0) { continue; }
size_t n = std::min(fs.t.size(), fs.v.size());
auto& buf = src.signals[sigIdx].buf;
for (size_t i = 0; i < n; i++) { buf.push(fs.t[i], fs.v[i]); }
}
}
/* ── JSON handlers (port of App.cpp) ─────────────────────────────────────── */
void Hub::onSources(const std::string& json) {
std::vector<SourceInfo> infos;
ParseSources(json, infos);
for (const auto& info : infos) {
int idx = findSource(info.id);
if (idx < 0) {
Source s;
s.id = info.id; s.label = info.label; s.addr = info.addr;
s.port = info.port; s.state = info.state;
sources_.push_back(std::move(s));
} else {
sources_[idx].state = info.state;
sources_[idx].label = info.label;
}
}
sources_.erase(std::remove_if(sources_.begin(), sources_.end(),
[&infos](const Source& s) {
for (const auto& i : infos) { if (i.id == s.id) { return false; } }
return true;
}), sources_.end());
/* Request config for any source not yet configured. */
for (auto& s : sources_) {
if (!s.configured) { sendGetConfig(s.id); }
}
Q_EMIT sourcesChanged();
}
void Hub::onConfig(const std::string& json) {
std::string sourceId;
int publishMode = 0;
std::vector<SignalMeta> metas;
if (!ParseConfig(json, sourceId, publishMode, metas)) { return; }
int idx = findSource(sourceId);
if (idx < 0) { return; }
Source& src = sources_[idx];
src.configured = true;
src.publishMode = publishMode;
for (size_t m = 0; m < metas.size(); m++) {
int si = findSignal(src, metas[m].name);
if (si < 0) {
SignalView sig;
sig.meta = metas[m];
sig.color = tracePalette(static_cast<int>(src.signals.size()));
src.signals.push_back(std::move(sig));
} else {
src.signals[si].meta = metas[m];
}
}
Q_EMIT configChanged(QString::fromStdString(sourceId));
}
void Hub::onStats(const std::string& json) {
std::vector<std::pair<std::string, SourceStats>> statsVec;
ParseStats(json, statsVec);
for (const auto& kv : statsVec) {
int idx = findSource(kv.first);
if (idx >= 0) {
sources_[idx].stats = kv.second;
sources_[idx].state = kv.second.state;
}
}
Q_EMIT statsChanged();
}
void Hub::onTriggerState(const std::string& json) {
TriggerStateMsg msg;
if (!ParseTriggerState(json, msg)) { return; }
trigger_.status = msg.state;
trigger_.stopped = msg.stopped;
if (msg.hasTrigTime) {
trigger_.trigTime = msg.trigTime;
trigger_.hasTrigTime = true;
}
if (msg.state == "idle") { trigger_.hasTrigTime = false; }
Q_EMIT triggerStateChanged();
}
static void routeZoom(PlotZoomCache* caches, const ZoomResponse& resp,
int& outPlot) {
outPlot = -1;
for (int i = 0; i < kMaxPlotSlots; i++) {
auto& zc = caches[i];
if (zc.pending && zc.reqId == resp.reqId) {
double dataT0 = 1e300, dataT1 = -1e300;
bool anyData = false;
for (const auto& zs : resp.signals) {
for (double tv : zs.t) {
if (tv < dataT0) { dataT0 = tv; }
if (tv > dataT1) { dataT1 = tv; }
anyData = true;
}
}
if (anyData) {
zc.pts = resp.signals;
zc.t0 = dataT0; zc.t1 = dataT1; zc.valid = true;
}
zc.pending = false;
outPlot = i;
return;
}
}
}
void Hub::onZoom(const std::string& json) {
ZoomResponse resp;
if (!ParseZoom(json, resp)) { return; }
int plot = -1;
routeZoom(zoomCache_, resp, plot);
if (plot >= 0) { Q_EMIT zoomReceived(plot); }
}
void Hub::onHistoryZoom(const std::string& json) {
ZoomResponse resp;
if (!ParseZoom(json, resp)) { return; }
int plot = -1;
routeZoom(histZoomCache_, resp, plot);
if (plot >= 0) { Q_EMIT historyZoomReceived(plot); }
}
void Hub::onHistoryInfo(const std::string& json) {
ParseHistoryInfo(json, historyInfo_);
Q_EMIT historyInfoChanged();
}
void Hub::onMaxPointsUpdated(const std::string& json) {
uint32_t mp = 0;
ParseMaxPointsUpdated(json, mp);
if (mp >= 2) {
maxPoints_ = mp;
for (auto& src : sources_) {
for (auto& sig : src.signals) { sig.buf.setCapacity(mp); }
}
Q_EMIT maxPointsChanged(mp);
}
}
/* ── helpers ─────────────────────────────────────────────────────────────── */
int Hub::findSource(const std::string& id) const {
for (int i = 0; i < static_cast<int>(sources_.size()); i++) {
if (sources_[i].id == id) { return i; }
}
return -1;
}
int Hub::findSignal(const Source& src, const std::string& name) const {
for (int i = 0; i < static_cast<int>(src.signals.size()); i++) {
if (src.signals[i].meta.name == name) { return i; }
}
return -1;
}
std::string Hub::slotKey(const PlotAssignment& a) const {
if (a.sourceIdx < 0 || a.sourceIdx >= static_cast<int>(sources_.size())) {
return std::string();
}
const Source& src = sources_[a.sourceIdx];
if (a.signalIdx < 0 || a.signalIdx >= static_cast<int>(src.signals.size())) {
return std::string();
}
return src.id + ":" + src.signals[a.signalIdx].meta.name;
}
/* ── commands ────────────────────────────────────────────────────────────── */
void Hub::sendGetSources() { ws_.sendText(BuildGetSources()); }
void Hub::sendGetStats() { ws_.sendText(BuildGetStats()); }
void Hub::sendGetConfig(const std::string& id) { ws_.sendText(BuildGetConfig(id)); }
void Hub::sendHistoryInfo(){ ws_.sendText(BuildHistoryInfo()); }
void Hub::sendAddSource(const std::string& label, const std::string& addr,
const std::string& mcast, uint16_t dataPort) {
ws_.sendText(BuildAddSource(label, addr, mcast, dataPort));
}
void Hub::sendRemoveSource(const std::string& id) {
ws_.sendText(BuildRemoveSource(id));
}
void Hub::sendSaveSources() { ws_.sendText(BuildSaveSources()); }
void Hub::sendSetMaxPoints(uint32_t n) { ws_.sendText(BuildSetMaxPoints(n)); }
void Hub::sendSetTrigger(const std::string& key, const std::string& edge,
double threshold, double windowSec, double prePercent,
const std::string& mode) {
ws_.sendText(BuildSetTrigger(key, edge, threshold, windowSec, prePercent, mode));
}
void Hub::sendArm() { ws_.sendText(BuildArm()); }
void Hub::sendDisarm() { ws_.sendText(BuildDisarm()); }
void Hub::sendRearm() { ws_.sendText(BuildRearm()); }
void Hub::sendTrigStop(bool s) { ws_.sendText(BuildTrigStop(s)); }
void Hub::requestZoom(int plotIdx, double t0, double t1, const std::string& csv) {
if (plotIdx < 0 || plotIdx >= kMaxPlotSlots) { return; }
if (!ws_.isConnected() || csv.empty()) { return; }
auto& zc = zoomCache_[plotIdx];
zc.reqId = nextZoomReqId_++;
zc.reqT0 = t0; zc.reqT1 = t1; zc.pending = true;
ws_.sendText(BuildZoom(zc.reqId, t0, t1, 2400, csv));
}
void Hub::requestHistoryZoom(int plotIdx, double t0, double t1,
const std::string& csv) {
if (plotIdx < 0 || plotIdx >= kMaxPlotSlots) { return; }
if (!ws_.isConnected() || csv.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, csv));
}
} /* namespace shq */