async_std_gpiod/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::{
4    fmt,
5    marker::PhantomData,
6    ops::Deref,
7    os::unix::{
8        fs::{FileTypeExt, MetadataExt},
9        io::{AsRawFd, FromRawFd, RawFd},
10    },
11    pin::Pin,
12    sync::Arc,
13    task::{Context, Poll},
14};
15
16use gpiod_core::{invalid_input, major, minor, set_nonblock, 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_io::Async;
25use async_std::{
26    fs,
27    fs::OpenOptions,
28    io::{Read, ReadExt},
29    os::unix::fs::OpenOptionsExt,
30    path::{Path, PathBuf},
31    stream::StreamExt,
32    task::spawn_blocking as asyncify,
33};
34
35#[doc(hidden)]
36pub struct File {
37    // use file to call close when drop
38    inner: Async<std::fs::File>,
39}
40
41impl File {
42    pub fn from_fd(fd: RawFd) -> Result<Self> {
43        let file = unsafe { std::fs::File::from_raw_fd(fd) };
44        Ok(Self {
45            inner: Async::new(file)?,
46        })
47    }
48
49    pub fn from_file(file: fs::File) -> Result<Self> {
50        let fd = file.as_raw_fd();
51        core::mem::forget(file);
52        Self::from_fd(fd)
53    }
54}
55
56impl AsRawFd for File {
57    fn as_raw_fd(&self) -> RawFd {
58        self.inner.as_raw_fd()
59    }
60}
61
62impl Read for File {
63    fn poll_read(
64        self: Pin<&mut Self>,
65        cx: &mut Context<'_>,
66        buf: &mut [u8],
67    ) -> Poll<Result<usize>> {
68        use std::io::Read;
69
70        match self.inner.poll_readable(cx) {
71            Poll::Ready(x) => x,
72            Poll::Pending => return Poll::Pending,
73        }?;
74
75        Poll::Ready(self.inner.get_ref().read(buf))
76    }
77}
78
79/// The interface for getting the values of GPIO lines configured for input
80///
81/// Use [Chip::request_lines] with [Options::input] or [Options::output] to configure specific
82/// GPIO lines for input or output.
83pub struct Lines<Direction> {
84    dir: PhantomData<Direction>,
85    info: Arc<Internal<ValuesInfo>>,
86    // wrap file to call close on drop
87    file: File,
88}
89
90impl<Direction> Deref for Lines<Direction> {
91    type Target = ValuesInfo;
92
93    fn deref(&self) -> &Self::Target {
94        &self.info
95    }
96}
97
98impl<Direction: DirectionType> Lines<Direction> {
99    /// Get the value of GPIO lines
100    ///
101    /// The values can only be read if the lines have previously been requested as inputs
102    /// or outputs using the [Chip::request_lines] method with [Options::input] or with
103    /// [Options::output].
104    pub async fn get_values<T: AsValuesMut + Send + 'static>(&self, mut values: T) -> Result<T> {
105        let fd = self.file.as_raw_fd();
106        let info = self.info.clone();
107        asyncify(move || info.get_values(fd, &mut values).map(|_| values)).await
108    }
109}
110
111impl Lines<Input> {
112    /// Read GPIO events synchronously
113    ///
114    /// The values can only be read if the lines have previously been requested as inputs
115    /// using the [Chip::request_lines] method with [Options::input].
116    pub async fn read_event(&mut self) -> Result<Event> {
117        #[cfg(not(feature = "v2"))]
118        {
119            todo!();
120        }
121
122        #[cfg(feature = "v2")]
123        {
124            let mut event = gpiod_core::RawEvent::default();
125
126            gpiod_core::check_size(self.file.read(event.as_mut()).await?, &event)?;
127
128            event.as_event(self.info.index())
129        }
130    }
131}
132
133impl Lines<Output> {
134    /// Set the value of GPIO lines
135    ///
136    /// The value can only be set if the lines have previously been requested as outputs
137    /// using the [Chip::request_lines] with [Options::output].
138    pub async fn set_values<T: AsValues + Send + 'static>(&self, values: T) -> Result<()> {
139        let fd = self.file.as_raw_fd();
140        let info = self.info.clone();
141        asyncify(move || info.set_values(fd, values)).await
142    }
143}
144
145/// A Linux chardev GPIO chip interface
146///
147/// It can be used to get information about the chip and lines and
148/// to request GPIO lines that can be used as inputs or outputs.
149pub struct Chip {
150    info: Arc<Internal<ChipInfo>>,
151    // wrap file to call close on drop
152    file: File,
153}
154
155impl Deref for Chip {
156    type Target = ChipInfo;
157
158    fn deref(&self) -> &Self::Target {
159        &self.info
160    }
161}
162
163impl fmt::Display for Chip {
164    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165        self.info.fmt(f)
166    }
167}
168
169const O_NONBLOCK: i32 = 2048;
170
171impl Chip {
172    /// Create a new GPIO chip interface using path
173    pub async fn new(path: impl AsRef<Path>) -> Result<Chip> {
174        let path = path.as_ref();
175
176        #[allow(unused_assignments)]
177        let mut full_path = None;
178
179        let path = if path.starts_with("/dev") {
180            path
181        } else {
182            full_path = Path::new("/dev").join(path).into();
183            full_path.as_ref().unwrap()
184        };
185
186        let file = File::from_file(
187            OpenOptions::new()
188                .read(true)
189                .write(true)
190                .custom_flags(O_NONBLOCK)
191                .open(path)
192                .await?,
193        )?;
194
195        Chip::check_device(path).await?;
196
197        let fd = file.as_raw_fd();
198        let info = Arc::new(asyncify(move || Internal::<ChipInfo>::from_fd(fd)).await?);
199
200        Ok(Chip { info, file })
201    }
202
203    /// List all found chips
204    pub async fn list_devices() -> Result<Vec<PathBuf>> {
205        let mut devices = Vec::new();
206        let mut dir = fs::read_dir("/dev").await?;
207
208        while let Some(ent) = dir.next().await {
209            let path = ent?.path();
210            if Self::check_device(&path).await.is_ok() {
211                devices.push(path);
212            }
213        }
214
215        Ok(devices)
216    }
217
218    async fn check_device(path: &Path) -> Result<()> {
219        let metadata = fs::symlink_metadata(&path).await?;
220
221        /* Is it a character device? */
222        if !metadata.file_type().is_char_device() {
223            return Err(invalid_input("File is not character device"));
224        }
225
226        let rdev = metadata.rdev();
227
228        /* Is the device associated with the GPIO subsystem? */
229        if fs::canonicalize(format!(
230            "/sys/dev/char/{}:{}/subsystem",
231            major(rdev),
232            minor(rdev)
233        ))
234        .await?
235            != Path::new("/sys/bus/gpio")
236        {
237            return Err(invalid_input("Character device is not a GPIO"));
238        }
239
240        Ok(())
241    }
242
243    /// Request the info of a specific GPIO line.
244    pub async fn line_info(&self, line: LineId) -> Result<LineInfo> {
245        let fd = self.file.as_raw_fd();
246        let info = self.info.clone();
247        asyncify(move || info.line_info(fd, line)).await
248    }
249
250    /// Request the GPIO chip to configure the lines passed as argument as inputs or outputs
251    ///
252    /// Calling this operation is a precondition to being able to set the state of the GPIO lines.
253    /// 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.
254    pub async fn request_lines<Direction: DirectionType>(
255        &self,
256        options: Options<Direction, impl AsRef<[LineId]>, impl AsRef<str>>,
257    ) -> Result<Lines<Direction>> {
258        let fd = self.file.as_raw_fd();
259        let options = options.to_owned();
260        let info = self.info.clone();
261
262        let (info, fd) = asyncify(move || -> Result<_> {
263            let (info, fd) = info.request_lines(fd, options)?;
264            set_nonblock(fd)?;
265            Ok((info, fd))
266        })
267        .await?;
268
269        let file = File::from_fd(fd)?;
270        let info = Arc::new(info);
271
272        Ok(Lines {
273            dir: PhantomData,
274            info,
275            file,
276        })
277    }
278}