[feat](trx-backend-soapysdr): implement LNA gain element control
Add set_named_gain to the IqSource trait and implement it in RealIqSource via soapysdr Device::set_gain_element. Wire a lna_gain_cmd channel through SdrPipeline so the IQ read loop applies LNA gain changes on the next iteration. Add set_sdr_lna_gain to SoapySdrRig and expose the current value via filter_state. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
@@ -57,6 +57,12 @@ pub trait IqSource: Send + 'static {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Apply a new gain to a named gain element (e.g. "LNA"). Default
|
||||
/// implementation is a no-op for sources that do not support named gains.
|
||||
fn set_named_gain(&mut self, _name: &str, _gain_db: f64) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Enable or disable hardware automatic gain control. Default
|
||||
/// implementation is a no-op for sources that do not support AGC.
|
||||
fn set_gain_mode(&mut self, _automatic: bool) -> Result<(), String> {
|
||||
@@ -106,6 +112,9 @@ pub struct SdrPipeline {
|
||||
/// Write `Some(gain_db)` here to adjust the hardware RX gain.
|
||||
/// The IQ read loop picks it up on the next iteration.
|
||||
pub gain_cmd: Arc<std::sync::Mutex<Option<f64>>>,
|
||||
/// Write `Some(gain_db)` here to adjust the LNA gain element.
|
||||
/// The IQ read loop picks it up on the next iteration.
|
||||
pub lna_gain_cmd: Arc<std::sync::Mutex<Option<f64>>>,
|
||||
/// Write `Some(enabled)` here to switch hardware AGC on or off.
|
||||
/// The IQ read loop picks it up on the next iteration.
|
||||
pub agc_cmd: Arc<std::sync::Mutex<Option<bool>>>,
|
||||
@@ -183,6 +192,9 @@ impl SdrPipeline {
|
||||
let thread_retune_cmd = retune_cmd.clone();
|
||||
let gain_cmd: Arc<std::sync::Mutex<Option<f64>>> = Arc::new(std::sync::Mutex::new(None));
|
||||
let thread_gain_cmd = gain_cmd.clone();
|
||||
let lna_gain_cmd: Arc<std::sync::Mutex<Option<f64>>> =
|
||||
Arc::new(std::sync::Mutex::new(None));
|
||||
let thread_lna_gain_cmd = lna_gain_cmd.clone();
|
||||
let agc_cmd: Arc<std::sync::Mutex<Option<bool>>> = Arc::new(std::sync::Mutex::new(None));
|
||||
let thread_agc_cmd = agc_cmd.clone();
|
||||
|
||||
@@ -197,6 +209,7 @@ impl SdrPipeline {
|
||||
thread_spectrum_buf,
|
||||
thread_retune_cmd,
|
||||
thread_gain_cmd,
|
||||
thread_lna_gain_cmd,
|
||||
thread_agc_cmd,
|
||||
);
|
||||
})
|
||||
@@ -210,6 +223,7 @@ impl SdrPipeline {
|
||||
sdr_sample_rate,
|
||||
retune_cmd,
|
||||
gain_cmd,
|
||||
lna_gain_cmd,
|
||||
agc_cmd,
|
||||
shared_center_hz: Arc::new(AtomicI64::new(0)),
|
||||
audio_sample_rate,
|
||||
@@ -290,6 +304,7 @@ fn iq_read_loop(
|
||||
spectrum_buf: Arc<Mutex<Option<Vec<f32>>>>,
|
||||
retune_cmd: Arc<std::sync::Mutex<Option<f64>>>,
|
||||
gain_cmd: Arc<std::sync::Mutex<Option<f64>>>,
|
||||
lna_gain_cmd: Arc<std::sync::Mutex<Option<f64>>>,
|
||||
agc_cmd: Arc<std::sync::Mutex<Option<bool>>>,
|
||||
) {
|
||||
let mut block = vec![Complex::new(0.0_f32, 0.0_f32); IQ_BLOCK_SIZE];
|
||||
@@ -323,6 +338,15 @@ fn iq_read_loop(
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Ok(mut cmd) = lna_gain_cmd.try_lock() {
|
||||
if let Some(gain_db) = cmd.take() {
|
||||
if let Err(e) = source.set_named_gain("LNA", gain_db) {
|
||||
tracing::warn!("SDR LNA gain change to {:.1} dB failed: {}", gain_db, e);
|
||||
} else {
|
||||
tracing::info!("SDR LNA gain updated to {:.1} dB", gain_db);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Ok(mut cmd) = agc_cmd.try_lock() {
|
||||
if let Some(enabled) = cmd.take() {
|
||||
if let Err(e) = source.set_gain_mode(enabled) {
|
||||
|
||||
@@ -52,6 +52,8 @@ pub struct SoapySdrRig {
|
||||
gain_db: f64,
|
||||
/// Optional hard ceiling for the applied hardware gain in dB.
|
||||
max_gain_db: Option<f64>,
|
||||
/// Requested LNA gain element setting in dB (None if not set by user).
|
||||
lna_gain_db: Option<f64>,
|
||||
/// Whether hardware AGC is currently enabled.
|
||||
agc_enabled: bool,
|
||||
/// Whether software squelch is enabled on primary channel (except WFM mode).
|
||||
@@ -285,6 +287,7 @@ impl SoapySdrRig {
|
||||
wfm_denoise: WfmDenoiseLevel::Auto,
|
||||
gain_db,
|
||||
max_gain_db,
|
||||
lna_gain_db: None,
|
||||
agc_enabled,
|
||||
squelch_enabled,
|
||||
squelch_threshold_db,
|
||||
@@ -579,6 +582,25 @@ impl RigCat for SoapySdrRig {
|
||||
})
|
||||
}
|
||||
|
||||
fn set_sdr_lna_gain<'a>(
|
||||
&'a mut self,
|
||||
gain_db: f64,
|
||||
) -> Pin<Box<dyn std::future::Future<Output = DynResult<()>> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
if !gain_db.is_finite() {
|
||||
return Err("LNA gain must be finite".into());
|
||||
}
|
||||
if gain_db < 0.0 {
|
||||
return Err("LNA gain must be >= 0".into());
|
||||
}
|
||||
self.lna_gain_db = Some(gain_db);
|
||||
if let Ok(mut cmd) = self.pipeline.lna_gain_cmd.lock() {
|
||||
*cmd = Some(gain_db);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn set_sdr_agc<'a>(
|
||||
&'a mut self,
|
||||
enabled: bool,
|
||||
@@ -803,6 +825,7 @@ impl RigCat for SoapySdrRig {
|
||||
.map(|max_gain| self.gain_db.min(max_gain))
|
||||
.unwrap_or(self.gain_db),
|
||||
),
|
||||
sdr_lna_gain_db: self.lna_gain_db,
|
||||
sdr_agc_enabled: Some(self.agc_enabled),
|
||||
sdr_squelch_enabled: Some(self.squelch_enabled),
|
||||
sdr_squelch_threshold_db: Some(self.squelch_threshold_db as f64),
|
||||
|
||||
@@ -179,6 +179,12 @@ impl IqSource for RealIqSource {
|
||||
.map_err(|e| format!("Failed to set SDR gain: {}", e))
|
||||
}
|
||||
|
||||
fn set_named_gain(&mut self, name: &str, gain_db: f64) -> Result<(), String> {
|
||||
self.device
|
||||
.set_gain_element(soapysdr::Direction::Rx, 0, name, gain_db)
|
||||
.map_err(|e| format!("Failed to set SDR {} gain: {}", name, e))
|
||||
}
|
||||
|
||||
fn set_gain_mode(&mut self, automatic: bool) -> Result<(), String> {
|
||||
self.device
|
||||
.set_gain_mode(soapysdr::Direction::Rx, 0, automatic)
|
||||
|
||||
Reference in New Issue
Block a user