214 lines
7.4 KiB
C++
214 lines
7.4 KiB
C++
/**
|
||
* @file Protocol.h
|
||
* @brief StreamHub wire protocol: binary frame parser + JSON builder/parser.
|
||
*/
|
||
|
||
#pragma once
|
||
|
||
#include "SignalBuffer.h"
|
||
|
||
#include <string>
|
||
#include <vector>
|
||
#include <functional>
|
||
#include <cstdint>
|
||
|
||
namespace StreamHubClient {
|
||
|
||
/* ── Signal metadata (mirrors UDPSSignalDescriptor fields used by UI) ─────── */
|
||
|
||
struct SignalMeta {
|
||
std::string name;
|
||
std::string unit;
|
||
int typeCode = 0;
|
||
int quantType = 0;
|
||
int timeMode = 0;
|
||
int timeSignalIdx = -1;
|
||
float samplingRate = 0.f;
|
||
double rangeMin = 0.0;
|
||
double rangeMax = 0.0;
|
||
uint32_t numRows = 1u;
|
||
uint32_t numCols = 1u;
|
||
uint32_t numElements = 1u; /* numRows × numCols */
|
||
};
|
||
|
||
/* ── Per-source statistics (hub "stats" broadcast, Go field names) ────────── */
|
||
|
||
struct SourceStats {
|
||
std::string state = "disconnected";
|
||
uint64_t totalReceived = 0;
|
||
uint64_t totalLost = 0;
|
||
double rateHz = 0.0;
|
||
double rateStdHz = 0.0;
|
||
double fragsPerCycle = 0.0;
|
||
double bytesPerCycle = 0.0;
|
||
double cycleAvgMs = 0.0;
|
||
double cycleStdMs = 0.0;
|
||
double cycleMinMs = 0.0;
|
||
double cycleMaxMs = 0.0;
|
||
double cycleHistMin = 0.0;
|
||
double cycleHistMax = 0.0;
|
||
double cycleHist[20] = {};
|
||
};
|
||
|
||
/* ── Source descriptor ────────────────────────────────────────────────────── */
|
||
|
||
struct SourceInfo {
|
||
std::string id;
|
||
std::string label;
|
||
std::string addr;
|
||
std::string state;
|
||
uint32_t port = 0;
|
||
};
|
||
|
||
/* ── Decoded push-frame signal data ──────────────────────────────────────── */
|
||
|
||
struct FrameSignal {
|
||
std::string name;
|
||
std::vector<double> t;
|
||
std::vector<double> v;
|
||
};
|
||
|
||
struct DataFrame {
|
||
std::string sourceId;
|
||
std::vector<FrameSignal> signals;
|
||
};
|
||
|
||
/* ── Zoom response ────────────────────────────────────────────────────────── */
|
||
|
||
struct ZoomSignal {
|
||
std::string name; /* full key "src:sig" */
|
||
std::vector<double> t;
|
||
std::vector<double> v;
|
||
};
|
||
|
||
struct ZoomResponse {
|
||
uint32_t reqId = 0;
|
||
std::vector<ZoomSignal> signals;
|
||
};
|
||
|
||
/* ── Trigger capture (binary frame version=2) ─────────────────────────────── */
|
||
|
||
struct CaptureSignal {
|
||
std::string key; /* full key "src:sig" */
|
||
std::vector<double> t;
|
||
std::vector<double> v;
|
||
};
|
||
|
||
struct CaptureFrame {
|
||
double trigTime = 0.0;
|
||
double preSec = 0.0;
|
||
double postSec = 0.0;
|
||
std::vector<CaptureSignal> signals;
|
||
};
|
||
|
||
/* ── Trigger state event ──────────────────────────────────────────────────── */
|
||
|
||
struct TriggerStateMsg {
|
||
std::string state; /* idle|armed|collecting|triggered */
|
||
std::string mode = "normal"; /* normal|single */
|
||
bool stopped = false;
|
||
bool hasTrigTime = false;
|
||
double trigTime = 0.0;
|
||
};
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* Binary frame parser */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
/**
|
||
* @brief Parse one StreamHub binary push frame.
|
||
*
|
||
* Format:
|
||
* [1] version | [1] idLen | [idLen] sourceId | [4] numSignals
|
||
* per signal: [2] keyLen | [key] | [4] pairCount | [N×8] t | [N×8] v
|
||
*
|
||
* @return false if the frame is malformed.
|
||
*/
|
||
bool ParseBinaryFrame(const uint8_t* buf, size_t len, DataFrame& out);
|
||
|
||
/**
|
||
* @brief Parse a version=2 trigger-capture binary frame.
|
||
*
|
||
* Format:
|
||
* [1] version=2 | [8] trigTime | [8] preSec | [8] postSec | [4] nSig
|
||
* per signal: [2] keyLen | [key] | [4] N | [N×8] t | [N×8] v
|
||
*
|
||
* @return false if the frame is malformed or version != 2.
|
||
*/
|
||
bool ParseCaptureFrame(const uint8_t* buf, size_t len, CaptureFrame& out);
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* JSON command builders */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
std::string BuildPing();
|
||
std::string BuildGetSources();
|
||
std::string BuildGetConfig(const std::string& sourceId);
|
||
std::string BuildGetStats();
|
||
std::string BuildAddSource(const std::string& label, const std::string& addr,
|
||
const std::string& multicastGroup = std::string(),
|
||
uint16_t dataPort = 0);
|
||
std::string BuildRemoveSource(const std::string& id);
|
||
std::string BuildArm();
|
||
std::string BuildDisarm();
|
||
std::string BuildRearm();
|
||
std::string BuildTrigStop(bool stopped);
|
||
std::string BuildSaveSources();
|
||
/** edge: "rising" | "falling" | "both"; mode: "normal" | "single". */
|
||
std::string BuildSetTrigger(const std::string& signalKey, const std::string& edge,
|
||
double threshold, double windowSec,
|
||
double prePercent, const std::string& mode);
|
||
/** 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);
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* JSON event parsers */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
/** @brief Extract the "type" field from a JSON text frame. */
|
||
std::string ParseType(const std::string& json);
|
||
|
||
/** @brief Parse a "sources" event. */
|
||
bool ParseSources(const std::string& json, std::vector<SourceInfo>& out);
|
||
|
||
/** @brief Parse a "config" event (one source). */
|
||
bool ParseConfig(const std::string& json, std::string& sourceId,
|
||
int& publishMode, std::vector<SignalMeta>& signals);
|
||
|
||
/** @brief Parse a "stats" event. */
|
||
bool ParseStats(const std::string& json,
|
||
std::vector<std::pair<std::string, SourceStats>>& out);
|
||
|
||
/** @brief Parse a "triggerState" event. */
|
||
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);
|
||
|
||
} /* namespace StreamHubClient */
|