async_gpiod/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::{
4    fs::{File, OpenOptions},
5    io::Read,
6    marker::PhantomData,
7    ops::Deref,
8    os::unix::{
9        fs::{FileTypeExt, MetadataExt},
10        io::{AsRawFd, FromRawFd},
11    },
12    path::{Path, PathBuf},
13    sync::Arc,
14};
15
16use gpiod_core::{invalid_input, major, minor, set_nonblock, AsDevicePath, Internal, Result};
17
18pub use gpiod_core::{
19    Active, AsValues, AsValuesMut, Bias, BitId, ChipInfo, Direction, DirectionType, Drive, Edge,
20    EdgeDetect, Event, Input, LineId, LineInfo, Masked, Options, Output, Values, ValuesInfo,
21    MAX_BITS, MAX_VALUES,
22};
23
24use async_fs as fs;
25use async_io::Async;
26use blocking::unblock as asyncify;
27use futures_lite::stream::StreamExt;
28
29/// The interface for getting the values of GPIO lines configured for input
30///
31/// Use [Chip::request_lines] with [Options::input] or [Options::output] to configure specific
32/// GPIO lines for input or output.
33pub struct Lines<Direction> {
34    dir: PhantomData<Direction>,
35    info: Arc<Internal<ValuesInfo>>,
36    // wrap file to call close on drop
37    file: Async<File>,
38}
39
40impl<Direction> Deref for Lines<Direction> {
41    type Target = ValuesInfo;
42
43    fn deref(&self) -> &Self::Target {
44        &self.info
45    }
46}
47
48impl<Direction: DirectionType> Lines<Direction> {
49    /// Get the value of GPIO lines
50    ///
51    /// The values can only be read if the lines have previously been requested as inputs
52    /// or outputs using the [Chip::request_lines] method with [Options::input] or with
53    /// [Options::output].
54    pub async fn get_values<T: AsValuesMut + Send + 'static>(&self, mut values: T) -> Result<T> {
55        let fd = self.file.as_raw_fd();
56        let info = self.info.clone();
57        asyncify(move || info.get_values(fd, &mut values).map(|_| values)).await
58    }
59}
60
61impl Lines<Input> {
62    /// Read GPIO events synchronously
63    ///
64    /// The values can only be read if the lines have previously been requested as inputs
65    /// using the [Chip::request_lines] method with [Options::input].
66    pub async fn read_event(&mut self) -> Result<Event> {
67        #[cfg(not(feature = "v2"))]
68        {
69            todo!();
70        }
71
72        #[cfg(feature = "v2")]
73        {
74            self.file.readable().await?;
75
76            let mut event = gpiod_core::RawEvent::default();
77            let len = self.file.get_ref().read(event.as_mut())?;
78
79            gpiod_core::check_size(len, &event)?;
80
81            event.as_event(self.info.index())
82        }
83    }
84}
85
86impl Lines<Output> {
87    /// Set the value of GPIO lines
88    ///
89    /// The value can only be set if the lines have previously been requested as outputs
90    /// using the [Chip::request_lines] with [Options::output].
91    pub async fn set_values<T: AsValues + Send + 'static>(&self, values: T) -> Result<()> {
92        let fd = self.file.as_raw_fd();
93        let info = self.info.clone();
94        asyncify(move || info.set_values(fd, values)).await
95    }
96}
97
98/// A Linux chardev GPIO chip interface
99///
100/// It can be used to get information about the chip and lines and
101/// to request GPIO lines that can be used as inputs or outputs.
102pub struct Chip {
103    info: Arc<Internal<ChipInfo>>,
104    // wrap file to call close on drop
105    file: Async<File>,
106}
107
108impl Deref for Chip {
109    type Target = ChipInfo;
110
111    fn deref(&self) -> &Self::Target {
112        &self.info
113    }
114}
115
116impl core::fmt::Display for Chip {
117    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
118        self.info.fmt(f)
119    }
120}
121
122impl Chip {
123    /// Create a new GPIO chip interface using path
124    pub async fn new(device: impl AsDevicePath) -> Result<Chip> {
125        let path = device.as_device_path();
126
127        Chip::check_device(path.as_ref()).await?;
128
129        let file = Async::new(
130            asyncify(move || OpenOptions::new().read(true).write(true).open(path)).await?,
131        )?;
132
133        let fd = file.as_raw_fd();
134        let info = Arc::new(asyncify(move || Internal::<ChipInfo>::from_fd(fd)).await?);
135
136        Ok(Chip { info, file })
137    }
138
139    /// List all found chips
140    pub async fn list_devices() -> Result<Vec<PathBuf>> {
141        let mut devices = Vec::new();
142        let mut dir = fs::read_dir("/dev").await?;
143
144        while let Some(ent) = dir.next().await {
145            let path = ent?.path();
146            if Self::check_device(&path).await.is_ok() {
147                devices.push(path);
148            }
149        }
150
151        Ok(devices)
152    }
153
154    async fn check_device(path: &Path) -> Result<()> {
155        let metadata = fs::symlink_metadata(&path).await?;
156
157        /* Is it a character device? */
158        if !metadata.file_type().is_char_device() {
159            return Err(invalid_input("File is not character device"));
160        }
161
162        let rdev = metadata.rdev();
163
164        /* Is the device associated with the GPIO subsystem? */
165        if fs::canonicalize(format!(
166            "/sys/dev/char/{}:{}/subsystem",
167            major(rdev),
168            minor(rdev)
169        ))
170        .await?
171            != Path::new("/sys/bus/gpio")
172        {
173            return Err(invalid_input("Character device is not a GPIO"));
174        }
175
176        Ok(())
177    }
178
179    /// Request the info of a specific GPIO line.
180    pub async fn line_info(&self, line: LineId) -> Result<LineInfo> {
181        let fd = self.file.as_raw_fd();
182        let info = self.info.clone();
183        asyncify(move || info.line_info(fd, line)).await
184    }
185
186    /// Request the GPIO chip to configure the lines passed as argument as inputs or outputs
187    ///
188    /// Calling this operation is a precondition to being able to set the state of the GPIO lines.
189    /// All the lines passed in one request must share the configured options such as active state, edge detect, GPIO bias, output drive and consumer string.
190    pub async fn request_lines<Direction: DirectionType>(
191        &self,
192        options: Options<Direction, impl AsRef<[LineId]>, impl AsRef<str>>,
193    ) -> Result<Lines<Direction>> {
194        let fd = self.file.as_raw_fd();
195        let options = options.to_owned();
196        let info = self.info.clone();
197
198        let (info, fd) = asyncify(move || -> Result<_> {
199            let (info, fd) = info.request_lines(fd, options)?;
200            set_nonblock(fd)?;
201            Ok((info, fd))
202        })
203        .await?;
204
205        let file = Async::new(unsafe { File::from_raw_fd(fd) })?;
206        let info = Arc::new(info);
207
208        Ok(Lines {
209            dir: PhantomData,
210            info,
211            file,
212        })
213    }
214}