use core::fmt::Debug;
use core::num::TryFromIntError;
use std::borrow::Cow;
use std::num::NonZero;
use std::sync::Arc;
use std::sync::atomic::{AtomicU8, Ordering};
use std::time::Duration;
use ashv2::{
Actor, FlowControl, NativeSerialPort, Payload, Proxy, SerialPort, Tasks, TryCloneNative, open,
};
use le_stream::ToLeStream;
use log::{debug, error, info, trace, warn};
use tokio::spawn;
use tokio::sync::mpsc::{Receiver, Sender, WeakSender, channel};
use tokio::task::{JoinError, JoinHandle};
use tokio::time::sleep;
use tokio_task_pool::Pool;
pub use self::buffers::Buffers;
pub use self::channel_sizes::ChannelSizes;
use self::encoder::Encoder;
use crate::constants::MIN_NON_LEGACY_VERSION;
use crate::error::Error;
use crate::frame::{Command, Header, Parameter};
use crate::parameters::configuration::version;
use crate::transport::Transport;
use crate::uart::decoder::Decoder;
use crate::uart::splitter::Splitter;
use crate::{Callback, Configuration, Connection, Extended, Legacy, Parameters, ValueError};
mod buffers;
mod channel_sizes;
mod decoder;
mod encoder;
mod splitter;
const REQUEUE_GRACE_PERIOD: Duration = Duration::from_millis(100);
#[derive(Debug)]
pub struct Uart {
protocol_version: u8,
negotiated_version: Arc<AtomicU8>,
connection: Connection,
responses_tx: Sender<Result<Parameters, Error>>,
responses_rx: Receiver<Result<Parameters, Error>>,
encoder: Encoder,
splitter: JoinHandle<()>,
sequence: u8,
tasks: Pool,
}
impl Uart {
#[must_use]
pub fn new(
ash_proxy: Proxy,
ash_rx: Receiver<Payload>,
callbacks: Sender<Callback>,
protocol_version: u8,
channel_size: usize,
) -> Self {
let negotiated_version = Arc::new(AtomicU8::new(0));
let (responses_tx, responses_rx) = channel(channel_size);
let splitter = spawn(
Splitter::new(
Decoder::new(ash_rx, negotiated_version.clone()),
responses_tx.clone(),
callbacks,
)
.run(),
);
Self {
protocol_version,
negotiated_version,
connection: Connection::Disconnected,
encoder: Encoder::new(ash_proxy),
responses_tx,
responses_rx,
splitter,
sequence: 0,
tasks: Pool::bounded(channel_size),
}
}
pub fn from_serial_port<P>(
serial_port: P,
protocol_version: u8,
channel_sizes: &ChannelSizes,
) -> Result<(Self, Tasks<P>, Receiver<Callback>), Error>
where
P: SerialPort + TryCloneNative + Sync + 'static,
{
let (tx, rx) = channel(channel_sizes.payload);
let (tasks, proxy) = Actor::new(serial_port, tx, channel_sizes.message_queue)
.map_err(|error| Error::Io(error.into()))?
.spawn();
let (callbacks_tx, callbacks_rx) = channel(channel_sizes.callbacks);
Ok((
Self::new(
proxy,
rx,
callbacks_tx,
protocol_version,
channel_sizes.responses,
),
tasks,
callbacks_rx,
))
}
pub fn open<'a, P>(
path: P,
flow_control: FlowControl,
protocol_version: u8,
channel_sizes: &ChannelSizes,
) -> Result<(Self, Tasks<NativeSerialPort>, Receiver<Callback>), Error>
where
P: Into<Cow<'a, str>>,
{
Self::from_serial_port(
open(path, flow_control).map_err(|error| Error::Io(error.into()))?,
protocol_version,
channel_sizes,
)
}
fn next_header(&mut self, id: u16) -> Result<Header, TryFromIntError> {
let header = if self.negotiated_version.load(Ordering::Relaxed) < MIN_NON_LEGACY_VERSION {
Header::Legacy(Legacy::new(
self.sequence,
Command::default().into(),
id.try_into()?,
))
} else {
Header::Extended(Extended::new(self.sequence, Command::default().into(), id))
};
self.sequence = self.sequence.wrapping_add(1);
Ok(header)
}
async fn negotiate_version(&mut self) -> Result<version::Response, Error> {
debug!("Negotiating legacy version");
let mut response = self.version(self.protocol_version).await?;
self.negotiated_version
.store(response.protocol_version(), Ordering::Relaxed);
if response.protocol_version() >= MIN_NON_LEGACY_VERSION {
debug!("Negotiating non-legacy version");
response = self.version(response.protocol_version()).await?;
self.negotiated_version
.store(response.protocol_version(), Ordering::Relaxed);
}
if response.protocol_version() == self.protocol_version {
info!(
"Negotiated protocol version: {:#04X}",
response.protocol_version()
);
Ok(response)
} else {
self.connection = Connection::Failed;
Err(Error::ProtocolVersionMismatch {
desired: self.protocol_version,
negotiated: response,
})
}
}
pub async fn abort(self) -> Result<(), JoinError> {
self.splitter.abort();
self.splitter.await
}
}
impl Transport for Uart {
async fn connect(&mut self) -> Result<version::Response, Error> {
let response = self.negotiate_version().await?;
self.connection = Connection::Connected;
Ok(response)
}
fn state(&self) -> Connection {
self.connection
}
fn negotiated_version(&self) -> Option<NonZero<u8>> {
NonZero::<u8>::new(self.negotiated_version.load(Ordering::Relaxed))
}
async fn send<T>(&mut self, command: T) -> Result<u16, Error>
where
T: Parameter + ToLeStream,
{
let header = self
.next_header(T::ID)
.map_err(ValueError::InvalidFrameId)?;
let id = header.id();
self.encoder.send(header, command).await?;
Ok(id)
}
async fn receive<T>(&mut self) -> Result<T, Error>
where
T: TryFrom<Parameters> + Send,
<T as TryFrom<Parameters>>::Error: Into<Parameters> + Send,
{
loop {
let Some(parameters) = self.responses_rx.recv().await else {
return Err(Error::ChannelClosed);
};
match T::try_from(parameters?) {
Ok(frame) => return Ok(frame),
Err(error) => {
self.tasks
.spawn(requeue(
self.responses_tx.downgrade(),
error.into(),
REQUEUE_GRACE_PERIOD,
))
.await
.map_or_else(
|error| {
error!("Failed to re-queue response: {error:?}");
},
drop,
);
}
}
}
}
}
async fn requeue(
responses: WeakSender<Result<Parameters, Error>>,
parameters: Parameters,
delay: Duration,
) {
warn!(
"Received unexpected response: {parameters:?}, re-queueing in {}ms.",
delay.as_millis()
);
sleep(delay).await;
let Some(responses) = responses.upgrade() else {
trace!("Re-queueing channel is closed, aborting");
return;
};
responses
.send(Ok(parameters))
.await
.expect("Re-queueing channel should be open. This is a bug.");
}