diff --git a/src/decoders/trx-rds/src/lib.rs b/src/decoders/trx-rds/src/lib.rs index 4830069..4fe70b6 100644 --- a/src/decoders/trx-rds/src/lib.rs +++ b/src/decoders/trx-rds/src/lib.rs @@ -631,7 +631,7 @@ mod tests { let mut candidate = Candidate::new(240_000.0, 0.0); let pi = 0x52ab; let block_a = encode_block(pi, OFFSET_A); - let block_b = encode_block((10 << 5) | 0, OFFSET_B); + let block_b = encode_block(10 << 5, OFFSET_B); let block_d = encode_block(u16::from_be_bytes(*b"AB"), OFFSET_D); for bit_idx in (0..26).rev() { diff --git a/src/trx-client/src/audio_client.rs b/src/trx-client/src/audio_client.rs index 99d2bdd..2f6c729 100644 --- a/src/trx-client/src/audio_client.rs +++ b/src/trx-client/src/audio_client.rs @@ -25,6 +25,7 @@ use trx_core::audio::{ use trx_core::decode::DecodedMessage; /// Run the audio client with auto-reconnect. +#[allow(clippy::too_many_arguments)] pub async fn run_audio_client( server_host: String, default_port: u16, @@ -102,6 +103,7 @@ pub async fn run_audio_client( } } +#[allow(clippy::too_many_arguments)] async fn handle_audio_connection( stream: TcpStream, server_host: &str, diff --git a/src/trx-client/src/remote_client.rs b/src/trx-client/src/remote_client.rs index 5cb1051..a7e4f7f 100644 --- a/src/trx-client/src/remote_client.rs +++ b/src/trx-client/src/remote_client.rs @@ -523,7 +523,7 @@ fn parse_port(port_str: &str) -> Result { #[cfg(test)] mod tests { - use super::{parse_remote_url, RemoteClientConfig, RemoteEndpoint}; + use super::{parse_remote_url, RemoteClientConfig, RemoteEndpoint, SharedSpectrum}; use std::sync::{Arc, Mutex}; use std::time::Duration; diff --git a/src/trx-client/trx-frontend/trx-frontend-http/src/api.rs b/src/trx-client/trx-frontend/trx-frontend-http/src/api.rs index 99f3e9c..2bd6bac 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/src/api.rs +++ b/src/trx-client/trx-frontend/trx-frontend-http/src/api.rs @@ -29,6 +29,16 @@ const LOGO_BYTES: &[u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/trx-logo.png")); const REQUEST_TIMEOUT: Duration = Duration::from_secs(15); +struct FrontendMeta { + http_clients: usize, + rigctl_clients: usize, + rigctl_addr: Option, + active_rig_id: Option, + rig_ids: Vec, + owner_callsign: Option, + show_sdr_gain_control: bool, +} + #[get("/status")] pub async fn status_api( state: web::Data>, @@ -39,13 +49,7 @@ pub async fn status_api( let json = serde_json::to_string(&state).map_err(actix_web::error::ErrorInternalServerError)?; let json = inject_frontend_meta( &json, - clients.load(Ordering::Relaxed), - context.rigctl_clients.load(Ordering::Relaxed), - rigctl_addr_from_context(context.get_ref().as_ref()), - active_rig_id_from_context(context.get_ref().as_ref()), - rig_ids_from_context(context.get_ref().as_ref()), - owner_callsign_from_context(context.get_ref().as_ref()), - show_sdr_gain_control_from_context(context.get_ref().as_ref()), + frontend_meta_from_context(clients.load(Ordering::Relaxed), context.get_ref().as_ref()), ); Ok(HttpResponse::Ok() .insert_header((header::CONTENT_TYPE, "application/json")) @@ -53,16 +57,7 @@ pub async fn status_api( } /// Inject `"clients": N` into a JSON object string. -fn inject_frontend_meta( - json: &str, - http_clients: usize, - rigctl_clients: usize, - rigctl_addr: Option, - active_rig_id: Option, - rig_ids: Vec, - owner_callsign: Option, - show_sdr_gain_control: bool, -) -> String { +fn inject_frontend_meta(json: &str, meta: FrontendMeta) -> String { let mut value: serde_json::Value = match serde_json::from_str(json) { Ok(v) => v, Err(_) => return json.to_string(), @@ -71,29 +66,41 @@ fn inject_frontend_meta( let Some(map) = value.as_object_mut() else { return json.to_string(); }; - map.insert("clients".to_string(), serde_json::json!(http_clients)); + map.insert("clients".to_string(), serde_json::json!(meta.http_clients)); map.insert( "rigctl_clients".to_string(), - serde_json::json!(rigctl_clients), + serde_json::json!(meta.rigctl_clients), ); - if let Some(addr) = rigctl_addr { + if let Some(addr) = meta.rigctl_addr { map.insert("rigctl_addr".to_string(), serde_json::json!(addr)); } - if let Some(rig_id) = active_rig_id { + if let Some(rig_id) = meta.active_rig_id { map.insert("active_rig_id".to_string(), serde_json::json!(rig_id)); } - map.insert("rig_ids".to_string(), serde_json::json!(rig_ids)); - if let Some(owner) = owner_callsign { + map.insert("rig_ids".to_string(), serde_json::json!(meta.rig_ids)); + if let Some(owner) = meta.owner_callsign { map.insert("owner_callsign".to_string(), serde_json::json!(owner)); } map.insert( "show_sdr_gain_control".to_string(), - serde_json::json!(show_sdr_gain_control), + serde_json::json!(meta.show_sdr_gain_control), ); serde_json::to_string(&value).unwrap_or_else(|_| json.to_string()) } +fn frontend_meta_from_context(http_clients: usize, context: &FrontendRuntimeContext) -> FrontendMeta { + FrontendMeta { + http_clients, + rigctl_clients: context.rigctl_clients.load(Ordering::Relaxed), + rigctl_addr: rigctl_addr_from_context(context), + active_rig_id: active_rig_id_from_context(context), + rig_ids: rig_ids_from_context(context), + owner_callsign: owner_callsign_from_context(context), + show_sdr_gain_control: show_sdr_gain_control_from_context(context), + } +} + fn rigctl_addr_from_context(context: &FrontendRuntimeContext) -> Option { context .rigctl_listen_addr @@ -144,13 +151,7 @@ pub async fn events( serde_json::to_string(&initial).map_err(actix_web::error::ErrorInternalServerError)?; let initial_json = inject_frontend_meta( &initial_json, - count, - context.rigctl_clients.load(Ordering::Relaxed), - rigctl_addr_from_context(context.get_ref().as_ref()), - active_rig_id_from_context(context.get_ref().as_ref()), - rig_ids_from_context(context.get_ref().as_ref()), - owner_callsign_from_context(context.get_ref().as_ref()), - show_sdr_gain_control_from_context(context.get_ref().as_ref()), + frontend_meta_from_context(count, context.get_ref().as_ref()), ); let initial_stream = once(async move { Ok::(Bytes::from(format!("data: {initial_json}\n\n"))) }); @@ -165,13 +166,10 @@ pub async fn events( serde_json::to_string(&v).ok().map(|json| { let json = inject_frontend_meta( &json, - counter.load(Ordering::Relaxed), - context.rigctl_clients.load(Ordering::Relaxed), - rigctl_addr_from_context(context.as_ref()), - active_rig_id_from_context(context.as_ref()), - rig_ids_from_context(context.as_ref()), - owner_callsign_from_context(context.as_ref()), - show_sdr_gain_control_from_context(context.as_ref()), + frontend_meta_from_context( + counter.load(Ordering::Relaxed), + context.as_ref(), + ), ); Ok::(Bytes::from(format!("data: {json}\n\n"))) }) diff --git a/src/trx-server/src/audio.rs b/src/trx-server/src/audio.rs index 142ab1f..a0bdd2e 100644 --- a/src/trx-server/src/audio.rs +++ b/src/trx-server/src/audio.rs @@ -265,6 +265,7 @@ pub fn spawn_audio_capture( }) } +#[allow(clippy::too_many_arguments)] fn run_capture( sample_rate: u32, channels: u16, @@ -1306,6 +1307,7 @@ pub async fn run_audio_listener( Ok(()) } +#[allow(clippy::too_many_arguments)] async fn handle_audio_client( socket: TcpStream, peer: SocketAddr, diff --git a/src/trx-server/src/config.rs b/src/trx-server/src/config.rs index ce5cd1c..4421515 100644 --- a/src/trx-server/src/config.rs +++ b/src/trx-server/src/config.rs @@ -27,7 +27,7 @@ use trx_core::rig::state::RigMode; /// `[behavior]` / `[decode_logs]` fields are still supported via /// `ServerConfig::resolved_rigs()` which synthesises a single-element list /// with `id = "default"` when `rigs` is empty. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(default)] pub struct RigInstanceConfig { /// Stable rig identifier used in protocol routing. @@ -51,22 +51,6 @@ pub struct RigInstanceConfig { pub decode_logs: DecodeLogsConfig, } -impl Default for RigInstanceConfig { - fn default() -> Self { - Self { - id: String::new(), // Empty by default so auto-generation triggers in resolved_rigs() - name: None, - rig: RigConfig::default(), - behavior: BehaviorConfig::default(), - audio: AudioConfig::default(), - sdr: SdrConfig::default(), - pskreporter: PskReporterConfig::default(), - aprsfi: AprsFiConfig::default(), - decode_logs: DecodeLogsConfig::default(), - } - } -} - impl RigInstanceConfig { /// Get the display name for this rig. /// Returns the configured name if set, otherwise the id. @@ -520,18 +504,14 @@ impl ServerConfig { let mut seen_ports: std::collections::HashSet = std::collections::HashSet::new(); for rig in &self.rigs { // Check for explicit duplicate IDs (empty IDs are auto-generated later). - if !rig.id.trim().is_empty() { - if !seen_ids.insert(rig.id.clone()) { - return Err(format!("[[rigs]] duplicate rig id: \"{}\"", rig.id)); - } + if !rig.id.trim().is_empty() && !seen_ids.insert(rig.id.clone()) { + return Err(format!("[[rigs]] duplicate rig id: \"{}\"", rig.id)); } - if rig.audio.enabled { - if !seen_ports.insert(rig.audio.port) { - return Err(format!( - "[[rigs]] duplicate audio port {} (rig id: \"{}\")", - rig.audio.port, rig.id - )); - } + if rig.audio.enabled && !seen_ports.insert(rig.audio.port) { + return Err(format!( + "[[rigs]] duplicate audio port {} (rig id: \"{}\")", + rig.audio.port, rig.id + )); } if let Some(max_gain) = rig.sdr.gain.max_value { if !max_gain.is_finite() { @@ -979,12 +959,13 @@ tokens = ["secret123"] stream_opus: bool, decoders: Vec, ) { - let mut ch = SdrChannelConfig::default(); - ch.id = id.to_string(); - ch.offset_hz = offset_hz; - ch.stream_opus = stream_opus; - ch.decoders = decoders; - cfg.sdr.channels.push(ch); + cfg.sdr.channels.push(SdrChannelConfig { + id: id.to_string(), + offset_hz, + stream_opus, + decoders, + ..SdrChannelConfig::default() + }); } #[test] diff --git a/src/trx-server/src/main.rs b/src/trx-server/src/main.rs index 7b3439a..7643914 100644 --- a/src/trx-server/src/main.rs +++ b/src/trx-server/src/main.rs @@ -269,14 +269,18 @@ fn parse_rig_mode( } } -/// Build a `SoapySdrRig` with full channel config from a `RigInstanceConfig`. #[cfg(feature = "soapysdr")] -fn build_sdr_rig_from_instance( - rig_cfg: &RigInstanceConfig, -) -> DynResult<( +type SdrRigBuildResult = DynResult<( Box, tokio::sync::broadcast::Receiver>, -)> { +)>; + +type OptionalSdrRig = Option>; +type OptionalSdrPcmRx = Option>>; + +/// Build a `SoapySdrRig` with full channel config from a `RigInstanceConfig`. +#[cfg(feature = "soapysdr")] +fn build_sdr_rig_from_instance(rig_cfg: &RigInstanceConfig) -> SdrRigBuildResult { use trx_core::radio::freq::Freq; use trx_core::rig::AudioSource; @@ -332,6 +336,7 @@ fn build_sdr_rig_from_instance( } /// Build a `RigTaskConfig` for a single rig instance. +#[allow(clippy::too_many_arguments)] fn build_rig_task_config( rig_cfg: &RigInstanceConfig, rig_model: String, @@ -391,6 +396,7 @@ fn build_rig_task_config( /// /// `sdr_pcm_rx` carries a live SDR PCM receiver when the rig uses the /// SoapySDR backend; `None` selects the cpal capture path. +#[allow(clippy::too_many_arguments)] fn spawn_rig_audio_stack( rig_cfg: &RigInstanceConfig, state_rx: watch::Receiver, @@ -812,10 +818,8 @@ async fn main() -> DynResult<()> { // Build SDR rig when applicable. #[cfg(feature = "soapysdr")] - let (sdr_prebuilt_rig, sdr_pcm_rx): ( - Option>, - Option>>, - ) = if rig_cfg.rig.access.access_type.as_deref() == Some("sdr") { + let (sdr_prebuilt_rig, sdr_pcm_rx): (OptionalSdrRig, OptionalSdrPcmRx) = + if rig_cfg.rig.access.access_type.as_deref() == Some("sdr") { let (rig, pcm_rx) = build_sdr_rig_from_instance(rig_cfg)?; (Some(rig), Some(pcm_rx)) } else { @@ -823,10 +827,7 @@ async fn main() -> DynResult<()> { }; #[cfg(not(feature = "soapysdr"))] - let (sdr_prebuilt_rig, sdr_pcm_rx): ( - Option>, - Option>>, - ) = (None, None); + let (sdr_prebuilt_rig, sdr_pcm_rx): (OptionalSdrRig, OptionalSdrPcmRx) = (None, None); let histories = DecoderHistories::new(); diff --git a/src/trx-server/trx-backend/src/dummy.rs b/src/trx-server/trx-backend/src/dummy.rs index e13d903..507239a 100644 --- a/src/trx-server/trx-backend/src/dummy.rs +++ b/src/trx-server/trx-backend/src/dummy.rs @@ -147,6 +147,7 @@ impl DummyRig { } #[cfg(test)] +#[allow(clippy::items_after_test_module)] mod tests { use super::*; use trx_core::rig::Rig; 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 0e524af..99c49d3 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 @@ -154,7 +154,7 @@ fn fast_atan2(y: f32, x: f32) -> f32 { } } - let angle = if x > 0.0 { + if x > 0.0 { fast_atan(y / x) } else if x < 0.0 { if y >= 0.0 { @@ -164,8 +164,7 @@ fn fast_atan2(y: f32, x: f32) -> f32 { } } else { 0.0 - }; - angle + } } /// 7th-order minimax atan approximation for |z| ≤ 1. @@ -1511,14 +1510,14 @@ mod tests { let pilot_freq = 19_000.0_f32; let carrier_freq = 38_000.0_f32; let mut composite = vec![0.0_f32; num_samples]; - for n in 0..num_samples { + for (n, sample) in composite.iter_mut().enumerate() { let t = n as f32 / fs; let audio = (TAU * audio_freq * t).sin(); // L = audio, R = 0 let sum = audio; // L + R let diff = audio; // L - R let pilot = 0.1 * (TAU * pilot_freq * t).cos(); let carrier = (TAU * carrier_freq * t).cos(); - composite[n] = sum + pilot + diff * carrier; + *sample = sum + pilot + diff * carrier; } // --- FM-modulate composite → IQ samples --- @@ -1613,7 +1612,7 @@ mod tests { // Test both L-only (diff = +audio) and R-only (diff = -audio). for (label, diff_sign) in [("L-only", 1.0_f32), ("R-only", -1.0_f32)] { let mut composite = vec![0.0_f32; num_samples]; - for n in 0..num_samples { + for (n, sample) in composite.iter_mut().enumerate() { let t = n as f32 / fs; let audio: f32 = freqs.iter().map(|&f| (TAU * f * t).sin()).sum::() / freqs.len() as f32; @@ -1621,7 +1620,7 @@ mod tests { let diff = audio * diff_sign; // L - R let pilot = 0.1 * (TAU * pilot_freq * t).cos(); let carrier = (TAU * carrier_freq * t).cos(); - composite[n] = sum + pilot + diff * carrier; + *sample = sum + pilot + diff * carrier; } let peak_composite = 2.1_f32; @@ -1928,14 +1927,14 @@ mod tests { let pilot_freq = 19_000.0_f32; let carrier_freq = 38_000.0_f32; let mut composite = vec![0.0_f32; num_samples]; - for n in 0..num_samples { + for (n, sample) in composite.iter_mut().enumerate() { let t = n as f32 / fs; let audio = (TAU * audio_freq * t).sin(); let sum = audio; let diff = audio; let pilot = 0.1 * (TAU * pilot_freq * t).cos(); let carrier = (TAU * carrier_freq * t).cos(); - composite[n] = sum + pilot + diff * carrier; + *sample = sum + pilot + diff * carrier; } let peak_composite = 2.1_f32; diff --git a/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp.rs b/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp.rs index ea17fb0..e0ccb93 100644 --- a/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp.rs +++ b/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp.rs @@ -178,16 +178,18 @@ pub struct BlockFirFilterPair { scratch_freq: Vec>, } -fn build_fir_kernel( - cutoff_norm: f32, - taps: usize, - block_size: usize, -) -> ( +type FirKernel = ( Vec>, usize, Arc>, Arc>, -) { +); + +fn build_fir_kernel( + cutoff_norm: f32, + taps: usize, + block_size: usize, +) -> FirKernel { let coeffs = windowed_sinc_coeffs(cutoff_norm, taps); let fft_size = (block_size + taps - 1).next_power_of_two(); @@ -943,6 +945,7 @@ pub struct SdrPipeline { } impl SdrPipeline { + #[allow(clippy::too_many_arguments)] pub fn start( source: Box, sdr_sample_rate: u32,