Skip to content

Commit aec3243

Browse files
committed
Simplify implementation by using marker types for input/output mode.
1 parent 1f6a4db commit aec3243

File tree

3 files changed

+151
-113
lines changed

3 files changed

+151
-113
lines changed

Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,7 @@ version = "0.3"
4646
[[example]]
4747
name = "gpio-wait"
4848
required-features = ["async-tokio"]
49+
50+
51+
[patch.crates-io]
52+
gpiocdev = { git = "https://github.com/warthog618/gpiocdev-rs" }

examples/gpio-wait.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ const OUTPUT_LINE: u32 = 17;
1313

1414
#[tokio::main]
1515
async fn main() -> Result<(), Box<dyn Error>> {
16-
let mut input_pin = CdevPin::new(CHIP, INPUT_LINE)?.into_input_pin()?;
17-
let mut output_pin = CdevPin::new(CHIP, OUTPUT_LINE)?.into_output_pin(PinState::Low)?;
16+
let mut input_pin = CdevPin::new_input(CHIP, INPUT_LINE)?;
17+
let mut output_pin = CdevPin::new_output(CHIP, OUTPUT_LINE, PinState::Low)?;
1818

1919
timeout(Duration::from_secs(10), async move {
2020
let set_output = tokio::spawn(async move {

src/cdev_pin.rs

+145-111
Original file line numberDiff line numberDiff line change
@@ -3,56 +3,124 @@
33
//! [`embedded-hal`]: https://docs.rs/embedded-hal
44
55
use std::fmt;
6+
use std::marker::PhantomData;
67
use std::path::Path;
78

8-
use embedded_hal::digital::InputPin;
9+
use embedded_hal::digital::{Error, ErrorType, InputPin, OutputPin, PinState, StatefulOutputPin};
10+
use gpiocdev::{
11+
line::{Config, Direction, Offset, Value},
12+
request::Request,
13+
};
914
#[cfg(feature = "async-tokio")]
1015
use gpiocdev::{
1116
line::{EdgeDetection, EdgeKind},
1217
tokio::AsyncRequest,
1318
};
14-
use gpiocdev::{
15-
line::{Offset, Value},
16-
request::{Config, Request},
17-
};
19+
20+
/// Marker type for a [`CdevPin`] in input mode.
21+
#[non_exhaustive]
22+
pub struct Input;
23+
24+
/// Marker type for a [`CdevPin`] in output mode.
25+
#[non_exhaustive]
26+
pub struct Output;
1827

1928
/// Wrapper around [`gpiocdev::request::Request`] that implements the `embedded-hal` traits.
2029
#[derive(Debug)]
21-
pub struct CdevPin {
30+
pub struct CdevPin<MODE> {
2231
#[cfg(not(feature = "async-tokio"))]
2332
req: Request,
2433
#[cfg(feature = "async-tokio")]
2534
req: AsyncRequest,
2635
line: Offset,
36+
line_config: Config,
37+
mode: PhantomData<MODE>,
2738
}
2839

29-
impl CdevPin {
30-
/// Creates a new pin for the given `line` on the given `chip`.
40+
impl CdevPin<Input> {
41+
/// Creates a new input pin for the given `line` on the given `chip`.
3142
///
3243
/// ```
3344
/// use linux_embedded_hal::CdevPin;
3445
/// # use linux_embedded_hal::CdevPinError;
3546
///
3647
/// # fn main() -> Result<(), CdevPinError> {
37-
/// let mut pin = CdevPin::new("/dev/gpiochip0", 4)?.into_output_pin()?;
48+
/// let mut pin = CdevPin::new_input("/dev/gpiochip0", 4)?;
49+
/// pin.is_high()?;
50+
/// # }
51+
/// ```
52+
pub fn new_input<P>(chip: P, line: u32) -> Result<Self, CdevPinError>
53+
where
54+
P: AsRef<Path>,
55+
{
56+
let line_config = Config {
57+
direction: Some(Direction::Input),
58+
..Default::default()
59+
};
60+
61+
let req = Request::builder()
62+
.on_chip(chip.as_ref())
63+
.from_line_config(&line_config)
64+
.request()?;
65+
66+
#[cfg(feature = "async-tokio")]
67+
let req = AsyncRequest::new(req);
68+
69+
Ok(Self {
70+
req,
71+
line,
72+
line_config,
73+
mode: PhantomData,
74+
})
75+
}
76+
}
77+
78+
impl CdevPin<Output> {
79+
/// Creates a new output pin for the given `line` on the given `chip`,
80+
/// initialized with the given `initial_state`.
81+
///
82+
/// ```
83+
/// use linux_embedded_hal::CdevPin;
84+
/// # use linux_embedded_hal::CdevPinError;
85+
///
86+
/// # fn main() -> Result<(), CdevPinError> {
87+
/// let mut pin = CdevPin::new_output("/dev/gpiochip0", 4)?;
88+
/// pin.is_set_high()?;
3889
/// pin.set_high()?;
3990
/// # }
4091
/// ```
41-
pub fn new<P>(chip: P, line: u32) -> Result<Self, CdevPinError>
92+
pub fn new_output<P>(chip: P, line: u32, initial_state: PinState) -> Result<Self, CdevPinError>
4293
where
4394
P: AsRef<Path>,
4495
{
96+
let line_config = Config {
97+
direction: Some(Direction::Output),
98+
active_low: false,
99+
value: Some(match initial_state {
100+
PinState::High => Value::Active,
101+
PinState::Low => Value::Inactive,
102+
}),
103+
..Default::default()
104+
};
105+
45106
let req = Request::builder()
46107
.on_chip(chip.as_ref())
47-
.with_line(line)
108+
.from_line_config(&line_config)
48109
.request()?;
49110

50111
#[cfg(feature = "async-tokio")]
51112
let req = AsyncRequest::new(req);
52113

53-
Ok(Self { req, line })
114+
Ok(Self {
115+
req,
116+
line,
117+
line_config,
118+
mode: PhantomData,
119+
})
54120
}
121+
}
55122

123+
impl<MODE> CdevPin<MODE> {
56124
#[inline]
57125
fn request(&self) -> &Request {
58126
#[cfg(not(feature = "async-tokio"))]
@@ -66,58 +134,19 @@ impl CdevPin {
66134
}
67135
}
68136

69-
fn config(&self) -> Config {
70-
self.request().config()
71-
}
72-
73-
/// Set this pin to input mode
74-
pub fn into_input_pin(self) -> Result<CdevPin, CdevPinError> {
75-
let config = self.config();
76-
let line_config = config.line_config(self.line).unwrap();
77-
78-
if line_config.direction == Some(gpiocdev::line::Direction::Input) {
79-
return Ok(self);
80-
}
81-
82-
let mut new_config = config;
83-
new_config.as_input();
84-
self.request().reconfigure(&new_config)?;
85-
86-
Ok(self)
87-
}
88-
89-
/// Set this pin to output mode
90-
pub fn into_output_pin(
91-
self,
92-
state: embedded_hal::digital::PinState,
93-
) -> Result<CdevPin, CdevPinError> {
94-
let config = self.config();
95-
let line_config = config.line_config(self.line).unwrap();
96-
if line_config.direction == Some(gpiocdev::line::Direction::Output) {
97-
return Ok(self);
98-
}
99-
let is_active_low = line_config.active_low;
100-
101-
let mut new_config = config;
102-
new_config.as_output(state_to_value(state, is_active_low));
103-
self.request().reconfigure(&new_config)?;
104-
105-
Ok(self)
106-
}
107-
}
108-
109-
/// Converts a pin state to the gpio_cdev compatible numeric value, accounting
110-
/// for the active_low condition.
111-
fn state_to_value(state: embedded_hal::digital::PinState, is_active_low: bool) -> Value {
112-
if is_active_low {
113-
match state {
114-
embedded_hal::digital::PinState::High => Value::Inactive,
115-
embedded_hal::digital::PinState::Low => Value::Active,
116-
}
117-
} else {
118-
match state {
119-
embedded_hal::digital::PinState::High => Value::Active,
120-
embedded_hal::digital::PinState::Low => Value::Inactive,
137+
/// Converts a pin state to a value, depending on
138+
/// whether the pin is configured as active-low.
139+
fn state_to_value(&self, state: PinState) -> Value {
140+
if self.line_config.active_low {
141+
match state {
142+
PinState::High => Value::Inactive,
143+
PinState::Low => Value::Active,
144+
}
145+
} else {
146+
match state {
147+
PinState::High => Value::Active,
148+
PinState::Low => Value::Inactive,
149+
}
121150
}
122151
}
123152
}
@@ -146,60 +175,65 @@ impl std::error::Error for CdevPinError {
146175
}
147176
}
148177

149-
impl embedded_hal::digital::Error for CdevPinError {
178+
impl Error for CdevPinError {
150179
fn kind(&self) -> embedded_hal::digital::ErrorKind {
151180
use embedded_hal::digital::ErrorKind;
152181
ErrorKind::Other
153182
}
154183
}
155184

156-
impl embedded_hal::digital::ErrorType for CdevPin {
185+
impl<MODE> ErrorType for CdevPin<MODE> {
157186
type Error = CdevPinError;
158187
}
159188

160-
impl embedded_hal::digital::OutputPin for CdevPin {
189+
impl InputPin for CdevPin<Input> {
190+
fn is_low(&mut self) -> Result<bool, Self::Error> {
191+
let low_value = self.state_to_value(PinState::Low);
192+
Ok(self.request().value(self.line)? == low_value)
193+
}
194+
195+
fn is_high(&mut self) -> Result<bool, Self::Error> {
196+
let high_value = self.state_to_value(PinState::High);
197+
Ok(self.request().value(self.line)? == high_value)
198+
}
199+
}
200+
201+
impl OutputPin for CdevPin<Output> {
161202
fn set_low(&mut self) -> Result<(), Self::Error> {
162-
let line = self.line;
163-
let is_active_low = self.config().line_config(line).unwrap().active_low;
164-
self.request()
165-
.set_value(
166-
line,
167-
state_to_value(embedded_hal::digital::PinState::Low, is_active_low),
168-
)
169-
.map(|_| ())
170-
.map_err(CdevPinError::from)
203+
let new_value = self.state_to_value(PinState::Low);
204+
205+
self.request().set_value(self.line, new_value)?;
206+
self.line_config.value = Some(new_value);
207+
208+
Ok(())
171209
}
172210

173211
fn set_high(&mut self) -> Result<(), Self::Error> {
174-
let line = self.line;
175-
let is_active_low = self.config().line_config(line).unwrap().active_low;
176-
self.request()
177-
.set_value(
178-
line,
179-
state_to_value(embedded_hal::digital::PinState::High, is_active_low),
180-
)
181-
.map(|_| ())
182-
.map_err(CdevPinError::from)
212+
let new_value = self.state_to_value(PinState::High);
213+
214+
self.request().set_value(self.line, new_value)?;
215+
self.line_config.value = Some(new_value);
216+
217+
Ok(())
183218
}
184219
}
185220

186-
impl InputPin for CdevPin {
187-
fn is_high(&mut self) -> Result<bool, Self::Error> {
188-
let line = self.line;
189-
let is_active_low = self.config().line_config(line).unwrap().active_low;
190-
self.request()
191-
.value(line)
192-
.map(|val| val == state_to_value(embedded_hal::digital::PinState::High, is_active_low))
193-
.map_err(CdevPinError::from)
221+
impl StatefulOutputPin for CdevPin<Output> {
222+
#[inline]
223+
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
224+
let low_value = self.state_to_value(PinState::Low);
225+
Ok(self.line_config.value == Some(low_value))
194226
}
195227

196-
fn is_low(&mut self) -> Result<bool, Self::Error> {
197-
self.is_high().map(|val| !val)
228+
#[inline]
229+
fn is_set_high(&mut self) -> Result<bool, Self::Error> {
230+
let high_value = self.state_to_value(PinState::High);
231+
Ok(self.line_config.value == Some(high_value))
198232
}
199233
}
200234

201235
#[cfg(feature = "async-tokio")]
202-
impl embedded_hal_async::digital::Wait for CdevPin {
236+
impl embedded_hal_async::digital::Wait for CdevPin<Input> {
203237
async fn wait_for_high(&mut self) -> Result<(), Self::Error> {
204238
if self.is_high()? {
205239
return Ok(());
@@ -217,15 +251,14 @@ impl embedded_hal_async::digital::Wait for CdevPin {
217251
}
218252

219253
async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> {
220-
let config = self.config();
221-
let line_config = config.line_config(self.line).unwrap();
222254
if !matches!(
223-
line_config.edge_detection,
255+
self.line_config.edge_detection,
224256
Some(EdgeDetection::RisingEdge | EdgeDetection::BothEdges)
225257
) {
226-
let mut new_config = config;
258+
let mut new_config = self.req.as_ref().config();
227259
new_config.with_edge_detection(EdgeDetection::RisingEdge);
228-
self.request().reconfigure(&new_config)?;
260+
self.req.as_ref().reconfigure(&new_config)?;
261+
self.line_config.edge_detection = Some(EdgeDetection::RisingEdge);
229262
}
230263

231264
loop {
@@ -237,15 +270,14 @@ impl embedded_hal_async::digital::Wait for CdevPin {
237270
}
238271

239272
async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> {
240-
let config = self.config();
241-
let line_config = config.line_config(self.line).unwrap();
242273
if !matches!(
243-
line_config.edge_detection,
274+
self.line_config.edge_detection,
244275
Some(EdgeDetection::FallingEdge | EdgeDetection::BothEdges)
245276
) {
246-
let mut new_config = config;
277+
let mut new_config = self.req.as_ref().config();
247278
new_config.with_edge_detection(EdgeDetection::FallingEdge);
248-
self.request().reconfigure(&new_config)?;
279+
self.req.as_ref().reconfigure(&new_config)?;
280+
self.line_config.edge_detection = Some(EdgeDetection::FallingEdge);
249281
}
250282

251283
loop {
@@ -257,12 +289,14 @@ impl embedded_hal_async::digital::Wait for CdevPin {
257289
}
258290

259291
async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> {
260-
let config = self.config();
261-
let line_config = config.line_config(self.line).unwrap();
262-
if line_config.edge_detection != Some(EdgeDetection::BothEdges) {
263-
let mut new_config = config;
292+
if !matches!(
293+
self.line_config.edge_detection,
294+
Some(EdgeDetection::BothEdges)
295+
) {
296+
let mut new_config = self.req.as_ref().config();
264297
new_config.with_edge_detection(EdgeDetection::BothEdges);
265-
self.request().reconfigure(&new_config)?;
298+
self.req.as_ref().reconfigure(&new_config)?;
299+
self.line_config.edge_detection = Some(EdgeDetection::BothEdges);
266300
}
267301

268302
self.req.read_edge_event().await?;

0 commit comments

Comments
 (0)