-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Hal update and async implementation #12
base: master
Are you sure you want to change the base?
Changes from all commits
77c3a70
5686c24
1cce963
c0ade87
53c1ac4
d831552
3266119
fb54216
c29e4d2
67f0382
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,70 @@ | ||
#[cfg(feature = "async")] | ||
use crate::WaitableInputSwitch; | ||
use crate::{ActiveHigh, ActiveLow, InputSwitch, Switch}; | ||
use embedded_hal::digital::v2::InputPin; | ||
use embedded_hal::digital::{ErrorType, InputPin}; | ||
#[cfg(feature = "async")] | ||
use embedded_hal_async::digital::Wait; | ||
|
||
impl<T: InputPin> InputSwitch for Switch<T, ActiveHigh> { | ||
type Error = <T as InputPin>::Error; | ||
type Error = <T as ErrorType>::Error; | ||
|
||
fn is_active(&self) -> Result<bool, Self::Error> { | ||
self.pin.is_high() | ||
self.pin.borrow_mut().is_high() | ||
} | ||
} | ||
|
||
impl<T: InputPin> InputSwitch for Switch<T, ActiveLow> { | ||
type Error = <T as InputPin>::Error; | ||
type Error = <T as ErrorType>::Error; | ||
|
||
fn is_active(&self) -> Result<bool, Self::Error> { | ||
self.pin.is_low() | ||
self.pin.borrow_mut().is_low() | ||
} | ||
} | ||
|
||
#[cfg(feature = "async")] | ||
impl<T: Wait + InputPin> WaitableInputSwitch for Switch<T, ActiveHigh> | ||
where | ||
Switch<T, ActiveHigh>: InputSwitch, | ||
{ | ||
type Error = <T as ErrorType>::Error; | ||
|
||
async fn wait_for_active(&mut self) -> Result<(), Self::Error> { | ||
self.pin.get_mut().wait_for_high().await | ||
} | ||
|
||
async fn wait_for_inactive(&mut self) -> Result<(), Self::Error> { | ||
self.pin.get_mut().wait_for_low().await | ||
} | ||
|
||
async fn wait_for_change(&mut self) -> Result<(), Self::Error> { | ||
if self.pin.get_mut().is_high()? { | ||
self.pin.get_mut().wait_for_low().await | ||
} else { | ||
self.pin.get_mut().wait_for_high().await | ||
} | ||
} | ||
} | ||
|
||
#[cfg(feature = "async")] | ||
impl<T: Wait + InputPin> WaitableInputSwitch for Switch<T, ActiveLow> | ||
where | ||
Switch<T, ActiveHigh>: InputSwitch, | ||
{ | ||
type Error = <T as ErrorType>::Error; | ||
|
||
async fn wait_for_active(&mut self) -> Result<(), Self::Error> { | ||
self.pin.get_mut().wait_for_low().await | ||
} | ||
|
||
async fn wait_for_inactive(&mut self) -> Result<(), Self::Error> { | ||
self.pin.get_mut().wait_for_high().await | ||
} | ||
|
||
async fn wait_for_change(&mut self) -> Result<(), Self::Error> { | ||
if self.pin.get_mut().is_high()? { | ||
self.pin.get_mut().wait_for_low().await | ||
} else { | ||
self.pin.get_mut().wait_for_high().await | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,7 @@ | ||
//! "Async" feature enables async support for input switches. | ||
daniel-dbg-ginsburg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
#![no_std] | ||
#![allow(async_fn_in_trait)] | ||
|
||
mod input; | ||
mod output; | ||
|
@@ -19,7 +22,7 @@ pub trait InputSwitch { | |
/// use switch_hal::{InputSwitch, OutputSwitch, Switch, IntoSwitch}; | ||
/// # let pin = mock::Pin::with_state(mock::State::High); | ||
/// # let mut status_led = mock::Pin::new().into_active_high_switch(); | ||
/// let button = pin.into_active_low_switch(); | ||
/// let mut button = pin.into_active_low_switch(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This example highlights why I question the mutability. It makes no sense for a button input to be mutable. For the application code, after setup is complete, it’s a read only peripheral. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless you have a Debouncer pin wrapper (maintains state) for example. Hidden mutability is then problematic for multi threading etc. Same for port expanders (again, hidden mutability is needed for the underlying bus). I would have been OK with both approaches, but this one is more explicitly saying that sharing is not guaranteed. However that ship sailed 10 months ago, the API is stabilized and I am more affected by switch-hal still depending 0.2 than by the mut :) I can work around the dependency, but it is annoying. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All the rest of this PR has already been changed to use interior mutability; this mut can be removed now. |
||
/// match button.is_active() { | ||
/// Ok(true) => { status_led.on().ok(); } | ||
/// Ok(false) => { status_led.off().ok(); } | ||
|
@@ -29,6 +32,22 @@ pub trait InputSwitch { | |
fn is_active(&self) -> Result<bool, Self::Error>; | ||
} | ||
|
||
#[cfg(feature = "async")] | ||
/// Represents an input switch that can be asynchronously waited for | ||
pub trait WaitableInputSwitch { | ||
type Error; | ||
|
||
/// Waits until the switch becomes active. If the switch in already active, returns immediately | ||
/// | ||
async fn wait_for_active(&mut self) -> Result<(), Self::Error>; | ||
/// Waits until the switch becomes inactive. If the switch is already inactive, returns immediately | ||
/// | ||
async fn wait_for_inactive(&mut self) -> Result<(), Self::Error>; | ||
/// Waits until the switch changess from active to inactive, or from inactive to active | ||
/// | ||
async fn wait_for_change(&mut self) -> Result<(), Self::Error>; | ||
} | ||
|
||
/// Represents an output switch, such as a LED "switch" or transistor | ||
pub trait OutputSwitch { | ||
type Error; | ||
|
@@ -63,11 +82,11 @@ pub trait OutputSwitch { | |
/// Toggles the switch from it's current state to it's opposite state. | ||
/// | ||
/// # Notes | ||
/// This is only available if the underlying hal has implemented [ToggleableOutputPin](embedded_hal::digital::v2::ToggleableOutputPin) | ||
/// This is only available if the underlying hal has implemented [StatefulOutputPin](embedded_hal::digital::StatefulOutputPin) | ||
pub trait ToggleableOutputSwitch { | ||
type Error; | ||
|
||
/// Toggles the current state of the [OutputSwitch](OutputSwitch) | ||
/// Toggles the current state of the [OutputSwitch] | ||
/// | ||
/// # Examples | ||
/// | ||
|
@@ -84,7 +103,7 @@ pub trait ToggleableOutputSwitch { | |
/// Checks current switch state | ||
/// | ||
/// # Notes | ||
/// This is only available if the underlying hal has implemented [StatefulOutputPin](embedded_hal::digital::v2::StatefulOutputPin) | ||
/// This is only available if the underlying hal has implemented [StatefulOutputPin](embedded_hal::digital::StatefulOutputPin) | ||
pub trait StatefulOutputSwitch { | ||
type Error; | ||
|
||
|
@@ -98,7 +117,7 @@ pub trait StatefulOutputSwitch { | |
/// # let pin = mock::Pin::new(); | ||
/// let mut led = pin.into_active_high_switch(); | ||
/// led.off().ok(); | ||
/// assert!(!led.is_on().unwrap()); | ||
/// assert_eq!(false, led.is_on().unwrap()); | ||
/// ``` | ||
fn is_on(&mut self) -> Result<bool, Self::Error>; | ||
|
||
|
@@ -112,7 +131,7 @@ pub trait StatefulOutputSwitch { | |
/// # let pin = mock::Pin::new(); | ||
/// let mut led = pin.into_active_high_switch(); | ||
/// led.off().ok(); | ||
/// assert!(led.is_off().unwrap()); | ||
/// assert_eq!(true, led.is_off().unwrap()); | ||
/// ``` | ||
fn is_off(&mut self) -> Result<bool, Self::Error>; | ||
} | ||
|
@@ -122,23 +141,23 @@ pub struct ActiveHigh; | |
/// Zero sized struct for signaling to [Switch](struct.Switch.html) that it is active low | ||
pub struct ActiveLow; | ||
|
||
use core::marker::PhantomData; | ||
use core::{cell::RefCell, marker::PhantomData}; | ||
|
||
/// Concrete implementation for [InputSwitch](trait.InputSwitch.html) and [OutputSwitch](trait.OutputSwitch.html) | ||
/// | ||
/// # Type Params | ||
/// - `IoPin` must be a type that implements either of the [InputPin](embedded_hal::digital::v2::InputPin) or [OutputPin](embedded_hal::digital::v2::OutputPin) traits. | ||
/// - `ActiveLevel` indicates whether the `Switch` is [ActiveHigh](ActiveHigh) or [ActiveLow](ActiveLow). | ||
/// - `IoPin` must be a type that implements either of the [InputPin](embedded_hal::digital::InputPin) or [OutputPin](embedded_hal::digital::OutputPin) traits. | ||
/// - `ActiveLevel` indicates whether the `Switch` is [ActiveHigh] or [ActiveLow]. | ||
/// `ActiveLevel` is not actually stored in the struct. | ||
/// It's [PhantomData](core::marker::PhantomData) used to indicate which implementation to use. | ||
/// It's [PhantomData] used to indicate which implementation to use. | ||
pub struct Switch<IoPin, ActiveLevel> { | ||
pin: IoPin, | ||
pin: RefCell<IoPin>, | ||
active: PhantomData<ActiveLevel>, | ||
} | ||
|
||
impl<IoPin, ActiveLevel> Switch<IoPin, ActiveLevel> { | ||
/// Constructs a new [Switch](struct.Switch.html) from a concrete implementation of an | ||
/// [InputPin](embedded_hal::digital::v2::InputPin) or [OutputPin](embedded_hal::digital::v2::OutputPin) | ||
/// [InputPin](embedded_hal::digital::InputPin) or [OutputPin](embedded_hal::digital::OutputPin) | ||
/// | ||
/// **Prefer the [IntoSwitch](trait.IntoSwitch.html) trait over calling [new](#method.new) directly.** | ||
/// | ||
|
@@ -183,12 +202,12 @@ impl<IoPin, ActiveLevel> Switch<IoPin, ActiveLevel> { | |
/// ``` | ||
pub fn new(pin: IoPin) -> Self { | ||
Switch { | ||
pin: pin, | ||
pin: RefCell::new(pin), | ||
active: PhantomData::<ActiveLevel>, | ||
} | ||
} | ||
|
||
/// Consumes the [Switch](struct.Switch.html) and returns the underlying [InputPin](embedded_hal::digital::v2::InputPin) or [OutputPin](embedded_hal::digital::v2::OutputPin). | ||
/// Consumes the [Switch](struct.Switch.html) and returns the underlying [InputPin](embedded_hal::digital::InputPin) or [OutputPin](embedded_hal::digital::OutputPin). | ||
/// | ||
/// This is useful fore retrieving the underlying pin to use it for a different purpose. | ||
/// | ||
|
@@ -204,19 +223,18 @@ impl<IoPin, ActiveLevel> Switch<IoPin, ActiveLevel> { | |
/// // do something else with the pin | ||
/// ``` | ||
pub fn into_pin(self) -> IoPin { | ||
self.pin | ||
self.pin.into_inner() | ||
} | ||
} | ||
|
||
/// Convenience functions for converting [InputPin](embedded_hal::digital::v2::InputPin) | ||
/// and [OutputPin](embedded_hal::digital::v2::OutputPin) to a [Switch](struct.Switch.html). | ||
/// Convenience functions for converting [InputPin](embedded_hal::digital::InputPin) | ||
/// and [OutputPin](embedded_hal::digital::OutputPin) to a [Switch](struct.Switch.html). | ||
/// | ||
/// The type of [Switch](struct.Switch.html) returned, | ||
/// [InputSwitch](trait.InputSwitch.html) or [OutputSwitch](trait.OutputSwitch.html) is | ||
/// determined by whether the `IoPin` being consumed is an [InputPin](embedded_hal::digital::v2::InputPin) | ||
/// or [OutputPin](embedded_hal::digital::v2::OutputPin). | ||
/// determined by whether the `IoPin` being consumed is an [InputPin](embedded_hal::digital::InputPin) | ||
/// or [OutputPin](embedded_hal::digital::OutputPin). | ||
pub trait IntoSwitch { | ||
|
||
/// Consumes the `IoPin` returning a [Switch](struct.Switch.html) of the appropriate `ActiveLevel`. | ||
/// | ||
/// This method exists so other, more convenient functions, can have blanket implementations. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, doesn't this possibly introduce a race?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose that's alright behavior considering the semantics of the function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does. In context switching environments (FreeRTOS based ESP32s for example) you could miss some toggles this way. But I do not see a way to make it reliable without hardware support or interrupts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't this just be a single get_mut across all the function?
After all, the refcell is kept locked for the duration of the awaiting period already because it is kept across await points, so wait_for_change'ing on a
Switch<T, _>
already panics with failure to get_mut. That wouldn't get worse by having a single mutex, but the raciness is reduced.