android随笔之HID设备遥控

提示:本文基于android 11.0讲解如何适配TV android系统常用的2.4G和蓝牙HID遥控设备



前言

HID(人体学接口设备 )是一个设备类定义,使用通用 USB 驱动程序。 在 HID 之前,设备只能对鼠标和键盘使用严格定义的协议(PS/2)。 硬件创新要求使用现有协议重载数据,或使用其自己的专用驱动程序创建非标准硬件。 HID 为这些“启动模式”设备提供了支持,同时通过可扩展、标准化且易于编程的接口添加对硬件创新的支持。

HID一开始为USB,但设计为与总线无关。 它为低延迟、低带宽设备而设计,但可以灵活地指定基础传输中的速率。 1996 年,基于 USB 的 HID 的规范被 USB-IF 批准。不久之后,对其他传输的支持又获得批准。如2.4G,蓝牙,I2C,GPIO,SPI等 。 此外,还允许通过自定义传输驱动程序进行特定于供应商的第三方传输。

目前,HID设备包括各种设备,例如字母数字显示器、条码读取器、扬声器/耳机上的音量控制、辅助显示器、传感器、2.4G遥控、蓝牙遥控等。 许多硬件供应商还对其专用设备使用 HID。

USB-IF HID 规范: link


1. HID遥控设备是什么?

它们通常长这样:
在这里插入图片描述
TV常用的HID遥控设备分两种:2.4G和蓝牙。

1.1 2.4G遥控

在这里插入图片描述
我们通常拿到这种HID设备,都会有一个说明书。里面会详细的定义每个按键的信息,这里截取了一部分。可以看到里面的每个按键都会定义一个hid page,一个hid code,和按键描述。

2.4G遥控都需要搭配usb接收端(俗称usb dongle)使用,即插即用。

1.2. 蓝牙遥控

在这里插入图片描述
蓝牙跟2.4G遥控,按键的HID这部分相似,每个按键都有一个hid page,一个hid id,一个按键描述。另外这个说明书还多了一个IR CODE,这个是红外协议的码值。说明书上面也有描述红外协议NEC,头码0x8890。

扫描二维码关注公众号,回复: 16993757 查看本文章

蓝牙遥控有的带usb dongle,有的不带。

为什么会有这种区别呢?
这里简单说下,蓝牙usb dongle有HCI模式和HID模式两种工作模式。在HCI工作模式中,dongle只是相当一个标准的HCI蓝牙usb dongle。在HID工作模式下,dongle是属于一个标准usb的HID设备,具有HID键盘、按键、鼠标等功能。
所以要看遥控厂商提供的usb dongle是在哪种工作模式,如果是HCI,那它就相当于一个usb蓝牙模块,需要配对后才能使用按键。如果是HID,那么它就相当于一个标准的HID设备,即插即用,无需适配。

2. HID遥控设备响应流程

linux HID协议
android input子系统之kl文件
android framework
遥控外设发码
scancode
keycode
apk实现对应keycode功能

3. linux HID协议

主要实现在kernel部分里的drivers/hid/hid-input.c

ps :这里插个题外话。如果我们拿到的android code,搜索这个hid-input.c文件,发现存在好几个kernel版本里。那么我们可以通过在对应平台系统里执行cat /proc/version命令查看当前平台使用的kernel版本,从而找到对应使用的文件。比如下面这个搜索到两个,我们在跑起来的平台下执行命令发现当前使用的是4.9版本,那么我们使用的文件就是第一个
在这里插入图片描述
在这里插入图片描述
回归正题,在HID协议里,hidinput_configure_usage()会先匹配HID_USAGE_PAGE(高8位),再匹配HID_USAGE(低8位)。
遥控规格书里的Usage Page或者Page ID就是HID_USAGE_PAGE,Usage ID或者HID code就是HID_USAGE。
TV目前常用的HID遥控设备HID_USAGE_PAGE基本都是HID_UP_KEYBOARD(0x07),HID_UP_CONSUMER(0x0c)这两种。

