某项目中,需要实现屏幕的自动翻转,即通过读取gsensor实时上报的坐标数据,来做一些逻辑判断。这方面 android已经早已实现,网上搜下即可。安卓主要是通过方向和加速度的判断来实现翻转。手机翻转可参考 https://blog.csdn.net/yun_hen/article/details/78799172
而本项目的翻转策略跟手机不同。
本文简单实现gsensor的数据读取,具体的自动翻转策略需要自己实现。
什么是输入设备?输入设备其实就是能够产生输入事件的设备就称为输入设备,常见的输入设备包括鼠标、键盘、触摸屏、按钮等等,它们都能够产生输入事件,产生输入数据给计算机系统。
对于输入设备的应用编程其主要是获取输入设备上报的数据、输入设备当前状态等,譬如获取触摸屏当前触摸点的X、Y轴位置信息以及触摸屏当前处于按下还是松开状态。
本章将会讨论如下主题内容。
先来了解什么是输入设备(也称为input设备),常见的输入设备有鼠标、键盘、触摸屏、遥控器、电脑画图板等,用户通过输入设备与系统进行交互。
由上面的介绍可知,输入设备种类非常多,每种设备上报的数据又不一样,那么Linux系统如何管理呢?Linux系统为了统一管理这些输入设备,实现了一套能够兼容所有输入设备的框架,那么这个框架就是input子系统。驱动开发人员基于input子系统开发输入设备的驱动程序,input子系统可以屏蔽硬件的差异,向应用层提供一套统一的接口。
基于input子系统注册成功的输入设备,都会在/dev/input目录下生成对应的设备节点(设备文件),通过读取这些设备节点可以获取输入设备上报的数据。
如果我们要读取触摸屏的数据,假设触摸屏设备对应的设备节点为/dev/input/event0,那么数据读取流程如下:
当无数据可读时,程序会进入休眠状态(也就是阻塞),譬如应用程序读触摸屏,当前我们并没有去触摸触摸屏,所以自然无数据可读;当我们用手指触摸触摸屏时,此时就有数据可读了,应用程序会被唤醒,成功读取到数据。那么对于其它输入设备亦是如此,无数据可读时应用程序会进入休眠状态,当然我们这里说的是在阻塞式I/O方式下,当有数据可读时才会被唤醒。
首先我们要知道,应用程序打开输入设备的设备文件,向其发起读操作,那么这个读操作获取到的是什么样的数据呢?其实每一次read操作获取的都是一个struct input_event结构体类型数据,该结构体定义在
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
结构体中的time成员变量是一个struct timeval类型的变量,该结构体在前面给大家介绍过,每个输入事件struct input_event中都包含了其发生的时间,就通过这个struct timeval类型的变量time来描述。时间参数通常不是很重要,而其它3个成员变量type、code、value更为重要。
type:type用于描述发生了哪类事件,Linux系统所支持的输入事件类型如下所示:
/*
* Event types
*/
#define EV_SYN 0x00 //同步事件,用于分隔事件
#define EV_KEY 0x01 //按键事件
#define EV_REL 0x02 //相对位移事件(譬如鼠标)
#define EV_ABS 0x03 //绝对位移事件(譬如触摸屏)
#define EV_MSC 0x04 //其它杂类
#define EV_SW 0x05
#define EV_LED 0x11
#define EV_SND 0x12
#define EV_REP 0x14
#define EV_FF 0x15
#define EV_PWR 0x16
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)
以上这些宏定义也是在
code:code表示该类事件下的哪一个具体的事件,每一种事件类型都有对应的一系列事件。不同的事件类型所包含的事件是不同的,譬如对于按键事件来说,一个键盘上有很多的按键,譬如字母A、B、C、D或者数字1、2、3、4等,这每一个不同的按键都会对应一个具体的事件,如下所示:
#define KEY_RESERVED 0
#define KEY_ESC 1 //ESC键
#define KEY_1 2 //数字1键
#define KEY_2 3 //数字2键
#define KEY_TAB 15 //TAB键
#define KEY_Q 16 //字母Q键
#define KEY_W 17 //字母W键
#define KEY_E 18 //字母E键
#define KEY_R 19 //字母R键
……
相对位移事件的code值
#define REL_X 0x00 //X轴
#define REL_Y 0x01 //Y轴
#define REL_Z 0x02 //Z轴
#define REL_RX 0x03
#define REL_RY 0x04
#define REL_RZ 0x05
#define REL_HWHEEL 0x06
#define REL_DIAL 0x07
#define REL_WHEEL 0x08
#define REL_MISC 0x09
#define REL_MAX 0x0f
#define REL_CNT (REL_MAX+1)
绝对位移事件的code值
譬如对于触摸屏设备来说,一个触摸点有X轴坐标和Y轴坐标,甚至一个触摸点还有压力值等这些信息,这每一种信息都会对应一个具体的事件,也就是code值。
#define ABS_X 0x00 //X轴
#define ABS_Y 0x01 //Y轴
#define ABS_Z 0x02 //Z轴
#define ABS_RX 0x03
#define ABS_RY 0x04
#define ABS_RZ 0x05
#define ABS_THROTTLE 0x06
#define ABS_RUDDER 0x07
#define ABS_WHEEL 0x08
#define ABS_GAS 0x09
#define ABS_BRAKE 0x0a
#define ABS_HAT0X 0x10
#define ABS_HAT0Y 0x11
#define ABS_HAT1X 0x12
#define ABS_HAT1Y 0x13
#define ABS_HAT2X 0x14
#define ABS_HAT2Y 0x15
#define ABS_HAT3X 0x16
#define ABS_HAT3Y 0x17
#define ABS_PRESSURE 0x18
#define ABS_DISTANCE 0x19
#define ABS_TILT_X 0x1a
#define ABS_TILT_Y 0x1b
#define ABS_TOOL_WIDTH 0x1c
......
除了以上列举出来的之外,还有很多,大家可以自己浏览
value:value表示事件值,对value值的解释随着code类型的变化而变化。譬如对于按键事件来说,如果code表示按键1,那么value等于1表示按键1按下,value等于0表示按键1被松开,如果value等于2则表示按键被长按。譬如在绝对位移事件中,如果code表示X轴坐标ABS_X,那么value值就表示触摸点在X轴坐标的位置。所以对value值的解释需要根据不同的code类型而定!
上面我们提到了同步事件EV_SYN,同步事件用于对不同事件进行分隔、实现同步操作。应用程序读取输入设备的数据时,一次read操作只能读取一个struct input_event类型数据,譬如对于触摸屏来说,一个触摸点会上报X轴坐标位置、Y轴坐标位置,还有可能会上报压力值,对于这样情况,那么应用程序需要执行3次read操作才能把一个触摸点上报的数据全部读取到;这还只是单个触摸点的情况,如果触摸屏设备支持多点触摸,同一时间触摸屏上有多个触摸点,那么程序当中需要把所有触摸点的数据读取出来,这样才算完整。
那么应用程序如何知道已经读取到完整数据了呢?其实就是通过同步事件来实现的,驱动程序中将完整数据上报完之后(譬如触摸点的X轴、Y轴以及压力等之类的数据),会上报一个同步事件,以告知应用程序数据已经完整、进行同步。
同步事件类型也包含了多种不同的事件,也就是code值,如下所示:
/*
* Synchronization events.
*/
#define SYN_REPORT 0
#define SYN_CONFIG 1
#define SYN_MT_REPORT 2
#define SYN_DROPPED 3
#define SYN_MAX 0xf
#define SYN_CNT (SYN_MAX+1)
上报的同步事件通常是第一个SYN_REPORT(code值),而value值通常为0。对于同步事件,应用程序通常不需要去管是哪个具体的同事事件,只需知道type等于EV_SYN则表示数据完整了。
项目中还需要判断坐标的阈值,方向等。但是数据采集的核心代码如下:
class GsensorHandler : public QObject
{
Q_OBJECT
public:
explicit GsensorHandler(QObject *parent = nullptr);
~GsensorHandler();
private slots:
void readSensorData();
signals:
void sensorData(int x,int y,int z);
private:
QSocketNotifier * m_notify;
int m_fd;
int m_x, m_y=0,m_z;
};
GsensorHandler::GsensorHandler(QObject *parent) : QObject(parent)
{
QByteArray device = QByteArrayLiteral("/dev/input/event0");
m_fd = open(device.constData(), O_RDONLY);
if (m_fd >= 0) {
qInfo() << "open file success";
m_notify = new QSocketNotifier(m_fd, QSocketNotifier::Read, this);
connect(m_notify, SIGNAL(activated(int)), this, SLOT(readSensorData()));
} else {
qErrnoWarning(errno, "Gsensor: Cannot open input device %s", device.constData());
}
}
GsensorHandler::~GsensorHandler()
{
if (m_fd >= 0)
close(m_fd);
}
void GsensorHandler::readSensorData()
{
struct input_event in_ev = {0};
for ( ; ; ) {
/* 循环读取数据 */
if (sizeof(struct input_event) !=
read(m_fd, &in_ev, sizeof(struct input_event))) {
perror("read error");
exit(-1);
}
switch (in_ev.type) {
case EV_ABS: //绝对位移事件
{
if (ABS_X == in_ev.code) //X轴
m_x = in_ev.value; //保存X轴坐标位置
else if (ABS_Y == in_ev.code)//Y轴
m_y = in_ev.value; //保存Y轴坐标位置
else if (ABS_Z == in_ev.code)//Z轴
m_z = in_ev.value; //保存Z轴坐标位置
emit sensorData(m_x,m_y,m_z);
}
}
}
}
效果如图: