use core::fmt;
use embassy_futures::select::{Either, select};
use embassy_time::{Duration, Timer};
use embedded_hal_bus::i2c::CriticalSectionDevice;
use esp_hal::{
delay::Delay,
gpio::{Event, Input, InputConfig, Pull},
peripherals::GPIO11,
};
use crate::{Error, I2cBus, I2cDevice, I2cPins, i2c, keys};
#[derive(Debug)]
pub struct KeyboardPins {
pub i2c: I2cPins,
pub interrupt: GPIO11<'static>,
}
const FALLBACK_POLL: Duration = Duration::from_secs(1);
const SPURIOUS_BACKOFF: Duration = Duration::from_millis(10);
const MAX_INPUTS: usize = 10;
pub struct Keyboard {
keys: keys::Keyboard<I2cDevice>,
interrupt: Input<'static>,
pending: bool,
missed: u32,
}
enum Wake {
Interrupt,
Fallback,
Pending,
}
impl Keyboard {
pub async fn wait_for_inputs(&mut self) -> Result<impl Iterator<Item = keys::KeyInput>, Error> {
loop {
let wake = if core::mem::take(&mut self.pending) {
Wake::Pending
} else {
match select(self.interrupt.wait_for_low(), Timer::after(FALLBACK_POLL)).await {
Either::First(()) => Wake::Interrupt,
Either::Second(()) => Wake::Fallback,
}
};
let events = self.keys.events().map_err(|_| Error::Keyboard)?;
let cleared = self.keys.clear_interrupts().is_ok();
self.pending = !cleared || self.keys.events_available().unwrap_or(true);
let mut events = events.peekable();
let any_event = events.peek().is_some();
let mut inputs = [None; MAX_INPUTS];
for (slot, input) in inputs
.iter_mut()
.zip(events.filter_map(|event| event.input))
{
*slot = Some(input);
}
if !any_event {
if matches!(wake, Wake::Interrupt) {
Timer::after(SPURIOUS_BACKOFF).await;
}
continue;
}
if matches!(wake, Wake::Fallback) {
self.missed = self.missed.saturating_add(1);
}
if inputs[0].is_none() {
continue;
}
return Ok(inputs.into_iter().flatten());
}
}
pub fn listen_for_wake(&mut self) {
self.interrupt.listen(Event::LowLevel);
}
#[must_use]
pub const fn missed_interrupts(&self) -> u32 {
self.missed
}
pub const fn driver_mut(&mut self) -> &mut keys::Keyboard<I2cDevice> {
&mut self.keys
}
}
pub fn keyboard(pins: KeyboardPins) -> Result<Keyboard, Error> {
let bus = i2c(pins.i2c)?;
keyboard_on(bus, pins.interrupt)
}
const KEYBOARD_TRIES: u32 = 5;
const KEYBOARD_RETRY_MS: u32 = 20;
pub fn keyboard_on(bus: &'static I2cBus, interrupt: GPIO11<'static>) -> Result<Keyboard, Error> {
let set_up = || -> Result<keys::Keyboard<I2cDevice>, Error> {
let mut keys =
keys::Keyboard::new(CriticalSectionDevice::new(bus)).map_err(|_| Error::Keyboard)?;
keys.enable_keyboard_interrupt()
.map_err(|_| Error::Keyboard)?;
keys.clear_interrupts().map_err(|_| Error::Keyboard)?;
Ok(keys)
};
let keys = (0..KEYBOARD_TRIES)
.find_map(|attempt| {
if attempt > 0 {
Delay::new().delay_millis(KEYBOARD_RETRY_MS);
}
set_up().ok()
})
.ok_or(Error::Keyboard)?;
let interrupt = Input::new(interrupt, InputConfig::default().with_pull(Pull::Up));
Ok(Keyboard {
keys,
interrupt,
pending: false,
missed: 0,
})
}
impl fmt::Debug for Keyboard {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Keyboard")
.field("pending", &self.pending)
.field("missed_interrupts", &self.missed)
.finish_non_exhaustive()
}
}