Tuning panel
The Tuning panel (web /tuning) surfaces the live demodulator’s
internal loop state — GopherTrunk’s take on OP25’s Mixer and
Tuner (FLL) tabs. It answers “is the receiver actually locking onto
this channel, and how much tuner error is it fighting?” while the other
scopes answer “what does the signal look like.”
The daemon runs a parallel P25 receiver on the selected channel and stamps its loop state onto every symbol frame, so this is the real state of the production demod, not a re-implementation.
What you see
Carrier-error trend (the FLL view)
A rolling line of the receiver’s residual carrier-frequency-offset estimate in Hz:
- C4FM — the AFC’s estimate of the true carrier offset.
- CQPSK / LSM — the carrier-recovery loop’s estimate (coarse seed plus the fine Costas residual).
On a healthy lock the trace converges toward 0 Hz and holds. A steady non-zero value is your tuner’s PPM error (a 420 MHz / 50 ppm RTL-SDR can sit ~20 kHz off); a trace that wanders or never settles is a loop that hasn’t acquired — usually a too-weak signal or the wrong demod mode for the site.
Loop-state meters
| Meter | Meaning | Healthy |
|---|---|---|
| Carrier error | Current residual offset (Hz) | Near 0, steady |
| AGC level (C4FM) | Symbol-AGC mean|x|, vs its target | Tracks target |
| AGC gain (CQPSK) | Matched-filter AGC gain | Settles, not pinned at 1.0 |
| Clock μ | Symbol-clock sub-sample phase + nominal sps | Cycles steadily, no monotonic drift |
| CMA error (CQPSK) | Blind-equalizer convergence proxy | Trends toward 0 |
A Clock μ that drifts monotonically means the nominal samples-per- symbol disagrees with the stream’s actual baud (a sample-rate / PPM issue); a CMA error stuck large means the equalizer never opened the constellation.
Controls
Pick the receiver with Mode (C4FM or CQPSK — match the site), and use the Offset / Hold / Centre controls (shared with the other scopes) to point the receiver at a locked control or voice channel. See the Constellation panel for the DC-spike explanation.
How it works
- The panel opens
WS /api/v1/diag/symbols?device=...&proto=<…>&offset=<hz>— the same per-channel receiver the Constellation, Symbol scope and Eye diagram use, so all four are views of one decode. - Each frame carries the receiver’s state getters
(
carrier_offset_hz,agc_level,agc_target,clock_mu,clock_sps,cma_error), read live from the demod. The getters are demod-specific (Mueller-Müller vs Gardner, AFC vs carrier recovery); the daemon routes by the selected mode.
Implementation
| Path | Role |
|---|---|
internal/radio/p25/phase1/receiver/receiver.go |
The state getters (AFCOffsetHz, AGCLevel, MMClockMu, CQPSKCarrierOffsetHz, CMAError, …) |
internal/scanner/symbolscope/scope.go |
stampMetrics reads the getters into each frame |
internal/api/symbols.go |
Metric fields on the symbol WS frame |
web/src/panels/Tuning.tsx |
Carrier-error trend (Chart.js) + loop-state meters |