Implemented new C++ logic

This commit is contained in:
Martino Ferrari
2026-06-12 15:25:13 +02:00
parent 617b5bd712
commit f25bd7f08e
220 changed files with 39185 additions and 850 deletions
+47 -3
View File
@@ -82,6 +82,14 @@ type MarteController struct {
cmdPort int
udpPort int
logPort int
// rawSigs holds the most-recently received UDP CONFIG signals before
// translation. After DISCOVER populates m.signals, we re-translate and
// push a corrected config to the hub so that src.signals names match the
// s.Values keys used by ParseData (which also re-translates rawSigs).
rawSigsMu sync.RWMutex
rawSigs []udpsprotocol.SignalInfo
rawPm uint8
}
// NewMarteController creates a MarteController bound to the given hub.
@@ -483,8 +491,19 @@ func (m *MarteController) handleJSONResponse(tag, data string) {
all := append(m.discoverAcc, resp.Signals...)
m.discoverAcc = nil
m.parseDiscoverSignals(all)
// Synthesize SignalInfo for the hub so the plot system gets a config
m.synthesizeHubConfig(all)
// Update hub config now that m.signals is populated for name translation.
// If UDP CONFIG was already received (rawSigs non-empty), re-translate the
// real config so that src.signals uses the same names as s.Values produced
// by ParseData (which also re-translates rawSigs on every PktData).
// If UDP CONFIG has not arrived yet, fall back to synthesizing from DISCOVER.
m.rawSigsMu.RLock()
raw := m.rawSigs
m.rawSigsMu.RUnlock()
if len(raw) > 0 {
m.hub.UpdateConfigForSource("debug", m.translateSignalNames(raw))
} else {
m.synthesizeHubConfig(all)
}
// Re-marshal the merged list so the browser gets a single consistent blob.
merged, _ := json.Marshal(discoverResp{Signals: all})
broadcastHub(m.hub, map[string]any{
@@ -747,7 +766,15 @@ func (m *MarteController) synthesizeHubConfig(sigs []discoverSignalJSON) {
}
sigInfos = append(sigInfos, si)
}
m.hub.UpdateConfigForSource("debug", sigInfos)
// Translate to GAM-path names so the synthesised config matches the names
// that the real UDP CONFIG (and data frames) use. Without this translation,
// synthesizeHubConfig would overwrite the real config with DISCOVER-canonical
// names (e.g. "DDB1.Ch3"), causing buildBinaryDataMessageForSource to fail to
// find signals in s.Values (which uses translated names), starving the JS
// buffer and limiting live streaming to the fraction of a second that
// accumulated before the DISCOVER response arrived.
translated := m.translateSignalNames(sigInfos)
m.hub.UpdateConfigForSource("debug", translated)
}
// ---------------------------------------------------------------------------
@@ -829,6 +856,12 @@ func (m *MarteController) runDebugUDP(host string, port int) {
log.Printf("[debug-udp] parse config: %v", err)
continue
}
// Store raw (untranslated) sigs so that after DISCOVER populates
// m.signals we can re-translate and push a corrected config to the hub.
m.rawSigsMu.Lock()
m.rawSigs = sigs
m.rawPm = pm
m.rawSigsMu.Unlock()
sigs = m.translateSignalNames(sigs)
currentSigs = sigs
currentPublishMode = pm
@@ -839,6 +872,17 @@ func (m *MarteController) runDebugUDP(host string, port int) {
if len(currentSigs) == 0 {
continue
}
// Re-translate on every data packet: if DISCOVER ran since the last
// UDP CONFIG arrived (which is the common case — CONFIG is sent once
// at startup, before the browser connects and triggers DISCOVER),
// m.signals is now populated and we must use translated names so that
// s.Values keys match src.signals keys in buildBinaryDataMessageForSource.
m.rawSigsMu.RLock()
raw := m.rawSigs
m.rawSigsMu.RUnlock()
if len(raw) > 0 {
currentSigs = m.translateSignalNames(raw)
}
samples, err := udpsprotocol.ParseData(complete, currentSigs, currentPublishMode, arrivalTime)
if err != nil {
log.Printf("[debug-udp] parse data: %v", err)
+37 -22
View File
@@ -338,6 +338,12 @@ function connectWS() {
ws.onopen = () => { wsBackoff = 1000; setStatus('orange', 'Connected waiting for data'); };
ws.onclose = () => {
setStatus('red', 'Disconnected (reconnecting…)');
// Reset MARTe connection state so that when the WS reconnects the hub can
// replay the 'connected' message and trigger a fresh DISCOVER/TREE sequence.
// Without this, the marteConnected guard in onMarteConnected blocks the
// sequence from re-running after a hub-WS reconnect (even if the MARTe2 TCP
// connection stayed alive and no 'disconnected' message was sent).
marteConnected = false;
setTimeout(connectWS, wsBackoff);
wsBackoff = Math.min(wsBackoff * 2, 30000);
};
@@ -353,7 +359,7 @@ function connectWS() {
else if (msg.type === 'connected') onMarteConnected();
else if (msg.type === 'disconnected') onMarteDisconnected();
else if (msg.type === 'log') onLog(msg);
else if (msg.type === 'response') onResponse(msg);
else if (msg.type === 'response') { onResponse(msg); }
else if (msg.type === 'tree_node') { try { onTreeNode(JSON.parse(msg.data)); } catch(e) { console.error('tree_node error:', e, msg.data); appendLog('sys','ERROR','Tree render error: '+e.message); } }
else if (msg.type === 'text_line') onTextLine(msg.data);
else if (msg.type === 'service_config') onServiceConfig(msg);
@@ -411,7 +417,12 @@ function onConfig(msg) {
const src = sourcesMap[sid];
const newSigs = msg.signals || [];
const oldSigs = src.signals || [];
const fp = s => s.name + ':' + s.typeCode + ':' + (s.numRows || 1) + ':' + (s.numCols || 1) + ':' + (s.timeMode || 0);
// Fingerprint only covers fields that change the binary layout of the data.
// timeMode and samplingRate are intentionally excluded: synthesizeHubConfig
// always sets timeMode=TimeModePacket, so including it would cause a buffer
// reset on every real UDP CONFIG arrival (synthesized→real transition), wiping
// the fast-signal buffer and limiting visible history to ~0.5 s.
const fp = s => s.name + ':' + s.typeCode + ':' + numElements(s);
const changed = newSigs.length !== oldSigs.length || newSigs.some((s, i) => fp(s) !== fp(oldSigs[i]));
src.signals = newSigs;
if (changed) {
@@ -1011,14 +1022,17 @@ function buildDataFromFetched(p, fetchedSignals, targetPts) {
const t0 = p.xRange ? p.xRange[0] : -Infinity;
const t1 = p.xRange ? p.xRange[1] : Infinity;
let masterKey = p.traces[0], masterCount = -1, masterRate = -1;
let masterKey = p.traces[0], masterCount = -1, masterRate = -1, masterSpan = -1;
for (const key of p.traces) {
let sd = fetchedSignals[key];
if (!sd || !sd.t || sd.t.length < 2) sd = supplementWithBrackets(sd, buffers[key], t0, t1);
if (!sd || !sd.t.length) continue;
const rate = getKeySamplingRate(key);
if (rate > masterRate || (rate === masterRate && sd.t.length > masterCount)) {
masterRate = rate; masterCount = sd.t.length; masterKey = key;
const span = sd.t.length > 1 ? sd.t[sd.t.length - 1] - sd.t[0] : 0;
if (span > masterSpan ||
(span === masterSpan && rate > masterRate) ||
(span === masterSpan && rate === masterRate && sd.t.length > masterCount)) {
masterRate = rate; masterSpan = span; masterCount = sd.t.length; masterKey = key;
}
}
@@ -1244,7 +1258,7 @@ function drawBandSeparators(u, p) {
const dpr = window.devicePixelRatio || 1;
const bandH = 8 / n;
ctx.save();
ctx.strokeStyle = 'rgba(127,132,156,0.25)';
ctx.strokeStyle = 'rgba(127,132,156,0.55)';
ctx.lineWidth = dpr;
for (let i = 1; i < n; i++) {
const yNorm = 4 - i * bandH; // boundary between band i-1 and band i
@@ -1841,20 +1855,26 @@ function buildLiveData(p) {
}
}
// Slice all traces; pick master by sampling rate then count.
// Slice all traces; pick master by time span (widest window wins), then
// by sampling rate, then by point count. A fast signal that has only 0.5s
// of history must NOT beat a slow signal that has 5s of history — otherwise
// sharedT covers only 0.5s and all other signals appear truncated.
// In zoom mode, include one bracketing point on each side so lines are drawn
// across the visible area even when the window contains 0 or 1 samples.
const sliceFn = isRolling ? getBufferSliceRange : getBufferSliceRangeWithBrackets;
const slices = {};
let masterKey = p.traces[0], masterCount = -1, masterRate = -1;
let masterKey = p.traces[0], masterCount = -1, masterRate = -1, masterSpan = -1;
for (const key of p.traces) {
const buf = buffers[key];
if (!buf || buf.size === 0) continue;
const sl = sliceFn(buf, t0, t1);
slices[key] = sl;
const rate = getKeySamplingRate(key);
if (rate > masterRate || (rate === masterRate && sl.t.length > masterCount)) {
masterRate = rate; masterCount = sl.t.length; masterKey = key;
const span = sl.t.length > 1 ? sl.t[sl.t.length - 1] - sl.t[0] : 0;
if (span > masterSpan ||
(span === masterSpan && rate > masterRate) ||
(span === masterSpan && rate === masterRate && sl.t.length > masterCount)) {
masterRate = rate; masterSpan = span; masterCount = sl.t.length; masterKey = key;
}
}
@@ -2090,19 +2110,9 @@ document.getElementById('btn-zoom-fit').addEventListener('click', zoomFit);
/* ════════════════════════════════════════════════════════════════
Cursor controls
════════════════════════════════════════════════════════════════ */
// Show the cursor button only when paused or in trigger-snapshot mode.
// Cursor button is always visible; readout visibility follows cursors.mode.
function updateCursorBtnVisibility() {
const canUseCursors = globalPause || (trig.enabled && trig.snapshot !== null);
const btn = document.getElementById('btn-cursor');
btn.style.display = canUseCursors ? '' : 'none';
if (!canUseCursors && cursors.mode !== 'off') {
cursors.mode = 'off';
cursors.tA = null; cursors.tB = null; // context changed, clear positions
btn.textContent = 'Cursors';
btn.classList.remove('active');
document.getElementById('cursor-readout').classList.remove('visible');
cursorsDirty = true;
}
document.getElementById('btn-cursor').style.display = '';
}
document.getElementById('btn-cursor').addEventListener('click', () => {
@@ -3684,6 +3694,11 @@ function onTracedState(msg) {
}
function onMarteConnected() {
// Guard against double-invocation: onSources() may call us when it detects
// debug.state==='connected', and replayStateToClient also sends a 'connected'
// message. A second call would wipe _cmdSent mid-flight (via _clearStartupState),
// cancelling the in-progress DISCOVER/TREE sequence.
if (marteConnected) return;
marteConnected = true;
_clearStartupState();
const el = document.getElementById('conn-status');
+1 -1
View File
@@ -70,7 +70,7 @@
<option value="10">10 s</option><option value="30">30 s</option>
<option value="60">60 s</option>
</select>
<button id="btn-cursor" class="ctrl-btn" style="display:none">Cursor</button>
<button id="btn-cursor" class="ctrl-btn">Cursors</button>
<button id="btn-zoom-back" class="ctrl-btn" style="display:none">← Back</button>
<button id="btn-zoom-fit" class="ctrl-btn">Fit</button>
<button id="btn-csv-all" class="ctrl-btn" title="Export all signals to CSV">⬇ CSV</button>