Implemented client datasource
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "HistoryWriter.h"
|
||||
#include "AdvancedErrorManagement.h"
|
||||
#include "UDPSProtocol.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
@@ -129,8 +130,8 @@ void HistoryWriter::OnSourceConfigured(uint32 sessionIdx, const char *sourceId,
|
||||
MARTe::UDPSSignalDescriptor desc;
|
||||
if (!sess.GetSignalDescriptor(s, desc)) { continue; }
|
||||
|
||||
/* Skip time-only signals (type uint64, typically TimeArray) */
|
||||
if (desc.typeCode == 13u) { continue; } /* uint64 = 13 in UDPS */
|
||||
/* Skip time-reference signals (uint64 type, typically TimeArray) */
|
||||
if (desc.typeCode == MARTe::UDPS_TYPECODE_UINT64) { continue; }
|
||||
|
||||
float64 estRate = static_cast<float64>(desc.samplingRate);
|
||||
if (estRate <= 0.0) { estRate = 1000.0; } /* fallback */
|
||||
@@ -490,8 +491,9 @@ void HistoryWriter::AppendInfoJSON(char *&buf, uint32 &off, uint32 &cap) const {
|
||||
for (uint32 s = 0u; s < kHistMaxSignals; s++) {
|
||||
if (!fileActive_[i][s]) { continue; }
|
||||
const SignalFile &sf = files_[i][s];
|
||||
if (sf.count == 0u) { continue; }
|
||||
|
||||
/* Report even when count==0 — the file exists but hasn't received
|
||||
* data yet (or was just created). Clients need the entry to enable
|
||||
* history UI; t0/t1 == 0 signals "still priming". */
|
||||
HistJsonAppendf(buf, off, cap,
|
||||
"%s\"%s:%s\":{\"t0\":%.17g,\"t1\":%.17g,\"count\":%u,\"capacity\":%u}",
|
||||
(first ? "" : ","),
|
||||
|
||||
@@ -42,6 +42,12 @@ public:
|
||||
*/
|
||||
void Write(float64 t, float64 v);
|
||||
|
||||
/**
|
||||
* @brief Write @p n (t, v) pairs in a single lock acquisition.
|
||||
* Much faster than calling Write() n times for large batches.
|
||||
*/
|
||||
void WriteBatch(const float64 *t, const float64 *v, uint32 n);
|
||||
|
||||
/**
|
||||
* @brief Copy the last (oldest-to-newest) min(n, count) points into tOut/vOut.
|
||||
* @return Number of points actually written.
|
||||
@@ -144,6 +150,22 @@ inline void SignalRingBuffer::Write(float64 t, float64 v) {
|
||||
mutex.FastUnLock();
|
||||
}
|
||||
|
||||
inline void SignalRingBuffer::WriteBatch(const float64 *t, const float64 *v,
|
||||
uint32 n) {
|
||||
if (n == 0u) { return; }
|
||||
(void) mutex.FastLock();
|
||||
if (capacity > 0u) {
|
||||
for (uint32 i = 0u; i < n; i++) {
|
||||
tBuf[head] = t[i];
|
||||
vBuf[head] = v[i];
|
||||
head = (head + 1u) % capacity;
|
||||
if (count < capacity) { count++; }
|
||||
}
|
||||
totalWritten += static_cast<MARTe::uint64>(n);
|
||||
}
|
||||
mutex.FastUnLock();
|
||||
}
|
||||
|
||||
inline uint32 SignalRingBuffer::Read(float64 *tOut, float64 *vOut, uint32 n) const {
|
||||
if ((capacity == 0u) || (count == 0u)) { return 0u; }
|
||||
(void) mutex.FastLock();
|
||||
|
||||
@@ -255,13 +255,27 @@ bool StreamHub::Run() {
|
||||
PushStats();
|
||||
}
|
||||
|
||||
/* History: flush headers periodically */
|
||||
/* History: flush headers at the configured interval, then re-broadcast
|
||||
* historyInfo so new clients (or clients that saw empty signal lists
|
||||
* before the first data was written) pick up the current state. */
|
||||
if (history_.IsEnabled()) {
|
||||
uint32 histFlushDiv = history_.Decimation() > 0u
|
||||
? pushRateHz_ * 5u : pushRateHz_ * 5u; /* every 5s */
|
||||
if (histFlushDiv == 0u) { histFlushDiv = 1u; }
|
||||
if ((tickCount_ % histFlushDiv) == 0u) {
|
||||
uint32 flushIntervalTicks = pushRateHz_ * 5u; /* default 5 s */
|
||||
if (pushRateHz_ > 0u) {
|
||||
flushIntervalTicks = pushRateHz_ * 5u;
|
||||
}
|
||||
if (flushIntervalTicks == 0u) { flushIntervalTicks = 1u; }
|
||||
if ((tickCount_ % flushIntervalTicks) == 0u) {
|
||||
history_.FlushHeaders();
|
||||
/* Re-broadcast so clients see updated signal counts and
|
||||
* time ranges after the first batch of data is written. */
|
||||
uint32 cap2 = 8192u;
|
||||
char *hbuf = new char[cap2];
|
||||
uint32 hoff = 0u;
|
||||
JsonAppendf(hbuf, hoff, cap2, "{\"type\":\"historyInfo\",");
|
||||
history_.AppendInfoJSON(hbuf, hoff, cap2);
|
||||
JsonAppendf(hbuf, hoff, cap2, "}");
|
||||
wsServer_.BroadcastText(hbuf, hoff);
|
||||
delete[] hbuf;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -648,6 +662,14 @@ void StreamHub::OnWSClientConnected() {
|
||||
wsServer_.BroadcastText(hbuf, hoff);
|
||||
delete[] hbuf;
|
||||
}
|
||||
|
||||
/* Inform the new client about the current MaxPoints setting. */
|
||||
{
|
||||
char mp[64];
|
||||
int n = snprintf(mp, sizeof(mp),
|
||||
"{\"type\":\"maxPointsUpdated\",\"maxPoints\":%u}", maxPoints_);
|
||||
if (n > 0) { wsServer_.BroadcastText(mp, static_cast<uint32>(n)); }
|
||||
}
|
||||
}
|
||||
|
||||
void StreamHub::OnWSClientDisconnected() {
|
||||
|
||||
@@ -63,7 +63,10 @@ UDPSourceSession::UDPSourceSession()
|
||||
statLastRxTicks_(0u),
|
||||
statFragCount_(0u),
|
||||
statByteCount_(0u),
|
||||
hrtFreq_(static_cast<float64>(MARTe::HighResolutionTimer::Frequency())),
|
||||
timeScratch_(static_cast<float64 *>(0)),
|
||||
valScratch_(static_cast<float64 *>(0)),
|
||||
tsBatchScratch_(static_cast<float64 *>(0)),
|
||||
timeScratchLen_(0u),
|
||||
trigEngine_(static_cast<TriggerEngine *>(0)),
|
||||
trigEpochSeen_(0xFFFFFFFFu),
|
||||
@@ -76,6 +79,8 @@ UDPSourceSession::UDPSourceSession()
|
||||
UDPSourceSession::~UDPSourceSession() {
|
||||
(void) Stop();
|
||||
delete[] timeScratch_;
|
||||
delete[] valScratch_;
|
||||
delete[] tsBatchScratch_;
|
||||
}
|
||||
|
||||
void UDPSourceSession::ResetCalibration() {
|
||||
@@ -84,6 +89,7 @@ void UDPSourceSession::ResetCalibration() {
|
||||
timeSigCalib_[i] = 0.0;
|
||||
lastPktWallValid_[i] = false;
|
||||
lastPktWallS_[i] = 0.0;
|
||||
accScalarPrevN_[i] = 0u;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +233,11 @@ void UDPSourceSession::ParseConfigPayload(const uint8 *payload, uint32 size) {
|
||||
}
|
||||
if (maxElems > timeScratchLen_) {
|
||||
delete[] timeScratch_;
|
||||
delete[] valScratch_;
|
||||
delete[] tsBatchScratch_;
|
||||
timeScratch_ = new float64[maxElems];
|
||||
valScratch_ = new float64[maxElems];
|
||||
tsBatchScratch_ = new float64[maxElems];
|
||||
timeScratchLen_ = maxElems;
|
||||
}
|
||||
|
||||
@@ -369,17 +379,19 @@ void UDPSourceSession::ParseDataPayload(const uint8 *payload, uint32 size) {
|
||||
} else {
|
||||
anchorIsFirstSample = false;
|
||||
}
|
||||
/* Batch decode: compute all timestamps and values, then batch write. */
|
||||
const float64 dt = (desc.samplingRate > 0.0) ? (1.0 / desc.samplingRate) : 0.0;
|
||||
DecodeElems(payload, sigOff[s], desc, nElems, valScratch_);
|
||||
for (uint32 e = 0u; e < nElems; e++) {
|
||||
const float64 val = DecodeOneElem(payload, sigOff[s], desc, e);
|
||||
const float64 t = anchorIsFirstSample
|
||||
tsBatchScratch_[e] = anchorIsFirstSample
|
||||
? (anchor + static_cast<float64>(e) * dt)
|
||||
: (anchor - static_cast<float64>(nElems - 1u - e) * dt);
|
||||
WriteSample(s, e, t, val);
|
||||
}
|
||||
WriteSampleBatch(s, tsBatchScratch_, valScratch_, nElems);
|
||||
}
|
||||
else if (isFullArray) {
|
||||
/* Per-element timestamps from the referenced time-signal array. */
|
||||
/* Per-element timestamps from the referenced time-signal array.
|
||||
* Batch decode + batch write (single ring lock per signal). */
|
||||
if (hasTimeSig && (sigElems[tIdx] >= nElems) &&
|
||||
(nElems <= timeScratchLen_)) {
|
||||
DecodeElems(payload, sigOff[tIdx], descs[tIdx], nElems, timeScratch_);
|
||||
@@ -390,15 +402,18 @@ void UDPSourceSession::ParseDataPayload(const uint8 *payload, uint32 size) {
|
||||
timeSigCalibValid_[tIdx] = true;
|
||||
}
|
||||
const float64 calib = timeSigCalib_[tIdx];
|
||||
DecodeElems(payload, sigOff[s], desc, nElems, valScratch_);
|
||||
for (uint32 e = 0u; e < nElems; e++) {
|
||||
const float64 val = DecodeOneElem(payload, sigOff[s], desc, e);
|
||||
WriteSample(s, e, calib + timeScratch_[e] * timerToSec, val);
|
||||
tsBatchScratch_[e] = calib + timeScratch_[e] * timerToSec;
|
||||
}
|
||||
WriteSampleBatch(s, tsBatchScratch_, valScratch_, nElems);
|
||||
} else {
|
||||
/* No time signal: all samples get arrival wall time. */
|
||||
DecodeElems(payload, sigOff[s], desc, nElems, valScratch_);
|
||||
for (uint32 e = 0u; e < nElems; e++) {
|
||||
const float64 val = DecodeOneElem(payload, sigOff[s], desc, e);
|
||||
WriteSample(s, e, wallNowS, val);
|
||||
tsBatchScratch_[e] = wallNowS;
|
||||
}
|
||||
WriteSampleBatch(s, tsBatchScratch_, valScratch_, nElems);
|
||||
}
|
||||
}
|
||||
else if (numElements == 1u) {
|
||||
@@ -407,22 +422,55 @@ void UDPSourceSession::ParseDataPayload(const uint8 *payload, uint32 size) {
|
||||
const float64 val = DecodeOneElem(payload, sigOff[s], desc, 0u);
|
||||
WriteSample(s, 0u, wallNowS, val);
|
||||
}
|
||||
else if (lastPktWallValid_[s] && (wallNowS > lastPktWallS_[s])) {
|
||||
/* Accumulated scalar: the packet carries one sample per RT
|
||||
* cycle — spread them across the inter-packet wall-clock gap
|
||||
* (same scheme as packed arrays) instead of stamping them all
|
||||
* with the arrival time, which renders as a stair plot. */
|
||||
const float64 dt = (wallNowS - lastPktWallS_[s]) /
|
||||
static_cast<float64>(nElems);
|
||||
const float64 base = lastPktWallS_[s];
|
||||
for (uint32 e = 0u; e < nElems; e++) {
|
||||
const float64 val = DecodeOneElem(payload, sigOff[s], desc, e);
|
||||
WriteSample(s, 0u, base + static_cast<float64>(e + 1u) * dt, val);
|
||||
else {
|
||||
/* Accumulated scalar: reconstruct per-sample timestamps from
|
||||
* the packet's embedded sender HRT (the acquisition time of the
|
||||
* oldest sample), NOT the packet arrival time. The kernel
|
||||
* frequently delivers several queued datagrams in one burst, so
|
||||
* two packets can be processed microseconds apart even though
|
||||
* each represents ~10 ms of signal; arrival-time interpolation
|
||||
* then crams a packet's samples into that tiny gap, rendering
|
||||
* the trace as a sawtooth ("chainsaw"). The sender HRT is
|
||||
* immune to this because it is sampled at acquisition.
|
||||
*
|
||||
* hrtTimestamp is the HRT counter of sample 0; hrtFreq_ (the
|
||||
* local HRT frequency, identical to the sender on the same
|
||||
* host) converts it to seconds, then a one-time calibration
|
||||
* maps the sender clock onto wall-clock. */
|
||||
const float64 hrt0Sec = static_cast<float64>(hrtTimestamp) /
|
||||
hrtFreq_;
|
||||
if ((!timeSigCalibValid_[s]) ||
|
||||
(Fabs((timeSigCalib_[s] + hrt0Sec) - wallNowS) >
|
||||
kRecalibThresholdS)) {
|
||||
timeSigCalib_[s] = wallNowS - hrt0Sec;
|
||||
timeSigCalibValid_[s] = true;
|
||||
}
|
||||
}
|
||||
if (nElems > 1u) {
|
||||
lastPktWallS_[s] = wallNowS;
|
||||
|
||||
/* Per-sample dt: samplingRate if present, else derive it from
|
||||
* the sender-HRT gap to the previous packet divided by that
|
||||
* packet's sample count (the flushes carry contiguous RT
|
||||
* cycles, so this is exactly one cycle period). */
|
||||
float64 dt;
|
||||
if (desc.samplingRate > 0.0) {
|
||||
dt = 1.0 / desc.samplingRate;
|
||||
} else if (lastPktWallValid_[s] && (accScalarPrevN_[s] > 0u) &&
|
||||
(hrt0Sec > lastPktWallS_[s])) {
|
||||
dt = (hrt0Sec - lastPktWallS_[s]) /
|
||||
static_cast<float64>(accScalarPrevN_[s]);
|
||||
} else {
|
||||
dt = 1.0e-3; /* 1 kHz default until the gap is known */
|
||||
}
|
||||
|
||||
const float64 base = timeSigCalib_[s] + hrt0Sec;
|
||||
DecodeElems(payload, sigOff[s], desc, nElems, valScratch_);
|
||||
for (uint32 e = 0u; e < nElems; e++) {
|
||||
tsBatchScratch_[e] = base + static_cast<float64>(e) * dt;
|
||||
}
|
||||
WriteSampleBatch(s, tsBatchScratch_, valScratch_, nElems);
|
||||
|
||||
lastPktWallS_[s] = hrt0Sec; /* sender seconds for next dt */
|
||||
lastPktWallValid_[s] = true;
|
||||
accScalarPrevN_[s] = nElems;
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -435,14 +483,23 @@ void UDPSourceSession::ParseDataPayload(const uint8 *payload, uint32 size) {
|
||||
* and overlaps the next packet under jitter), elements span
|
||||
* (lastPktWall, wallNow]: the samples were acquired before the
|
||||
* packet arrived, and this keeps ring time strictly monotonic. */
|
||||
if (lastPktWallValid_[s] && (wallNowS > lastPktWallS_[s])) {
|
||||
const float64 dt = (wallNowS - lastPktWallS_[s]) /
|
||||
static_cast<float64>(nElems);
|
||||
const float64 base = lastPktWallS_[s];
|
||||
for (uint32 e = 0u; e < nElems; e++) {
|
||||
const float64 val = DecodeOneElem(payload, sigOff[s], desc, e);
|
||||
WriteSample(s, e, base + static_cast<float64>(e + 1u) * dt, val);
|
||||
if (lastPktWallValid_[s] && (wallNowS >= lastPktWallS_[s])) {
|
||||
float64 dt;
|
||||
float64 base;
|
||||
if (wallNowS > lastPktWallS_[s]) {
|
||||
dt = (wallNowS - lastPktWallS_[s]) /
|
||||
static_cast<float64>(nElems);
|
||||
base = lastPktWallS_[s];
|
||||
} else {
|
||||
dt = (desc.samplingRate > 0.0)
|
||||
? (1.0 / desc.samplingRate) : 1.0e-6;
|
||||
base = lastPktWallS_[s] - static_cast<float64>(nElems) * dt;
|
||||
}
|
||||
DecodeElems(payload, sigOff[s], desc, nElems, valScratch_);
|
||||
for (uint32 e = 0u; e < nElems; e++) {
|
||||
tsBatchScratch_[e] = base + static_cast<float64>(e + 1u) * dt;
|
||||
}
|
||||
WriteSampleBatch(s, tsBatchScratch_, valScratch_, nElems);
|
||||
}
|
||||
lastPktWallS_[s] = wallNowS;
|
||||
lastPktWallValid_[s] = true;
|
||||
|
||||
@@ -187,6 +187,22 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Batch ring write for signal @p s, with optional trigger check.
|
||||
* Uses WriteBatch internally (single lock acquisition for all samples). */
|
||||
inline void WriteSampleBatch(uint32 s, const float64 *t, const float64 *v,
|
||||
uint32 n) {
|
||||
rings_[s].WriteBatch(t, v, n);
|
||||
/* Trigger check must still run per-sample for the watched signal. */
|
||||
if ((trigSigIdx_ >= 0) && (static_cast<uint32>(trigSigIdx_) == s) &&
|
||||
(trigEngine_ != static_cast<TriggerEngine *>(0))) {
|
||||
for (uint32 e = 0u; e < n; e++) {
|
||||
if ((trigElemIdx_ < 0) || (static_cast<uint32>(trigElemIdx_) == e)) {
|
||||
trigEngine_->CheckSample(t[e], v[e]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Decode @p nElems consecutive elements of signal @p desc starting
|
||||
* at payload offset @p off into @p out (dequantised physical values).
|
||||
@@ -253,12 +269,23 @@ private:
|
||||
bool timeSigCalibValid_[UDPSS_MAX_SIGNALS];
|
||||
|
||||
/* Last packet arrival wall time per signal — used to interpolate per-element
|
||||
* timestamps for packed TIMEMODE_PACKET arrays (Go hub lastPktNs). */
|
||||
* timestamps for packed TIMEMODE_PACKET arrays (Go hub lastPktNs).
|
||||
* For accumulated scalars this instead holds the previous packet's
|
||||
* sender-clock seconds (HRT-derived), used to derive the per-sample dt. */
|
||||
float64 lastPktWallS_[UDPSS_MAX_SIGNALS];
|
||||
bool lastPktWallValid_[UDPSS_MAX_SIGNALS];
|
||||
|
||||
/* Scratch for decoding referenced time-signal arrays (receive thread only). */
|
||||
float64 *timeScratch_;
|
||||
/* Accumulated-scalar timing: HRT counter frequency (local == sender on the
|
||||
* same host) and the previous packet's sample count, used to reconstruct
|
||||
* per-sample timestamps from the embedded sender HRT instead of the (UDP
|
||||
* burst-sensitive) packet arrival time. */
|
||||
float64 hrtFreq_;
|
||||
uint32 accScalarPrevN_[UDPSS_MAX_SIGNALS];
|
||||
|
||||
/* Scratch buffers for decoding arrays (receive thread only). */
|
||||
float64 *timeScratch_; ///< Time values scratch
|
||||
float64 *valScratch_; ///< Data values scratch
|
||||
float64 *tsBatchScratch_; ///< Timestamps scratch for batch write
|
||||
uint32 timeScratchLen_;
|
||||
|
||||
/* Trigger hookup (resolution cache is receive-thread only). */
|
||||
|
||||
Reference in New Issue
Block a user