#![cfg_attr(feature = "async-tokio", allow(deprecated))]
#[cfg(feature = "async-tokio")]
extern crate futures;
#[cfg(feature = "mio-evented")]
extern crate mio;
#[cfg(not(target_os = "wasi"))]
extern crate nix;
#[cfg(feature = "async-tokio")]
extern crate tokio;
use std::io;
use std::io::prelude::*;
#[cfg(any(target_os = "linux", target_os = "android", feature = "async-tokio"))]
use std::io::SeekFrom;
#[cfg(not(target_os = "wasi"))]
use std::os::unix::prelude::*;
use std::path::Path;
use std::{fs, fs::File};
#[cfg(feature = "async-tokio")]
use futures::{ready, Stream};
#[cfg(feature = "mio-evented")]
use mio::event::Source;
#[cfg(feature = "mio-evented")]
use mio::unix::SourceFd;
#[cfg(any(target_os = "linux", target_os = "android"))]
use nix::sys::epoll::*;
#[cfg(not(target_os = "wasi"))]
use nix::unistd::close;
#[cfg(feature = "async-tokio")]
use std::task::Poll;
#[cfg(feature = "async-tokio")]
use tokio::io::unix::AsyncFd;
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) => {
$gpio.unexport()?;
return Err(e);
}
}
};
}
pub type Result<T> = ::std::result::Result<T, error::Error>;
#[cfg(any(target_os = "linux", target_os = "android"))]
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)
}
#[cfg(any(target_os = "linux", target_os = "android", feature = "async-tokio"))]
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 }
}
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_start_matches("gpio").parse::<u64>().ok())
.ok_or_else(|| 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(()) => {
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(())
}
#[cfg(not(target_os = "wasi"))]
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 = "async-tokio")]
pub fn get_stream(&self) -> Result<PinStream> {
PinStream::init(*self)
}
#[cfg(feature = "async-tokio")]
pub fn get_value_stream(&self) -> Result<PinValueStream> {
Ok(PinValueStream(PinStream::init(*self)?))
}
}
#[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!(err1.is_err());
let err2 = Pin::extract_pin_from_path(&"/sys/class/gpio/gpioSDS");
assert!(err2.is_err());
}
#[cfg(not(target_os = "wasi"))]
#[derive(Debug)]
pub struct PinPoller {
pin_num: u64,
epoll_fd: RawFd,
devfile: File,
}
#[cfg(not(target_os = "wasi"))]
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,
devfile,
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()))
}
}
#[cfg(not(target_os = "wasi"))]
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 })
}
}
#[cfg(feature = "mio-evented")]
impl Source for AsyncPinPoller {
fn register(
&mut self,
poll: &mio::Registry,
token: mio::Token,
interest: mio::Interest,
) -> io::Result<()> {
SourceFd(&self.as_raw_fd()).register(poll, token, interest)
}
fn reregister(
&mut self,
poll: &mio::Registry,
token: mio::Token,
interest: mio::Interest,
) -> io::Result<()> {
SourceFd(&self.as_raw_fd()).reregister(poll, token, interest)
}
fn deregister(&mut self, poll: &mio::Registry) -> io::Result<()> {
SourceFd(&self.as_raw_fd()).deregister(poll)
}
}
#[cfg(any(feature = "async-tokio", feature = "mio-evented"))]
impl AsRawFd for AsyncPinPoller {
fn as_raw_fd(&self) -> RawFd {
self.devfile.as_raw_fd()
}
}
#[cfg(feature = "async-tokio")]
pub struct PinStream {
evented: AsyncFd<AsyncPinPoller>,
skipped_first_event: bool,
}
#[cfg(feature = "async-tokio")]
impl PinStream {
pub fn init(pin: Pin) -> Result<Self> {
Ok(PinStream {
evented: AsyncFd::new(pin.get_async_poller()?)?,
skipped_first_event: false,
})
}
}
#[cfg(feature = "async-tokio")]
impl Stream for PinStream {
type Item = Result<()>;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Poll<Option<Self::Item>> {
loop {
let mut guard = ready!(self.evented.poll_read_ready(cx))?;
guard.clear_ready();
if self.skipped_first_event {
return Poll::Ready(Some(Ok(())));
} else {
self.skipped_first_event = true;
}
}
}
}
#[cfg(feature = "async-tokio")]
pub struct PinValueStream(PinStream);
#[cfg(feature = "async-tokio")]
impl PinValueStream {
#[inline]
fn get_value(&mut self) -> Result<u8> {
get_value_from_file(&mut self.0.evented.get_mut().devfile)
}
}
#[cfg(feature = "async-tokio")]
impl Stream for PinValueStream {
type Item = Result<u8>;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Poll<Option<Self::Item>> {
ready!(std::pin::Pin::new(&mut self.0).poll_next(cx));
Poll::Ready(Some(Ok(self.get_value()?)))
}
}