diff --git a/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp.rs b/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp.rs index d054753..9f59b7c 100644 --- a/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp.rs +++ b/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp.rs @@ -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>>, + /// 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>>, /// 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>>, @@ -183,6 +192,9 @@ impl SdrPipeline { let thread_retune_cmd = retune_cmd.clone(); let gain_cmd: Arc>> = Arc::new(std::sync::Mutex::new(None)); let thread_gain_cmd = gain_cmd.clone(); + let lna_gain_cmd: Arc>> = + Arc::new(std::sync::Mutex::new(None)); + let thread_lna_gain_cmd = lna_gain_cmd.clone(); let agc_cmd: Arc>> = 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>>>, retune_cmd: Arc>>, gain_cmd: Arc>>, + lna_gain_cmd: Arc>>, agc_cmd: Arc>>, ) { 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) { diff --git a/src/trx-server/trx-backend/trx-backend-soapysdr/src/lib.rs b/src/trx-server/trx-backend/trx-backend-soapysdr/src/lib.rs index bb8217c..9850e8d 100644 --- a/src/trx-server/trx-backend/trx-backend-soapysdr/src/lib.rs +++ b/src/trx-server/trx-backend/trx-backend-soapysdr/src/lib.rs @@ -52,6 +52,8 @@ pub struct SoapySdrRig { gain_db: f64, /// Optional hard ceiling for the applied hardware gain in dB. max_gain_db: Option, + /// Requested LNA gain element setting in dB (None if not set by user). + lna_gain_db: Option, /// 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> + 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), diff --git a/src/trx-server/trx-backend/trx-backend-soapysdr/src/real_iq_source.rs b/src/trx-server/trx-backend/trx-backend-soapysdr/src/real_iq_source.rs index a06cb14..f6bceb5 100644 --- a/src/trx-server/trx-backend/trx-backend-soapysdr/src/real_iq_source.rs +++ b/src/trx-server/trx-backend/trx-backend-soapysdr/src/real_iq_source.rs @@ -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)