use alloc::{ffi::CString, string::String};
use core::ffi::CStr;
use no_std_io::io;
use pros_core::{
bail_errno, bail_on,
error::{FromErrno, PortError},
map_errno,
};
use pros_sys::{link_receive, link_transmit, E_LINK_RECEIVER, E_LINK_TRANSMITTER};
use snafu::Snafu;
use super::{SmartDevice, SmartDeviceType, SmartPort};
pub trait Link: SmartDevice {
fn id(&self) -> &CStr;
fn connected(&self) -> bool {
unsafe { pros_sys::link_connected(self.port_index()) }
}
fn new(port: SmartPort, id: String, vexlink_override: bool) -> Result<Self, LinkError>
where
Self: Sized;
}
#[derive(Debug)]
pub struct RxLink {
port: SmartPort,
id: CString,
}
impl RxLink {
pub fn num_incoming_bytes(&self) -> Result<u32, LinkError> {
let num = unsafe {
bail_on!(
pros_sys::PROS_ERR as _,
pros_sys::link_raw_receivable_size(self.port.index())
)
};
Ok(num)
}
pub fn clear_incoming_buf(&self) -> Result<(), LinkError> {
unsafe {
bail_on!(
pros_sys::PROS_ERR as _,
pros_sys::link_clear_receive_buf(self.port.index())
)
};
Ok(())
}
pub fn receive(&self, buf: &mut [u8]) -> Result<u32, LinkError> {
const PROS_ERR_U32: u32 = pros_sys::PROS_ERR as _;
match unsafe { link_receive(self.port.index(), buf.as_mut_ptr().cast(), buf.len() as _) } {
PROS_ERR_U32 => {
bail_errno!();
unreachable!("Expected errno to be set");
}
0 => Err(LinkError::Busy),
n => Ok(n),
}
}
}
impl Link for RxLink {
fn id(&self) -> &CStr {
&self.id
}
fn new(port: SmartPort, id: String, vexlink_override: bool) -> Result<Self, LinkError> {
let id = CString::new(id).unwrap();
unsafe {
bail_on!(
pros_sys::PROS_ERR as _,
if vexlink_override {
pros_sys::link_init(port.index(), id.as_ptr().cast(), E_LINK_RECEIVER)
} else {
pros_sys::link_init_override(port.index(), id.as_ptr().cast(), E_LINK_RECEIVER)
}
)
};
Ok(Self { port, id })
}
}
impl SmartDevice for RxLink {
fn port_index(&self) -> u8 {
self.port.index()
}
fn device_type(&self) -> SmartDeviceType {
SmartDeviceType::Radio
}
}
impl io::Read for RxLink {
fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
let bytes_read = self
.receive(dst)
.map_err(|_| io::Error::new(io::ErrorKind::Other, "failed to read from link"))?;
Ok(bytes_read as _)
}
}
#[derive(Debug)]
pub struct TxLink {
port: SmartPort,
id: CString,
}
impl TxLink {
pub fn num_outgoing_bytes(&self) -> Result<u32, LinkError> {
let num = unsafe {
bail_on!(
pros_sys::PROS_ERR as _,
pros_sys::link_raw_transmittable_size(self.port.index())
)
};
Ok(num)
}
pub fn transmit(&self, buf: &[u8]) -> Result<u32, LinkError> {
const PROS_ERR_U32: u32 = pros_sys::PROS_ERR as _;
match unsafe { link_transmit(self.port.index(), buf.as_ptr().cast(), buf.len() as _) } {
PROS_ERR_U32 => {
let errno = pros_core::error::take_errno();
Err(FromErrno::from_errno(errno)
.unwrap_or_else(|| panic!("Unknown errno code {errno}")))
}
0 => Err(LinkError::Busy),
n => Ok(n),
}
}
}
impl io::Write for TxLink {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let bytes_written = self
.transmit(buf)
.map_err(|_| io::Error::new(io::ErrorKind::Other, "failed to write to link"))?;
Ok(bytes_written as _)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl Link for TxLink {
fn id(&self) -> &CStr {
&self.id
}
fn new(port: SmartPort, id: String, vexlink_override: bool) -> Result<Self, LinkError> {
let id = CString::new(id).unwrap();
unsafe {
bail_on!(
pros_sys::PROS_ERR as _,
if vexlink_override {
pros_sys::link_init(port.index(), id.as_ptr().cast(), E_LINK_TRANSMITTER)
} else {
pros_sys::link_init_override(
port.index(),
id.as_ptr().cast(),
E_LINK_TRANSMITTER,
)
}
)
};
Ok(Self { port, id })
}
}
impl SmartDevice for TxLink {
fn port_index(&self) -> u8 {
self.port.index()
}
fn device_type(&self) -> SmartDeviceType {
SmartDeviceType::Radio
}
}
#[derive(Debug, Snafu)]
pub enum LinkError {
NoLink,
BufferBusyFull,
NullData,
Protocol,
Busy,
#[snafu(display("{source}"), context(false))]
Port {
source: PortError,
},
}
map_errno! {
LinkError {
ENXIO => Self::NoLink,
EBUSY => Self::BufferBusyFull,
EINVAL => Self::NullData,
EBADMSG => Self::Protocol,
}
inherit PortError;
}