use core::fmt::Display;
use std::io::{self, Read, Write};
use std::sync::MutexGuard;
use crate::MavlinkReader;
#[cfg(feature = "mav2-message-signing")]
use crate::SigningConfig;
use crate::connection_shared::{
ConnectionState, read_message_with_dialect, read_raw_message_with_dialect,
write_message_with_dialect, write_raw_message,
};
use crate::error::{MessageReadError, MessageWriteError};
use crate::static_dialect::StaticDialect;
use crate::{
Dialect, MAVLinkMessageRaw, MavFrame, MavHeader, MavlinkVersion, Message,
connectable::ConnectionAddress,
};
pub(crate) trait SyncTransport: Send + Sync {
type Reader: Read;
type Writer: Write;
fn reader(&self) -> MutexGuard<'_, MavlinkReader<Self::Reader>>;
fn lock_writer(&self) -> Option<MutexGuard<'_, Self::Writer>>;
fn retry_receive(&self, _error: &MessageReadError) -> bool {
false
}
fn set_nonblocking(
&self,
_reader: &mut MavlinkReader<Self::Reader>,
_enabled: bool,
) -> io::Result<()> {
Ok(())
}
}
pub type Connection<M> = DialectConnection<StaticDialect<M>>;
pub struct DialectConnection<D> {
transport: Transport,
dialect: D,
state: ConnectionState,
}
pub(crate) enum Transport {
#[cfg(feature = "transport-tcp")]
Tcp(super::tcp::TcpConnection),
#[cfg(feature = "transport-udp")]
Udp(super::udp::UdpConnection),
#[cfg(feature = "transport-direct-serial")]
Serial(super::direct_serial::SerialConnection),
File(super::file::FileConnection),
}
impl<D> DialectConnection<D> {
pub(crate) fn new(transport: Transport, dialect: D) -> Self {
Self {
transport,
dialect,
state: ConnectionState::new(),
}
}
}
pub trait MavConnection<M>: Send + Sync {
fn recv(&self) -> Result<(MavHeader, M), MessageReadError>;
fn recv_raw(&self) -> Result<MAVLinkMessageRaw, MessageReadError>;
fn try_recv(&self) -> Result<(MavHeader, M), MessageReadError>;
fn send(&self, header: &MavHeader, data: &M) -> Result<usize, MessageWriteError>;
fn send_raw(&self, data: &MAVLinkMessageRaw) -> Result<usize, MessageWriteError>;
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>) -> Result<usize, MessageWriteError>
where
M: Message,
{
self.send(&frame.header, &frame.msg)
}
fn recv_frame(&self) -> Result<MavFrame<M>, MessageReadError>
where
M: Message,
{
let (header, msg) = self.recv()?;
Ok(MavFrame {
header,
msg,
protocol_version: self.protocol_version(),
})
}
fn send_default(&self, data: &M) -> Result<usize, MessageWriteError> {
self.send(&MavHeader::default(), data)
}
#[cfg(feature = "mav2-message-signing")]
fn setup_signing(&mut self, signing_data: Option<SigningConfig>);
}
impl<D: Dialect + Send + Sync> MavConnection<D::Message> for DialectConnection<D> {
fn recv(&self) -> Result<(MavHeader, D::Message), MessageReadError> {
dispatch_transport!(&self.transport, |transport| {
let mut reader = transport.reader();
loop {
match read_message_with_dialect(&mut reader, &self.state, &self.dialect) {
Ok(message) => return Ok(message),
Err(error) if transport.retry_receive(&error) => {}
Err(error) => return Err(error),
}
}
})
}
fn recv_raw(&self) -> Result<MAVLinkMessageRaw, MessageReadError> {
dispatch_transport!(&self.transport, |transport| {
let mut reader = transport.reader();
loop {
match read_raw_message_with_dialect(&mut reader, &self.state, &self.dialect) {
Ok(message) => return Ok(message),
Err(error) if transport.retry_receive(&error) => {}
Err(error) => return Err(error),
}
}
})
}
fn try_recv(&self) -> Result<(MavHeader, D::Message), MessageReadError> {
dispatch_transport!(&self.transport, |transport| {
let mut reader = transport.reader();
transport.set_nonblocking(&mut reader, true)?;
let result = read_message_with_dialect(&mut reader, &self.state, &self.dialect);
transport.set_nonblocking(&mut reader, false)?;
result
})
}
fn send(&self, header: &MavHeader, data: &D::Message) -> Result<usize, MessageWriteError> {
dispatch_transport!(&self.transport, |transport| {
let Some(mut writer) = transport.lock_writer() else {
return Ok(0);
};
let header = self.state.next_header(header);
write_message_with_dialect(&mut *writer, &self.state, header, &self.dialect, data)
})
}
fn send_raw(&self, data: &MAVLinkMessageRaw) -> Result<usize, MessageWriteError> {
dispatch_transport!(&self.transport, |transport| {
let Some(mut writer) = transport.lock_writer() else {
return Ok(0);
};
write_raw_message(&mut *writer, data)
})
}
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 fn connect<M: Message + Sync + Send>(address: &str) -> io::Result<Connection<M>> {
ConnectionAddress::parse_address(address)?.connect::<M>()
}
pub fn connect_with_dialect<D: Dialect + Send + Sync>(
address: &str,
dialect: D,
) -> io::Result<DialectConnection<D>> {
ConnectionAddress::parse_address(address)?.connect_with_dialect(dialect)
}
pub trait Connectable: Display {
fn connect<M: Message>(&self) -> io::Result<Connection<M>> {
self.connect_with_dialect(StaticDialect::<M>::new())
}
fn connect_with_dialect<D: Dialect + Send + Sync>(
&self,
dialect: D,
) -> io::Result<DialectConnection<D>>;
}
impl Connectable for ConnectionAddress {
fn connect_with_dialect<D: Dialect + Send + Sync>(
&self,
dialect: D,
) -> io::Result<DialectConnection<D>> {
match self {
#[cfg(feature = "transport-tcp")]
Self::Tcp(c) => c.connect_with_dialect(dialect),
#[cfg(feature = "transport-udp")]
Self::Udp(c) => c.connect_with_dialect(dialect),
#[cfg(feature = "transport-direct-serial")]
Self::Serial(c) => c.connect_with_dialect(dialect),
Self::File(c) => c.connect_with_dialect(dialect),
}
}
}