Skip to content

Commit 994ffbf

Browse files
thepacketgeekrahul-thakoor
authored andcommitted
Implementing debounce for DigitalInputDevice/Button
1 parent 4c6d92a commit 994ffbf

File tree

5 files changed

+226
-40
lines changed

5 files changed

+226
-40
lines changed

examples/button.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
//! Display message in console when a Button is pressed
2-
3-
use rust_gpiozero::*;
2+
use rust_gpiozero::{Button, Debounce};
3+
use std::time::Duration;
44

55
fn main() {
66
// Create a button which is attached to Pin 17
7-
let mut button = Button::new(17);
7+
let mut button = Button::new(17)
8+
// Add debouncing so that subsequent presses within 100ms don't trigger a press
9+
.debounce(Duration::from_millis(100));
10+
811
button.wait_for_press(None);
912
println!("button pressed");
1013
}

examples/button_async.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//! Display message in console when a Button is pressed
2+
use rust_gpiozero::{Button, Debounce};
3+
use std::time::Duration;
4+
5+
fn main() {
6+
// Create a button which is attached to Pin 17
7+
let mut button = Button::new(17)
8+
// Add debouncing so that subsequent presses within 100ms don't trigger a press
9+
.debounce(Duration::from_millis(100));
10+
11+
// Add an async interrupt to trigger whenever the button is pressed
12+
button
13+
.when_pressed(|_| {
14+
println!("button pressed");
15+
})
16+
.unwrap();
17+
}

src/debounce.rs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
use std::fmt;
2+
use std::ops::{Deref, DerefMut};
3+
use std::sync::{Arc, Mutex};
4+
use std::time::{Duration, Instant};
5+
6+
use crate::Button;
7+
use rppal::gpio::{self, Level, Trigger};
8+
9+
/// Adds `.debounce()` method to [`Button`] for converting to a [`Debounced`] button
10+
pub trait Debounce {
11+
fn debounce(self, duration: Duration) -> Debounced;
12+
}
13+
14+
impl Debounce for Button {
15+
fn debounce(self, duration: Duration) -> Debounced {
16+
Debounced {
17+
inner: self,
18+
period: duration,
19+
last_trigger: Arc::new(Mutex::new(None)),
20+
}
21+
}
22+
}
23+
24+
/// Wrapper type for [`Button`] to allow for software [debouncing](https://en.wikipedia.org/wiki/Switch#Contact%20Bounce). Will prevent
25+
/// Subsequent triggers with the given debounce period (E.g. 50-100 milliseconds)
26+
///
27+
/// Can be used with blocking functions (E.g [`Button::wait_for_press`]):
28+
/// ```
29+
/// use rust_gpiozero::{Button, Debounce};
30+
/// use std::time::Duration;
31+
///
32+
/// // Create a button which is attached to Pin 17
33+
/// let mut button = Button::new(17)
34+
/// // Add debouncing so that subsequent presses within 100ms don't trigger a press
35+
/// .debounce(Duration::from_millis(100));
36+
///
37+
/// button.wait_for_press(None);
38+
/// println!("button pressed");
39+
/// ```
40+
///
41+
/// Or async interrupt functions (E.g. [`Button::when_pressed`]):
42+
/// ```
43+
/// use rust_gpiozero::{Button, Debounce};
44+
/// use std::time::Duration;
45+
///
46+
/// // Create a button which is attached to Pin 17
47+
/// let mut button = Button::new(17)
48+
/// // Add debouncing so that subsequent presses within 100ms don't trigger a press
49+
/// .debounce(Duration::from_millis(100));
50+
///
51+
/// // Add an async interrupt to trigger whenever the button is pressed
52+
/// button.when_pressed(|_| {
53+
/// println!("button pressed");
54+
/// }).unwrap();
55+
/// ```
56+
57+
pub struct Debounced {
58+
inner: Button,
59+
period: Duration,
60+
last_trigger: Arc<Mutex<Option<Instant>>>,
61+
}
62+
63+
impl fmt::Debug for Debounced {
64+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65+
f.debug_struct("Debounced")
66+
.field("pin", &self.inner.pin())
67+
.field("period", &self.period)
68+
.finish()
69+
}
70+
}
71+
72+
impl Debounced {
73+
/// Pause the program until the device is deactivated, or the timeout is reached.
74+
/// * `timeout` - Number of seconds to wait before proceeding. If this is None, then wait indefinitely until the device is inactive.
75+
pub fn wait_for_release(&mut self, timeout: Option<f32>) {
76+
Debounced::wait_for(self, timeout, false)
77+
}
78+
79+
/// Pause the program until the device is activated, or the timeout is reached.
80+
/// * `timeout` - Number of seconds to wait before proceeding. If this is None, then wait indefinitely until the device is active.
81+
pub fn wait_for_press(&mut self, timeout: Option<f32>) {
82+
Debounced::wait_for(self, timeout, true)
83+
}
84+
85+
fn wait_for(&mut self, timeout: Option<f32>, active: bool) {
86+
let trigger = match active {
87+
true => Trigger::RisingEdge,
88+
false => Trigger::FallingEdge,
89+
};
90+
let timeout = timeout.map(|seconds| Duration::from_millis((seconds * 1000.0) as u64));
91+
self.inner.pin.set_interrupt(trigger).unwrap();
92+
loop {
93+
self.inner.pin.poll_interrupt(true, timeout).unwrap();
94+
// Check that enough time has passed since the last press
95+
if let Some(last_trigger) = self.last_trigger.lock().unwrap().as_ref() {
96+
// If this press is within the debounce time, continue blocking until the next press
97+
if last_trigger.elapsed() < self.period {
98+
continue;
99+
}
100+
}
101+
// if self.last_trigger is not set, there have been no previous presses so debounce time doesn't matter
102+
break;
103+
}
104+
self.last_trigger.lock().unwrap().replace(Instant::now());
105+
}
106+
107+
/// Asynchronously invokes the passed closure everytime the button is pressed, if the debounce period has passed
108+
pub fn when_pressed<C>(&mut self, action: C) -> Result<(), gpio::Error>
109+
where
110+
C: FnMut(Level) + Send + 'static,
111+
{
112+
self.action_on(true, action)
113+
}
114+
115+
/// Asynchronously invokes the passed closure everytime the button is released, if the debounce period has passed
116+
pub fn when_released<C>(&mut self, action: C) -> Result<(), gpio::Error>
117+
where
118+
C: FnMut(Level) + Send + 'static,
119+
{
120+
self.action_on(false, action)
121+
}
122+
123+
pub(crate) fn action_on<C>(&mut self, active: bool, mut action: C) -> Result<(), gpio::Error>
124+
where
125+
C: FnMut(Level) + Send + 'static,
126+
{
127+
let period = self.period;
128+
let last_trigger = self.last_trigger.clone();
129+
self.inner.action_on(active, move |level| {
130+
let mut lt = last_trigger.lock().unwrap();
131+
if let Some(last) = lt.as_ref() {
132+
if last.elapsed() < period {
133+
// Within debounce period, don't execute action
134+
return;
135+
}
136+
}
137+
lt.replace(Instant::now());
138+
action(level);
139+
})
140+
}
141+
}
142+
143+
impl Deref for Debounced {
144+
type Target = Button;
145+
146+
fn deref(&self) -> &Self::Target {
147+
&self.inner
148+
}
149+
}
150+
151+
impl DerefMut for Debounced {
152+
fn deref_mut(&mut self) -> &mut Self::Target {
153+
&mut self.inner
154+
}
155+
}

