[fix](trx-client): preserve ft8 history on reload

Co-authored-by: OpenAI Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-03-04 19:45:17 +01:00
parent 8b174db04d
commit 0836091bbc
4 changed files with 62 additions and 29 deletions
+32 -21
View File
@@ -286,6 +286,8 @@ async fn async_init() -> DynResult<AppState> {
}));
// Audio streaming setup
let mut pending_audio_client = None;
let mut pending_audio_bridge = None;
if cfg.frontends.audio.enabled {
let (rx_audio_tx, _) = broadcast::channel::<Bytes>(256);
let (tx_audio_tx, tx_audio_rx) = mpsc::channel::<Bytes>(64);
@@ -304,7 +306,7 @@ async fn async_init() -> DynResult<AppState> {
let audio_rig_ports: HashMap<String, u16> = cfg.frontends.audio.rig_ports.clone();
let audio_shutdown_rx = shutdown_rx.clone();
task_handles.push(tokio::spawn(audio_client::run_audio_client(
pending_audio_client = Some(tokio::spawn(audio_client::run_audio_client(
remote_host,
cfg.frontends.audio.server_port,
audio_rig_ports,
@@ -318,26 +320,7 @@ async fn async_init() -> DynResult<AppState> {
)));
if cfg.frontends.audio.bridge.enabled {
info!("Audio bridge enabled (local virtual-device integration)");
task_handles.push(audio_bridge::spawn_audio_bridge(
cfg.frontends.audio.bridge.clone(),
frontend_runtime
.audio_rx
.as_ref()
.expect("audio rx must be set")
.clone(),
frontend_runtime
.audio_tx
.as_ref()
.expect("audio tx must be set")
.clone(),
frontend_runtime
.audio_info
.as_ref()
.expect("audio info must be set")
.clone(),
shutdown_rx.clone(),
));
pending_audio_bridge = Some(cfg.frontends.audio.bridge.clone());
}
} else {
info!("Audio disabled in config, decode will not be available");
@@ -404,6 +387,34 @@ async fn async_init() -> DynResult<AppState> {
)?;
}
// Start the audio connection only after frontends are running so decode
// subscribers can capture the server's initial history replay.
if let Some(handle) = pending_audio_client {
task_handles.push(handle);
}
if let Some(bridge_cfg) = pending_audio_bridge {
info!("Audio bridge enabled (local virtual-device integration)");
task_handles.push(audio_bridge::spawn_audio_bridge(
bridge_cfg,
frontend_runtime_ctx
.audio_rx
.as_ref()
.expect("audio rx must be set")
.clone(),
frontend_runtime_ctx
.audio_tx
.as_ref()
.expect("audio tx must be set")
.clone(),
frontend_runtime_ctx
.audio_info
.as_ref()
.expect("audio info must be set")
.clone(),
shutdown_rx.clone(),
));
}
Ok(AppState {
shutdown_tx,
task_handles,
@@ -3877,8 +3877,15 @@ function initAprsMap() {
function sizeAprsMapToViewport() {
const mapEl = document.getElementById("aprs-map");
if (!mapEl) return;
const mapRect = mapEl.getBoundingClientRect();
const stage = mapStageEl();
if (mapIsFullscreen() && stage) {
const stageHeight = stage.clientHeight || stage.getBoundingClientRect().height;
const target = Math.max(260, Math.floor(stageHeight));
mapEl.style.height = `${target}px`;
if (aprsMap) aprsMap.invalidateSize();
return;
}
const mapRect = mapEl.getBoundingClientRect();
const width = mapEl.clientWidth || mapRect.width;
const footer = document.querySelector(".footer");
let bottom = mapIsFullscreen() && stage
@@ -10,6 +10,16 @@ const FT8_PERIOD_SECONDS = 15;
let ft8FilterText = "";
let ft8MessageHistory = [];
function normalizeFt8DisplayFreqHz(freqHz) {
const rawHz = Number(freqHz);
if (!Number.isFinite(rawHz)) return null;
const baseHz = Number.isFinite(window.ft8BaseHz) ? Number(window.ft8BaseHz) : null;
if (Number.isFinite(baseHz) && baseHz > 0 && rawHz >= 0 && rawHz < 100000) {
return baseHz + rawHz;
}
return rawHz;
}
function fmtTime(tsMs) {
if (!tsMs) return "--:--:--";
return new Date(tsMs).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
@@ -33,7 +43,8 @@ function renderFt8Row(msg) {
row.dataset.storedFreqHz = Number.isFinite(msg.freq_hz) ? String(msg.freq_hz) : "";
const snr = Number.isFinite(msg.snr_db) ? msg.snr_db.toFixed(1) : "--";
const dt = Number.isFinite(msg.dt_s) ? msg.dt_s.toFixed(2) : "--";
const freq = Number.isFinite(msg.freq_hz) ? msg.freq_hz.toFixed(0) : "--";
const displayFreqHz = normalizeFt8DisplayFreqHz(msg.freq_hz);
const freq = Number.isFinite(displayFreqHz) ? displayFreqHz.toFixed(0) : "--";
const renderedMessage = renderFt8Message(rawMessage);
row.innerHTML = `<span class="ft8-time">${fmtTime(msg.ts_ms)}</span><span class="ft8-snr">${snr}</span><span class="ft8-dt">${dt}</span><span class="ft8-freq">${freq}</span><span class="ft8-msg">${renderedMessage}</span>`;
applyFt8FilterToRow(row);
@@ -51,8 +62,9 @@ function addFt8Message(msg) {
}
function ft8BarRfText(msg) {
if (!Number.isFinite(msg.freq_hz)) return null;
return `${msg.freq_hz.toFixed(0)} Hz`;
const displayFreqHz = normalizeFt8DisplayFreqHz(msg.freq_hz);
if (!Number.isFinite(displayFreqHz)) return null;
return `${displayFreqHz.toFixed(0)} Hz`;
}
function updateFt8Bar() {
@@ -173,8 +185,9 @@ function updateFt8RowRf(row) {
const freqEl = row.querySelector(".ft8-freq");
if (!freqEl) return;
const storedFreqHz = row.dataset.storedFreqHz ? Number(row.dataset.storedFreqHz) : NaN;
if (Number.isFinite(storedFreqHz)) {
freqEl.textContent = storedFreqHz.toFixed(0);
const displayFreqHz = normalizeFt8DisplayFreqHz(storedFreqHz);
if (Number.isFinite(displayFreqHz)) {
freqEl.textContent = displayFreqHz.toFixed(0);
} else {
freqEl.textContent = "--";
}
@@ -1132,12 +1132,14 @@ small { color: var(--text-muted); }
#map-stage:fullscreen,
#map-stage:-webkit-full-screen {
background: var(--bg);
padding: 0.75rem;
width: 100%;
height: 100%;
padding: 0;
box-sizing: border-box;
}
#map-stage:fullscreen #aprs-map,
#map-stage:-webkit-full-screen #aprs-map {
border-radius: 8px;
border-radius: 0;
}
.aprs-controls { display: flex; gap: 0.6rem; align-items: center; margin-bottom: 0.75rem; }
.aprs-summary {