Repository navigation
HL2: panadapter SQL line and Auto SQL use the Flex −160+level scale; the HL2 amsq gate is −140+0.7·level dBFS #6092
Description
Problem
#5982 gives the HL2 a receive squelch. On SSB and AM it uses WDSP's level squelch (amsq), whose gate opens at −140 + 0.7·level dBFS. on8st measured that map on a real HL2, and manual SQL is verified on the radio.
The shared GUI assumes a different scale. Both the panadapter SQL line and Auto SQL treat a level as −160 + level, one dB per step:
src/gui/SpectrumWidget.cpp:4568draws the line atkSqlMinDbm + level, wherekSqlMinDbm = -160.SpectrumWidget::updateAutoSquelchFromBins(around line 4654) pickslevel = floor + margin + 160.- Auto SQL is not gated by any capability, so it runs on the HL2 (
MainWindow::autoSquelchShouldRunOnSpectrum,src/gui/MainWindow_Wiring.cpp:1356). - HL2 panadapter bins are dBFS shifted by the LNA reference.
Hl2Backend.cpp:1736already says the absolute levels "must not be compared, spotted or thresholded", and Auto SQL does exactly that.
Where the line is drawn versus where the gate actually is:
| level | line drawn at | amsq gate |
|---|---|---|
| 20 | −140 | −126 dBFS |
| 50 | −110 | −105 dBFS |
| ~67 | the two cross | |
| 100 | −60 | −70 dBFS |
Auto SQL, worked out from the code (not measured):
- Quiet band, floor −116 with a 10 dB margin: it sends level 54, and the gate lands at −102, 14 dB above the floor.
- Busy 40 m band, floor −80 with a 10 dB margin: it sends level 90, and the gate lands at −77, only 3 dB above the floor.
So on the HL2 the line is misplaced and Auto SQL does not hold the margin the operator chose. A matching slope would probably still not make the line exact: the bins are per FFT bin and LNA-referenced, while amsq measures the whole passband. That last point is reasoning, not a measurement.
This is not HL2-only. Icom maps its 0..100 slider onto CI-V 0..255 (IcomControls.cpp:169). Kiwi already has its own branch in updateAutoSquelchFromBins because its scale is server-relative. The −160+level assumption is Flex's.
Fix shapes (maintainer call)
- B: move the HL2 amsq map onto the GUI scale (
-160 + leveldBFS). Tested as a mutation on feat(hl2): receive squelch per mode family, with an HL2-measured threshold map. Principle II. #5982: the audio-gate test still passes at level 50, and only the exact-value checks change. But it overrides a hardware-measured calibration without re-measuring, and the line still only lines up when the LNA offset is 0. - D: hide the line and Auto SQL when
panAmplitude.calibratedDbmis false. No new capability and no family branch, but the HL2 loses both features. - Backend-published level→dB mapping, as a maintainer-reviewed capability record that SpectrumWidget uses for the line and for Auto SQL. This would also cover Icom and Kiwi, but it is architecture work.
The maintainer landed #5982 with on8st's measured map unchanged and this as the follow-up.
Activity
aethersdr-agent commentedon Oct 2, 2026
Triage:
maintainer-review. Every code claim in the report checks out againstmain(7975baf). Two corrections to the framing, both of which change the handoff rather than the conclusion.Verified in the tree
src/gui/SpectrumWidget.cpp:4534constexpr float kSqlMinDbm = -160.0f, and:4568draws the line atsquelchDbm = kSqlMinDbm + m_flexSquelchLevel. The member name says Flex, but it is the non-Kiwi path for every family.updateAutoSquelchFromBins(:4610) has exactly one family branch — Kiwi, at:4616-4626, returningm_autoSqlMarginDbraw because the server scale is median-RSSI-relative. Everything else falls through to:4654-4659:level = clamp(int(floor + margin + 160 + 0.5), 1, 100). The arithmetic in the issue's two worked examples is right.MainWindow::autoSquelchShouldRunOnSpectrum(src/gui/MainWindow_Wiring.cpp:1356-1384) gates on Kiwi assignment andRxApplet::SqlMode::Autoonly.Hl2Backend.cpp:1734-1742setsamplitude.calibratedDbm = m_dbRef.isCalibrated()(false — nothing insrc/callssetFullScaleDbm()) and states the absolute levels "must not be compared, spotted or thresholded." Auto SQL thresholds them.Correction 1: #5982 has not landed
The body says "The maintainer landed #5982 with on8st's measured map unchanged." On
maintoday #5982 is open, and the map is not in the tree:grep -ri amsq src/returns nothing (onlythird_party/wdsp/upstream/amsq.h),git log --all --grep 5982is empty, andHl2Backend.cpp:1762still describes the no-squelch state. The HL2 has no RX squelch at all right now.That does not invalidate the issue — the −160 assumption is a real defect on
main— but it splits the work across two bases, and authorizing it as one change againstmainwould produce a PR that can't test its own premise:mainindependently: the SpectrumWidget side. The line scale at:4568, the Auto SQL conversion at:4654, and the capability plumbing.Correction 2: shape D does not cover Icom
RadioCapabilities::dbmAxisIsCalibrated()is!panAmplitude || panAmplitude->calibratedDbm(RadioCapabilities.h:235-238) — absent record means calibrated, deliberately, to avoid restating a claim about unread backends. The header notes the Icom scope's record is absent, so the accessor returns true for Icom. Hiding oncalibratedDbm == falsetherefore covers HL2, ANAN (AnanBackend.cpp:381) and RTL (RtlSdrBackend.cpp:111) and leaves the Flex-scaled line in place on Icom, whose squelch is0..255percent with no published dB mapping (src/core/backends/icom/IcomControls.cpp:169, and cf. the siblingrf.gaincomment at:162-168). D is a valid interim, but it does not earn the issue's "this is not HL2-only" claim without also giving Icom aPanAmplitudeModelrecord — which is a separate maintainer call about a backend nobody has measured.Recommended shape
The backend-published level→dB mapping is the right fix, and the seam already exists with a template to copy:
PanAmplitudeModelgains an optional level→dB mapping (offset + slope, or an explicit "unmapped"). Anstd::optional<...>record is not a bool, so this is not a capability-ratchet event.SpectrumWidgetmirrors it the way it already mirrorsbinsAbsolute—setPanBinsAbsolute/m_panBinsAbsoluteatSpectrumWidget.h:358-363, with the comment there explaining why the widget takes a pushed value instead of reading caps.src/gui/MainWindow.cpp:7563-7570, which already fansradioOwnsDbmScaleandpanBinsAbsolute()out overfindChildren<SpectrumWidget*>()with a!connected ||reset.:4568and:4654use the mapping; absent mapping hides the line and suppresses Auto SQL (shape D's behaviour, as the fallback rather than the whole fix). Flex declares −160 + 1·level and keeps today's behaviour; HL2 declares −140 + 0.7·level; Icom and Kiwi declare unmapped until someone measures them.Not B. Rewriting the HL2 amsq map to −160 + level overwrites a hardware-measured calibration to satisfy a GUI constant, and by the issue's own table it would have put level 50 at −110 dBFS instead of the measured-good −105. The manual SQL that on8st verified on the radio is the part that currently works; it should not be the part that moves.
The issue's closing caveat is worth keeping in the eventual PR description: even a correct slope will not make the line exact, because the bins are per-FFT-bin and LNA-referenced while amsq integrates the whole passband. The goal is a line that is right to within the operator's margin, not one that is exact.
🤖 Generated with Claude Code
🤖 aethersdr-agent · cost: $2.6475 · model: claude-opus-5
📍 Citation health check (Foundry Constitution Principle I): 4 resolved, 6 unresolved against
/Users/aetherclaude/workspace/AetherSDRat origin/main.Unresolved citations (6)
AnanBackend.cpp:381Hl2Backend.cpp:1734-1742Hl2Backend.cpp:1762RadioCapabilities.h:235-238RtlSdrBackend.cpp:111SpectrumWidget.h:358-363feat(hl2): receive squelch per mode family, with an HL2-measured thre…
ten9876 commentedon Oct 3, 2026
@on8st, one question before we fix this. It's about the squelch map you measured on the HL2 for #5982.
What we found: the error is larger than this issue says. HL2 pan bins are
binDbfs + offsetDb()(Hl2Backend.cpp:850), whereoffsetDb = fullScaleDbm − LNA, i.e. −17 dB at the default 20 dB LNA. The amsq gate (−140 + 0.7·leveldBFS,WdspChannel.cpp:649) doesn't carry that offset.−23 + 0.3L + GdB off the gate: about +12 dB at level 50 with G = 20.The plan (my decision):
gate = −140 + 0.7L − lnaOffsetDb(). That reproduces your map exactly at the LNA gain you measured at, and holds it when RF gain moves.The question: what RF/LNA gain (dB) was the HL2 at when you measured the map, i.e. level 50 opening at −105 dBFS and the broadcast-station check? We'll anchor the referral at that gain so your calibration is preserved exactly. If it was the default (+20), say so and we'll go ahead.
73, Jeremy KK7GWY
on8st commentedon Oct 3, 2026
@ten9876 Bad news first: we did not record the gain, so I cannot give you one to anchor on, and #5982 understated the problem.
Referring the gate to the LNA is the right shape. Rather than anchor on a guess, I will re-run the sweep receive-only with LNA pinned at +20, auto off, gain logged per row, plus one leg at a second gain to check the 1:1 shift, and post the brackets here.
73, ON8ST
on8st commentedon Oct 3, 2026
@ten9876 Bad news first: the map was not measured at +20, and at +20 it does not work.
Re-run today: real HL2, receive only, main 732b99a, EFHW, AM 8 kHz, auto RF gain off, LNA read back in every row. Gate opens at, raw dBFS:
So
−140 + 0.7L − lnaOffsetDb(), zero at +20, keeps the broken +20 case. Anchored at −12:−108 + 0.7L − lnaOffsetDb().One point on the code side, about plan item 3. The gap between pan bins and what amsq sees is not a fixed figure; it depends on the filter width.
xamsqcap(third_party/wdsp/upstream/RXA.c:650) capturesmidbuffafterxnbp(nbp0)(:647), which is the passband filter, and beforexwcpagc(:665). So it reads passband-limited power ahead of the AGC, which also confirms that the gate sits in raw dBFS.xamsqcompares an averaged magnitude against10^(dB/20)(amsq.c:125-126,:221-224). For noise, the reading therefore grows 10·log10 with the passband width: 2.7 kHz against 1.8 kHz is about +1.8 dB. Per-bin pan noise depends instead on the bin width, which follows span and FFT size.A single offset between the two holds for one filter and one span only. If Auto SQL comes back on the HL2, the detector-side floor in item 3 is the right source. A pan-derived floor would need both widths applied.
73, ON8ST
fix(squelch): SQL line and Auto SQL from a backend-published scale; H…