[feat](trx-server,trx-backend-soapysdr): add sdr gain ceiling
Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -362,6 +362,9 @@ pub struct SdrGainConfig {
|
||||
pub mode: String,
|
||||
/// Gain in dB; effective only when mode = "manual".
|
||||
pub value: f64,
|
||||
/// Optional hard ceiling for the applied hardware gain in dB.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub max_value: Option<f64>,
|
||||
}
|
||||
|
||||
impl Default for SdrGainConfig {
|
||||
@@ -369,6 +372,7 @@ impl Default for SdrGainConfig {
|
||||
Self {
|
||||
mode: "auto".to_string(),
|
||||
value: 30.0,
|
||||
max_value: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -501,6 +505,15 @@ impl ServerConfig {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(max_gain) = self.sdr.gain.max_value {
|
||||
if !max_gain.is_finite() {
|
||||
return Err("[sdr.gain].max_value must be finite".to_string());
|
||||
}
|
||||
if max_gain < 0.0 {
|
||||
return Err("[sdr.gain].max_value must be >= 0".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-rig uniqueness checks.
|
||||
if !self.rigs.is_empty() {
|
||||
let mut seen_ids: std::collections::HashSet<String> = std::collections::HashSet::new();
|
||||
@@ -520,6 +533,20 @@ impl ServerConfig {
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(max_gain) = rig.sdr.gain.max_value {
|
||||
if !max_gain.is_finite() {
|
||||
return Err(format!(
|
||||
"[[rigs]] [sdr.gain].max_value must be finite (rig id: \"{}\")",
|
||||
rig.id
|
||||
));
|
||||
}
|
||||
if max_gain < 0.0 {
|
||||
return Err(format!(
|
||||
"[[rigs]] [sdr.gain].max_value must be >= 0 (rig id: \"{}\")",
|
||||
rig.id
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -313,6 +313,7 @@ fn build_sdr_rig_from_instance(
|
||||
&channels,
|
||||
&rig_cfg.sdr.gain.mode,
|
||||
rig_cfg.sdr.gain.value,
|
||||
rig_cfg.sdr.gain.max_value,
|
||||
rig_cfg.audio.sample_rate,
|
||||
rig_cfg.audio.channels as usize,
|
||||
rig_cfg.audio.frame_duration_ms,
|
||||
|
||||
@@ -55,6 +55,7 @@ impl SoapySdrRig {
|
||||
/// `(channel_if_hz, initial_mode, audio_bandwidth_hz, fir_taps)`.
|
||||
/// - `gain_mode`: `"auto"` or `"manual"`.
|
||||
/// - `gain_db`: gain in dB; used when `gain_mode == "manual"`.
|
||||
/// - `max_gain_db`: optional hard ceiling for the applied hardware gain.
|
||||
/// When `gain_mode == "auto"` hardware AGC is not yet wired, so this
|
||||
/// value acts as the fallback.
|
||||
/// - `audio_sample_rate`: output PCM rate (Hz).
|
||||
@@ -72,6 +73,7 @@ impl SoapySdrRig {
|
||||
channels: &[(f64, RigMode, u32, usize)],
|
||||
gain_mode: &str,
|
||||
gain_db: f64,
|
||||
max_gain_db: Option<f64>,
|
||||
audio_sample_rate: u32,
|
||||
audio_channels: usize,
|
||||
frame_duration_ms: u16,
|
||||
@@ -83,10 +85,11 @@ impl SoapySdrRig {
|
||||
center_offset_hz: i64,
|
||||
) -> DynResult<Self> {
|
||||
tracing::info!(
|
||||
"initialising SoapySDR backend (args={:?}, gain_mode={:?}, gain_db={})",
|
||||
"initialising SoapySDR backend (args={:?}, gain_mode={:?}, gain_db={}, max_gain_db={:?})",
|
||||
args,
|
||||
gain_mode,
|
||||
gain_db,
|
||||
max_gain_db,
|
||||
);
|
||||
|
||||
if gain_mode == "auto" {
|
||||
@@ -97,6 +100,17 @@ impl SoapySdrRig {
|
||||
);
|
||||
}
|
||||
|
||||
let effective_gain_db = max_gain_db
|
||||
.map(|max_gain| gain_db.min(max_gain))
|
||||
.unwrap_or(gain_db);
|
||||
if (effective_gain_db - gain_db).abs() > f64::EPSILON {
|
||||
tracing::info!(
|
||||
"Clamping SoapySDR gain from {} dB to {} dB due to configured max_value",
|
||||
gain_db,
|
||||
effective_gain_db,
|
||||
);
|
||||
}
|
||||
|
||||
// The hardware tunes `center_offset_hz` below the dial frequency so
|
||||
// the desired signal avoids the DC spike. The DSP mixer compensates.
|
||||
let hardware_center_hz = initial_freq.hz as i64 - center_offset_hz;
|
||||
@@ -107,7 +121,7 @@ impl SoapySdrRig {
|
||||
hardware_center_hz as f64,
|
||||
sdr_sample_rate as f64,
|
||||
bandwidth_hz as f64,
|
||||
gain_db,
|
||||
effective_gain_db,
|
||||
)?);
|
||||
|
||||
let pipeline = dsp::SdrPipeline::start(
|
||||
@@ -198,6 +212,7 @@ impl SoapySdrRig {
|
||||
&[], // no channels — pipeline does nothing; filter defaults applied in new_with_config
|
||||
"auto",
|
||||
30.0,
|
||||
None,
|
||||
48_000,
|
||||
1,
|
||||
20,
|
||||
|
||||
Reference in New Issue
Block a user