[fix](trx-backend-soapysdr): keep vchans across scheduler retunes
Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -18,8 +18,9 @@
|
|||||||
//! ## Center-frequency updates
|
//! ## Center-frequency updates
|
||||||
//!
|
//!
|
||||||
//! When the hardware retunes (changing `center_hz`), all channel IF offsets must
|
//! When the hardware retunes (changing `center_hz`), all channel IF offsets must
|
||||||
//! be recomputed. The rig calls `update_center_hz()` after every retune; this
|
//! be recomputed. The rig calls `update_center_hz()` after every retune; this
|
||||||
//! updates every `ChannelDsp` in a single write-locked pass.
|
//! updates every `ChannelDsp` in place and pauses out-of-span channels instead
|
||||||
|
//! of destroying them.
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicI64, Ordering};
|
use std::sync::atomic::{AtomicI64, Ordering};
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
@@ -90,7 +91,7 @@ pub struct SdrVirtualChannelManager {
|
|||||||
/// Maximum total channels including the primary (enforced on `add_channel`).
|
/// Maximum total channels including the primary (enforced on `add_channel`).
|
||||||
max_total: usize,
|
max_total: usize,
|
||||||
channels: RwLock<Vec<ManagedChannel>>,
|
channels: RwLock<Vec<ManagedChannel>>,
|
||||||
/// Fires whenever a channel is destroyed (e.g. went out of SDR bandwidth).
|
/// Fires whenever a channel is explicitly destroyed.
|
||||||
destroyed_tx: broadcast::Sender<Uuid>,
|
destroyed_tx: broadcast::Sender<Uuid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,31 +207,24 @@ impl SdrVirtualChannelManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Called by `SoapySdrRig` whenever the hardware center frequency changes.
|
/// Called by `SoapySdrRig` whenever the hardware center frequency changes.
|
||||||
/// Recomputes the IF offset for every virtual channel.
|
/// Recomputes the IF offset for every virtual channel and pauses any
|
||||||
|
/// channel that is temporarily outside the current SDR span.
|
||||||
pub fn update_center_hz(&self, new_center_hz: i64) {
|
pub fn update_center_hz(&self, new_center_hz: i64) {
|
||||||
self.center_hz.store(new_center_hz, Ordering::Relaxed);
|
self.center_hz.store(new_center_hz, Ordering::Relaxed);
|
||||||
let half_span = self.half_span_hz();
|
let half_span = self.half_span_hz();
|
||||||
|
|
||||||
// Single pass under read lock: update in-band IF offsets and collect OOB IDs.
|
let channels = self.channels.read().unwrap();
|
||||||
let oob_ids: Vec<Uuid> = {
|
let dsps = self.pipeline.channel_dsps.read().unwrap();
|
||||||
let channels = self.channels.read().unwrap();
|
for ch in channels.iter().filter(|c| !c.permanent) {
|
||||||
let dsps = self.pipeline.channel_dsps.read().unwrap();
|
let new_if_hz = ch.freq_hz as i64 - new_center_hz;
|
||||||
let mut oob = Vec::new();
|
let in_span = new_if_hz.unsigned_abs() as i64 <= half_span;
|
||||||
for ch in channels.iter().filter(|c| !c.permanent) {
|
if let Some(dsp_arc) = dsps.get(ch.pipeline_slot) {
|
||||||
let new_if_hz = ch.freq_hz as i64 - new_center_hz;
|
let mut dsp = dsp_arc.lock().unwrap();
|
||||||
if new_if_hz.unsigned_abs() as i64 > half_span {
|
if in_span {
|
||||||
oob.push(ch.id);
|
dsp.set_channel_if_hz(new_if_hz as f64);
|
||||||
} else if let Some(dsp_arc) = dsps.get(ch.pipeline_slot) {
|
|
||||||
dsp_arc.lock().unwrap().set_channel_if_hz(new_if_hz as f64);
|
|
||||||
}
|
}
|
||||||
|
dsp.set_processing_enabled(in_span);
|
||||||
}
|
}
|
||||||
oob
|
|
||||||
}; // read locks released here
|
|
||||||
|
|
||||||
// Destroy OOB channels and notify subscribers.
|
|
||||||
for id in oob_ids {
|
|
||||||
let _ = self.remove_channel(id); // acquires write lock internally
|
|
||||||
let _ = self.destroyed_tx.send(id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -508,4 +502,24 @@ mod tests {
|
|||||||
assert_eq!(visible.len(), 2);
|
assert_eq!(visible.len(), 2);
|
||||||
assert!(visible.iter().all(|channel| channel.id != hidden_id));
|
assert!(visible.iter().all(|channel| channel.id != hidden_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn retune_keeps_virtual_channel_allocated() {
|
||||||
|
let p = make_pipeline();
|
||||||
|
let mgr = SdrVirtualChannelManager::new(p, 1, 4);
|
||||||
|
mgr.update_center_hz(14_100_000);
|
||||||
|
let mut destroyed_rx = mgr.subscribe_destroyed();
|
||||||
|
|
||||||
|
let (id, _) = mgr.add_channel(14_074_000, &RigMode::USB).unwrap();
|
||||||
|
mgr.update_center_hz(16_000_000);
|
||||||
|
|
||||||
|
assert!(mgr.channels().iter().any(|channel| channel.id == id));
|
||||||
|
assert!(matches!(
|
||||||
|
destroyed_rx.try_recv(),
|
||||||
|
Err(tokio::sync::broadcast::error::TryRecvError::Empty)
|
||||||
|
));
|
||||||
|
|
||||||
|
mgr.update_center_hz(14_100_000);
|
||||||
|
assert!(mgr.channels().iter().any(|channel| channel.id == id));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user