keebrs 0.3.0

Keyboard firmware building blocks
//! Traits for keyboard matrix scanning
//!
//! You shouldn't have to implement any of these yourself. MxN matrices are
//! supported up to 26 rows/columns via the `Pull` and `Read`
//! implementations on tuples of GPIO pins.

use embedded_hal::digital::v2::{
    InputPin,
    OutputPin,
};

/// Trait for setting a line high or low.
pub trait Pull {
    /// The number of pulled lines in this matrix
    const NPULL: usize;
    /// Activate the line
    fn pull(&mut self, x: usize);
    /// Deactivate the line
    fn release(&mut self, x: usize);
}

impl<'a, P> Pull for &'a mut P
where
    P: Pull,
{
    const NPULL: usize = P::NPULL;
    fn pull(&mut self, x: usize) {
        <P as Pull>::pull(self, x)
    }
    fn release(&mut self, x: usize) {
        <P as Pull>::release(self, x)
    }
}

/// Trait for reading the state of a line
pub trait Read {
    /// The number of lines in this matrix
    const NREAD: usize;
    /// Get the state of the line
    fn read(&self, y: usize) -> bool;
}

impl<'a, R> Read for &'a mut R
where
    R: Read,
{
    const NREAD: usize = R::NREAD;
    fn read(&self, y: usize) -> bool {
        <R as Read>::read(self, y)
    }
}

impl<'a, R> Read for &'a R
where
    R: Read,
{
    const NREAD: usize = R::NREAD;
    fn read(&self, y: usize) -> bool {
        <R as Read>::read(self, y)
    }
}

/// Trait combining [Pull] and [Read] to represent a full matrix
pub trait Scan: Pull + Read {}

impl<M> Scan for M where M: Pull + Read {}

/// Struct implementing [Scan]
///
/// # Example
/// ```rust,ignore
/// let matrix = Matrix {
///     pull: (pa1, pa2, pa3, pa4),
///     read: (pb1, pb2, pb3, pb4),
/// }:
/// ```
pub struct Matrix<P, R> {
    /// The pulled lines of this matrix
    pub pull: P,
    /// The read lines of this matrix
    pub read: R,
}

impl<P, R> Read for Matrix<P, R>
where
    R: Read,
{
    const NREAD: usize = R::NREAD;
    #[inline]
    fn read(&self, y: usize) -> bool {
        self.read.read(y)
    }
}

impl<P, R> Pull for Matrix<P, R>
where
    P: Pull,
{
    const NPULL: usize = P::NPULL;
    #[inline]
    fn pull(&mut self, x: usize) {
        self.pull.pull(x)
    }
    #[inline]
    fn release(&mut self, x: usize) {
        self.pull.release(x)
    }
}

macro_rules! replace_expr {
    ($_t:tt $sub:expr) => {
        $sub
    };
}

macro_rules! count_tts {
    ($($tts:tt)*) => {0usize $(+ replace_expr!($tts 1usize))*};
}

macro_rules! impl_scan {
    (@impl $($pos:tt: $slot:ident),*) => {
        impl<$($slot : OutputPin),*> Pull for ($($slot),*)
        {
            const NPULL: usize = count_tts!($($slot)*);

            #[inline]
            fn pull(&mut self, x: usize) {
                match x {
                    $(
                        $pos => if self.$pos.set_low().is_err() {
                            panic!(concat!("error setting ", $pos, " low"));
                        },
                    )*
                    _ => unreachable!(),
                }
            }

            #[inline]
            fn release(&mut self, x: usize) {
                match x {
                    $(
                        $pos => if self.$pos.set_high().is_err() {
                            panic!(concat!("error setting ", $pos, " high"));
                        },
                    )*
                    _ => unreachable!(),
                }
            }
        }

        impl<$($slot : InputPin),*> Read for ($($slot),*)
        {
            const NREAD: usize = count_tts!($($slot)*);

            #[inline]
            fn read(&self, y: usize) -> bool {
                match y {
                    $(
                        $pos => self.$pos.is_low().unwrap_or(false),
                    )*
                    _ => unreachable!(),
                }
            }
        }
    };

    (@rec ($($cur_pos:tt: $cur_slot:ident),*) $next_pos:tt: $next_slot:ident , $($pos:tt: $slot:ident),*) => {
        impl_scan!(@impl $($cur_pos: $cur_slot,)* $next_pos: $next_slot);
        impl_scan!(@rec ($($cur_pos: $cur_slot,)* $next_pos: $next_slot) $($pos: $slot),*);
    };

    (@rec ($($cur_pos:tt: $cur_slot:ident),*) $next_pos:tt: $next_slot:ident) => {};

    ($zero:tt: $first:ident, $($pos:tt: $slot:ident),* $(,)*) => {
        impl_scan!(@rec ($zero: $first) $($pos: $slot),*);
    }
}
#[cfg(not(feature = "exhaustive"))]
impl_scan!(
    0: A,
    1: B,
    2: C,
    3: D,
    4: E,
    5: F,
    6: G,
    7: H,
    8: I,
    9: J,
    10: K,
    11: L,
    12: M,
    13: N,
    14: O,
);

#[cfg(feature = "exhaustive")]
impl_scan!(
    0: A,
    1: B,
    2: C,
    3: D,
    4: E,
    5: F,
    6: G,
    7: H,
    8: I,
    9: J,
    10: K,
    11: L,
    12: M,
    13: N,
    14: O,
    15: P,
    16: Q,
    17: R,
    18: S,
    19: T,
    20: U,
    21: V,
    22: W,
    23: X,
    24: Y,
    25: Z,
);