#![warn(clippy::all, clippy::pedantic)] use nautilus_common::udp::tokio::AsyncUdpSocketExt; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; mod command; mod telemetry; use crate::command::CommandHandler; use crate::telemetry::TelemetryHandler; use anyhow::{Result, anyhow, bail}; use api::client::Client; use futures::executor::block_on; use futures::future::{Either, select}; use log::{info, warn}; use nautilus_common::add_ctrlc_handler_cancel; use std::sync::Arc; use std::thread::{Builder, scope}; use tokio::net::UdpSocket; use tokio::pin; use tokio::sync::RwLock; use tokio_util::sync::CancellationToken; /// Run the Ground Software /// /// # Errors /// If any unhandled error occurred in the Ground Software pub fn run() -> Result<()> { info!( "Project Nautilus Ground Software {}", env!("CARGO_PKG_VERSION") ); let udp_shutdown = CancellationToken::new(); let cancel = udp_shutdown.child_token(); add_ctrlc_handler_cancel(cancel.clone())?; let (udp_thread, udp) = { let udp_shutdown = udp_shutdown.clone(); let (udp_tx, udp) = tokio::sync::oneshot::channel(); let udp_thread = Builder::new() .name("flight-udp-connection-handler".to_string()) .spawn(move || { let runtime = tokio::runtime::Builder::new_current_thread() .enable_all() .build()?; runtime.block_on(async { let udp = UdpSocket::bind("0.0.0.0:14000").await?; udp_tx .send(udp) .map_err(|_| anyhow!("Couldn't complete UDP establish"))?; Ok(()) as Result<()> })?; runtime.block_on(udp_shutdown.cancelled()); Ok(()) as Result<()> })?; let f1 = cancel.cancelled(); pin!(f1); let udp = block_on(async { let f2 = udp; select(f1, f2).await }); let udp = match udp { Either::Left(_) => bail!("Cancelled Before UDP established"), Either::Right(x) => x.0?, }; (udp_thread, udp) }; let flight_addr = RwLock::new(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0)); let lock = flight_addr.blocking_write(); scope(|scope| { let client = Arc::new(Client::connect("ws://localhost:8080/backend")?); let tlm = TelemetryHandler::new(client.clone(), &flight_addr, lock, &udp, cancel.clone()); let cmd = CommandHandler::new(client, &flight_addr, &udp, cancel.clone()); let tlm_thread = Builder::new() .name("telemetry-handler".to_string()) .spawn_scoped(scope, move || tlm.run())?; // move to take ownership (drop when exited) let cmd_thread = Builder::new() .name("command-handler".to_string()) .spawn_scoped(scope, move || cmd.run())?; // Force the thread panics into anyhow errors tlm_thread.join().map_err(|e| anyhow!("{e:?}"))??; cmd_thread.join().map_err(|e| anyhow!("{e:?}"))??; Ok(()) as Result<()> })?; info!("Sending Shutdown Command"); if let Ok(flight_addr) = flight_addr.try_read() { block_on(async { udp.send_command("/shutdown", &(), *flight_addr).await })?; } udp_shutdown.cancel(); udp_thread.join().map_err(|e| anyhow!("{e:?}"))??; Ok(()) }