Files
MARTe-Integrated-Components/Source/Applications/StreamHub/StreamHub.h
T
2026-06-14 14:09:51 +02:00

241 lines
8.4 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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_ */