1#![cfg_attr(feature = "async-tokio", allow(deprecated))]
44
45#[cfg(feature = "async-tokio")]
46extern crate futures;
47#[cfg(feature = "mio-evented")]
48extern crate mio;
49#[cfg(not(target_os = "wasi"))]
50extern crate nix;
51#[cfg(feature = "async-tokio")]
52extern crate tokio;
53
54use std::io;
55use std::io::prelude::*;
56#[cfg(any(target_os = "linux", target_os = "android", feature = "async-tokio"))]
57use std::io::SeekFrom;
58#[cfg(not(target_os = "wasi"))]
59use std::os::unix::prelude::*;
60use std::path::Path;
61use std::{fs, fs::File};
62
63#[cfg(feature = "async-tokio")]
64use futures::{ready, Stream};
65#[cfg(feature = "mio-evented")]
66use mio::event::Source;
67#[cfg(feature = "mio-evented")]
68use mio::unix::SourceFd;
69#[cfg(any(target_os = "linux", target_os = "android"))]
70use nix::sys::epoll::*;
71#[cfg(not(target_os = "wasi"))]
72use nix::unistd::close;
73#[cfg(feature = "async-tokio")]
74use std::task::Poll;
75#[cfg(feature = "async-tokio")]
76use tokio::io::unix::AsyncFd;
77
78pub use error::Error;
79
80mod error;
81
82#[derive(Clone, Copy, Debug, PartialEq, Eq)]
83pub struct Pin {
84 pin_num: u64,
85}
86
87#[derive(Clone, Copy, Debug, PartialEq, Eq)]
88pub enum Direction {
89 In,
90 Out,
91 High,
92 Low,
93}
94
95#[derive(Clone, Copy, Debug, PartialEq, Eq)]
96pub enum Edge {
97 NoInterrupt,
98 RisingEdge,
99 FallingEdge,
100 BothEdges,
101}
102
103#[macro_export]
104macro_rules! try_unexport {
105 ($gpio:ident, $e:expr) => {
106 match $e {
107 Ok(res) => res,
108 Err(e) => {
109 $gpio.unexport()?;
110 return Err(e);
111 }
112 }
113 };
114}
115
116pub type Result<T> = ::std::result::Result<T, error::Error>;
117
118#[cfg(any(target_os = "linux", target_os = "android"))]
124fn flush_input_from_file(dev_file: &mut File, max: usize) -> io::Result<usize> {
125 let mut s = String::with_capacity(max);
126 dev_file.read_to_string(&mut s)
127}
128
129#[cfg(any(target_os = "linux", target_os = "android", feature = "async-tokio"))]
131fn get_value_from_file(dev_file: &mut File) -> Result<u8> {
132 let mut s = String::with_capacity(10);
133 dev_file.seek(SeekFrom::Start(0))?;
134 dev_file.read_to_string(&mut s)?;
135 match s[..1].parse::<u8>() {
136 Ok(n) => Ok(n),
137 Err(_) => Err(Error::Unexpected(format!(
138 "Unexpected value file contents: {:?}",
139 s
140 ))),
141 }
142}
143
144impl Pin {
145 fn write_to_device_file(&self, dev_file_name: &str, value: &str) -> io::Result<()> {
147 let gpio_path = format!("/sys/class/gpio/gpio{}/{}", self.pin_num, dev_file_name);
148 let mut dev_file = File::create(&gpio_path)?;
149 dev_file.write_all(value.as_bytes())?;
150 Ok(())
151 }
152
153 fn read_from_device_file(&self, dev_file_name: &str) -> io::Result<String> {
154 let gpio_path = format!("/sys/class/gpio/gpio{}/{}", self.pin_num, dev_file_name);
155 let mut dev_file = File::open(&gpio_path)?;
156 let mut s = String::new();
157 dev_file.read_to_string(&mut s)?;
158 Ok(s)
159 }
160
161 pub fn new(pin_num: u64) -> Pin {
165 Pin { pin_num }
166 }
167
168 pub fn from_path<T: AsRef<Path>>(path: T) -> Result<Pin> {
181 let pb = fs::canonicalize(path.as_ref())?;
183
184 if !fs::metadata(&pb)?.is_dir() {
186 return Err(Error::Unexpected(
187 "Provided path not a directory or symlink to \
188 a directory"
189 .to_owned(),
190 ));
191 }
192 let num = Pin::extract_pin_from_path(&pb)?;
193 Ok(Pin::new(num))
194 }
195
196 fn extract_pin_from_path<P: AsRef<Path>>(path: P) -> Result<u64> {
198 path.as_ref()
199 .file_name()
200 .and_then(|filename| filename.to_str())
201 .and_then(|filename_str| filename_str.trim_start_matches("gpio").parse::<u64>().ok())
202 .ok_or_else(|| Error::InvalidPath(format!("{:?}", path.as_ref())))
203 }
204
205 pub fn get_pin_num(&self) -> u64 {
207 self.pin_num
208 }
209
210 #[inline]
231 pub fn with_exported<F: FnOnce() -> Result<()>>(&self, closure: F) -> Result<()> {
232 self.export()?;
233 match closure() {
234 Ok(()) => {
235 self.unexport()?;
236 Ok(())
237 }
238 Err(err) => {
239 self.unexport()?;
240 Err(err)
241 }
242 }
243 }
244
245 pub fn is_exported(&self) -> bool {
250 fs::metadata(&format!("/sys/class/gpio/gpio{}", self.pin_num)).is_ok()
251 }
252
253 pub fn export(&self) -> Result<()> {
279 if fs::metadata(&format!("/sys/class/gpio/gpio{}", self.pin_num)).is_err() {
280 let mut export_file = File::create("/sys/class/gpio/export")?;
281 export_file.write_all(format!("{}", self.pin_num).as_bytes())?;
282 }
283 Ok(())
284 }
285
286 pub fn unexport(&self) -> Result<()> {
293 if fs::metadata(&format!("/sys/class/gpio/gpio{}", self.pin_num)).is_ok() {
294 let mut unexport_file = File::create("/sys/class/gpio/unexport")?;
295 unexport_file.write_all(format!("{}", self.pin_num).as_bytes())?;
296 }
297 Ok(())
298 }
299
300 pub fn get_pin(&self) -> u64 {
302 self.pin_num
303 }
304
305 pub fn get_direction(&self) -> Result<Direction> {
307 match self.read_from_device_file("direction") {
308 Ok(s) => match s.trim() {
309 "in" => Ok(Direction::In),
310 "out" => Ok(Direction::Out),
311 "high" => Ok(Direction::High),
312 "low" => Ok(Direction::Low),
313 other => Err(Error::Unexpected(format!(
314 "direction file contents {}",
315 other
316 ))),
317 },
318 Err(e) => Err(::std::convert::From::from(e)),
319 }
320 }
321
322 pub fn set_direction(&self, dir: Direction) -> Result<()> {
336 self.write_to_device_file(
337 "direction",
338 match dir {
339 Direction::In => "in",
340 Direction::Out => "out",
341 Direction::High => "high",
342 Direction::Low => "low",
343 },
344 )?;
345
346 Ok(())
347 }
348
349 pub fn get_value(&self) -> Result<u8> {
356 match self.read_from_device_file("value") {
357 Ok(s) => match s.trim() {
358 "1" => Ok(1),
359 "0" => Ok(0),
360 other => Err(Error::Unexpected(format!("value file contents {}", other))),
361 },
362 Err(e) => Err(::std::convert::From::from(e)),
363 }
364 }
365
366 pub fn set_value(&self, value: u8) -> Result<()> {
372 self.write_to_device_file(
373 "value",
374 match value {
375 0 => "0",
376 _ => "1",
377 },
378 )?;
379
380 Ok(())
381 }
382
383 pub fn get_edge(&self) -> Result<Edge> {
388 match self.read_from_device_file("edge") {
389 Ok(s) => match s.trim() {
390 "none" => Ok(Edge::NoInterrupt),
391 "rising" => Ok(Edge::RisingEdge),
392 "falling" => Ok(Edge::FallingEdge),
393 "both" => Ok(Edge::BothEdges),
394 other => Err(Error::Unexpected(format!(
395 "Unexpected file contents {}",
396 other
397 ))),
398 },
399 Err(e) => Err(::std::convert::From::from(e)),
400 }
401 }
402
403 pub fn set_edge(&self, edge: Edge) -> Result<()> {
409 self.write_to_device_file(
410 "edge",
411 match edge {
412 Edge::NoInterrupt => "none",
413 Edge::RisingEdge => "rising",
414 Edge::FallingEdge => "falling",
415 Edge::BothEdges => "both",
416 },
417 )?;
418
419 Ok(())
420 }
421
422 pub fn get_active_low(&self) -> Result<bool> {
424 match self.read_from_device_file("active_low") {
425 Ok(s) => match s.trim() {
426 "1" => Ok(true),
427 "0" => Ok(false),
428 other => Err(Error::Unexpected(format!(
429 "active_low file contents {}",
430 other
431 ))),
432 },
433 Err(e) => Err(::std::convert::From::from(e)),
434 }
435 }
436
437 pub fn set_active_low(&self, active_low: bool) -> Result<()> {
442 self.write_to_device_file(
443 "active_low",
444 match active_low {
445 true => "1",
446 false => "0",
447 },
448 )?;
449
450 Ok(())
451 }
452
453 #[cfg(not(target_os = "wasi"))]
459 pub fn get_poller(&self) -> Result<PinPoller> {
460 PinPoller::new(self.pin_num)
461 }
462
463 #[cfg(feature = "mio-evented")]
470 pub fn get_async_poller(&self) -> Result<AsyncPinPoller> {
471 AsyncPinPoller::new(self.pin_num)
472 }
473
474 #[cfg(feature = "async-tokio")]
481 pub fn get_stream(&self) -> Result<PinStream> {
482 PinStream::init(*self)
483 }
484
485 #[cfg(feature = "async-tokio")]
497 pub fn get_value_stream(&self) -> Result<PinValueStream> {
498 Ok(PinValueStream(PinStream::init(*self)?))
499 }
500}
501
502#[test]
503fn extract_pin_fom_path_test() {
504 let tok1 = Pin::extract_pin_from_path(&"/sys/class/gpio/gpio951");
505 assert_eq!(951, tok1.unwrap());
506 let tok2 = Pin::extract_pin_from_path(&"/sys/CLASS/gpio/gpio951/");
507 assert_eq!(951, tok2.unwrap());
508 let tok3 = Pin::extract_pin_from_path(&"../../devices/soc0/gpiochip3/gpio/gpio124");
509 assert_eq!(124, tok3.unwrap());
510 let err1 = Pin::extract_pin_from_path(&"/sys/CLASS/gpio/gpio");
511 assert!(err1.is_err());
512 let err2 = Pin::extract_pin_from_path(&"/sys/class/gpio/gpioSDS");
513 assert!(err2.is_err());
514}
515#[cfg(not(target_os = "wasi"))]
516#[derive(Debug)]
517pub struct PinPoller {
518 pin_num: u64,
519 epoll_fd: RawFd,
520 devfile: File,
521}
522#[cfg(not(target_os = "wasi"))]
523impl PinPoller {
524 pub fn get_pin(&self) -> Pin {
529 Pin::new(self.pin_num)
530 }
531
532 #[cfg(any(target_os = "linux", target_os = "android"))]
534 pub fn new(pin_num: u64) -> Result<PinPoller> {
535 let devfile: File = File::open(&format!("/sys/class/gpio/gpio{}/value", pin_num))?;
536 let devfile_fd = devfile.as_raw_fd();
537 let epoll_fd = epoll_create()?;
538 let mut event = EpollEvent::new(EpollFlags::EPOLLPRI | EpollFlags::EPOLLET, 0u64);
539
540 match epoll_ctl(epoll_fd, EpollOp::EpollCtlAdd, devfile_fd, &mut event) {
541 Ok(_) => Ok(PinPoller {
542 pin_num,
543 devfile,
544 epoll_fd,
545 }),
546 Err(err) => {
547 let _ = close(epoll_fd); Err(::std::convert::From::from(err))
549 }
550 }
551 }
552
553 #[cfg(not(any(target_os = "linux", target_os = "android")))]
554 pub fn new(_pin_num: u64) -> Result<PinPoller> {
555 Err(Error::Unsupported("PinPoller".into()))
556 }
557
558 #[cfg(any(target_os = "linux", target_os = "android"))]
576 pub fn poll(&mut self, timeout_ms: isize) -> Result<Option<u8>> {
577 flush_input_from_file(&mut self.devfile, 255)?;
578 let dummy_event = EpollEvent::new(EpollFlags::EPOLLPRI | EpollFlags::EPOLLET, 0u64);
579 let mut events: [EpollEvent; 1] = [dummy_event];
580 let cnt = epoll_wait(self.epoll_fd, &mut events, timeout_ms)?;
581 Ok(match cnt {
582 0 => None, _ => Some(get_value_from_file(&mut self.devfile)?),
584 })
585 }
586
587 #[cfg(not(any(target_os = "linux", target_os = "android")))]
588 pub fn poll(&mut self, _timeout_ms: isize) -> Result<Option<u8>> {
589 Err(Error::Unsupported("PinPoller".into()))
590 }
591}
592
593#[cfg(not(target_os = "wasi"))]
594impl Drop for PinPoller {
595 fn drop(&mut self) {
596 close(self.epoll_fd).unwrap(); }
601}
602
603#[cfg(feature = "mio-evented")]
604#[derive(Debug)]
605pub struct AsyncPinPoller {
606 devfile: File,
607}
608
609#[cfg(feature = "mio-evented")]
610impl AsyncPinPoller {
611 fn new(pin_num: u64) -> Result<Self> {
612 let devfile = File::open(&format!("/sys/class/gpio/gpio{}/value", pin_num))?;
613 Ok(AsyncPinPoller { devfile })
614 }
615}
616
617#[cfg(feature = "mio-evented")]
618impl Source for AsyncPinPoller {
619 fn register(
620 &mut self,
621 poll: &mio::Registry,
622 token: mio::Token,
623 interest: mio::Interest,
624 ) -> io::Result<()> {
625 SourceFd(&self.as_raw_fd()).register(poll, token, interest)
626 }
627
628 fn reregister(
629 &mut self,
630 poll: &mio::Registry,
631 token: mio::Token,
632 interest: mio::Interest,
633 ) -> io::Result<()> {
634 SourceFd(&self.as_raw_fd()).reregister(poll, token, interest)
635 }
636
637 fn deregister(&mut self, poll: &mio::Registry) -> io::Result<()> {
638 SourceFd(&self.as_raw_fd()).deregister(poll)
639 }
640}
641
642#[cfg(any(feature = "async-tokio", feature = "mio-evented"))]
643impl AsRawFd for AsyncPinPoller {
644 fn as_raw_fd(&self) -> RawFd {
645 self.devfile.as_raw_fd()
646 }
647}
648
649#[cfg(feature = "async-tokio")]
650pub struct PinStream {
651 evented: AsyncFd<AsyncPinPoller>,
652 skipped_first_event: bool,
653}
654
655#[cfg(feature = "async-tokio")]
656impl PinStream {
657 pub fn init(pin: Pin) -> Result<Self> {
658 Ok(PinStream {
659 evented: AsyncFd::new(pin.get_async_poller()?)?,
660 skipped_first_event: false,
661 })
662 }
663}
664
665#[cfg(feature = "async-tokio")]
666impl Stream for PinStream {
667 type Item = Result<()>;
668
669 fn poll_next(
670 mut self: std::pin::Pin<&mut Self>,
671 cx: &mut std::task::Context<'_>,
672 ) -> Poll<Option<Self::Item>> {
673 loop {
674 let mut guard = ready!(self.evented.poll_read_ready(cx))?;
675 guard.clear_ready();
676 if self.skipped_first_event {
677 return Poll::Ready(Some(Ok(())));
678 } else {
679 self.skipped_first_event = true;
680 }
681 }
682 }
683}
684
685#[cfg(feature = "async-tokio")]
686pub struct PinValueStream(PinStream);
687
688#[cfg(feature = "async-tokio")]
689impl PinValueStream {
690 #[inline]
691 fn get_value(&mut self) -> Result<u8> {
692 get_value_from_file(&mut self.0.evented.get_mut().devfile)
693 }
694}
695
696#[cfg(feature = "async-tokio")]
697impl Stream for PinValueStream {
698 type Item = Result<u8>;
699
700 fn poll_next(
701 mut self: std::pin::Pin<&mut Self>,
702 cx: &mut std::task::Context<'_>,
703 ) -> Poll<Option<Self::Item>> {
704 ready!(std::pin::Pin::new(&mut self.0).poll_next(cx));
705 Poll::Ready(Some(Ok(self.get_value()?)))
706 }
707}