use crate::command::Command;
use crate::config::VehicleConfig;
use crate::error::VehicleError;
use crate::event_loop::run_event_loop;
use crate::mission::{HomePosition, MissionHandle, TransferProgress};
use crate::params::{ParamProgress, ParamStore, ParamsHandle};
use crate::state::{
Attitude, Battery, FlightMode, Gps, LinkState, MissionState, Navigation, Position, RcChannels,
StateChannels, StatusMessage, Telemetry, Terrain, VehicleIdentity, VehicleState,
create_channels,
};
use mavlink::common::{self, MavCmd};
use std::sync::Arc;
use tokio::sync::{mpsc, oneshot, watch};
use tokio_util::sync::CancellationToken;
#[derive(Clone)]
pub struct Vehicle {
pub(crate) inner: Arc<VehicleInner>,
}
pub(crate) struct VehicleInner {
pub(crate) command_tx: mpsc::Sender<Command>,
cancel: CancellationToken,
channels: StateChannels,
_config: VehicleConfig,
}
impl Drop for VehicleInner {
fn drop(&mut self) {
self.cancel.cancel();
}
}
impl Vehicle {
pub async fn connect(address: &str) -> Result<Self, VehicleError> {
Self::connect_with_config(address, VehicleConfig::default()).await
}
pub async fn connect_udp(bind_addr: &str) -> Result<Self, VehicleError> {
Self::connect(&format!("udpin:{bind_addr}")).await
}
pub async fn connect_tcp(addr: &str) -> Result<Self, VehicleError> {
Self::connect(&format!("tcpin:{addr}")).await
}
pub async fn connect_serial(port: &str, baud: u32) -> Result<Self, VehicleError> {
Self::connect(&format!("serial:{port}:{baud}")).await
}
pub async fn connect_with_config(
address: &str,
config: VehicleConfig,
) -> Result<Self, VehicleError> {
let connection = mavlink::connect_async::<common::MavMessage>(address)
.await
.map_err(|err| VehicleError::ConnectionFailed(err.to_string()))?;
Self::from_connection(connection, config).await
}
pub async fn from_connection(
connection: Box<dyn mavlink::AsyncMavConnection<common::MavMessage> + Sync + Send>,
config: VehicleConfig,
) -> Result<Self, VehicleError> {
let (writers, channels) = create_channels();
let cancel = CancellationToken::new();
let (command_tx, command_rx) = mpsc::channel(config.command_buffer_size);
let loop_cancel = cancel.clone();
let loop_config_timeout = config.connect_timeout;
let writers_for_loop = writers;
tokio::spawn(run_event_loop(
connection,
command_rx,
writers_for_loop,
config.clone(),
loop_cancel,
));
let vehicle = Vehicle {
inner: Arc::new(VehicleInner {
command_tx,
cancel,
channels,
_config: config,
}),
};
let mut vs_rx = vehicle.state();
let heartbeat_wait = async {
loop {
vs_rx
.changed()
.await
.map_err(|_| VehicleError::Disconnected)?;
let state = vs_rx.borrow().clone();
if state.heartbeat_received {
return Ok::<(), VehicleError>(());
}
}
};
tokio::select! {
result = heartbeat_wait => result?,
_ = tokio::time::sleep(loop_config_timeout) => {
return Err(VehicleError::Timeout);
}
}
Ok(vehicle)
}
pub fn state(&self) -> watch::Receiver<VehicleState> {
self.inner.channels.vehicle_state.clone()
}
pub fn telemetry(&self) -> watch::Receiver<Telemetry> {
self.inner.channels.telemetry.clone()
}
pub fn position(&self) -> watch::Receiver<Position> {
self.inner.channels.position.clone()
}
pub fn attitude(&self) -> watch::Receiver<Attitude> {
self.inner.channels.attitude.clone()
}
pub fn battery(&self) -> watch::Receiver<Battery> {
self.inner.channels.battery.clone()
}
pub fn gps(&self) -> watch::Receiver<Gps> {
self.inner.channels.gps.clone()
}
pub fn navigation(&self) -> watch::Receiver<Navigation> {
self.inner.channels.navigation.clone()
}
pub fn terrain(&self) -> watch::Receiver<Terrain> {
self.inner.channels.terrain.clone()
}
pub fn rc_channels(&self) -> watch::Receiver<RcChannels> {
self.inner.channels.rc_channels.clone()
}
pub fn home_position(&self) -> watch::Receiver<Option<HomePosition>> {
self.inner.channels.home_position.clone()
}
pub fn mission_state(&self) -> watch::Receiver<MissionState> {
self.inner.channels.mission_state.clone()
}
pub fn link_state(&self) -> watch::Receiver<LinkState> {
self.inner.channels.link_state.clone()
}
pub fn mission_progress(&self) -> watch::Receiver<Option<TransferProgress>> {
self.inner.channels.mission_progress.clone()
}
pub fn param_store(&self) -> watch::Receiver<ParamStore> {
self.inner.channels.param_store.clone()
}
pub fn param_progress(&self) -> watch::Receiver<ParamProgress> {
self.inner.channels.param_progress.clone()
}
pub fn statustext(&self) -> watch::Receiver<Option<StatusMessage>> {
self.inner.channels.statustext.clone()
}
pub async fn arm(&self, force: bool) -> Result<(), VehicleError> {
self.send_command(|reply| Command::Arm { force, reply })
.await
}
pub async fn disarm(&self, force: bool) -> Result<(), VehicleError> {
self.send_command(|reply| Command::Disarm { force, reply })
.await
}
pub async fn set_mode(&self, custom_mode: u32) -> Result<(), VehicleError> {
self.send_command(|reply| Command::SetMode { custom_mode, reply })
.await
}
pub async fn set_mode_by_name(&self, name: &str) -> Result<(), VehicleError> {
let state = self.inner.channels.vehicle_state.borrow().clone();
let custom_mode = crate::modes::mode_number(state.autopilot, state.vehicle_type, name)
.ok_or_else(|| VehicleError::ModeNotAvailable(name.to_string()))?;
self.set_mode(custom_mode).await
}
pub async fn takeoff(&self, altitude_m: f32) -> Result<(), VehicleError> {
self.command_long(
MavCmd::MAV_CMD_NAV_TAKEOFF,
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, altitude_m],
)
.await
}
pub async fn goto(&self, lat_deg: f64, lon_deg: f64, alt_m: f32) -> Result<(), VehicleError> {
let lat_e7 = (lat_deg * 1e7) as i32;
let lon_e7 = (lon_deg * 1e7) as i32;
self.send_command(|reply| Command::GuidedGoto {
lat_e7,
lon_e7,
alt_m,
reply,
})
.await
}
pub async fn command_long(&self, cmd: MavCmd, params: [f32; 7]) -> Result<(), VehicleError> {
self.send_command(|reply| Command::Long {
command: cmd,
params,
reply,
})
.await
}
pub async fn preflight_calibration(
&self,
gyro: bool,
accel: bool,
radio_trim: bool,
) -> Result<(), VehicleError> {
self.command_long(
MavCmd::MAV_CMD_PREFLIGHT_CALIBRATION,
[
if gyro { 1.0 } else { 0.0 }, 0.0, 0.0, if radio_trim { 1.0 } else { 0.0 }, if accel { 1.0 } else { 0.0 }, 0.0, 0.0, ],
)
.await
}
pub fn available_modes(&self) -> Vec<FlightMode> {
let state = self.inner.channels.vehicle_state.borrow().clone();
crate::modes::available_modes(state.autopilot, state.vehicle_type)
}
pub fn identity(&self) -> Option<VehicleIdentity> {
let state = self.inner.channels.vehicle_state.borrow().clone();
if state.mode_name.is_empty() && !state.armed && state.custom_mode == 0 {
return None;
}
Some(VehicleIdentity {
system_id: state.system_id,
component_id: state.component_id,
autopilot: state.autopilot,
vehicle_type: state.vehicle_type,
})
}
pub fn mission(&self) -> MissionHandle<'_> {
MissionHandle::new(self)
}
pub fn params(&self) -> ParamsHandle<'_> {
ParamsHandle::new(self)
}
pub async fn disconnect(self) -> Result<(), VehicleError> {
let _ = self.inner.command_tx.send(Command::Shutdown).await;
Ok(())
}
pub(crate) async fn send_command<T>(
&self,
make: impl FnOnce(oneshot::Sender<Result<T, VehicleError>>) -> Command,
) -> Result<T, VehicleError> {
let (tx, rx) = oneshot::channel();
self.inner
.command_tx
.send(make(tx))
.await
.map_err(|_| VehicleError::Disconnected)?;
rx.await.map_err(|_| VehicleError::Disconnected)?
}
}