feat(udpscope): per-element timestamp reconstruction from UDPS frames
Implements FrameDecoder with five timing rules that mirror UDPSourceSession.cpp: FullArray (per-element time signal), FirstSample and LastSample (rate-spread from anchor), accumulated scalar with declared rate (forward-chain anchoring, immune to arrival jitter), and PACKET burst (backward-span from previous arrival). 9 new tests, 38 pass total. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
c89decef8e
commit
5a8479cda9
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* @file FrameDecoder.h
|
||||
* @brief Per-element timestamp reconstruction for UDPS frames.
|
||||
*
|
||||
* The C client's udps_frame_element_time() is explicitly an arrival-anchored
|
||||
* estimate. It is not sufficient: the kernel frequently delivers several queued
|
||||
* datagrams in one burst, so two packets are processed microseconds apart even
|
||||
* though each represents ~10 ms of signal, and arrival-time interpolation then
|
||||
* crams a packet's samples into that tiny gap — the trace renders as a sawtooth.
|
||||
* Source/Applications/StreamHub/UDPSourceSession.cpp documents this failure and
|
||||
* solves it; these are the same rules, computed from udps_frame_t's own fields
|
||||
* so the scope and StreamHub agree on the same stream.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "TimeBase.h"
|
||||
#include "Types.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace udpscope {
|
||||
|
||||
class FrameDecoder {
|
||||
public:
|
||||
/** Installs the signal table. Clears all per-signal timing history. */
|
||||
void setSignals(const std::vector<SignalMeta>& signals);
|
||||
|
||||
const std::vector<SignalMeta>& signals() const { return signals_; }
|
||||
|
||||
/** Call once per frame, before any timestamps() call for that frame. */
|
||||
void beginFrame(const FrameView& f);
|
||||
|
||||
/**
|
||||
* @brief Timestamps for every value of signal @p idx in this frame.
|
||||
* @return false when the signal produced nothing usable — an empty slot, or
|
||||
* the first PACKET burst after connect, which has no previous
|
||||
* arrival to span from and would otherwise poison the ring with
|
||||
* wrongly spaced timestamps.
|
||||
*/
|
||||
bool timestamps(const FrameView& f, uint32_t idx, std::vector<double>& tsOut);
|
||||
|
||||
/** Forgets all timing history; call on reconnect. */
|
||||
void reset();
|
||||
|
||||
private:
|
||||
bool packetBurst(uint32_t idx, uint32_t nElems, double wallNow,
|
||||
std::vector<double>& tsOut);
|
||||
|
||||
struct SigState {
|
||||
ClockOffset offset;
|
||||
double lastPacketWall = 0.0;
|
||||
bool lastPacketValid = false;
|
||||
double lastAccHrtSec = 0.0;
|
||||
bool lastAccValid = false;
|
||||
uint32_t prevAccCount = 0;
|
||||
/** For accumulated scalars with a declared sampling rate: end timestamp
|
||||
* of the most recently emitted burst, used as a forward-chain anchor
|
||||
* that is immune to arrival-time jitter. */
|
||||
double lastEmittedEnd = 0.0;
|
||||
bool lastEmittedValid = false;
|
||||
};
|
||||
|
||||
std::vector<SignalMeta> signals_;
|
||||
std::vector<SigState> state_;
|
||||
HrtRateFit hrtFit_;
|
||||
};
|
||||
|
||||
} /* namespace udpscope */
|
||||
Reference in New Issue
Block a user