[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:
+32
-21
@@ -286,6 +286,8 @@ async fn async_init() -> DynResult<AppState> {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// Audio streaming setup
|
// Audio streaming setup
|
||||||
|
let mut pending_audio_client = None;
|
||||||
|
let mut pending_audio_bridge = None;
|
||||||
if cfg.frontends.audio.enabled {
|
if cfg.frontends.audio.enabled {
|
||||||
let (rx_audio_tx, _) = broadcast::channel::<Bytes>(256);
|
let (rx_audio_tx, _) = broadcast::channel::<Bytes>(256);
|
||||||
let (tx_audio_tx, tx_audio_rx) = mpsc::channel::<Bytes>(64);
|
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_rig_ports: HashMap<String, u16> = cfg.frontends.audio.rig_ports.clone();
|
||||||
let audio_shutdown_rx = shutdown_rx.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,
|
remote_host,
|
||||||
cfg.frontends.audio.server_port,
|
cfg.frontends.audio.server_port,
|
||||||
audio_rig_ports,
|
audio_rig_ports,
|
||||||
@@ -318,26 +320,7 @@ async fn async_init() -> DynResult<AppState> {
|
|||||||
)));
|
)));
|
||||||
|
|
||||||
if cfg.frontends.audio.bridge.enabled {
|
if cfg.frontends.audio.bridge.enabled {
|
||||||
info!("Audio bridge enabled (local virtual-device integration)");
|
pending_audio_bridge = Some(cfg.frontends.audio.bridge.clone());
|
||||||
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(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
info!("Audio disabled in config, decode will not be available");
|
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 {
|
Ok(AppState {
|
||||||
shutdown_tx,
|
shutdown_tx,
|
||||||
task_handles,
|
task_handles,
|
||||||
|
|||||||
@@ -3877,8 +3877,15 @@ function initAprsMap() {
|
|||||||
function sizeAprsMapToViewport() {
|
function sizeAprsMapToViewport() {
|
||||||
const mapEl = document.getElementById("aprs-map");
|
const mapEl = document.getElementById("aprs-map");
|
||||||
if (!mapEl) return;
|
if (!mapEl) return;
|
||||||
const mapRect = mapEl.getBoundingClientRect();
|
|
||||||
const stage = mapStageEl();
|
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 width = mapEl.clientWidth || mapRect.width;
|
||||||
const footer = document.querySelector(".footer");
|
const footer = document.querySelector(".footer");
|
||||||
let bottom = mapIsFullscreen() && stage
|
let bottom = mapIsFullscreen() && stage
|
||||||
|
|||||||
@@ -10,6 +10,16 @@ const FT8_PERIOD_SECONDS = 15;
|
|||||||
let ft8FilterText = "";
|
let ft8FilterText = "";
|
||||||
let ft8MessageHistory = [];
|
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) {
|
function fmtTime(tsMs) {
|
||||||
if (!tsMs) return "--:--:--";
|
if (!tsMs) return "--:--:--";
|
||||||
return new Date(tsMs).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
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) : "";
|
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 snr = Number.isFinite(msg.snr_db) ? msg.snr_db.toFixed(1) : "--";
|
||||||
const dt = Number.isFinite(msg.dt_s) ? msg.dt_s.toFixed(2) : "--";
|
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);
|
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>`;
|
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);
|
applyFt8FilterToRow(row);
|
||||||
@@ -51,8 +62,9 @@ function addFt8Message(msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ft8BarRfText(msg) {
|
function ft8BarRfText(msg) {
|
||||||
if (!Number.isFinite(msg.freq_hz)) return null;
|
const displayFreqHz = normalizeFt8DisplayFreqHz(msg.freq_hz);
|
||||||
return `${msg.freq_hz.toFixed(0)} Hz`;
|
if (!Number.isFinite(displayFreqHz)) return null;
|
||||||
|
return `${displayFreqHz.toFixed(0)} Hz`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFt8Bar() {
|
function updateFt8Bar() {
|
||||||
@@ -173,8 +185,9 @@ function updateFt8RowRf(row) {
|
|||||||
const freqEl = row.querySelector(".ft8-freq");
|
const freqEl = row.querySelector(".ft8-freq");
|
||||||
if (!freqEl) return;
|
if (!freqEl) return;
|
||||||
const storedFreqHz = row.dataset.storedFreqHz ? Number(row.dataset.storedFreqHz) : NaN;
|
const storedFreqHz = row.dataset.storedFreqHz ? Number(row.dataset.storedFreqHz) : NaN;
|
||||||
if (Number.isFinite(storedFreqHz)) {
|
const displayFreqHz = normalizeFt8DisplayFreqHz(storedFreqHz);
|
||||||
freqEl.textContent = storedFreqHz.toFixed(0);
|
if (Number.isFinite(displayFreqHz)) {
|
||||||
|
freqEl.textContent = displayFreqHz.toFixed(0);
|
||||||
} else {
|
} else {
|
||||||
freqEl.textContent = "--";
|
freqEl.textContent = "--";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1132,12 +1132,14 @@ small { color: var(--text-muted); }
|
|||||||
#map-stage:fullscreen,
|
#map-stage:fullscreen,
|
||||||
#map-stage:-webkit-full-screen {
|
#map-stage:-webkit-full-screen {
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
padding: 0.75rem;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
#map-stage:fullscreen #aprs-map,
|
#map-stage:fullscreen #aprs-map,
|
||||||
#map-stage:-webkit-full-screen #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-controls { display: flex; gap: 0.6rem; align-items: center; margin-bottom: 0.75rem; }
|
||||||
.aprs-summary {
|
.aprs-summary {
|
||||||
|
|||||||
Reference in New Issue
Block a user