[feat](trx-wxsat): add Meteor-M LRPT decoder and Weather Satellites frontend panel
Restructure trx-wxsat into noaa/ (APT) and lrpt/ (Meteor-M LRPT) submodules with shared crate base. Add QPSK demodulator, CCSDS CADU framer, MCU channel assembler for LRPT. Wire LRPT through full stack (core types, protocol, server decoder task, client). Add Weather Satellites sub-tab in Digital Modes with toggle buttons for NOAA APT and Meteor LRPT, descriptions, and image history. https://claude.ai/code/session_01JA13DHuzuHUL4nSBBRU83f Signed-off-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -68,6 +68,8 @@ pub const AUDIO_MSG_FT4_DECODE: u8 = 0x14;
|
||||
pub const AUDIO_MSG_FT2_DECODE: u8 = 0x15;
|
||||
/// Server → client: weather satellite APT image complete (JSON `DecodedMessage::WxsatImage`).
|
||||
pub const AUDIO_MSG_WXSAT_IMAGE: u8 = 0x16;
|
||||
/// Server → client: Meteor-M LRPT image complete (JSON `DecodedMessage::LrptImage`).
|
||||
pub const AUDIO_MSG_LRPT_IMAGE: u8 = 0x17;
|
||||
|
||||
/// Maximum payload size for normal messages (1 MB).
|
||||
const MAX_PAYLOAD_SIZE: u32 = 1_048_576;
|
||||
|
||||
@@ -30,6 +30,8 @@ pub enum DecodedMessage {
|
||||
Wspr(WsprMessage),
|
||||
#[serde(rename = "wxsat_image")]
|
||||
WxsatImage(WxsatImage),
|
||||
#[serde(rename = "lrpt_image")]
|
||||
LrptImage(LrptImage),
|
||||
}
|
||||
|
||||
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::WxsatImage(m) => m.rig_id = Some(id),
|
||||
Self::LrptImage(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::WxsatImage(m) => m.rig_id.as_deref(),
|
||||
Self::LrptImage(m) => m.rig_id.as_deref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,3 +252,26 @@ pub struct WsprMessage {
|
||||
/// Decoded message text
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
/// A completed Meteor-M LRPT satellite image, saved to disk as a PNG.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LrptImage {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub rig_id: Option<String>,
|
||||
/// UTC timestamp (milliseconds since epoch) of pass start.
|
||||
pub pass_start_ms: i64,
|
||||
/// UTC timestamp (milliseconds since epoch) when the image was finalised.
|
||||
pub pass_end_ms: i64,
|
||||
/// Number of decoded MCU rows.
|
||||
pub mcu_count: u32,
|
||||
/// Absolute filesystem path to the saved image file.
|
||||
pub path: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ts_ms: Option<i64>,
|
||||
/// Identified satellite (e.g. "Meteor-M N2-3", "Meteor-M N2-4").
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub satellite: Option<String>,
|
||||
/// APID channels decoded (e.g. "64,65,66" for RGB).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub channels: Option<String>,
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ pub enum RigCommand {
|
||||
SetFt4DecodeEnabled(bool),
|
||||
SetFt2DecodeEnabled(bool),
|
||||
SetWsprDecodeEnabled(bool),
|
||||
SetWxsatDecodeEnabled(bool),
|
||||
SetLrptDecodeEnabled(bool),
|
||||
ResetAprsDecoder,
|
||||
ResetHfAprsDecoder,
|
||||
ResetCwDecoder,
|
||||
@@ -38,6 +40,8 @@ pub enum RigCommand {
|
||||
ResetFt4Decoder,
|
||||
ResetFt2Decoder,
|
||||
ResetWsprDecoder,
|
||||
ResetWxsatDecoder,
|
||||
ResetLrptDecoder,
|
||||
SetBandwidth(u32),
|
||||
SetSdrGain(f64),
|
||||
SetSdrLnaGain(f64),
|
||||
|
||||
@@ -520,6 +520,10 @@ pub fn command_from_rig_command(cmd: RigCommand) -> Box<dyn RigCommandHandler> {
|
||||
| RigCommand::ResetFt4Decoder
|
||||
| RigCommand::ResetFt2Decoder
|
||||
| RigCommand::ResetWsprDecoder
|
||||
| RigCommand::SetWxsatDecodeEnabled(_)
|
||||
| RigCommand::SetLrptDecodeEnabled(_)
|
||||
| RigCommand::ResetWxsatDecoder
|
||||
| RigCommand::ResetLrptDecoder
|
||||
| RigCommand::SetBandwidth(_)
|
||||
| RigCommand::SetSdrGain(_)
|
||||
| RigCommand::SetSdrLnaGain(_)
|
||||
|
||||
@@ -48,6 +48,8 @@ pub struct RigState {
|
||||
#[serde(default)]
|
||||
pub wxsat_decode_enabled: bool,
|
||||
#[serde(default)]
|
||||
pub lrpt_decode_enabled: bool,
|
||||
#[serde(default)]
|
||||
pub cw_auto: bool,
|
||||
#[serde(default)]
|
||||
pub cw_wpm: u32,
|
||||
@@ -81,6 +83,8 @@ pub struct RigState {
|
||||
pub wspr_decode_reset_seq: u64,
|
||||
#[serde(default, skip_serializing)]
|
||||
pub wxsat_decode_reset_seq: u64,
|
||||
#[serde(default, skip_serializing)]
|
||||
pub lrpt_decode_reset_seq: u64,
|
||||
}
|
||||
|
||||
/// Mode supported by the rig.
|
||||
@@ -164,6 +168,7 @@ impl RigState {
|
||||
ft2_decode_enabled: false,
|
||||
wspr_decode_enabled: false,
|
||||
wxsat_decode_enabled: false,
|
||||
lrpt_decode_enabled: false,
|
||||
cw_auto: true,
|
||||
cw_wpm: 15,
|
||||
cw_tone_hz: 700,
|
||||
@@ -178,6 +183,7 @@ impl RigState {
|
||||
ft2_decode_reset_seq: 0,
|
||||
wspr_decode_reset_seq: 0,
|
||||
wxsat_decode_reset_seq: 0,
|
||||
lrpt_decode_reset_seq: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,6 +244,7 @@ impl RigState {
|
||||
ft2_decode_enabled: snapshot.ft2_decode_enabled,
|
||||
wspr_decode_enabled: snapshot.wspr_decode_enabled,
|
||||
wxsat_decode_enabled: snapshot.wxsat_decode_enabled,
|
||||
lrpt_decode_enabled: snapshot.lrpt_decode_enabled,
|
||||
filter: snapshot.filter,
|
||||
spectrum: None, // spectrum flows through /api/spectrum, not persistent state
|
||||
vchan_rds: None, // vchan RDS flows through /api/spectrum, not persistent state
|
||||
@@ -249,6 +256,7 @@ impl RigState {
|
||||
ft2_decode_reset_seq: 0,
|
||||
wspr_decode_reset_seq: 0,
|
||||
wxsat_decode_reset_seq: 0,
|
||||
lrpt_decode_reset_seq: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,6 +295,7 @@ impl RigState {
|
||||
ft2_decode_enabled: self.ft2_decode_enabled,
|
||||
wspr_decode_enabled: self.wspr_decode_enabled,
|
||||
wxsat_decode_enabled: self.wxsat_decode_enabled,
|
||||
lrpt_decode_enabled: self.lrpt_decode_enabled,
|
||||
filter: self.filter.clone(),
|
||||
spectrum: self.spectrum.clone(),
|
||||
vchan_rds: self.vchan_rds.clone(),
|
||||
@@ -500,6 +509,8 @@ pub struct RigSnapshot {
|
||||
#[serde(default)]
|
||||
pub wxsat_decode_enabled: bool,
|
||||
#[serde(default)]
|
||||
pub lrpt_decode_enabled: bool,
|
||||
#[serde(default)]
|
||||
pub cw_auto: bool,
|
||||
#[serde(default)]
|
||||
pub cw_wpm: u32,
|
||||
|
||||
Reference in New Issue
Block a user