diff --git a/src/trx-client/src/audio_client.rs b/src/trx-client/src/audio_client.rs index 1c0ee5a..99d2bdd 100644 --- a/src/trx-client/src/audio_client.rs +++ b/src/trx-client/src/audio_client.rs @@ -15,6 +15,7 @@ use tokio::net::TcpStream; use tokio::sync::{broadcast, mpsc, watch}; use tokio::time; use tracing::{info, warn}; +use trx_frontend::RemoteRigEntry; use trx_core::audio::{ read_audio_msg, write_audio_msg, AudioStreamInfo, AUDIO_MSG_APRS_DECODE, AUDIO_MSG_CW_DECODE, @@ -29,6 +30,7 @@ pub async fn run_audio_client( default_port: u16, rig_ports: HashMap, selected_rig_id: Arc>>, + known_rigs: Arc>>, rx_tx: broadcast::Sender, mut tx_rx: mpsc::Receiver, stream_info_tx: watch::Sender>, @@ -47,6 +49,7 @@ pub async fn run_audio_client( &server_host, default_port, &rig_ports, + &known_rigs, selected_rig_id .lock() .ok() @@ -63,6 +66,7 @@ pub async fn run_audio_client( default_port, &rig_ports, &selected_rig_id, + &known_rigs, &server_addr, &rx_tx, &mut tx_rx, @@ -104,6 +108,7 @@ async fn handle_audio_connection( default_port: u16, rig_ports: &HashMap, selected_rig_id: &Arc>>, + known_rigs: &Arc>>, connected_addr: &str, rx_tx: &broadcast::Sender, tx_rx: &mut mpsc::Receiver, @@ -196,6 +201,7 @@ async fn handle_audio_connection( server_host, default_port, rig_ports, + known_rigs, current_rig.as_deref(), ); if desired_addr != connected_addr { @@ -217,11 +223,20 @@ fn resolve_audio_addr( host: &str, default_port: u16, rig_ports: &HashMap, + known_rigs: &Arc>>, selected_rig_id: Option<&str>, ) -> String { let port = selected_rig_id - .and_then(|rig_id| rig_ports.get(rig_id)) - .copied() + .and_then(|rig_id| { + rig_ports.get(rig_id).copied().or_else(|| { + known_rigs.lock().ok().and_then(|entries| { + entries + .iter() + .find(|entry| entry.rig_id == rig_id) + .and_then(|entry| entry.audio_port) + }) + }) + }) .unwrap_or(default_port); format!("{}:{}", host, port) } diff --git a/src/trx-client/src/main.rs b/src/trx-client/src/main.rs index 3e94517..b14e365 100644 --- a/src/trx-client/src/main.rs +++ b/src/trx-client/src/main.rs @@ -295,6 +295,7 @@ async fn async_init() -> DynResult { cfg.frontends.audio.server_port, audio_rig_ports, frontend_runtime.remote_active_rig_id.clone(), + frontend_runtime.remote_rigs.clone(), rx_audio_tx, tx_audio_rx, stream_info_tx, diff --git a/src/trx-client/src/remote_client.rs b/src/trx-client/src/remote_client.rs index 51d7cd1..751c604 100644 --- a/src/trx-client/src/remote_client.rs +++ b/src/trx-client/src/remote_client.rs @@ -278,6 +278,7 @@ fn cache_remote_rigs(config: &RemoteClientConfig, rigs: &[RigEntry]) { .map(|entry| RemoteRigEntry { rig_id: entry.rig_id.clone(), state: entry.state.clone(), + audio_port: entry.audio_port, }) .collect(); } @@ -561,6 +562,7 @@ mod tests { rigs: Some(vec![RigEntry { rig_id: "default".to_string(), state: snapshot.clone(), + audio_port: Some(4531), }]), error: None, }) diff --git a/src/trx-client/trx-frontend/src/lib.rs b/src/trx-client/trx-frontend/src/lib.rs index 3a69141..da04382 100644 --- a/src/trx-client/trx-frontend/src/lib.rs +++ b/src/trx-client/trx-frontend/src/lib.rs @@ -21,6 +21,7 @@ use trx_core::{DynResult, RigRequest, RigState}; pub struct RemoteRigEntry { pub rig_id: String, pub state: RigSnapshot, + pub audio_port: Option, } /// Trait implemented by concrete frontends to expose a runner entrypoint. diff --git a/src/trx-client/trx-frontend/trx-frontend-http-json/src/server.rs b/src/trx-client/trx-frontend/trx-frontend-http-json/src/server.rs index 21314e0..c857e94 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http-json/src/server.rs +++ b/src/trx-client/trx-frontend/trx-frontend-http-json/src/server.rs @@ -244,6 +244,7 @@ fn snapshot_remote_rigs(context: &FrontendRuntimeContext) -> Vec { .map(|entry| RigEntry { rig_id: entry.rig_id.clone(), state: entry.state.clone(), + audio_port: entry.audio_port, }) .collect() }) diff --git a/src/trx-protocol/src/types.rs b/src/trx-protocol/src/types.rs index 9af4e23..9866a9b 100644 --- a/src/trx-protocol/src/types.rs +++ b/src/trx-protocol/src/types.rs @@ -55,6 +55,8 @@ pub struct ClientEnvelope { pub struct RigEntry { pub rig_id: String, pub state: RigSnapshot, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub audio_port: Option, } /// Response sent to network clients over TCP. diff --git a/src/trx-server/src/listener.rs b/src/trx-server/src/listener.rs index 9ab8745..c0944c6 100644 --- a/src/trx-server/src/listener.rs +++ b/src/trx-server/src/listener.rs @@ -244,6 +244,7 @@ async fn handle_client( entries.push(RigEntry { rig_id: handle.rig_id.clone(), state: snapshot, + audio_port: Some(handle.audio_port), }); } } @@ -303,7 +304,10 @@ async fn handle_client( match time::timeout(IO_TIMEOUT, handle.rig_tx.send(req)).await { Ok(Ok(())) => {} Ok(Err(e)) => { - error!("Failed to send request to rig_task for '{}': {:?}", target_rig_id, e); + error!( + "Failed to send request to rig_task for '{}': {:?}", + target_rig_id, e + ); let resp = ClientResponse { success: false, rig_id: Some(target_rig_id.clone()), @@ -404,8 +408,8 @@ mod tests { use trx_core::radio::freq::Band; use trx_core::rig::request::RigRequest; - use trx_core::rig::{RigAccessMethod, RigCapabilities, RigInfo}; use trx_core::rig::state::RigState; + use trx_core::rig::{RigAccessMethod, RigCapabilities, RigInfo}; fn loopback_addr() -> SocketAddr { let listener = std::net::TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).expect("bind"); @@ -458,6 +462,7 @@ mod tests { rig_id: "default".to_string(), rig_tx, state_rx, + audio_port: 4531, }; let mut map = HashMap::new(); map.insert("default".to_string(), handle); @@ -568,7 +573,11 @@ mod tests { reader.read_line(&mut line).await.expect("read"); let resp: ClientResponse = serde_json::from_str(line.trim_end()).expect("response json"); assert!(!resp.success); - assert!(resp.error.as_deref().unwrap_or("").contains("Unknown rig_id")); + assert!(resp + .error + .as_deref() + .unwrap_or("") + .contains("Unknown rig_id")); let _ = shutdown_tx.send(true); handle.abort(); diff --git a/src/trx-server/src/main.rs b/src/trx-server/src/main.rs index a54f7ab..0c78d8a 100644 --- a/src/trx-server/src/main.rs +++ b/src/trx-server/src/main.rs @@ -219,11 +219,9 @@ fn access_from_rig_instance(rig_cfg: &RigInstanceConfig) -> DynResult let args = rig_cfg.rig.access.args.clone().unwrap_or_default(); Ok(RigAccess::Sdr { args }) } - Some(other) => Err(format!( - "Unknown access type '{}' for rig '{}'", - other, rig_cfg.id - ) - .into()), + Some(other) => { + Err(format!("Unknown access type '{}' for rig '{}'", other, rig_cfg.id).into()) + } } } @@ -455,7 +453,10 @@ fn spawn_rig_audio_stack( if rig_cfg.audio.rx_enabled { if let Some(mut sdr_rx) = sdr_pcm_rx { // SDR path: the backend pipeline provides demodulated PCM. - info!("[{}] using SDR audio source — cpal capture disabled", rig_cfg.id); + info!( + "[{}] using SDR audio source — cpal capture disabled", + rig_cfg.id + ); let pcm_tx_clone = pcm_tx.clone(); handles.push(tokio::spawn(async move { loop { @@ -661,7 +662,10 @@ async fn main() -> DynResult<()> { } } } - let callsign = cli.callsign.clone().or_else(|| cfg.general.callsign.clone()); + let callsign = cli + .callsign + .clone() + .or_else(|| cfg.general.callsign.clone()); (callsign, cfg.general.latitude, cfg.general.longitude) }; @@ -799,6 +803,7 @@ async fn main() -> DynResult<()> { rig_id: rig_cfg.id.clone(), rig_tx, state_rx, + audio_port: rig_cfg.audio.port, }, ); } diff --git a/src/trx-server/src/rig_handle.rs b/src/trx-server/src/rig_handle.rs index ccadad2..761c1e7 100644 --- a/src/trx-server/src/rig_handle.rs +++ b/src/trx-server/src/rig_handle.rs @@ -20,4 +20,6 @@ pub struct RigHandle { pub rig_tx: mpsc::Sender, /// Watch the latest rig state for fast GetState/GetRigs responses. pub state_rx: watch::Receiver, + /// Per-rig audio listener TCP port. + pub audio_port: u16, }