[fix](trx): speed up web load and harden FT817/Soapy recovery
This commit is contained in:
@@ -7,11 +7,13 @@
|
|||||||
<link rel="icon" type="image/png" sizes="any" href="/favicon.ico?v=5" />
|
<link rel="icon" type="image/png" sizes="any" href="/favicon.ico?v=5" />
|
||||||
<link rel="shortcut icon" href="/favicon.ico?v=5" />
|
<link rel="shortcut icon" href="/favicon.ico?v=5" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/favicon.png?v=5" />
|
<link rel="apple-touch-icon" sizes="180x180" href="/favicon.png?v=5" />
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/dseg14-classic/400.css" />
|
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
|
||||||
|
<link rel="preconnect" href="https://unpkg.com" crossorigin />
|
||||||
|
<link rel="preload" as="style" href="https://cdn.jsdelivr.net/npm/@fontsource/dseg14-classic/400.css" onload="this.onload=null;this.rel='stylesheet'" />
|
||||||
|
<noscript><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/dseg14-classic/400.css" /></noscript>
|
||||||
<link rel="stylesheet" href="/style.css" />
|
<link rel="stylesheet" href="/style.css" />
|
||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
<link rel="preload" as="style" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" onload="this.onload=null;this.rel='stylesheet'" />
|
||||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
<noscript><link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /></noscript>
|
||||||
<script src="/leaflet-ais-tracksymbol.js"></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="card" id="card">
|
<div class="card" id="card">
|
||||||
@@ -638,5 +640,7 @@
|
|||||||
<script src="/wspr.js"></script>
|
<script src="/wspr.js"></script>
|
||||||
<script src="/cw.js"></script>
|
<script src="/cw.js"></script>
|
||||||
<script src="/bookmarks.js"></script>
|
<script src="/bookmarks.js"></script>
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
|
<script src="/leaflet-ais-tracksymbol.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -250,6 +250,7 @@ pub async fn events(
|
|||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
.insert_header((header::CONTENT_TYPE, "text/event-stream"))
|
.insert_header((header::CONTENT_TYPE, "text/event-stream"))
|
||||||
|
.insert_header((header::CONTENT_ENCODING, "identity"))
|
||||||
.insert_header((header::CACHE_CONTROL, "no-cache"))
|
.insert_header((header::CACHE_CONTROL, "no-cache"))
|
||||||
.insert_header((header::CONNECTION, "keep-alive"))
|
.insert_header((header::CONNECTION, "keep-alive"))
|
||||||
.streaming(stream))
|
.streaming(stream))
|
||||||
@@ -330,6 +331,7 @@ pub async fn decode_events(
|
|||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
.insert_header((header::CONTENT_TYPE, "text/event-stream"))
|
.insert_header((header::CONTENT_TYPE, "text/event-stream"))
|
||||||
|
.insert_header((header::CONTENT_ENCODING, "identity"))
|
||||||
.insert_header((header::CACHE_CONTROL, "no-cache"))
|
.insert_header((header::CACHE_CONTROL, "no-cache"))
|
||||||
.insert_header((header::CONNECTION, "keep-alive"))
|
.insert_header((header::CONNECTION, "keep-alive"))
|
||||||
.streaming(stream))
|
.streaming(stream))
|
||||||
@@ -416,6 +418,7 @@ pub async fn spectrum(
|
|||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
.insert_header((header::CONTENT_TYPE, "text/event-stream"))
|
.insert_header((header::CONTENT_TYPE, "text/event-stream"))
|
||||||
|
.insert_header((header::CONTENT_ENCODING, "identity"))
|
||||||
.insert_header((header::CACHE_CONTROL, "no-cache"))
|
.insert_header((header::CACHE_CONTROL, "no-cache"))
|
||||||
.insert_header((header::CONNECTION, "keep-alive"))
|
.insert_header((header::CONNECTION, "keep-alive"))
|
||||||
.streaming(stream))
|
.streaming(stream))
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use actix_web::dev::Server;
|
use actix_web::dev::Server;
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
middleware::{DefaultHeaders, Logger},
|
middleware::{Compress, DefaultHeaders, Logger},
|
||||||
web, App, HttpServer,
|
web, App, HttpServer,
|
||||||
};
|
};
|
||||||
use tokio::signal;
|
use tokio::signal;
|
||||||
@@ -126,6 +126,7 @@ fn build_server(
|
|||||||
.app_data(context_data.clone())
|
.app_data(context_data.clone())
|
||||||
.app_data(auth_state.clone())
|
.app_data(auth_state.clone())
|
||||||
.app_data(bookmark_store.clone())
|
.app_data(bookmark_store.clone())
|
||||||
|
.wrap(Compress::default())
|
||||||
.wrap(
|
.wrap(
|
||||||
DefaultHeaders::new()
|
DefaultHeaders::new()
|
||||||
.add(("Referrer-Policy", "same-origin"))
|
.add(("Referrer-Policy", "same-origin"))
|
||||||
|
|||||||
@@ -170,19 +170,8 @@ pub async fn run_rig_task(
|
|||||||
match rig.power_on().await {
|
match rig.power_on().await {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
state.control.enabled = Some(true);
|
state.control.enabled = Some(true);
|
||||||
time::sleep(Duration::from_secs(3)).await;
|
if let Err(e) = refresh_after_power_on(&mut rig, &mut state, retry).await {
|
||||||
if let Err(e) = refresh_state_with_retry(&mut rig, &mut state, retry).await {
|
warn!("Initial PowerOn refresh failed after retries (continuing): {}", e);
|
||||||
warn!(
|
|
||||||
"Initial PowerOn refresh failed: {:?}; retrying once after short delay",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
time::sleep(Duration::from_millis(500)).await;
|
|
||||||
if let Err(e2) = refresh_state_with_retry(&mut rig, &mut state, retry).await {
|
|
||||||
warn!(
|
|
||||||
"Initial PowerOn second refresh failed (continuing): {:?}",
|
|
||||||
e2
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
initial_status_read = true;
|
initial_status_read = true;
|
||||||
}
|
}
|
||||||
@@ -562,23 +551,14 @@ async fn process_command(
|
|||||||
CommandResult::PowerUpdated(on) => {
|
CommandResult::PowerUpdated(on) => {
|
||||||
ctx.state.control.enabled = Some(on);
|
ctx.state.control.enabled = Some(on);
|
||||||
if on {
|
if on {
|
||||||
time::sleep(Duration::from_secs(3)).await;
|
if let Err(e) = refresh_after_power_on(ctx.rig, ctx.state, ctx.retry).await
|
||||||
|
{
|
||||||
|
error!("Failed to refresh after PowerOn: {}", e);
|
||||||
|
return Err(RigError::communication(format!("CAT error: {}", e)));
|
||||||
|
}
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
*ctx.poll_pause_until = Some(now + Duration::from_secs(3));
|
*ctx.poll_pause_until = Some(now + Duration::from_secs(3));
|
||||||
*ctx.last_power_on = Some(now);
|
*ctx.last_power_on = Some(now);
|
||||||
// Refresh state after power on
|
|
||||||
if let Err(e) =
|
|
||||||
refresh_state_with_retry(ctx.rig, ctx.state, ctx.retry).await
|
|
||||||
{
|
|
||||||
if is_invalid_bcd_error(e.as_ref()) {
|
|
||||||
warn!("Transient CAT decode after PowerOn (ignored): {:?}", e);
|
|
||||||
*ctx.poll_pause_until =
|
|
||||||
Some(Instant::now() + Duration::from_millis(1500));
|
|
||||||
} else {
|
|
||||||
error!("Failed to refresh after PowerOn: {:?}", e);
|
|
||||||
return Err(RigError::communication(format!("CAT error: {}", e)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ctx.state.status.tx_en = false;
|
ctx.state.status.tx_en = false;
|
||||||
}
|
}
|
||||||
@@ -716,6 +696,43 @@ async fn refresh_state_from_cat(rig: &mut Box<dyn RigCat>, state: &mut RigState)
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn refresh_after_power_on(
|
||||||
|
rig: &mut Box<dyn RigCat>,
|
||||||
|
state: &mut RigState,
|
||||||
|
retry: &ExponentialBackoff,
|
||||||
|
) -> DynResult<()> {
|
||||||
|
let mut last_err = String::new();
|
||||||
|
for attempt in 1..=3 {
|
||||||
|
if attempt == 1 {
|
||||||
|
time::sleep(Duration::from_secs(3)).await;
|
||||||
|
} else {
|
||||||
|
warn!(
|
||||||
|
"PowerOn refresh attempt {} failed; issuing additional PowerOn pulse",
|
||||||
|
attempt - 1
|
||||||
|
);
|
||||||
|
if let Err(e) = rig.power_on().await {
|
||||||
|
warn!("PowerOn retry {} failed: {:?}", attempt, e);
|
||||||
|
}
|
||||||
|
time::sleep(Duration::from_millis(1300)).await;
|
||||||
|
}
|
||||||
|
match refresh_state_with_retry(rig, state, retry).await {
|
||||||
|
Ok(()) => return Ok(()),
|
||||||
|
Err(e) => {
|
||||||
|
last_err = e.to_string();
|
||||||
|
if is_invalid_bcd_error(e.as_ref()) {
|
||||||
|
warn!(
|
||||||
|
"Transient CAT decode after PowerOn attempt {}: {:?}",
|
||||||
|
attempt, e
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
warn!("PowerOn refresh attempt {} failed: {:?}", attempt, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(format!("refresh after PowerOn failed after retries: {}", last_err).into())
|
||||||
|
}
|
||||||
|
|
||||||
/// Apply initial mode/frequency after a successful CAT status read.
|
/// Apply initial mode/frequency after a successful CAT status read.
|
||||||
async fn apply_initial_tune(
|
async fn apply_initial_tune(
|
||||||
rig: &mut Box<dyn RigCat>,
|
rig: &mut Box<dyn RigCat>,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ mod filter;
|
|||||||
mod spectrum;
|
mod spectrum;
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use num_complex::Complex;
|
use num_complex::Complex;
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
@@ -57,7 +58,7 @@ pub trait IqSource: Send + 'static {
|
|||||||
/// Gives a source-specific implementation a chance to recover from a
|
/// Gives a source-specific implementation a chance to recover from a
|
||||||
/// read error (for example, by rearming a hardware stream after overflow).
|
/// read error (for example, by rearming a hardware stream after overflow).
|
||||||
/// Returns `true` when an active recovery action was attempted.
|
/// 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)
|
Ok(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,6 +200,8 @@ fn iq_read_loop(
|
|||||||
|
|
||||||
let mut spectrum = SpectrumSnapshotter::new();
|
let mut spectrum = SpectrumSnapshotter::new();
|
||||||
let mut read_error_streak: u32 = 0;
|
let mut read_error_streak: u32 = 0;
|
||||||
|
let mut overflow_log_window_start: Option<Instant> = None;
|
||||||
|
let mut overflow_log_suppressed: u32 = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Apply any pending hardware retune before the next read.
|
// Apply any pending hardware retune before the next read.
|
||||||
@@ -228,7 +231,9 @@ fn iq_read_loop(
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
read_error_streak = read_error_streak.saturating_add(1);
|
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,
|
Ok(result) => result,
|
||||||
Err(recovery_err) => {
|
Err(recovery_err) => {
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
@@ -238,12 +243,39 @@ fn iq_read_loop(
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
tracing::warn!(
|
if is_overflow {
|
||||||
"IQ source read error: {}; retrying (streak={}, recovered={})",
|
let now = Instant::now();
|
||||||
e,
|
if overflow_log_window_start
|
||||||
read_error_streak,
|
.map(|ts| now.duration_since(ts) >= Duration::from_secs(3))
|
||||||
recovered
|
.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 {
|
let base_sleep_ms = if recovered {
|
||||||
block_duration_ms.max(20)
|
block_duration_ms.max(20)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -179,17 +179,22 @@ impl IqSource for RealIqSource {
|
|||||||
.map_err(|e| format!("Failed to set SDR gain: {}", e))
|
.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 err_lc = err.to_ascii_lowercase();
|
||||||
let is_overrun = err_lc.contains("overflow") || err_lc.contains("overrun");
|
let is_overrun = err_lc.contains("overflow") || err_lc.contains("overrun");
|
||||||
if !is_overrun {
|
if !is_overrun {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
// Overflow is often transient; avoid immediate stream restart churn.
|
||||||
tracing::warn!("SoapySDR RX overflow detected; restarting RX stream");
|
// Only restart after several consecutive read failures.
|
||||||
self.stream
|
if streak < 3 {
|
||||||
.deactivate(None)
|
return Ok(true);
|
||||||
.map_err(|e| format!("Failed to deactivate RX stream after overflow: {}", e))?;
|
}
|
||||||
|
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));
|
std::thread::sleep(std::time::Duration::from_millis(25));
|
||||||
self.stream
|
self.stream
|
||||||
.activate(None)
|
.activate(None)
|
||||||
|
|||||||
Reference in New Issue
Block a user