V131R-260725:修正FSSI/FWD显示并优化8VSB频谱分析
修正FSSI/FWD标定、存储和LCD显示;增强8VSB场同步、MER分析及网页频谱展示;更新版本号与修改记录。
This commit is contained in:
@@ -78,15 +78,21 @@
|
||||
eyeRestart();
|
||||
}
|
||||
|
||||
/* PR12: the Refresh/Pause/Peak-hold toolbar and the status lines were
|
||||
* removed on request, so these helpers tolerate the elements being absent
|
||||
* (fixed 1 s refresh, never paused, no peak-hold). */
|
||||
function elChecked(id){ var e = document.getElementById(id); return !!(e && e.checked); }
|
||||
function specRateMs(){ var e = document.getElementById('spec_rate'); return e ? Number(e.value) : 1000; }
|
||||
|
||||
function specRestart(){
|
||||
if (SPEC_TIMER) clearInterval(SPEC_TIMER);
|
||||
if (document.getElementById('spec_pause').checked) { setState('paused'); return; }
|
||||
var ms = Number(document.getElementById('spec_rate').value);
|
||||
if (elChecked('spec_pause')) { setState('paused'); return; }
|
||||
specFetch();
|
||||
SPEC_TIMER = setInterval(specFetch, ms);
|
||||
SPEC_TIMER = setInterval(specFetch, specRateMs());
|
||||
}
|
||||
|
||||
function setState(s){ document.getElementById('spec_state').innerText = s; }
|
||||
function setState(s){ var e = document.getElementById('spec_state'); if (e) e.innerText = s; }
|
||||
function setEyeState(s){ var e = document.getElementById('eye_state'); if (e) e.innerText = s; }
|
||||
|
||||
function specFetch(){
|
||||
$.ajax({url: 'iq_spectrum.cgi?t=' + Date.now(), dataType: 'json', cache: false})
|
||||
@@ -106,18 +112,8 @@
|
||||
}
|
||||
|
||||
function updateReadouts(d){
|
||||
document.getElementById('rd_shoulder').innerText = d.shoulder.toFixed(1);
|
||||
document.getElementById('rd_limd').innerText = (d.limd === undefined) ? '--' : d.limd.toFixed(1);
|
||||
document.getElementById('rd_uimd').innerText = (d.uimd === undefined) ? '--' : d.uimd.toFixed(1);
|
||||
document.getElementById('rd_pilot').innerText = d.pilot.toFixed(4);
|
||||
document.getElementById('rd_peak').innerText = d.peak.toFixed(1);
|
||||
document.getElementById('rd_rbw').innerText = d.rbw_khz.toFixed(2);
|
||||
document.getElementById('rd_pkts').innerText = d.pkts + '/' + d.npkt;
|
||||
document.getElementById('rd_ms').innerText = d.ms.toFixed(2);
|
||||
document.getElementById('rd_pkts').style.color = (d.pkts === d.npkt) ? '' : '#ef4444';
|
||||
var fld = document.getElementById('rd_field');
|
||||
fld.innerText = (d.ms > 24.2) ? 'yes' : 'no'; // one ATSC data field = 24.2 ms
|
||||
fld.style.color = (d.ms > 24.2) ? '#22c55e' : '#ef4444';
|
||||
/* PR12: the readout cards under the spectrum were removed; d.shoulder
|
||||
* and d.pilot are still drawn on the plot itself in drawSpectrum(). */
|
||||
}
|
||||
|
||||
|
||||
@@ -128,54 +124,37 @@
|
||||
* polls at the same rate as the spectrum and redraws only when the window id changes.
|
||||
*/
|
||||
var EYE_TIMER = null, eyeWid = -1, eyeFails = 0;
|
||||
var eyeLastData = null; // newest iq_eye.json, for the in-band noise line
|
||||
|
||||
function eyeRestart(){
|
||||
if (EYE_TIMER) clearInterval(EYE_TIMER);
|
||||
if (document.getElementById('spec_pause').checked) return;
|
||||
if (elChecked('spec_pause')) return;
|
||||
eyeFetch();
|
||||
EYE_TIMER = setInterval(eyeFetch, Number(document.getElementById('spec_rate').value));
|
||||
EYE_TIMER = setInterval(eyeFetch, specRateMs());
|
||||
}
|
||||
|
||||
function eyeFetch(){
|
||||
$.ajax({url: 'iq_eye.cgi?t=' + Date.now(), dataType: 'json', cache: false})
|
||||
.done(function(d){
|
||||
var st = document.getElementById('eye_state');
|
||||
if (!d || d.error){
|
||||
if (d && d.redirect) { window.location.href = d.redirect; return; }
|
||||
st.innerText = (d && d.error === 'nodata') ? 'no window demodulated yet' : 'error';
|
||||
setEyeState((d && d.error === 'nodata') ? 'no window demodulated yet' : 'error');
|
||||
return;
|
||||
}
|
||||
if (!d.sync){ st.innerText = 'no symbol lock'; return; }
|
||||
if (!d.sync){ setEyeState('no symbol lock'); return; }
|
||||
eyeFails = 0;
|
||||
eyeLastData = d;
|
||||
eyeReadouts(d);
|
||||
if (d.wid !== eyeWid){ eyeWid = d.wid; drawEye(d); }
|
||||
st.innerText = 'window ' + d.wid + ' | ' + d.nsym + ' symbols | updated ' +
|
||||
new Date(d.ts * 1000).toLocaleTimeString();
|
||||
setEyeState('window ' + d.wid + ' | ' + d.nsym + ' symbols');
|
||||
})
|
||||
.fail(function(){ eyeFails++;
|
||||
document.getElementById('eye_state').innerText = 'fetch failed (' + eyeFails + ')'; });
|
||||
.fail(function(){ eyeFails++; setEyeState('fetch failed (' + eyeFails + ')'); });
|
||||
}
|
||||
|
||||
function eyeReadouts(d){
|
||||
var minh = Math.min.apply(null, d.eye_h);
|
||||
var sAvg = d.sigma.reduce(function(a,b){ return a + b; }, 0) / d.sigma.length;
|
||||
/* PR12: only SNR/MER and SNR raw remain on request. */
|
||||
document.getElementById('rd_snr').innerText = d.snr_db.toFixed(2);
|
||||
document.getElementById('rd_eyeh').innerText = minh.toFixed(2);
|
||||
document.getElementById('rd_eyew').innerText = d.eye_w_ui.toFixed(3);
|
||||
document.getElementById('rd_tau').innerText = d.tau.toFixed(4);
|
||||
document.getElementById('rd_segsync').innerText = d.segsync_ratio.toFixed(1);
|
||||
document.getElementById('rd_coff').innerText = d.carrier_off_hz.toFixed(3);
|
||||
document.getElementById('rd_snrraw').innerText = (d.snr_raw_db === undefined) ? '--' : d.snr_raw_db.toFixed(2);
|
||||
document.getElementById('rd_gjit').innerText = (d.gain_jitter_pct === undefined) ? '--' : d.gain_jitter_pct.toFixed(2);
|
||||
var gj = document.getElementById('rd_gjit');
|
||||
gj.style.color = (d.gain_jitter_pct < 1.0) ? '#22c55e' : (d.gain_jitter_pct < 4 ? '#f59e0b' : '#ef4444');
|
||||
/* min(eye_h) should track 2 - 6*sigma. A big shortfall means ISI or non-Gaussian
|
||||
* distortion -- something a single SNR number cannot show. Guard the absolute
|
||||
* value too: once sigma is large, 2-6*sigma goes negative and a shut eye would
|
||||
* otherwise pass the relative test. */
|
||||
var el = document.getElementById('rd_eyeh');
|
||||
el.style.color = (minh > 0.6 && minh > 2 - 6 * sAvg - 0.35) ? '#22c55e'
|
||||
: (minh > 0.3 ? '#f59e0b' : '#ef4444');
|
||||
var sn = document.getElementById('rd_snr');
|
||||
sn.style.color = (d.snr_db >= 27) ? '#22c55e' : (d.snr_db >= 20 ? '#f59e0b' : '#ef4444');
|
||||
}
|
||||
@@ -306,7 +285,7 @@
|
||||
var fmin = d.f0, fmax = d.f0 + (d.n-1)*d.df;
|
||||
var m = drawGrid(fmin, fmax), i;
|
||||
|
||||
if (document.getElementById('spec_hold').checked){
|
||||
if (elChecked('spec_hold')){
|
||||
if (!specPeak || specPeak.length !== d.psd.length) specPeak = d.psd.slice();
|
||||
else for (i = 0; i < d.psd.length; i++) if (d.psd[i] > specPeak[i]) specPeak[i] = d.psd[i];
|
||||
trace(m, specPeak, d.f0, d.df, fmin, fmax, '#f59e0b', 1);
|
||||
@@ -321,11 +300,32 @@
|
||||
m.g.fillStyle = '#f87171'; m.g.textAlign = 'left'; m.g.textBaseline = 'bottom';
|
||||
m.g.fillText('shoulder ' + d.shoulder.toFixed(1) + ' dB', m.l+6, y-3);
|
||||
|
||||
var xp = xOf(m, d.pilot, fmin, fmax); // pilot marker
|
||||
m.g.strokeStyle = '#a3e635'; m.g.beginPath();
|
||||
m.g.moveTo(xp, m.t); m.g.lineTo(xp, m.h-m.b); m.g.stroke();
|
||||
m.g.fillStyle = '#a3e635'; m.g.textAlign = 'left'; m.g.textBaseline = 'top';
|
||||
m.g.fillText('pilot ' + d.pilot.toFixed(3) + ' MHz', xp+4, m.t+2);
|
||||
/* PR12: pilot vertical marker removed on request (it covered the pilot
|
||||
* peak). The pilot frequency is still available in d.pilot. */
|
||||
|
||||
/* PR12: error signal spectrum (yellow) = reshaped decisions subtracted
|
||||
* from the actual IQ, in the channel frame so it lines up with the blue
|
||||
* trace. Full-band residual (shows in-band distortion AND out-of-band
|
||||
* shoulder). Drawn only where it stays below the blue PSD -- any point
|
||||
* above the signal breaks the segment (clip, not clamp). */
|
||||
/* DISABLED on request (2026-07-25): drop the leading `false &&` to restore */
|
||||
if (false && eyeLastData && eyeLastData.err && eyeLastData.err.db){
|
||||
var er = eyeLastData.err, g2 = m.g, started = false, ei;
|
||||
g2.strokeStyle = '#eab308'; g2.lineWidth = 1.4; g2.beginPath();
|
||||
for (ei = 0; ei < er.db.length; ei++){
|
||||
var ef = er.f0 + ei*er.df;
|
||||
if (ef < fmin || ef > fmax){ started = false; continue; }
|
||||
/* blue signal level at this frequency (nearest published bin) */
|
||||
var bi = Math.round((ef - d.f0)/d.df);
|
||||
var sig = (bi >= 0 && bi < d.psd.length) ? d.psd[bi] : 99;
|
||||
if (er.db[ei] > sig){ started = false; continue; } /* above signal: hide */
|
||||
var ex = xOf(m, ef, fmin, fmax),
|
||||
ey = yOf(m, Math.max(Math.min(er.db[ei], Y_TOP), Y_BOT));
|
||||
if (!started){ g2.moveTo(ex, ey); started = true; } else g2.lineTo(ex, ey);
|
||||
}
|
||||
g2.stroke();
|
||||
/* PR12: yellow error trace has no text label on request */
|
||||
}
|
||||
}
|
||||
|
||||
// Opens the modal - gets current username from server
|
||||
@@ -672,49 +672,23 @@
|
||||
<main class="app-main">
|
||||
<h2>ORx Receive Spectrum</h2>
|
||||
<div class="box">
|
||||
<div class="spec-toolbar">
|
||||
<label>Refresh
|
||||
<select id="spec_rate" onchange="specRestart()">
|
||||
<option value="500">0.5 s</option>
|
||||
<option value="1000" selected>1 s</option>
|
||||
<option value="2000">2 s</option>
|
||||
<option value="5000">5 s</option>
|
||||
</select>
|
||||
</label>
|
||||
<label><input type="checkbox" id="spec_pause" onchange="specRestart()"> Pause</label>
|
||||
<label><input type="checkbox" id="spec_hold" onchange="specPeak=null"> Peak hold</label>
|
||||
<span id="spec_state">starting…</span>
|
||||
</div>
|
||||
<!-- PR12: Refresh/Pause/Peak-hold toolbar + status line removed on
|
||||
request (fixed 1 s refresh). Readout cards were removed earlier;
|
||||
the on-plot shoulder line and yellow error trace remain. -->
|
||||
<canvas id="spec_canvas"></canvas>
|
||||
<div class="spec-readouts">
|
||||
<div class="spec-readout"><span class="k">Shoulder</span><span class="v"><span id="rd_shoulder">--</span> dB</span></div>
|
||||
<div class="spec-readout" title="10log10(P@+24kHz / P@3.1494MHz on the pilot side), 11 bins each. Raw dB, without the +11 dB offset the Status page adds."><span class="k">LIMD raw</span><span class="v"><span id="rd_limd">--</span> dB</span></div>
|
||||
<div class="spec-readout" title="10log10(P@+24kHz / P@3.1494MHz opposite the pilot), 11 bins each. Raw dB, without the +11 dB offset the Status page adds."><span class="k">UIMD raw</span><span class="v"><span id="rd_uimd">--</span> dB</span></div>
|
||||
<div class="spec-readout"><span class="k">Pilot</span><span class="v"><span id="rd_pilot">--</span> MHz</span></div>
|
||||
<div class="spec-readout"><span class="k">Peak</span><span class="v"><span id="rd_peak">--</span> dB</span></div>
|
||||
<div class="spec-readout"><span class="k">RBW</span><span class="v"><span id="rd_rbw">--</span> kHz</span></div>
|
||||
<div class="spec-readout"><span class="k">Packets</span><span class="v" id="rd_pkts">--</span></div>
|
||||
<div class="spec-readout"><span class="k">Contiguous</span><span class="v"><span id="rd_ms">--</span> ms</span></div>
|
||||
<div class="spec-readout"><span class="k">> 1 field</span><span class="v" id="rd_field">--</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>8VSB Eye Diagram</h2>
|
||||
<div class="box">
|
||||
<div class="spec-toolbar">
|
||||
<span style="font-size:13px;opacity:.75">Matched-filtered I rail, 2 symbol periods, log persistence</span>
|
||||
<span id="eye_state">waiting for the demodulator…</span>
|
||||
</div>
|
||||
<!-- PR12: eye caption + status line removed on request. -->
|
||||
<canvas id="eye_canvas"></canvas>
|
||||
<div class="spec-readouts">
|
||||
<div class="spec-readout" title="MER, I-path definition: 10log10(sum d^2 / sum (r-d)^2)"><span class="k">SNR / MER</span><span class="v"><span id="rd_snr">--</span> dB</span></div>
|
||||
<div class="spec-readout" title="Smallest of the 7 eye openings at the decision instant (1%/99% percentiles); ideal 2.00"><span class="k">Min eye height</span><span class="v" id="rd_eyeh">--</span></div>
|
||||
<div class="spec-readout" title="Time span over which the central eye stays open"><span class="k">Eye width</span><span class="v"><span id="rd_eyew">--</span> UI</span></div>
|
||||
<div class="spec-readout" title="Sampling phase offset; re-solved for every window because the capture start is not symbol-locked"><span class="k">τ0</span><span class="v"><span id="rd_tau">--</span> sa</span></div>
|
||||
<div class="spec-readout" title="Correlation peak / rms of the 4-symbol segment sync, period 832. Above 10 means locked"><span class="k">Seg sync</span><span class="v" id="rd_segsync">--</span></div>
|
||||
<div class="spec-readout" title="Pilot phase drift across the window. TX and ORx share the LO, so this must be ~0"><span class="k">Carrier offset</span><span class="v"><span id="rd_coff">--</span> Hz</span></div>
|
||||
<div class="spec-readout" title="MER before the ORx AGC gain steps are tracked out. The gap to SNR/MER is what the observation receiver costs you, not the transmitter."><span class="k">SNR raw</span><span class="v"><span id="rd_snrraw">--</span> dB</span></div>
|
||||
<div class="spec-readout" title="rms of the ORx RF AGC gain steps over the window (0.43 dB per gain index). This is a receiver artifact; a dead zone in dpd_rx_rfagc would remove it at the source."><span class="k">ORx AGC jitter</span><span class="v"><span id="rd_gjit">--</span> %</span></div>
|
||||
<!-- PR13: FSSI from the ORx FAGC gain index (255->0%, 195->100%).
|
||||
Value comes from the shared 3 s dashboard poll (dashboard.js),
|
||||
not from iq_eye.cgi. Shows "A / B" when FSSI refresh is on. -->
|
||||
<div class="spec-readout" title="ORx front-end signal strength from the FAGC gain index (gain index 255 -> 0%, 195 -> 100%). A path; A / B when the FSSI refresh switch is on."><span class="k">FSSI</span><span class="v"><span id="rd_fssi">--</span> %</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
Reference in New Issue
Block a user