我正在采用的方法是创建一个原生 C++ JNI 函数来提供一种实现poll(2) Linux 服务调用的方法。
我在开发和测试过程中遇到的一个有趣问题是poll()
立即返回,而不是等待超时或 GPIO 输入引脚的电压。在 DragonBoard 410C 的 96Boards.org 论坛上发帖后,How to use poll() with sysfs interface to input GPIO pin to handle a switch press event之后,有人提出了一个可行的解决方案,在开始投票之前读取伪文件(2)。
为了使用这个功能,我需要某种 Kotlin 协程或侧线程,这样当主 UI 正在处理开始轮询 GPIO 输入引脚的按钮单击时,主 UI 线程不会被阻塞,直到函数返回 GPIO 事件或超时。
我还不能辨别如何做这样的协程,所以这仍然是一项正在进行的工作。经过一番思考,似乎某种事件侦听器架构将是最合适的方法。
然而,测试表明该功能pollPseudoFile()
可以正常工作,方法是超时或返回一个值,该值/value
是使用从 1.8v 电源(引脚 38)到设置为 GPIO 输入引脚的导线手动施加电压时的值a rising
or伪文件falling
中的设置。/edge
Native C++ JNI 函数的源代码如下。我将它与以下 Kotlin 源代码一起使用。
首先,在我的MainActivity.kt
源文件中,我使用以下源提供了 Native C++ 库:
// See the StackOverFlow question with answer at URL:
// https://stackoverflow.com/questions/36932662/android-how-to-call-ndk-function-from-kotlin
init {
System.loadLibrary("pollfileservice")
}
external fun pollFileWithTimeOut(pathPseudo : String, timeOutMs : Int): Int
external fun pollGetLastRevents() : Int
接下来我在 Kotlin 源文件中使用这个函数Gpio.kt
来实际对伪文件执行poll()
服务调用。
class Gpio(pin: Int) {
private val pin : Int
private val 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".
*/
private fun MakeFileName(pin: Int, op: String): String {
return "/sys/class/gpio/gpio$pin$op"
}
// ....... other source code in the Kotlin class Gpio
fun pinPoll (timeMs: Int) : Int {
val iStatus : Int = pinGpio.pollPseudoFile (MakeFileName(pin, "/value"), timeMs)
return iStatus
}
上面的 Gpio 类在实际的 UI 按钮点击监听器中使用如下:
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
轮询文件服务.h
//
// Created by rchamber on 9/24/2020.
//
#ifndef MY_APPLICATION_POLLFILESERVICE_H
#define MY_APPLICATION_POLLFILESERVICE_H
class PollFileService {
private:
int iValue;
int fd; /* file descriptor */
public:
// See poll(2) man page at https://linux.die.net/man/2/poll
static const int PollSuccess = 0;
static const int PollTimeOut = 1;
static const int PollErrorEFAULT = -1;
static const int PollErrorEINTR = -2;
static const int PollErrorEINVAL = -3;
static const int PollErrorENOMEM = -4;
static const int PollErrorPOLLERR = -5;
static const int PollErrorPOLLNVAL = -6;
static const int PollErrorPOLLERRNVAL = -7;
static const int PollErrorPOLLHUP = -8;
static const int PollErrorPOLLERRDEFLT = -9;
static const int PollErrorUNKNOWN = -100;
static int iPollStatus;
static int iPollRet;
static int iPollRevents;
PollFileService(const char *pathName = nullptr, int timeMilliSec = -1);
~PollFileService();
int PollFileCheck (const char *pathName, int timeMilliSec = -1);
int PollFileRead (const char *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
轮询文件服务.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(const char *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;
}
}
int PollFileService::PollFileCheck(const char *pathName, int timeMilliSec /* = -1 */)
{
struct pollfd fdList[] = {
{fd, POLLPRI | POLLERR, 0},
{0}
};
nfds_t nfds = 1;
unsigned char 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;
} else if (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;
}
} else if (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 read
case (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 9
case (POLLPRI | POLLERR): // value of 10
case (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;
}
int PollFileService::PollFileRead (const char *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
//
static int 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;
const char *str = pEnv->GetStringUTFChars(pKey, 0);
int timeMSint = 10000; // timeMS;
#if 1
int iStatus = myPoll.PollFileCheck(str, timeMSint);
#else
int iStatus = myPoll.PollFileRead(str);
#endif
pEnv->ReleaseStringUTFChars(pKey, str);
lastRevents = myPoll.iPollRevents;
return iStatus;
}
#if 0
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_myapplication_MainActivity_pollGetLastStatus (JNIEnv* pEnv, jobject pThis) {
return PollFileService::iPollStatus;
}
#endif
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_myapplication_MainActivity_pollGetLastRevents (JNIEnv* pEnv, jobject pThis)
{
return lastRevents;
}