#define HID_USAGE_PAGE		0xffff0000
#define HID_UP_KEYBOARD		0x00070000
#define HID_UP_CONSUMER		0x000c0000
#define HID_USAGE		0x0000ffff
static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_field *field,
                     struct hid_usage *usage)
{
    
    
******
    switch (usage->hid & HID_USAGE_PAGE) {
    
    
******
    case HID_UP_KEYBOARD:
        set_bit(EV_REP, input->evbit);

        if ((usage->hid & HID_USAGE) < 256) {
    
    
            if (!hid_keyboard[usage->hid & HID_USAGE]) goto ignore;
            map_key_clear(hid_keyboard[usage->hid & HID_USAGE]);
        } else
            map_key(KEY_UNKNOWN);

        break;
******
    case HID_UP_CONSUMER:   /* USB HUT v1.12, pages 75-84 */
        set_bit(EV_REP, input->evbit);
        switch (usage->hid & HID_USAGE) {
    
    
        case 0x000: goto ignore;
        ******
        case 0x041: map_key_clear(KEY_SELECT);      break; /* Menu Pick */
        case 0x042: map_key_clear(KEY_UP);      break; /* Menu Up */
        case 0x043: map_key_clear(KEY_DOWN);        break; /* Menu Down */
        case 0x044: map_key_clear(KEY_LEFT);        break; /* Menu Left */
        case 0x045: map_key_clear(KEY_RIGHT);       break; /* Menu Right */
        ******
        case 0x076: map_key_clear(KEY_OPEN);       break;
        ******
        default: map_key_clear(KEY_UNKNOWN);
        }
        break;
******
    }
******
}

#define KEY_UNKNOWN 240
这里补充一下,后面会用到

3.1. HID USAGE PAGE之KEYBOARD(0x07)

从第3节的switch中,我们可以看到HID_UP_KEYBOARD判断HID_USAGE最终流向hid_keyboard[256]数组

#define unk KEY_UNKNOWN

static const unsigned char hid_keyboard[256] = {
    
    
      0,  0,  0,  0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38,
     50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44,  2,  3,
      4,  5,  6,  7,  8,  9, 10, 11, 28,  1, 14, 15, 57, 12, 13, 26,
     27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64,
     65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106,
    105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71,
     72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190,
    191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113,
    115,114,unk,unk,unk,121,unk, 89, 93,124, 92, 94, 95,unk,unk,unk,
    122,123, 90, 91, 85,unk,unk,unk,unk,unk,unk,unk,111,unk,unk,unk,
    unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,
    unk,unk,unk,unk,unk,unk,179,180,unk,unk,unk,unk,unk,unk,unk,unk,
    unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,
    unk,unk,unk,unk,unk,unk,unk,unk,111,unk,unk,unk,unk,unk,unk,unk,
     29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113,
    150,158,159,128,136,177,178,176,142,152,173,140,unk,unk,unk,unk
};

这个表的规则是,序号从0开始,HID_USAGE高位代表行,低位代表列。
比如规格书里的一个按键对应Usage ID 0x76,即是表中第8行第7列。或者把0x76换算成十进制118,即是hid_keyboard[118]的值,只是这样不太方便查。
(注意,在这里查的前提是这个按键的Usage Page是0x07,07才是调用HID_UP_KEYBOARD)

这个数组里的数字我们一般叫scancode,它的定义通常都在linux kernel里的input.h文件里。如果这个按键的HID_USAGE查到是对应数组里的unk位置,也就是KEY_UNKNOWN,即240,则代表这个HID_USAGE未做过适配。我们需要把数组里对应位置的unk修改为我们想要的scancode即可。如何修改scancode,第4节将进行讲解。

3.2. HID USAGE PAGE之CONSUMER(0x0c)

