[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(())
|
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
|
/// Enable or disable hardware automatic gain control. Default
|
||||||
/// implementation is a no-op for sources that do not support AGC.
|
/// implementation is a no-op for sources that do not support AGC.
|
||||||
fn set_gain_mode(&mut self, _automatic: bool) -> Result<(), String> {
|
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.
|
/// Write `Some(gain_db)` here to adjust the hardware RX gain.
|
||||||
/// The IQ read loop picks it up on the next iteration.
|
/// The IQ read loop picks it up on the next iteration.
|
||||||
pub gain_cmd: Arc<std::sync::Mutex<Option<f64>>>,
|
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.
|
/// Write `Some(enabled)` here to switch hardware AGC on or off.
|
||||||
/// The IQ read loop picks it up on the next iteration.
|
/// The IQ read loop picks it up on the next iteration.
|
||||||
pub agc_cmd: Arc<std::sync::Mutex<Option<bool>>>,
|
pub agc_cmd: Arc<std::sync::Mutex<Option<bool>>>,
|
||||||
@@ -183,6 +192,9 @@ impl SdrPipeline {
|
|||||||
let thread_retune_cmd = retune_cmd.clone();
|
let thread_retune_cmd = retune_cmd.clone();
|
||||||
let gain_cmd: Arc<std::sync::Mutex<Option<f64>>> = Arc::new(std::sync::Mutex::new(None));
|
let gain_cmd: Arc<std::sync::Mutex<Option<f64>>> = Arc::new(std::sync::Mutex::new(None));
|
||||||
let thread_gain_cmd = gain_cmd.clone();
|
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 agc_cmd: Arc<std::sync::Mutex<Option<bool>>> = Arc::new(std::sync::Mutex::new(None));
|
||||||
let thread_agc_cmd = agc_cmd.clone();
|
let thread_agc_cmd = agc_cmd.clone();
|
||||||
|
|
||||||
@@ -197,6 +209,7 @@ impl SdrPipeline {
|
|||||||
thread_spectrum_buf,
|
thread_spectrum_buf,
|
||||||
thread_retune_cmd,
|
thread_retune_cmd,
|
||||||
thread_gain_cmd,
|
thread_gain_cmd,
|
||||||
|
thread_lna_gain_cmd,
|
||||||
thread_agc_cmd,
|
thread_agc_cmd,
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@@ -210,6 +223,7 @@ impl SdrPipeline {
|
|||||||
sdr_sample_rate,
|
sdr_sample_rate,
|
||||||
retune_cmd,
|
retune_cmd,
|
||||||
gain_cmd,
|
gain_cmd,
|
||||||
|
lna_gain_cmd,
|
||||||
agc_cmd,
|
agc_cmd,
|
||||||
shared_center_hz: Arc::new(AtomicI64::new(0)),
|
shared_center_hz: Arc::new(AtomicI64::new(0)),
|
||||||
audio_sample_rate,
|
audio_sample_rate,
|
||||||
@@ -290,6 +304,7 @@ fn iq_read_loop(
|
|||||||
spectrum_buf: Arc<Mutex<Option<Vec<f32>>>>,
|
spectrum_buf: Arc<Mutex<Option<Vec<f32>>>>,
|
||||||
retune_cmd: Arc<std::sync::Mutex<Option<f64>>>,
|
retune_cmd: Arc<std::sync::Mutex<Option<f64>>>,
|
||||||
gain_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>>>,
|
agc_cmd: Arc<std::sync::Mutex<Option<bool>>>,
|
||||||
) {
|
) {
|
||||||
let mut block = vec![Complex::new(0.0_f32, 0.0_f32); IQ_BLOCK_SIZE];
|
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 Ok(mut cmd) = agc_cmd.try_lock() {
|
||||||
if let Some(enabled) = cmd.take() {
|
if let Some(enabled) = cmd.take() {
|
||||||
if let Err(e) = source.set_gain_mode(enabled) {
|
if let Err(e) = source.set_gain_mode(enabled) {
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ pub struct SoapySdrRig {
|
|||||||
gain_db: f64,
|
gain_db: f64,
|
||||||
/// Optional hard ceiling for the applied hardware gain in dB.
|
/// Optional hard ceiling for the applied hardware gain in dB.
|
||||||
max_gain_db: Option<f64>,
|
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.
|
/// Whether hardware AGC is currently enabled.
|
||||||
agc_enabled: bool,
|
agc_enabled: bool,
|
||||||
/// Whether software squelch is enabled on primary channel (except WFM mode).
|
/// Whether software squelch is enabled on primary channel (except WFM mode).
|
||||||
@@ -285,6 +287,7 @@ impl SoapySdrRig {
|
|||||||
wfm_denoise: WfmDenoiseLevel::Auto,
|
wfm_denoise: WfmDenoiseLevel::Auto,
|
||||||
gain_db,
|
gain_db,
|
||||||
max_gain_db,
|
max_gain_db,
|
||||||
|
lna_gain_db: None,
|
||||||
agc_enabled,
|
agc_enabled,
|
||||||
squelch_enabled,
|
squelch_enabled,
|
||||||
squelch_threshold_db,
|
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>(
|
fn set_sdr_agc<'a>(
|
||||||
&'a mut self,
|
&'a mut self,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
@@ -803,6 +825,7 @@ impl RigCat for SoapySdrRig {
|
|||||||
.map(|max_gain| self.gain_db.min(max_gain))
|
.map(|max_gain| self.gain_db.min(max_gain))
|
||||||
.unwrap_or(self.gain_db),
|
.unwrap_or(self.gain_db),
|
||||||
),
|
),
|
||||||
|
sdr_lna_gain_db: self.lna_gain_db,
|
||||||
sdr_agc_enabled: Some(self.agc_enabled),
|
sdr_agc_enabled: Some(self.agc_enabled),
|
||||||
sdr_squelch_enabled: Some(self.squelch_enabled),
|
sdr_squelch_enabled: Some(self.squelch_enabled),
|
||||||
sdr_squelch_threshold_db: Some(self.squelch_threshold_db as f64),
|
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))
|
.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> {
|
fn set_gain_mode(&mut self, automatic: bool) -> Result<(), String> {
|
||||||
self.device
|
self.device
|
||||||
.set_gain_mode(soapysdr::Direction::Rx, 0, automatic)
|
.set_gain_mode(soapysdr::Direction::Rx, 0, automatic)
|
||||||
|
|||||||
Reference in New Issue
Block a user