@@ 0,0 1,99 @@
+use core::convert::Infallible;
+
+use alloc::boxed::Box;
+use core::marker::PhantomData;
+use embedded_hal::digital::v2::InputPin;
+
+pub struct ActiveHigh;
+pub struct ActiveLow;
+
+const DEBOUNCE: u8 = 2;
+const LONG_PRESS: u8 = 5;
+
+#[derive(PartialEq, Eq)]
+pub enum ButtonState {
+ Off,
+ JustPressed,
+ LongPress,
+ Click,
+ DoubleClick,
+ Released,
+}
+
+pub struct Button<ActiveLevel> {
+ pin: Box<dyn InputPin<Error = Infallible>>,
+ debounce: u8,
+ prev_state: bool,
+ level: PhantomData<ActiveLevel>,
+}
+
+pub trait ActiveLevel {
+ fn raw_is_pressed(pin_state: bool) -> bool;
+}
+
+impl ActiveLevel for ActiveHigh {
+ fn raw_is_pressed(pin_state: bool) -> bool {
+ pin_state
+ }
+}
+
+impl ActiveLevel for ActiveLow {
+ fn raw_is_pressed(pin_state: bool) -> bool {
+ !pin_state
+ }
+}
+
+impl<ACTIVELEVEL: ActiveLevel> Button<ACTIVELEVEL> {
+ pub fn new(pin: Box<dyn InputPin<Error = Infallible>>) -> Self {
+ Self {
+ pin,
+ debounce: 0,
+ prev_state: false,
+ level: PhantomData::<ACTIVELEVEL>,
+ }
+ }
+
+ pub fn raw_is_pressed(&self) -> bool {
+ ACTIVELEVEL::raw_is_pressed(self.pin.is_high().unwrap())
+ }
+
+ pub fn is_just_pressed(&self) -> bool {
+ self.raw_is_pressed() && self.debounce == DEBOUNCE
+ }
+
+ pub fn is_pressed(&self) -> bool {
+ self.raw_is_pressed() && self.debounce >= DEBOUNCE
+ }
+
+ pub fn is_long_pressed(&self) -> bool {
+ self.raw_is_pressed() && self.debounce >= LONG_PRESS
+ }
+
+ pub fn state(&self) -> ButtonState {
+ if !self.raw_is_pressed() {
+ ButtonState::Off
+ } else if self.is_just_pressed() {
+ ButtonState::JustPressed
+ } else if self.is_long_pressed() {
+ ButtonState::LongPress
+ } else {
+ ButtonState::Off
+ }
+ }
+
+ pub fn update(&mut self) {
+ let raw_pressed = self.raw_is_pressed();
+
+ if raw_pressed != self.prev_state {
+ self.debounce = 0;
+ } else if self.debounce < u8::MAX {
+ self.debounce += 1;
+ }
+
+ self.prev_state = raw_pressed;
+ }
+
+ pub fn reset() {
+ // resets is_clicked etc.
+ }
+}