从第3节的switch中,我们可以看到HID_UP_CONSUMER里还嵌套了一个switch,用于判断HID_USAGE。这个就比较简单了,我们添加的时候按顺序从上到下递增,后面检查也方便。如果这个按键HID_USAGE没有匹配到,那么则抛出KEY_UNKNOWN,即240,则代表这个HID_USAGE未做过适配。我们需要按顺序在合理的位置添加上对应的case HID_USAGE: map_key_clear(scancode);即可。

4. scancode

scancode都定义在linux kernel里的include/uapi/linux/input.h文件

HID为什么会使用到这个文件呢?这里简单画个kernel流程图

#include <linux/hid.h>
#include <linux/input.h>
#include <uapi/linux/input.h>
扩展自定义#include
drivers/hid/hid-input.c
include/linux/hid.h
include/linux/input.h
include/uapi/linux/input.h
扩展自定义文件

input.h这里,我们也可以做一些扩展
比如在input.h里

#include "input-event-codes.h"

或者

#include "key_define.h"

就可以把scancode定义在这些扩展文件里了。
如果是一些新加入的芯片厂商,大家都知道该怎么找他们使用的客制化scancode了吧。
这里也附上一些scancode定义,定义格式如下:

******
#define KEY_STOP        128 /* AC Stop */
#define KEY_AGAIN       129
#define KEY_PROPS       130 /* AC Properties */
#define KEY_UNDO        131 /* AC Undo */
#define KEY_FRONT       132
#define KEY_COPY        133 /* AC Copy */
#define KEY_OPEN        134 /* AC Open */
#define KEY_PASTE       135 /* AC Paste */
#define KEY_FIND        136 /* AC Search */
#define KEY_CUT         137 /* AC Cut */
#define KEY_HELP        138 /* AL Integrated Help Center */
#define KEY_MENU        139 /* Menu (show menu) */
#define KEY_CALC        140 /* AL Calculator */
******

所以,我们在第3节的两个小节中,如果查到该按键匹配的HID_USAGE是KEY_UNKNOWN,那么我们在这里找一个我们想要的KEY定义或者新增一个define,然后07 page是在hid_keyboard[256]数组里填上10进制数字,0c page是在switch里加上对应case HID_USAGE: map_key_clear(KEY_XXX);

5. android input子系统之kl文件

scancode是通过input子系统映射到对应设备节点使用的KeyLayoutFile文件,即kl文件

input子系统详细原理这里就不做介绍了,有兴趣的同学可以在网上搜索下,一大把资料
这里也推荐一篇
AndroidR Input子系统(5)解析“.kl“文件
Android Framework 输入子系统(04)InputReader解读

5.1 getevent命令获取当前设备节点,HID page,HID code及scancode

先在平台上执行getevent命令,然后按下任意遥控按键会刷出几行打印,最前面就是当前遥控设备使用的节点,比如下面这个案例使用的是/dev/input/event4,该按键HID page是0x0c,HID code是0x76,scancode是0xf0

这里注意核对读到按键的HID page和HID code是否与规格书写的一致,不一致则要联系遥控厂商

在这里插入图片描述注意,scancode 0xf0=240,代表unknown,说明这个按键的HID code没有在hid-input.c里有对应的scancode关系。按照第4节的思路进行添加或者修改,在hid-input.c的0c page也就是CONSUMER使用的switch位置处,按顺序增加定义:
case 0x076: map_key_clear(KEY_OPEN); break;
再比如下面这两个,就已经有对应的scancode了,无需修改hid-input.c文件
在这里插入图片描述在这里插入图片描述

5.2 dumpsys input查看对应设备节点使用的kl文件

然后在平台上执行dumpsys input命令,有一大堆的打印出来。我们需要找到对应节点使用的KeyLayoutFile地方
在这里插入图片描述可以看到,读到了当前遥控设备的vendor product,使用的KeyLayoutFile和KeyCharacterMapFile。
KeyCharacterMapFile,即kcm文件,本篇就不做介绍了,tv的话用的相对较少。有兴趣的同学可以读读这篇文章,介绍的比较详细。弄懂规则后也比较简单。
AndroidR Input子系统(6)解析“.kcm“文件
这里说下,如果我们是新的HID设备,读到的KeyLayoutFile都是调用android默认的/vendor/user/keylayout/Generic.kl,因为没有对新设备的vendor product做过适配。
那怎么适配kl呢?
其实也比较简单,在对应的路径下放置对应vendor product的kl文件,命名格式是Vendor_xxxx_Product_xxxx.kl,所有的kl文件内容格式都是长这样的:

key 8     7
key 9     8
key 10    9
key 11    0
key 12    MINUS
key 13    EQUALS
key 14    DEL
key 15    TAB

格式:key arg1 arg2
arg1:取值scancode对应的10进制值
arg2:取值KeyCodeLabel或者KeyCodeLabel对应的10进制值

举例:第5.1小节中的按键HID page是0x0c,HID code是0x76,那么我们先在hid-input.c里找到0c page也就是CONSUMER使用的switch位置,0x76对应的是KEY_OPEN,KEY_OPEN在input.h里找到定义的数值是134,那么我们就可以写key 134 OPEN,这个OPEN字符串的来源在第6节会讲解
添加的kl可以放在/vendor/user/keylayout/里,也可以放在/system/user/keylayout/里,如果这两个路径下存在同名的kl文件,会优先调用vendor路径下的。
上面读到的vendor=0x005d product=0x0002,我们可以放在/vendor/user/keylayout/Vendor_005d_Product_0002.kl,修改重启系统后就会调用我们这个对应的kl文件
在这里插入图片描述如果要加到android源码code里,kl一般会放在frameworks/base/data/keyboards/路径下,这个路径下的kl是跑在系统的/system/usr/keylayout/路径里。另外芯片厂商会有自己的客制化kl文件,一般是跑在系统的/vendor/usr/keylayout/路径里以保证高优先级,我们也可以把新kl加芯片厂商的客制化路径下。

6. keycode

这里也简单画下kl中的KeyCodeLabel是怎么转成apk可识别的keycode的

frameworks/native/include/input/InputEventLabels.h
frameworks/native/include/android/keycodes.h
frameworks/base/core/java/android/view/keyEvent.java
KeyCodeLabel XXX
AKEYCODE_XXX
value
KEYCODE_XXX

涉及到的3个文件,格式都是统一的,比如
在这里插入图片描述如果要新增一个,记得这3个文件都要加上,value要对应上。

7. apk实现keycode功能

apk在监听按键事件处理中,就可以对该KEYCODE_XXX或者value数值进行相关功能实现了。


总结

通过上面的分析,我们知道了整个流程。那么我们拿到一个未添加过的新HID设备,整个思路应该是这样的

Yes
No
2.4G遥控先插上dongle
带HID工作模式蓝牙dongle遥控先插上dongle
带HCI工作模式蓝牙dongle遥控先进行配对连接
不带dongle蓝牙遥控先进行配对连接
getevent命令获取设备节点, HID page, HID code及scancode
该scancode是否为240
添加/修改hid-input.c对应HID page下的该HID code与scancode对应关系
dumpsys input命令确认设备节点使用的kl文件
创建或者修改对应路径下的kl文件, 添加对应scancode与KeyCodeLabel映射关系
apk实现对应keycode的功能
系统重启验证

遇到的问题----KeyLayoutFile调用为空

在这里插入图片描述什么原因呢?
有两种情况会造成这种现象

  1. 错误的未知关键词
    比如这样
    在这里插入图片描述未知的ket关键词,kl文件内容格式只有1个关键词,那就是key
  2. 错误的未知KEYCODE
    比如上面的MZHE没有在第6节中的3个文件中进行定义

猜你喜欢

转载自blog.csdn.net/hmz0303hf/article/details/128712660