241 lines
8.4 KiB
C++
241 lines
8.4 KiB
C++
/**
|
||
* @file StreamHub.h
|
||
* @brief Top-level orchestrator: N UDP sources, M WebSocket clients, push loop, trigger.
|
||
*
|
||
* StreamHub owns:
|
||
* - Up to kMaxSessions UDPSourceSessions (one per MARTe2 UDPStreamer source)
|
||
* - WSServer (multi-client WebSocket server)
|
||
* - TriggerEngine (continuous / armed / triggered FSM)
|
||
*
|
||
* The push loop runs at pushRateHz Hz and:
|
||
* 1. For each configured session, reads ring buffers, applies LTTB decimation,
|
||
* serializes a binary data frame, broadcasts to all WS clients.
|
||
* 2. At statsRateHz intervals, serializes a JSON stats frame and broadcasts.
|
||
*
|
||
* Commands are received as JSON text frames from any connected WS client via the
|
||
* WSCommandCallback interface.
|
||
*/
|
||
|
||
#ifndef STREAMHUB_H_
|
||
#define STREAMHUB_H_
|
||
|
||
#include "CompilerTypes.h"
|
||
#include "FastPollingMutexSem.h"
|
||
#include "StreamString.h"
|
||
#include "StructuredDataI.h"
|
||
#include "Threads.h"
|
||
|
||
#include "UDPSourceSession.h"
|
||
#include "WSServer.h"
|
||
#include "TriggerEngine.h"
|
||
#include "HistoryWriter.h"
|
||
|
||
namespace StreamHub {
|
||
|
||
using MARTe::uint8;
|
||
using MARTe::uint16;
|
||
using MARTe::uint32;
|
||
using MARTe::uint64;
|
||
using MARTe::float64;
|
||
using MARTe::StreamString;
|
||
using MARTe::FastPollingMutexSem;
|
||
using MARTe::StructuredDataI;
|
||
|
||
/** Maximum number of simultaneously connected UDPStreamer sources. */
|
||
static const uint32 kMaxSessions = 32u;
|
||
|
||
/**
|
||
* @brief Top-level StreamHub orchestrator.
|
||
*
|
||
* Usage:
|
||
* StreamHub hub;
|
||
* hub.Initialise(cfg); // parse WSPort, MaxPoints, PushRate, Sources
|
||
* hub.Run(); // blocking push loop; returns on Stop()
|
||
*/
|
||
class StreamHub : public WSCommandCallback {
|
||
public:
|
||
|
||
StreamHub();
|
||
virtual ~StreamHub();
|
||
|
||
/**
|
||
* @brief Configure the hub from a MARTe2 StructuredDataI.
|
||
*
|
||
* Expected keys:
|
||
* WSPort (uint32, default 8090)
|
||
* MaxPoints (uint32, default 20000) — ring buffer capacity per signal
|
||
* PushRate (uint32, default 30) — push loop rate in Hz
|
||
* MaxPushPoints (uint32, default 500) — LTTB threshold for live push
|
||
* StatsRate (uint32, default 1) — stats broadcast rate in Hz
|
||
* +Sources { +<id> { Label=...; Addr=...; Port=... } }
|
||
*
|
||
* @return true on success.
|
||
*/
|
||
bool Initialise(StructuredDataI &cfg);
|
||
|
||
/**
|
||
* @brief Start the WebSocket server and run the push loop (blocking).
|
||
* Returns when Stop() is called (e.g. from SIGINT handler).
|
||
*/
|
||
bool Run();
|
||
|
||
/**
|
||
* @brief Signal the push loop to exit. Thread-safe.
|
||
*/
|
||
void Stop();
|
||
|
||
/* ---- WSCommandCallback (called from WS client read threads) ---------- */
|
||
virtual void OnWSCommand(const char *json, uint32 len, uint32 slotIdx);
|
||
virtual void OnWSClientConnected();
|
||
virtual void OnWSClientDisconnected();
|
||
|
||
private:
|
||
|
||
/* ---- Push loop helpers ----------------------------------------------- */
|
||
|
||
/** One push tick: for each session emit a binary frame; handle trigger. */
|
||
void PushData();
|
||
|
||
/** Serialize one binary data frame for session idx into pushBuf_. */
|
||
uint32 SerializeBinaryFrame(uint32 sessionIdx, uint8 *buf, uint32 bufSize);
|
||
|
||
/** Broadcast JSON stats for all sessions. */
|
||
void PushStats();
|
||
|
||
/** Broadcast {"type":"sources"} listing. */
|
||
void BroadcastSources();
|
||
|
||
/** Broadcast {"type":"config","sourceId":...} for one session. */
|
||
void BroadcastConfig(uint32 sessionIdx);
|
||
|
||
/* ---- Trigger (push loop side) ----------------------------------------- */
|
||
|
||
/**
|
||
* @brief Per-tick trigger servicing: finalize captures, auto-rearm,
|
||
* broadcast state transitions.
|
||
*/
|
||
void TriggerTick(float64 wallNowS);
|
||
|
||
/** Broadcast {"type":"triggerState",...} reflecting the current FSM. */
|
||
void BroadcastTriggerState();
|
||
|
||
/**
|
||
* @brief Build and broadcast the version=2 binary capture frame:
|
||
* [u8 2][f64 trigTime][f64 preSec][f64 postSec][u32 nSig]
|
||
* {[u16 keyLen][fullKey][u32 N][t f64×N][v f64×N]}
|
||
*/
|
||
void BroadcastTriggerCapture(float64 trigTime, float64 preSec,
|
||
float64 postSec);
|
||
|
||
/* ---- Command handlers (called from OnWSCommand) ---------------------- */
|
||
|
||
void HandleAddSource(const char *json);
|
||
void HandleRemoveSource(const char *json);
|
||
void HandleSaveSources();
|
||
void HandleGetSources();
|
||
void HandleGetConfig(const char *json);
|
||
void HandleGetStats();
|
||
void HandleArm();
|
||
void HandleDisarm();
|
||
void HandleRearm();
|
||
void HandleTrigStop(const char *json);
|
||
void HandleSetTrigger(const char *json);
|
||
void HandleZoom(const char *json, uint32 slotIdx);
|
||
void HandleHistoryZoom(const char *json, uint32 slotIdx);
|
||
void HandleHistoryInfo(uint32 slotIdx);
|
||
void HandleSetMaxPoints(const char *json);
|
||
void HandlePing(uint32 slotIdx);
|
||
|
||
/* ---- Sources persistence (Go SourceConfig schema) --------------------- */
|
||
|
||
/**
|
||
* @brief Start a new dynamic session from "host:port" + options.
|
||
* Generates the session id ("s1", "s2", ...).
|
||
* @return true if the session was started.
|
||
*/
|
||
bool AddSourceInternal(const char *label, const char *addrPort,
|
||
const char *mcGroup, uint16 dataPort);
|
||
|
||
/**
|
||
* @brief Load sources from sourcesFile_ (JSON array of
|
||
* {"label","addr","multicastGroup","dataPort"}) and start them.
|
||
*/
|
||
void LoadSourcesFile();
|
||
|
||
/* ---- Tiny JSON helpers ----------------------------------------------- */
|
||
|
||
/**
|
||
* @brief Extract a string field from flat JSON: "key":"value".
|
||
* @return true if found; value written into out (up to outSize-1 chars).
|
||
*/
|
||
static bool JsonGetString(const char *json, const char *key,
|
||
char *out, uint32 outSize);
|
||
|
||
/**
|
||
* @brief Extract a numeric field from flat JSON: "key":number.
|
||
* @return true if found.
|
||
*/
|
||
static bool JsonGetFloat(const char *json, const char *key, float64 &out);
|
||
static bool JsonGetUint32(const char *json, const char *key, uint32 &out);
|
||
static bool JsonGetBool(const char *json, const char *key, bool &out);
|
||
|
||
/* ---- Members --------------------------------------------------------- */
|
||
|
||
UDPSourceSession sessions_[kMaxSessions];
|
||
uint32 numSessions_; ///< Count of active sessions (not an upper slot bound)
|
||
bool sessionActive_[kMaxSessions]; ///< Per-slot in-use flag (slots are not compacted on removal)
|
||
bool configBroadcast_[kMaxSessions]; ///< True once BroadcastConfig was sent for this session
|
||
FastPollingMutexSem sessionsMutex_; ///< Serializes add/remove (WS threads); push thread reads flags lock-free
|
||
|
||
WSServer wsServer_;
|
||
TriggerEngine trigger_;
|
||
HistoryWriter history_;
|
||
|
||
/* Configuration */
|
||
uint16 wsPort_;
|
||
uint32 maxPoints_;
|
||
uint32 pushRateHz_;
|
||
uint32 maxPushPoints_;
|
||
uint32 statsRateHz_;
|
||
uint32 ringTemporal_; ///< Ring capacity for multi-element (waveform) signals
|
||
uint32 ringScalar_; ///< Ring capacity for scalar signals
|
||
StreamString sourcesFile_; ///< Persistent dynamic source list (JSON)
|
||
uint32 nextSourceId_; ///< Counter for generated session ids ("sN")
|
||
|
||
/* Push loop state */
|
||
volatile bool running_;
|
||
uint32 tickCount_; ///< incremented each push tick
|
||
|
||
/* Deferred setMaxPoints (applied by the push loop, not the WS thread) */
|
||
volatile bool pendingMaxPointsSet_;
|
||
uint32 pendingMaxPoints_;
|
||
|
||
/* Per-push scratch buffer (~8 MiB): one binary frame at a time */
|
||
static const uint32 kPushBufSize = 8u * 1024u * 1024u;
|
||
uint8 *pushBuf_;
|
||
|
||
/* Decimated output scratch (LTTB): maxPushPoints × 2 arrays per signal */
|
||
float64 *lttbT_;
|
||
float64 *lttbV_;
|
||
|
||
/* Ring-buffer read scratch for the push loop (preallocated in Initialise) */
|
||
static const uint32 kPushScratchPts = 262144u; ///< Max new pts/signal/tick
|
||
float64 *pushT_;
|
||
float64 *pushV_;
|
||
|
||
/* Per-(session, signal) monotonic read cursors for ReadSignalSince.
|
||
* Only new samples since the last tick are pushed (fixes re-LTTB
|
||
* corruption of overlapping windows). */
|
||
uint64 pushCursor_[kMaxSessions][UDPSS_MAX_SIGNALS];
|
||
|
||
/* Trigger servicing state (push thread only) */
|
||
static const uint32 kTrigCapturePts = 20000u; ///< LTTB cap per captured signal
|
||
TrigState lastTrigState_; ///< Last broadcast FSM state
|
||
bool rearmPending_; ///< Normal-mode auto-rearm scheduled
|
||
float64 rearmAtWallS_; ///< Wall time of the scheduled auto-rearm
|
||
};
|
||
|
||
} /* namespace StreamHub */
|
||
|
||
#endif /* STREAMHUB_H_ */
|