#########################################################################################
# Package HiPi::Interrupt::GPIO
# Description: Device GPIO Interrupt Handler
# Created Wed Apr 24 05:59:33 2013
# SVN Id $Id: GPIO.pm 63 2016-03-15 00:45:13Z Mark Dootson $
# Copyright: Copyright (c) 2013 Mark Dootson
# Licence: This work is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 3 of the License, or any later
# version.
#########################################################################################
package HiPi::Interrupt::GPIO;
#########################################################################################
use 5.14.0;
use strict;
use warnings;
use parent qw( HiPi::Interrupt::Base );
use IO::Epoll;
use Fcntl;
use Try::Tiny;
use Time::HiRes;
use POSIX;
our $VERSION = '0.42';
__PACKAGE__->create_accessors( qw( handles handleindex epfd epollflags epfdsize ) );
use constant {
EDGE_DETECT_FALLING => 4,
EDGE_DETECT_RISING => 8,
};
sub new {
my ($class, %params) = @_;
# common
$params{pinclass} = 'gpio';
# override
$params{polltimeout} = 1000;
# specific
$params{epollflags} = EPOLLET | EPOLLPRI | EPOLLIN;
$params{epfdsize} = 30;
$params{epfd} = epoll_create( $params{epfdsize} );
$params{handles} = {};
$params{handleindex} = {};
my $self = $class->SUPER::new(%params);
return $self;
}
sub run {
my $self = shift;
$self->docontinue(1);
#-----------------------------
# Run Loop
#-----------------------------
while( $self->docontinue ) {
# Standard Queue Check
last unless $self->check_queue;
# Loop wait if no interrupts
unless($self->pollcount) {
$self->sleep_timeout($self->polltimeout);
next;
}
my $epevents = epoll_wait($self->epfd, $self->epfdsize, $self->polltimeout );
if( $epevents ) {
my $timestamp = $self->get_timestamp;
for my $event ( @$epevents ) {
my ($fileno, $evmask) = @$event;
my $gpioname = $self->handleindex->{$fileno};
my $fhdata = $self->handles->{$gpioname};
try {
my $value = $self->read_value($fhdata->{fh});
$fhdata->{BCMVALUE} = $value;
my $sendinterrupt = 0;
$sendinterrupt = 1 if(($value == 0) && ( $fhdata->{EMASK} & EDGE_DETECT_FALLING ) );
$sendinterrupt = 1 if(($value == 1) && ( $fhdata->{EMASK} & EDGE_DETECT_RISING ) );
if( $fhdata->{FIRST} || $evmask == EPOLLIN ) {
# ignore first epoll or plain EPOLLIN
$fhdata->{FIRST} = 0;
$sendinterrupt = 0;
}
$self->send_interrupt_result($fhdata->{BCMGPIOID}, $value, $timestamp) if $sendinterrupt;
} catch {
$self->send_action_result('interrupt', $fhdata->{BCMGPIOID}, qq(Error : $_));
};
}
} else {
# error
$self->send_action_result('interrupt', -1, qq(Error : $!));
}
}
#-----------------------------
# close everything
#-----------------------------
for my $gpioname ( sort keys(%{ $self->handles } ) ) {
$self->handle_remove( $self->pinname_2_pinid($gpioname) );
}
POSIX::close( $self->epfd );
$self->send_action_result('stop', -1);
}
sub handle_add {
my($self, $gpiopin) = @_;
my $gpioname = $self->pinid_2_pinname($gpiopin);
if( exists $self->handles->{$gpioname} ) {
$self->send_action_result('add', $gpiopin, qq(Pin already polled by handler));
return;
}
my $valfile = qq(/sys/class/gpio/gpio${gpiopin}/value);
my $edgfile = qq(/sys/class/gpio/gpio${gpiopin}/edge);
my ($efh, $edgstr);
$edgstr = try {
my $buffer;
sysopen($efh, $edgfile, O_RDONLY) or die $!;
sysseek($efh,0,0);
defined(sysread($efh, $buffer, 16)) or die $!;
chomp($buffer);
close($efh);
return $buffer;
} catch {
close($efh) if $efh;
$self->send_action_result('add', $gpiopin, qq(Failed to read interrupt status : $_));
return 'error';
};
return if(!$edgstr || $edgstr eq 'error');
if( $edgstr eq 'none' ) {
$self->send_action_result('add', $gpiopin, qq(Pin has egde detection set to 'none'));
return;
}
try {
my ($fh, $rmask);
sysopen($fh, $valfile, O_RDONLY|O_NONBLOCK) or die $!;
my $value = $self->read_value($fh);
epoll_ctl($self->epfd, EPOLL_CTL_ADD, $fh->fileno, $self->epollflags ) >= 0 || die qq(epoll_ctl: $!);
if( $edgstr eq 'falling' ) {
$rmask = EDGE_DETECT_FALLING;
} elsif( $edgstr eq 'rising' ) {
$rmask = EDGE_DETECT_RISING;
} elsif( $edgstr eq 'both' ) {
$rmask = EDGE_DETECT_RISING | EDGE_DETECT_FALLING;
} else {
die qq(unknown edge detect setting $edgstr);
}
$self->handles->{$gpioname}->{fh} = $fh;
$self->handles->{$gpioname}->{BCMVALUE} = $value;
$self->handles->{$gpioname}->{BCMGPIONAME} = $gpioname;
$self->handles->{$gpioname}->{BCMGPIOID} = $gpiopin;
$self->handles->{$gpioname}->{EMASK} = $rmask;
$self->handles->{$gpioname}->{FIRST} = 1;
my $index = $fh->fileno;
$self->handleindex->{$index} = $gpioname;
$self->pollcount( $self->pollcount + 1 );
$self->send_action_result('add', $gpiopin);
} catch {
$self->send_action_result('add', $gpiopin, qq(Failed to add pin to interrupt handler : $_));
};
}
sub handle_remove {
my($self, $gpiopin) = @_;
my $gpioname = $self->pinid_2_pinname($gpiopin);
unless( exists $self->handles->{$gpioname} ) {
$self->send_action_result('remove', $gpiopin, qq(Pin not polled by handler));
return;
}
my $fh = $self->handles->{$gpioname}->{fh};
try {
epoll_ctl($self->epfd, EPOLL_CTL_DEL, $fh->fileno, $self->epollflags ) >= 0 || die qq(epoll_ctl: $!);
my $index = $fh->fileno;
delete( $self->handleindex->{$index} );
close($fh);
delete( $self->handles->{$gpioname} );
$self->send_action_result('remove', $gpiopin);
} catch {
$self->send_action_result('remove', $gpiopin, qq(Error removing pin from interrupt handler : $_));
};
$self->pollcount( $self->pollcount - 1 );
}
sub read_value {
my($self, $fh) = @_;
sysseek($fh,0,0);
my $value;
defined(sysread($fh, $value, 1)) or die $!;
chomp($value);
return $value;
}
1;