src/input_devices.rs

Lines changed: 45 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! Input device component interfaces for devices such as `Button`
2-
use rppal::gpio::{Gpio, InputPin, Level, Trigger};
2+
use rppal::gpio::{self, Gpio, InputPin, Level, Trigger};
33
use std::time::Duration;
44

55
/// Represents a generic GPIO input device.
@@ -17,7 +17,7 @@ impl InputDevice {
1717
/// # Arguments
1818
///
1919
/// * `pin` - The GPIO pin which the device is attached to
20-
///
20+
///
2121
pub fn new(pin: u8) -> InputDevice {
2222
match Gpio::new() {
2323
Err(e) => panic!("{:?}", e),
@@ -37,7 +37,7 @@ impl InputDevice {
3737
/// # Arguments
3838
///
3939
/// * `pin` - The GPIO pin which the device is attached to
40-
///
40+
///
4141
pub fn new_with_pullup(pin: u8) -> InputDevice {
4242
match Gpio::new() {
4343
Err(e) => panic!("{:?}", e),
@@ -61,30 +61,13 @@ macro_rules! impl_events_mixin {
6161
() => {
6262
/// Pause the program until the device is activated, or the timeout is reached.
6363
fn wait_for(&mut self, timeout: Option<f32>, active: bool) {
64-
match timeout {
65-
None => {
66-
if active {
67-
self.pin.set_interrupt(Trigger::RisingEdge).unwrap();
68-
self.pin.poll_interrupt(true, None).unwrap();
69-
} else {
70-
self.pin.set_interrupt(Trigger::FallingEdge).unwrap();
71-
self.pin.poll_interrupt(true, None).unwrap();
72-
}
73-
}
74-
Some(n) => {
75-
if active {
76-
self.pin.set_interrupt(Trigger::RisingEdge).unwrap();
77-
self.pin
78-
.poll_interrupt(true, Some(Duration::from_millis((n * 1000.0) as u64)))
79-
.unwrap();
80-
} else {
81-
self.pin.set_interrupt(Trigger::FallingEdge).unwrap();
82-
self.pin
83-
.poll_interrupt(true, Some(Duration::from_millis((n * 1000.0) as u64)))
84-
.unwrap();
85-
}
86-
}
87-
}
64+
let trigger = match active {
65+
true => Trigger::RisingEdge,
66+
false => Trigger::FallingEdge,
67+
};
68+
let timeout = timeout.map(|seconds| Duration::from_millis((seconds * 1000.0) as u64));
69+
self.pin.set_interrupt(trigger).unwrap();
70+
self.pin.poll_interrupt(true, timeout).unwrap();
8871
}
8972
};
9073
}
@@ -98,7 +81,6 @@ pub struct DigitalInputDevice {
9881
pin: InputPin,
9982
active_state: bool,
10083
inactive_state: bool,
101-
bounce_time: Option<f32>,
10284
}
10385

10486
impl DigitalInputDevice {
@@ -120,7 +102,6 @@ impl DigitalInputDevice {
120102
pin: pin.into_input_pulldown(),
121103
active_state: true,
122104
inactive_state: false,
123-
bounce_time: None,
124105
},
125106
},
126107
}
@@ -131,7 +112,7 @@ impl DigitalInputDevice {
131112
/// # Arguments
132113
///
133114
/// * `pin` - The GPIO pin which the device is attached to
134-
///
115+
///
135116
pub fn new_with_pullup(pin: u8) -> DigitalInputDevice {
136117
match Gpio::new() {
137118
Err(e) => panic!("{:?}", e),
@@ -141,7 +122,6 @@ impl DigitalInputDevice {
141122
pin: pin.into_input_pullup(),
142123
active_state: false,
143124
inactive_state: true,
144-
bounce_time: None,
145125
},
146126
},
147127
}
@@ -168,12 +148,9 @@ impl DigitalInputDevice {
168148
/// Alternatively, connect one side of the button to the 3V3 pin, and the other to any GPIO pin,
169149
/// and then create a Button instance with Button::new_with_pulldown
170150
pub struct Button {
171-
pin: InputPin,
151+
pub(crate) pin: InputPin,
172152
active_state: bool,
173153
inactive_state: bool,
174-
// FIXME: Implement debouncing
175-
#[allow(dead_code)]
176-
bounce_time: Option<f32>,
177154
}
178155

179156
impl Button {
@@ -188,7 +165,6 @@ impl Button {
188165
pin: pin.into_input_pullup(),
189166
active_state: false,
190167
inactive_state: true,
191-
bounce_time: None,
192168
},
193169
},
194170
}
@@ -204,7 +180,6 @@ impl Button {
204180
pin: pin.into_input_pulldown(),
205181
active_state: true,
206182
inactive_state: false,
207-
bounce_time: None,
208183
},
209184
},
210185
}
@@ -226,4 +201,37 @@ impl Button {
226201
pub fn wait_for_press(&mut self, timeout: Option<f32>) {
227202
self.wait_for(timeout, true)
228203
}
204+
205+
/// Invokes the passed closure everytime the button is pressed
206+
pub fn when_pressed<C>(&mut self, action: C) -> Result<(), gpio::Error>
207+
where
208+
C: FnMut(Level) + Send + 'static,
209+
{
210+
self.action_on(true, action)
211+
}
212+
213+
/// Invokes the passed closure everytime the button is released
214+
pub fn when_released<C>(&mut self, action: C) -> Result<(), gpio::Error>
215+
where
216+
C: FnMut(Level) + Send + 'static,
217+
{
218+
self.action_on(false, action)
219+
}
220+
221+
/// Adds an async interrupt for the corresponding trigger type to support `when_pressed`/`when_released`
222+
pub(crate) fn action_on<C>(&mut self, active: bool, action: C) -> Result<(), gpio::Error>
223+
where
224+
C: FnMut(Level) + Send + 'static,
225+
{
226+
let trigger = match active {
227+
true => Trigger::RisingEdge,
228+
false => Trigger::FallingEdge,
229+
};
230+
self.pin.set_async_interrupt(trigger, action)
231+
}
232+
233+
/// Removes all previously configured async trigger(s) (E.g. `when_pressed`/`when_released`)
234+
pub fn clear_async_interrupt(&mut self) -> Result<(), gpio::Error> {
235+
self.pin.clear_async_interrupt()
236+
}
229237
}

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,6 @@ pub mod devices;
3737
pub mod output_devices;
3838
#[macro_use]
3939
pub mod input_devices;
40+
41+
mod debounce;
42+
pub use debounce::{Debounce, Debounced};

0 commit comments

Comments
 (0)