feat: enhance waveform viewer with unit info display and event selection functionality
This commit is contained in:
@@ -427,14 +427,12 @@ def device_event_waveform(
|
||||
def _do():
|
||||
with _build_client(port, baud, host, tcp_port) as client:
|
||||
info = client.connect()
|
||||
events = client.get_events()
|
||||
# full_waveform=True fetches the complete 5A stream inside the
|
||||
# 1E→0A→0C→5A→1F loop. Issuing a second 5A after 1F times out.
|
||||
events = client.get_events(full_waveform=True)
|
||||
matching = [ev for ev in events if ev.index == index]
|
||||
if not matching:
|
||||
return None, None, info
|
||||
ev = matching[0]
|
||||
client.download_waveform(ev)
|
||||
return ev, events, info
|
||||
ev, events, info = _run_with_retry(_do, is_tcp=_is_tcp(host))
|
||||
return matching[0] if matching else None, info
|
||||
ev, info = _run_with_retry(_do, is_tcp=_is_tcp(host))
|
||||
except HTTPException:
|
||||
raise
|
||||
except ProtocolError as exc:
|
||||
|
||||
@@ -132,6 +132,49 @@
|
||||
.ch-vert { color: #3fb950; }
|
||||
.ch-long { color: #d29922; }
|
||||
.ch-mic { color: #bc8cff; }
|
||||
|
||||
#unit-bar {
|
||||
background: #0d1117;
|
||||
border-bottom: 1px solid #21262d;
|
||||
padding: 8px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.unit-field { display: flex; flex-direction: column; gap: 1px; }
|
||||
.unit-field .uf-label { color: #484f58; font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; }
|
||||
.unit-field .uf-value { color: #c9d1d9; font-family: monospace; font-size: 13px; }
|
||||
.unit-field .uf-value.highlight { color: #58a6ff; font-weight: 600; }
|
||||
|
||||
.event-chips {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
flex-wrap: wrap;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.event-chip {
|
||||
background: #21262d;
|
||||
border: 1px solid #30363d;
|
||||
border-radius: 5px;
|
||||
color: #8b949e;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
padding: 3px 10px;
|
||||
transition: all 0.12s;
|
||||
}
|
||||
.event-chip:hover { background: #1f6feb; border-color: #1f6feb; color: #fff; }
|
||||
.event-chip.active { background: #1f6feb; border-color: #388bfd; color: #fff; font-weight: 600; }
|
||||
|
||||
#connect-btn {
|
||||
background: #238636;
|
||||
margin-left: auto;
|
||||
}
|
||||
#connect-btn:hover { background: #2ea043; }
|
||||
#connect-btn:disabled { background: #21262d; color: #484f58; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -147,15 +190,35 @@
|
||||
<input type="text" id="dev-host" value="" placeholder="e.g. 10.0.0.5" />
|
||||
<label>TCP port</label>
|
||||
<input type="number" id="dev-tcp-port" value="9034" />
|
||||
<label>Event #</label>
|
||||
<input type="number" id="event-index" value="0" min="0" style="width:55px" />
|
||||
</div>
|
||||
<button id="load-btn" onclick="loadWaveform()">Load Waveform</button>
|
||||
<button id="connect-btn" onclick="connectUnit()">Connect</button>
|
||||
<button id="load-btn" onclick="loadWaveform()" disabled>Load Waveform</button>
|
||||
<button id="prev-btn" onclick="stepEvent(-1)" disabled>◀ Prev</button>
|
||||
<button id="next-btn" onclick="stepEvent(+1)" disabled>Next ▶</button>
|
||||
</header>
|
||||
|
||||
<div id="status-bar">Ready — enter device host and click Load Waveform.</div>
|
||||
<!-- Unit info bar — hidden until connected -->
|
||||
<div id="unit-bar" style="display:none">
|
||||
<div class="unit-field">
|
||||
<span class="uf-label">Serial</span>
|
||||
<span class="uf-value" id="u-serial">—</span>
|
||||
</div>
|
||||
<div class="unit-field">
|
||||
<span class="uf-label">Firmware</span>
|
||||
<span class="uf-value" id="u-fw">—</span>
|
||||
</div>
|
||||
<div class="unit-field">
|
||||
<span class="uf-label">Sample rate</span>
|
||||
<span class="uf-value" id="u-sr">—</span>
|
||||
</div>
|
||||
<div class="unit-field">
|
||||
<span class="uf-label">Events</span>
|
||||
<span class="uf-value highlight" id="u-count">—</span>
|
||||
</div>
|
||||
<div class="event-chips" id="event-chips"></div>
|
||||
</div>
|
||||
|
||||
<div id="status-bar">Ready — enter device host and click Connect.</div>
|
||||
|
||||
<div id="empty-state">
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
@@ -176,6 +239,8 @@
|
||||
|
||||
let charts = {};
|
||||
let lastData = null;
|
||||
let unitInfo = null;
|
||||
let currentEventIndex = 0;
|
||||
|
||||
function setStatus(msg, cls = '') {
|
||||
const bar = document.getElementById('status-bar');
|
||||
@@ -191,11 +256,84 @@
|
||||
bar.appendChild(pill);
|
||||
}
|
||||
|
||||
async function connectUnit() {
|
||||
const apiBase = document.getElementById('api-base').value.replace(/\/$/, '');
|
||||
const devHost = document.getElementById('dev-host').value.trim();
|
||||
const tcpPort = document.getElementById('dev-tcp-port').value;
|
||||
|
||||
if (!devHost) { setStatus('Enter a device host first.', 'error'); return; }
|
||||
|
||||
const btn = document.getElementById('connect-btn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Connecting…';
|
||||
setStatus('Connecting to unit…', 'loading');
|
||||
|
||||
const url = `${apiBase}/device/info?host=${encodeURIComponent(devHost)}&tcp_port=${tcpPort}`;
|
||||
try {
|
||||
const resp = await fetch(url);
|
||||
if (!resp.ok) {
|
||||
const err = await resp.json().catch(() => ({ detail: resp.statusText }));
|
||||
throw new Error(err.detail || resp.statusText);
|
||||
}
|
||||
unitInfo = await resp.json();
|
||||
} catch (e) {
|
||||
setStatus(`Error: ${e.message}`, 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Connect';
|
||||
return;
|
||||
}
|
||||
|
||||
// Populate unit bar
|
||||
document.getElementById('u-serial').textContent = unitInfo.serial || '—';
|
||||
document.getElementById('u-fw').textContent = unitInfo.firmware_version || '—';
|
||||
const sr = unitInfo.compliance_config?.sample_rate;
|
||||
document.getElementById('u-sr').textContent = sr ? `${sr} sps` : '—';
|
||||
const count = unitInfo.event_count ?? 0;
|
||||
document.getElementById('u-count').textContent = count;
|
||||
|
||||
// Build event chips
|
||||
const chipsEl = document.getElementById('event-chips');
|
||||
chipsEl.innerHTML = '';
|
||||
for (let i = 0; i < count; i++) {
|
||||
const chip = document.createElement('button');
|
||||
chip.className = 'event-chip' + (i === 0 ? ' active' : '');
|
||||
chip.textContent = `Event ${i}`;
|
||||
chip.onclick = () => selectEvent(i);
|
||||
chipsEl.appendChild(chip);
|
||||
}
|
||||
|
||||
document.getElementById('unit-bar').style.display = 'flex';
|
||||
document.getElementById('load-btn').disabled = count === 0;
|
||||
document.getElementById('prev-btn').disabled = true;
|
||||
document.getElementById('next-btn').disabled = count <= 1;
|
||||
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Reconnect';
|
||||
|
||||
if (count === 0) {
|
||||
setStatus('Connected — no events stored on device.', 'ok');
|
||||
} else {
|
||||
setStatus(`Connected — ${count} event${count !== 1 ? 's' : ''} stored. Select an event or click Load Waveform.`, 'ok');
|
||||
}
|
||||
}
|
||||
|
||||
function selectEvent(idx) {
|
||||
currentEventIndex = idx;
|
||||
// Update chip highlight
|
||||
document.querySelectorAll('.event-chip').forEach((c, i) => {
|
||||
c.classList.toggle('active', i === idx);
|
||||
});
|
||||
document.getElementById('prev-btn').disabled = idx <= 0;
|
||||
const count = unitInfo?.event_count ?? 0;
|
||||
document.getElementById('next-btn').disabled = idx >= count - 1;
|
||||
loadWaveform();
|
||||
}
|
||||
|
||||
async function loadWaveform() {
|
||||
const apiBase = document.getElementById('api-base').value.replace(/\/$/, '');
|
||||
const devHost = document.getElementById('dev-host').value.trim();
|
||||
const tcpPort = document.getElementById('dev-tcp-port').value;
|
||||
const evIndex = parseInt(document.getElementById('event-index').value, 10);
|
||||
const apiBase = document.getElementById('api-base').value.replace(/\/$/, '');
|
||||
const devHost = document.getElementById('dev-host').value.trim();
|
||||
const tcpPort = document.getElementById('dev-tcp-port').value;
|
||||
const evIndex = currentEventIndex;
|
||||
|
||||
if (!devHost) { setStatus('Enter a device host first.', 'error'); return; }
|
||||
|
||||
@@ -222,15 +360,12 @@
|
||||
lastData = data;
|
||||
renderWaveform(data);
|
||||
btn.disabled = false;
|
||||
document.getElementById('prev-btn').disabled = evIndex <= 0;
|
||||
document.getElementById('next-btn').disabled = false;
|
||||
}
|
||||
|
||||
function stepEvent(delta) {
|
||||
const el = document.getElementById('event-index');
|
||||
const next = Math.max(0, parseInt(el.value, 10) + delta);
|
||||
el.value = next;
|
||||
loadWaveform();
|
||||
const count = unitInfo?.event_count ?? 0;
|
||||
const next = Math.max(0, Math.min(count - 1, currentEventIndex + delta));
|
||||
selectEvent(next);
|
||||
}
|
||||
|
||||
function renderWaveform(data) {
|
||||
@@ -379,9 +514,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Allow Enter key on inputs to trigger load
|
||||
document.querySelectorAll('input').forEach(el => {
|
||||
el.addEventListener('keydown', e => { if (e.key === 'Enter') loadWaveform(); });
|
||||
// Allow Enter key on connection inputs to trigger connect
|
||||
['api-base', 'dev-host', 'dev-tcp-port'].forEach(id => {
|
||||
document.getElementById(id).addEventListener('keydown', e => {
|
||||
if (e.key === 'Enter') connectUnit();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user