Files
MARTe-Integrated-Components/Client/udpscope/TimeBase.h
T
Martino FerrariandClaude Opus 4.6 c89decef8e docs: say that HrtRateFit::toSeconds returns producer-epoch, not wall, seconds
The fit keeps the slope and discards the intercept, so the result counts from
the producer's boot. Tasks 4 and 7 compose it with ClockOffset::map, which is
correct, but the bare name invites passing it straight to a plot axis.

Also unwrapped the stalled-clock assertion from behind `if (fit.ready())` —
that branch never runs, so the test confirmed nothing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-27 19:55:24 +02:00

88 lines
2.8 KiB
C++

/**
* @file TimeBase.h
* @brief Producer-clock to wall-clock reconstruction.
*
* Framework-free. A UDPS stream's accurate timestamps come from a producer
* clock — either a declared time signal or the packet's embedded hrt — and both
* need mapping onto the client's wall clock before they can be plotted.
*/
#pragma once
#include <cstddef>
#include <cstdint>
#include <deque>
namespace udpscope {
/**
* @brief Seconds per count of a time signal, from its type code.
*
* The protocol carries uint64 time signals in nanoseconds and everything else
* in microseconds; this mirrors UDPSourceSession so the two agree on a stream.
*/
double TimeSignalScale(uint8_t typeCode);
/**
* @brief A latched producer-to-wall offset.
*
* Established from the first sample and then held, so network jitter does not
* wobble the trace. Only a drift beyond kRecalibThresholdS — a producer restart
* or re-phase, not delivery noise — forces a new calibration.
*/
class ClockOffset {
public:
static constexpr double kRecalibThresholdS = 0.5;
/** @return producerSec mapped onto wall clock. */
double map(double producerSec, double wallSec);
bool valid() const { return valid_; }
void reset() { valid_ = false; offset_ = 0.0; }
double offset() const { return offset_; }
private:
double offset_ = 0.0;
bool valid_ = false;
};
/**
* @brief Recovers the producer's hrt tick rate by least squares against arrival
* time.
*
* The protocol does not carry the tick rate, and StreamHub's approach of using
* the local MARTe HighResolutionTimer frequency is only valid when the client
* runs on the producer's host. A remote bench scope cannot assume that, so the
* rate is measured: hrt against recv_time is a straight line whose slope is
* ticks per second.
*/
class HrtRateFit {
public:
static constexpr size_t kMinSamples = 32;
static constexpr size_t kWindow = 256;
void add(uint64_t hrt, double wallSec);
bool ready() const { return n_ >= kMinSamples && rate_ > 0.0; }
double ticksPerSecond() const { return rate_; }
/**
* @brief Converts a tick count to seconds on the PRODUCER's own epoch.
*
* The fit recovers the slope only and discards the intercept, so this is
* `hrt / ticksPerSecond()` — not a wall-clock time. A producer's hrt counts
* from its own boot, not from the Unix epoch. Pass the result to
* ClockOffset::map() to land it on the wall clock; latching that arbitrary
* epoch difference is precisely what ClockOffset is for.
*/
double toSeconds(uint64_t hrt) const;
void reset();
private:
void refit();
struct Sample { double hrt; double wall; };
std::deque<Sample> samples_;
size_t n_ = 0;
double rate_ = 0.0;
};
} /* namespace udpscope */