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>
88 lines
2.8 KiB
C++
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 */
|