/** * @file TriggerEngine.h * @brief Hub-side trigger FSM with web-oscilloscope semantics. * * Mirrors the web client's trigger logic (Client/udpstreamer/static/app.js): * * IDLE --Arm()--> ARMED * ARMED --edge fires--> COLLECTING (trigTime + pre/post window latched) * COLLECTING -capture done-> TRIGGERED (StreamHub broadcasts the capture) * TRIGGERED --Arm()--> ARMED (auto after ~200 ms in normal mode, * manual "rearm" in single mode) * any --Disarm()--> IDLE * * Configuration is the web client's: full signal key ("src:sig" or * "src:sig[i]"), edge rising/falling/both, threshold, capture window length * windowSec, pre-trigger percentage, and acquisition mode normal/single with * a "stopped" flag pausing auto-rearm. * * CheckSample() is called from the UDPSClient receive thread for every decoded * sample of the configured signal. All state is protected by a * FastPollingMutexSem so the StreamHub push thread can poll it safely. */ #ifndef STREAMHUB_TRIGGER_ENGINE_H_ #define STREAMHUB_TRIGGER_ENGINE_H_ #include "CompilerTypes.h" #include "FastPollingMutexSem.h" #include "StreamString.h" namespace StreamHub { using MARTe::uint32; using MARTe::float64; using MARTe::StreamString; using MARTe::FastPollingMutexSem; /** Trigger FSM states (badge mapping: idle/armed/collecting/triggered). */ enum TrigState { kTrigIdle = 0, kTrigArmed = 1, kTrigCollecting = 2, kTrigTriggered = 3 }; /** Edge selection. */ enum TrigEdge { kEdgeRising = 0, kEdgeFalling = 1, kEdgeBoth = 2 }; /** Acquisition mode. */ enum TrigAcqMode { kTrigNormal = 0, ///< Auto-rearm after each capture (unless stopped) kTrigSingle = 1 ///< Stay TRIGGERED until an explicit rearm }; /** @brief Trigger configuration (web client semantics). */ struct TriggerConfig { TriggerConfig(); StreamString signalKey; ///< Full key: "src:sig" or "src:sig[i]" TrigEdge edge; ///< Rising / falling / both float64 threshold; ///< Trigger threshold (physical units) float64 windowSec; ///< Capture window length [1e-4 .. 60] s float64 prePercent; ///< Pre-trigger part of the window [0 .. 100] % TrigAcqMode mode; ///< Normal (auto-rearm) or single float64 holdoffSec; ///< Rearm delay after a capture [0 .. 60] s }; /** * @brief Hub-side trigger FSM. */ class TriggerEngine { public: TriggerEngine(); /** @brief Replace the configuration (bumps the config epoch). */ void SetConfig(const TriggerConfig &cfg); /** @return Copy of the current configuration. */ TriggerConfig GetConfig() const; /** * @return Monotonic configuration epoch. UDPSourceSession caches the * resolved (signal idx, element idx) per epoch and re-resolves on change. */ uint32 GetConfigEpoch() const; /** * @brief Arm: any state → ARMED (resets edge detection). * This is the user's own arm, so it discards any edge remembered during the * previous capture: the user asked for the next event, not for one that has * already been and gone. */ void Arm(); /** * @brief The automatic arm at the end of a capture (normal mode). * Unlike Arm() it honours an edge seen while the capture was being * collected, firing on it at once rather than waiting for the next one, and * it keeps the tracked level so the first sample afterwards is compared * against its real predecessor. TRIGGERED → COLLECTING when an edge was * remembered, otherwise → ARMED. */ void Rearm(); /** @brief Disarm: any state → IDLE; clears the stopped flag. */ void Disarm(); /** @brief Set/clear the stopped flag (pauses normal-mode auto-rearm). */ void SetStopped(bool stopped); /** @return Current stopped flag. */ bool GetStopped() const; /** * @brief Edge-detect one decoded sample of the configured signal. * Receive-thread context. In ARMED state a matching edge latches trigTime * and the pre/post window and moves to COLLECTING. While a capture is in * flight (COLLECTING/TRIGGERED) the comparator keeps running and the first * edge clear of that capture is remembered for the next Rearm(). */ void CheckSample(float64 t, float64 v); /** * @brief Fire the trigger unconditionally at the most recent sample time of * the watched signal, latching the pre/post window exactly as CheckSample * does. Any state except COLLECTING → COLLECTING. * @return false when no sample has been seen yet, or a capture is already * being collected. */ bool Force(); /** @return Current FSM state. */ TrigState GetState() const; /** * @brief Get the latched capture window of the last fired trigger. * @return false if no trigger has fired since the last Disarm(). */ bool GetFiredWindow(float64 &trigTime, float64 &preSec, float64 &postSec) const; /** @brief COLLECTING → TRIGGERED (push thread, after the capture frame). */ void MarkTriggered(); private: mutable FastPollingMutexSem mutex_; TriggerConfig config_; uint32 epoch_; TrigState state_; bool stopped_; float64 prevValue_; ///< Last sample (edge detection) bool prevValid_; ///< First-sample guard in ARMED state float64 lastTime_; ///< Timestamp of the newest watched sample bool lastTimeValid_;///< true once a watched sample has been seen float64 trigTime_; ///< Latched trigger time (Unix s) float64 firedPreSec_; ///< Window pre-part latched at fire time float64 firedPostSec_; ///< Window post-part latched at fire time bool firedValid_; ///< true after a fire, until Disarm() /** * The edge to fire on as soon as the FSM rearms, in sample time, recorded * while a capture is still being collected or handed out. Without it the * trigger is deaf from its own trigger point until the capture has been * harvested and the holdoff has run, and then waits for a fresh edge, which * on a sparse pulse train rounds the capture spacing up to a whole pulse * period. Remembering the edge instead makes the blind stretch exactly the * guard interval it has to be, since the capture is built from the edge's * own timestamp and the rings still hold everything around it. */ float64 pendingTime_; bool pendingValid_; /** @brief Freeze the pre/post split at fire time; caller holds the mutex. */ void LatchWindowLocked(float64 t); }; inline TriggerConfig::TriggerConfig() : edge(kEdgeRising), threshold(0.0), windowSec(1.0), prePercent(20.0), mode(kTrigNormal), holdoffSec(0.2) { } } /* namespace StreamHub */ #endif /* STREAMHUB_TRIGGER_ENGINE_H_ */