From 6203207523c72374d06d343a295526411f7208c8 Mon Sep 17 00:00:00 2001 From: Markus Reiter Date: Wed, 24 Jan 2024 23:54:55 +0100 Subject: [PATCH 01/10] Implement `Wait` for `CdevPin`. --- Cargo.toml | 8 ++- examples/gpio-wait.rs | 46 +++++++++++++++++ src/cdev_pin.rs | 114 ++++++++++++++++++++++++++++++++++++++---- 3 files changed, 157 insertions(+), 11 deletions(-) create mode 100644 examples/gpio-wait.rs diff --git a/Cargo.toml b/Cargo.toml index 590aa91..26d4ad8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" [features] gpio_sysfs = ["sysfs_gpio"] gpio_cdev = ["gpio-cdev"] -async-tokio = ["gpio-cdev/async-tokio", "dep:embedded-hal-async", "tokio/time"] +async-tokio = ["gpio-cdev/async-tokio", "dep:embedded-hal-async", "tokio/time", "tokio/rt", "gpiocdev/async_tokio"] i2c = ["i2cdev"] spi = ["spidev"] @@ -26,6 +26,7 @@ embedded-hal = "1" embedded-hal-nb = "1" embedded-hal-async = { version = "1", optional = true } gpio-cdev = { version = "0.6.0", optional = true } +gpiocdev = { version = "0.6.0" } sysfs_gpio = { version = "0.6.1", optional = true } i2cdev = { version = "0.6.0", optional = true } nb = "1" @@ -36,8 +37,13 @@ tokio = { version = "1", default-features = false, optional = true } [dev-dependencies] openpty = "0.2.0" +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } [dependencies.cast] # we don't need the `Error` implementation default-features = false version = "0.3" + +[[example]] +name = "gpio-wait" +required-features = ["async-tokio"] diff --git a/examples/gpio-wait.rs b/examples/gpio-wait.rs new file mode 100644 index 0000000..05b0468 --- /dev/null +++ b/examples/gpio-wait.rs @@ -0,0 +1,46 @@ +use std::error::Error; +use std::time::Duration; + +use embedded_hal::digital::{InputPin, OutputPin, PinState}; +use embedded_hal_async::digital::Wait; +use gpio_cdev::{Chip, LineRequestFlags}; +use linux_embedded_hal::CdevPin; +use tokio::time::{sleep, timeout}; + +// This example assumes that input/output pins are shorted. +const INPUT_LINE: u32 = 4; +const OUTPUT_LINE: u32 = 17; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let mut chip = Chip::new("/dev/gpiochip0")?; + let input = chip.get_line(INPUT_LINE)?; + let output = chip.get_line(OUTPUT_LINE)?; + + let mut input_pin = + CdevPin::new(input.request(LineRequestFlags::INPUT, 0, "")?)?.into_input_pin()?; + let mut output_pin = CdevPin::new(output.request(LineRequestFlags::OUTPUT, 0, "")?)? + .into_output_pin(PinState::Low)?; + + timeout(Duration::from_secs(10), async move { + let set_output = tokio::spawn(async move { + sleep(Duration::from_secs(5)).await; + println!("Setting output high."); + output_pin.set_high() + }); + + println!("Waiting for input to go high."); + + input_pin.wait_for_high().await?; + + assert!(input_pin.is_high()?); + println!("Input is now high."); + + set_output.await??; + + Ok::<_, Box>(()) + }) + .await??; + + Ok(()) +} diff --git a/src/cdev_pin.rs b/src/cdev_pin.rs index be08bd0..17bbc72 100644 --- a/src/cdev_pin.rs +++ b/src/cdev_pin.rs @@ -4,10 +4,63 @@ use std::fmt; +use embedded_hal::digital::InputPin; +#[cfg(feature = "async-tokio")] +use gpiocdev::{line::EdgeDetection, request::Request, tokio::AsyncRequest}; + /// Newtype around [`gpio_cdev::LineHandle`] that implements the `embedded-hal` traits /// /// [`gpio_cdev::LineHandle`]: https://docs.rs/gpio-cdev/0.5.0/gpio_cdev/struct.LineHandle.html -pub struct CdevPin(pub gpio_cdev::LineHandle, gpio_cdev::LineInfo); +#[derive(Debug)] +pub struct CdevPin(pub Option, gpio_cdev::LineInfo); + +#[cfg(feature = "async-tokio")] +#[derive(Debug)] +struct CdevPinEdgeWaiter<'a> { + pin: &'a mut CdevPin, + edge: EdgeDetection, +} + +#[cfg(feature = "async-tokio")] +impl<'a> CdevPinEdgeWaiter<'a> { + pub fn new(pin: &'a mut CdevPin, edge: EdgeDetection) -> Result { + Ok(Self { pin, edge }) + } + + pub async fn wait(self) -> Result<(), gpiocdev::Error> { + let line_handle = self.pin.0.take().unwrap(); + let line_info = &self.pin.1; + + let line = line_handle.line().clone(); + let flags = line_handle.flags(); + let chip = line.chip().path().to_owned(); + let offset = line.offset(); + let consumer = line_info.consumer().unwrap_or("").to_owned(); + let edge = self.edge; + + // Close line handle. + drop(line_handle); + + let req = Request::builder() + .on_chip(chip) + .with_line(offset) + .as_is() + .with_consumer(consumer.clone()) + .with_edge_detection(edge) + .request()?; + + let req = AsyncRequest::new(req); + let event = req.read_edge_event().await; + drop(req); + + // Recreate line handle. + self.pin.0 = Some(line.request(flags, 0, &consumer).unwrap()); + + event?; + + Ok(()) + } +} impl CdevPin { /// See [`gpio_cdev::Line::request`][0] for details. @@ -15,7 +68,11 @@ impl CdevPin { /// [0]: https://docs.rs/gpio-cdev/0.5.0/gpio_cdev/struct.Line.html#method.request pub fn new(handle: gpio_cdev::LineHandle) -> Result { let info = handle.line().info()?; - Ok(CdevPin(handle, info)) + Ok(CdevPin(Some(handle), info)) + } + + fn line_handle(&self) -> &gpio_cdev::LineHandle { + self.0.as_ref().unwrap() } fn get_input_flags(&self) -> gpio_cdev::LineRequestFlags { @@ -43,7 +100,7 @@ impl CdevPin { if self.1.direction() == gpio_cdev::LineDirection::In { return Ok(self); } - let line = self.0.line().clone(); + let line = self.line_handle().line().clone(); let input_flags = self.get_input_flags(); let consumer = self.1.consumer().unwrap_or("").to_owned(); @@ -62,7 +119,7 @@ impl CdevPin { return Ok(self); } - let line = self.0.line().clone(); + let line = self.line_handle().line().clone(); let output_flags = self.get_output_flags(); let consumer = self.1.consumer().unwrap_or("").to_owned(); @@ -138,7 +195,7 @@ impl embedded_hal::digital::ErrorType for CdevPin { impl embedded_hal::digital::OutputPin for CdevPin { fn set_low(&mut self) -> Result<(), Self::Error> { - self.0 + self.line_handle() .set_value(state_to_value( embedded_hal::digital::PinState::Low, self.1.is_active_low(), @@ -147,7 +204,7 @@ impl embedded_hal::digital::OutputPin for CdevPin { } fn set_high(&mut self) -> Result<(), Self::Error> { - self.0 + self.line_handle() .set_value(state_to_value( embedded_hal::digital::PinState::High, self.1.is_active_low(), @@ -156,9 +213,9 @@ impl embedded_hal::digital::OutputPin for CdevPin { } } -impl embedded_hal::digital::InputPin for CdevPin { +impl InputPin for CdevPin { fn is_high(&mut self) -> Result { - self.0 + self.line_handle() .get_value() .map(|val| { val == state_to_value( @@ -178,12 +235,49 @@ impl core::ops::Deref for CdevPin { type Target = gpio_cdev::LineHandle; fn deref(&self) -> &Self::Target { - &self.0 + self.line_handle() } } impl core::ops::DerefMut for CdevPin { fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 + self.0.as_mut().unwrap() + } +} + +#[cfg(feature = "async-tokio")] +impl embedded_hal_async::digital::Wait for CdevPin { + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + if self.is_high()? { + return Ok(()); + } + + self.wait_for_rising_edge().await + } + + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + if self.is_low()? { + return Ok(()); + } + + self.wait_for_falling_edge().await + } + + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + let waiter = CdevPinEdgeWaiter::new(self, EdgeDetection::RisingEdge).unwrap(); + waiter.wait().await.unwrap(); + Ok(()) + } + + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + let waiter = CdevPinEdgeWaiter::new(self, EdgeDetection::FallingEdge).unwrap(); + waiter.wait().await.unwrap(); + Ok(()) + } + + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + let waiter = CdevPinEdgeWaiter::new(self, EdgeDetection::BothEdges).unwrap(); + waiter.wait().await.unwrap(); + Ok(()) } } From 8d63222cf8416819e97c62586e7d4579d0ec3a42 Mon Sep 17 00:00:00 2001 From: Markus Reiter Date: Sat, 27 Jan 2024 22:53:08 +0100 Subject: [PATCH 02/10] Replace `gpio_cdev` with `gpiocdev`. --- Cargo.toml | 7 +- examples/gpio-wait.rs | 23 ++-- src/cdev_pin.rs | 256 ++++++++++++++++++------------------------ src/lib.rs | 2 - 4 files changed, 126 insertions(+), 162 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 26d4ad8..161f606 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,8 @@ edition = "2018" [features] gpio_sysfs = ["sysfs_gpio"] -gpio_cdev = ["gpio-cdev"] -async-tokio = ["gpio-cdev/async-tokio", "dep:embedded-hal-async", "tokio/time", "tokio/rt", "gpiocdev/async_tokio"] +gpio_cdev = ["dep:gpiocdev"] +async-tokio = ["dep:embedded-hal-async", "tokio/time", "tokio/rt", "gpiocdev?/async_tokio"] i2c = ["i2cdev"] spi = ["spidev"] @@ -25,8 +25,7 @@ default = [ "gpio_cdev", "gpio_sysfs", "i2c", "spi" ] embedded-hal = "1" embedded-hal-nb = "1" embedded-hal-async = { version = "1", optional = true } -gpio-cdev = { version = "0.6.0", optional = true } -gpiocdev = { version = "0.6.0" } +gpiocdev = { version = "0.6.1", optional = true } sysfs_gpio = { version = "0.6.1", optional = true } i2cdev = { version = "0.6.0", optional = true } nb = "1" diff --git a/examples/gpio-wait.rs b/examples/gpio-wait.rs index 05b0468..fe65d14 100644 --- a/examples/gpio-wait.rs +++ b/examples/gpio-wait.rs @@ -3,24 +3,28 @@ use std::time::Duration; use embedded_hal::digital::{InputPin, OutputPin, PinState}; use embedded_hal_async::digital::Wait; -use gpio_cdev::{Chip, LineRequestFlags}; +use gpiocdev::Request; use linux_embedded_hal::CdevPin; use tokio::time::{sleep, timeout}; // This example assumes that input/output pins are shorted. +const CHIP: &str = "/dev/gpiochip0"; const INPUT_LINE: u32 = 4; const OUTPUT_LINE: u32 = 17; #[tokio::main] async fn main() -> Result<(), Box> { - let mut chip = Chip::new("/dev/gpiochip0")?; - let input = chip.get_line(INPUT_LINE)?; - let output = chip.get_line(OUTPUT_LINE)?; - - let mut input_pin = - CdevPin::new(input.request(LineRequestFlags::INPUT, 0, "")?)?.into_input_pin()?; - let mut output_pin = CdevPin::new(output.request(LineRequestFlags::OUTPUT, 0, "")?)? - .into_output_pin(PinState::Low)?; + let input = Request::builder() + .on_chip(CHIP) + .with_line(INPUT_LINE) + .request()?; + let output = Request::builder() + .on_chip(CHIP) + .with_line(OUTPUT_LINE) + .request()?; + + let mut input_pin = CdevPin::new(input)?.into_input_pin()?; + let mut output_pin = CdevPin::new(output)?.into_output_pin(PinState::Low)?; timeout(Duration::from_secs(10), async move { let set_output = tokio::spawn(async move { @@ -30,7 +34,6 @@ async fn main() -> Result<(), Box> { }); println!("Waiting for input to go high."); - input_pin.wait_for_high().await?; assert!(input_pin.is_high()?); diff --git a/src/cdev_pin.rs b/src/cdev_pin.rs index 17bbc72..df56eb2 100644 --- a/src/cdev_pin.rs +++ b/src/cdev_pin.rs @@ -6,166 +6,146 @@ use std::fmt; use embedded_hal::digital::InputPin; #[cfg(feature = "async-tokio")] -use gpiocdev::{line::EdgeDetection, request::Request, tokio::AsyncRequest}; +use gpiocdev::{line::EdgeDetection, tokio::AsyncRequest}; +use gpiocdev::{ + line::{Offset, Value}, + request::{Config, Request}, +}; /// Newtype around [`gpio_cdev::LineHandle`] that implements the `embedded-hal` traits /// /// [`gpio_cdev::LineHandle`]: https://docs.rs/gpio-cdev/0.5.0/gpio_cdev/struct.LineHandle.html #[derive(Debug)] -pub struct CdevPin(pub Option, gpio_cdev::LineInfo); - -#[cfg(feature = "async-tokio")] -#[derive(Debug)] -struct CdevPinEdgeWaiter<'a> { - pin: &'a mut CdevPin, - edge: EdgeDetection, +pub struct CdevPin { + req: Option, + config: Config, + line: Offset, } -#[cfg(feature = "async-tokio")] -impl<'a> CdevPinEdgeWaiter<'a> { - pub fn new(pin: &'a mut CdevPin, edge: EdgeDetection) -> Result { - Ok(Self { pin, edge }) - } - - pub async fn wait(self) -> Result<(), gpiocdev::Error> { - let line_handle = self.pin.0.take().unwrap(); - let line_info = &self.pin.1; - - let line = line_handle.line().clone(); - let flags = line_handle.flags(); - let chip = line.chip().path().to_owned(); - let offset = line.offset(); - let consumer = line_info.consumer().unwrap_or("").to_owned(); - let edge = self.edge; - - // Close line handle. - drop(line_handle); - - let req = Request::builder() - .on_chip(chip) - .with_line(offset) - .as_is() - .with_consumer(consumer.clone()) - .with_edge_detection(edge) - .request()?; - - let req = AsyncRequest::new(req); - let event = req.read_edge_event().await; - drop(req); +impl CdevPin { + /// See [`gpiocdev::request::Request`] for details. + /// + /// # Panics + /// + /// Panics if the `Request` does not contain exactly one line. + pub fn new(req: Request) -> Result { + let config = req.config(); + let lines = config.lines(); - // Recreate line handle. - self.pin.0 = Some(line.request(flags, 0, &consumer).unwrap()); + assert!( + lines.len() == 1, + "A `CdevPin` must correspond to a single GPIO line." + ); + let line = lines[0]; - event?; + let config = req.config(); - Ok(()) + Ok(CdevPin { + req: Some(req), + config, + line, + }) } -} -impl CdevPin { - /// See [`gpio_cdev::Line::request`][0] for details. - /// - /// [0]: https://docs.rs/gpio-cdev/0.5.0/gpio_cdev/struct.Line.html#method.request - pub fn new(handle: gpio_cdev::LineHandle) -> Result { - let info = handle.line().info()?; - Ok(CdevPin(Some(handle), info)) + fn request(&mut self) -> Result<&Request, gpiocdev::Error> { + if self.req.is_some() { + return Ok(self.req.as_ref().unwrap()); + } + + let req = Request::from_config(self.config.clone()).request()?; + Ok(self.req.insert(req)) } - fn line_handle(&self) -> &gpio_cdev::LineHandle { - self.0.as_ref().unwrap() + fn config(&self) -> &Config { + &self.config } - fn get_input_flags(&self) -> gpio_cdev::LineRequestFlags { - if self.1.is_active_low() { - return gpio_cdev::LineRequestFlags::INPUT | gpio_cdev::LineRequestFlags::ACTIVE_LOW; - } - gpio_cdev::LineRequestFlags::INPUT + fn is_active_low(&self) -> bool { + self.line_config().active_low } - fn get_output_flags(&self) -> gpio_cdev::LineRequestFlags { - let mut flags = gpio_cdev::LineRequestFlags::OUTPUT; - if self.1.is_active_low() { - flags.insert(gpio_cdev::LineRequestFlags::ACTIVE_LOW); - } - if self.1.is_open_drain() { - flags.insert(gpio_cdev::LineRequestFlags::OPEN_DRAIN); - } else if self.1.is_open_source() { - flags.insert(gpio_cdev::LineRequestFlags::OPEN_SOURCE); - } - flags + fn line_config(&self) -> &gpiocdev::line::Config { + // Unwrapping is fine, since `self.line` comes from a `Request` and is guaranteed to exist. + self.config().line_config(self.line).unwrap() } /// Set this pin to input mode - pub fn into_input_pin(self) -> Result { - if self.1.direction() == gpio_cdev::LineDirection::In { + pub fn into_input_pin(mut self) -> Result { + let line_config = self.line_config(); + + if line_config.direction == Some(gpiocdev::line::Direction::Input) { return Ok(self); } - let line = self.line_handle().line().clone(); - let input_flags = self.get_input_flags(); - let consumer = self.1.consumer().unwrap_or("").to_owned(); - // Drop self to free the line before re-requesting it in a new mode. - std::mem::drop(self); + drop(self.req.take()); - CdevPin::new(line.request(input_flags, 0, &consumer)?) + CdevPin::new(Request::from_config(self.config).as_input().request()?) } /// Set this pin to output mode pub fn into_output_pin( - self, + mut self, state: embedded_hal::digital::PinState, - ) -> Result { - if self.1.direction() == gpio_cdev::LineDirection::Out { + ) -> Result { + let line_config = self.line_config(); + let is_active_low = line_config.active_low; + + if line_config.direction == Some(gpiocdev::line::Direction::Output) { return Ok(self); } - let line = self.line_handle().line().clone(); - let output_flags = self.get_output_flags(); - let consumer = self.1.consumer().unwrap_or("").to_owned(); + drop(self.req.take()); + + CdevPin::new( + Request::from_config(self.config) + .as_output(state_to_value(state, is_active_low)) + .request()?, + ) + } + + #[cfg(feature = "async-tokio")] + async fn wait_for_edge(&mut self, edge: EdgeDetection) -> Result<(), CdevPinError> { + let config = if let Some(req) = self.req.take() { + req.config() + } else { + self.config.clone() + }; - // Drop self to free the line before re-requesting it in a new mode. - std::mem::drop(self); + let req = Request::from_config(config) + .with_edge_detection(edge) + .request()?; - let is_active_low = output_flags.intersects(gpio_cdev::LineRequestFlags::ACTIVE_LOW); - CdevPin::new(line.request( - output_flags, - state_to_value(state, is_active_low), - &consumer, - )?) + let req = AsyncRequest::new(req); + req.read_edge_event().await?; + + Ok(()) } } /// Converts a pin state to the gpio_cdev compatible numeric value, accounting /// for the active_low condition. -fn state_to_value(state: embedded_hal::digital::PinState, is_active_low: bool) -> u8 { +fn state_to_value(state: embedded_hal::digital::PinState, is_active_low: bool) -> Value { if is_active_low { match state { - embedded_hal::digital::PinState::High => 0, - embedded_hal::digital::PinState::Low => 1, + embedded_hal::digital::PinState::High => Value::Inactive, + embedded_hal::digital::PinState::Low => Value::Active, } } else { match state { - embedded_hal::digital::PinState::High => 1, - embedded_hal::digital::PinState::Low => 0, + embedded_hal::digital::PinState::High => Value::Active, + embedded_hal::digital::PinState::Low => Value::Inactive, } } } -/// Error type wrapping [gpio_cdev::errors::Error](gpio_cdev::errors::Error) to implement [embedded_hal::digital::Error] +/// Error type wrapping [`gpiocdev::Error`] to implement [`embedded_hal::digital::Error`]. #[derive(Debug)] pub struct CdevPinError { - err: gpio_cdev::errors::Error, -} - -impl CdevPinError { - /// Fetch inner (concrete) [`gpio_cdev::errors::Error`] - pub fn inner(&self) -> &gpio_cdev::errors::Error { - &self.err - } + err: gpiocdev::Error, } -impl From for CdevPinError { - fn from(err: gpio_cdev::errors::Error) -> Self { +impl From for CdevPinError { + fn from(err: gpiocdev::Error) -> Self { Self { err } } } @@ -195,33 +175,37 @@ impl embedded_hal::digital::ErrorType for CdevPin { impl embedded_hal::digital::OutputPin for CdevPin { fn set_low(&mut self) -> Result<(), Self::Error> { - self.line_handle() - .set_value(state_to_value( - embedded_hal::digital::PinState::Low, - self.1.is_active_low(), - )) + let line = self.line; + let is_active_low = self.is_active_low(); + self.request()? + .set_value( + line, + state_to_value(embedded_hal::digital::PinState::Low, is_active_low), + ) + .map(|_| ()) .map_err(CdevPinError::from) } fn set_high(&mut self) -> Result<(), Self::Error> { - self.line_handle() - .set_value(state_to_value( - embedded_hal::digital::PinState::High, - self.1.is_active_low(), - )) + let line = self.line; + let is_active_low = self.is_active_low(); + self.request()? + .set_value( + line, + state_to_value(embedded_hal::digital::PinState::High, is_active_low), + ) + .map(|_| ()) .map_err(CdevPinError::from) } } impl InputPin for CdevPin { fn is_high(&mut self) -> Result { - self.line_handle() - .get_value() + let line = self.line; + self.request()? + .value(line) .map(|val| { - val == state_to_value( - embedded_hal::digital::PinState::High, - self.1.is_active_low(), - ) + val == state_to_value(embedded_hal::digital::PinState::High, self.is_active_low()) }) .map_err(CdevPinError::from) } @@ -231,20 +215,6 @@ impl InputPin for CdevPin { } } -impl core::ops::Deref for CdevPin { - type Target = gpio_cdev::LineHandle; - - fn deref(&self) -> &Self::Target { - self.line_handle() - } -} - -impl core::ops::DerefMut for CdevPin { - fn deref_mut(&mut self) -> &mut Self::Target { - self.0.as_mut().unwrap() - } -} - #[cfg(feature = "async-tokio")] impl embedded_hal_async::digital::Wait for CdevPin { async fn wait_for_high(&mut self) -> Result<(), Self::Error> { @@ -264,20 +234,14 @@ impl embedded_hal_async::digital::Wait for CdevPin { } async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { - let waiter = CdevPinEdgeWaiter::new(self, EdgeDetection::RisingEdge).unwrap(); - waiter.wait().await.unwrap(); - Ok(()) + self.wait_for_edge(EdgeDetection::RisingEdge).await } async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { - let waiter = CdevPinEdgeWaiter::new(self, EdgeDetection::FallingEdge).unwrap(); - waiter.wait().await.unwrap(); - Ok(()) + self.wait_for_edge(EdgeDetection::FallingEdge).await } async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { - let waiter = CdevPinEdgeWaiter::new(self, EdgeDetection::BothEdges).unwrap(); - waiter.wait().await.unwrap(); - Ok(()) + self.wait_for_edge(EdgeDetection::BothEdges).await } } diff --git a/src/lib.rs b/src/lib.rs index ee52767..e406986 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,8 +22,6 @@ pub use spidev; #[cfg(feature = "gpio_sysfs")] pub use sysfs_gpio; -#[cfg(feature = "gpio_cdev")] -pub use gpio_cdev; #[cfg(feature = "gpio_sysfs")] /// Sysfs Pin wrapper module mod sysfs_pin; From 0c33069787167c9f23978c2f5ec151ecca3ada43 Mon Sep 17 00:00:00 2001 From: Markus Reiter Date: Sun, 28 Jan 2024 04:16:45 +0100 Subject: [PATCH 03/10] Refactor `CdevPin::new`. --- examples/gpio-wait.rs | 14 ++------------ src/cdev_pin.rs | 44 +++++++++++++++++++++++++++++++++++-------- src/lib.rs | 3 --- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/examples/gpio-wait.rs b/examples/gpio-wait.rs index fe65d14..7b50aea 100644 --- a/examples/gpio-wait.rs +++ b/examples/gpio-wait.rs @@ -3,7 +3,6 @@ use std::time::Duration; use embedded_hal::digital::{InputPin, OutputPin, PinState}; use embedded_hal_async::digital::Wait; -use gpiocdev::Request; use linux_embedded_hal::CdevPin; use tokio::time::{sleep, timeout}; @@ -14,17 +13,8 @@ const OUTPUT_LINE: u32 = 17; #[tokio::main] async fn main() -> Result<(), Box> { - let input = Request::builder() - .on_chip(CHIP) - .with_line(INPUT_LINE) - .request()?; - let output = Request::builder() - .on_chip(CHIP) - .with_line(OUTPUT_LINE) - .request()?; - - let mut input_pin = CdevPin::new(input)?.into_input_pin()?; - let mut output_pin = CdevPin::new(output)?.into_output_pin(PinState::Low)?; + let mut input_pin = CdevPin::new(CHIP, INPUT_LINE)?.into_input_pin()?; + let mut output_pin = CdevPin::new(CHIP, OUTPUT_LINE)?.into_output_pin(PinState::Low)?; timeout(Duration::from_secs(10), async move { let set_output = tokio::spawn(async move { diff --git a/src/cdev_pin.rs b/src/cdev_pin.rs index df56eb2..e9dfa4b 100644 --- a/src/cdev_pin.rs +++ b/src/cdev_pin.rs @@ -3,6 +3,7 @@ //! [`embedded-hal`]: https://docs.rs/embedded-hal use std::fmt; +use std::path::Path; use embedded_hal::digital::InputPin; #[cfg(feature = "async-tokio")] @@ -12,9 +13,7 @@ use gpiocdev::{ request::{Config, Request}, }; -/// Newtype around [`gpio_cdev::LineHandle`] that implements the `embedded-hal` traits -/// -/// [`gpio_cdev::LineHandle`]: https://docs.rs/gpio-cdev/0.5.0/gpio_cdev/struct.LineHandle.html +/// Newtype around [`gpiocdev::request::Request`] that implements the `embedded-hal` traits. #[derive(Debug)] pub struct CdevPin { req: Option, @@ -23,12 +22,41 @@ pub struct CdevPin { } impl CdevPin { - /// See [`gpiocdev::request::Request`] for details. + /// Creates a new pin for the given `line` on the given `chip`. + /// + /// ``` + /// use linux_embedded_hal::CdevPin; + /// # use linux_embedded_hal::CdevPinError; + /// + /// # fn main() -> Result<(), CdevPinError> { + /// let mut pin = CdevPin::new("/dev/gpiochip0", 4)?.into_output_pin()?; + /// pin.set_high()?; + /// # } + /// ``` + pub fn new

