use std::{future::Future, io};
use futures::lock::{Mutex, MutexGuard};
use tokio::io::{AsyncRead, AsyncWrite};
use crate::AsyncMavlinkReader;
#[cfg(feature = "mav2-message-signing")]
use crate::SigningConfig;
use crate::error::{MessageReadError, MessageWriteError};
use crate::static_dialect::StaticDialect;
use crate::{
Dialect, MAVLinkMessageRaw, MavFrame, MavHeader, MavlinkVersion, Message,
connectable::ConnectionAddress,
};
#[doc(hidden)]
pub(crate) trait AsyncTransport: Send + Sync {
type Reader: AsyncRead + Unpin + Send;
type Writer: AsyncWrite + Unpin + Send;
fn reader(&self) -> &Mutex<AsyncMavlinkReader<Self::Reader>>;
fn lock_writer(&self) -> impl Future<Output = Option<MutexGuard<'_, Self::Writer>>> + Send;
fn retry_receive(&self, _error: &MessageReadError) -> bool {
false
}
}
pub type AsyncConnection<M> = AsyncDialectConnection<StaticDialect<M>>;
pub struct AsyncDialectConnection<D> {
transport: Transport,
dialect: D,
state: crate::connection_shared::ConnectionState,
}
pub(crate) enum Transport {
#[cfg(feature = "transport-tcp")]
Tcp(super::tcp::AsyncTcpConnection),
#[cfg(feature = "transport-udp")]
Udp(super::udp::AsyncUdpConnection),
#[cfg(feature = "transport-direct-serial")]
Serial(super::direct_serial::AsyncSerialConnection),
File(super::file::AsyncFileConnection),
}
impl<D> AsyncDialectConnection<D> {
pub(crate) fn new(transport: Transport, dialect: D) -> Self {
Self {
transport,
dialect,
state: crate::connection_shared::ConnectionState::new(),
}
}
}
pub trait AsyncMavConnection<M: Sync + Send>: Send + Sync {
fn recv(&self) -> impl Future<Output = Result<(MavHeader, M), MessageReadError>> + Send;
fn recv_raw(&self) -> impl Future<Output = Result<MAVLinkMessageRaw, MessageReadError>> + Send;
fn try_recv(&self) -> impl Future<Output = Result<(MavHeader, M), MessageReadError>> + Send;
fn send(
&self,
header: &MavHeader,
data: &M,
) -> impl Future<Output = Result<usize, MessageWriteError>> + Send;
fn send_raw(
&self,
data: &MAVLinkMessageRaw,
) -> impl Future<Output = Result<usize, MessageWriteError>> + Send;
fn set_protocol_version(&mut self, version: MavlinkVersion);
fn protocol_version(&self) -> MavlinkVersion;
fn set_allow_recv_any_version(&mut self, allow: bool);
fn allow_recv_any_version(&self) -> bool;
fn send_frame(
&self,
frame: &MavFrame<M>,
) -> impl Future<Output = Result<usize, MessageWriteError>> + Send
where
M: Message,
{
self.send(&frame.header, &frame.msg)
}
fn recv_frame(&self) -> impl Future<Output = Result<MavFrame<M>, MessageReadError>> + Send
where
M: Message,
{
async move {
let (header, msg) = self.recv().await?;
Ok(MavFrame {
header,
msg,
protocol_version: self.protocol_version(),
})
}
}
fn send_default(
&self,
data: &M,
) -> impl Future<Output = Result<usize, MessageWriteError>> + Send {
async move { self.send(&MavHeader::default(), data).await }
}
#[cfg(feature = "mav2-message-signing")]
fn setup_signing(&mut self, signing_data: Option<SigningConfig>);
}
impl<D: Dialect + Send + Sync> AsyncMavConnection<D::Message> for AsyncDialectConnection<D>
where
D::Message: Send + Sync,
{
async fn recv(&self) -> Result<(MavHeader, D::Message), MessageReadError> {
dispatch_transport!(&self.transport, |transport| {
let mut reader = transport.reader().lock().await;
loop {
match crate::connection_shared::read_message_async_with_dialect(
&mut reader,
&self.state,
&self.dialect,
)
.await
{
Ok(message) => return Ok(message),
Err(error) if transport.retry_receive(&error) => {}
Err(error) => return Err(error),
}
}
})
}
async fn recv_raw(&self) -> Result<MAVLinkMessageRaw, MessageReadError> {
dispatch_transport!(&self.transport, |transport| {
let mut reader = transport.reader().lock().await;
loop {
match crate::connection_shared::read_raw_message_async_with_dialect(
&mut reader,
&self.state,
&self.dialect,
)
.await
{
Ok(message) => return Ok(message),
Err(error) if transport.retry_receive(&error) => {}
Err(error) => return Err(error),
}
}
})
}
async fn try_recv(&self) -> Result<(MavHeader, D::Message), MessageReadError> {
dispatch_transport!(&self.transport, |transport| {
let mut reader = transport.reader().lock().await;
crate::connection_shared::read_message_async_with_dialect(
&mut reader,
&self.state,
&self.dialect,
)
.await
})
}
async fn send(
&self,
header: &MavHeader,
data: &D::Message,
) -> Result<usize, MessageWriteError> {
dispatch_transport!(&self.transport, |transport| {
let Some(mut writer) = transport.lock_writer().await else {
return Ok(0);
};
let header = self.state.next_header(header);
crate::connection_shared::write_message_async_with_dialect(
&mut *writer,
&self.state,
header,
&self.dialect,
data,
)
.await
})
}
async fn send_raw(&self, data: &MAVLinkMessageRaw) -> Result<usize, MessageWriteError> {
dispatch_transport!(&self.transport, |transport| {
let Some(mut writer) = transport.lock_writer().await else {
return Ok(0);
};
crate::connection_shared::write_raw_message_async(&mut *writer, data).await
})
}
fn set_protocol_version(&mut self, version: MavlinkVersion) {
self.state.set_protocol_version(version);
}
fn protocol_version(&self) -> MavlinkVersion {
self.state.protocol_version()
}
fn set_allow_recv_any_version(&mut self, allow: bool) {
self.state.set_allow_recv_any_version(allow);
}
fn allow_recv_any_version(&self) -> bool {
self.state.allow_recv_any_version()
}
#[cfg(feature = "mav2-message-signing")]
fn setup_signing(&mut self, signing_data: Option<SigningConfig>) {
self.state.setup_signing(signing_data);
}
}
pub async fn connect_async<M: Message + Sync + Send>(
address: &str,
) -> io::Result<AsyncConnection<M>> {
ConnectionAddress::parse_address(address)?
.connect_async()
.await
}
pub async fn connect_async_with_dialect<D: Dialect + Send + Sync>(
address: &str,
dialect: D,
) -> io::Result<AsyncDialectConnection<D>>
where
D::Message: Send + Sync,
{
ConnectionAddress::parse_address(address)?
.connect_async_with_dialect(dialect)
.await
}
pub trait AsyncConnectable: Sync {
fn connect_async<M: Message + Sync + Send>(
&self,
) -> impl Future<Output = io::Result<AsyncConnection<M>>> + Send {
self.connect_async_with_dialect(StaticDialect::<M>::new())
}
fn connect_async_with_dialect<D: Dialect + Send + Sync>(
&self,
dialect: D,
) -> impl Future<Output = io::Result<AsyncDialectConnection<D>>> + Send
where
D::Message: Send + Sync;
}
impl AsyncConnectable for ConnectionAddress {
async fn connect_async_with_dialect<D: Dialect + Send + Sync>(
&self,
dialect: D,
) -> io::Result<AsyncDialectConnection<D>>
where
D::Message: Send + Sync,
{
match self {
#[cfg(feature = "transport-tcp")]
Self::Tcp(c) => c.connect_async_with_dialect(dialect).await,
#[cfg(feature = "transport-udp")]
Self::Udp(c) => c.connect_async_with_dialect(dialect).await,
#[cfg(feature = "transport-direct-serial")]
Self::Serial(c) => c.connect_async_with_dialect(dialect).await,
Self::File(c) => c.connect_async_with_dialect(dialect).await,
}
}
}