Implemented qt port + e2e
This commit is contained in:
@@ -0,0 +1,299 @@
|
||||
/**
|
||||
* @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 */
|
||||
Reference in New Issue
Block a user