autd3-rs 0.7.0

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

use super::LegacyOperation;
use crate::legacy::error::LegacyError;
use crate::legacy::wire::{InfoType, Tag};

#[repr(C)]
#[derive(Clone, Copy, IntoBytes, Immutable)]
struct TagPair {
    tag: u8,
    value: u8,
}

fn write<T: IntoBytes + Immutable>(tx: &mut [u8], msg: &T) -> usize {
    let bytes = msg.as_bytes();
    tx[..bytes.len()].copy_from_slice(bytes);
    bytes.len()
}

macro_rules! tag_pair_op {
    ($name:ident, $tag:expr, $value:expr) => {
        impl LegacyOperation for $name {
            fn required_size(&self, _device: &Device) -> usize {
                size_of::<TagPair>()
            }

            fn pack(&mut self, _device: &Device, tx: &mut [u8]) -> Result<usize, LegacyError> {
                let value = $value(self);
                self.done = true;
                Ok(write(
                    tx,
                    &TagPair {
                        tag: $tag.as_u8(),
                        value,
                    },
                ))
            }

            fn is_done(&self) -> bool {
                self.done
            }
        }
    };
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Nop {
    done: bool,
}

impl Nop {
    #[must_use]
    pub const fn new() -> Self {
        Self { done: false }
    }
}

tag_pair_op!(Nop, Tag::Nop, |_: &Nop| 0u8);

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Clear {
    done: bool,
}

impl Clear {
    #[must_use]
    pub const fn new() -> Self {
        Self { done: false }
    }
}

tag_pair_op!(Clear, Tag::Clear, |_: &Clear| 0u8);

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Sync {
    done: bool,
}

impl Sync {
    #[must_use]
    pub const fn new() -> Self {
        Self { done: false }
    }
}

tag_pair_op!(Sync, Tag::Sync, |_: &Sync| 0u8);

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FirmInfo {
    ty: InfoType,
    done: bool,
}

impl FirmInfo {
    #[must_use]
    pub const fn new(ty: InfoType) -> Self {
        Self { ty, done: false }
    }
}

tag_pair_op!(FirmInfo, Tag::FirmInfo, |s: &FirmInfo| s.ty.as_u8());

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ForceFan {
    value: bool,
    done: bool,
}

impl ForceFan {
    #[must_use]
    pub const fn new(value: bool) -> Self {
        Self { value, done: false }
    }
}

tag_pair_op!(ForceFan, Tag::ForceFan, |s: &ForceFan| u8::from(s.value));

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ReadsFpgaState {
    value: bool,
    done: bool,
}

impl ReadsFpgaState {
    #[must_use]
    pub const fn new(value: bool) -> Self {
        Self { value, done: false }
    }
}

tag_pair_op!(ReadsFpgaState, Tag::ReadsFpgaState, |s: &ReadsFpgaState| {
    u8::from(s.value)
});

#[cfg(test)]
mod tests {
    use autd3_rs_core::geometry::{Autd3, Geometry};

    use super::*;

    fn geometry() -> Geometry {
        Geometry::new(vec![Autd3::default()])
    }

    fn packed<O: LegacyOperation>(mut op: O) -> [u8; 2] {
        let geo = geometry();
        let mut tx = [0xFFu8; 2];
        assert_eq!(op.required_size(&geo[0]), 2);
        assert!(!op.is_done());
        assert_eq!(op.pack(&geo[0], &mut tx).unwrap(), 2);
        assert!(op.is_done());
        tx
    }

    #[test]
    fn tag_only_ops_pad_the_second_byte_with_zero() {
        assert_eq!(packed(Nop::new()), [Tag::Nop.as_u8(), 0x00]);
        assert_eq!(packed(Clear::new()), [Tag::Clear.as_u8(), 0x00]);
        assert_eq!(packed(Sync::new()), [Tag::Sync.as_u8(), 0x00]);
    }

    #[test]
    fn firm_info_encodes_the_info_type() {
        for ty in [
            InfoType::CpuMajor,
            InfoType::CpuMinor,
            InfoType::FpgaMajor,
            InfoType::FpgaMinor,
            InfoType::FpgaFunctions,
            InfoType::Clear,
        ] {
            assert_eq!(
                packed(FirmInfo::new(ty)),
                [Tag::FirmInfo.as_u8(), ty.as_u8()]
            );
        }
    }

    #[test]
    fn bool_ops_encode_zero_or_one() {
        for value in [false, true] {
            assert_eq!(
                packed(ForceFan::new(value)),
                [Tag::ForceFan.as_u8(), u8::from(value)]
            );
            assert_eq!(
                packed(ReadsFpgaState::new(value)),
                [Tag::ReadsFpgaState.as_u8(), u8::from(value)]
            );
        }
    }
}