[refactor](trx-rs): resolve all P1/P2 improvement areas

P1 (High Priority):
- Fix LIFO command batching in rig_task.rs (batch.pop→batch.remove(0))
- Add ±25% jitter to ExponentialBackoff to prevent thundering herd
- Add 10,000-entry capacity bounds to decoder history queues
- Add rig task crash detection with Error state broadcast
- Decompose FrontendRuntimeContext 50-field god-struct into 9 sub-structs
  (AudioContext, DecodeHistoryContext, HttpAuthConfig, HttpUiConfig,
   RigRoutingContext, OwnerInfo, VChanContext, SpectrumContext, PerRigAudioContext)
- Migrate std::sync::RwLock to tokio::sync::RwLock in background_decode.rs
- Extract find_input_device/find_output_device helpers from audio pipeline

P2 (Medium Priority):
- Introduce SoapySdrConfig builder struct (replaces 20+ positional params)
- Add define_command_mappings! macro for ClientCommand↔RigCommand mapping
- Replace silent lock poison recovery with lock_or_recover() warning logger
- Make timeouts configurable via RigTaskConfig/ListenerConfig and TOML
- Extract shared config types to trx-app/src/shared_config.rs

Documentation updated in CLAUDE.md, Architecture.md, Improvement-Areas.md.

