[feat](trx-rs): receive SSTV pictures end to end
CI / lint (pull_request) Successful in 2m16s
CI / frontend (pull_request) Successful in 4m12s
CI / reuse (pull_request) Successful in 2s
CI / lint (push) Successful in 2m15s
CI / test (pull_request) Successful in 9m37s
CI / test (push) Successful in 7m36s
CI / frontend (push) Failing after 31s
CI / reuse (push) Successful in 3s
CI / lint (pull_request) Successful in 2m16s
CI / frontend (pull_request) Successful in 4m12s
CI / reuse (pull_request) Successful in 2s
CI / lint (push) Successful in 2m15s
CI / test (pull_request) Successful in 9m37s
CI / test (push) Successful in 7m36s
CI / frontend (push) Failing after 31s
CI / reuse (push) Successful in 3s
Wires the SSTV decoder into the stack, from the audio the server already has to a panel in the browser that shows the picture arriving. Server: a decoder task alongside the WEFAX one, running whenever the decoder is enabled and the rig is in a mode SSTV is sent in. A finished picture is written to the cache as a PNG and sent on as a message; the rows are sent as they decode, so a client can watch two minutes of Martin M1 fill in rather than waiting for it. Pictures join the decode history, are replayed to a client that connects later, and survive a restart. Protocol: SetSstvDecodeEnabled and ResetSstvDecoder, a sstv_decode _enabled flag in the rig state, two audio message types, and Sstv and SstvProgress on DecodedMessage. The history stores the message without its base64 payload -- the picture is already on disk, and a megabyte per entry is not what a history is for. Client: pictures land in their own history, and the PNG the server sent is written to the local cache so /sstv-images/ can serve it back. That endpoint and the WEFAX one now share their filename checks rather than each carrying a copy: no separators, no parent references, .png only. Web UI: an SSTV sub-tab beside WEFAX, with a live canvas the rows paint into at the line number they carry, a card for the last picture, and a filterable history with links to the files. Rows below the one arriving are grey rather than black -- not yet received is a different thing from received as black. A picture is not a spot, so neither pictures nor their progress updates reach the decode statistics; that exclusion list had grown by hand for LRPT and WEFAX and is now one named set. The decoder crate gains what the server needed to hand a picture on: to_png, to_png_base64 and save_png, with file names stamped in UTC so they sort. Panel behaviour is tested with the plugin runtime: rows painting at their own line numbers rather than in arrival order, a completed picture linked by file name alone with no server path in the page, a cut-off picture reported as partial, clearing, and the toggle following the rig state. Signed-off-by: Stan Grams <sjg@haxx.space>
This commit was merged in pull request #44.
This commit is contained in:
@@ -74,6 +74,11 @@ pub const AUDIO_MSG_LRPT_PROGRESS: u8 = 0x18;
|
||||
pub const AUDIO_MSG_WEFAX_DECODE: u8 = 0x19;
|
||||
/// Server → client: WEFAX decode progress (JSON `DecodedMessage::WefaxProgress`).
|
||||
pub const AUDIO_MSG_WEFAX_PROGRESS: u8 = 0x1A;
|
||||
/// Server → client: SSTV received picture (JSON `DecodedMessage::Sstv`).
|
||||
pub const AUDIO_MSG_SSTV_DECODE: u8 = 0x1B;
|
||||
/// Server → client: SSTV decode progress, one line at a time
|
||||
/// (JSON `DecodedMessage::SstvProgress`).
|
||||
pub const AUDIO_MSG_SSTV_PROGRESS: u8 = 0x1C;
|
||||
|
||||
/// Maximum payload size for normal messages (1 MB).
|
||||
const MAX_PAYLOAD_SIZE: u32 = 1_048_576;
|
||||
|
||||
@@ -36,6 +36,10 @@ pub enum DecodedMessage {
|
||||
Wefax(WefaxMessage),
|
||||
#[serde(rename = "wefax_progress")]
|
||||
WefaxProgress(WefaxProgress),
|
||||
#[serde(rename = "sstv")]
|
||||
Sstv(SstvMessage),
|
||||
#[serde(rename = "sstv_progress")]
|
||||
SstvProgress(SstvProgress),
|
||||
}
|
||||
|
||||
impl DecodedMessage {
|
||||
@@ -52,6 +56,8 @@ impl DecodedMessage {
|
||||
Self::LrptProgress(m) => m.rig_id = Some(id),
|
||||
Self::Wefax(m) => m.rig_id = Some(id),
|
||||
Self::WefaxProgress(m) => m.rig_id = Some(id),
|
||||
Self::Sstv(m) => m.rig_id = Some(id),
|
||||
Self::SstvProgress(m) => m.rig_id = Some(id),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +74,8 @@ impl DecodedMessage {
|
||||
Self::LrptProgress(m) => m.rig_id.as_deref(),
|
||||
Self::Wefax(m) => m.rig_id.as_deref(),
|
||||
Self::WefaxProgress(m) => m.rig_id.as_deref(),
|
||||
Self::Sstv(m) => m.rig_id.as_deref(),
|
||||
Self::SstvProgress(m) => m.rig_id.as_deref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -319,3 +327,52 @@ pub struct WefaxProgress {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub state: Option<String>,
|
||||
}
|
||||
|
||||
/// A received SSTV picture, complete or cut short.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SstvMessage {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub rig_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ts_ms: Option<i64>,
|
||||
/// VIS code the transmission announced itself with.
|
||||
pub vis: u8,
|
||||
/// Mode name, e.g. "Martin M1".
|
||||
pub mode: String,
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
/// Image lines actually received, which is the height only if the whole
|
||||
/// frame arrived.
|
||||
pub lines: u16,
|
||||
/// Whether reception reached the bottom of the frame.
|
||||
pub complete: bool,
|
||||
/// Filesystem path to the saved PNG, when one was written.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub path: Option<String>,
|
||||
/// Base64-encoded PNG for the trip to a client. Populated when sending and
|
||||
/// stripped before the message is stored in history, which would otherwise
|
||||
/// hold a megabyte per picture.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub png_data: Option<String>,
|
||||
}
|
||||
|
||||
/// A picture arriving, emitted per line so it can be watched.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SstvProgress {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub rig_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ts_ms: Option<i64>,
|
||||
pub vis: u8,
|
||||
pub mode: String,
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
/// Index of the line this update carries.
|
||||
pub line: u16,
|
||||
/// Base64-encoded RGB triples for that one line.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub line_data: Option<String>,
|
||||
/// Decoder state for display, e.g. "Receiving Martin M1".
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub state: Option<String>,
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ pub enum RigCommand {
|
||||
SetWsprDecodeEnabled(bool),
|
||||
SetLrptDecodeEnabled(bool),
|
||||
SetWefaxDecodeEnabled(bool),
|
||||
SetSstvDecodeEnabled(bool),
|
||||
ResetAprsDecoder,
|
||||
ResetHfAprsDecoder,
|
||||
ResetCwDecoder,
|
||||
@@ -42,6 +43,7 @@ pub enum RigCommand {
|
||||
ResetWsprDecoder,
|
||||
ResetLrptDecoder,
|
||||
ResetWefaxDecoder,
|
||||
ResetSstvDecoder,
|
||||
SetBandwidth(u32),
|
||||
SetSdrGain(f64),
|
||||
SetSdrLnaGain(f64),
|
||||
|
||||
@@ -462,6 +462,8 @@ pub fn command_from_rig_command(cmd: RigCommand) -> Box<dyn RigCommandHandler> {
|
||||
| RigCommand::ResetLrptDecoder
|
||||
| RigCommand::SetWefaxDecodeEnabled(_)
|
||||
| RigCommand::ResetWefaxDecoder
|
||||
| RigCommand::SetSstvDecodeEnabled(_)
|
||||
| RigCommand::ResetSstvDecoder
|
||||
| RigCommand::SetBandwidth(_)
|
||||
| RigCommand::SetSdrGain(_)
|
||||
| RigCommand::SetSdrLnaGain(_)
|
||||
|
||||
@@ -34,6 +34,8 @@ pub struct DecoderConfig {
|
||||
#[serde(default)]
|
||||
pub wefax_decode_enabled: bool,
|
||||
#[serde(default)]
|
||||
pub sstv_decode_enabled: bool,
|
||||
#[serde(default)]
|
||||
pub recorder_enabled: bool,
|
||||
}
|
||||
|
||||
@@ -62,6 +64,8 @@ pub struct DecoderResetSeqs {
|
||||
pub lrpt_decode_reset_seq: u64,
|
||||
#[serde(default, skip_serializing)]
|
||||
pub wefax_decode_reset_seq: u64,
|
||||
#[serde(default, skip_serializing)]
|
||||
pub sstv_decode_reset_seq: u64,
|
||||
}
|
||||
|
||||
/// Simple transceiver state representation held by the rig task.
|
||||
|
||||
Reference in New Issue
Block a user