From 1fe0b75e20df684010c07ca282dec49dcc9c0db3 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 31 Mar 2026 14:17:56 +0000 Subject: [PATCH] [fix](trx-rs): fix LRPT pass detection status never updating during active decoding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The #sat-status element was stuck on "Waiting for satellite pass" because: 1. The client audio handler (audio_client.rs) did not include AUDIO_MSG_LRPT_IMAGE in its message type match, so LRPT image messages from the server were silently dropped and never reached the frontend. 2. No progress was reported during active LRPT decoding — the only status update happened when a complete image was finalized, which could take the entire pass. 3. The sat-status text was never updated when the decoder was enabled/disabled, leaving it permanently at the HTML default text. Changes: - Add DecodedMessage::LrptProgress variant for live MCU progress reporting - Send LRPT progress updates from the decoder task when new MCUs are decoded - Add AUDIO_MSG_LRPT_IMAGE and AUDIO_MSG_LRPT_PROGRESS to client audio handler - Update sat-status text when decoder state changes (enabled/disabled) - Handle lrpt_progress messages in the frontend to show "Receiving — N MCU rows" https://claude.ai/code/session_017knbD7dr6hJGAWR6pModL7 Signed-off-by: Claude --- src/trx-client/src/audio_client.rs | 8 +++++--- src/trx-client/src/main.rs | 1 + .../trx-frontend-http/assets/web/app.js | 7 ++++--- .../trx-frontend-http/assets/web/plugins/sat.js | 13 +++++++++++++ .../trx-frontend/trx-frontend-http/src/audio.rs | 1 + src/trx-core/src/audio.rs | 2 ++ src/trx-core/src/decode.rs | 13 +++++++++++++ src/trx-server/src/audio.rs | 12 +++++++++++- 8 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/trx-client/src/audio_client.rs b/src/trx-client/src/audio_client.rs index 3150a8b..2ce6f89 100644 --- a/src/trx-client/src/audio_client.rs +++ b/src/trx-client/src/audio_client.rs @@ -29,8 +29,8 @@ use trx_core::audio::{ AUDIO_MSG_HF_APRS_DECODE, AUDIO_MSG_HISTORY_COMPRESSED, AUDIO_MSG_RX_FRAME, AUDIO_MSG_RX_FRAME_CH, AUDIO_MSG_STREAM_INFO, AUDIO_MSG_TX_FRAME, AUDIO_MSG_VCHAN_ALLOCATED, AUDIO_MSG_VCHAN_BW, AUDIO_MSG_VCHAN_DESTROYED, AUDIO_MSG_VCHAN_FREQ, AUDIO_MSG_VCHAN_MODE, - AUDIO_MSG_VCHAN_REMOVE, AUDIO_MSG_VCHAN_SUB, AUDIO_MSG_VCHAN_UNSUB, AUDIO_MSG_VDES_DECODE, - AUDIO_MSG_WSPR_DECODE, + AUDIO_MSG_LRPT_IMAGE, AUDIO_MSG_LRPT_PROGRESS, AUDIO_MSG_VCHAN_REMOVE, AUDIO_MSG_VCHAN_SUB, + AUDIO_MSG_VCHAN_UNSUB, AUDIO_MSG_VDES_DECODE, AUDIO_MSG_WSPR_DECODE, }; use trx_core::decode::DecodedMessage; use trx_frontend::VChanAudioCmd; @@ -567,7 +567,9 @@ async fn handle_single_rig_connection( | AUDIO_MSG_FT8_DECODE | AUDIO_MSG_FT4_DECODE | AUDIO_MSG_FT2_DECODE - | AUDIO_MSG_WSPR_DECODE, + | AUDIO_MSG_WSPR_DECODE + | AUDIO_MSG_LRPT_IMAGE + | AUDIO_MSG_LRPT_PROGRESS, payload, )) => { if let Ok(mut msg) = serde_json::from_slice::(&payload) { diff --git a/src/trx-client/src/main.rs b/src/trx-client/src/main.rs index 8fa34a2..03fd85f 100644 --- a/src/trx-client/src/main.rs +++ b/src/trx-client/src/main.rs @@ -523,6 +523,7 @@ async fn async_init() -> DynResult { } } DecodedMessage::LrptImage(_) => {} + DecodedMessage::LrptProgress(_) => {} } }); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js index 23240f0..760a25c 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js @@ -9134,7 +9134,8 @@ function dispatchDecodeMessage(msg, skipStats) { if (msg.type === "ft2" && window.onServerFt2) window.onServerFt2(msg); if (msg.type === "wspr" && window.onServerWspr) window.onServerWspr(msg); if (msg.type === "lrpt_image" && window.onServerLrptImage) window.onServerLrptImage(msg); - if (!skipStats && msg.type && msg.type !== "lrpt_image") { + if (msg.type === "lrpt_progress" && window.onServerLrptProgress) window.onServerLrptProgress(msg); + if (!skipStats && msg.type && msg.type !== "lrpt_image" && msg.type !== "lrpt_progress") { statsRecordDecode(msg.type, msg.rig_id || msg.remote || null); scheduleStatsRender(); } @@ -9144,7 +9145,7 @@ function dispatchDecodeBatch(batch) { if (!Array.isArray(batch) || batch.length === 0) return; // Record statistics for every message in the batch regardless of dispatch path. for (const msg of batch) { - if (msg.type && msg.type !== "lrpt_image") { + if (msg.type && msg.type !== "lrpt_image" && msg.type !== "lrpt_progress") { statsRecordDecode(msg.type, msg.rig_id || msg.remote || null); } } @@ -9231,7 +9232,7 @@ function loadDecodeHistoryOnMainThread(onReady, onError) { function restoreDecodeHistoryGroup(kind, messages) { if (!Array.isArray(messages) || messages.length === 0) return; // Record statistics for restored history messages. - if (kind !== "lrpt_image") { + if (kind !== "lrpt_image" && kind !== "lrpt_progress") { for (const msg of messages) { statsRecordDecode(kind, msg.rig_id || msg.remote || null, msg.ts_ms || undefined); } diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/sat.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/sat.js index eb53f7a..467661b 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/sat.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/sat.js @@ -91,6 +91,13 @@ window.updateSatLiveState = function (update) { _lastSatLrptOn = lrptOn; satDom.lrptState.textContent = lrptOn ? "Listening" : "Idle"; satDom.lrptState.className = "sat-live-value " + (lrptOn ? "sat-state-listening" : "sat-state-idle"); + if (satDom.status) { + if (lrptOn) { + satDom.status.textContent = "Decoder active \u2014 waiting for signal"; + } else { + satDom.status.textContent = "Decoder idle"; + } + } } }; @@ -233,6 +240,12 @@ function addSatImage(img, decoder) { } // ── Server callbacks ──────────────────────────────────────────────── +window.onServerLrptProgress = function (msg) { + if (satDom.status && msg.mcu_count > 0) { + satDom.status.textContent = "Receiving \u2014 " + msg.mcu_count + " MCU rows decoded"; + } +}; + window.onServerLrptImage = function (msg) { if (satDom.status) satDom.status.textContent = "Image received (Meteor LRPT)"; addSatImage(msg, "lrpt"); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/src/audio.rs b/src/trx-client/trx-frontend/trx-frontend-http/src/audio.rs index 6e438a2..181536c 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/src/audio.rs +++ b/src/trx-client/trx-frontend/trx-frontend-http/src/audio.rs @@ -585,6 +585,7 @@ pub fn start_decode_history_collector(context: Arc) { DecodedMessage::Ft2(msg) => record_ft2(&context, msg), DecodedMessage::Wspr(msg) => record_wspr(&context, msg), DecodedMessage::LrptImage(_) => {} + DecodedMessage::LrptProgress(_) => {} }, Err(broadcast::error::RecvError::Lagged(_)) => continue, Err(broadcast::error::RecvError::Closed) => break, diff --git a/src/trx-core/src/audio.rs b/src/trx-core/src/audio.rs index 6bc0505..1eb100b 100644 --- a/src/trx-core/src/audio.rs +++ b/src/trx-core/src/audio.rs @@ -68,6 +68,8 @@ pub const AUDIO_MSG_FT4_DECODE: u8 = 0x14; pub const AUDIO_MSG_FT2_DECODE: u8 = 0x15; /// Server → client: Meteor-M LRPT image complete (JSON `DecodedMessage::LrptImage`). pub const AUDIO_MSG_LRPT_IMAGE: u8 = 0x17; +/// Server → client: LRPT decode progress update (JSON `DecodedMessage::LrptProgress`). +pub const AUDIO_MSG_LRPT_PROGRESS: u8 = 0x18; /// Maximum payload size for normal messages (1 MB). const MAX_PAYLOAD_SIZE: u32 = 1_048_576; diff --git a/src/trx-core/src/decode.rs b/src/trx-core/src/decode.rs index d6b7134..2fbc10c 100644 --- a/src/trx-core/src/decode.rs +++ b/src/trx-core/src/decode.rs @@ -30,6 +30,8 @@ pub enum DecodedMessage { Wspr(WsprMessage), #[serde(rename = "lrpt_image")] LrptImage(LrptImage), + #[serde(rename = "lrpt_progress")] + LrptProgress(LrptProgress), } impl DecodedMessage { @@ -43,6 +45,7 @@ impl DecodedMessage { Self::Ft8(m) | Self::Ft4(m) | Self::Ft2(m) => m.rig_id = Some(id), Self::Wspr(m) => m.rig_id = Some(id), Self::LrptImage(m) => m.rig_id = Some(id), + Self::LrptProgress(m) => m.rig_id = Some(id), } } @@ -56,6 +59,7 @@ impl DecodedMessage { Self::Ft8(m) | Self::Ft4(m) | Self::Ft2(m) => m.rig_id.as_deref(), Self::Wspr(m) => m.rig_id.as_deref(), Self::LrptImage(m) => m.rig_id.as_deref(), + Self::LrptProgress(m) => m.rig_id.as_deref(), } } } @@ -223,6 +227,15 @@ pub struct WsprMessage { pub message: String, } +/// Live LRPT decode progress update, sent periodically during active decoding. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LrptProgress { + #[serde(skip_serializing_if = "Option::is_none")] + pub rig_id: Option, + /// Number of MCU rows decoded so far in this pass. + pub mcu_count: u32, +} + /// A completed Meteor-M LRPT satellite image, saved to disk as a PNG. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LrptImage { diff --git a/src/trx-server/src/audio.rs b/src/trx-server/src/audio.rs index 3ed3f6b..823470f 100644 --- a/src/trx-server/src/audio.rs +++ b/src/trx-server/src/audio.rs @@ -26,13 +26,15 @@ use trx_core::audio::{ write_vchan_uuid_msg, AudioStreamInfo, AUDIO_MSG_AIS_DECODE, AUDIO_MSG_APRS_DECODE, AUDIO_MSG_CW_DECODE, AUDIO_MSG_FT2_DECODE, AUDIO_MSG_FT4_DECODE, AUDIO_MSG_FT8_DECODE, AUDIO_MSG_HF_APRS_DECODE, AUDIO_MSG_HISTORY_COMPRESSED, AUDIO_MSG_LRPT_IMAGE, + AUDIO_MSG_LRPT_PROGRESS, AUDIO_MSG_RX_FRAME, AUDIO_MSG_STREAM_INFO, AUDIO_MSG_TX_FRAME, AUDIO_MSG_VCHAN_ALLOCATED, AUDIO_MSG_VCHAN_BW, AUDIO_MSG_VCHAN_DESTROYED, AUDIO_MSG_VCHAN_FREQ, AUDIO_MSG_VCHAN_MODE, AUDIO_MSG_VCHAN_REMOVE, AUDIO_MSG_VCHAN_SUB, AUDIO_MSG_VCHAN_UNSUB, AUDIO_MSG_VDES_DECODE, AUDIO_MSG_WSPR_DECODE, }; use trx_core::decode::{ - AisMessage, AprsPacket, CwEvent, DecodedMessage, Ft8Message, LrptImage, VdesMessage, + AisMessage, AprsPacket, CwEvent, DecodedMessage, Ft8Message, LrptImage, LrptProgress, + VdesMessage, WsprMessage, }; use trx_core::rig::state::{RigMode, RigState}; @@ -2445,6 +2447,12 @@ pub async fn run_lrpt_decoder( }; if new_mcus > 0 { last_mcu_at = tokio::time::Instant::now(); + let _ = decode_tx.send(DecodedMessage::LrptProgress( + LrptProgress { + rig_id: None, + mcu_count: decoder.mcu_count(), + }, + )); } } Err(broadcast::error::RecvError::Lagged(n)) => { @@ -3301,6 +3309,7 @@ async fn handle_audio_client( DecodedMessage::Wspr(_) => AUDIO_MSG_WSPR_DECODE, DecodedMessage::LrptImage(_) => AUDIO_MSG_LRPT_IMAGE, + DecodedMessage::LrptProgress(_) => AUDIO_MSG_LRPT_PROGRESS, }; if let Ok(json) = serde_json::to_vec(&msg) { if let Err(e) = write_audio_msg(&mut writer_for_rx, msg_type, &json).await { @@ -3330,6 +3339,7 @@ async fn handle_audio_client( DecodedMessage::Wspr(_) => AUDIO_MSG_WSPR_DECODE, DecodedMessage::LrptImage(_) => AUDIO_MSG_LRPT_IMAGE, + DecodedMessage::LrptProgress(_) => AUDIO_MSG_LRPT_PROGRESS, }; if let Ok(json) = serde_json::to_vec(&msg) { if let Err(e) = write_audio_msg(&mut writer_for_rx, msg_type, &json).await {