https://claude.ai/code/session_01P9G7QCWfiYbPVJ7cgiXznf
Signed-off-by: Claude <noreply@anthropic.com>
This commit is contained in:
Claude
2026-03-28 23:26:55 +00:00
committed by Stan Grams
parent 0a60684e28
commit 16426548de
22 changed files with 1245 additions and 916 deletions
+146 -141
View File
@@ -10,152 +10,157 @@ use trx_core::rig::command::RigCommand;
use crate::codec::{mode_to_string, parse_mode};
use crate::types::ClientCommand;
/// Convert a ClientCommand to a RigCommand.
/// Generates `client_command_to_rig` and `rig_command_to_client` from a
/// single definition table, eliminating the mechanical duplication of
/// mapping every variant by hand.
///
/// This maps client-side commands to internal rig commands, parsing
/// mode strings into RigMode values.
pub fn client_command_to_rig(cmd: ClientCommand) -> RigCommand {
match cmd {
ClientCommand::GetRigs => {
unreachable!("GetRigs is handled in the listener before reaching rig_task")
/// Supported row forms (each section is introduced by a keyword):
///
/// - **`client_only:`** `Name, ...;`
/// Variants that exist only in `ClientCommand` with no `RigCommand`
/// counterpart. `client_command_to_rig` panics if called with one.
///
/// - **`unit:`** `ClientName <=> RigName, ...;`
/// Unit variant on both sides, same or different names.
///
/// - **`field:`** `Name { field } <=> Name, ...;`
/// Client struct with one named field mapped to a rig tuple variant.
///
/// - **`multi:`** `Name { a, b } <=> Name, ...;`
/// Both sides use named fields with the same field names.
///
/// - **`freq:`** `Name { field } <=> Name, ...;`
/// Client `u64` field converted to/from `Freq { hz }`.
///
/// - **`mode:`** `Name { field } <=> Name, ...;`
/// Client `String` field converted to/from `RigMode` via
/// `parse_mode`/`mode_to_string`.
macro_rules! define_command_mapping {
(
client_only: $( $co:ident ),* ;
unit: $( $cu:ident <=> $ru:ident ),* ;
field: $( $cf:ident { $fld:ident } <=> $rf:ident ),* ;
multi: $( $cs:ident { $( $sfld:ident ),+ } <=> $rs:ident ),* ;
freq: $( $cfq:ident { $ffld:ident } <=> $rfq:ident ),* ;
mode: $( $cm:ident { $mfld:ident } <=> $rm:ident ),* ;
) => {
/// Convert a [`ClientCommand`] to a [`RigCommand`].
///
/// # Panics
///
/// Panics if called with a client-only command (e.g. `GetRigs`,
/// `GetSatPasses`) that has no `RigCommand` counterpart. Those
/// commands must be intercepted by the caller before reaching this
/// function.
pub fn client_command_to_rig(cmd: ClientCommand) -> RigCommand {
match cmd {
// Client-only variants -- no RigCommand equivalent.
$(
ClientCommand::$co => {
panic!(
"{} has no RigCommand mapping; \
it must be handled before reaching rig_task",
stringify!($co),
);
}
)*
// Unit <=> Unit
$( ClientCommand::$cu => RigCommand::$ru, )*
// Single-field struct <=> tuple
$( ClientCommand::$cf { $fld } => RigCommand::$rf($fld), )*
// Multi-field struct passthrough
$( ClientCommand::$cs { $( $sfld ),+ } => RigCommand::$rs { $( $sfld ),+ }, )*
// Freq conversion (u64 => Freq)
$( ClientCommand::$cfq { $ffld } => RigCommand::$rfq(Freq { hz: $ffld }), )*
// Mode conversion (String => RigMode)
$( ClientCommand::$cm { $mfld } => RigCommand::$rm(parse_mode(&$mfld)), )*
}
}
ClientCommand::GetSatPasses => {
unreachable!("GetSatPasses is handled in the listener before reaching rig_task")
/// Convert a [`RigCommand`] back to a [`ClientCommand`].
///
/// This is the inverse of [`client_command_to_rig`], converting
/// `RigMode` values back to mode strings.
pub fn rig_command_to_client(cmd: RigCommand) -> ClientCommand {
match cmd {
// Unit <=> Unit
$( RigCommand::$ru => ClientCommand::$cu, )*
// Single-field struct <=> tuple
$( RigCommand::$rf($fld) => ClientCommand::$cf { $fld }, )*
// Multi-field struct passthrough
$( RigCommand::$rs { $( $sfld ),+ } => ClientCommand::$cs { $( $sfld ),+ }, )*
// Freq conversion (Freq => u64)
$( RigCommand::$rfq(freq) => ClientCommand::$cfq { $ffld: freq.hz }, )*
// Mode conversion (RigMode => String)
$( RigCommand::$rm(mode) => ClientCommand::$cm {
$mfld: mode_to_string(&mode).into_owned(),
}, )*
}
}
ClientCommand::GetState => RigCommand::GetSnapshot,
ClientCommand::SetFreq { freq_hz } => RigCommand::SetFreq(Freq { hz: freq_hz }),
ClientCommand::SetCenterFreq { freq_hz } => RigCommand::SetCenterFreq(Freq { hz: freq_hz }),
ClientCommand::SetMode { mode } => RigCommand::SetMode(parse_mode(&mode)),
ClientCommand::SetPtt { ptt } => RigCommand::SetPtt(ptt),
ClientCommand::PowerOn => RigCommand::PowerOn,
ClientCommand::PowerOff => RigCommand::PowerOff,
ClientCommand::ToggleVfo => RigCommand::ToggleVfo,
ClientCommand::Lock => RigCommand::Lock,
ClientCommand::Unlock => RigCommand::Unlock,
ClientCommand::GetTxLimit => RigCommand::GetTxLimit,
ClientCommand::SetTxLimit { limit } => RigCommand::SetTxLimit(limit),
ClientCommand::SetAprsDecodeEnabled { enabled } => {
RigCommand::SetAprsDecodeEnabled(enabled)
}
ClientCommand::SetHfAprsDecodeEnabled { enabled } => {
RigCommand::SetHfAprsDecodeEnabled(enabled)
}
ClientCommand::SetCwDecodeEnabled { enabled } => RigCommand::SetCwDecodeEnabled(enabled),
ClientCommand::SetCwAuto { enabled } => RigCommand::SetCwAuto(enabled),
ClientCommand::SetCwWpm { wpm } => RigCommand::SetCwWpm(wpm),
ClientCommand::SetCwToneHz { tone_hz } => RigCommand::SetCwToneHz(tone_hz),
ClientCommand::SetFt8DecodeEnabled { enabled } => RigCommand::SetFt8DecodeEnabled(enabled),
ClientCommand::SetFt4DecodeEnabled { enabled } => RigCommand::SetFt4DecodeEnabled(enabled),
ClientCommand::SetFt2DecodeEnabled { enabled } => RigCommand::SetFt2DecodeEnabled(enabled),
ClientCommand::SetWsprDecodeEnabled { enabled } => {
RigCommand::SetWsprDecodeEnabled(enabled)
}
ClientCommand::ResetAprsDecoder => RigCommand::ResetAprsDecoder,
ClientCommand::ResetHfAprsDecoder => RigCommand::ResetHfAprsDecoder,
ClientCommand::ResetCwDecoder => RigCommand::ResetCwDecoder,
ClientCommand::ResetFt8Decoder => RigCommand::ResetFt8Decoder,
ClientCommand::ResetFt4Decoder => RigCommand::ResetFt4Decoder,
ClientCommand::ResetFt2Decoder => RigCommand::ResetFt2Decoder,
ClientCommand::ResetWsprDecoder => RigCommand::ResetWsprDecoder,
ClientCommand::SetLrptDecodeEnabled { enabled } => {
RigCommand::SetLrptDecodeEnabled(enabled)
}
ClientCommand::ResetLrptDecoder => RigCommand::ResetLrptDecoder,
ClientCommand::SetBandwidth { bandwidth_hz } => RigCommand::SetBandwidth(bandwidth_hz),
ClientCommand::SetSdrGain { gain_db } => RigCommand::SetSdrGain(gain_db),
ClientCommand::SetSdrLnaGain { gain_db } => RigCommand::SetSdrLnaGain(gain_db),
ClientCommand::SetSdrAgc { enabled } => RigCommand::SetSdrAgc(enabled),
ClientCommand::SetSdrSquelch {
enabled,
threshold_db,
} => RigCommand::SetSdrSquelch {
enabled,
threshold_db,
},
ClientCommand::SetSdrNoiseBlanker { enabled, threshold } => {
RigCommand::SetSdrNoiseBlanker { enabled, threshold }
}
ClientCommand::SetWfmDeemphasis { deemphasis_us } => {
RigCommand::SetWfmDeemphasis(deemphasis_us)
}
ClientCommand::SetWfmStereo { enabled } => RigCommand::SetWfmStereo(enabled),
ClientCommand::SetWfmDenoise { level } => RigCommand::SetWfmDenoise(level),
ClientCommand::SetSamStereoWidth { width } => RigCommand::SetSamStereoWidth(width),
ClientCommand::SetSamCarrierSync { enabled } => RigCommand::SetSamCarrierSync(enabled),
ClientCommand::GetSpectrum => RigCommand::GetSpectrum,
}
};
}
/// Convert a RigCommand back to a ClientCommand.
///
/// This is the inverse of client_command_to_rig, converting RigMode
/// values back to mode strings.
pub fn rig_command_to_client(cmd: RigCommand) -> ClientCommand {
match cmd {
RigCommand::GetSnapshot => ClientCommand::GetState,
RigCommand::SetFreq(freq) => ClientCommand::SetFreq { freq_hz: freq.hz },
RigCommand::SetCenterFreq(freq) => ClientCommand::SetCenterFreq { freq_hz: freq.hz },
RigCommand::SetMode(mode) => ClientCommand::SetMode {
mode: mode_to_string(&mode).into_owned(),
},
RigCommand::SetPtt(ptt) => ClientCommand::SetPtt { ptt },
RigCommand::PowerOn => ClientCommand::PowerOn,
RigCommand::PowerOff => ClientCommand::PowerOff,
RigCommand::ToggleVfo => ClientCommand::ToggleVfo,
RigCommand::Lock => ClientCommand::Lock,
RigCommand::Unlock => ClientCommand::Unlock,
RigCommand::GetTxLimit => ClientCommand::GetTxLimit,
RigCommand::SetTxLimit(limit) => ClientCommand::SetTxLimit { limit },
RigCommand::SetAprsDecodeEnabled(enabled) => {
ClientCommand::SetAprsDecodeEnabled { enabled }
}
RigCommand::SetHfAprsDecodeEnabled(enabled) => {
ClientCommand::SetHfAprsDecodeEnabled { enabled }
}
RigCommand::SetCwDecodeEnabled(enabled) => ClientCommand::SetCwDecodeEnabled { enabled },
RigCommand::SetCwAuto(enabled) => ClientCommand::SetCwAuto { enabled },
RigCommand::SetCwWpm(wpm) => ClientCommand::SetCwWpm { wpm },
RigCommand::SetCwToneHz(tone_hz) => ClientCommand::SetCwToneHz { tone_hz },
RigCommand::SetFt8DecodeEnabled(enabled) => ClientCommand::SetFt8DecodeEnabled { enabled },
RigCommand::SetFt4DecodeEnabled(enabled) => ClientCommand::SetFt4DecodeEnabled { enabled },
RigCommand::SetFt2DecodeEnabled(enabled) => ClientCommand::SetFt2DecodeEnabled { enabled },
RigCommand::SetWsprDecodeEnabled(enabled) => {
ClientCommand::SetWsprDecodeEnabled { enabled }
}
RigCommand::ResetAprsDecoder => ClientCommand::ResetAprsDecoder,
RigCommand::ResetHfAprsDecoder => ClientCommand::ResetHfAprsDecoder,
RigCommand::ResetCwDecoder => ClientCommand::ResetCwDecoder,
RigCommand::ResetFt8Decoder => ClientCommand::ResetFt8Decoder,
RigCommand::ResetFt4Decoder => ClientCommand::ResetFt4Decoder,
RigCommand::ResetFt2Decoder => ClientCommand::ResetFt2Decoder,
RigCommand::ResetWsprDecoder => ClientCommand::ResetWsprDecoder,
RigCommand::SetLrptDecodeEnabled(enabled) => {
ClientCommand::SetLrptDecodeEnabled { enabled }
}
RigCommand::ResetLrptDecoder => ClientCommand::ResetLrptDecoder,
RigCommand::SetBandwidth(bandwidth_hz) => ClientCommand::SetBandwidth { bandwidth_hz },
RigCommand::SetSdrGain(gain_db) => ClientCommand::SetSdrGain { gain_db },
RigCommand::SetSdrLnaGain(gain_db) => ClientCommand::SetSdrLnaGain { gain_db },
RigCommand::SetSdrAgc(enabled) => ClientCommand::SetSdrAgc { enabled },
RigCommand::SetSdrSquelch {
enabled,
threshold_db,
} => ClientCommand::SetSdrSquelch {
enabled,
threshold_db,
},
RigCommand::SetSdrNoiseBlanker { enabled, threshold } => {
ClientCommand::SetSdrNoiseBlanker { enabled, threshold }
}
RigCommand::SetWfmDeemphasis(deemphasis_us) => {
ClientCommand::SetWfmDeemphasis { deemphasis_us }
}
RigCommand::SetWfmStereo(enabled) => ClientCommand::SetWfmStereo { enabled },
RigCommand::SetWfmDenoise(level) => ClientCommand::SetWfmDenoise { level },
RigCommand::SetSamStereoWidth(width) => ClientCommand::SetSamStereoWidth { width },
RigCommand::SetSamCarrierSync(enabled) => ClientCommand::SetSamCarrierSync { enabled },
RigCommand::GetSpectrum => ClientCommand::GetSpectrum,
}
define_command_mapping! {
// ── Client-only variants (no RigCommand counterpart) ─────────────
client_only: GetRigs, GetSatPasses;
// ── Unit variants (no payload) ───────────────────────────────────
unit:
GetState <=> GetSnapshot,
PowerOn <=> PowerOn,
PowerOff <=> PowerOff,
ToggleVfo <=> ToggleVfo,
Lock <=> Lock,
Unlock <=> Unlock,
GetTxLimit <=> GetTxLimit,
GetSpectrum <=> GetSpectrum,
ResetAprsDecoder <=> ResetAprsDecoder,
ResetHfAprsDecoder <=> ResetHfAprsDecoder,
ResetCwDecoder <=> ResetCwDecoder,
ResetFt8Decoder <=> ResetFt8Decoder,
ResetFt4Decoder <=> ResetFt4Decoder,
ResetFt2Decoder <=> ResetFt2Decoder,
ResetWsprDecoder <=> ResetWsprDecoder,
ResetLrptDecoder <=> ResetLrptDecoder;
// ── Single-field struct <=> tuple ────────────────────────────────
field:
SetPtt { ptt } <=> SetPtt,
SetTxLimit { limit } <=> SetTxLimit,
SetAprsDecodeEnabled { enabled } <=> SetAprsDecodeEnabled,
SetHfAprsDecodeEnabled { enabled } <=> SetHfAprsDecodeEnabled,
SetCwDecodeEnabled { enabled } <=> SetCwDecodeEnabled,
SetCwAuto { enabled } <=> SetCwAuto,
SetCwWpm { wpm } <=> SetCwWpm,
SetCwToneHz { tone_hz } <=> SetCwToneHz,
SetFt8DecodeEnabled { enabled } <=> SetFt8DecodeEnabled,
SetFt4DecodeEnabled { enabled } <=> SetFt4DecodeEnabled,
SetFt2DecodeEnabled { enabled } <=> SetFt2DecodeEnabled,
SetWsprDecodeEnabled { enabled } <=> SetWsprDecodeEnabled,
SetLrptDecodeEnabled { enabled } <=> SetLrptDecodeEnabled,
SetBandwidth { bandwidth_hz } <=> SetBandwidth,
SetSdrGain { gain_db } <=> SetSdrGain,
SetSdrLnaGain { gain_db } <=> SetSdrLnaGain,
SetSdrAgc { enabled } <=> SetSdrAgc,
SetWfmDeemphasis { deemphasis_us } <=> SetWfmDeemphasis,
SetWfmStereo { enabled } <=> SetWfmStereo,
SetWfmDenoise { level } <=> SetWfmDenoise,
SetSamStereoWidth { width } <=> SetSamStereoWidth,
SetSamCarrierSync { enabled } <=> SetSamCarrierSync;
// ── Multi-field struct passthrough ───────────────────────────────
multi:
SetSdrSquelch { enabled, threshold_db } <=> SetSdrSquelch,
SetSdrNoiseBlanker { enabled, threshold } <=> SetSdrNoiseBlanker;
// ── Freq conversions (u64 <=> Freq) ──────────────────────────────
freq:
SetFreq { freq_hz } <=> SetFreq,
SetCenterFreq { freq_hz } <=> SetCenterFreq;
// ── Mode conversion (String <=> RigMode) ─────────────────────────
mode:
SetMode { mode } <=> SetMode;
}
#[cfg(test)]