Skip to content

Commit

Permalink
Track multiple holds
Browse files Browse the repository at this point in the history
  • Loading branch information
maxwase committed May 26, 2024
1 parent 3edaa3b commit b46ff04
Show file tree
Hide file tree
Showing 4 changed files with 227 additions and 37 deletions.
38 changes: 26 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,30 @@ fn main() -> Result<(), EspError> {
loop {
button.tick();

if button.is_clicked() {
info!("Click");
} else if button.is_double_clicked() {
info!("Double click");
} else if button.is_triple_clicked() {
info!("Triple click");
} else if let Some(dur) = button.current_holding_time() {
info!("Held for {dur:?}");
} else if let Some(dur) = button.held_time() {
info!("Total holding time {time:?}");
if let Some(dur) = button.held_time() {
info!("Total holding time {:?}", dur);

if button.is_clicked() {
info!("Clicked + held");
} else if button.is_double_clicked() {
info!("Double clicked + held");
} else if button.holds() == 2 && button.clicks() > 0 {
info!("Held twice with {} clicks", button.clicks());
} else if button.holds() == 2 {
info!("Held twice");
}
} else {
if button.is_clicked() {
led_pin.set_low();
info!("Click");
} else if button.is_double_clicked() {
led_pin.set_high();
info!("Double click");
} else if button.is_triple_clicked() {
info!("Triple click");
} else if let Some(dur) = button.current_holding_time() {
info!("Held for {:?}", dur);
}
}

button.reset();
Expand All @@ -56,6 +70,6 @@ fn main() -> Result<(), EspError> {
2. Debounce strategies [support](/~https://github.com/maxwase/button-driver/issues/12)

## Algorithm
High level state-machine diagram
High-level state machine diagram

<img src="/~https://github.com/maxwase/button-driver/assets/23321756/e066694a-2379-4805-82e5-18e4a3a557de" width=150% height=150%>
![button-driver drawio](/~https://github.com/maxwase/button-driver/assets/23321756/fd19165a-6107-4a7d-8050-9897afd523c6)
53 changes: 41 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub struct Button<P, I, D = Duration> {
state: State<I>,
clicks: usize,
held: Option<D>,
holds: usize,
config: ButtonConfig<D>,
}

Expand Down Expand Up @@ -105,11 +106,12 @@ where
config,
state: State::Unknown,
clicks: 0,
holds: 0,
held: None,
}
}

/// Returns number of clicks that happened before last release.
/// Returns the number of clicks that happened before the last release.
/// Returns 0 if clicks are still being counted or a new streak has started.
pub fn clicks(&self) -> usize {
if self.state == State::Released {
Expand All @@ -119,6 +121,16 @@ where
}
}

/// Returns the number of holds (how many times the button was held) that happened before the last release.
/// Returns 0 if clicks or holds are still being counted or a new streak has started.
pub fn holds(&self) -> usize {
if self.state == State::Released {
self.holds
} else {
0
}
}

/// Resets clicks amount and held time after release.
///
/// Example:
Expand All @@ -140,6 +152,7 @@ where
pub fn reset(&mut self) {
if self.state == State::Released {
self.clicks = 0;
self.holds = 0;
self.held = None;
}
}
Expand All @@ -159,10 +172,14 @@ where
self.clicks() == 3
}

/// Returns holing duration before last release.
/// Returns [None] if the button is still being held or was not held at all.
/// Returns holing duration before the last release.
/// Returns [None] if the button is still being held, not released or was not held at all.
pub fn held_time(&self) -> Option<D> {
self.held.clone()
if self.state == State::Released {
self.held.clone()
} else {
None
}
}

/// Returns current holding duration.
Expand All @@ -176,21 +193,26 @@ where
}

/// Returns current button state.
pub fn raw_state(&self) -> &State<I> {
pub const fn raw_state(&self) -> &State<I> {
&self.state
}

/// Returns current amount of clicks, ignoring release timeout.
pub fn raw_clicks(&self) -> usize {
pub const fn raw_clicks(&self) -> usize {
self.clicks
}

/// Returns current amount of holds (how many times the button was held), ignoring release timeout.
pub const fn raw_holds(&self) -> usize {
self.holds
}

/// Updates button state.
/// Call as frequently as you can, ideally in a loop in separate thread or interrupt.
pub fn tick(&mut self) {
match self.state.clone() {
State::Unknown if self.is_pin_pressed() => {
self.clicks = 1;
self.clicks += 1;
self.state = State::Down(I::now());
}
State::Unknown if self.is_pin_released() => self.state = State::Released,
Expand All @@ -209,7 +231,9 @@ where
State::Pressed(elapsed) => {
if self.is_pin_pressed() {
if elapsed.elapsed() >= self.config.hold {
self.clicks = 0;
// Do not count a click that leads to a hold
self.clicks -= 1;
self.holds += 1;
self.state = State::Held(elapsed.clone());
} else {
// holding
Expand All @@ -232,13 +256,18 @@ where
}

State::Released if self.is_pin_pressed() => {
self.clicks = 1;
self.clicks += 1;
self.held = None;
self.state = State::Down(I::now());
}
State::Held(elapsed) if self.is_pin_released() => {
self.held = Some(elapsed.elapsed());
self.state = State::Released;
State::Held(elapsed) => {
if self.is_pin_released() {
// TODO: save prior held time?
self.held = Some(elapsed.elapsed());
self.state = State::Up(I::now());
} else {
// holding
}
}
_ => {}
}
Expand Down
17 changes: 17 additions & 0 deletions src/pin_wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,22 +89,39 @@ pub(crate) mod tests {
self.pin.release();
self.tick();
}

pub fn hold_button(&mut self) {
self.press_button();
sleep(CONFIG.hold);
self.tick();
self.release_button();
}
}

impl MockPin {
/// Press the pin with debounce.
pub fn press(&self) {
self.0.store(true, Ordering::SeqCst);
sleep(CONFIG.debounce);
}

/// Release the pin with debounce.
pub fn release(&self) {
self.0.store(false, Ordering::SeqCst);
sleep(CONFIG.debounce);
}

/// Simulate pin state changes corresponding to one full button click with debounce.
pub fn click(&self) {
self.press();
self.release();
}

/// Simulate pin state changes corresponding to one full button hold with debounce.
pub fn hold(&self) {
self.press();
sleep(CONFIG.hold);
self.release();
}
}
}
Loading

0 comments on commit b46ff04

Please sign in to comment.