[fix](trx-rs): fix LRPT pass detection status never updating during active decoding
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 <noreply@anthropic.com>
This commit is contained in:
@@ -29,8 +29,8 @@ use trx_core::audio::{
|
|||||||
AUDIO_MSG_HF_APRS_DECODE, AUDIO_MSG_HISTORY_COMPRESSED, AUDIO_MSG_RX_FRAME,
|
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_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_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_LRPT_IMAGE, AUDIO_MSG_LRPT_PROGRESS, AUDIO_MSG_VCHAN_REMOVE, AUDIO_MSG_VCHAN_SUB,
|
||||||
AUDIO_MSG_WSPR_DECODE,
|
AUDIO_MSG_VCHAN_UNSUB, AUDIO_MSG_VDES_DECODE, AUDIO_MSG_WSPR_DECODE,
|
||||||
};
|
};
|
||||||
use trx_core::decode::DecodedMessage;
|
use trx_core::decode::DecodedMessage;
|
||||||
use trx_frontend::VChanAudioCmd;
|
use trx_frontend::VChanAudioCmd;
|
||||||
@@ -567,7 +567,9 @@ async fn handle_single_rig_connection(
|
|||||||
| AUDIO_MSG_FT8_DECODE
|
| AUDIO_MSG_FT8_DECODE
|
||||||
| AUDIO_MSG_FT4_DECODE
|
| AUDIO_MSG_FT4_DECODE
|
||||||
| AUDIO_MSG_FT2_DECODE
|
| AUDIO_MSG_FT2_DECODE
|
||||||
| AUDIO_MSG_WSPR_DECODE,
|
| AUDIO_MSG_WSPR_DECODE
|
||||||
|
| AUDIO_MSG_LRPT_IMAGE
|
||||||
|
| AUDIO_MSG_LRPT_PROGRESS,
|
||||||
payload,
|
payload,
|
||||||
)) => {
|
)) => {
|
||||||
if let Ok(mut msg) = serde_json::from_slice::<DecodedMessage>(&payload) {
|
if let Ok(mut msg) = serde_json::from_slice::<DecodedMessage>(&payload) {
|
||||||
|
|||||||
@@ -523,6 +523,7 @@ async fn async_init() -> DynResult<AppState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DecodedMessage::LrptImage(_) => {}
|
DecodedMessage::LrptImage(_) => {}
|
||||||
|
DecodedMessage::LrptProgress(_) => {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9134,7 +9134,8 @@ function dispatchDecodeMessage(msg, skipStats) {
|
|||||||
if (msg.type === "ft2" && window.onServerFt2) window.onServerFt2(msg);
|
if (msg.type === "ft2" && window.onServerFt2) window.onServerFt2(msg);
|
||||||
if (msg.type === "wspr" && window.onServerWspr) window.onServerWspr(msg);
|
if (msg.type === "wspr" && window.onServerWspr) window.onServerWspr(msg);
|
||||||
if (msg.type === "lrpt_image" && window.onServerLrptImage) window.onServerLrptImage(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);
|
statsRecordDecode(msg.type, msg.rig_id || msg.remote || null);
|
||||||
scheduleStatsRender();
|
scheduleStatsRender();
|
||||||
}
|
}
|
||||||
@@ -9144,7 +9145,7 @@ function dispatchDecodeBatch(batch) {
|
|||||||
if (!Array.isArray(batch) || batch.length === 0) return;
|
if (!Array.isArray(batch) || batch.length === 0) return;
|
||||||
// Record statistics for every message in the batch regardless of dispatch path.
|
// Record statistics for every message in the batch regardless of dispatch path.
|
||||||
for (const msg of batch) {
|
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);
|
statsRecordDecode(msg.type, msg.rig_id || msg.remote || null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9231,7 +9232,7 @@ function loadDecodeHistoryOnMainThread(onReady, onError) {
|
|||||||
function restoreDecodeHistoryGroup(kind, messages) {
|
function restoreDecodeHistoryGroup(kind, messages) {
|
||||||
if (!Array.isArray(messages) || messages.length === 0) return;
|
if (!Array.isArray(messages) || messages.length === 0) return;
|
||||||
// Record statistics for restored history messages.
|
// Record statistics for restored history messages.
|
||||||
if (kind !== "lrpt_image") {
|
if (kind !== "lrpt_image" && kind !== "lrpt_progress") {
|
||||||
for (const msg of messages) {
|
for (const msg of messages) {
|
||||||
statsRecordDecode(kind, msg.rig_id || msg.remote || null, msg.ts_ms || undefined);
|
statsRecordDecode(kind, msg.rig_id || msg.remote || null, msg.ts_ms || undefined);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,13 @@ window.updateSatLiveState = function (update) {
|
|||||||
_lastSatLrptOn = lrptOn;
|
_lastSatLrptOn = lrptOn;
|
||||||
satDom.lrptState.textContent = lrptOn ? "Listening" : "Idle";
|
satDom.lrptState.textContent = lrptOn ? "Listening" : "Idle";
|
||||||
satDom.lrptState.className = "sat-live-value " + (lrptOn ? "sat-state-listening" : "sat-state-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 ────────────────────────────────────────────────
|
// ── 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) {
|
window.onServerLrptImage = function (msg) {
|
||||||
if (satDom.status) satDom.status.textContent = "Image received (Meteor LRPT)";
|
if (satDom.status) satDom.status.textContent = "Image received (Meteor LRPT)";
|
||||||
addSatImage(msg, "lrpt");
|
addSatImage(msg, "lrpt");
|
||||||
|
|||||||
@@ -585,6 +585,7 @@ pub fn start_decode_history_collector(context: Arc<FrontendRuntimeContext>) {
|
|||||||
DecodedMessage::Ft2(msg) => record_ft2(&context, msg),
|
DecodedMessage::Ft2(msg) => record_ft2(&context, msg),
|
||||||
DecodedMessage::Wspr(msg) => record_wspr(&context, msg),
|
DecodedMessage::Wspr(msg) => record_wspr(&context, msg),
|
||||||
DecodedMessage::LrptImage(_) => {}
|
DecodedMessage::LrptImage(_) => {}
|
||||||
|
DecodedMessage::LrptProgress(_) => {}
|
||||||
},
|
},
|
||||||
Err(broadcast::error::RecvError::Lagged(_)) => continue,
|
Err(broadcast::error::RecvError::Lagged(_)) => continue,
|
||||||
Err(broadcast::error::RecvError::Closed) => break,
|
Err(broadcast::error::RecvError::Closed) => break,
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ pub const AUDIO_MSG_FT4_DECODE: u8 = 0x14;
|
|||||||
pub const AUDIO_MSG_FT2_DECODE: u8 = 0x15;
|
pub const AUDIO_MSG_FT2_DECODE: u8 = 0x15;
|
||||||
/// Server → client: Meteor-M LRPT image complete (JSON `DecodedMessage::LrptImage`).
|
/// Server → client: Meteor-M LRPT image complete (JSON `DecodedMessage::LrptImage`).
|
||||||
pub const AUDIO_MSG_LRPT_IMAGE: u8 = 0x17;
|
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).
|
/// Maximum payload size for normal messages (1 MB).
|
||||||
const MAX_PAYLOAD_SIZE: u32 = 1_048_576;
|
const MAX_PAYLOAD_SIZE: u32 = 1_048_576;
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ pub enum DecodedMessage {
|
|||||||
Wspr(WsprMessage),
|
Wspr(WsprMessage),
|
||||||
#[serde(rename = "lrpt_image")]
|
#[serde(rename = "lrpt_image")]
|
||||||
LrptImage(LrptImage),
|
LrptImage(LrptImage),
|
||||||
|
#[serde(rename = "lrpt_progress")]
|
||||||
|
LrptProgress(LrptProgress),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DecodedMessage {
|
impl DecodedMessage {
|
||||||
@@ -43,6 +45,7 @@ impl DecodedMessage {
|
|||||||
Self::Ft8(m) | Self::Ft4(m) | Self::Ft2(m) => m.rig_id = Some(id),
|
Self::Ft8(m) | Self::Ft4(m) | Self::Ft2(m) => m.rig_id = Some(id),
|
||||||
Self::Wspr(m) => m.rig_id = Some(id),
|
Self::Wspr(m) => m.rig_id = Some(id),
|
||||||
Self::LrptImage(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::Ft8(m) | Self::Ft4(m) | Self::Ft2(m) => m.rig_id.as_deref(),
|
||||||
Self::Wspr(m) => m.rig_id.as_deref(),
|
Self::Wspr(m) => m.rig_id.as_deref(),
|
||||||
Self::LrptImage(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,
|
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<String>,
|
||||||
|
/// 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.
|
/// A completed Meteor-M LRPT satellite image, saved to disk as a PNG.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct LrptImage {
|
pub struct LrptImage {
|
||||||
|
|||||||
@@ -26,13 +26,15 @@ use trx_core::audio::{
|
|||||||
write_vchan_uuid_msg, AudioStreamInfo, AUDIO_MSG_AIS_DECODE, AUDIO_MSG_APRS_DECODE,
|
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_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_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_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_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_VCHAN_REMOVE, AUDIO_MSG_VCHAN_SUB, AUDIO_MSG_VCHAN_UNSUB, AUDIO_MSG_VDES_DECODE,
|
||||||
AUDIO_MSG_WSPR_DECODE,
|
AUDIO_MSG_WSPR_DECODE,
|
||||||
};
|
};
|
||||||
use trx_core::decode::{
|
use trx_core::decode::{
|
||||||
AisMessage, AprsPacket, CwEvent, DecodedMessage, Ft8Message, LrptImage, VdesMessage,
|
AisMessage, AprsPacket, CwEvent, DecodedMessage, Ft8Message, LrptImage, LrptProgress,
|
||||||
|
VdesMessage,
|
||||||
WsprMessage,
|
WsprMessage,
|
||||||
};
|
};
|
||||||
use trx_core::rig::state::{RigMode, RigState};
|
use trx_core::rig::state::{RigMode, RigState};
|
||||||
@@ -2445,6 +2447,12 @@ pub async fn run_lrpt_decoder(
|
|||||||
};
|
};
|
||||||
if new_mcus > 0 {
|
if new_mcus > 0 {
|
||||||
last_mcu_at = tokio::time::Instant::now();
|
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)) => {
|
Err(broadcast::error::RecvError::Lagged(n)) => {
|
||||||
@@ -3301,6 +3309,7 @@ async fn handle_audio_client(
|
|||||||
DecodedMessage::Wspr(_) => AUDIO_MSG_WSPR_DECODE,
|
DecodedMessage::Wspr(_) => AUDIO_MSG_WSPR_DECODE,
|
||||||
|
|
||||||
DecodedMessage::LrptImage(_) => AUDIO_MSG_LRPT_IMAGE,
|
DecodedMessage::LrptImage(_) => AUDIO_MSG_LRPT_IMAGE,
|
||||||
|
DecodedMessage::LrptProgress(_) => AUDIO_MSG_LRPT_PROGRESS,
|
||||||
};
|
};
|
||||||
if let Ok(json) = serde_json::to_vec(&msg) {
|
if let Ok(json) = serde_json::to_vec(&msg) {
|
||||||
if let Err(e) = write_audio_msg(&mut writer_for_rx, msg_type, &json).await {
|
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::Wspr(_) => AUDIO_MSG_WSPR_DECODE,
|
||||||
|
|
||||||
DecodedMessage::LrptImage(_) => AUDIO_MSG_LRPT_IMAGE,
|
DecodedMessage::LrptImage(_) => AUDIO_MSG_LRPT_IMAGE,
|
||||||
|
DecodedMessage::LrptProgress(_) => AUDIO_MSG_LRPT_PROGRESS,
|
||||||
};
|
};
|
||||||
if let Ok(json) = serde_json::to_vec(&msg) {
|
if let Ok(json) = serde_json::to_vec(&msg) {
|
||||||
if let Err(e) = write_audio_msg(&mut writer_for_rx, msg_type, &json).await {
|
if let Err(e) = write_audio_msg(&mut writer_for_rx, msg_type, &json).await {
|
||||||
|
|||||||
Reference in New Issue
Block a user