(chip: P, line: u32) -> Result + where + P: AsRef, + { + let req = Request::builder() + .on_chip(chip.as_ref()) + .with_line(line) + .request()?; + + let config = req.config(); + + Ok(Self { + req: Some(req), + config, + line, + }) + } + + /// Creates a new pin from a [`Request`](gpiocdev::request::Request). /// /// # Panics /// - /// Panics if the `Request` does not contain exactly one line. - pub fn new(req: Request) -> Result { + /// Panics if the [`Request`](gpiocdev::request::Request) does not contain exactly one line. + pub fn from_request(req: Request) -> Result { let config = req.config(); let lines = config.lines(); @@ -79,7 +107,7 @@ impl CdevPin { drop(self.req.take()); - CdevPin::new(Request::from_config(self.config).as_input().request()?) + CdevPin::from_request(Request::from_config(self.config).as_input().request()?) } /// Set this pin to output mode @@ -96,7 +124,7 @@ impl CdevPin { drop(self.req.take()); - CdevPin::new( + CdevPin::from_request( Request::from_config(self.config) .as_output(state_to_value(state, is_active_low)) .request()?, diff --git a/src/lib.rs b/src/lib.rs index e406986..3c46074 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,11 +27,8 @@ pub use sysfs_gpio; mod sysfs_pin; #[cfg(feature = "gpio_cdev")] -/// Cdev Pin wrapper module mod cdev_pin; - #[cfg(feature = "gpio_cdev")] -/// Cdev pin re-export pub use cdev_pin::{CdevPin, CdevPinError}; #[cfg(feature = "gpio_sysfs")] From 0b0553d72abb4e0281f0c33da4981e014ecd0eb9 Mon Sep 17 00:00:00 2001 From: Markus Reiter Date: Thu, 15 Feb 2024 00:12:00 +0100 Subject: [PATCH 04/10] Don't recreate `Request`. --- src/cdev_pin.rs | 93 +++++++++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 50 deletions(-) diff --git a/src/cdev_pin.rs b/src/cdev_pin.rs index e9dfa4b..15e919e 100644 --- a/src/cdev_pin.rs +++ b/src/cdev_pin.rs @@ -14,10 +14,12 @@ use gpiocdev::{ }; /// Newtype around [`gpiocdev::request::Request`] that implements the `embedded-hal` traits. -#[derive(Debug)] +#[cfg_attr(not(feature = "async-tokio"), derive(Debug))] pub struct CdevPin { - req: Option, - config: Config, + #[cfg(not(feature = "async-tokio"))] + req: Request, + #[cfg(feature = "async-tokio")] + req: AsyncRequest, line: Offset, } @@ -42,13 +44,10 @@ impl CdevPin { .with_line(line) .request()?; - let config = req.config(); + #[cfg(feature = "async-tokio")] + let req = AsyncRequest::new(req); - Ok(Self { - req: Some(req), - config, - line, - }) + Ok(Self { req, line }) } /// Creates a new pin from a [`Request`](gpiocdev::request::Request). @@ -66,86 +65,80 @@ impl CdevPin { ); let line = lines[0]; - let config = req.config(); + #[cfg(feature = "async-tokio")] + let req = AsyncRequest::new(req); - Ok(CdevPin { - req: Some(req), - config, - line, - }) + Ok(CdevPin { req, line }) } - fn request(&mut self) -> Result<&Request, gpiocdev::Error> { - if self.req.is_some() { - return Ok(self.req.as_ref().unwrap()); + #[inline] + fn request(&self) -> &Request { + #[cfg(not(feature = "async-tokio"))] + { + &self.req } - let req = Request::from_config(self.config.clone()).request()?; - Ok(self.req.insert(req)) + #[cfg(feature = "async-tokio")] + { + self.req.as_ref() + } } - fn config(&self) -> &Config { - &self.config + fn config(&self) -> Config { + self.request().config() } fn is_active_low(&self) -> bool { self.line_config().active_low } - fn line_config(&self) -> &gpiocdev::line::Config { + fn line_config(&self) -> gpiocdev::line::Config { // Unwrapping is fine, since `self.line` comes from a `Request` and is guaranteed to exist. - self.config().line_config(self.line).unwrap() + self.config().line_config(self.line).unwrap().clone() } /// Set this pin to input mode - pub fn into_input_pin(mut self) -> Result { + pub fn into_input_pin(self) -> Result { let line_config = self.line_config(); if line_config.direction == Some(gpiocdev::line::Direction::Input) { return Ok(self); } - drop(self.req.take()); + let mut new_config = self.config(); + new_config.as_input(); + self.request().reconfigure(&new_config)?; - CdevPin::from_request(Request::from_config(self.config).as_input().request()?) + Ok(self) } /// Set this pin to output mode pub fn into_output_pin( - mut self, + self, state: embedded_hal::digital::PinState, ) -> Result { let line_config = self.line_config(); - let is_active_low = line_config.active_low; if line_config.direction == Some(gpiocdev::line::Direction::Output) { return Ok(self); } - drop(self.req.take()); + let mut new_config = self.config(); + new_config.as_output(state_to_value(state, line_config.active_low)); + self.request().reconfigure(&new_config)?; - CdevPin::from_request( - Request::from_config(self.config) - .as_output(state_to_value(state, is_active_low)) - .request()?, - ) + Ok(self) } #[cfg(feature = "async-tokio")] async fn wait_for_edge(&mut self, edge: EdgeDetection) -> Result<(), CdevPinError> { - let config = if let Some(req) = self.req.take() { - req.config() - } else { - self.config.clone() - }; - - let req = Request::from_config(config) - .with_edge_detection(edge) - .request()?; - - let req = AsyncRequest::new(req); - req.read_edge_event().await?; + if self.line_config().edge_detection != Some(edge) { + let mut new_config = self.config(); + new_config.with_edge_detection(edge); + self.request().reconfigure(&new_config)?; + } + self.req.read_edge_event().await?; Ok(()) } } @@ -205,7 +198,7 @@ impl embedded_hal::digital::OutputPin for CdevPin { fn set_low(&mut self) -> Result<(), Self::Error> { let line = self.line; let is_active_low = self.is_active_low(); - self.request()? + self.request() .set_value( line, state_to_value(embedded_hal::digital::PinState::Low, is_active_low), @@ -217,7 +210,7 @@ impl embedded_hal::digital::OutputPin for CdevPin { fn set_high(&mut self) -> Result<(), Self::Error> { let line = self.line; let is_active_low = self.is_active_low(); - self.request()? + self.request() .set_value( line, state_to_value(embedded_hal::digital::PinState::High, is_active_low), @@ -230,7 +223,7 @@ impl embedded_hal::digital::OutputPin for CdevPin { impl InputPin for CdevPin { fn is_high(&mut self) -> Result { let line = self.line; - self.request()? + self.request() .value(line) .map(|val| { val == state_to_value(embedded_hal::digital::PinState::High, self.is_active_low()) From 53a2df38b72b67180c67e618afd6d9c741b49a29 Mon Sep 17 00:00:00 2001 From: Markus Reiter Date: Thu, 15 Feb 2024 01:06:01 +0100 Subject: [PATCH 05/10] Don't get config multiple times. --- src/cdev_pin.rs | 96 ++++++++++++++++++++++++++++++------------------- 1 file changed, 60 insertions(+), 36 deletions(-) diff --git a/src/cdev_pin.rs b/src/cdev_pin.rs index 15e919e..fa3db15 100644 --- a/src/cdev_pin.rs +++ b/src/cdev_pin.rs @@ -7,7 +7,10 @@ use std::path::Path; use embedded_hal::digital::InputPin; #[cfg(feature = "async-tokio")] -use gpiocdev::{line::EdgeDetection, tokio::AsyncRequest}; +use gpiocdev::{ + line::{EdgeDetection, EdgeKind}, + tokio::AsyncRequest, +}; use gpiocdev::{ line::{Offset, Value}, request::{Config, Request}, @@ -88,24 +91,16 @@ impl CdevPin { self.request().config() } - fn is_active_low(&self) -> bool { - self.line_config().active_low - } - - fn line_config(&self) -> gpiocdev::line::Config { - // Unwrapping is fine, since `self.line` comes from a `Request` and is guaranteed to exist. - self.config().line_config(self.line).unwrap().clone() - } - /// Set this pin to input mode pub fn into_input_pin(self) -> Result { - let line_config = self.line_config(); + let config = self.config(); + let line_config = config.line_config(self.line).unwrap(); if line_config.direction == Some(gpiocdev::line::Direction::Input) { return Ok(self); } - let mut new_config = self.config(); + let mut new_config = config; new_config.as_input(); self.request().reconfigure(&new_config)?; @@ -117,30 +112,19 @@ impl CdevPin { self, state: embedded_hal::digital::PinState, ) -> Result { - let line_config = self.line_config(); - + let config = self.config(); + let line_config = config.line_config(self.line).unwrap(); if line_config.direction == Some(gpiocdev::line::Direction::Output) { return Ok(self); } + let is_active_low = line_config.active_low; - let mut new_config = self.config(); - new_config.as_output(state_to_value(state, line_config.active_low)); + let mut new_config = config; + new_config.as_output(state_to_value(state, is_active_low)); self.request().reconfigure(&new_config)?; Ok(self) } - - #[cfg(feature = "async-tokio")] - async fn wait_for_edge(&mut self, edge: EdgeDetection) -> Result<(), CdevPinError> { - if self.line_config().edge_detection != Some(edge) { - let mut new_config = self.config(); - new_config.with_edge_detection(edge); - self.request().reconfigure(&new_config)?; - } - - self.req.read_edge_event().await?; - Ok(()) - } } /// Converts a pin state to the gpio_cdev compatible numeric value, accounting @@ -197,7 +181,7 @@ impl embedded_hal::digital::ErrorType for CdevPin { impl embedded_hal::digital::OutputPin for CdevPin { fn set_low(&mut self) -> Result<(), Self::Error> { let line = self.line; - let is_active_low = self.is_active_low(); + let is_active_low = self.config().line_config(line).unwrap().active_low; self.request() .set_value( line, @@ -209,7 +193,7 @@ impl embedded_hal::digital::OutputPin for CdevPin { fn set_high(&mut self) -> Result<(), Self::Error> { let line = self.line; - let is_active_low = self.is_active_low(); + let is_active_low = self.config().line_config(line).unwrap().active_low; self.request() .set_value( line, @@ -223,11 +207,10 @@ impl embedded_hal::digital::OutputPin for CdevPin { impl InputPin for CdevPin { fn is_high(&mut self) -> Result { let line = self.line; + let is_active_low = self.config().line_config(line).unwrap().active_low; self.request() .value(line) - .map(|val| { - val == state_to_value(embedded_hal::digital::PinState::High, self.is_active_low()) - }) + .map(|val| val == state_to_value(embedded_hal::digital::PinState::High, is_active_low)) .map_err(CdevPinError::from) } @@ -255,14 +238,55 @@ impl embedded_hal_async::digital::Wait for CdevPin { } async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { - self.wait_for_edge(EdgeDetection::RisingEdge).await + let config = self.config(); + let line_config = config.line_config(self.line).unwrap(); + if !matches!( + line_config.edge_detection, + Some(EdgeDetection::RisingEdge | EdgeDetection::BothEdges) + ) { + let mut new_config = config; + new_config.with_edge_detection(EdgeDetection::RisingEdge); + self.request().reconfigure(&new_config)?; + } + + loop { + let event = self.req.read_edge_event().await?; + if event.kind == EdgeKind::Rising { + return Ok(()); + } + } } async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { - self.wait_for_edge(EdgeDetection::FallingEdge).await + let config = self.config(); + let line_config = config.line_config(self.line).unwrap(); + if !matches!( + line_config.edge_detection, + Some(EdgeDetection::FallingEdge | EdgeDetection::BothEdges) + ) { + let mut new_config = config; + new_config.with_edge_detection(EdgeDetection::FallingEdge); + self.request().reconfigure(&new_config)?; + } + + loop { + let event = self.req.read_edge_event().await?; + if event.kind == EdgeKind::Falling { + return Ok(()); + } + } } async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { - self.wait_for_edge(EdgeDetection::BothEdges).await + let config = self.config(); + let line_config = config.line_config(self.line).unwrap(); + if line_config.edge_detection != Some(EdgeDetection::BothEdges) { + let mut new_config = config; + new_config.with_edge_detection(EdgeDetection::BothEdges); + self.request().reconfigure(&new_config)?; + } + + self.req.read_edge_event().await?; + Ok(()) } } From a20f347b642e44a3a6bb12e66b203ec26decd3de Mon Sep 17 00:00:00 2001 From: Markus Reiter Date: Fri, 16 Feb 2024 05:35:29 +0100 Subject: [PATCH 06/10] Fix documentation. --- src/cdev_pin.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cdev_pin.rs b/src/cdev_pin.rs index fa3db15..5ff5d16 100644 --- a/src/cdev_pin.rs +++ b/src/cdev_pin.rs @@ -1,4 +1,4 @@ -//! Implementation of [`embedded-hal`] digital input/output traits using a Linux CDev pin +//! Implementation of [`embedded-hal`] digital input/output traits using a Linux cdev pin. //! //! [`embedded-hal`]: https://docs.rs/embedded-hal @@ -16,8 +16,8 @@ use gpiocdev::{ request::{Config, Request}, }; -/// Newtype around [`gpiocdev::request::Request`] that implements the `embedded-hal` traits. -#[cfg_attr(not(feature = "async-tokio"), derive(Debug))] +/// Wrapper around [`gpiocdev::request::Request`] that implements the `embedded-hal` traits. +#[derive(Debug)] pub struct CdevPin { #[cfg(not(feature = "async-tokio"))] req: Request, From 1f6a4db8c1f771d5ffcd469396c7a99ad11e08fe Mon Sep 17 00:00:00 2001 From: Markus Reiter Date: Fri, 16 Feb 2024 05:35:45 +0100 Subject: [PATCH 07/10] Remove `CdevPin::from_request`. --- src/cdev_pin.rs | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/cdev_pin.rs b/src/cdev_pin.rs index 5ff5d16..ef65bc5 100644 --- a/src/cdev_pin.rs +++ b/src/cdev_pin.rs @@ -53,27 +53,6 @@ impl CdevPin { Ok(Self { req, line }) } - /// Creates a new pin from a [`Request`](gpiocdev::request::Request). - /// - /// # Panics - /// - /// Panics if the [`Request`](gpiocdev::request::Request) does not contain exactly one line. - pub fn from_request(req: Request) -> Result { - let config = req.config(); - let lines = config.lines(); - - assert!( - lines.len() == 1, - "A `CdevPin` must correspond to a single GPIO line." - ); - let line = lines[0]; - - #[cfg(feature = "async-tokio")] - let req = AsyncRequest::new(req); - - Ok(CdevPin { req, line }) - } - #[inline] fn request(&self) -> &Request { #[cfg(not(feature = "async-tokio"))] From aec3243a2e027857ec11ce108df89a512c3d53e1 Mon Sep 17 00:00:00 2001 From: Markus Reiter Date: Fri, 16 Feb 2024 06:44:45 +0100 Subject: [PATCH 08/10] Simplify implementation by using marker types for input/output mode. --- Cargo.toml | 4 + examples/gpio-wait.rs | 4 +- src/cdev_pin.rs | 256 ++++++++++++++++++++++++------------------ 3 files changed, 151 insertions(+), 113 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 161f606..f0749e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,3 +46,7 @@ version = "0.3" [[example]] name = "gpio-wait" required-features = ["async-tokio"] + + +[patch.crates-io] +gpiocdev = { git = "https://github.com/warthog618/gpiocdev-rs" } diff --git a/examples/gpio-wait.rs b/examples/gpio-wait.rs index 7b50aea..d95e95a 100644 --- a/examples/gpio-wait.rs +++ b/examples/gpio-wait.rs @@ -13,8 +13,8 @@ const OUTPUT_LINE: u32 = 17; #[tokio::main] async fn main() -> Result<(), Box> { - let mut input_pin = CdevPin::new(CHIP, INPUT_LINE)?.into_input_pin()?; - let mut output_pin = CdevPin::new(CHIP, OUTPUT_LINE)?.into_output_pin(PinState::Low)?; + let mut input_pin = CdevPin::new_input(CHIP, INPUT_LINE)?; + let mut output_pin = CdevPin::new_output(CHIP, OUTPUT_LINE, PinState::Low)?; timeout(Duration::from_secs(10), async move { let set_output = tokio::spawn(async move { diff --git a/src/cdev_pin.rs b/src/cdev_pin.rs index ef65bc5..8a45089 100644 --- a/src/cdev_pin.rs +++ b/src/cdev_pin.rs @@ -3,56 +3,124 @@ //! [`embedded-hal`]: https://docs.rs/embedded-hal use std::fmt; +use std::marker::PhantomData; use std::path::Path; -use embedded_hal::digital::InputPin; +use embedded_hal::digital::{Error, ErrorType, InputPin, OutputPin, PinState, StatefulOutputPin}; +use gpiocdev::{ + line::{Config, Direction, Offset, Value}, + request::Request, +}; #[cfg(feature = "async-tokio")] use gpiocdev::{ line::{EdgeDetection, EdgeKind}, tokio::AsyncRequest, }; -use gpiocdev::{ - line::{Offset, Value}, - request::{Config, Request}, -}; + +/// Marker type for a [`CdevPin`] in input mode. +#[non_exhaustive] +pub struct Input; + +/// Marker type for a [`CdevPin`] in output mode. +#[non_exhaustive] +pub struct Output; /// Wrapper around [`gpiocdev::request::Request`] that implements the `embedded-hal` traits. #[derive(Debug)] -pub struct CdevPin { +pub struct CdevPin { #[cfg(not(feature = "async-tokio"))] req: Request, #[cfg(feature = "async-tokio")] req: AsyncRequest, line: Offset, + line_config: Config, + mode: PhantomData, } -impl CdevPin { - /// Creates a new pin for the given `line` on the given `chip`. +impl CdevPin { + /// Creates a new input pin for the given `line` on the given `chip`. /// /// ``` /// use linux_embedded_hal::CdevPin; /// # use linux_embedded_hal::CdevPinError; /// /// # fn main() -> Result<(), CdevPinError> { - /// let mut pin = CdevPin::new("/dev/gpiochip0", 4)?.into_output_pin()?; + /// let mut pin = CdevPin::new_input("/dev/gpiochip0", 4)?; + /// pin.is_high()?; + /// # } + /// ``` + pub fn new_input

