diff --git a/src/decoders/trx-vdes/src/lib.rs b/src/decoders/trx-vdes/src/lib.rs index ed14666..19b9918 100644 --- a/src/decoders/trx-vdes/src/lib.rs +++ b/src/decoders/trx-vdes/src/lib.rs @@ -33,6 +33,8 @@ const TER_MCS1_100_FEC_OUTPUT_BITS: usize = 1_872; const TER_MCS1_100_FEC_TAIL_BITS: usize = 10; const TER_MCS1_100_SYNC_BITS: &[u8; TER_MCS1_100_SYNC_SYMBOLS] = b"111111001101010000011001010"; const PI4_QPSK_DIBITS: [u8; 4] = [0b00, 0b01, 0b11, 0b10]; +const MIN_SYNC_CANDIDATE_SCORE: f32 = 0.20; +const MIN_SYNC_PARSE_SCORE: f32 = 0.50; #[derive(Debug, Clone)] pub struct VdesDecoder { @@ -117,14 +119,32 @@ impl VdesDecoder { } let framed = extract_candidate_frame(&symbols)?; - let link_id = decode_link_id_from_symbols(&framed.symbols); + let rms = burst_rms(&samples); + let mode = classify_vdes_burst(framed.symbols.len()); let payload_symbols = framed.payload_symbols(); let deinterleaved = deinterleave_100khz_frame(payload_symbols); + if framed.sync_score < MIN_SYNC_PARSE_SCORE { + return Some(build_unsynced_message( + channel, + &framed, + &mode, + rms, + &deinterleaved, + )); + } + + let link_id = decode_link_id_from_symbols(&framed.symbols); let (fec_input_symbols, fec_tail_symbols) = split_fec_frame(&deinterleaved); let coded_bits = dibits_to_bits(fec_input_symbols); let decoded_bits = viterbi_decode_rate_half(&coded_bits); if decoded_bits.is_empty() { - return None; + return Some(build_unsynced_message( + channel, + &framed, + &mode, + rms, + &deinterleaved, + )); } let parsed = parse_vdes_payload(&decoded_bits); let payload_bits = if parsed.payload_bits.is_empty() { @@ -133,8 +153,6 @@ impl VdesDecoder { parsed.payload_bits.as_slice() }; let raw_bytes = pack_bits_msb(payload_bits); - let rms = burst_rms(&samples); - let mode = classify_vdes_burst(framed.symbols.len()); let link_text = link_id .map(|value| format!("LID {}", value)) .unwrap_or_else(|| "LID ?".to_string()); @@ -308,7 +326,7 @@ fn extract_candidate_frame(symbols: &[u8]) -> Option { } } } - if best_score <= 0.5 { + if best_score <= MIN_SYNC_CANDIDATE_SCORE { return None; } @@ -327,6 +345,53 @@ fn extract_candidate_frame(symbols: &[u8]) -> Option { }) } +fn build_unsynced_message( + channel: &str, + framed: &FrameSlice, + mode: &BurstMode<'_>, + rms: f32, + deinterleaved: &[u8], +) -> VdesMessage { + let raw_bytes = pack_dibits_msb(deinterleaved); + let sync_pct = framed.sync_score * 100.0; + VdesMessage { + ts_ms: None, + channel: channel.to_string(), + message_type: mode.message_type, + repeat: 0, + mmsi: 0, + crc_ok: false, + bit_len: deinterleaved.len() * 2, + raw_bytes, + lat: None, + lon: None, + sog_knots: None, + cog_deg: None, + heading_deg: None, + nav_status: None, + vessel_name: Some(format!("Unsynced {} sym", framed.symbols.len())), + callsign: Some(format!("{} raw @{}", mode.label, framed.start_offset)), + destination: Some(format!( + "Weak sync {:.0}% ({}) · RMS {:.2} · raw symbol dump", + sync_pct, framed.sync_errors, rms + )), + message_label: Some("Unsynced".to_string()), + session_id: None, + source_id: None, + destination_id: None, + data_count: None, + asm_identifier: None, + ack_nack_mask: None, + channel_quality: None, + payload_preview: None, + link_id: None, + sync_score: Some(framed.sync_score), + sync_errors: Some(framed.sync_errors), + phase_rotation: Some(framed.phase_rotation), + fec_state: Some("Sync below parse threshold".to_string()), + } +} + fn syncword_score(symbols: &[u8], rotation: u8) -> (f32, u8) { if symbols.len() < TER_MCS1_100_SYNC_SYMBOLS { return (0.0, u8::MAX); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/ft8.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/ft8.js index f721966..64dc848 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/ft8.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/ft8.js @@ -34,10 +34,7 @@ function renderFt8Row(msg) { const rfHz = Number.isFinite(msg.freq_hz) && Number.isFinite(baseHz) ? (baseHz + msg.freq_hz) : null; const freq = Number.isFinite(rfHz) ? rfHz.toFixed(0) : "--"; const renderedMessage = renderFt8Message(rawMessage); - const receiverHtml = msg.receiver - ? `${msg.receiver.label}` - : ""; - row.innerHTML = `${fmtTime(msg.ts_ms)}${receiverHtml}${snr}${dt}${freq}${renderedMessage}`; + row.innerHTML = `${fmtTime(msg.ts_ms)}${snr}${dt}${freq}${renderedMessage}`; applyFt8FilterToRow(row); return row; } diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/wspr.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/wspr.js index c25750b..2bbea28 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/wspr.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/wspr.js @@ -34,10 +34,7 @@ function renderWsprRow(msg) { const freq = Number.isFinite(rfHz) ? rfHz.toFixed(0) : "--"; const message = (msg.message || "").toString(); row.dataset.message = message.toUpperCase(); - const receiverHtml = msg.receiver - ? `${msg.receiver.label}` - : ""; - row.innerHTML = `${fmtWsprTime(msg.ts_ms)}${receiverHtml}${snr}${dt}${freq}${escapeWsprHtml(message)}`; + row.innerHTML = `${fmtWsprTime(msg.ts_ms)}${snr}${dt}${freq}${escapeWsprHtml(message)}`; applyWsprFilterToRow(row); return row; } diff --git a/src/trx-core/src/rig/state.rs b/src/trx-core/src/rig/state.rs index 5f0ddbc..c311fd4 100644 --- a/src/trx-core/src/rig/state.rs +++ b/src/trx-core/src/rig/state.rs @@ -72,6 +72,7 @@ pub enum RigMode { FM, AIS, VDES, + MARINE, DIG, PKT, Other(String), diff --git a/src/trx-protocol/src/codec.rs b/src/trx-protocol/src/codec.rs index 30cf1ad..72c1c15 100644 --- a/src/trx-protocol/src/codec.rs +++ b/src/trx-protocol/src/codec.rs @@ -24,6 +24,7 @@ pub fn parse_mode(s: &str) -> RigMode { "WFM" => RigMode::WFM, "AIS" => RigMode::AIS, "VDES" => RigMode::VDES, + "MARINE" => RigMode::MARINE, "DIG" | "DIGI" => RigMode::DIG, "PKT" | "PACKET" => RigMode::PKT, other => RigMode::Other(other.to_string()), @@ -45,6 +46,7 @@ pub fn mode_to_string(mode: &RigMode) -> String { RigMode::WFM => "WFM".to_string(), RigMode::AIS => "AIS".to_string(), RigMode::VDES => "VDES".to_string(), + RigMode::MARINE => "MARINE".to_string(), RigMode::DIG => "DIG".to_string(), RigMode::PKT => "PKT".to_string(), RigMode::Other(s) => s.clone(), diff --git a/src/trx-server/src/main.rs b/src/trx-server/src/main.rs index 01bd41a..94b8d2e 100644 --- a/src/trx-server/src/main.rs +++ b/src/trx-server/src/main.rs @@ -245,7 +245,7 @@ fn default_audio_bandwidth_for_mode(mode: &trx_core::rig::state::RigMode) -> u32 RigMode::FM => 12_500, RigMode::WFM => 180_000, RigMode::AIS => 25_000, - RigMode::VDES => 100_000, + RigMode::VDES | RigMode::MARINE => 100_000, RigMode::Other(_) => 3_000, } } @@ -268,6 +268,7 @@ fn parse_rig_mode( "FM" => RigMode::FM, "AIS" => RigMode::AIS, "VDES" => RigMode::VDES, + "MARINE" => RigMode::MARINE, "DIG" => RigMode::DIG, "PKT" => RigMode::PKT, _ => initial_mode.clone(), diff --git a/src/trx-server/src/rig_task.rs b/src/trx-server/src/rig_task.rs index 339b6ab..7931993 100644 --- a/src/trx-server/src/rig_task.rs +++ b/src/trx-server/src/rig_task.rs @@ -773,7 +773,9 @@ fn map_signal_strength(mode: &RigMode, raw: u8) -> i32 { // FT-817 returns 0-15 for signal strength // Map to approximate dBm / S-units match mode { - RigMode::FM | RigMode::WFM | RigMode::AIS | RigMode::VDES => -120 + (raw as i32 * 6), + RigMode::FM | RigMode::WFM | RigMode::AIS | RigMode::VDES | RigMode::MARINE => { + -120 + (raw as i32 * 6) + } _ => -127 + (raw as i32 * 6), } } diff --git a/src/trx-server/trx-backend/trx-backend-ft450d/src/lib.rs b/src/trx-server/trx-backend/trx-backend-ft450d/src/lib.rs index e7d612e..02009c6 100644 --- a/src/trx-server/trx-backend/trx-backend-ft450d/src/lib.rs +++ b/src/trx-server/trx-backend/trx-backend-ft450d/src/lib.rs @@ -513,7 +513,7 @@ fn encode_mode(mode: &RigMode) -> DynResult { RigMode::USB => Ok('2'), RigMode::CW => Ok('3'), RigMode::FM => Ok('4'), - RigMode::AIS | RigMode::VDES => Ok('4'), + RigMode::AIS | RigMode::VDES | RigMode::MARINE => Ok('4'), RigMode::AM => Ok('5'), RigMode::DIG => Ok('6'), RigMode::CWR => Ok('7'), diff --git a/src/trx-server/trx-backend/trx-backend-ft817/src/lib.rs b/src/trx-server/trx-backend/trx-backend-ft817/src/lib.rs index 74d9cdd..487d62a 100644 --- a/src/trx-server/trx-backend/trx-backend-ft817/src/lib.rs +++ b/src/trx-server/trx-backend/trx-backend-ft817/src/lib.rs @@ -590,7 +590,7 @@ fn encode_mode(mode: &RigMode) -> u8 { RigMode::AM => 0x04, RigMode::WFM => 0x06, RigMode::FM => 0x08, - RigMode::AIS | RigMode::VDES => 0x08, + RigMode::AIS | RigMode::VDES | RigMode::MARINE => 0x08, RigMode::DIG => 0x0A, RigMode::PKT => 0x0C, RigMode::Other(_) => 0x00, diff --git a/src/trx-server/trx-backend/trx-backend-soapysdr/src/demod.rs b/src/trx-server/trx-backend/trx-backend-soapysdr/src/demod.rs index 1aeb3c4..d6796fd 100644 --- a/src/trx-server/trx-backend/trx-backend-soapysdr/src/demod.rs +++ b/src/trx-server/trx-backend/trx-backend-soapysdr/src/demod.rs @@ -156,7 +156,7 @@ impl Demodulator { RigMode::AM => Self::Am, RigMode::FM => Self::Fm, RigMode::WFM => Self::Wfm, - RigMode::AIS | RigMode::VDES => Self::Fm, + RigMode::AIS | RigMode::VDES | RigMode::MARINE => Self::Fm, RigMode::CW | RigMode::CWR => Self::Cw, RigMode::DIG => Self::Passthrough, // VHF/UHF packet radio (APRS, AX.25) is FM-encoded AFSK. diff --git a/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp/channel.rs b/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp/channel.rs index 4a6ba89..21af42f 100644 --- a/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp/channel.rs +++ b/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp/channel.rs @@ -45,7 +45,7 @@ fn default_bandwidth_for_mode(mode: &RigMode) -> u32 { RigMode::FM => 12_500, RigMode::WFM => 180_000, RigMode::AIS => 25_000, - RigMode::VDES => 100_000, + RigMode::VDES | RigMode::MARINE => 100_000, RigMode::Other(_) => 3_000, } } diff --git a/src/trx-server/trx-backend/trx-backend-soapysdr/src/lib.rs b/src/trx-server/trx-backend/trx-backend-soapysdr/src/lib.rs index e5fe5cd..dc771fc 100644 --- a/src/trx-server/trx-backend/trx-backend-soapysdr/src/lib.rs +++ b/src/trx-server/trx-backend/trx-backend-soapysdr/src/lib.rs @@ -58,7 +58,7 @@ impl SoapySdrRig { match mode { RigMode::LSB | RigMode::USB | RigMode::DIG => 3_000, RigMode::PKT | RigMode::AIS => 25_000, - RigMode::VDES => 100_000, + RigMode::VDES | RigMode::MARINE => 100_000, RigMode::CW | RigMode::CWR => 500, RigMode::AM => 9_000, RigMode::FM => 12_500,