How To Use Poll(2) Or Select(2) Service Call To Watch A Pseudo File For Changes With Kotlin
Solution 1:
The approach I am taking is to create a native C++ JNI function to provide a way to implement the poll(2) Linux service call.
One interesting issue I ran into during development and testing was the poll()
returning immediately rather than waiting on either a time out or a voltage to the GPIO input pin. After posting on the 96Boards.org forum for the DragonBoard 410C, How to use poll() with sysfs interface to input GPIO pin to handle a switch press event, someone proposed a possible solution which worked, to read the pseudo file before starting the poll(2).
In order to use this function, I need to have some kind of a Kotlin coroutine or side thread so that when the main UI is processing a button click which starts the polling of the GPIO input pin, the main UI thread is not blocked until the function returns with either a GPIO event or a time out.
I have not yet been able to discern how to do such a coroutine so this is still a work in progress. After some thinking, it appears that some kind of an event listener architecture would be the most appropriate approach.
However testing indicates that the function pollPseudoFile()
is working properly by either doing a time out or returning with a value from /value
when a voltage is applied by hand using a wire from the 1.8v power (pin 38) to the GPIO input pin that is set with either a rising
or falling
setting in the /edge
pseudo file.
The source code for the Native C++ JNI function is below. I am using it with the following Kotlin source code.
First of all in my MainActivity.kt
source file, I make the Native C++ library available with the following source:
// See the StackOverFlow question with answer at URL:// https://stackoverflow.com/questions/36932662/android-how-to-call-ndk-function-from-kotlininit {
System.loadLibrary("pollfileservice")
}
externalfunpollFileWithTimeOut(pathPseudo : String, timeOutMs : Int): IntexternalfunpollGetLastRevents() : Int
Next I'm using this function in the Kotlin source file Gpio.kt
to actually perform the poll()
service call on the pseudo file.
classGpio(pin: Int) {
privateval pin : Intprivateval pinGpio : GpioFile = GpioFile()
/*
* The GPIO pins are represented by folders in the Linux file system
* within the folder /sys/class/gpio. Each pin is represented by a folder
* whose name is the prefix "gpio" followed by the pin number.
* Within the folder representing the pin are two files, "value" used to
* set or get the value of the pin and "direction" used to set or get
* the direction of the pin.
*
* This function creates the path to the Linux file which represents a particular
* GPIO pin function, "value" or "direction".
*/privatefunMakeFileName(pin: Int, op: String): String {
return"/sys/class/gpio/gpio$pin$op"
}
// ....... other source code in the Kotlin class GpiofunpinPoll(timeMs: Int) : Int {
val iStatus : Int = pinGpio.pollPseudoFile (MakeFileName(pin, "/value"), timeMs)
return iStatus
}
The above Gpio class is used in the actual UI button click listener as follows:
val gpioProcessor = GpioProcessor()
// Get reference of GPIO23.val gpioPin26 = gpioProcessor.pin26
// Set GPIO26 as input.
gpioPin26.pinIn()
gpioPin26.pinEdgeRising()
var xStatus: Int = gpioPin26.pinPoll(10000)
val xvalue = gpioPin26.value
PollFileService.h
//// Created by rchamber on 9/24/2020.//#ifndef MY_APPLICATION_POLLFILESERVICE_H#define MY_APPLICATION_POLLFILESERVICE_HclassPollFileService {
private:
int iValue;
int fd; /* file descriptor */public:
// See poll(2) man page at https://linux.die.net/man/2/pollstaticconstint PollSuccess = 0;
staticconstint PollTimeOut = 1;
staticconstint PollErrorEFAULT = -1;
staticconstint PollErrorEINTR = -2;
staticconstint PollErrorEINVAL = -3;
staticconstint PollErrorENOMEM = -4;
staticconstint PollErrorPOLLERR = -5;
staticconstint PollErrorPOLLNVAL = -6;
staticconstint PollErrorPOLLERRNVAL = -7;
staticconstint PollErrorPOLLHUP = -8;
staticconstint PollErrorPOLLERRDEFLT = -9;
staticconstint PollErrorUNKNOWN = -100;
staticint iPollStatus;
staticint iPollRet;
staticint iPollRevents;
PollFileService(constchar *pathName = nullptr, int timeMilliSec = -1);
~PollFileService();
intPollFileCheck(constchar *pathName, int timeMilliSec = -1);
intPollFileRead(constchar *pathName = nullptr);
};
extern"C"JNIEXPORT jint JNICALL
Java_com_example_myapplication_MainActivity_pollFileWithTimeOut(JNIEnv* pEnv, jobject pThis, jstring pKey, jint timeMS);
#endif//MY_APPLICATION_POLLFILESERVICE_H
PollFileService.cpp
//// Created by rchamber on 9/24/2020.//#include<sys/types.h>#include<sys/stat.h>#include<unistd.h>#include<fcntl.h>#include<math.h>#include<errno.h>#include<poll.h>#include<jni.h>#include"PollFileService.h"int PollFileService::iPollStatus = 0;
int PollFileService::iPollRet = 0;
int PollFileService::iPollRevents = 0;
PollFileService::PollFileService(constchar *pathName /* = nullptr */, int timeMilliSec /* = -1 */) : iValue(23), fd(-1)
{
iPollStatus = 0;
if (pathName) {
fd = open (pathName, O_RDONLY);
}
}
PollFileService::~PollFileService()
{
if (fd >= 0) {
close (fd);
fd = -1;
}
}
intPollFileService::PollFileCheck(constchar *pathName, int timeMilliSec /* = -1 */){
structpollfd fdList[] = {
{fd, POLLPRI | POLLERR, 0},
{0}
};
nfds_t nfds = 1;
unsignedchar tempbuff[256] = {0};
if (fd < 0 && pathName) {
fd = open (pathName, O_RDONLY);
fdList[0].fd = fd;
}
// with a edge triggered GPIO that we are going to use the poll(2)// function to wait on an event, we need to read from the// pin before we do the poll(2). If the read is not done then// the poll(2) returns with both POLLPRI and POLLERR set in the// revents member. however if we read first then do the poll2()// the poll(2) will wait for the event, input voltage change with// either a rising edge or a falling edge, depending on the setting// in the /edge pseudo file.ssize_t iCount = read (fdList[0].fd, tempbuff, 255);
iPollStatus = PollErrorUNKNOWN;
int iRet = poll(fdList, nfds, timeMilliSec);
if (iRet == 0) {
iPollStatus = PollTimeOut;
} elseif (iRet < 0) {
switch (errno) {
case EFAULT:
iPollStatus = PollErrorEFAULT;
break;
case EINTR:
iPollStatus = PollErrorEINTR;
break;
case EINVAL:
iPollStatus = PollErrorEINVAL;
break;
case ENOMEM:
iPollStatus = PollErrorENOMEM;
break;
default:
iPollStatus = PollErrorUNKNOWN;
break;
}
} elseif (iRet > 0) {
// successful call now determine what we should return.
iPollRevents = fdList[0].revents; /* & (POLLIN | POLLPRI | POLLERR); */switch (fdList[0].revents & (POLLIN | POLLPRI | POLLERR /* | POLLNVAL | POLLHUP*/)) {
case (POLLIN): // value of 1, There is data to read.case (POLLPRI): // value of 2, There is urgent data to readcase (POLLOUT): // , Writing now will not block.case (POLLIN | POLLPRI): // value of 3
iPollStatus = PollSuccess;
break;
// testing with a DragonBoard 410C indicates that we may// see the POLLERR indicator set in revents along with// the POLLIN and/or POLLPRI indicator set indicating there// is data to be read.// see as well poll(2) man page which states:// POLLERR Error condition (output only).case (POLLIN | POLLERR): // value of 9case (POLLPRI | POLLERR): // value of 10case (POLLIN | POLLPRI | POLLERR): // value of 11
iPollStatus = PollSuccess;
break;
case (POLLHUP): // , Hang up (output only).
iPollStatus = PollErrorPOLLHUP;
break;
case (POLLERR): // value of 8, Error condition (output only).
iPollStatus = PollErrorPOLLERR;
break;
case (POLLNVAL): // , Invalid request: fd not open (output only).
iPollStatus = PollErrorPOLLNVAL;
break;
case (POLLERR | POLLNVAL):
iPollStatus = PollErrorPOLLERRNVAL;
break;
default:
iPollStatus = PollErrorPOLLERRDEFLT;
break;
}
}
return iPollStatus;
}
intPollFileService::PollFileRead(constchar *pathName /* = nullptr */){
char buffer[12] = {0};
int iRet = -1;
if (fd < 0 && pathName) {
fd = open (pathName, O_RDONLY);
}
int nCount = read (fd, buffer, 10);
if (nCount > 0) {
iRet = atoi (buffer);
}
return iRet;
}
// Check the specified file using the poll(2) service and// return a status as follows:// - 0 -> poll(2) success indicating something is available// - 1 -> poll(2) failed with time out before anything available// - -1 -> poll(2) error - EFAULT// - -2 -> poll(2) error - EINTR// - -3 -> poll(2) error - EINVAL// - -4 -> poll(2) error - ENOMEM// - -5 -> poll(2) error - POLLERR// - -6 -> poll(2) error - POLLNVAL// - -7 -> poll(2) error - POLLERR | POLLNVAL// - -8 -> poll(2) error - POLLHUP// - -9 -> poll(2) error - poll(2) revent indicator Unknown// - -100 -> poll(2) error - Unknown error//staticint lastRevents = 0;
extern"C"JNIEXPORT jint JNICALL
Java_com_example_myapplication_MainActivity_pollFileWithTimeOut(JNIEnv* pEnv, jobject pThis, jstring pKey, jint timeMS){
char *pathName;
int timeMilliSec;
PollFileService myPoll;
constchar *str = pEnv->GetStringUTFChars(pKey, 0);
int timeMSint = 10000; // timeMS;#if 1int iStatus = myPoll.PollFileCheck(str, timeMSint);
#elseint iStatus = myPoll.PollFileRead(str);
#endif
pEnv->ReleaseStringUTFChars(pKey, str);
lastRevents = myPoll.iPollRevents;
return iStatus;
}
#if 0extern"C"JNIEXPORT jint JNICALL
Java_com_example_myapplication_MainActivity_pollGetLastStatus(JNIEnv* pEnv, jobject pThis){
return PollFileService::iPollStatus;
}
#endifextern"C"JNIEXPORT jint JNICALL
Java_com_example_myapplication_MainActivity_pollGetLastRevents(JNIEnv* pEnv, jobject pThis){
return lastRevents;
}
Post a Comment for "How To Use Poll(2) Or Select(2) Service Call To Watch A Pseudo File For Changes With Kotlin"