keebrs 0.3.0

Keyboard firmware building blocks
//! Debouncing utilities

mod state;

use crate::{
    key::*,
    matrix::Scan,
};

pub use self::state::{
    DebounceState,
    State,
};

/// Debouncer utility
///
/// Keeps track of key state and reports changes only after they've been
/// debounced.
pub struct Debouncer<Sc, St, D> {
    scanner: Sc,
    state: St,
    threshold: usize,
    quick: bool,
    delay: D,
}

impl<Sc, St, D> Debouncer<Sc, St, D>
where
    Sc: Scan,
    St: State<Sc>,
    D: FnMut(),
{
    /// Create a new debouncer from a scanner and debounce configuration
    ///
    /// When `quick` is `true`, it wait `threshold` scans after the *first*
    /// change it sees to a key, and then read it again. If they key is still in
    /// the state from the first change, it is considered changed and returned.
    /// If not, then it's considered a spurious change and ignored.
    ///
    /// When `quick` is `false`, it will wait until a key has been in the same
    /// state for `threshold` scans, and then report it as changed. In the event
    /// of interference, this may report a key as changed to the same state it's
    /// already in.
    pub fn new(scanner: Sc, quick: bool, threshold: usize, delay: D) -> Self {
        Debouncer {
            scanner,
            threshold,
            quick,
            state: St::init(),
            delay,
        }
    }

    /// Scan an entire line and return any changed and debounced keys.
    pub fn debounce_x(&mut self, x: usize) -> Option<(usize, KeyState)> {
        let mut found = None;
        self.scanner.pull(x);
        (self.delay)();
        for y in 0..Sc::NREAD {
            if let Some(change) = self.debounce(x, y) {
                found = Some((y, change.into()));
                break;
            }
        }
        self.scanner.release(x);
        found
    }

    fn debounce(&mut self, x: usize, y: usize) -> Option<bool> {
        if self.quick {
            self.debounce_quick(x, y)
        } else {
            self.debounce_slow(x, y)
        }
    }

    fn debounce_quick(&mut self, x: usize, y: usize) -> Option<bool> {
        let state = self.state.get_state(x, y);

        match state {
            // If we're at a steady state, read the line unconditionally
            DebounceState::Steady(last_steady) => {
                let last_read = self.scanner.read(y);
                if last_steady != last_read {
                    self.state.set_state(
                        x,
                        y,
                        DebounceState::Bouncing {
                            last_steady,
                            last_read,
                            ctr: 0,
                        },
                    );
                }
                None
            }
            // Otherwise, wait till the threshold is reached, then read it again
            DebounceState::Bouncing {
                last_steady,
                last_read,
                ctr,
            } => {
                if ctr >= self.threshold {
                    let current = self.scanner.read(y);
                    // If it's the same as the original change we saw, it's
                    // good, return it
                    if current == last_read {
                        self.state.set_state(x, y, DebounceState::Steady(current));
                        Some(current)
                    } else {
                        // Otherwise, it's just noise. go back to old state
                        self.state
                            .set_state(x, y, DebounceState::Steady(last_steady));
                        None
                    }
                } else {
                    self.state.set_state(
                        x,
                        y,
                        DebounceState::Bouncing {
                            last_steady,
                            last_read,
                            ctr: ctr + 1,
                        },
                    );
                    None
                }
            }
        }
    }

    fn debounce_slow(&mut self, x: usize, y: usize) -> Option<bool> {
        let state = self.state.get_state(x, y);
        let current = self.scanner.read(y);
        match state {
            DebounceState::Steady(last_steady) => {
                if current != last_steady {
                    self.state.set_state(
                        x,
                        y,
                        DebounceState::Bouncing {
                            last_steady,
                            last_read: current,
                            ctr: 0,
                        },
                    );
                }
                None
            }
            DebounceState::Bouncing {
                last_steady,
                last_read,
                mut ctr,
            } => {
                if current == last_read {
                    if ctr >= self.threshold {
                        self.state.set_state(x, y, DebounceState::Steady(current));
                        Some(current)
                    } else {
                        ctr += 1;
                        self.state.set_state(
                            x,
                            y,
                            DebounceState::Bouncing {
                                last_steady,
                                last_read,
                                ctr,
                            },
                        );
                        None
                    }
                } else {
                    self.state.set_state(
                        x,
                        y,
                        DebounceState::Bouncing {
                            last_steady,
                            last_read: current,
                            ctr: 0,
                        },
                    );
                    None
                }
            }
        }
    }

    /// Change the debouncing algorithm. May do weird things if called while
    /// debouncing is in progress.
    pub fn change_alg(&mut self, quick: bool) {
        self.quick = quick;
    }
}