92423f1e02
Add a new trx-vdes decoder path alongside AIS, wire VDES through the server/frontend decode pipeline, and fix the web map so AIS vessel symbols load correctly and the TRX receiver marker appears when location data arrives. Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Stan Grams <sjg@haxx.space>
149 lines
4.5 KiB
Rust
149 lines
4.5 KiB
Rust
// SPDX-FileCopyrightText: 2025 Stanislaw Grams <stanislawgrams@gmail.com>
|
|
//
|
|
// SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
//! Shared types for server-side decoded messages (APRS, AIS, CW).
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// A decoded message from the server-side decoders.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(tag = "type")]
|
|
pub enum DecodedMessage {
|
|
#[serde(rename = "ais")]
|
|
Ais(AisMessage),
|
|
#[serde(rename = "vdes")]
|
|
Vdes(VdesMessage),
|
|
#[serde(rename = "aprs")]
|
|
Aprs(AprsPacket),
|
|
#[serde(rename = "cw")]
|
|
Cw(CwEvent),
|
|
#[serde(rename = "ft8")]
|
|
Ft8(Ft8Message),
|
|
#[serde(rename = "wspr")]
|
|
Wspr(WsprMessage),
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AisMessage {
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub ts_ms: Option<i64>,
|
|
pub channel: String,
|
|
pub message_type: u8,
|
|
pub repeat: u8,
|
|
pub mmsi: u32,
|
|
pub crc_ok: bool,
|
|
pub bit_len: usize,
|
|
pub raw_bytes: Vec<u8>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub lat: Option<f64>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub lon: Option<f64>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub sog_knots: Option<f32>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub cog_deg: Option<f32>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub heading_deg: Option<u16>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub nav_status: Option<u8>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub vessel_name: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub callsign: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub destination: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct VdesMessage {
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub ts_ms: Option<i64>,
|
|
pub channel: String,
|
|
pub message_type: u8,
|
|
pub repeat: u8,
|
|
pub mmsi: u32,
|
|
pub crc_ok: bool,
|
|
pub bit_len: usize,
|
|
pub raw_bytes: Vec<u8>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub lat: Option<f64>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub lon: Option<f64>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub sog_knots: Option<f32>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub cog_deg: Option<f32>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub heading_deg: Option<u16>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub nav_status: Option<u8>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub vessel_name: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub callsign: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub destination: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AprsPacket {
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub ts_ms: Option<i64>,
|
|
pub src_call: String,
|
|
pub dest_call: String,
|
|
pub path: String,
|
|
pub info: String,
|
|
pub info_bytes: Vec<u8>,
|
|
pub packet_type: String,
|
|
pub crc_ok: bool,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub lat: Option<f64>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub lon: Option<f64>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub symbol_table: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub symbol_code: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CwEvent {
|
|
/// Decoded text fragment (one or more characters)
|
|
pub text: String,
|
|
/// Current detected WPM
|
|
pub wpm: u32,
|
|
/// Current detected tone frequency (Hz)
|
|
pub tone_hz: u32,
|
|
/// Whether a CW tone is currently detected
|
|
pub signal_on: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Ft8Message {
|
|
/// UTC timestamp (milliseconds since epoch)
|
|
pub ts_ms: i64,
|
|
/// Approximate SNR (dB)
|
|
pub snr_db: f32,
|
|
/// Time offset within slot (seconds)
|
|
pub dt_s: f32,
|
|
/// Audio frequency (Hz)
|
|
pub freq_hz: f32,
|
|
/// Decoded message text
|
|
pub message: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct WsprMessage {
|
|
/// UTC timestamp (milliseconds since epoch)
|
|
pub ts_ms: i64,
|
|
/// Approximate SNR (dB)
|
|
pub snr_db: f32,
|
|
/// Time offset within slot (seconds)
|
|
pub dt_s: f32,
|
|
/// Audio frequency (Hz)
|
|
pub freq_hz: f32,
|
|
/// Decoded message text
|
|
pub message: String,
|
|
}
|