autd3-rs 0.5.0

Core async client library for the AUTD3 phased-array kit.
Documentation
use crate::error::Error;
use crate::params::NUM_BANKS;
use crate::value::{LoopBehavior, TransitionMode};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BankLoop {
    Infinite,
    Finite,
}

impl BankLoop {
    #[must_use]
    pub const fn of(loop_behavior: LoopBehavior) -> Self {
        match loop_behavior {
            LoopBehavior::Infinite => BankLoop::Infinite,
            LoopBehavior::Finite(_) => BankLoop::Finite,
        }
    }

    #[must_use]
    pub const fn accepts(self, mode: TransitionMode) -> bool {
        if mode.is_later() {
            return true;
        }
        match self {
            BankLoop::Infinite => {
                matches!(mode, TransitionMode::Immediate | TransitionMode::Ext)
            }
            BankLoop::Finite => matches!(
                mode,
                TransitionMode::SyncIdx
                    | TransitionMode::SysTime {
                        time: _,
                        margin: None
                    }
                    | TransitionMode::Gpio(_)
            ),
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TransitionGuardState {
    pub mod_loop: [BankLoop; NUM_BANKS],
    pub pattern_loop: [BankLoop; NUM_BANKS],
}

impl TransitionGuardState {
    #[must_use]
    pub fn boot_default() -> Self {
        Self {
            mod_loop: [BankLoop::Infinite; NUM_BANKS],
            pattern_loop: [BankLoop::Infinite; NUM_BANKS],
        }
    }

    pub fn note_mod_loop(&mut self, bank: u8, loop_behavior: LoopBehavior) {
        self.mod_loop[usize::from(bank)] = BankLoop::of(loop_behavior);
    }

    pub fn note_pattern_loop(&mut self, bank: u8, loop_behavior: LoopBehavior) {
        self.pattern_loop[usize::from(bank)] = BankLoop::of(loop_behavior);
    }

    pub fn check_mod_bank(
        &self,
        device: usize,
        bank: u8,
        mode: TransitionMode,
    ) -> Result<(), Error> {
        check(device, self.mod_loop[usize::from(bank)], mode)
    }

    pub fn check_pattern_bank(
        &self,
        device: usize,
        bank: u8,
        mode: TransitionMode,
    ) -> Result<(), Error> {
        check(device, self.pattern_loop[usize::from(bank)], mode)
    }
}

fn check(device: usize, bank_loop: BankLoop, mode: TransitionMode) -> Result<(), Error> {
    if bank_loop.accepts(mode) {
        Ok(())
    } else {
        Err(Error::TransitionConstraint {
            device,
            transition_mode: mode,
            bank_loop,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::value::{DcSysTime, GpioIn};
    use core::num::NonZeroU16;

    const FINITE: LoopBehavior = LoopBehavior::ONCE;
    const INFINITE: LoopBehavior = LoopBehavior::Infinite;

    #[test]
    fn boot_default_is_all_infinite() {
        let g = TransitionGuardState::boot_default();
        assert_eq!(g.mod_loop, [BankLoop::Infinite; NUM_BANKS]);
        assert_eq!(g.pattern_loop, [BankLoop::Infinite; NUM_BANKS]);
    }

    #[test]
    fn infinite_accepts_only_immediate_and_ext() {
        assert!(BankLoop::Infinite.accepts(TransitionMode::Immediate));
        assert!(BankLoop::Infinite.accepts(TransitionMode::Ext));
        assert!(!BankLoop::Infinite.accepts(TransitionMode::SyncIdx));
        assert!(!BankLoop::Infinite.accepts(TransitionMode::SysTime {
            time: DcSysTime::from_nanos(0),
            margin: None
        }));
        assert!(!BankLoop::Infinite.accepts(TransitionMode::Gpio(GpioIn::I0)));
    }

    #[test]
    fn finite_accepts_only_timed_modes() {
        assert!(BankLoop::Finite.accepts(TransitionMode::SyncIdx));
        assert!(BankLoop::Finite.accepts(TransitionMode::SysTime {
            time: DcSysTime::from_nanos(0),
            margin: None
        }));
        assert!(BankLoop::Finite.accepts(TransitionMode::Gpio(GpioIn::I0)));
        assert!(!BankLoop::Finite.accepts(TransitionMode::Immediate));
        assert!(!BankLoop::Finite.accepts(TransitionMode::Ext));
    }

    #[test]
    fn later_is_outside_the_loop_constraint() {
        assert!(BankLoop::Infinite.accepts(TransitionMode::Later));
        assert!(BankLoop::Finite.accepts(TransitionMode::Later));
    }

    #[test]
    fn check_mod_bank_uses_target_bank_loop() {
        let mut g = TransitionGuardState::boot_default();
        g.note_mod_loop(1, LoopBehavior::Finite(NonZeroU16::new(5).unwrap()));

        assert!(g.check_mod_bank(0, 0, TransitionMode::Immediate).is_ok());
        assert!(matches!(
            g.check_mod_bank(2, 1, TransitionMode::Immediate),
            Err(Error::TransitionConstraint {
                device: 2,
                transition_mode: TransitionMode::Immediate,
                bank_loop: BankLoop::Finite,
            })
        ));
        assert!(g.check_mod_bank(0, 1, TransitionMode::SyncIdx).is_ok());
    }

    #[test]
    fn check_pattern_bank_uses_target_bank_loop() {
        let mut g = TransitionGuardState::boot_default();
        g.note_pattern_loop(0, FINITE);
        g.note_pattern_loop(1, INFINITE);

        assert!(
            g.check_pattern_bank(0, 0, TransitionMode::Gpio(GpioIn::I1))
                .is_ok()
        );
        assert!(g.check_pattern_bank(0, 0, TransitionMode::Ext).is_err());
        assert!(g.check_pattern_bank(0, 1, TransitionMode::Ext).is_ok());
        assert!(g.check_pattern_bank(0, 1, TransitionMode::SyncIdx).is_err());
    }
}