xpanse-api 0.1.1

Shared API for xpanse apps, module drivers, and platform firmware for the hackxpansion console
//! UART bus abstraction.
//!
//! Wraps a concrete backend (hardware, PIO, or bit-banged) behind a single
//! boxed trait object so that drivers can operate on a `UartBusHandle`
//! without knowing which backend was selected.
//!
//! [`BusAllocator`](crate::bus::allocator::BusAllocator) is the entry point used to
//! allocate UART buses at startup.

use alloc::boxed::Box;
use core::future::Future;
use core::pin::Pin;

/// Error returned by UART operations.
#[derive(Debug, Clone, Copy, PartialEq, Eq, defmt::Format)]
pub enum UartError {
    BufferFull,
    InvalidBaudRate,
    Overrun,
    Break,
    Parity,
    Framing,
    /// Any other error reported by the underlying backend.
    Other,
}

impl core::fmt::Display for UartError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "{:?}", self)
    }
}

impl core::error::Error for UartError {}

impl embedded_io_async::Error for UartError {
    fn kind(&self) -> embedded_io_async::ErrorKind {
        embedded_io_async::ErrorKind::Other
    }
}

impl From<embassy_rp::uart::Error> for UartError {
    fn from(error: embassy_rp::uart::Error) -> Self {
        match error {
            embassy_rp::uart::Error::Overrun => Self::Overrun,
            embassy_rp::uart::Error::Break => Self::Break,
            embassy_rp::uart::Error::Parity => Self::Parity,
            embassy_rp::uart::Error::Framing => Self::Framing,
            _ => Self::Other,
        }
    }
}

/// Backend variant of a [`UartBusHandle`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, defmt::Format)]
pub enum UartBusVersion {
    /// Hardware UART peripheral.
    Hardware,
    /// PIO-based programmable UART.
    Pio,
    /// Pure GPIO bit-bang.
    BitBang,
}

/// Trait-object-safe async UART bus.
pub trait DynUartBus: Send {
    fn write<'a>(
        &'a mut self,
        buf: &'a [u8],
    ) -> Pin<Box<dyn Future<Output = Result<usize, UartError>> + 'a>>;
    fn read<'a>(
        &'a mut self,
        buf: &'a mut [u8],
    ) -> Pin<Box<dyn Future<Output = Result<usize, UartError>> + 'a>>;
    fn flush<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), UartError>> + 'a>>;
}

/// Owned handle to an async UART bus.
///
/// Dropping this handle does not return its startup resources, so keep it alive
/// for as long as you need UART access.
#[must_use = "dropping a bus handle does not return its startup resources"]
pub struct UartBusHandle {
    inner: Box<dyn DynUartBus>,
    version: UartBusVersion,
}

impl UartBusHandle {
    /// Wraps a boxed backend and records its [`UartBusVersion`].
    pub fn new(inner: Box<dyn DynUartBus>, version: UartBusVersion) -> Self {
        Self { inner, version }
    }

    /// Returns the backend variant.
    pub fn version(&self) -> UartBusVersion {
        self.version
    }
}

impl embedded_io_async::ErrorType for UartBusHandle {
    type Error = UartError;
}

impl embedded_io_async::Read for UartBusHandle {
    async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
        self.inner.read(buf).await
    }
}

impl embedded_io_async::Write for UartBusHandle {
    async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
        self.inner.write(buf).await
    }

    async fn flush(&mut self) -> Result<(), Self::Error> {
        self.inner.flush().await
    }
}

impl<T> DynUartBus for T
where
    T: embedded_io_async::Read<Error = UartError> + embedded_io_async::Write<Error = UartError>,
    T: Send,
{
    fn write<'a>(
        &'a mut self,
        buf: &'a [u8],
    ) -> Pin<Box<dyn Future<Output = Result<usize, UartError>> + 'a>> {
        Box::pin(async move { embedded_io_async::Write::write(self, buf).await })
    }

    fn read<'a>(
        &'a mut self,
        buf: &'a mut [u8],
    ) -> Pin<Box<dyn Future<Output = Result<usize, UartError>> + 'a>> {
        Box::pin(async move { embedded_io_async::Read::read(self, buf).await })
    }

    fn flush<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<(), UartError>> + 'a>> {
        Box::pin(async move { embedded_io_async::Write::flush(self).await })
    }
}