autd3-rs 0.4.0

Core async client library for the AUTD3 phased-array kit.
Documentation
use crate::error::{Error, PayloadError};
use crate::geometry::Device;
use crate::params::{EMISSION_MAX_INDICES, EMISSION_SLOT_WORDS};
use crate::protocol::{Cmd, PAYLOAD_BYTES};
use crate::value::{Emission, PatternBank};

use super::{Distribution, Operation};
use autd3_cpu_wire::payload::WritePatternPayload;
use zerocopy::little_endian::{U16, U32};
use zerocopy::{FromBytes, IntoBytes};

#[derive(Clone, Copy, Debug)]
pub struct WritePatternBuffer<'a> {
    pub bank: PatternBank,
    pub index: usize,
    pub emissions: &'a [Vec<Emission>],
}

impl Operation for WritePatternBuffer<'_> {
    fn distribution(&self) -> Distribution {
        Distribution::PerDevice
    }

    fn encode(&self, device: &Device, out: &mut [u8; PAYLOAD_BYTES]) -> Result<Cmd, Error> {
        let emissions =
            self.emissions
                .get(device.idx())
                .ok_or(PayloadError::EmissionsDeviceOutOfRange {
                    device: device.idx(),
                    len: self.emissions.len(),
                })?;
        if self.index >= EMISSION_MAX_INDICES {
            return Err(PayloadError::PatternIndexOutOfRange {
                index: self.index,
                max: EMISSION_MAX_INDICES,
            }
            .into());
        }
        if emissions.len() != device.num_transducers() {
            return Err(PayloadError::TransducerCountMismatch {
                device: device.idx(),
                got: emissions.len(),
                expected: device.num_transducers(),
            }
            .into());
        }
        let offset =
            u32::try_from(self.index * EMISSION_SLOT_WORDS).expect("bounded by EMISSION_RAM_WORDS");
        let bytes = emissions.as_bytes();
        let (h, rest) = WritePatternPayload::mut_from_prefix(&mut out[..]).unwrap();
        if bytes.len() > rest.len() {
            return Err(PayloadError::PatternWriteExceedsCapacity {
                device: device.idx(),
                len: bytes.len(),
                capacity: rest.len(),
            }
            .into());
        }
        let len = u16::try_from(bytes.len()).expect("bounded by frame capacity");
        *h = WritePatternPayload {
            bank: self.bank.as_u8(),
            reserved: 0,
            offset: U32::new(offset),
            data_len: U16::new(len),
        };
        rest[..bytes.len()].copy_from_slice(bytes);
        Ok(Cmd::WritePatternBuffer)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::test_utils::test_device;
    use crate::value::{Intensity, Phase};
    const HEADER_BYTES: usize = core::mem::size_of::<WritePatternPayload>();

    #[test]
    fn write_pattern_lays_out_slot_words() {
        let dev = test_device(0);
        let mut emissions = vec![Emission::default(); dev.num_transducers()];
        for (i, e) in emissions.iter_mut().enumerate() {
            e.phase = Phase(u8::try_from(i % 251).unwrap());
            e.intensity = Intensity(u8::try_from((i * 3) % 256).unwrap());
        }
        let patterns = [emissions];
        let op = WritePatternBuffer {
            bank: PatternBank::B1,
            index: 3,
            emissions: &patterns,
        };

        let mut out = [0u8; PAYLOAD_BYTES];
        let cmd = op.encode(&dev, &mut out).unwrap();

        assert_eq!(cmd, Cmd::WritePatternBuffer);
        assert_eq!(out[0], 1);
        let expected_offset = u32::try_from(3 * EMISSION_SLOT_WORDS).unwrap();
        assert_eq!(&out[2..6], &expected_offset.to_le_bytes());
        assert_eq!(
            &out[6..8],
            &u16::try_from(dev.num_transducers() * 2)
                .unwrap()
                .to_le_bytes()
        );
        for (i, e) in patterns[0].iter().enumerate() {
            assert_eq!(out[HEADER_BYTES + 2 * i], e.phase.0);
            assert_eq!(out[HEADER_BYTES + 2 * i + 1], e.intensity.0);
        }
    }

    #[test]
    fn write_pattern_rejects_index_out_of_range() {
        let dev = test_device(0);
        let patterns = [vec![Emission::default(); dev.num_transducers()]];
        let op = WritePatternBuffer {
            bank: PatternBank::B0,
            index: EMISSION_MAX_INDICES,
            emissions: &patterns,
        };
        let mut out = [0u8; PAYLOAD_BYTES];
        assert!(matches!(
            op.encode(&dev, &mut out),
            Err(Error::InvalidPayload(_))
        ));
    }

    #[test]
    fn write_pattern_rejects_device_out_of_range() {
        let dev = test_device(0);
        let patterns = [vec![Emission::default(); dev.num_transducers()]];
        let op = WritePatternBuffer {
            bank: PatternBank::B0,
            index: 0,
            emissions: &patterns,
        };
        let mut out = [0u8; PAYLOAD_BYTES];
        assert!(op.encode(&dev, &mut out).is_ok());
        assert!(matches!(
            op.encode(&test_device(1), &mut out),
            Err(Error::InvalidPayload(_))
        ));
    }
}