(chip: P, line: u32) -> Result + where + P: AsRef, + { + let line_config = Config { + direction: Some(Direction::Input), + ..Default::default() + }; + + let req = Request::builder() + .on_chip(chip.as_ref()) + .from_line_config(&line_config) + .request()?; + + #[cfg(feature = "async-tokio")] + let req = AsyncRequest::new(req); + + Ok(Self { + req, + line, + line_config, + mode: PhantomData, + }) + } +} + +impl CdevPin { + /// Creates a new output pin for the given `line` on the given `chip`, + /// initialized with the given `initial_state`. + /// + /// ``` + /// use linux_embedded_hal::CdevPin; + /// # use linux_embedded_hal::CdevPinError; + /// + /// # fn main() -> Result<(), CdevPinError> { + /// let mut pin = CdevPin::new_output("/dev/gpiochip0", 4)?; + /// pin.is_set_high()?; /// pin.set_high()?; /// # } /// ``` - pub fn new

(chip: P, line: u32) -> Result + pub fn new_output

(chip: P, line: u32, initial_state: PinState) -> Result where P: AsRef, { + let line_config = Config { + direction: Some(Direction::Output), + active_low: false, + value: Some(match initial_state { + PinState::High => Value::Active, + PinState::Low => Value::Inactive, + }), + ..Default::default() + }; + let req = Request::builder() .on_chip(chip.as_ref()) - .with_line(line) + .from_line_config(&line_config) .request()?; #[cfg(feature = "async-tokio")] let req = AsyncRequest::new(req); - Ok(Self { req, line }) + Ok(Self { + req, + line, + line_config, + mode: PhantomData, + }) } +} +impl CdevPin { #[inline] fn request(&self) -> &Request { #[cfg(not(feature = "async-tokio"))] @@ -66,58 +134,19 @@ impl CdevPin { } } - fn config(&self) -> Config { - self.request().config() - } - - /// Set this pin to input mode - pub fn into_input_pin(self) -> Result { - let config = self.config(); - let line_config = config.line_config(self.line).unwrap(); - - if line_config.direction == Some(gpiocdev::line::Direction::Input) { - return Ok(self); - } - - let mut new_config = config; - new_config.as_input(); - self.request().reconfigure(&new_config)?; - - Ok(self) - } - - /// Set this pin to output mode - pub fn into_output_pin( - self, - state: embedded_hal::digital::PinState, - ) -> Result { - let config = self.config(); - let line_config = config.line_config(self.line).unwrap(); - if line_config.direction == Some(gpiocdev::line::Direction::Output) { - return Ok(self); - } - let is_active_low = line_config.active_low; - - let mut new_config = config; - new_config.as_output(state_to_value(state, is_active_low)); - self.request().reconfigure(&new_config)?; - - Ok(self) - } -} - -/// Converts a pin state to the gpio_cdev compatible numeric value, accounting -/// for the active_low condition. -fn state_to_value(state: embedded_hal::digital::PinState, is_active_low: bool) -> Value { - if is_active_low { - match state { - embedded_hal::digital::PinState::High => Value::Inactive, - embedded_hal::digital::PinState::Low => Value::Active, - } - } else { - match state { - embedded_hal::digital::PinState::High => Value::Active, - embedded_hal::digital::PinState::Low => Value::Inactive, + /// Converts a pin state to a value, depending on + /// whether the pin is configured as active-low. + fn state_to_value(&self, state: PinState) -> Value { + if self.line_config.active_low { + match state { + PinState::High => Value::Inactive, + PinState::Low => Value::Active, + } + } else { + match state { + PinState::High => Value::Active, + PinState::Low => Value::Inactive, + } } } } @@ -146,60 +175,65 @@ impl std::error::Error for CdevPinError { } } -impl embedded_hal::digital::Error for CdevPinError { +impl Error for CdevPinError { fn kind(&self) -> embedded_hal::digital::ErrorKind { use embedded_hal::digital::ErrorKind; ErrorKind::Other } } -impl embedded_hal::digital::ErrorType for CdevPin { +impl ErrorType for CdevPin { type Error = CdevPinError; } -impl embedded_hal::digital::OutputPin for CdevPin { +impl InputPin for CdevPin { + fn is_low(&mut self) -> Result { + let low_value = self.state_to_value(PinState::Low); + Ok(self.request().value(self.line)? == low_value) + } + + fn is_high(&mut self) -> Result { + let high_value = self.state_to_value(PinState::High); + Ok(self.request().value(self.line)? == high_value) + } +} + +impl OutputPin for CdevPin { fn set_low(&mut self) -> Result<(), Self::Error> { - let line = self.line; - let is_active_low = self.config().line_config(line).unwrap().active_low; - self.request() - .set_value( - line, - state_to_value(embedded_hal::digital::PinState::Low, is_active_low), - ) - .map(|_| ()) - .map_err(CdevPinError::from) + let new_value = self.state_to_value(PinState::Low); + + self.request().set_value(self.line, new_value)?; + self.line_config.value = Some(new_value); + + Ok(()) } fn set_high(&mut self) -> Result<(), Self::Error> { - let line = self.line; - let is_active_low = self.config().line_config(line).unwrap().active_low; - self.request() - .set_value( - line, - state_to_value(embedded_hal::digital::PinState::High, is_active_low), - ) - .map(|_| ()) - .map_err(CdevPinError::from) + let new_value = self.state_to_value(PinState::High); + + self.request().set_value(self.line, new_value)?; + self.line_config.value = Some(new_value); + + Ok(()) } } -impl InputPin for CdevPin { - fn is_high(&mut self) -> Result { - let line = self.line; - let is_active_low = self.config().line_config(line).unwrap().active_low; - self.request() - .value(line) - .map(|val| val == state_to_value(embedded_hal::digital::PinState::High, is_active_low)) - .map_err(CdevPinError::from) +impl StatefulOutputPin for CdevPin { + #[inline] + fn is_set_low(&mut self) -> Result { + let low_value = self.state_to_value(PinState::Low); + Ok(self.line_config.value == Some(low_value)) } - fn is_low(&mut self) -> Result { - self.is_high().map(|val| !val) + #[inline] + fn is_set_high(&mut self) -> Result { + let high_value = self.state_to_value(PinState::High); + Ok(self.line_config.value == Some(high_value)) } } #[cfg(feature = "async-tokio")] -impl embedded_hal_async::digital::Wait for CdevPin { +impl embedded_hal_async::digital::Wait for CdevPin { async fn wait_for_high(&mut self) -> Result<(), Self::Error> { if self.is_high()? { return Ok(()); @@ -217,15 +251,14 @@ impl embedded_hal_async::digital::Wait for CdevPin { } async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { - let config = self.config(); - let line_config = config.line_config(self.line).unwrap(); if !matches!( - line_config.edge_detection, + self.line_config.edge_detection, Some(EdgeDetection::RisingEdge | EdgeDetection::BothEdges) ) { - let mut new_config = config; + let mut new_config = self.req.as_ref().config(); new_config.with_edge_detection(EdgeDetection::RisingEdge); - self.request().reconfigure(&new_config)?; + self.req.as_ref().reconfigure(&new_config)?; + self.line_config.edge_detection = Some(EdgeDetection::RisingEdge); } loop { @@ -237,15 +270,14 @@ impl embedded_hal_async::digital::Wait for CdevPin { } async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { - let config = self.config(); - let line_config = config.line_config(self.line).unwrap(); if !matches!( - line_config.edge_detection, + self.line_config.edge_detection, Some(EdgeDetection::FallingEdge | EdgeDetection::BothEdges) ) { - let mut new_config = config; + let mut new_config = self.req.as_ref().config(); new_config.with_edge_detection(EdgeDetection::FallingEdge); - self.request().reconfigure(&new_config)?; + self.req.as_ref().reconfigure(&new_config)?; + self.line_config.edge_detection = Some(EdgeDetection::FallingEdge); } loop { @@ -257,12 +289,14 @@ impl embedded_hal_async::digital::Wait for CdevPin { } async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { - let config = self.config(); - let line_config = config.line_config(self.line).unwrap(); - if line_config.edge_detection != Some(EdgeDetection::BothEdges) { - let mut new_config = config; + if !matches!( + self.line_config.edge_detection, + Some(EdgeDetection::BothEdges) + ) { + let mut new_config = self.req.as_ref().config(); new_config.with_edge_detection(EdgeDetection::BothEdges); - self.request().reconfigure(&new_config)?; + self.req.as_ref().reconfigure(&new_config)?; + self.line_config.edge_detection = Some(EdgeDetection::BothEdges); } self.req.read_edge_event().await?; From 6f39de67365a8395359176942303e9db147aed6d Mon Sep 17 00:00:00 2001 From: Markus Reiter Date: Fri, 16 Feb 2024 07:02:24 +0100 Subject: [PATCH 09/10] Remove unneeded `request` method. --- src/cdev_pin.rs | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/src/cdev_pin.rs b/src/cdev_pin.rs index 8a45089..a74e44b 100644 --- a/src/cdev_pin.rs +++ b/src/cdev_pin.rs @@ -121,19 +121,6 @@ impl CdevPin { } impl CdevPin { - #[inline] - fn request(&self) -> &Request { - #[cfg(not(feature = "async-tokio"))] - { - &self.req - } - - #[cfg(feature = "async-tokio")] - { - self.req.as_ref() - } - } - /// Converts a pin state to a value, depending on /// whether the pin is configured as active-low. fn state_to_value(&self, state: PinState) -> Value { @@ -189,12 +176,12 @@ impl ErrorType for CdevPin { impl InputPin for CdevPin { fn is_low(&mut self) -> Result { let low_value = self.state_to_value(PinState::Low); - Ok(self.request().value(self.line)? == low_value) + Ok(self.req.as_ref().value(self.line)? == low_value) } fn is_high(&mut self) -> Result { let high_value = self.state_to_value(PinState::High); - Ok(self.request().value(self.line)? == high_value) + Ok(self.req.as_ref().value(self.line)? == high_value) } } @@ -202,7 +189,7 @@ impl OutputPin for CdevPin { fn set_low(&mut self) -> Result<(), Self::Error> { let new_value = self.state_to_value(PinState::Low); - self.request().set_value(self.line, new_value)?; + self.req.as_ref().set_value(self.line, new_value)?; self.line_config.value = Some(new_value); Ok(()) @@ -211,7 +198,7 @@ impl OutputPin for CdevPin { fn set_high(&mut self) -> Result<(), Self::Error> { let new_value = self.state_to_value(PinState::High); - self.request().set_value(self.line, new_value)?; + self.req.as_ref().set_value(self.line, new_value)?; self.line_config.value = Some(new_value); Ok(()) From 2021ba5878cea59a34e55596c4bc058407fd2f46 Mon Sep 17 00:00:00 2001 From: Markus Reiter Date: Fri, 16 Feb 2024 07:24:04 +0100 Subject: [PATCH 10/10] Add conversion between input/output pins. --- src/cdev_pin.rs | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/cdev_pin.rs b/src/cdev_pin.rs index a74e44b..77a3df8 100644 --- a/src/cdev_pin.rs +++ b/src/cdev_pin.rs @@ -73,6 +73,26 @@ impl CdevPin { mode: PhantomData, }) } + + /// Converts this input pin into an output pin with the given `initial_state`. + pub fn into_output

(self, initial_state: PinState) -> Result, CdevPinError> { + let new_value = self.state_to_value(initial_state); + + let req = self.req; + let mut new_config = req.as_ref().config(); + new_config.as_output(new_value); + req.as_ref().reconfigure(&new_config)?; + + let line = self.line; + let line_config = new_config.line_config(line).unwrap().clone(); + + Ok(CdevPin { + req, + line, + line_config, + mode: PhantomData, + }) + } } impl CdevPin { @@ -118,6 +138,24 @@ impl CdevPin { mode: PhantomData, }) } + + /// Converts this output pin into an input pin. + pub fn into_input

(self) -> Result, CdevPinError> { + let req = self.req; + let mut new_config = req.as_ref().config(); + new_config.as_input(); + req.as_ref().reconfigure(&new_config)?; + + let line = self.line; + let line_config = new_config.line_config(line).unwrap().clone(); + + Ok(CdevPin { + req, + line, + line_config, + mode: PhantomData, + }) + } } impl CdevPin { @@ -242,9 +280,10 @@ impl embedded_hal_async::digital::Wait for CdevPin { self.line_config.edge_detection, Some(EdgeDetection::RisingEdge | EdgeDetection::BothEdges) ) { - let mut new_config = self.req.as_ref().config(); + let req = self.req.as_ref(); + let mut new_config = req.config(); new_config.with_edge_detection(EdgeDetection::RisingEdge); - self.req.as_ref().reconfigure(&new_config)?; + req.reconfigure(&new_config)?; self.line_config.edge_detection = Some(EdgeDetection::RisingEdge); }