diff --git a/CHANGELOG.md b/CHANGELOG.md index bfa9671e4..2f7ef0201 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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` diff --git a/src/digital/mod.rs b/src/digital/mod.rs index 5784f0fbe..4e67f9851 100644 --- a/src/digital/mod.rs +++ b/src/digital/mod.rs @@ -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(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); - - /// 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 / infallible traits #[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}; +pub mod v1; - /// Software-driven `toggle()` implementation. - /// - /// *This trait is available if embedded-hal is built with the `"unproven"` feature.* - #[allow(deprecated)] - pub trait Default: OutputPin + StatefulOutputPin {} +// New / fallible traits +pub mod v2; - #[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(); - } - } - } -} +// v2 -> v1 compatibility wrappers +// These require explicit casts from v2 -> v1 +pub mod v1_compat; -/// 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; +// v1 -> v2 compatibility shims +// These are implicit over v1 implementations +pub mod v2_compat; - /// Is the input pin low? - fn is_low(&self) -> bool; -} +// Re-export old traits so this isn't a breaking change +#[allow(deprecated)] +pub use self::v1::*; -/// Improved version of the digital traits where the methods can also return an error. -pub mod v2; diff --git a/src/digital/v1.rs b/src/digital/v1.rs new file mode 100644 index 000000000..d7bbfacaa --- /dev/null +++ b/src/digital/v1.rs @@ -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; +} diff --git a/src/digital/v1_compat.rs b/src/digital/v1_compat.rs new file mode 100644 index 000000000..aed9aaf7a --- /dev/null +++ b/src/digital/v1_compat.rs @@ -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(); + } + +} diff --git a/src/digital/v2.rs b/src/digital/v2.rs index 460005eb0..748807225 100644 --- a/src/digital/v2.rs +++ b/src/digital/v2.rs @@ -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 { diff --git a/src/digital/v2_compat.rs b/src/digital/v2_compat.rs new file mode 100644 index 000000000..779c089d3 --- /dev/null +++ b/src/digital/v2_compat.rs @@ -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); + } +} \ No newline at end of file