[feat](trx-backend-soapysdr): add hardware AGC toggle and SDR settings UI row

Add hardware AGC on/off control for SoapySDR backend, wired through the
full stack from RigCommand to the web UI:

- RigCommand::SetSdrAgc(bool) + ClientCommand::SetSdrAgc in protocol
- set_sdr_agc() on RigCat trait (not-supported default)
- SoapySdrRig: agc_enabled field, set_sdr_agc() via pipeline agc_cmd,
  sdr_agc_enabled in filter_state(); removes the "not yet implemented"
  warning — gain_mode="auto" now properly enables hardware AGC via
  SoapySDR set_gain_mode()
- IqSource::set_gain_mode() trait method; RealIqSource implements it
- SdrPipeline: agc_cmd channel, read loop applies it each iteration
- POST /set_sdr_agc endpoint in trx-frontend-http
- New "SDR settings" full-row in index.html with Hardware AGC checkbox
  and RF Gain (moved out of WFM controls); row hidden when
  show_sdr_gain_control is false
- app.js: AGC checkbox handler, disables RF gain input while AGC is on,
  syncs checkbox state from filter.sdr_agc_enabled

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-03-15 09:44:46 +01:00
parent 262e78e72b
commit 3d99cac03b
13 changed files with 122 additions and 16 deletions
@@ -2755,8 +2755,12 @@ function render(update) {
}
updateSdrSquelchControlVisibility();
}
if (sdrGainControlsEl && typeof update.show_sdr_gain_control === "boolean") {
sdrGainControlsEl.style.display = update.show_sdr_gain_control ? "" : "none";
if (typeof update.show_sdr_gain_control === "boolean") {
if (sdrSettingsRowEl) sdrSettingsRowEl.style.display = update.show_sdr_gain_control ? "" : "none";
}
if (update.filter && sdrAgcEl && typeof update.filter.sdr_agc_enabled === "boolean") {
sdrAgcEl.checked = update.filter.sdr_agc_enabled;
updateSdrGainInputState();
}
if (update.status && update.status.freq && typeof update.status.freq.hz === "number") {
applyLocalTunedFrequency(update.status.freq.hz, true);
@@ -6712,9 +6716,11 @@ const wfmControlsCol = document.getElementById("wfm-controls-col");
const wfmDeemphasisEl = document.getElementById("wfm-deemphasis");
const wfmAudioModeEl = document.getElementById("wfm-audio-mode");
const wfmDenoiseEl = document.getElementById("wfm-denoise");
const sdrSettingsRowEl = document.getElementById("sdr-settings-row");
const sdrGainControlsEl = document.getElementById("sdr-gain-controls");
const sdrGainEl = document.getElementById("sdr-gain-db");
const sdrGainSetBtn = document.getElementById("sdr-gain-set");
const sdrAgcEl = document.getElementById("sdr-agc-enabled");
const wfmStFlagEl = document.getElementById("wfm-st-flag");
const sdrSquelchWrapEl = document.getElementById("sdr-squelch-wrap");
const sdrSquelchEl = document.getElementById("sdr-squelch");
@@ -6889,6 +6895,18 @@ function submitSdrGain() {
if (!Number.isFinite(parsed) || parsed < 0) return;
postPath(`/set_sdr_gain?db=${encodeURIComponent(parsed)}`).catch(() => {});
}
function updateSdrGainInputState() {
if (!sdrAgcEl || !sdrGainEl || !sdrGainSetBtn) return;
const agcOn = sdrAgcEl.checked;
sdrGainEl.disabled = agcOn;
sdrGainSetBtn.disabled = agcOn;
}
if (sdrAgcEl) {
sdrAgcEl.addEventListener("change", () => {
postPath(`/set_sdr_agc?enabled=${sdrAgcEl.checked ? "true" : "false"}`).catch(() => {});
updateSdrGainInputState();
});
}
if (sdrGainSetBtn) {
sdrGainSetBtn.addEventListener("click", submitSdrGain);
}
@@ -232,13 +232,6 @@
<option value="high">High</option>
</select>
</label>
<div class="wfm-gain-group" id="sdr-gain-controls">
<label class="wfm-control">
<span class="wfm-control-label">RF Gain</span>
<input id="sdr-gain-db" class="status-input" type="number" min="0" max="60" step="1" inputmode="decimal">
</label>
<button id="sdr-gain-set" type="button" class="wfm-inline-btn">Set</button>
</div>
<label class="wfm-control wfm-st-flag-wrap" aria-label="Stereo pilot status">
<span class="wfm-control-label">Pilot</span>
<span id="wfm-st-flag" class="wfm-st-flag wfm-st-flag-mono">MO</span>
@@ -259,6 +252,22 @@
<div class="label"><span>VFO</span></div>
<div class="vfo-picker" id="vfo-picker"></div>
</div>
<div class="full-row label-below-row" id="sdr-settings-row" style="display:none;">
<div class="label"><span>SDR settings</span></div>
<div class="inline" style="gap: 0.6rem; flex-wrap: wrap; align-items: center;">
<label class="vol-label" id="sdr-agc-wrap">
<span style="margin-right:0.3rem;">Hardware AGC</span>
<input type="checkbox" id="sdr-agc-enabled">
</label>
<div class="wfm-gain-group" id="sdr-gain-controls">
<label class="wfm-control">
<span class="wfm-control-label">RF Gain</span>
<input id="sdr-gain-db" class="status-input" type="number" min="0" max="60" step="1" inputmode="decimal">
</label>
<button id="sdr-gain-set" type="button" class="wfm-inline-btn">Set</button>
</div>
</div>
</div>
<div class="full-row label-below-row" id="vchan-row">
<div class="label"><span>Channels / Scheduler</span></div>
<div class="channel-scheduler-controls">