Skip to content

Commit bd9f5b5

Browse files
authored
Merge pull request #26 from emosenkis/futures
Add support for reading asynchronously with mio::Evented and tokio_core::Stream
2 parents c85cc3b + 1be4e73 commit bd9f5b5

File tree

6 files changed

+243
-8
lines changed

6 files changed

+243
-8
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*.so
44
*.rlib
55
*.dll
6+
*.bk
67

78
# Executables
89
*.exe

.travis.yml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
language: rust
22
sudo: false
33
rust:
4-
- 1.5.0
4+
- 1.10.0
55
- stable
66
- beta
77
- nightly
@@ -25,9 +25,13 @@ env:
2525

2626
script:
2727
- |
28-
travis-cargo build &&
29-
travis-cargo test &&
30-
travis-cargo --only stable doc
28+
travis-cargo build -- --features=tokio &&
29+
travis-cargo test -- --features=tokio &&
30+
travis-cargo build -- --features=mio-evented &&
31+
travis-cargo test -- --features=mio-evented &&
32+
travis-cargo build -- &&
33+
travis-cargo test -- &&
34+
travis-cargo --only stable doc -- --features=tokio
3135
3236
after_success:
3337
- travis-cargo --only stable doc-upload

Cargo.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "sysfs_gpio"
3-
version = "0.4.4"
3+
version = "0.5.0"
44
authors = ["Paul Osborne <[email protected]>"]
55
license = "MIT/Apache-2.0"
66
repository = "https://github.com/rust-embedded/rust-sysfs-gpio"
@@ -15,6 +15,13 @@ interrupts) GPIOs from userspace.
1515
See https://www.kernel.org/doc/Documentation/gpio/sysfs.txt
1616
"""
1717

18+
[features]
19+
mio-evented = ["mio"]
20+
tokio = ["futures", "tokio-core", "mio-evented"]
21+
1822
[dependencies]
23+
futures = { version = "0.1", optional = true }
1924
nix = "0.6.0"
2025
regex = "0.1.0"
26+
mio = { version = "0.6", optional = true }
27+
tokio-core = { version = "0.1", optional = true }

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ More Examples:
6666
- [Blink an LED](examples/blinky.rs)
6767
- [Poll a GPIO Input](examples/poll.rs)
6868
- [Receive interrupt on GPIO Change](examples/interrupt.rs)
69+
- [Poll several pins asynchronously with Tokio](examples/tokio.rs)
6970
- [gpio-utils Project (uses most features)](https://github.com/rust-embedded/gpio-utils)
7071

7172
Features
@@ -81,6 +82,8 @@ The following features are planned for the library:
8182
- [ ] Support for configuring whether a pin is active low/high
8283
- [x] Support for configuring interrupts on GPIO
8384
- [x] Support for polling on GPIO with configured interrupt
85+
- [x] Support for asynchronous polling using `mio` or `tokio-core` (requires
86+
enabling the `mio-evented` or `tokio` crate features, respectively)
8487

8588
Cross Compiling
8689
---------------

examples/tokio.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#[cfg(feature = "tokio")]
2+
extern crate futures;
3+
#[cfg(feature = "tokio")]
4+
extern crate sysfs_gpio;
5+
#[cfg(feature = "tokio")]
6+
extern crate tokio_core;
7+
8+
#[cfg(feature = "tokio")]
9+
use futures::{Future, Stream};
10+
#[cfg(feature = "tokio")]
11+
use sysfs_gpio::{Direction, Edge, Pin};
12+
#[cfg(feature = "tokio")]
13+
use std::env;
14+
#[cfg(feature = "tokio")]
15+
use tokio_core::reactor::Core;
16+
17+
#[cfg(feature = "tokio")]
18+
fn stream(pin_nums: Vec<u64>) -> sysfs_gpio::Result<()> {
19+
// NOTE: this currently runs forever and as such if
20+
// the app is stopped (Ctrl-C), no cleanup will happen
21+
// and the GPIO will be left exported. Not much
22+
// can be done about this as Rust signal handling isn't
23+
// really present at the moment. Revisit later.
24+
let pins: Vec<_> = pin_nums.iter().map(|&p| (p, Pin::new(p))).collect();
25+
let mut l = try!(Core::new());
26+
let handle = l.handle();
27+
for &(i, ref pin) in pins.iter() {
28+
try!(pin.export());
29+
try!(pin.set_direction(Direction::In));
30+
try!(pin.set_edge(Edge::BothEdges));
31+
handle.spawn(try!(pin.get_value_stream(&handle))
32+
.for_each(move |val| {
33+
println!("Pin {} changed value to {}", i, val);
34+
Ok(())
35+
})
36+
.map_err(|_| ()));
37+
}
38+
// Wait forever for events
39+
loop {
40+
l.turn(None)
41+
}
42+
}
43+
44+
#[cfg(feature = "tokio")]
45+
fn main() {
46+
let pins: Vec<u64> = env::args()
47+
.skip(1)
48+
.map(|a| a.parse().expect("Pins must be specified as integers"))
49+
.collect();
50+
if pins.is_empty() {
51+
println!("Usage: ./tokio <pin> [pin ...]");
52+
} else {
53+
stream(pins).unwrap();
54+
}
55+
}
56+
57+
#[cfg(not(feature = "tokio"))]
58+
fn main() {
59+
println!("This example requires the `tokio` feature to be enabled.");
60+
}

src/lib.rs

Lines changed: 163 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,22 @@
4444
//! }
4545
//! ```
4646
47+
#[cfg(feature = "tokio")]
48+
extern crate futures;
49+
#[cfg(feature = "mio-evented")]
50+
extern crate mio;
4751
extern crate nix;
4852
extern crate regex;
53+
#[cfg(feature = "tokio")]
54+
extern crate tokio_core;
55+
56+
#[cfg(feature = "tokio")]
57+
use futures::{Async, Poll, Stream};
58+
59+
#[cfg(feature = "mio-evented")]
60+
use mio::Evented;
61+
#[cfg(feature = "mio-evented")]
62+
use mio::unix::EventedFd;
4963

5064
use nix::sys::epoll::*;
5165
use nix::unistd::close;
@@ -57,6 +71,9 @@ use std::fs;
5771
use std::fs::File;
5872
use std::path::Path;
5973

74+
#[cfg(feature = "tokio")]
75+
use tokio_core::reactor::{Handle, PollEvented};
76+
6077
mod error;
6178
pub use error::Error;
6279

@@ -395,6 +412,44 @@ impl Pin {
395412
pub fn get_poller(&self) -> Result<PinPoller> {
396413
PinPoller::new(self.pin_num)
397414
}
415+
416+
/// Get an AsyncPinPoller object for this pin
417+
///
418+
/// The async pin poller object can be used with the `mio` crate. You should probably call
419+
/// `set_edge()` before using this.
420+
///
421+
/// This method is only available when the `mio-evented` crate feature is enabled.
422+
#[cfg(feature = "mio-evented")]
423+
pub fn get_async_poller(&self) -> Result<AsyncPinPoller> {
424+
AsyncPinPoller::new(self.pin_num)
425+
}
426+
427+
/// Get a Stream of pin interrupts for this pin
428+
///
429+
/// The PinStream object can be used with the `tokio-core` crate. You should probably call
430+
/// `set_edge()` before using this.
431+
///
432+
/// This method is only available when the `tokio` crate feature is enabled.
433+
#[cfg(feature = "tokio")]
434+
pub fn get_stream(&self, handle: &Handle) -> Result<PinStream> {
435+
PinStream::init(self.clone(), handle)
436+
}
437+
438+
/// Get a Stream of pin values for this pin
439+
///
440+
/// The PinStream object can be used with the `tokio-core` crate. You should probably call
441+
/// `set_edge(Edge::BothEdges)` before using this.
442+
///
443+
/// Note that the values produced are the value of the pin as soon as we get to handling the
444+
/// interrupt in userspace. Each time this stream produces a value, a change has occurred, but
445+
/// it could end up producing the same value multiple times if the value has changed back
446+
/// between when the interrupt occurred and when the value was read.
447+
///
448+
/// This method is only available when the `tokio` crate feature is enabled.
449+
#[cfg(feature = "tokio")]
450+
pub fn get_value_stream(&self, handle: &Handle) -> Result<PinValueStream> {
451+
Ok(PinValueStream(try!(PinStream::init(self.clone(), handle))))
452+
}
398453
}
399454

400455
#[derive(Debug)]
@@ -445,9 +500,9 @@ impl PinPoller {
445500
/// of interrupts which may result in this call returning
446501
/// may be configured by calling `set_edge()` prior to
447502
/// making this call. This call makes use of epoll under the
448-
/// covers. If it is desirable to poll on multiple GPIOs or
449-
/// other event source, you will need to implement that logic
450-
/// yourself.
503+
/// covers. To poll on multiple GPIOs or other event sources,
504+
/// poll asynchronously using the integration with either `mio`
505+
/// or `tokio_core`.
451506
///
452507
/// This function will return Some(value) of the pin if a change is
453508
/// detected or None if a timeout occurs. Note that the value provided
@@ -479,3 +534,108 @@ impl Drop for PinPoller {
479534
close(self.epoll_fd).unwrap(); // panic! if close files
480535
}
481536
}
537+
538+
#[cfg(feature = "mio-evented")]
539+
#[derive(Debug)]
540+
pub struct AsyncPinPoller {
541+
devfile: File,
542+
}
543+
544+
#[cfg(feature = "mio-evented")]
545+
impl AsyncPinPoller {
546+
fn new(pin_num: u64) -> Result<Self> {
547+
let devfile = try!(File::open(&format!("/sys/class/gpio/gpio{}/value", pin_num)));
548+
Ok(AsyncPinPoller { devfile: devfile })
549+
}
550+
}
551+
552+
#[cfg(feature = "mio-evented")]
553+
impl Evented for AsyncPinPoller {
554+
fn register(&self,
555+
poll: &mio::Poll,
556+
token: mio::Token,
557+
interest: mio::Ready,
558+
opts: mio::PollOpt)
559+
-> io::Result<()> {
560+
EventedFd(&self.devfile.as_raw_fd()).register(poll, token, interest, opts)
561+
}
562+
563+
fn reregister(&self,
564+
poll: &mio::Poll,
565+
token: mio::Token,
566+
interest: mio::Ready,
567+
opts: mio::PollOpt)
568+
-> io::Result<()> {
569+
EventedFd(&self.devfile.as_raw_fd()).reregister(poll, token, interest, opts)
570+
}
571+
572+
fn deregister(&self, poll: &mio::Poll) -> io::Result<()> {
573+
EventedFd(&self.devfile.as_raw_fd()).deregister(poll)
574+
}
575+
}
576+
577+
#[cfg(feature = "tokio")]
578+
pub struct PinStream {
579+
evented: PollEvented<AsyncPinPoller>,
580+
skipped_first_event: bool,
581+
}
582+
583+
#[cfg(feature = "tokio")]
584+
impl PinStream {
585+
pub fn init(pin: Pin, handle: &Handle) -> Result<Self> {
586+
Ok(PinStream {
587+
evented: try!(PollEvented::new(try!(pin.get_async_poller()), &handle)),
588+
skipped_first_event: false,
589+
})
590+
}
591+
}
592+
593+
#[cfg(feature = "tokio")]
594+
impl Stream for PinStream {
595+
type Item = ();
596+
type Error = Error;
597+
598+
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
599+
Ok(match self.evented.poll_read() {
600+
Async::Ready(()) => {
601+
self.evented.need_read();
602+
if self.skipped_first_event {
603+
Async::Ready(Some(()))
604+
} else {
605+
self.skipped_first_event = true;
606+
Async::NotReady
607+
}
608+
}
609+
Async::NotReady => Async::NotReady,
610+
})
611+
}
612+
}
613+
614+
#[cfg(feature = "tokio")]
615+
pub struct PinValueStream(PinStream);
616+
617+
#[cfg(feature = "tokio")]
618+
impl PinValueStream {
619+
#[inline]
620+
fn get_value(&mut self) -> Result<u8> {
621+
get_value_from_file(&mut self.0.evented.get_mut().devfile)
622+
}
623+
}
624+
625+
#[cfg(feature = "tokio")]
626+
impl Stream for PinValueStream {
627+
type Item = u8;
628+
type Error = Error;
629+
630+
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
631+
match self.0.poll() {
632+
Ok(Async::Ready(Some(()))) => {
633+
let value = try!(self.get_value());
634+
Ok(Async::Ready(Some(value)))
635+
}
636+
Ok(Async::Ready(None)) => Ok(Async::Ready(None)),
637+
Ok(Async::NotReady) => Ok(Async::NotReady),
638+
Err(e) => Err(e),
639+
}
640+
}
641+
}

0 commit comments

Comments
 (0)