[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:
2026-02-25 20:24:55 +01:00
parent 3335942374
commit dc33d1e841
4 changed files with 92 additions and 1 deletions
+34
View File
@@ -104,6 +104,11 @@ impl DummyRig {
rit: false,
rpt: false,
split: false,
tx: true,
tx_limit: true,
vfo_switch: true,
filter_controls: false,
signal_meter: true,
},
access: RigAccessMethod::Serial {
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 {
fn info(&self) -> &RigInfo {
&self.info
@@ -176,6 +176,11 @@ impl Ft450d {
rpt: false,
split: false,
lock: true,
tx: true,
tx_limit: true,
vfo_switch: true,
filter_controls: false,
signal_meter: true,
},
access: RigAccessMethod::Serial {
path: path.to_string(),
@@ -207,6 +207,11 @@ impl Ft817 {
rpt: false,
split: false,
lock: true,
tx: true,
tx_limit: true,
vfo_switch: true,
filter_controls: false,
signal_meter: true,
},
access: RigAccessMethod::Serial {
path: path.to_string(),
@@ -12,6 +12,7 @@ use trx_core::rig::{
AudioSource, Rig, RigAccessMethod, RigCapabilities, RigCat, RigInfo, RigStatusFuture,
};
use trx_core::rig::response::RigError;
use trx_core::rig::state::RigFilterState;
use trx_core::{DynResult, RigMode};
/// RX-only backend for any SoapySDR-compatible device.
@@ -22,6 +23,9 @@ pub struct SoapySdrRig {
pipeline: dsp::SdrPipeline,
/// Index of the primary channel in `pipeline.channel_dsps`.
primary_channel_idx: usize,
/// Current filter state of the primary channel (for filter_controls support).
bandwidth_hz: u32,
fir_taps: u32,
}
impl SoapySdrRig {
@@ -108,6 +112,11 @@ impl SoapySdrRig {
rit: false,
rpt: 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.
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 {
info,
freq: initial_freq,
mode: initial_mode,
pipeline,
primary_channel_idx: 0,
bandwidth_hz,
fir_taps,
})
}
@@ -130,7 +147,7 @@ impl SoapySdrRig {
pub fn new(args: &str) -> DynResult<Self> {
Self::new_with_config(
args,
&[], // no channels — pipeline does nothing
&[], // no channels — pipeline does nothing; filter defaults applied in new_with_config
"auto",
30.0,
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.
fn as_audio_source(&self) -> Option<&dyn AudioSource> {
Some(self)