[feat](trx-backend): add dummy rig backend

Register a dummy rig backend that holds state in memory and responds
to all CAT commands immediately. Useful for development and testing
without hardware.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
2026-02-07 13:03:05 +01:00
parent e7512b3cf0
commit 3d137fabb1
2 changed files with 220 additions and 0 deletions
+213
View File
@@ -0,0 +1,213 @@
// SPDX-FileCopyrightText: 2025 Stanislaw Grams <stanislawgrams@gmail.com>
//
// SPDX-License-Identifier: BSD-2-Clause
//! Dummy rig backend for development and testing.
//!
//! Holds rig state in memory and responds to all commands immediately.
//! No hardware or serial port required.
use std::pin::Pin;
use trx_core::radio::freq::{Band, Freq};
use trx_core::rig::state::RigMode;
use trx_core::rig::{
Rig, RigAccessMethod, RigCapabilities, RigCat, RigInfo, RigStatusFuture, RigVfo, RigVfoEntry,
};
use trx_core::DynResult;
pub struct DummyRig {
info: RigInfo,
freq: Freq,
mode: RigMode,
ptt: bool,
powered: bool,
locked: bool,
tx_limit: u8,
active_vfo: usize,
vfo_b_freq: Freq,
vfo_b_mode: RigMode,
}
impl DummyRig {
pub fn new() -> Self {
Self {
info: RigInfo {
manufacturer: "Dummy".to_string(),
model: "dummy".to_string(),
revision: "1.0".to_string(),
capabilities: RigCapabilities {
supported_bands: vec![
Band { low_hz: 1_800_000, high_hz: 2_000_000, tx_allowed: true },
Band { low_hz: 3_500_000, high_hz: 4_000_000, tx_allowed: true },
Band { low_hz: 7_000_000, high_hz: 7_300_000, tx_allowed: true },
Band { low_hz: 14_000_000, high_hz: 14_350_000, tx_allowed: true },
Band { low_hz: 21_000_000, high_hz: 21_450_000, tx_allowed: true },
Band { low_hz: 28_000_000, high_hz: 29_700_000, tx_allowed: true },
Band { low_hz: 50_000_000, high_hz: 54_000_000, tx_allowed: true },
Band { low_hz: 144_000_000, high_hz: 148_000_000, tx_allowed: true },
Band { low_hz: 430_000_000, high_hz: 440_000_000, tx_allowed: true },
],
supported_modes: vec![
RigMode::LSB,
RigMode::USB,
RigMode::CW,
RigMode::CWR,
RigMode::AM,
RigMode::FM,
RigMode::WFM,
RigMode::DIG,
RigMode::PKT,
],
num_vfos: 2,
lock: false,
lockable: true,
attenuator: false,
preamp: false,
rit: false,
rpt: false,
split: false,
},
access: RigAccessMethod::Serial {
path: "/dev/null".to_string(),
baud: 9600,
},
},
freq: Freq { hz: 144_300_000 },
mode: RigMode::USB,
ptt: false,
powered: true,
locked: false,
tx_limit: 5,
active_vfo: 0,
vfo_b_freq: Freq { hz: 7_100_000 },
vfo_b_mode: RigMode::LSB,
}
}
fn build_vfo(&self) -> RigVfo {
RigVfo {
active: Some(self.active_vfo),
entries: vec![
RigVfoEntry {
name: "A".to_string(),
freq: self.freq,
mode: Some(self.mode.clone()),
},
RigVfoEntry {
name: "B".to_string(),
freq: self.vfo_b_freq,
mode: Some(self.vfo_b_mode.clone()),
},
],
}
}
}
impl Rig for DummyRig {
fn info(&self) -> &RigInfo {
&self.info
}
}
impl RigCat for DummyRig {
fn get_status<'a>(&'a mut self) -> RigStatusFuture<'a> {
Box::pin(async move {
Ok((self.freq, self.mode.clone(), Some(self.build_vfo())))
})
}
fn set_freq<'a>(
&'a mut self,
freq: Freq,
) -> Pin<Box<dyn std::future::Future<Output = DynResult<()>> + Send + 'a>> {
self.freq = freq;
Box::pin(async { Ok(()) })
}
fn set_mode<'a>(
&'a mut self,
mode: RigMode,
) -> Pin<Box<dyn std::future::Future<Output = DynResult<()>> + Send + 'a>> {
self.mode = mode;
Box::pin(async { Ok(()) })
}
fn set_ptt<'a>(
&'a mut self,
ptt: bool,
) -> Pin<Box<dyn std::future::Future<Output = DynResult<()>> + Send + 'a>> {
self.ptt = ptt;
Box::pin(async { Ok(()) })
}
fn power_on<'a>(
&'a mut self,
) -> Pin<Box<dyn std::future::Future<Output = DynResult<()>> + Send + 'a>> {
self.powered = true;
Box::pin(async { Ok(()) })
}
fn power_off<'a>(
&'a mut self,
) -> Pin<Box<dyn std::future::Future<Output = DynResult<()>> + Send + 'a>> {
self.powered = false;
Box::pin(async { Ok(()) })
}
fn get_signal_strength<'a>(
&'a mut self,
) -> Pin<Box<dyn std::future::Future<Output = DynResult<u8>> + Send + 'a>> {
Box::pin(async { Ok(5) })
}
fn get_tx_power<'a>(
&'a mut self,
) -> Pin<Box<dyn std::future::Future<Output = DynResult<u8>> + Send + 'a>> {
let power = if self.ptt { 5 } else { 0 };
Box::pin(async move { Ok(power) })
}
fn get_tx_limit<'a>(
&'a mut self,
) -> Pin<Box<dyn std::future::Future<Output = DynResult<u8>> + Send + 'a>> {
let limit = self.tx_limit;
Box::pin(async move { Ok(limit) })
}
fn set_tx_limit<'a>(
&'a mut self,
limit: u8,
) -> Pin<Box<dyn std::future::Future<Output = DynResult<()>> + Send + 'a>> {
self.tx_limit = limit;
Box::pin(async { Ok(()) })
}
fn toggle_vfo<'a>(
&'a mut self,
) -> Pin<Box<dyn std::future::Future<Output = DynResult<()>> + Send + 'a>> {
// Swap active VFO and swap freq/mode
let old_freq = self.freq;
let old_mode = self.mode.clone();
self.freq = self.vfo_b_freq;
self.mode = self.vfo_b_mode.clone();
self.vfo_b_freq = old_freq;
self.vfo_b_mode = old_mode;
self.active_vfo = if self.active_vfo == 0 { 1 } else { 0 };
Box::pin(async { Ok(()) })
}
fn lock<'a>(
&'a mut self,
) -> Pin<Box<dyn std::future::Future<Output = DynResult<()>> + Send + 'a>> {
self.locked = true;
Box::pin(async { Ok(()) })
}
fn unlock<'a>(
&'a mut self,
) -> Pin<Box<dyn std::future::Future<Output = DynResult<()>> + Send + 'a>> {
self.locked = false;
Box::pin(async { Ok(()) })
}
}
+7
View File
@@ -8,6 +8,8 @@ use std::sync::{Mutex, OnceLock};
use trx_core::rig::RigCat;
use trx_core::DynResult;
mod dummy;
#[cfg(feature = "ft817")]
use trx_backend_ft817::Ft817;
@@ -53,10 +55,15 @@ pub fn register_backend(name: &str, factory: BackendFactory) {
/// Register all built-in backends enabled by features.
pub fn register_builtin_backends() {
register_backend("dummy", dummy_factory);
#[cfg(feature = "ft817")]
register_backend("ft817", ft817_factory);
}
fn dummy_factory(_access: RigAccess) -> DynResult<Box<dyn RigCat>> {
Ok(Box::new(dummy::DummyRig::new()))
}
/// Check whether a backend name is registered.
pub fn is_backend_registered(name: &str) -> bool {
let key = normalize_name(name);