use embedded_hal::digital::InputPin;
#[cfg(feature = "async_matrix")]
use embedded_hal_async::digital::Wait;
use postcard::experimental::max_size::MaxSize;
use rmk_macro::input_device;
use serde::{Deserialize, Serialize};
use crate::event::KeyboardEvent;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[input_device(publish = KeyboardEvent)]
pub struct RotaryEncoder<
#[cfg(feature = "async_matrix")] A: InputPin + Wait,
#[cfg(not(feature = "async_matrix"))] A: InputPin,
#[cfg(feature = "async_matrix")] B: InputPin + Wait,
#[cfg(not(feature = "async_matrix"))] B: InputPin,
P: Phase,
> {
pin_a: A,
pin_b: B,
state: u8,
phase: P,
id: u8,
last_action: Option<Direction>,
last_event_time: Option<embassy_time::Instant>,
debounce_ms: u16,
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug, MaxSize, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Direction {
Clockwise,
CounterClockwise,
None,
}
pub trait Phase {
fn direction(&mut self, s: u8) -> Direction;
}
pub struct DefaultPhase;
impl Phase for DefaultPhase {
fn direction(&mut self, s: u8) -> Direction {
match s {
0b0001 | 0b0111 | 0b1000 | 0b1110 => Direction::Clockwise,
0b0010 | 0b0100 | 0b1011 | 0b1101 => Direction::CounterClockwise,
_ => Direction::None,
}
}
}
pub struct E8H7Phase;
impl Phase for E8H7Phase {
fn direction(&mut self, s: u8) -> Direction {
match s {
0b0010 | 0b1101 => Direction::Clockwise,
0b0001 | 0b1110 => Direction::CounterClockwise,
_ => Direction::None,
}
}
}
pub struct ResolutionPhase {
resolution: u8,
lut: [i8; 16],
current_pulses: i8,
}
impl ResolutionPhase {
pub fn new(resolution: u8, reverse: bool) -> Self {
let mut lut = [0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0];
if reverse {
lut = lut.map(|x| -x);
}
Self {
resolution,
lut,
current_pulses: 0,
}
}
pub fn new_with_detent_and_pulse(detent: u8, pulse: u8, reverse: bool) -> Self {
let mut lut = [0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0];
if reverse {
lut = lut.map(|x| -x);
}
Self {
resolution: pulse * 4 / detent,
lut,
current_pulses: 0,
}
}
}
impl Phase for ResolutionPhase {
fn direction(&mut self, s: u8) -> Direction {
if (s & 0xC) != (s & 0x3) {
self.current_pulses += self.lut[s as usize & 0xF];
if self.current_pulses >= self.resolution as i8 {
self.current_pulses %= self.resolution as i8;
return Direction::CounterClockwise;
} else if self.current_pulses <= -(self.resolution as i8) {
self.current_pulses %= self.resolution as i8;
return Direction::Clockwise;
}
}
Direction::None
}
}
impl<
#[cfg(feature = "async_matrix")] A: InputPin + Wait,
#[cfg(not(feature = "async_matrix"))] A: InputPin,
#[cfg(feature = "async_matrix")] B: InputPin + Wait,
#[cfg(not(feature = "async_matrix"))] B: InputPin,
> RotaryEncoder<A, B, DefaultPhase>
{
pub fn new(pin_a: A, pin_b: B, id: u8) -> Self {
Self {
pin_a,
pin_b,
state: 0u8,
phase: DefaultPhase,
id,
last_action: None,
last_event_time: None,
debounce_ms: 0,
}
}
}
impl<
#[cfg(feature = "async_matrix")] A: InputPin + Wait,
#[cfg(not(feature = "async_matrix"))] A: InputPin,
#[cfg(feature = "async_matrix")] B: InputPin + Wait,
#[cfg(not(feature = "async_matrix"))] B: InputPin,
> RotaryEncoder<A, B, ResolutionPhase>
{
pub fn with_resolution(pin_a: A, pin_b: B, resolution: u8, reverse: bool, id: u8) -> Self {
Self {
pin_a,
pin_b,
state: 0u8,
phase: ResolutionPhase::new(resolution, reverse),
id,
last_action: None,
last_event_time: None,
debounce_ms: 0,
}
}
}
impl<
#[cfg(feature = "async_matrix")] A: InputPin + Wait,
#[cfg(not(feature = "async_matrix"))] A: InputPin,
#[cfg(feature = "async_matrix")] B: InputPin + Wait,
#[cfg(not(feature = "async_matrix"))] B: InputPin,
P: Phase,
> RotaryEncoder<A, B, P>
{
pub fn with_phase(pin_a: A, pin_b: B, phase: P, id: u8) -> Self {
Self {
pin_a,
pin_b,
state: 0u8,
phase,
id,
last_action: None,
last_event_time: None,
debounce_ms: 0,
}
}
pub fn with_debounce(mut self, debounce_ms: u16) -> Self {
self.debounce_ms = debounce_ms;
self
}
pub fn update(&mut self) -> Direction {
let mut s = self.state & 0b11;
let (a_is_low, b_is_low) = (self.pin_a.is_low(), self.pin_b.is_low());
match a_is_low {
Ok(true) => s |= 0b0100,
Ok(false) => {}
Err(_) => return Direction::None,
}
match b_is_low {
Ok(true) => s |= 0b1000,
Ok(false) => {}
Err(_) => return Direction::None,
}
self.state = s >> 2;
self.phase.direction(s)
}
pub fn pin_a(&mut self) -> &mut A {
&mut self.pin_a
}
pub fn pin_b(&mut self) -> &mut B {
&mut self.pin_b
}
pub fn pins(&mut self) -> (&mut A, &mut B) {
(&mut self.pin_a, &mut self.pin_b)
}
pub fn into_inner(self) -> (A, B) {
(self.pin_a, self.pin_b)
}
fn debounce_check(&mut self) -> bool {
let now = embassy_time::Instant::now();
let ok = match self.last_event_time {
Some(last) => now.duration_since(last).as_millis() >= self.debounce_ms as u64,
None => true,
};
if ok {
self.last_event_time = Some(now);
}
ok
}
}
impl<
#[cfg(feature = "async_matrix")] A: InputPin + Wait,
#[cfg(not(feature = "async_matrix"))] A: InputPin,
#[cfg(feature = "async_matrix")] B: InputPin + Wait,
#[cfg(not(feature = "async_matrix"))] B: InputPin,
P: Phase,
> RotaryEncoder<A, B, P>
{
async fn read_keyboard_event(&mut self) -> KeyboardEvent {
if let Some(last_action) = self.last_action {
embassy_time::Timer::after_millis(5).await;
self.last_action = None;
return KeyboardEvent::rotary_encoder(self.id, last_action, false);
}
loop {
#[cfg(feature = "async_matrix")]
{
let (pin_a, pin_b) = self.pins();
embassy_futures::select::select(pin_a.wait_for_any_edge(), pin_b.wait_for_any_edge()).await;
}
let direction = self.update();
if direction != Direction::None && self.debounce_check() {
self.last_action = Some(direction);
return KeyboardEvent::rotary_encoder(self.id, direction, true);
}
#[cfg(not(feature = "async_matrix"))]
{
embassy_time::Timer::after_millis(20).await;
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_resolutin_phase() {
let mut default_phase = E8H7Phase {};
let mut resolution_phase = ResolutionPhase::new(2, true);
for item in [0b100, 0b1101, 0b1011, 0b10] {
let d = default_phase.direction(item);
let d2 = resolution_phase.direction(item);
info!("Item: {:b}, {:?} {:?}", item, d, d2);
assert_eq!(d, d2);
}
for item in [0b1000, 0b1110, 0b111, 0b1] {
let d = default_phase.direction(item);
let d2 = resolution_phase.direction(item);
info!("Item: {:b}, {:?} {:?}", item, d, d2);
assert_eq!(d, d2);
}
let mut default_phase = DefaultPhase {};
let mut resolution_phase = ResolutionPhase::new(1, false);
for item in 0u8..16 {
let d = default_phase.direction(item);
let d2 = resolution_phase.direction(item);
info!("Item: {:b}, {:?} {:?}", item, d, d2);
assert_eq!(d, d2);
}
}
}