Implemented and fixed many issues
This commit is contained in:
@@ -65,6 +65,8 @@ StreamHub::StreamHub()
|
||||
statsRateHz_(1u),
|
||||
ringTemporal_(1000000u),
|
||||
ringScalar_(100000u),
|
||||
ringMaxPts_(8388608u),
|
||||
trigRetentionSec_(0.0),
|
||||
nextSourceId_(1u),
|
||||
calibration_(static_cast<CalibrationEntry *>(0)),
|
||||
numCalibration_(0u),
|
||||
@@ -79,13 +81,19 @@ StreamHub::StreamHub()
|
||||
pushV_(static_cast<float64 *>(0)),
|
||||
lastTrigState_(kTrigIdle),
|
||||
rearmPending_(false),
|
||||
rearmAtWallS_(0.0) {
|
||||
rearmAtWallS_(0.0),
|
||||
collectStartWallS_(0.0),
|
||||
capBuf_(static_cast<uint8 *>(0)),
|
||||
capCap_(0u),
|
||||
capOff_(0u),
|
||||
capNSig_(0u) {
|
||||
memset(&recorderCfg_, 0, sizeof(recorderCfg_));
|
||||
calibration_ = new CalibrationEntry[kMaxCalibration];
|
||||
memset(calibration_, 0, sizeof(CalibrationEntry) * kMaxCalibration);
|
||||
for (uint32 i = 0u; i < kMaxSessions; i++) {
|
||||
sessionActive_[i] = false;
|
||||
configBroadcast_[i] = false;
|
||||
capHarvested_[i] = false;
|
||||
for (uint32 s = 0u; s < UDPSS_MAX_SIGNALS; s++) {
|
||||
pushCursor_[i][s] = 0u;
|
||||
}
|
||||
@@ -104,6 +112,10 @@ StreamHub::~StreamHub() {
|
||||
delete[] pushBuf_;
|
||||
pushBuf_ = static_cast<uint8 *>(0);
|
||||
}
|
||||
if (capBuf_ != static_cast<uint8 *>(0)) {
|
||||
delete[] capBuf_;
|
||||
capBuf_ = static_cast<uint8 *>(0);
|
||||
}
|
||||
if (lttbT_ != static_cast<float64 *>(0)) {
|
||||
delete[] lttbT_;
|
||||
lttbT_ = static_cast<float64 *>(0);
|
||||
@@ -138,11 +150,38 @@ bool StreamHub::Initialise(StructuredDataI &cfg) {
|
||||
if (cfg.Read("StatsRate", tmp)) { statsRateHz_ = (tmp > 0u) ? tmp : 1u; }
|
||||
if (cfg.Read("RingTemporal", tmp)) { ringTemporal_ = (tmp > 0u) ? tmp : 1000000u; }
|
||||
if (cfg.Read("RingScalar", tmp)) { ringScalar_ = (tmp > 0u) ? tmp : 100000u; }
|
||||
/* Per-signal ceiling when a trigger window forces a ring to grow.
|
||||
* 128 MiB / (2 × float64) = 8388608 points — ~8 s at 1 Msps, ~1.7 s at
|
||||
* 5 Msps. Raise it if you need longer windows on very fast sources. */
|
||||
if (cfg.Read("RingMaxMB", tmp) && (tmp > 0u)) {
|
||||
ringMaxPts_ = tmp * (1048576u / 16u);
|
||||
}
|
||||
if (ringMaxPts_ < ringTemporal_) { ringMaxPts_ = ringTemporal_; }
|
||||
|
||||
sourcesFile_ = "streamhub_sources.json";
|
||||
StreamString sf;
|
||||
if (cfg.Read("SourcesFile", sf)) { sourcesFile_ = sf; }
|
||||
|
||||
/* Origins allowed to open the WebSocket, comma-separated
|
||||
* ("http://localhost:8080,http://box.lan:8080"). Without this only
|
||||
* same-origin upgrades pass, which rejects every browser that loaded the
|
||||
* SPA from a separate web server (the usual deployment). */
|
||||
StreamString origins;
|
||||
if (cfg.Read("AllowedOrigins", origins)) {
|
||||
char list[1024];
|
||||
strncpy(list, origins.Buffer(), sizeof(list) - 1u);
|
||||
list[sizeof(list) - 1u] = '\0';
|
||||
char *tok = strtok(list, ", \t");
|
||||
while (tok != static_cast<char *>(0)) {
|
||||
if (!wsServer_.AddAllowedOrigin(tok)) {
|
||||
REPORT_ERROR_STATIC(MARTe::ErrorManagement::Warning,
|
||||
"StreamHub: rejected allowed-origin '%s' (list full or too long).",
|
||||
tok);
|
||||
}
|
||||
tok = strtok(static_cast<char *>(0), ", \t");
|
||||
}
|
||||
}
|
||||
|
||||
/* Parse +History block (optional).
|
||||
* StandardParser stores the '+' prefix in the node name, so we try both. */
|
||||
if (cfg.MoveRelative("+History") || cfg.MoveRelative("History")) {
|
||||
@@ -194,8 +233,8 @@ bool StreamHub::Initialise(StructuredDataI &cfg) {
|
||||
|
||||
/* Allocate scratch buffers */
|
||||
pushBuf_ = new uint8[kPushBufSize];
|
||||
lttbT_ = new float64[maxPushPoints_];
|
||||
lttbV_ = new float64[maxPushPoints_];
|
||||
lttbT_ = new float64[kPushScratchPts];
|
||||
lttbV_ = new float64[kPushScratchPts];
|
||||
pushT_ = new float64[kPushScratchPts];
|
||||
pushV_ = new float64[kPushScratchPts];
|
||||
|
||||
@@ -309,6 +348,7 @@ bool StreamHub::Run() {
|
||||
if (statsDivisor == 0u) { statsDivisor = 1u; }
|
||||
if ((tickCount_ % statsDivisor) == 0u) {
|
||||
PushStats();
|
||||
GrowRingsForTrigger();
|
||||
}
|
||||
|
||||
/* History: flush headers at the configured interval, then re-broadcast
|
||||
@@ -337,11 +377,16 @@ bool StreamHub::Run() {
|
||||
|
||||
tickCount_++;
|
||||
|
||||
/* Sleep for remainder of period */
|
||||
/* Sleep for remainder of period. Both operands are unsigned, so the
|
||||
* comparison must be done additively: a tick that overruns the period
|
||||
* (easy at multi-Msps ingest, and guaranteed on the first tick, which
|
||||
* drains the whole ring) would otherwise wrap periodUs - elapsedUs to
|
||||
* ~2^64 and park the push thread for weeks — no data frames, no stats
|
||||
* and no trigger captures for the rest of the run. */
|
||||
uint64 t1 = MARTe::HighResolutionTimer::Counter();
|
||||
uint64 freq = MARTe::HighResolutionTimer::Frequency();
|
||||
uint64 elapsedUs = ((t1 - t0) * 1000000u) / freq;
|
||||
if (periodUs - elapsedUs > 1000) {
|
||||
if ((elapsedUs + 1000u) < periodUs) {
|
||||
Sleep::MSec(static_cast<uint32>((periodUs - elapsedUs) / 1000u));
|
||||
}
|
||||
}
|
||||
@@ -468,19 +513,35 @@ uint32 StreamHub::SerializeBinaryFrame(uint32 sessionIdx,
|
||||
pushT_, pushV_, kPushScratchPts);
|
||||
if (nRaw == 0u) { continue; }
|
||||
|
||||
/* LTTB decimation only for temporal (multi-element, sample-timed)
|
||||
* signals — Go hub policy. Scalars and PACKET-timed arrays are
|
||||
* pushed verbatim (their per-tick batches are small). */
|
||||
/* LTTB decimation for the live push, bounded for every signal.
|
||||
*
|
||||
* A PACKET-timed array is a snapshot waveform, so its floor is one
|
||||
* packet's worth of elements: below that LTTB would flatten the very
|
||||
* waveform the operator is looking at, above it each extra point is
|
||||
* just backlog from packets that piled up during the tick. Exempting
|
||||
* those arrays altogether (the old rule, on the assumption that their
|
||||
* per-tick batches are small) does not survive a fast producer: the
|
||||
* 5 kHz x 1000-element time array in the demo pushes ~65k points per
|
||||
* tick, 31 MB/s — 30x the channel it timestamps — and the per-client
|
||||
* push queue never drains.
|
||||
*
|
||||
* Decimating here costs no fidelity downstream: LTTB picks real
|
||||
* samples (it never interpolates), the rings keep every sample, and
|
||||
* zoom/history/trigger all re-read the rings at full resolution. */
|
||||
const uint32 nElems = desc.numRows * ((desc.numCols > 0u) ? desc.numCols : 1u);
|
||||
const bool temporal = (nElems > 1u) &&
|
||||
(desc.timeMode != MARTe::UDPS_TIMEMODE_PACKET);
|
||||
uint32 threshold = maxPushPoints_;
|
||||
if ((desc.timeMode == MARTe::UDPS_TIMEMODE_PACKET) &&
|
||||
(nElems > threshold)) {
|
||||
threshold = nElems;
|
||||
}
|
||||
if (threshold > kPushScratchPts) { threshold = kPushScratchPts; }
|
||||
|
||||
const float64 *tOut;
|
||||
const float64 *vOut;
|
||||
uint32 nOut;
|
||||
if (temporal && (nRaw > maxPushPoints_)) {
|
||||
if (nRaw > threshold) {
|
||||
nOut = LTTBDecimate(pushT_, pushV_, nRaw,
|
||||
lttbT_, lttbV_, maxPushPoints_);
|
||||
lttbT_, lttbV_, threshold);
|
||||
tOut = lttbT_;
|
||||
vOut = lttbV_;
|
||||
} else {
|
||||
@@ -1556,7 +1617,8 @@ void StreamHub::HandleTrigStop(const char *json) {
|
||||
void StreamHub::HandleSetTrigger(const char *json) {
|
||||
/* Web client shape:
|
||||
* {"type":"setTrigger","signal":"src:sig[i]","edge":"rising|falling|both",
|
||||
* "threshold":F,"windowSec":F,"prePercent":F,"mode":"normal|single"} */
|
||||
* "threshold":F,"windowSec":F,"prePercent":F,"mode":"normal|single",
|
||||
* "holdoffSec":F} */
|
||||
TriggerConfig cfg = trigger_.GetConfig();
|
||||
|
||||
char key[160] = "";
|
||||
@@ -1565,6 +1627,7 @@ void StreamHub::HandleSetTrigger(const char *json) {
|
||||
float64 thr = cfg.threshold;
|
||||
float64 winSec = cfg.windowSec;
|
||||
float64 prePct = cfg.prePercent;
|
||||
float64 holdoff = cfg.holdoffSec;
|
||||
|
||||
if (JsonGetString(json, "signal", key, sizeof(key))) {
|
||||
cfg.signalKey = key;
|
||||
@@ -1580,8 +1643,20 @@ void StreamHub::HandleSetTrigger(const char *json) {
|
||||
if (JsonGetFloat(json, "threshold", thr)) { cfg.threshold = thr; }
|
||||
if (JsonGetFloat(json, "windowSec", winSec)) { cfg.windowSec = winSec; }
|
||||
if (JsonGetFloat(json, "prePercent", prePct)) { cfg.prePercent = prePct; }
|
||||
if (JsonGetFloat(json, "holdoffSec", holdoff)) { cfg.holdoffSec = holdoff; }
|
||||
|
||||
trigger_.SetConfig(cfg);
|
||||
/* A capture can only contain what the rings still hold: the default
|
||||
* capacity is a point count, so at 1 Msps it covers ~1 s and every longer
|
||||
* window came back with only its tail populated. Publish the requested
|
||||
* retention so the push thread can size the rings to the actual measured
|
||||
* sample rate. */
|
||||
trigRetentionSec_ = cfg.windowSec;
|
||||
/* Grow now, not on the next stats tick: clients send setTrigger and arm
|
||||
* back to back, and a trigger that fires before the rings are resized
|
||||
* still loses its pre-trigger data. The periodic call stays as the catch-up
|
||||
* path for sources that connect later. */
|
||||
GrowRingsForTrigger();
|
||||
BroadcastTriggerState();
|
||||
}
|
||||
|
||||
@@ -1593,22 +1668,67 @@ void StreamHub::TriggerTick(float64 wallNowS) {
|
||||
/* Capture-margin: wait a little past the post window so the rings have
|
||||
* received the last post-trigger samples (web client used 120 ms). */
|
||||
static const float64 kCaptureMarginS = 0.15;
|
||||
static const float64 kAutoRearmDelayS = 0.2;
|
||||
|
||||
|
||||
const TrigState st = trigger_.GetState();
|
||||
|
||||
/* Wall-clock grace on top of the post window before giving up on a source
|
||||
* that stopped advancing; the capture is then broadcast with whatever the
|
||||
* rings hold. */
|
||||
static const float64 kCaptureWatchdogS = 2.0;
|
||||
|
||||
if (st == kTrigCollecting) {
|
||||
const bool justEntered = (lastTrigState_ != kTrigCollecting);
|
||||
if (justEntered) { collectStartWallS_ = wallNowS; }
|
||||
|
||||
float64 trigTime = 0.0;
|
||||
float64 preSec = 0.0;
|
||||
float64 postSec = 0.0;
|
||||
if (trigger_.GetFiredWindow(trigTime, preSec, postSec) &&
|
||||
(wallNowS >= (trigTime + postSec + kCaptureMarginS))) {
|
||||
BroadcastTriggerCapture(trigTime, preSec, postSec);
|
||||
trigger_.MarkTriggered();
|
||||
TriggerConfig cfg = trigger_.GetConfig();
|
||||
if ((cfg.mode == kTrigNormal) && !trigger_.GetStopped()) {
|
||||
rearmPending_ = true;
|
||||
rearmAtWallS_ = wallNowS + kAutoRearmDelayS;
|
||||
if (trigger_.GetFiredWindow(trigTime, preSec, postSec)) {
|
||||
/* Always restart the frame on entry: a capture abandoned by a
|
||||
* disarm would otherwise be resumed with the previous trigTime. */
|
||||
if (justEntered || (capBuf_ == static_cast<MARTe::uint8 *>(0))) {
|
||||
BeginTriggerCapture(trigTime, preSec, postSec);
|
||||
}
|
||||
/* Harvest on the *data's* clock. trigTime comes from the sample
|
||||
* timestamps, and a source's time base is offset from — and drifts
|
||||
* against — CLOCK_REALTIME, so a wall-clock deadline chops the tail
|
||||
* off every capture by exactly that offset. The wall clock is only
|
||||
* a watchdog for a source that went quiet. */
|
||||
const bool timedOut =
|
||||
(wallNowS >= (collectStartWallS_ + postSec + kCaptureWatchdogS));
|
||||
const float64 deadline = trigTime + postSec + kCaptureMarginS;
|
||||
|
||||
bool allDone = true;
|
||||
for (uint32 i = 0u; i < kMaxSessions; i++) {
|
||||
if (!sessionActive_[i] || capHarvested_[i]) { continue; }
|
||||
const float64 frontier = SourceFrontierTime(i, wallNowS);
|
||||
if (frontier >= deadline) {
|
||||
HarvestTriggerCapture(i, trigTime - preSec,
|
||||
trigTime + postSec);
|
||||
}
|
||||
else if (timedOut) {
|
||||
REPORT_ERROR_STATIC(MARTe::ErrorManagement::Warning,
|
||||
"StreamHub: source %s timed out at %.3f s of the %.3f s "
|
||||
"trigger window; its traces will be short.",
|
||||
sessions_[i].GetId().Buffer(), frontier - trigTime,
|
||||
postSec);
|
||||
HarvestTriggerCapture(i, trigTime - preSec,
|
||||
trigTime + postSec);
|
||||
}
|
||||
else {
|
||||
allDone = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (allDone || timedOut) {
|
||||
FinishTriggerCapture();
|
||||
trigger_.MarkTriggered();
|
||||
TriggerConfig cfg = trigger_.GetConfig();
|
||||
if ((cfg.mode == kTrigNormal) && !trigger_.GetStopped()) {
|
||||
rearmPending_ = true;
|
||||
rearmAtWallS_ = wallNowS + cfg.holdoffSec;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1627,6 +1747,45 @@ void StreamHub::TriggerTick(float64 wallNowS) {
|
||||
}
|
||||
}
|
||||
|
||||
float64 StreamHub::SourceFrontierTime(uint32 i, float64 wallNowS) const {
|
||||
const float64 t = sessions_[i].ProducerNewestTime();
|
||||
/* No producer clock means every sample was stamped on arrival, so this
|
||||
* source and the trigger both live in the hub's wall-clock domain. */
|
||||
return (t > 0.0) ? t : wallNowS;
|
||||
}
|
||||
|
||||
uint32 StreamHub::CurrentMaxRingCapacity() const {
|
||||
/* Rings grow at runtime for long trigger windows, so read the live
|
||||
* capacities rather than the configured starting size. */
|
||||
uint32 maxCap = (ringTemporal_ > ringScalar_) ? ringTemporal_ : ringScalar_;
|
||||
for (uint32 i = 0u; i < kMaxSessions; i++) {
|
||||
if (!sessionActive_[i]) { continue; }
|
||||
const uint32 c = sessions_[i].GetMaxRingCapacity();
|
||||
if (c > maxCap) { maxCap = c; }
|
||||
}
|
||||
return maxCap;
|
||||
}
|
||||
|
||||
void StreamHub::GrowRingsForTrigger() {
|
||||
const float64 want = trigRetentionSec_;
|
||||
if (want <= 0.0) { return; }
|
||||
|
||||
/* Retain the whole window plus the capture margin and one push period, so
|
||||
* the tail of the window is still in the ring when TriggerTick reads it. */
|
||||
const float64 target = want + 0.5;
|
||||
|
||||
for (uint32 i = 0u; i < kMaxSessions; i++) {
|
||||
if (!sessionActive_[i]) { continue; }
|
||||
if (!sessions_[i].IsConfigured()) { continue; }
|
||||
if (sessions_[i].GrowRingsForSeconds(target, ringMaxPts_)) {
|
||||
REPORT_ERROR_STATIC(MARTe::ErrorManagement::Information,
|
||||
"StreamHub: grew rings of source %s to hold %.2f s "
|
||||
"(trigger window %.2f s, cap %u pts/signal).",
|
||||
sessions_[i].GetId().Buffer(), target, want, ringMaxPts_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StreamHub::BroadcastTriggerState() {
|
||||
const TrigState st = trigger_.GetState();
|
||||
TriggerConfig cfg = trigger_.GetConfig();
|
||||
@@ -1639,17 +1798,23 @@ void StreamHub::BroadcastTriggerState() {
|
||||
(st == kTrigTriggered) ? "triggered" : "idle";
|
||||
const char *modeStr = (cfg.mode == kTrigSingle) ? "single" : "normal";
|
||||
|
||||
char buf[256];
|
||||
char buf[512];
|
||||
int n;
|
||||
float64 trigTime = 0.0;
|
||||
float64 preSec = 0.0;
|
||||
float64 postSec = 0.0;
|
||||
if (((st == kTrigCollecting) || (st == kTrigTriggered)) &&
|
||||
trigger_.GetFiredWindow(trigTime, preSec, postSec)) {
|
||||
/* The window latched at fire time. Clients draw the filling capture on
|
||||
* this axis before the v2 frame arrives, and config edits between arm
|
||||
* and fire would otherwise leave them inferring the wrong window from
|
||||
* their own copy of the config. */
|
||||
n = snprintf(buf, sizeof(buf),
|
||||
"{\"type\":\"triggerState\",\"state\":\"%s\",\"mode\":\"%s\","
|
||||
"\"stopped\":%s,\"trigTime\":%.17g}",
|
||||
stateStr, modeStr, (stopped ? "true" : "false"), trigTime);
|
||||
"\"stopped\":%s,\"trigTime\":%.17g,\"preSec\":%.17g,"
|
||||
"\"postSec\":%.17g}",
|
||||
stateStr, modeStr, (stopped ? "true" : "false"), trigTime,
|
||||
preSec, postSec);
|
||||
} else {
|
||||
n = snprintf(buf, sizeof(buf),
|
||||
"{\"type\":\"triggerState\",\"state\":\"%s\",\"mode\":\"%s\","
|
||||
@@ -1661,109 +1826,116 @@ void StreamHub::BroadcastTriggerState() {
|
||||
}
|
||||
}
|
||||
|
||||
void StreamHub::BroadcastTriggerCapture(float64 trigTime, float64 preSec,
|
||||
float64 postSec) {
|
||||
const float64 t0 = trigTime - preSec;
|
||||
const float64 t1 = trigTime + postSec;
|
||||
void StreamHub::BeginTriggerCapture(float64 trigTime, float64 preSec,
|
||||
float64 postSec) {
|
||||
delete[] capBuf_;
|
||||
capCap_ = 1u << 20;
|
||||
capBuf_ = new uint8[capCap_];
|
||||
capOff_ = 0u;
|
||||
capNSig_ = 0u;
|
||||
for (uint32 i = 0u; i < kMaxSessions; i++) { capHarvested_[i] = false; }
|
||||
|
||||
/* Header: [u8 2][f64 trigTime][f64 preSec][f64 postSec][u32 nSig] */
|
||||
capBuf_[capOff_++] = 2u;
|
||||
memcpy(capBuf_ + capOff_, &trigTime, 8u); capOff_ += 8u;
|
||||
memcpy(capBuf_ + capOff_, &preSec, 8u); capOff_ += 8u;
|
||||
memcpy(capBuf_ + capOff_, &postSec, 8u); capOff_ += 8u;
|
||||
capOff_ += 4u; /* nSig, patched in FinishTriggerCapture */
|
||||
}
|
||||
|
||||
void StreamHub::HarvestTriggerCapture(uint32 i, float64 t0, float64 t1) {
|
||||
capHarvested_[i] = true;
|
||||
|
||||
UDPSourceSession &sess = sessions_[i];
|
||||
if ((capBuf_ == static_cast<uint8 *>(0)) || !sess.IsConfigured()) { return; }
|
||||
|
||||
/* Read scratch sized for the largest ring; LTTB scratch for the cap. */
|
||||
const uint32 scratchCap = (ringTemporal_ > ringScalar_) ? ringTemporal_
|
||||
: ringScalar_;
|
||||
const uint32 scratchCap = CurrentMaxRingCapacity();
|
||||
float64 *tRaw = new float64[scratchCap];
|
||||
float64 *vRaw = new float64[scratchCap];
|
||||
float64 *tDec = new float64[kTrigCapturePts];
|
||||
float64 *vDec = new float64[kTrigCapturePts];
|
||||
|
||||
uint32 cap = 1u << 20;
|
||||
uint8 *buf = new uint8[cap];
|
||||
uint32 off = 0u;
|
||||
StreamString sid = sess.GetId();
|
||||
const uint32 numSigs = sess.GetNumSignals();
|
||||
|
||||
/* Header: [u8 2][f64 trigTime][f64 preSec][f64 postSec][u32 nSig] */
|
||||
buf[off++] = 2u;
|
||||
memcpy(buf + off, &trigTime, 8u); off += 8u;
|
||||
memcpy(buf + off, &preSec, 8u); off += 8u;
|
||||
memcpy(buf + off, &postSec, 8u); off += 8u;
|
||||
const uint32 nSigOff = off;
|
||||
uint32 nSigWritten = 0u;
|
||||
off += 4u;
|
||||
for (uint32 s = 0u; s < numSigs; s++) {
|
||||
MARTe::UDPSSignalDescriptor desc;
|
||||
if (!sess.GetSignalDescriptor(s, desc)) { continue; }
|
||||
|
||||
for (uint32 i = 0u; i < kMaxSessions; i++) {
|
||||
if (!sessionActive_[i]) { continue; }
|
||||
UDPSourceSession &sess = sessions_[i];
|
||||
if (!sess.IsConfigured()) { continue; }
|
||||
const uint32 nRaw = sess.ReadSignalRange(s, t0, t1,
|
||||
tRaw, vRaw, scratchCap);
|
||||
if (nRaw == 0u) { continue; }
|
||||
|
||||
StreamString sid = sess.GetId();
|
||||
const uint32 numSigs = sess.GetNumSignals();
|
||||
|
||||
for (uint32 s = 0u; s < numSigs; s++) {
|
||||
MARTe::UDPSSignalDescriptor desc;
|
||||
if (!sess.GetSignalDescriptor(s, desc)) { continue; }
|
||||
|
||||
const uint32 nRaw = sess.ReadSignalRange(s, t0, t1,
|
||||
tRaw, vRaw, scratchCap);
|
||||
if (nRaw == 0u) { continue; }
|
||||
|
||||
const float64 *tOut = tRaw;
|
||||
const float64 *vOut = vRaw;
|
||||
uint32 nOut = nRaw;
|
||||
if (nRaw > kTrigCapturePts) {
|
||||
nOut = LTTBDecimate(tRaw, vRaw, nRaw, tDec, vDec,
|
||||
kTrigCapturePts);
|
||||
tOut = tDec;
|
||||
vOut = vDec;
|
||||
}
|
||||
|
||||
char fullKey[192];
|
||||
const int kn = snprintf(fullKey, sizeof(fullKey), "%s:%s",
|
||||
sid.Buffer(), desc.name);
|
||||
if (kn <= 0) { continue; }
|
||||
const uint32 keyLen = static_cast<uint32>(kn);
|
||||
|
||||
const uint32 need = 2u + keyLen + 4u + nOut * 16u;
|
||||
if ((off + need) > cap) {
|
||||
uint32 newCap = cap * 2u;
|
||||
while ((off + need) > newCap) { newCap *= 2u; }
|
||||
uint8 *nb = new uint8[newCap];
|
||||
memcpy(nb, buf, off);
|
||||
delete[] buf;
|
||||
buf = nb;
|
||||
cap = newCap;
|
||||
}
|
||||
|
||||
buf[off++] = static_cast<uint8>( keyLen & 0xFFu);
|
||||
buf[off++] = static_cast<uint8>((keyLen >> 8) & 0xFFu);
|
||||
memcpy(buf + off, fullKey, keyLen);
|
||||
off += keyLen;
|
||||
|
||||
buf[off++] = static_cast<uint8>( nOut & 0xFFu);
|
||||
buf[off++] = static_cast<uint8>((nOut >> 8) & 0xFFu);
|
||||
buf[off++] = static_cast<uint8>((nOut >> 16) & 0xFFu);
|
||||
buf[off++] = static_cast<uint8>((nOut >> 24) & 0xFFu);
|
||||
|
||||
memcpy(buf + off, tOut, nOut * sizeof(float64));
|
||||
off += nOut * 8u;
|
||||
memcpy(buf + off, vOut, nOut * sizeof(float64));
|
||||
off += nOut * 8u;
|
||||
|
||||
nSigWritten++;
|
||||
const float64 *tOut = tRaw;
|
||||
const float64 *vOut = vRaw;
|
||||
uint32 nOut = nRaw;
|
||||
if (nRaw > kTrigCapturePts) {
|
||||
nOut = LTTBDecimate(tRaw, vRaw, nRaw, tDec, vDec, kTrigCapturePts);
|
||||
tOut = tDec;
|
||||
vOut = vDec;
|
||||
}
|
||||
|
||||
char fullKey[192];
|
||||
const int kn = snprintf(fullKey, sizeof(fullKey), "%s:%s",
|
||||
sid.Buffer(), desc.name);
|
||||
if (kn <= 0) { continue; }
|
||||
const uint32 keyLen = static_cast<uint32>(kn);
|
||||
|
||||
const uint32 need = 2u + keyLen + 4u + nOut * 16u;
|
||||
if ((capOff_ + need) > capCap_) {
|
||||
uint32 newCap = capCap_ * 2u;
|
||||
while ((capOff_ + need) > newCap) { newCap *= 2u; }
|
||||
uint8 *nb = new uint8[newCap];
|
||||
memcpy(nb, capBuf_, capOff_);
|
||||
delete[] capBuf_;
|
||||
capBuf_ = nb;
|
||||
capCap_ = newCap;
|
||||
}
|
||||
|
||||
capBuf_[capOff_++] = static_cast<uint8>( keyLen & 0xFFu);
|
||||
capBuf_[capOff_++] = static_cast<uint8>((keyLen >> 8) & 0xFFu);
|
||||
memcpy(capBuf_ + capOff_, fullKey, keyLen);
|
||||
capOff_ += keyLen;
|
||||
|
||||
capBuf_[capOff_++] = static_cast<uint8>( nOut & 0xFFu);
|
||||
capBuf_[capOff_++] = static_cast<uint8>((nOut >> 8) & 0xFFu);
|
||||
capBuf_[capOff_++] = static_cast<uint8>((nOut >> 16) & 0xFFu);
|
||||
capBuf_[capOff_++] = static_cast<uint8>((nOut >> 24) & 0xFFu);
|
||||
|
||||
memcpy(capBuf_ + capOff_, tOut, nOut * sizeof(float64));
|
||||
capOff_ += nOut * 8u;
|
||||
memcpy(capBuf_ + capOff_, vOut, nOut * sizeof(float64));
|
||||
capOff_ += nOut * 8u;
|
||||
|
||||
capNSig_++;
|
||||
}
|
||||
|
||||
/* Patch nSig */
|
||||
buf[nSigOff] = static_cast<uint8>( nSigWritten & 0xFFu);
|
||||
buf[nSigOff + 1u] = static_cast<uint8>((nSigWritten >> 8) & 0xFFu);
|
||||
buf[nSigOff + 2u] = static_cast<uint8>((nSigWritten >> 16) & 0xFFu);
|
||||
buf[nSigOff + 3u] = static_cast<uint8>((nSigWritten >> 24) & 0xFFu);
|
||||
delete[] tRaw; delete[] vRaw;
|
||||
delete[] tDec; delete[] vDec;
|
||||
}
|
||||
|
||||
wsServer_.BroadcastBinary(buf, off);
|
||||
void StreamHub::FinishTriggerCapture() {
|
||||
if (capBuf_ == static_cast<uint8 *>(0)) { return; }
|
||||
|
||||
/* Patch nSig (immediately after the [u8 2] + 3×f64 header). */
|
||||
const uint32 nSigOff = 25u;
|
||||
capBuf_[nSigOff] = static_cast<uint8>( capNSig_ & 0xFFu);
|
||||
capBuf_[nSigOff + 1u] = static_cast<uint8>((capNSig_ >> 8) & 0xFFu);
|
||||
capBuf_[nSigOff + 2u] = static_cast<uint8>((capNSig_ >> 16) & 0xFFu);
|
||||
capBuf_[nSigOff + 3u] = static_cast<uint8>((capNSig_ >> 24) & 0xFFu);
|
||||
|
||||
wsServer_.BroadcastBinary(capBuf_, capOff_);
|
||||
|
||||
REPORT_ERROR_STATIC(MARTe::ErrorManagement::Information,
|
||||
"StreamHub: trigger capture broadcast (%u signal(s), %u bytes).",
|
||||
nSigWritten, off);
|
||||
capNSig_, capOff_);
|
||||
|
||||
delete[] buf;
|
||||
delete[] tRaw; delete[] vRaw;
|
||||
delete[] tDec; delete[] vDec;
|
||||
delete[] capBuf_;
|
||||
capBuf_ = static_cast<uint8 *>(0);
|
||||
capCap_ = 0u;
|
||||
capOff_ = 0u;
|
||||
capNSig_ = 0u;
|
||||
}
|
||||
|
||||
void StreamHub::HandleZoom(const char *json, uint32 slotIdx) {
|
||||
@@ -1797,8 +1969,7 @@ void StreamHub::HandleZoom(const char *json, uint32 slotIdx) {
|
||||
|
||||
/* Read scratch sized for the largest possible ring (no double decimation:
|
||||
* the whole [t0,t1] slice is read, then LTTB'd once to maxOut). */
|
||||
const uint32 scratchCap = (ringTemporal_ > ringScalar_) ? ringTemporal_
|
||||
: ringScalar_;
|
||||
const uint32 scratchCap = CurrentMaxRingCapacity();
|
||||
float64 *tRaw = new float64[scratchCap];
|
||||
float64 *vRaw = new float64[scratchCap];
|
||||
float64 *tDec = (maxOut > 0u) ? new float64[maxOut] : static_cast<float64 *>(0);
|
||||
|
||||
Reference in New Issue
Block a user