diff --git a/Cargo.toml b/Cargo.toml index 5a7b910..c2b94db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,11 +9,23 @@ readme = "README.md" categories = ["embedded", "hardware-support", "os", "os::unix-apis"] keywords = ["linux", "gpio", "gpiochip", "embedded"] license = "MIT OR Apache-2.0" +edition = "2018" + +[features] +default = [] +async-tokio = ["tokio", "futures", "mio"] + +[[example]] +name = "async_tokio" +required-features = ["async-tokio"] [dependencies] bitflags = "1.0" libc = "0.2" nix = "0.14" +tokio = { version = "0.2", features = ["io-driver", "rt-threaded", "macros"], optional = true } +futures = { version = "0.3", optional = true } +mio = { version = "0.6", optional = true } [dev-dependencies] quicli = "0.2" diff --git a/examples/async_tokio.rs b/examples/async_tokio.rs new file mode 100644 index 0000000..6e1fb62 --- /dev/null +++ b/examples/async_tokio.rs @@ -0,0 +1,44 @@ +// Copyright (c) 2018 The rust-gpio-cdev Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use futures::stream::StreamExt; +use gpio_cdev::*; +use quicli::prelude::*; + +#[derive(Debug, StructOpt)] +struct Cli { + /// The gpiochip device (e.g. /dev/gpiochip0) + chip: String, + /// The offset of the GPIO line for the provided chip + line: u32, +} + +async fn do_main(args: Cli) -> std::result::Result<(), errors::Error> { + let mut chip = Chip::new(args.chip)?; + let line = chip.get_line(args.line)?; + let mut events = AsyncLineEventHandle::new(line.events( + LineRequestFlags::INPUT, + EventRequestFlags::BOTH_EDGES, + "gpioevents", + )?)?; + + loop { + match events.next().await { + Some(event) => println!("{:?}", event?), + None => break, + }; + } + + Ok(()) +} + +#[tokio::main] +async fn main() { + let args = Cli::from_args(); + do_main(args).await.unwrap(); +} diff --git a/src/async_tokio.rs b/src/async_tokio.rs new file mode 100644 index 0000000..c3e26ad --- /dev/null +++ b/src/async_tokio.rs @@ -0,0 +1,125 @@ +// Copyright (c) 2018 The rust-gpio-cdev Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Wrapper for asynchronous programming using Tokio. + +use futures::ready; +use futures::stream::Stream; +use futures::task::{Context, Poll}; +use mio::event::Evented; +use mio::unix::EventedFd; +use mio::{PollOpt, Ready, Token}; +use tokio::io::PollEvented; + +use std::io; +use std::os::unix::io::AsRawFd; +use std::pin::Pin; + +use super::Result; +use super::{LineEvent, LineEventHandle}; + +struct PollWrapper { + handle: LineEventHandle, +} + +impl Evented for PollWrapper { + fn register( + &self, + poll: &mio::Poll, + token: Token, + interest: Ready, + opts: PollOpt, + ) -> io::Result<()> { + EventedFd(&self.handle.file.as_raw_fd()).register(poll, token, interest, opts) + } + + fn reregister( + &self, + poll: &mio::Poll, + token: Token, + interest: Ready, + opts: PollOpt, + ) -> io::Result<()> { + EventedFd(&self.handle.file.as_raw_fd()).reregister(poll, token, interest, opts) + } + + fn deregister(&self, poll: &mio::Poll) -> io::Result<()> { + EventedFd(&self.handle.file.as_raw_fd()).deregister(poll) + } +} + +/// Wrapper around a `LineEventHandle` which implements a `futures::stream::Stream` for interrupts. +/// +/// # Example +/// +/// The following example waits for state changes on an input line. +/// +/// ```no_run +/// # type Result = std::result::Result; +/// use futures::stream::StreamExt; +/// use gpio_cdev::{AsyncLineEventHandle, Chip, EventRequestFlags, LineRequestFlags}; +/// +/// async fn print_events(line: u32) -> Result<()> { +/// let mut chip = Chip::new("/dev/gpiochip0")?; +/// let line = chip.get_line(line)?; +/// let mut events = AsyncLineEventHandle::new(line.events( +/// LineRequestFlags::INPUT, +/// EventRequestFlags::BOTH_EDGES, +/// "gpioevents", +/// )?)?; +/// +/// loop { +/// match events.next().await { +/// Some(event) => println!("{:?}", event?), +/// None => break, +/// }; +/// } +/// +/// Ok(()) +/// } +/// +/// # #[tokio::main] +/// # async fn main() { +/// # print_events(42).await.unwrap(); +/// # } +/// ``` +pub struct AsyncLineEventHandle { + evented: PollEvented, +} + +impl AsyncLineEventHandle { + /// Wraps the specified `LineEventHandle`. + /// + /// # Arguments + /// + /// * `handle` - handle to be wrapped. + pub fn new(handle: LineEventHandle) -> Result { + Ok(AsyncLineEventHandle { + evented: PollEvented::new(PollWrapper { handle })?, + }) + } +} + +impl Stream for AsyncLineEventHandle { + type Item = Result; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let ready = Ready::readable(); + if let Err(e) = ready!(self.evented.poll_read_ready(cx, ready)) { + return Poll::Ready(Some(Err(e.into()))); + } + + Poll::Ready(self.evented.get_mut().handle.next()) + } +} + +impl AsRef for AsyncLineEventHandle { + fn as_ref(&self) -> &LineEventHandle { + &self.evented.get_ref().handle + } +} diff --git a/src/lib.rs b/src/lib.rs index a42bf0d..d91f53f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,6 +89,12 @@ extern crate bitflags; extern crate libc; #[macro_use] extern crate nix; +#[cfg(feature = "async-tokio")] +extern crate futures; +#[cfg(feature = "async-tokio")] +extern crate mio; +#[cfg(feature = "async-tokio")] +extern crate tokio; use std::cmp::min; use std::ffi::CStr; @@ -101,9 +107,13 @@ use std::ptr; use std::slice; use std::sync::Arc; +#[cfg(feature = "async-tokio")] +mod async_tokio; pub mod errors; mod ffi; +#[cfg(feature = "async-tokio")] +pub use crate::async_tokio::AsyncLineEventHandle; use errors::*; unsafe fn rstr_lcpy(dst: *mut libc::c_char, src: &str, length: usize) { @@ -550,6 +560,17 @@ impl Line { file: unsafe { File::from_raw_fd(request.fd) }, }) } + + #[cfg(feature = "async-tokio")] + pub fn async_events( + &self, + handle_flags: LineRequestFlags, + event_flags: EventRequestFlags, + consumer: &str, + ) -> Result { + let events = self.events(handle_flags, event_flags, consumer)?; + Ok(AsyncLineEventHandle::new(events)?) + } } impl LineInfo {