Implemented new C++ logic
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user