#[cfg(feature = "use_tokio")]
extern crate futures;
#[cfg(feature = "mio-evented")]
extern crate mio;
extern crate nix;
#[cfg(feature = "use_tokio")]
extern crate tokio;
use std::fs;
use std::fs::File;
use std::io::{self, SeekFrom};
use std::io::prelude::*;
use std::os::unix::prelude::*;
use std::path::Path;
#[cfg(feature = "use_tokio")]
use futures::{Async, Poll, Stream};
#[cfg(feature = "mio-evented")]
use mio::Evented;
#[cfg(feature = "mio-evented")]
use mio::unix::EventedFd;
#[cfg(any(target_os = "linux", target_os = "android"))]
use nix::sys::epoll::*;
use nix::unistd::close;
#[cfg(feature = "use_tokio")]
use tokio::reactor::{Handle, PollEvented};
pub use error::Error;
mod error;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Pin {
pin_num: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Direction {
In,
Out,
High,
Low,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Edge {
NoInterrupt,
RisingEdge,
FallingEdge,
BothEdges,
}
#[macro_export]
macro_rules! try_unexport {
($gpio:ident, $e:expr) => (match $e {
Ok(res) => res,
Err(e) => { try!($gpio.unexport()); return Err(e) },
});
}
pub type Result<T> = ::std::result::Result<T, error::Error>;
fn flush_input_from_file(dev_file: &mut File, max: usize) -> io::Result<usize> {
let mut s = String::with_capacity(max);
dev_file.read_to_string(&mut s)
}
fn get_value_from_file(dev_file: &mut File) -> Result<u8> {
let mut s = String::with_capacity(10);
dev_file.seek(SeekFrom::Start(0))?;
dev_file.read_to_string(&mut s)?;
match s[..1].parse::<u8>() {
Ok(n) => Ok(n),
Err(_) => Err(Error::Unexpected(format!("Unexpected value file contents: {:?}", s))),
}
}
impl Pin {
fn write_to_device_file(&self, dev_file_name: &str, value: &str) -> io::Result<()> {
let gpio_path = format!("/sys/class/gpio/gpio{}/{}", self.pin_num, dev_file_name);
let mut dev_file = File::create(&gpio_path)?;
dev_file.write_all(value.as_bytes())?;
Ok(())
}
fn read_from_device_file(&self, dev_file_name: &str) -> io::Result<String> {
let gpio_path = format!("/sys/class/gpio/gpio{}/{}", self.pin_num, dev_file_name);
let mut dev_file = File::open(&gpio_path)?;
let mut s = String::new();
dev_file.read_to_string(&mut s)?;
Ok(s)
}
pub fn new(pin_num: u64) -> Pin {
Pin { pin_num: pin_num }
}
pub fn from_path<T: AsRef<Path>>(path: T) -> Result<Pin> {
let pb = fs::canonicalize(path.as_ref())?;
if !fs::metadata(&pb)?.is_dir() {
return Err(Error::Unexpected("Provided path not a directory or symlink to \
a directory"
.to_owned()));
}
let num = Pin::extract_pin_from_path(&pb)?;
Ok(Pin::new(num))
}
fn extract_pin_from_path<P: AsRef<Path>>(path: P) -> Result<u64> {
path.as_ref()
.file_name()
.and_then(|filename| filename.to_str())
.and_then(|filename_str| filename_str.trim_left_matches("gpio").parse::<u64>().ok())
.ok_or(Error::InvalidPath(format!("{:?}", path.as_ref())))
}
pub fn get_pin_num(&self) -> u64 {
self.pin_num
}
#[inline]
pub fn with_exported<F: FnOnce() -> Result<()>>(&self, closure: F) -> Result<()> {
self.export()?;
match closure() {
Ok(()) => {
try!(self.unexport());
Ok(())
}
Err(err) => {
self.unexport()?;
Err(err)
}
}
}
pub fn is_exported(&self) -> bool {
fs::metadata(&format!("/sys/class/gpio/gpio{}", self.pin_num)).is_ok()
}
pub fn export(&self) -> Result<()> {
if fs::metadata(&format!("/sys/class/gpio/gpio{}", self.pin_num)).is_err() {
let mut export_file = File::create("/sys/class/gpio/export")?;
export_file
.write_all(format!("{}", self.pin_num).as_bytes())?;
}
Ok(())
}
pub fn unexport(&self) -> Result<()> {
if fs::metadata(&format!("/sys/class/gpio/gpio{}", self.pin_num)).is_ok() {
let mut unexport_file = File::create("/sys/class/gpio/unexport")?;
unexport_file
.write_all(format!("{}", self.pin_num).as_bytes())?;
}
Ok(())
}
pub fn get_pin(&self) -> u64 {
self.pin_num
}
pub fn get_direction(&self) -> Result<Direction> {
match self.read_from_device_file("direction") {
Ok(s) => {
match s.trim() {
"in" => Ok(Direction::In),
"out" => Ok(Direction::Out),
"high" => Ok(Direction::High),
"low" => Ok(Direction::Low),
other => Err(Error::Unexpected(format!("direction file contents {}", other))),
}
}
Err(e) => Err(::std::convert::From::from(e)),
}
}
pub fn set_direction(&self, dir: Direction) -> Result<()> {
self.write_to_device_file("direction", match dir {
Direction::In => "in",
Direction::Out => "out",
Direction::High => "high",
Direction::Low => "low",
})?;
Ok(())
}
pub fn get_value(&self) -> Result<u8> {
match self.read_from_device_file("value") {
Ok(s) => {
match s.trim() {
"1" => Ok(1),
"0" => Ok(0),
other => Err(Error::Unexpected(format!("value file contents {}", other))),
}
}
Err(e) => Err(::std::convert::From::from(e)),
}
}
pub fn set_value(&self, value: u8) -> Result<()> {
self.write_to_device_file("value", match value {
0 => "0",
_ => "1",
})?;
Ok(())
}
pub fn get_edge(&self) -> Result<Edge> {
match self.read_from_device_file("edge") {
Ok(s) => {
match s.trim() {
"none" => Ok(Edge::NoInterrupt),
"rising" => Ok(Edge::RisingEdge),
"falling" => Ok(Edge::FallingEdge),
"both" => Ok(Edge::BothEdges),
other => Err(Error::Unexpected(format!("Unexpected file contents {}", other))),
}
}
Err(e) => Err(::std::convert::From::from(e)),
}
}
pub fn set_edge(&self, edge: Edge) -> Result<()> {
self.write_to_device_file("edge", match edge {
Edge::NoInterrupt => "none",
Edge::RisingEdge => "rising",
Edge::FallingEdge => "falling",
Edge::BothEdges => "both",
})?;
Ok(())
}
pub fn get_active_low(&self) -> Result<bool> {
match self.read_from_device_file("active_low") {
Ok(s) => {
match s.trim() {
"1" => Ok(true),
"0" => Ok(false),
other => Err(Error::Unexpected(format!("active_low file contents {}", other))),
}
}
Err(e) => Err(::std::convert::From::from(e)),
}
}
pub fn set_active_low(&self, active_low: bool) -> Result<()> {
self.write_to_device_file("active_low",
match active_low {
true => "1",
false => "0",
})?;
Ok(())
}
pub fn get_poller(&self) -> Result<PinPoller> {
PinPoller::new(self.pin_num)
}
#[cfg(feature = "mio-evented")]
pub fn get_async_poller(&self) -> Result<AsyncPinPoller> {
AsyncPinPoller::new(self.pin_num)
}
#[cfg(feature = "use_tokio")]
pub fn get_stream_with_handle(&self, handle: &Handle) -> Result<PinStream> {
PinStream::init_with_handle(self.clone(), handle)
}
#[cfg(feature = "use_tokio")]
pub fn get_stream(&self) -> Result<PinStream> {
PinStream::init(self.clone())
}
#[cfg(feature = "use_tokio")]
pub fn get_value_stream_with_handle(&self, handle: &Handle) -> Result<PinValueStream> {
Ok(PinValueStream(PinStream::init_with_handle(self.clone(), handle)?))
}
#[cfg(feature = "use_tokio")]
pub fn get_value_stream(&self) -> Result<PinValueStream> {
Ok(PinValueStream(PinStream::init(self.clone())?))
}
}
#[test]
fn extract_pin_fom_path_test() {
let tok1 = Pin::extract_pin_from_path(&"/sys/class/gpio/gpio951");
assert_eq!(951, tok1.unwrap());
let tok2 = Pin::extract_pin_from_path(&"/sys/CLASS/gpio/gpio951/");
assert_eq!(951, tok2.unwrap());
let tok3 = Pin::extract_pin_from_path(&"../../devices/soc0/gpiochip3/gpio/gpio124");
assert_eq!(124, tok3.unwrap());
let err1 = Pin::extract_pin_from_path(&"/sys/CLASS/gpio/gpio");
assert_eq!(true, err1.is_err());
let err2 = Pin::extract_pin_from_path(&"/sys/class/gpio/gpioSDS");
assert_eq!(true, err2.is_err());
}
#[derive(Debug)]
pub struct PinPoller {
pin_num: u64,
epoll_fd: RawFd,
devfile: File,
}
impl PinPoller {
pub fn get_pin(&self) -> Pin {
Pin::new(self.pin_num)
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn new(pin_num: u64) -> Result<PinPoller> {
let devfile: File = File::open(&format!("/sys/class/gpio/gpio{}/value", pin_num))?;
let devfile_fd = devfile.as_raw_fd();
let epoll_fd = epoll_create()?;
let mut event = EpollEvent::new(EpollFlags::EPOLLPRI | EpollFlags::EPOLLET, 0u64);
match epoll_ctl(epoll_fd, EpollOp::EpollCtlAdd, devfile_fd, &mut event) {
Ok(_) => {
Ok(PinPoller {
pin_num: pin_num,
devfile: devfile,
epoll_fd: epoll_fd,
})
}
Err(err) => {
let _ = close(epoll_fd); Err(::std::convert::From::from(err))
}
}
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn new(pin_num: u64) -> Result<PinPoller> {
Err(Error::Unsupported("PinPoller".into()))
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn poll(&mut self, timeout_ms: isize) -> Result<Option<u8>> {
flush_input_from_file(&mut self.devfile, 255)?;
let dummy_event = EpollEvent::new(EpollFlags::EPOLLPRI | EpollFlags::EPOLLET, 0u64);
let mut events: [EpollEvent; 1] = [dummy_event];
let cnt = epoll_wait(self.epoll_fd, &mut events, timeout_ms)?;
Ok(match cnt {
0 => None, _ => Some(get_value_from_file(&mut self.devfile)?),
})
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn poll(&mut self, timeout_ms: isize) -> Result<Option<u8>> {
Err(Error::Unsupported("PinPoller".into()))
}
}
impl Drop for PinPoller {
fn drop(&mut self) {
close(self.epoll_fd).unwrap(); }
}
#[cfg(feature = "mio-evented")]
#[derive(Debug)]
pub struct AsyncPinPoller {
devfile: File,
}
#[cfg(feature = "mio-evented")]
impl AsyncPinPoller {
fn new(pin_num: u64) -> Result<Self> {
let devfile = File::open(&format!("/sys/class/gpio/gpio{}/value", pin_num))?;
Ok(AsyncPinPoller { devfile: devfile })
}
}
#[cfg(feature = "mio-evented")]
impl Evented for AsyncPinPoller {
fn register(&self,
poll: &mio::Poll,
token: mio::Token,
interest: mio::Ready,
opts: mio::PollOpt)
-> io::Result<()> {
EventedFd(&self.devfile.as_raw_fd()).register(poll, token, interest, opts)
}
fn reregister(&self,
poll: &mio::Poll,
token: mio::Token,
interest: mio::Ready,
opts: mio::PollOpt)
-> io::Result<()> {
EventedFd(&self.devfile.as_raw_fd()).reregister(poll, token, interest, opts)
}
fn deregister(&self, poll: &mio::Poll) -> io::Result<()> {
EventedFd(&self.devfile.as_raw_fd()).deregister(poll)
}
}
#[cfg(feature = "use_tokio")]
pub struct PinStream {
evented: PollEvented<AsyncPinPoller>,
skipped_first_event: bool,
}
#[cfg(feature = "use_tokio")]
impl PinStream {
pub fn init_with_handle(pin: Pin, handle: &Handle) -> Result<Self> {
Ok(PinStream {
evented: PollEvented::new(pin.get_async_poller()?, &handle)?,
skipped_first_event: false,
})
}
}
#[cfg(feature = "use_tokio")]
impl PinStream {
pub fn init(pin: Pin) -> Result<Self> {
Ok(PinStream {
evented: PollEvented::new(pin.get_async_poller()?, &Handle::default())?,
skipped_first_event: false,
})
}
}
#[cfg(feature = "use_tokio")]
impl Stream for PinStream {
type Item = ();
type Error = Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
Ok(match self.evented.poll_read() {
Async::Ready(()) => {
self.evented.need_read();
if self.skipped_first_event {
Async::Ready(Some(()))
} else {
self.skipped_first_event = true;
Async::NotReady
}
}
Async::NotReady => Async::NotReady,
})
}
}
#[cfg(feature = "use_tokio")]
pub struct PinValueStream(PinStream);
#[cfg(feature = "use_tokio")]
impl PinValueStream {
#[inline]
fn get_value(&mut self) -> Result<u8> {
get_value_from_file(&mut self.0.evented.get_mut().devfile)
}
}
#[cfg(feature = "use_tokio")]
impl Stream for PinValueStream {
type Item = u8;
type Error = Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
match self.0.poll() {
Ok(Async::Ready(Some(()))) => {
let value = try!(self.get_value());
Ok(Async::Ready(Some(value)))
}
Ok(Async::Ready(None)) => Ok(Async::Ready(None)),
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(e) => Err(e),
}
}
}