autd3-rs 0.1.0

Core async client library for the AUTD3 phased-array kit.
Documentation
use crate::error::{Error, PayloadError};
use crate::geometry::Autd3;
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 zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, little_endian};

#[repr(C)]
#[derive(FromBytes, IntoBytes, Immutable, KnownLayout)]
struct PatternPayload {
    bank: u8,
    _reserved: u8,
    offset: little_endian::U32,
    len: little_endian::U16,
    emissions: [Emission; Autd3::NUM_TRANSDUCERS],
}

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

impl Operation for WritePatternBuffer<'_> {
    fn frames(&self) -> usize {
        1
    }

    fn distribution(&self) -> Distribution {
        Distribution::PerDevice
    }

    fn encode(
        &self,
        device: usize,
        _frame: usize,
        out: &mut [u8; PAYLOAD_BYTES],
    ) -> Result<Cmd, Error> {
        if device >= self.emissions.len() {
            return Err(Error::InvalidPayload(
                PayloadError::EmissionsDeviceOutOfRange {
                    device,
                    len: self.emissions.len(),
                },
            ));
        }
        if self.index >= EMISSION_MAX_INDICES {
            return Err(Error::InvalidPayload(
                PayloadError::PatternIndexOutOfRange {
                    index: self.index,
                    max: EMISSION_MAX_INDICES,
                },
            ));
        }
        let emissions = &self.emissions[device];
        if emissions.len() != Autd3::NUM_TRANSDUCERS {
            return Err(Error::InvalidPayload(
                PayloadError::TransducerCountMismatch {
                    device,
                    got: emissions.len(),
                    expected: Autd3::NUM_TRANSDUCERS,
                },
            ));
        }
        let offset =
            u32::try_from(self.index * EMISSION_SLOT_WORDS).expect("bounded by EMISSION_RAM_WORDS");
        let len = u16::try_from(Autd3::NUM_TRANSDUCERS * 2).expect("fits one frame");
        let (frame, _) =
            PatternPayload::mut_from_prefix(&mut out[..]).expect("PatternPayload fits the payload");
        frame.bank = self.bank.as_u8();
        frame.offset = offset.into();
        frame.len = len.into();
        frame.emissions.copy_from_slice(emissions);
        Ok(Cmd::WritePatternBuffer)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::operation::WRITE_HEADER_BYTES;
    use crate::value::{Intensity, Phase};

    #[test]
    fn write_pattern_lays_out_slot_words() {
        let mut emissions = vec![Emission::default(); Autd3::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(0, 0, &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], &498u16.to_le_bytes());
        for (i, e) in patterns[0].iter().enumerate() {
            assert_eq!(out[WRITE_HEADER_BYTES + 2 * i], e.phase.0);
            assert_eq!(out[WRITE_HEADER_BYTES + 2 * i + 1], e.intensity.0);
        }
    }

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

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