#pragma once #include "protocol.h" #include "signal_buffer.h" #include "udp_client.h" #include #include #include #include #include #include #include /** Time calibration: maps timer ticks from one time-signal → Unix wall-clock seconds. * * Mirrors the WebUI Go client approach exactly: * timer_to_sec = 1e-9 for uint64 (HRT nanoseconds), 1e-6 for all others (microseconds) * calib_offset = wallT - timerVal * timer_to_sec (set once on first packet) * t = calib_offset + timerVal * timer_to_sec */ struct TimeCalib { double calib_offset = 0.0; // wallT_unix - timerVal * timer_to_sec double timer_to_sec = 1e-6; // 1e-9 for uint64, 1e-6 for uint32/other bool valid = false; }; /** Per-signal display + buffer state. */ struct SignalState { SignalDef def; std::shared_ptr buffer; // high-res ring buffer (last ~1-60 s) std::shared_ptr archive; // low-res ~1 kHz history (last ~17 min) double archive_last_t = -1e18; // ingest-thread only // Render-thread state (only modified on render thread after initial setup) bool visible = true; uint8_t plot_mask = 0; // bitmask: bit k set → signal is shown on plot k (0–3) float color[4] = {1.f, 1.f, 1.f, 1.f}; // RGBA float line_width = 1.5f; bool use_envelope = false; // true → PlotShaded, false → PlotLine }; /** * One MARTe2 UDPStreamer source. * Owns a UDPClient (ingest thread) and a vector of SignalState objects * populated when CONFIG arrives. */ class Source { public: Source(std::string label, std::string host, uint16_t port, std::string multicast_group = "", uint16_t data_port = 0); ~Source(); void start(); void stop(); // ── Identification ──────────────────────────────────────────────── const std::string& label() const { return label_; } const std::string& host() const { return host_; } uint16_t port() const { return port_; } bool connected() const { return connected_.load(std::memory_order_relaxed); } // ── Signal list (protect with signals_mutex while iterating) ────── mutable std::mutex signals_mutex; std::vector signal_states; // indexed by signal_idx std::atomic config_dirty{false}; // set by ingest, cleared by render // ── Stats ───────────────────────────────────────────────────────── struct Stats { uint64_t packets_rx = 0; uint64_t packets_lost = 0; uint64_t bytes_rx = 0; }; Stats stats() const; // High-res ring buffer capacity (≈60 s @ 100 kHz, ≈1.2 s @ 5 MHz) static constexpr size_t MAX_SIGNAL_SAMPLES = 6'000'000; // Low-res archive capacity: ~1000 Hz effective rate → ~17 min history static constexpr size_t MAX_ARCHIVE_SAMPLES = 1'000'000; // Minimum time between archive writes (1 ms = 1000 Hz) static constexpr double ARCHIVE_DT = 1.0 / 1000.0; private: // Called from ingest thread void handle_config(const std::vector& defs); void handle_data(const DataPacket& dp, const std::vector& defs); std::string label_, host_; uint16_t port_; std::shared_ptr client_; std::atomic connected_{false}; // Time calibrations indexed by protocol signal index (size = defs.size()) std::vector time_calibs_; // Spatial-array expansion map. // Indexed by protocol signal index; maps to the first signal_states entry // for that signal and how many entries it occupies (1 for scalars/temporal). struct SigExpand { int start_state_idx; uint32_t num_elements; // 1 for scalar/temporal, N for spatial }; std::vector sig_expand_; // Stats (written by ingest thread, read by render — relaxed OK for display) std::atomic stat_rx_{0}; std::atomic stat_lost_{0}; std::atomic stat_bytes_{0}; };