127: Digital v1 <-> v2 compatibility wrappers and shims r=japaric a=ryankurte

This PR introduces implicit  v1 -> v2 (forward) compatibility, and explicit wrapper types for v2 -> v1 (reverse) compatibility between digital trait versions as a final step for #95, as well as moving the deprecated v1 traits to a re-exported module for clarity.

As @japaric pointed out, it is not feasible to have implicit compatibility in both directions, so it seemed reasonable to make regression explicit as it hides an `.unwrap()` on failure.

@therealprof, @hannobraun, @eldruin what do you think of this approach?
I think it probably needs more documentation, though I am definitely open to suggestions as to what / where.

See also: #100, #92, #102.



Co-authored-by: Ryan Kurte <ryankurte@gmail.com>
Co-authored-by: Diego Barrios Romero <eldruin@gmail.com>
Co-authored-by: Daniel Egger <daniel@eggers-club.de>
This commit is contained in:
bors[bot]
2019-03-21 06:19:21 +00:00
6 changed files with 581 additions and 149 deletions

View File

@@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
fallible and their methods now return a `Result` type as setting an output pin
and reading an input pin could potentially fail.
See [here](https://github.com/rust-embedded/embedded-hal/issues/95) for more info.
- Compatibility shims between `digital::v1` and `digital::v2` traits allowing v1 traits
to be implicitly promoted to v2, and for v2 traits to be explicitly cast to v1 wrappers.
### Changed
- The current versions of the `OutputPin`, `StatefulOutputPin`, `ToggleableOutputPin`

View File

@@ -1,155 +1,25 @@
//! Digital I/O
//!
//! The traits in this module are now deprecated. Please use the new versions included
//! in `digital::v2`.
//!
//!
/// Single digital push-pull output pin
///
/// *This version of the trait is now deprecated. Please use the new `OutputPin` trait in
/// `digital::v2::OutputPin`*.
// Deprecated / infallible traits
#[deprecated(since = "0.2.2", note = "Deprecated because the methods cannot return errors. \
Users should use the traits in digital::v2.")]
pub trait OutputPin {
/// Drives the pin low
///
/// *NOTE* the actual electrical state of the pin may not actually be low, e.g. due to external
/// electrical sources
fn set_low(&mut self);
pub mod v1;
/// Drives the pin high
///
/// *NOTE* the actual electrical state of the pin may not actually be high, e.g. due to external
/// electrical sources
fn set_high(&mut self);
}
/// Push-pull output pin that can read its output state
///
/// *This trait is available if embedded-hal is built with the `"unproven"` feature.*
///
/// *This version of the trait is now deprecated. Please use the new `StatefulOutputPin` trait in
/// `digital::v2::StatefulOutputPin`*.
#[deprecated(since = "0.2.2", note = "Deprecated because the methods cannot return errors. \
Users should use the traits in digital::v2.")]
#[cfg(feature = "unproven")]
pub trait StatefulOutputPin {
/// Is the pin in drive high mode?
///
/// *NOTE* this does *not* read the electrical state of the pin
fn is_set_high(&self) -> bool;
/// Is the pin in drive low mode?
///
/// *NOTE* this does *not* read the electrical state of the pin
fn is_set_low(&self) -> bool;
}
/// Output pin that can be toggled
///
/// *This trait is available if embedded-hal is built with the `"unproven"` feature.*
///
/// *This version of the trait is now deprecated. Please use the new `ToggleableOutputPin`
/// trait in `digital::v2::ToggleableOutputPin`*.
///
/// See [toggleable](toggleable) to use a software implementation if
/// both [OutputPin](trait.OutputPin.html) and
/// [StatefulOutputPin](trait.StatefulOutputPin.html) are
/// implemented. Otherwise, implement this using hardware mechanisms.
#[deprecated(since = "0.2.2", note = "Deprecated because the methods cannot return errors. \
Users should use the traits in digital::v2.")]
#[cfg(feature = "unproven")]
pub trait ToggleableOutputPin {
/// Toggle pin output.
fn toggle(&mut self);
}
/// If you can read **and** write the output state, a pin is
/// toggleable by software.
///
/// *This version of the module is now deprecated. Please use the new `toggleable` module in
/// `digital::v2::toggleable`*.
///
/// ```
/// use embedded_hal::digital::{OutputPin, StatefulOutputPin, ToggleableOutputPin};
/// use embedded_hal::digital::toggleable;
///
/// /// A virtual output pin that exists purely in software
/// struct MyPin {
/// state: bool
/// }
///
/// impl OutputPin for MyPin {
/// fn set_low(&mut self) {
/// self.state = false;
/// }
/// fn set_high(&mut self) {
/// self.state = true;
/// }
/// }
///
/// impl StatefulOutputPin for MyPin {
/// fn is_set_low(&self) -> bool {
/// !self.state
/// }
/// fn is_set_high(&self) -> bool {
/// self.state
/// }
/// }
///
/// /// Opt-in to the software implementation.
/// impl toggleable::Default for MyPin {}
///
/// let mut pin = MyPin { state: false };
/// pin.toggle();
/// assert!(pin.is_set_high());
/// pin.toggle();
/// assert!(pin.is_set_low());
/// ```
#[deprecated(since = "0.2.2", note = "Deprecated because the methods cannot return errors. \
Users should use the traits in digital::v2.")]
#[cfg(feature = "unproven")]
pub mod toggleable {
#[allow(deprecated)]
use super::{OutputPin, StatefulOutputPin, ToggleableOutputPin};
/// Software-driven `toggle()` implementation.
///
/// *This trait is available if embedded-hal is built with the `"unproven"` feature.*
#[allow(deprecated)]
pub trait Default: OutputPin + StatefulOutputPin {}
#[allow(deprecated)]
impl<P> ToggleableOutputPin for P
where
P: Default,
{
/// Toggle pin output
fn toggle(&mut self) {
if self.is_set_low() {
self.set_high();
} else {
self.set_low();
}
}
}
}
/// Single digital input pin
///
/// *This trait is available if embedded-hal is built with the `"unproven"` feature.*
///
/// *This version of the trait is now deprecated. Please use the new `InputPin` trait in
/// `digital::v2::InputPin`*.
#[deprecated(since = "0.2.2", note = "Deprecated because the methods cannot return errors. \
Users should use the traits in digital::v2.")]
#[cfg(feature = "unproven")]
pub trait InputPin {
/// Is the input pin high?
fn is_high(&self) -> bool;
/// Is the input pin low?
fn is_low(&self) -> bool;
}
/// Improved version of the digital traits where the methods can also return an error.
// New / fallible traits
pub mod v2;
// v2 -> v1 compatibility wrappers
// These require explicit casts from v2 -> v1
pub mod v1_compat;
// v1 -> v2 compatibility shims
// These are implicit over v1 implementations
pub mod v2_compat;
// Re-export old traits so this isn't a breaking change
#[allow(deprecated)]
pub use self::v1::*;

145
src/digital/v1.rs Normal file
View File

@@ -0,0 +1,145 @@
//! Digital I/O
//!
//! The traits in this module are now deprecated. Please use the new versions included
//! in `digital::v2`.
#![allow(deprecated)]
/// Single digital push-pull output pin
///
/// *This version of the trait is now deprecated. Please use the new `OutputPin` trait in
/// `digital::v2::OutputPin`*.
pub trait OutputPin {
/// Drives the pin low
///
/// *NOTE* the actual electrical state of the pin may not actually be low, e.g. due to external
/// electrical sources
fn set_low(&mut self);
/// Drives the pin high
///
/// *NOTE* the actual electrical state of the pin may not actually be high, e.g. due to external
/// electrical sources
fn set_high(&mut self);
}
/// Push-pull output pin that can read its output state
///
/// *This trait is available if embedded-hal is built with the `"unproven"` feature.*
///
/// *This version of the trait is now deprecated. Please use the new `StatefulOutputPin` trait in
/// `digital::v2::StatefulOutputPin`*.
#[cfg(feature = "unproven")]
pub trait StatefulOutputPin {
/// Is the pin in drive high mode?
///
/// *NOTE* this does *not* read the electrical state of the pin
fn is_set_high(&self) -> bool;
/// Is the pin in drive low mode?
///
/// *NOTE* this does *not* read the electrical state of the pin
fn is_set_low(&self) -> bool;
}
/// Output pin that can be toggled
///
/// *This trait is available if embedded-hal is built with the `"unproven"` feature.*
///
/// *This version of the trait is now deprecated. Please use the new `ToggleableOutputPin`
/// trait in `digital::v2::ToggleableOutputPin`*.
///
/// See [toggleable](toggleable) to use a software implementation if
/// both [OutputPin](trait.OutputPin.html) and
/// [StatefulOutputPin](trait.StatefulOutputPin.html) are
/// implemented. Otherwise, implement this using hardware mechanisms.
#[cfg(feature = "unproven")]
pub trait ToggleableOutputPin {
/// Toggle pin output.
fn toggle(&mut self);
}
/// If you can read **and** write the output state, a pin is
/// toggleable by software.
///
/// *This version of the module is now deprecated. Please use the new `toggleable` module in
/// `digital::v2::toggleable`*.
///
/// ```
/// use embedded_hal::digital::{OutputPin, StatefulOutputPin, ToggleableOutputPin};
/// use embedded_hal::digital::toggleable;
///
/// /// A virtual output pin that exists purely in software
/// struct MyPin {
/// state: bool
/// }
///
/// impl OutputPin for MyPin {
/// fn set_low(&mut self) {
/// self.state = false;
/// }
/// fn set_high(&mut self) {
/// self.state = true;
/// }
/// }
///
/// impl StatefulOutputPin for MyPin {
/// fn is_set_low(&self) -> bool {
/// !self.state
/// }
/// fn is_set_high(&self) -> bool {
/// self.state
/// }
/// }
///
/// /// Opt-in to the software implementation.
/// impl toggleable::Default for MyPin {}
///
/// let mut pin = MyPin { state: false };
/// pin.toggle();
/// assert!(pin.is_set_high());
/// pin.toggle();
/// assert!(pin.is_set_low());
/// ```
#[cfg(feature = "unproven")]
pub mod toggleable {
#[allow(deprecated)]
use super::{OutputPin, StatefulOutputPin, ToggleableOutputPin};
/// Software-driven `toggle()` implementation.
///
/// *This trait is available if embedded-hal is built with the `"unproven"` feature.*
#[allow(deprecated)]
pub trait Default: OutputPin + StatefulOutputPin {}
#[allow(deprecated)]
impl<P> ToggleableOutputPin for P
where
P: Default,
{
/// Toggle pin output
fn toggle(&mut self) {
if self.is_set_low() {
self.set_high();
} else {
self.set_low();
}
}
}
}
/// Single digital input pin
///
/// *This trait is available if embedded-hal is built with the `"unproven"` feature.*
///
/// *This version of the trait is now deprecated. Please use the new `InputPin` trait in
/// `digital::v2::InputPin`*.
#[cfg(feature = "unproven")]
pub trait InputPin {
/// Is the input pin high?
fn is_high(&self) -> bool;
/// Is the input pin low?
fn is_low(&self) -> bool;
}

256
src/digital/v1_compat.rs Normal file
View File

@@ -0,0 +1,256 @@
//! v1 compatibility wrapper
//! this module adds reverse support for v2 digital traits
//! v2 traits must be explicitly cast to the v1 version using `.into()`,
//! and will panic on internal errors
#[allow(deprecated)]
use super::v1;
use super::v2;
/// Wrapper to allow fallible `v2::OutputPin` traits to be converted to `v1::OutputPin` traits
pub struct OldOutputPin<T> {
pin: T,
}
impl <T, E> OldOutputPin<T>
where
T: v2::OutputPin<Error=E>,
E: core::fmt::Debug,
{
/// Create a new OldOutputPin wrapper around a `v2::OutputPin`
pub fn new(pin: T) -> Self {
Self{pin}
}
/// Fetch a reference to the inner `v2::OutputPin` impl
#[cfg(test)]
fn inner(&self) -> &T {
&self.pin
}
}
impl <T, E> From<T> for OldOutputPin<T>
where
T: v2::OutputPin<Error=E>,
E: core::fmt::Debug,
{
fn from(pin: T) -> Self {
OldOutputPin{pin}
}
}
/// Implementation of `v1::OutputPin` trait for fallible `v2::OutputPin` output pins
/// where errors will panic.
#[allow(deprecated)]
impl <T, E> v1::OutputPin for OldOutputPin<T>
where
T: v2::OutputPin<Error=E>,
E: core::fmt::Debug,
{
fn set_low(&mut self) {
self.pin.set_low().unwrap()
}
fn set_high(&mut self) {
self.pin.set_high().unwrap()
}
}
/// Implementation of `v1::StatefulOutputPin` trait for `v2::StatefulOutputPin` fallible pins
/// where errors will panic.
#[cfg(feature = "unproven")]
#[allow(deprecated)]
impl <T, E> v1::StatefulOutputPin for OldOutputPin<T>
where
T: v2::StatefulOutputPin<Error=E>,
E: core::fmt::Debug,
{
fn is_set_low(&self) -> bool {
self.pin.is_set_low().unwrap()
}
fn is_set_high(&self) -> bool {
self.pin.is_set_high().unwrap()
}
}
/// Wrapper to allow fallible `v2::InputPin` traits to be converted to `v1::InputPin` traits
/// where errors will panic.
#[cfg(feature = "unproven")]
pub struct OldInputPin<T> {
pin: T,
}
#[cfg(feature = "unproven")]
impl <T, E> OldInputPin<T>
where
T: v2::OutputPin<Error=E>,
E: core::fmt::Debug,
{
/// Create an `OldInputPin` wrapper around a `v2::InputPin`.
pub fn new(pin: T) -> Self {
Self{pin}
}
}
#[cfg(feature = "unproven")]
impl <T, E> From<T> for OldInputPin<T>
where
T: v2::InputPin<Error=E>,
E: core::fmt::Debug,
{
fn from(pin: T) -> Self {
OldInputPin{pin}
}
}
/// Implementation of `v1::InputPin` trait for `v2::InputPin` fallible pins
/// where errors will panic.
#[cfg(feature = "unproven")]
#[allow(deprecated)]
impl <T, E> v1::InputPin for OldInputPin<T>
where
T: v2::InputPin<Error=E>,
E: core::fmt::Debug,
{
fn is_low(&self) -> bool {
self.pin.is_low().unwrap()
}
fn is_high(&self) -> bool {
self.pin.is_high().unwrap()
}
}
#[cfg(test)]
#[allow(deprecated)]
mod tests {
use super::*;
#[allow(deprecated)]
use crate::digital::v1;
use crate::digital::v2;
use crate::digital::v1::OutputPin;
#[derive(Clone)]
struct NewOutputPinImpl {
state: bool,
res: Result<(), ()>
}
impl v2::OutputPin for NewOutputPinImpl {
type Error = ();
fn set_low(&mut self) -> Result<(), Self::Error> {
self.state = false;
self.res
}
fn set_high(&mut self) -> Result<(), Self::Error>{
self.state = true;
self.res
}
}
#[allow(deprecated)]
struct OldOutputPinConsumer<T: v1::OutputPin> {
_pin: T,
}
#[allow(deprecated)]
impl <T>OldOutputPinConsumer<T>
where T: v1::OutputPin
{
pub fn new(pin: T) -> OldOutputPinConsumer<T> {
OldOutputPinConsumer{ _pin: pin }
}
}
#[test]
fn v1_v2_output_explicit() {
let i = NewOutputPinImpl{state: false, res: Ok(())};
let _c: OldOutputPinConsumer<OldOutputPin<_>> = OldOutputPinConsumer::new(i.into());
}
#[test]
fn v1_v2_output_state() {
let mut o: OldOutputPin<_> = NewOutputPinImpl{state: false, res: Ok(())}.into();
o.set_high();
assert_eq!(o.inner().state, true);
o.set_low();
assert_eq!(o.inner().state, false);
}
#[test]
#[should_panic]
fn v1_v2_output_panic() {
let mut o: OldOutputPin<_> = NewOutputPinImpl{state: false, res: Err(())}.into();
o.set_high();
}
#[cfg(feature = "unproven")]
use crate::digital::v1::InputPin;
#[cfg(feature = "unproven")]
struct NewInputPinImpl {
state: Result<bool, ()>,
}
#[cfg(feature = "unproven")]
impl v2::InputPin for NewInputPinImpl {
type Error = ();
fn is_low(&self) -> Result<bool, Self::Error> {
self.state.map(|v| v == false)
}
fn is_high(&self) -> Result<bool, Self::Error>{
self.state.map(|v| v == true)
}
}
#[cfg(feature = "unproven")]
#[allow(deprecated)]
struct OldInputPinConsumer<T: v1::InputPin> {
_pin: T,
}
#[cfg(feature = "unproven")]
#[allow(deprecated)]
impl <T>OldInputPinConsumer<T>
where T: v1::InputPin
{
pub fn new(pin: T) -> OldInputPinConsumer<T> {
OldInputPinConsumer{ _pin: pin }
}
}
#[cfg(feature = "unproven")]
#[test]
fn v1_v2_input_explicit() {
let i = NewInputPinImpl{state: Ok(false)};
let _c: OldInputPinConsumer<OldInputPin<_>> = OldInputPinConsumer::new(i.into());
}
#[cfg(feature = "unproven")]
#[test]
fn v1_v2_input_state() {
let i: OldInputPin<_> = NewInputPinImpl{state: Ok(false)}.into();
assert_eq!(i.is_low(), true);
assert_eq!(i.is_high(), false);
}
#[cfg(feature = "unproven")]
#[test]
#[should_panic]
fn v1_v2_input_panic() {
let i: OldInputPin<_> = NewInputPinImpl{state: Err(())}.into();
i.is_low();
}
}

View File

@@ -1,4 +1,6 @@
/// Digital I/O
//! Digital I/O
//!
//! Version 2 / fallible traits. Infallible implementations should set Error to `!`.
/// Single digital push-pull output pin
pub trait OutputPin {

157
src/digital/v2_compat.rs Normal file
View File

@@ -0,0 +1,157 @@
//! v2 compatibility shims
//! this module adds implicit forward support to v1 digital traits
#[allow(deprecated)]
use super::v1;
use super::v2;
/// Implementation of fallible `v2::OutputPin` for `v1::OutputPin` traits
#[allow(deprecated)]
impl <T> v2::OutputPin for T
where
T: v1::OutputPin,
{
// TODO: update to ! when never_type is stabilized
type Error = ();
fn set_low(&mut self) -> Result<(), Self::Error> {
Ok(self.set_low())
}
fn set_high(&mut self) -> Result<(), Self::Error> {
Ok(self.set_high())
}
}
/// Implementation of fallible `v2::StatefulOutputPin` for `v1::StatefulOutputPin` digital traits
#[cfg(feature = "unproven")]
#[allow(deprecated)]
impl <T> v2::StatefulOutputPin for T
where
T: v1::StatefulOutputPin + v1::OutputPin,
{
fn is_set_low(&self) -> Result<bool, Self::Error> {
Ok(self.is_set_low())
}
fn is_set_high(&self) -> Result<bool, Self::Error> {
Ok(self.is_set_high())
}
}
/// Implementation of fallible `v2::InputPin` for `v1::InputPin` digital traits
#[cfg(feature = "unproven")]
#[allow(deprecated)]
impl <T> v2::InputPin for T
where
T: v1::InputPin
{
// TODO: update to ! when never_type is stabilized
type Error = ();
fn is_low(&self) -> Result<bool, Self::Error> {
Ok(self.is_low())
}
fn is_high(&self) -> Result<bool, Self::Error> {
Ok(self.is_high())
}
}
#[cfg(test)]
mod tests {
#[allow(deprecated)]
use crate::digital::v1;
use crate::digital::v2;
#[allow(deprecated)]
struct OldOutputPinImpl {
state: bool
}
#[allow(deprecated)]
impl v1::OutputPin for OldOutputPinImpl {
fn set_low(&mut self) {
self.state = false;
}
fn set_high(&mut self) {
self.state = true;
}
}
struct NewOutputPinConsumer<T: v2::OutputPin> {
_pin: T,
}
impl <T>NewOutputPinConsumer<T>
where T: v2::OutputPin {
pub fn new(pin: T) -> NewOutputPinConsumer<T> {
NewOutputPinConsumer{ _pin: pin }
}
}
#[test]
fn v2_v1_output_implicit() {
let i = OldOutputPinImpl{state: false};
let _c = NewOutputPinConsumer::new(i);
}
#[test]
fn v2_v1_output_state() {
let mut o = OldOutputPinImpl{state: false};
v2::OutputPin::set_high(&mut o).unwrap();
assert_eq!(o.state, true);
v2::OutputPin::set_low(&mut o).unwrap();
assert_eq!(o.state, false);
}
#[cfg(feature = "unproven")]
#[allow(deprecated)]
struct OldInputPinImpl {
state: bool
}
#[cfg(feature = "unproven")]
#[allow(deprecated)]
impl v1::InputPin for OldInputPinImpl {
fn is_low(&self) -> bool {
!self.state
}
fn is_high(&self) -> bool {
self.state
}
}
#[cfg(feature = "unproven")]
struct NewInputPinConsumer<T: v2::InputPin> {
_pin: T,
}
#[cfg(feature = "unproven")]
impl <T>NewInputPinConsumer<T>
where T: v2::InputPin {
pub fn new(pin: T) -> NewInputPinConsumer<T> {
NewInputPinConsumer{ _pin: pin }
}
}
#[cfg(feature = "unproven")]
#[test]
fn v2_v1_input_implicit() {
let i = OldInputPinImpl{state: false};
let _c = NewInputPinConsumer::new(i);
}
#[cfg(feature = "unproven")]
#[test]
fn v2_v1_input_state() {
let mut i = OldInputPinImpl{state: false};
assert_eq!(v2::InputPin::is_high(&mut i).unwrap(), false);
assert_eq!(v2::InputPin::is_low(&mut i).unwrap(), true);
}
}