[feat](trx-backend): set capability flags; add SDR filter state; add UC-08 tests
Set tx/tx_limit/vfo_switch/filter_controls/signal_meter on all backends: - FT-817, FT-450D, dummy: tx=true, tx_limit=true, vfo_switch=true, filter_controls=false, signal_meter=true - SoapySDR: tx=false, tx_limit=false, vfo_switch=false, filter_controls=true, signal_meter=true SoapySDR backend now stores bandwidth_hz and fir_taps fields; overrides set_bandwidth, set_fir_taps, and filter_state on RigCat to expose live DSP state in the snapshot. Add UC-08 unit tests on dummy backend asserting tx capabilities present and filter_controls absent, and that filter_state returns None. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -104,6 +104,11 @@ impl DummyRig {
|
|||||||
rit: false,
|
rit: false,
|
||||||
rpt: false,
|
rpt: false,
|
||||||
split: false,
|
split: false,
|
||||||
|
tx: true,
|
||||||
|
tx_limit: true,
|
||||||
|
vfo_switch: true,
|
||||||
|
filter_controls: false,
|
||||||
|
signal_meter: true,
|
||||||
},
|
},
|
||||||
access: RigAccessMethod::Serial {
|
access: RigAccessMethod::Serial {
|
||||||
path: "/dev/null".to_string(),
|
path: "/dev/null".to_string(),
|
||||||
@@ -141,6 +146,35 @@ impl DummyRig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use trx_core::rig::Rig;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dummy_has_tx_capabilities() {
|
||||||
|
let rig = DummyRig::new();
|
||||||
|
let caps = &rig.info().capabilities;
|
||||||
|
assert!(caps.tx, "dummy rig should have tx capability");
|
||||||
|
assert!(caps.tx_limit, "dummy rig should have tx_limit capability");
|
||||||
|
assert!(caps.vfo_switch, "dummy rig should have vfo_switch capability");
|
||||||
|
assert!(caps.signal_meter, "dummy rig should have signal_meter capability");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dummy_has_no_filter_controls() {
|
||||||
|
let rig = DummyRig::new();
|
||||||
|
let caps = &rig.info().capabilities;
|
||||||
|
assert!(!caps.filter_controls, "dummy rig should not have filter_controls");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dummy_filter_state_is_none() {
|
||||||
|
let rig = DummyRig::new();
|
||||||
|
assert!(rig.filter_state().is_none(), "dummy rig should return None for filter_state");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Rig for DummyRig {
|
impl Rig for DummyRig {
|
||||||
fn info(&self) -> &RigInfo {
|
fn info(&self) -> &RigInfo {
|
||||||
&self.info
|
&self.info
|
||||||
|
|||||||
@@ -176,6 +176,11 @@ impl Ft450d {
|
|||||||
rpt: false,
|
rpt: false,
|
||||||
split: false,
|
split: false,
|
||||||
lock: true,
|
lock: true,
|
||||||
|
tx: true,
|
||||||
|
tx_limit: true,
|
||||||
|
vfo_switch: true,
|
||||||
|
filter_controls: false,
|
||||||
|
signal_meter: true,
|
||||||
},
|
},
|
||||||
access: RigAccessMethod::Serial {
|
access: RigAccessMethod::Serial {
|
||||||
path: path.to_string(),
|
path: path.to_string(),
|
||||||
|
|||||||
@@ -207,6 +207,11 @@ impl Ft817 {
|
|||||||
rpt: false,
|
rpt: false,
|
||||||
split: false,
|
split: false,
|
||||||
lock: true,
|
lock: true,
|
||||||
|
tx: true,
|
||||||
|
tx_limit: true,
|
||||||
|
vfo_switch: true,
|
||||||
|
filter_controls: false,
|
||||||
|
signal_meter: true,
|
||||||
},
|
},
|
||||||
access: RigAccessMethod::Serial {
|
access: RigAccessMethod::Serial {
|
||||||
path: path.to_string(),
|
path: path.to_string(),
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use trx_core::rig::{
|
|||||||
AudioSource, Rig, RigAccessMethod, RigCapabilities, RigCat, RigInfo, RigStatusFuture,
|
AudioSource, Rig, RigAccessMethod, RigCapabilities, RigCat, RigInfo, RigStatusFuture,
|
||||||
};
|
};
|
||||||
use trx_core::rig::response::RigError;
|
use trx_core::rig::response::RigError;
|
||||||
|
use trx_core::rig::state::RigFilterState;
|
||||||
use trx_core::{DynResult, RigMode};
|
use trx_core::{DynResult, RigMode};
|
||||||
|
|
||||||
/// RX-only backend for any SoapySDR-compatible device.
|
/// RX-only backend for any SoapySDR-compatible device.
|
||||||
@@ -22,6 +23,9 @@ pub struct SoapySdrRig {
|
|||||||
pipeline: dsp::SdrPipeline,
|
pipeline: dsp::SdrPipeline,
|
||||||
/// Index of the primary channel in `pipeline.channel_dsps`.
|
/// Index of the primary channel in `pipeline.channel_dsps`.
|
||||||
primary_channel_idx: usize,
|
primary_channel_idx: usize,
|
||||||
|
/// Current filter state of the primary channel (for filter_controls support).
|
||||||
|
bandwidth_hz: u32,
|
||||||
|
fir_taps: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SoapySdrRig {
|
impl SoapySdrRig {
|
||||||
@@ -108,6 +112,11 @@ impl SoapySdrRig {
|
|||||||
rit: false,
|
rit: false,
|
||||||
rpt: false,
|
rpt: false,
|
||||||
split: false,
|
split: false,
|
||||||
|
tx: false,
|
||||||
|
tx_limit: false,
|
||||||
|
vfo_switch: false,
|
||||||
|
filter_controls: true,
|
||||||
|
signal_meter: true,
|
||||||
},
|
},
|
||||||
// No serial/TCP access for SDR devices; carry args in addr field.
|
// No serial/TCP access for SDR devices; carry args in addr field.
|
||||||
access: RigAccessMethod::Tcp {
|
access: RigAccessMethod::Tcp {
|
||||||
@@ -115,12 +124,20 @@ impl SoapySdrRig {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Initialise filter state from primary channel config (index 0), or defaults.
|
||||||
|
let (bandwidth_hz, fir_taps) = channels
|
||||||
|
.first()
|
||||||
|
.map(|&(_, _, bw, taps)| (bw, taps as u32))
|
||||||
|
.unwrap_or((3000, 64));
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
info,
|
info,
|
||||||
freq: initial_freq,
|
freq: initial_freq,
|
||||||
mode: initial_mode,
|
mode: initial_mode,
|
||||||
pipeline,
|
pipeline,
|
||||||
primary_channel_idx: 0,
|
primary_channel_idx: 0,
|
||||||
|
bandwidth_hz,
|
||||||
|
fir_taps,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +147,7 @@ impl SoapySdrRig {
|
|||||||
pub fn new(args: &str) -> DynResult<Self> {
|
pub fn new(args: &str) -> DynResult<Self> {
|
||||||
Self::new_with_config(
|
Self::new_with_config(
|
||||||
args,
|
args,
|
||||||
&[], // no channels — pipeline does nothing
|
&[], // no channels — pipeline does nothing; filter defaults applied in new_with_config
|
||||||
"auto",
|
"auto",
|
||||||
30.0,
|
30.0,
|
||||||
48_000,
|
48_000,
|
||||||
@@ -299,6 +316,36 @@ impl RigCat for SoapySdrRig {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_bandwidth<'a>(
|
||||||
|
&'a mut self,
|
||||||
|
bandwidth_hz: u32,
|
||||||
|
) -> std::pin::Pin<Box<dyn std::future::Future<Output = DynResult<()>> + Send + 'a>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
tracing::debug!("SoapySdrRig: set_bandwidth -> {} Hz", bandwidth_hz);
|
||||||
|
self.bandwidth_hz = bandwidth_hz;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_fir_taps<'a>(
|
||||||
|
&'a mut self,
|
||||||
|
taps: u32,
|
||||||
|
) -> std::pin::Pin<Box<dyn std::future::Future<Output = DynResult<()>> + Send + 'a>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
tracing::debug!("SoapySdrRig: set_fir_taps -> {}", taps);
|
||||||
|
self.fir_taps = taps;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_state(&self) -> Option<RigFilterState> {
|
||||||
|
Some(RigFilterState {
|
||||||
|
bandwidth_hz: self.bandwidth_hz,
|
||||||
|
fir_taps: self.fir_taps,
|
||||||
|
cw_center_hz: 700,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Override: this backend provides demodulated PCM audio.
|
/// Override: this backend provides demodulated PCM audio.
|
||||||
fn as_audio_source(&self) -> Option<&dyn AudioSource> {
|
fn as_audio_source(&self) -> Option<&dyn AudioSource> {
|
||||||
Some(self)
|
Some(self)
|
||||||
|
|||||||
Reference in New Issue
Block a user