[fix](trx): speed up web load and harden FT817/Soapy recovery

This commit is contained in:
2026-03-05 20:36:46 +01:00
parent bc0d9a6273
commit ccef359034
6 changed files with 108 additions and 46 deletions
@@ -16,6 +16,7 @@ mod filter;
mod spectrum;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use num_complex::Complex;
use tokio::sync::broadcast;
@@ -57,7 +58,7 @@ pub trait IqSource: Send + 'static {
/// Gives a source-specific implementation a chance to recover from a
/// read error (for example, by rearming a hardware stream after overflow).
/// Returns `true` when an active recovery action was attempted.
fn handle_read_error(&mut self, _err: &str) -> Result<bool, String> {
fn handle_read_error(&mut self, _err: &str, _streak: u32) -> Result<bool, String> {
Ok(false)
}
}
@@ -199,6 +200,8 @@ fn iq_read_loop(
let mut spectrum = SpectrumSnapshotter::new();
let mut read_error_streak: u32 = 0;
let mut overflow_log_window_start: Option<Instant> = None;
let mut overflow_log_suppressed: u32 = 0;
loop {
// Apply any pending hardware retune before the next read.
@@ -228,7 +231,9 @@ fn iq_read_loop(
}
Err(e) => {
read_error_streak = read_error_streak.saturating_add(1);
let recovered = match source.handle_read_error(&e) {
let err_lc = e.to_ascii_lowercase();
let is_overflow = err_lc.contains("overflow") || err_lc.contains("overrun");
let recovered = match source.handle_read_error(&e, read_error_streak) {
Ok(result) => result,
Err(recovery_err) => {
tracing::warn!(
@@ -238,12 +243,39 @@ fn iq_read_loop(
false
}
};
tracing::warn!(
"IQ source read error: {}; retrying (streak={}, recovered={})",
e,
read_error_streak,
recovered
);
if is_overflow {
let now = Instant::now();
if overflow_log_window_start
.map(|ts| now.duration_since(ts) >= Duration::from_secs(3))
.unwrap_or(true)
{
let suppressed = overflow_log_suppressed;
overflow_log_window_start = Some(now);
overflow_log_suppressed = 0;
tracing::warn!(
"IQ source overflow: {}; retrying (streak={}, recovered={}, suppressed={})",
e,
read_error_streak,
recovered,
suppressed
);
} else {
overflow_log_suppressed = overflow_log_suppressed.saturating_add(1);
tracing::debug!(
"IQ source overflow suppressed: {} (streak={}, recovered={})",
e,
read_error_streak,
recovered
);
}
} else {
tracing::warn!(
"IQ source read error: {}; retrying (streak={}, recovered={})",
e,
read_error_streak,
recovered
);
}
let base_sleep_ms = if recovered {
block_duration_ms.max(20)
} else {
@@ -179,17 +179,22 @@ impl IqSource for RealIqSource {
.map_err(|e| format!("Failed to set SDR gain: {}", e))
}
fn handle_read_error(&mut self, err: &str) -> Result<bool, String> {
fn handle_read_error(&mut self, err: &str, streak: u32) -> Result<bool, String> {
let err_lc = err.to_ascii_lowercase();
let is_overrun = err_lc.contains("overflow") || err_lc.contains("overrun");
if !is_overrun {
return Ok(false);
}
tracing::warn!("SoapySDR RX overflow detected; restarting RX stream");
self.stream
.deactivate(None)
.map_err(|e| format!("Failed to deactivate RX stream after overflow: {}", e))?;
// Overflow is often transient; avoid immediate stream restart churn.
// Only restart after several consecutive read failures.
if streak < 3 {
return Ok(true);
}
tracing::warn!(
"SoapySDR RX overflow persists (streak={}); restarting RX stream",
streak
);
let _ = self.stream.deactivate(None);
std::thread::sleep(std::time::Duration::from_millis(25));
self.stream
.activate(None)