From 3d137fabb1c3636bb3ff971b1b783e04926c0841 Mon Sep 17 00:00:00 2001 From: Stanislaw Grams Date: Sat, 7 Feb 2026 13:03:05 +0100 Subject: [PATCH] [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 Signed-off-by: Stanislaw Grams --- src/trx-server/trx-backend/src/dummy.rs | 213 ++++++++++++++++++++++++ src/trx-server/trx-backend/src/lib.rs | 7 + 2 files changed, 220 insertions(+) create mode 100644 src/trx-server/trx-backend/src/dummy.rs diff --git a/src/trx-server/trx-backend/src/dummy.rs b/src/trx-server/trx-backend/src/dummy.rs new file mode 100644 index 0000000..5ab281b --- /dev/null +++ b/src/trx-server/trx-backend/src/dummy.rs @@ -0,0 +1,213 @@ +// SPDX-FileCopyrightText: 2025 Stanislaw Grams +// +// 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> + Send + 'a>> { + self.freq = freq; + Box::pin(async { Ok(()) }) + } + + fn set_mode<'a>( + &'a mut self, + mode: RigMode, + ) -> Pin> + Send + 'a>> { + self.mode = mode; + Box::pin(async { Ok(()) }) + } + + fn set_ptt<'a>( + &'a mut self, + ptt: bool, + ) -> Pin> + Send + 'a>> { + self.ptt = ptt; + Box::pin(async { Ok(()) }) + } + + fn power_on<'a>( + &'a mut self, + ) -> Pin> + Send + 'a>> { + self.powered = true; + Box::pin(async { Ok(()) }) + } + + fn power_off<'a>( + &'a mut self, + ) -> Pin> + Send + 'a>> { + self.powered = false; + Box::pin(async { Ok(()) }) + } + + fn get_signal_strength<'a>( + &'a mut self, + ) -> Pin> + Send + 'a>> { + Box::pin(async { Ok(5) }) + } + + fn get_tx_power<'a>( + &'a mut self, + ) -> Pin> + 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> + Send + 'a>> { + let limit = self.tx_limit; + Box::pin(async move { Ok(limit) }) + } + + fn set_tx_limit<'a>( + &'a mut self, + limit: u8, + ) -> Pin> + Send + 'a>> { + self.tx_limit = limit; + Box::pin(async { Ok(()) }) + } + + fn toggle_vfo<'a>( + &'a mut self, + ) -> Pin> + 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> + Send + 'a>> { + self.locked = true; + Box::pin(async { Ok(()) }) + } + + fn unlock<'a>( + &'a mut self, + ) -> Pin> + Send + 'a>> { + self.locked = false; + Box::pin(async { Ok(()) }) + } +} diff --git a/src/trx-server/trx-backend/src/lib.rs b/src/trx-server/trx-backend/src/lib.rs index 469b6ef..f1efceb 100644 --- a/src/trx-server/trx-backend/src/lib.rs +++ b/src/trx-server/trx-backend/src/lib.rs @@ -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> { + 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);