autd3-rs 0.5.0

Core async client library for the AUTD3 phased-array kit.
Documentation
use zerocopy::{FromBytes, Immutable, IntoBytes};

pub const TX_FRAME_BYTES: usize = 626;
pub const RX_FRAME_BYTES: usize = 2;
pub const HEADER_BYTES: usize = 4;
pub const PAYLOAD_BYTES: usize = TX_FRAME_BYTES - HEADER_BYTES;

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct MsgId(u8);

impl MsgId {
    pub const MAX: Self = Self(0x0F);

    #[must_use]
    pub const fn new(id: u8) -> Self {
        Self(id)
    }

    #[must_use]
    pub const fn get(self) -> u8 {
        self.0
    }

    #[must_use]
    pub const fn next(self) -> Self {
        if self.0 >= Self::MAX.0 {
            Self(0)
        } else {
            Self(self.0 + 1)
        }
    }
}

#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Ack(u8);

impl Ack {
    #[must_use]
    pub const fn new(msg_id: u8, err: u8) -> Self {
        Self((err & 0x0F) << 4 | (msg_id & 0x0F))
    }

    #[must_use]
    pub const fn from_bits(bits: u8) -> Self {
        Self(bits)
    }

    #[must_use]
    pub const fn bits(self) -> u8 {
        self.0
    }

    #[must_use]
    pub const fn msg_id(self) -> u8 {
        self.0 & 0x0F
    }

    #[must_use]
    pub const fn err(self) -> u8 {
        (self.0 >> 4) & 0x0F
    }
}

impl core::fmt::Debug for Ack {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("Ack")
            .field("msg_id", &self.msg_id())
            .field("err", &self.err())
            .finish()
    }
}

#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, FromBytes, IntoBytes, Immutable)]
pub struct Header {
    pub msg_id: u8,
    pad: u8,
    pub slot_2_offset: u16,
}

const _: () = assert!(size_of::<Header>() == HEADER_BYTES);

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TxFrame {
    pub header: Header,
    pub payload: [u8; PAYLOAD_BYTES],
}

impl Default for TxFrame {
    fn default() -> Self {
        Self::new()
    }
}

impl TxFrame {
    #[must_use]
    pub const fn new() -> Self {
        Self {
            header: Header {
                msg_id: 0,
                pad: 0,
                slot_2_offset: 0,
            },
            payload: [0; PAYLOAD_BYTES],
        }
    }

    pub fn write_to(&self, dst: &mut [u8; TX_FRAME_BYTES]) {
        dst[..HEADER_BYTES].copy_from_slice(self.header.as_bytes());
        dst[HEADER_BYTES..].copy_from_slice(&self.payload);
    }

    #[cfg(test)]
    #[must_use]
    pub fn to_bytes(&self) -> [u8; TX_FRAME_BYTES] {
        let mut bytes = [0u8; TX_FRAME_BYTES];
        self.write_to(&mut bytes);
        bytes
    }

    #[must_use]
    pub fn parse(src: &[u8; TX_FRAME_BYTES]) -> Self {
        let header = Header::read_from_bytes(&src[..HEADER_BYTES]).expect("header is 4 bytes");
        let mut payload = [0u8; PAYLOAD_BYTES];
        payload.copy_from_slice(&src[HEADER_BYTES..]);
        Self { header, payload }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RxFrame {
    pub data: u8,
    pub ack: Ack,
}

impl RxFrame {
    #[must_use]
    pub const fn new(data: u8, ack: Ack) -> Self {
        Self { data, ack }
    }

    #[must_use]
    pub const fn parse(src: [u8; RX_FRAME_BYTES]) -> Self {
        Self {
            data: src[0],
            ack: Ack::from_bits(src[1]),
        }
    }

    pub const fn write_to(self, dst: &mut [u8; RX_FRAME_BYTES]) {
        dst[0] = self.data;
        dst[1] = self.ack.bits();
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn msg_id_wraps_at_0x0f() {
        assert_eq!(MsgId::new(0).next(), MsgId::new(1));
        assert_eq!(MsgId::new(0x0E).next(), MsgId::new(0x0F));
        assert_eq!(MsgId::new(0x0F).next(), MsgId::new(0));
    }

    #[test]
    fn ack_packs_err_high_msg_id_low() {
        let ack = Ack::new(0x05, 0x03);
        assert_eq!(ack.bits(), 0x35);
        assert_eq!(ack.msg_id(), 5);
        assert_eq!(ack.err(), 3);
        assert_eq!(format!("{ack:?}"), "Ack { msg_id: 5, err: 3 }");
    }

    #[test]
    fn tx_frame_round_trips_bytes() {
        let mut frame = TxFrame::new();
        frame.header.msg_id = 0x0A;
        frame.header.slot_2_offset = 0x0123;
        frame.payload[0] = 0x30;
        frame.payload[PAYLOAD_BYTES - 1] = 0xFF;

        let bytes = frame.to_bytes();
        assert_eq!(bytes[0], 0x0A);
        assert_eq!(bytes[1], 0x00);
        assert_eq!(&bytes[2..4], &0x0123u16.to_le_bytes());
        assert_eq!(bytes[4], 0x30);
        assert_eq!(bytes[TX_FRAME_BYTES - 1], 0xFF);
        assert_eq!(TxFrame::parse(&bytes), frame);
    }

    #[test]
    fn rx_frame_round_trips_bytes() {
        let rx = RxFrame::new(0xAB, Ack::new(3, 1));
        let mut bytes = [0u8; RX_FRAME_BYTES];
        rx.write_to(&mut bytes);
        assert_eq!(bytes, [0xAB, 0x13]);
        assert_eq!(RxFrame::parse(bytes), rx);
    }
}