[feat](trx-backend): VirtualChannelManager trait + SdrVirtualChannelManager impl
Add VirtualChannelManager trait in trx-core::vchan with types VChannelInfo, VChanError, and SharedVChanManager alias. Re-export from trx-backend::vchan. Implement SdrVirtualChannelManager in trx-backend-soapysdr: - Wraps Arc<SdrPipeline> + shared AtomicI64 center_hz - add_channel / remove_channel / set_channel_freq / set_channel_mode - Slot-stability: on remove, shifts pipeline_slot for surviving channels - update_center_hz: recomputes IF offsets for all virtual channels on retune - update_primary_meta: keeps channel-0 freq/mode in sync for API consumers Wire into SoapySdrRig (holds Arc<SdrVirtualChannelManager>, exposes channel_manager()), SdrPipeline (shared_center_hz AtomicI64), and RigHandle (vchan_manager: Option<SharedVChanManager>). main.rs extracts the manager before boxing the SDR rig and stores it in the handle. Add max_virtual_channels to SdrConfig (default 4, TOML-configurable). Add 5 unit tests: add, remove, permanent guard, cap, out-of-bandwidth. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -13,3 +13,4 @@ serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
flate2 = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
|
||||
@@ -7,6 +7,7 @@ pub mod decode;
|
||||
pub mod math;
|
||||
pub mod radio;
|
||||
pub mod rig;
|
||||
pub mod vchan;
|
||||
|
||||
pub type DynResult<T> = Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
// SPDX-FileCopyrightText: 2025 Stanislaw Grams <stanislawgrams@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
//! Virtual channel management trait and shared types.
|
||||
//!
|
||||
//! A *virtual channel* is an independent DSP slice within the capture bandwidth
|
||||
//! of an SDR rig. Each has its own frequency offset, demodulation mode, and
|
||||
//! PCM audio broadcast. Traditional (non-SDR) rigs do not support virtual
|
||||
//! channels; their `RigHandle::vchan_manager` field will be `None`.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::broadcast;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::rig::state::RigMode;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shared types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Snapshot of one virtual channel's state (HTTP-serialisable).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct VChannelInfo {
|
||||
/// Stable UUID identifier.
|
||||
pub id: Uuid,
|
||||
/// Display index in the ordered channel list (0 = primary).
|
||||
pub index: usize,
|
||||
/// Dial frequency in Hz.
|
||||
pub freq_hz: u64,
|
||||
/// Demodulation mode name (e.g. "USB", "FM").
|
||||
pub mode: String,
|
||||
/// `true` for the primary channel (index 0), which cannot be removed.
|
||||
pub permanent: bool,
|
||||
}
|
||||
|
||||
/// Errors returned by virtual channel management operations.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum VChanError {
|
||||
/// The configured channel cap would be exceeded.
|
||||
CapReached { max: usize },
|
||||
/// The requested frequency lies outside the current SDR capture bandwidth.
|
||||
OutOfBandwidth { half_span_hz: i64 },
|
||||
/// No channel with the given UUID exists.
|
||||
NotFound,
|
||||
/// Attempted to remove the permanent primary channel.
|
||||
Permanent,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for VChanError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
VChanError::CapReached { max } => {
|
||||
write!(f, "virtual channel cap reached (max {})", max)
|
||||
}
|
||||
VChanError::OutOfBandwidth { half_span_hz } => write!(
|
||||
f,
|
||||
"frequency outside SDR capture bandwidth (±{} Hz)",
|
||||
half_span_hz
|
||||
),
|
||||
VChanError::NotFound => write!(f, "virtual channel not found"),
|
||||
VChanError::Permanent => write!(f, "cannot remove the primary channel"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Trait
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Manages virtual DSP channels for an SDR rig.
|
||||
///
|
||||
/// Implementations are `Send + Sync` so the manager can be shared across
|
||||
/// tokio tasks and actix-web handlers.
|
||||
pub trait VirtualChannelManager: Send + Sync {
|
||||
/// Add a new virtual channel tuned to `freq_hz` with `mode`.
|
||||
///
|
||||
/// Returns the new channel UUID and a PCM broadcast receiver that delivers
|
||||
/// decoded audio frames for this channel.
|
||||
fn add_channel(
|
||||
&self,
|
||||
freq_hz: u64,
|
||||
mode: &RigMode,
|
||||
) -> Result<(Uuid, broadcast::Receiver<Vec<f32>>), VChanError>;
|
||||
|
||||
/// Remove a virtual channel by UUID. The primary channel (index 0) cannot
|
||||
/// be removed and returns `VChanError::Permanent`.
|
||||
fn remove_channel(&self, id: Uuid) -> Result<(), VChanError>;
|
||||
|
||||
/// Update the dial frequency of an existing channel.
|
||||
fn set_channel_freq(&self, id: Uuid, freq_hz: u64) -> Result<(), VChanError>;
|
||||
|
||||
/// Update the demodulation mode of an existing channel.
|
||||
fn set_channel_mode(&self, id: Uuid, mode: &RigMode) -> Result<(), VChanError>;
|
||||
|
||||
/// Subscribe to decoded PCM audio from a channel.
|
||||
/// Returns `None` if the channel UUID does not exist.
|
||||
fn subscribe_pcm(&self, id: Uuid) -> Option<broadcast::Receiver<Vec<f32>>>;
|
||||
|
||||
/// Return a snapshot of all channels in display order.
|
||||
fn channels(&self) -> Vec<VChannelInfo>;
|
||||
|
||||
/// Maximum number of channels (including the primary channel).
|
||||
fn max_channels(&self) -> usize;
|
||||
}
|
||||
|
||||
/// Convenience alias used in `RigHandle`.
|
||||
pub type SharedVChanManager = Arc<dyn VirtualChannelManager>;
|
||||
Reference in New Issue
Block a user