use crate::gpio_cdev::{
GPIO_GET_LINEEVENT_IOCTL, GPIOEVENT_REQUEST_BOTH_EDGES, GPIOEVENT_REQUEST_FALLING_EDGE,
GPIOEVENT_REQUEST_RISING_EDGE, GpioEventData, GpioEventRequest, chip_open_by_label,
request_event,
};
use anyhow::{Error, Result, anyhow};
use mio::unix::SourceFd;
use mio::{Events, Interest, Poll, Token};
use std::collections::HashMap;
use std::convert::TryFrom;
use std::ffi::c_void;
use std::os::fd::AsRawFd;
use std::sync::{Arc, Mutex};
use std::thread::{self, JoinHandle};
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Edge {
None = 0,
Rising = 1,
Falling = 2,
Both = 3,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InvalidEventFlagError(pub u32);
impl std::fmt::Display for InvalidEventFlagError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "invalid GPIO event flag: {}", self.0)
}
}
impl std::error::Error for InvalidEventFlagError {}
impl From<Edge> for u32 {
fn from(edge: Edge) -> Self {
match edge {
Edge::None => 0,
Edge::Rising => GPIOEVENT_REQUEST_RISING_EDGE,
Edge::Falling => GPIOEVENT_REQUEST_FALLING_EDGE,
Edge::Both => GPIOEVENT_REQUEST_BOTH_EDGES,
}
}
}
impl TryFrom<u32> for Edge {
type Error = InvalidEventFlagError;
fn try_from(flag: u32) -> Result<Self, Self::Error> {
match flag {
GPIOEVENT_REQUEST_RISING_EDGE => Ok(Edge::Rising),
GPIOEVENT_REQUEST_FALLING_EDGE => Ok(Edge::Falling),
GPIOEVENT_REQUEST_BOTH_EDGES => Ok(Edge::Both),
0 => Ok(Edge::None),
invalid => Err(InvalidEventFlagError(invalid)),
}
}
}
pub type EdgeCallback = Box<dyn Fn(u32) + Send + Sync>;
struct GpioEventObject {
value_fd: i32,
channel: u32,
bouncetime: Option<Duration>,
callbacks: Vec<EdgeCallback>,
last_call: Option<Instant>,
event_occurred: bool,
thread_running: Arc<Mutex<bool>>,
thread_handle: Option<JoinHandle<()>>,
}
impl GpioEventObject {
fn new(fd: i32, channel: u32, bouncetime: Option<Duration>) -> Self {
Self {
value_fd: fd,
channel,
bouncetime,
callbacks: Vec::new(),
last_call: None,
event_occurred: false,
thread_running: Arc::new(Mutex::new(false)),
thread_handle: None,
}
}
fn should_trigger(&mut self) -> bool {
let now = Instant::now();
match self.bouncetime {
Some(bouncetime) => {
let should = match self.last_call {
Some(last) => now.duration_since(last) >= bouncetime,
None => true,
};
if should {
self.last_call = Some(now);
}
should
}
None => {
self.last_call = Some(now);
true
}
}
}
fn trigger_callbacks(&self) {
for callback in &self.callbacks {
callback(self.channel);
}
}
}
impl Drop for GpioEventObject {
fn drop(&mut self) {
*self.thread_running.lock().unwrap() = false;
if self.value_fd >= 0 {
unsafe {
libc::close(self.value_fd);
}
}
}
}
pub struct EventManager {
event_list: HashMap<(String, u32), Arc<Mutex<GpioEventObject>>>,
}
impl EventManager {
pub fn new() -> Self {
Self {
event_list: HashMap::new(),
}
}
fn event_added(&self, chip_name: &str, channel: u32) -> bool {
self.event_list
.contains_key(&(chip_name.to_string(), channel))
}
pub fn add_edge_detect(
&mut self,
chip_name: &str,
channel: u32,
fd: i32,
bouncetime: Option<Duration>,
polltime: Duration,
) -> Result<()> {
if self.event_added(chip_name, channel) {
return Err(anyhow!("Event is already added for channel {}", channel));
}
let gpio_obj = Arc::new(Mutex::new(GpioEventObject::new(fd, channel, bouncetime)));
let running = Arc::clone(&gpio_obj.lock().unwrap().thread_running);
*running.lock().unwrap() = true;
let gpio_obj_clone = Arc::clone(&gpio_obj);
let handle = thread::spawn(move || {
edge_handler(running, gpio_obj_clone, fd, channel, polltime);
});
{
let mut obj = gpio_obj.lock().unwrap();
obj.thread_handle = Some(handle);
}
self.event_list
.insert((chip_name.to_string(), channel), gpio_obj);
Ok(())
}
pub fn is_event_added(&self, chip_name: &str, channel: u32) -> bool {
self.event_added(chip_name, channel)
}
pub fn remove_edge_detect(
&mut self,
chip_name: &str,
channel: u32,
timeout: Duration,
) -> Result<()> {
let key = (chip_name.to_string(), channel);
if let Some(gpio_obj) = self.event_list.remove(&key) {
let handle = {
let mut obj = gpio_obj.lock().unwrap();
*obj.thread_running.lock().unwrap() = false;
obj.thread_handle.take()
};
if let Some(handle) = handle {
let start = Instant::now();
loop {
if handle.is_finished() {
let _ = handle.join();
break;
}
if start.elapsed() >= timeout {
eprintln!(
"Warning: event handler thread for channel {} did not exit within timeout",
channel
);
break;
}
thread::sleep(Duration::from_millis(10));
}
}
}
Ok(())
}
pub fn add_callback(
&mut self,
chip_name: &str,
channel: u32,
callback: EdgeCallback,
) -> Result<()> {
let key = (chip_name.to_string(), channel);
if let Some(gpio_obj) = self.event_list.get(&key) {
let mut obj = gpio_obj.lock().unwrap();
obj.callbacks.push(callback);
Ok(())
} else {
Err(anyhow!("Event not found for channel {}", channel))
}
}
pub fn edge_event_detected(&mut self, chip_name: &str, channel: u32) -> bool {
let key = (chip_name.to_string(), channel);
if let Some(gpio_obj) = self.event_list.get(&key) {
let mut obj = gpio_obj.lock().unwrap();
let occurred = obj.event_occurred;
obj.event_occurred = false;
occurred
} else {
false
}
}
pub fn event_cleanup(&mut self, chip_name: &str, channel: u32) {
let _ = self.remove_edge_detect(chip_name, channel, Duration::from_millis(300));
}
}
impl Default for EventManager {
fn default() -> Self {
Self::new()
}
}
fn edge_handler(
running: Arc<Mutex<bool>>,
gpio_obj: Arc<Mutex<GpioEventObject>>,
fd: i32,
channel: u32,
polltime: Duration,
) {
let mut initial_buf = vec![0u8; std::mem::size_of::<GpioEventData>()];
unsafe {
libc::read(
fd,
initial_buf.as_mut_ptr() as *mut c_void,
initial_buf.len(),
);
}
let mut poll = match Poll::new() {
Ok(p) => p,
Err(e) => {
eprintln!("Failed to create Poll instance: {}", e);
return;
}
};
let mut source = SourceFd(&fd);
if let Err(e) =
poll.registry()
.register(&mut source, Token(channel as usize), Interest::READABLE)
{
eprintln!("Failed to register fd with Poll: {}", e);
return;
}
let mut events = Events::with_capacity(1);
while *running.lock().unwrap() {
if !*running.lock().unwrap() {
break;
}
match poll.poll(&mut events, Some(polltime)) {
Ok(_) => {
if events.is_empty() {
continue;
}
for _event in &events {
let mut buf = vec![0u8; std::mem::size_of::<GpioEventData>()];
let result =
unsafe { libc::read(fd, buf.as_mut_ptr() as *mut c_void, buf.len()) };
if result < 0 {
let errno = std::io::Error::last_os_error().raw_os_error().unwrap_or(-1);
if errno == libc::EAGAIN || errno == libc::EWOULDBLOCK {
continue;
}
break;
}
if result > 0 {
let event_data: GpioEventData =
unsafe { std::ptr::read(buf.as_ptr() as *const GpioEventData) };
if event_data.id != GPIOEVENT_REQUEST_RISING_EDGE
&& event_data.id != GPIOEVENT_REQUEST_FALLING_EDGE
{
continue;
}
let mut obj = gpio_obj.lock().unwrap();
if obj.should_trigger() {
obj.event_occurred = true;
obj.trigger_callbacks();
}
}
}
}
Err(_) => {
break;
}
}
}
}
pub fn blocking_wait_for_edge(
chip_fd: i32,
request: &mut GpioEventRequest,
bouncetime: Option<Duration>,
timeout: Option<Duration>,
) -> Result<bool> {
request.handleflags = crate::gpio_cdev::GPIOHANDLE_REQUEST_INPUT;
unsafe {
let result = libc::ioctl(
chip_fd,
GPIO_GET_LINEEVENT_IOCTL as libc::c_ulong,
request as *mut GpioEventRequest,
);
if result < 0 {
let errno = std::io::Error::last_os_error().raw_os_error().unwrap_or(-1);
return Err(Error::msg(format!(
"Opening input line event handle: errno {}",
errno
)));
}
}
let event_fd = request.fd;
if event_fd < 0 {
return Err(Error::msg("Failed to get valid line event handle"));
}
unsafe {
let flags = libc::fcntl(event_fd, libc::F_GETFL, 0);
libc::fcntl(event_fd, libc::F_SETFL, flags | libc::O_NONBLOCK);
}
let mut poll = Poll::new()?;
let mut source = SourceFd(&event_fd);
poll.registry()
.register(&mut source, Token(0), Interest::READABLE)?;
let mut events = Events::with_capacity(1);
let start = Instant::now();
let _ = bouncetime;
loop {
let remaining = match timeout {
Some(t) => {
let r = t.saturating_sub(start.elapsed());
if r.is_zero() {
unsafe {
libc::close(event_fd);
}
return Ok(false);
}
Some(r)
}
None => None,
};
match poll.poll(&mut events, remaining) {
Ok(_) => {
if events.is_empty() {
unsafe {
libc::close(event_fd);
}
return Ok(false);
}
for _event in &events {
let mut buf = vec![0u8; std::mem::size_of::<GpioEventData>()];
let result =
unsafe { libc::read(event_fd, buf.as_mut_ptr() as *mut c_void, buf.len()) };
if result < 0 {
let errno = std::io::Error::last_os_error().raw_os_error().unwrap_or(-1);
if errno == libc::EAGAIN || errno == libc::EWOULDBLOCK {
continue;
}
unsafe {
libc::close(event_fd);
}
return Err(Error::msg(format!("Reading GPIO event: errno {}", errno)));
}
if result > 0 {
let event_data: GpioEventData =
unsafe { std::ptr::read(buf.as_ptr() as *const GpioEventData) };
if event_data.id != GPIOEVENT_REQUEST_RISING_EDGE
&& event_data.id != GPIOEVENT_REQUEST_FALLING_EDGE
{
unsafe {
libc::close(event_fd);
}
return Err(Error::msg("Unknown event type"));
}
unsafe {
libc::close(event_fd);
}
return Ok(true);
}
}
}
Err(_) => {
unsafe {
libc::close(event_fd);
}
return Err(Error::msg("Poll error during blocking wait"));
}
}
}
}
pub fn open_event(chip_fd: i32, request: &mut GpioEventRequest) -> Result<i32> {
unsafe {
let result = libc::ioctl(
chip_fd,
GPIO_GET_LINEEVENT_IOCTL as libc::c_ulong,
request as *mut GpioEventRequest,
);
if result < 0 {
let errno = std::io::Error::last_os_error().raw_os_error().unwrap_or(-1);
return Err(Error::msg(format!(
"Opening input line event handle: errno {}",
errno
)));
}
}
let fd = request.fd;
if fd < 0 {
return Err(Error::msg("Failed to get valid line event handle"));
}
unsafe {
let flags = libc::fcntl(fd, libc::F_GETFL, 0);
libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK);
}
Ok(fd)
}
impl crate::GPIO {
pub fn wait_for_edge(
&self,
channel: u32,
edge: Edge,
timeout: Option<Duration>,
) -> Result<Option<u32>> {
let (chip_fd_raw, ch_info_line_offset) = {
let mut inner = self.inner();
let mode = inner
.gpio_mode
.ok_or_else(|| anyhow!("GPIO mode not set"))?;
let channel_data = self
.channel_data_by_mode
.get(&mode)
.ok_or_else(|| anyhow!("Invalid GPIO mode"))?;
let ch_info = channel_data
.get(&channel)
.ok_or_else(|| anyhow!("Invalid channel: {}", channel))?;
if ch_info.gpio_chip.is_empty() {
return Err(anyhow!("Channel {} is not a GPIO", channel));
}
let chip_name = &ch_info.gpio_chip;
let chip_fd = if !inner.chip_fd_map.contains_key(chip_name) {
let fd = chip_open_by_label(chip_name)?;
inner.chip_fd_map.insert(chip_name.clone(), fd);
inner.chip_fd_map.get(chip_name).unwrap().try_clone()?
} else {
inner.chip_fd_map.get(chip_name).unwrap().try_clone()?
};
if let Some(setup_ch_info) = inner.channel_data.get_mut(&channel) {
if let Some(line_handle) = setup_ch_info.line_handle.take() {
let _ = crate::gpio_cdev::close_line(Some(line_handle));
}
}
(chip_fd.as_raw_fd(), ch_info.line_offset)
};
let mut request = request_event(ch_info_line_offset, u32::from(edge), "jetsongpio-rs")?;
let detected = blocking_wait_for_edge(chip_fd_raw, &mut request, None, timeout)?;
Ok(if detected { Some(channel) } else { None })
}
pub fn add_event_detect(
&self,
channel: u32,
edge: Edge,
callback: Option<EdgeCallback>,
bouncetime: Option<Duration>,
polltime: Option<Duration>,
) -> Result<()> {
let (chip_fd_raw, ch_info_line_offset, chip_name) = {
let mut inner = self.inner();
let mode = inner
.gpio_mode
.ok_or_else(|| anyhow!("GPIO mode not set"))?;
let channel_data = self
.channel_data_by_mode
.get(&mode)
.ok_or_else(|| anyhow!("Invalid GPIO mode"))?;
let ch_info = channel_data
.get(&channel)
.ok_or_else(|| anyhow!("Invalid channel: {}", channel))?;
if ch_info.gpio_chip.is_empty() {
return Err(anyhow!("Channel {} is not a GPIO", channel));
}
if inner.event_manager.is_none() {
inner.event_manager = Some(EventManager::new());
}
let chip_name = ch_info.gpio_chip.clone();
let chip_fd = if !inner.chip_fd_map.contains_key(&chip_name) {
let fd = chip_open_by_label(&chip_name)?;
inner.chip_fd_map.insert(chip_name.clone(), fd);
inner.chip_fd_map.get(&chip_name).unwrap().try_clone()?
} else {
inner.chip_fd_map.get(&chip_name).unwrap().try_clone()?
};
if let Some(setup_ch_info) = inner.channel_data.get_mut(&channel) {
if let Some(line_handle) = setup_ch_info.line_handle.take() {
let _ = crate::gpio_cdev::close_line(Some(line_handle));
}
}
(chip_fd.as_raw_fd(), ch_info.line_offset, chip_name)
};
let mut request = request_event(ch_info_line_offset, u32::from(edge), "jetsongpio-rs")?;
let event_fd = open_event(chip_fd_raw, &mut request)?;
let polltime = polltime.unwrap_or(Duration::from_millis(200));
{
let mut inner = self.inner();
inner
.event_manager
.as_mut()
.unwrap()
.add_edge_detect(&chip_name, channel, event_fd, bouncetime, polltime)?;
if let Some(cb) = callback {
inner
.event_manager
.as_mut()
.unwrap()
.add_callback(&chip_name, channel, cb)?;
}
}
thread::sleep(Duration::from_secs(1));
Ok(())
}
pub fn add_event_callback(&self, channel: u32, callback: EdgeCallback) -> Result<()> {
let mut inner = self.inner();
let mode = inner
.gpio_mode
.ok_or_else(|| anyhow!("GPIO mode not set"))?;
let channel_data = self
.channel_data_by_mode
.get(&mode)
.ok_or_else(|| anyhow!("Invalid GPIO mode"))?;
let ch_info = channel_data
.get(&channel)
.ok_or_else(|| anyhow!("Invalid channel: {}", channel))?;
let chip_name = ch_info.gpio_chip.clone();
let manager = inner
.event_manager
.as_mut()
.ok_or_else(|| anyhow!("Add event detection using add_event_detect first"))?;
if !manager.is_event_added(&chip_name, channel) {
return Err(anyhow!(
"Add event detection using add_event_detect first before adding a callback"
));
}
manager.add_callback(&chip_name, channel, callback)?;
drop(inner);
thread::sleep(Duration::from_secs(1));
Ok(())
}
pub fn remove_event_detect(&self, channel: u32, timeout: Option<Duration>) -> Result<()> {
let mut inner = self.inner();
let mode = inner
.gpio_mode
.ok_or_else(|| anyhow!("GPIO mode not set"))?;
let channel_data = self
.channel_data_by_mode
.get(&mode)
.ok_or_else(|| anyhow!("Invalid GPIO mode"))?;
let ch_info = channel_data
.get(&channel)
.ok_or_else(|| anyhow!("Invalid channel: {}", channel))?;
let chip_name = &ch_info.gpio_chip;
let timeout = timeout.unwrap_or(Duration::from_millis(500));
if let Some(ref mut manager) = inner.event_manager {
let _ = manager.remove_edge_detect(chip_name, channel, timeout);
}
Ok(())
}
pub fn event_detected(&self, channel: u32) -> bool {
let mut inner = self.inner();
let mode = match inner.gpio_mode {
Some(m) => m,
None => return false,
};
let channel_data = match self.channel_data_by_mode.get(&mode) {
Some(data) => data,
None => return false,
};
let ch_info = match channel_data.get(&channel) {
Some(info) => info,
None => return false,
};
let chip_name = &ch_info.gpio_chip;
if let Some(ref mut manager) = inner.event_manager {
manager.edge_event_detected(chip_name, channel)
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_edge_conversion() {
assert_eq!(u32::from(Edge::Rising), GPIOEVENT_REQUEST_RISING_EDGE);
assert_eq!(u32::from(Edge::Falling), GPIOEVENT_REQUEST_FALLING_EDGE);
assert_eq!(u32::from(Edge::Both), GPIOEVENT_REQUEST_BOTH_EDGES);
assert_eq!(u32::from(Edge::None), 0);
assert_eq!(
Edge::try_from(GPIOEVENT_REQUEST_RISING_EDGE).unwrap(),
Edge::Rising
);
assert_eq!(
Edge::try_from(GPIOEVENT_REQUEST_FALLING_EDGE).unwrap(),
Edge::Falling
);
assert_eq!(
Edge::try_from(GPIOEVENT_REQUEST_BOTH_EDGES).unwrap(),
Edge::Both
);
assert_eq!(Edge::try_from(0).unwrap(), Edge::None);
assert!(Edge::try_from(999).is_err());
}
#[test]
fn test_event_manager() {
let manager = EventManager::new();
assert!(!manager.event_added("test", 1));
}
}