蓝牙键鼠 HID Reports

一. 引言

 蓝牙是一种短距的无线通讯技术,可实现固定设备、移动设备之间的数据交换。一般将蓝牙3.0之前的BR/EDR蓝牙称为传统蓝牙,而将蓝牙4.0规范下的蓝牙BLE(BluetoohLow Energy)称为低功耗蓝牙。 (BLE)连接都是建立在 GATT (Generic Attribute Profile) 协议之上。GATT 是一个在蓝牙连接之上的发送和接收很短的数据段的通用规范,这些很短的数据段被称为属性(Attribute)。  3.0 &4.0 4.0&5.0区别  
 

Bluetooth的一个很重要特性,就是所有的Bluetooth产品都无须实现全部的Bluetooth规范。为了更容易的保持Bluetooth设备之间的兼容,Bluetooth规范中定义了Profile。Profile定义了设备如何实现一种连接或者应用,你可以把Profile理解为连接层或者应用层协议。

从 Android 3.0 开始,Bluetooth API 便支持使用蓝牙配置文件) 

一、Profile

1.1 常用的profile介绍

请参考“蓝牙Profile的概念和常见种类”,几种种最基本的配置文件为:常见种类 在android源码中的体现  

 

1.通用访问配置文件(Generic Access Profile, GAP)   GAP&&GATT https://www.cnblogs.com/yongdaimi/p/11507397.html GAP&&GATT 

GAP是所有其他配置文件的https://www.cnblogs.com/yongdaimi/p/11507397.html基础,它定义了在蓝牙设备间建立基带链路的通用方法.除此之外,GAP还定义了下列内容:

   ① 必须在所有蓝牙设备中实施的功能
   ② 发现和链接设备的通用步骤
   ③ 基本用户界面术语.

它在用来控制设备连接和广播。GAP 使你的设备被其他设备可见,并决定了你的设备是否可以或者怎样与合同设备进行交互。例如 Beacon 设备就只是向外广播,不支持连接,小米手环就等设备就可以与中心设备连接。

2.服务发现应用配置文件(Service Discovery Application Profile, SDAP)
  

SDAP描述了应用程序如何使用SDP发现远程设备上的服务.由于GAP的要求,任何蓝牙设备都应能够连接至其他蓝牙设备.基于此,SDAP要求任何应用程序都应当能够发现它要连接的其他蓝牙设备上的可用服务.此配置文件可承担搜索已知和特定服务及一般的任务.SDAP涉及了称为“服务发现用户应用程序”的一个应用程序,这是蓝牙设备查找服务所必需的.此应用程序可与向/从其他蓝牙设备发送/接收服务查询的SDP相接.SDAP依赖于GAP,并可以重新使用部分GAP.

BluetoothHidDevice框架在应用注册期间添加了SDP记录,这样Android设备可以被发现为蓝牙HID设备。  (BluetoothHidDeviceAppSdpSettings)

3.串行端口配置文件(Serial Port Profile, SPP)
  

SPP定义了如何设置虚拟串行端口及如何连接两个蓝牙设备.SPP基于ETSI TS 07.10规格,使用RFCOMM协议提供串行商品仿真.SPP提供了以无线方式替代现有的RS-232串行通信应用程序和控制信号的方法.SPP为DUN,FAX,HSP和LAN配置文件提供了基础.此配置文件可以支持最高128kb/s的数据率.SPP依赖于GAP.

4.通用对象交换配置文件(Generic Object Exchange Profile, GOEP)
 

GOEP可用于将对象从一个设备传输到另一个设备.对象可以是任意的.如:图片,文档,名片等.此配置文件定义了两个角色:提供拉提或推送对象位置的服务器及启动操作的客户端.使用GOEP的应用程序假定链路和信道已按GAP的定义建立.GOEP依赖于串行端口配置文件.
GOEP为使用OBEX协议的其他配置文件提供了通用蓝图,并为设备定义了客户端和服务器角色.对于所有的OBEX事务.GOEP规定应由客户端启动所有事务.但是此配置文件并没有描述应用程序就如何定义要交换的对象或如何实施交换.这些细节留给属于GOEP的配置文件.即OPP,FTP和SYNC去完成.通常使用此配置文件的蓝牙设备为笔记本电脑,PDA,手机及智能电话.

注意:蓝牙1.1版本规范所有蓝牙设备的最小实现必须支持通用访问配置文件,服务发现应用配置文件和串行端口配置文件.

在两台电脑或者Labtop之间就可以建立这种连接,如下图所示: 

    SPP是基于RFCOMM的,spp 协议处于rfcomm的上层,spp的应用需走rfcomm层。如果你使用RFCOMM能够实现,那么也就不需要使用SPP,而却速度还会比SPP来做快,因为省略了采用profile的一些数据包头等。不过,还是推荐采用SPP来做,兼容性有保证,这也是为什么蓝牙本质上数据和语音的传送却出现HFP,HSP,SPP,OPP等诸多具体应用profile的原因。

1.2蓝牙profile框架

每个attribute属性被UUID(通用唯一标识符)唯一标识 ,UUID是标准128-bit格式的ID用来唯一标识信息。attributes 被 ATT 格式化characteristics和services形式进行传送。

特征(Characteristics)— 一个characteristics包含一个单独的value值和0 –n个用来描述characteristic 值(value)的descriptors。一个characteristics可以被认为是一种类型的,类似于一个类。

描述符(descriptor)—descriptor是被定义的attributes,用来描述一个characteristic的值。例如,一个descriptor可以指定一个人类可读的描述中,在可接受的范围里characteristic值,或者是测量单位,用来明确characteristic的值。

服务(service)—service是characteristic的集合。例如,你可以有一个所谓的“Heart RateMonitor”service,其中包括characteristic,如“heart rate measurement ”。你可以在 bluetooth.org 找到关于一系列基于GATT的profile和service。

如上图所示:蓝牙设备可以包括多个Profile,一个Profile中有多个Service,一个Service中有多个Characteristic,一个Characteristic中包括一个value和多个Descriptor ()。

描述符 HID_v1.1.1.pdf    from  bluetooth.org   描述HID_USB 

BLE HID规范是以USB HID规范为基础的,HID设备,即人机交互设备,常见的有鼠标,键盘,游戏手柄,等等。

二、HID Reports

Bluetooth HID devices 支持三种Report:Input, Output, Feature。


Input Reports,输入报告
Ble 中,表示 Bluetooth HID device 发送数据给 Bluetooth HID Host.
USB 中输入报告通常通过 中断输入端点来传输。当然也可以通过 控制端点由 HOST 使用 GET REPORT 控制传输请求来获取数据,即host 先发送 get report 命令,device 随后回复 input report,之后 host 会回复一个状态(0 字节数据表示成功)。
Output Reports,输出报告
Ble 中表示 Bluetooth HID Host.发送数据给 Bluetooth HID device.
USB 中输出报告通常通过 中断输出端点来传输。当然也可以通过 控制端点由 HOST 使用 SET REPORT 控制传输请求来发出数据,即HOST 先发送 set report 命令,随后 HOST 发送待发送的数据,最后 DEVICE 回复一个状态(0 字节表示成功)。
Feature Reports,特征报告
Ble 中双向数据通道
USB 中只能通过控制端点来传输双向数据, HOST 使用 GET REPORT 来从 DEVICE 获取数据,或者 HOST 使用 SET REPORT 发送数据给 DEVICE。

 HID报文描述符的结构

下图引用开源项目中的 蓝牙键鼠 输入报告描述    (结合 HID_v1.1.1.pdf  &描述HID_USB  & HID Usage表 查看) HID描述符 由各种  描述符宏  表述 

BluetoothHidDevice.SUBCLASS1_COMBO  
BluetoothHidDeviceAppSdpSettings(
            "Pixel HID1",
            "Mobile BController",
            "bla",
            BluetoothHidDevice.SUBCLASS1_COMBO,
            DescriptorCollection.MOUSE_KEYBOARD_COMBO
        )
val MOUSE_KEYBOARD_COMBO = byteArrayOf(
        //MOUSE TLC
        0x05.toByte(), 0x01.toByte(),                         // USAGE_PAGE (Generic Desktop) 
        0x09.toByte(), 0x02.toByte(),                         // USAGE (Mouse)

        0xa1.toByte(), 0x01.toByte(),                         // COLLECTION (Application)
        0x05.toByte(), 0x01.toByte(),                         // USAGE_PAGE (Generic Desktop)
        0x09.toByte(), 0x02.toByte(),                         // USAGE (Mouse)
        0xa1.toByte(), 0x02.toByte(),        //       COLLECTION (Logical) 集合开始

        0x85.toByte(), 0x04.toByte(),               //   REPORT_ID (Mouse)
        0x09.toByte(), 0x01.toByte(),                         //   USAGE (Pointer)
        0xa1.toByte(), 0x00.toByte(),                         //   COLLECTION (Physical)
        0x05.toByte(), 0x09.toByte(),                         //     USAGE_PAGE (Button)   //左键右键定义
        0x19.toByte(), 0x01.toByte(),                         //     USAGE_MINIMUM (Button 1)
        0x29.toByte(), 0x02.toByte(),                         //     USAGE_MAXIMUM (Button 2)
        0x15.toByte(), 0x00.toByte(),                         //     LOGICAL_MINIMUM (0)
        0x25.toByte(), 0x01.toByte(),                         //     LOGICAL_MAXIMUM (1)
        0x75.toByte(), 0x01.toByte(),                         //     REPORT_SIZE (1)
        0x95.toByte(), 0x02.toByte(),                         //     REPORT_COUNT (2)  //2 bit
        0x81.toByte(), 0x02.toByte(),                         //     INPUT (Data,Var,Abs)报文的类型,
        0x95.toByte(), 0x01.toByte(),                         //     REPORT_COUNT (1) 数据个数 
        0x75.toByte(), 0x06.toByte(),                         //     REPORT_SIZE (6)  数据大小
        0x81.toByte(), 0x03.toByte(),                         //     INPUT (Cnst,Var,Abs) ;Add field to the input report
        0x05.toByte(), 0x01.toByte(),                         //     USAGE_PAGE (Generic Desktop)
        0x09.toByte(), 0x30.toByte(),                         //     USAGE (X) 
        0x09.toByte(), 0x31.toByte(),                         //     USAGE (Y)
        0x16.toByte(), 0x01.toByte(),0xf8.toByte(),                         //     LOGICAL_MINIMUM (-2047)
        0x26.toByte(), 0xff.toByte(),0x07.toByte(),                         //     LOGICAL_MAXIMUM (2047)
        0x75.toByte(), 0x10.toByte(),                         //     REPORT_SIZE (16)
        0x95.toByte(), 0x02.toByte(),                         //     REPORT_COUNT (2)  //2x2 四个字节
        0x81.toByte(), 0x06.toByte(),                         //     INPUT (Data,Var,Rel) //相对位置

        0xa1.toByte(), 0x02.toByte(),        //       COLLECTION (Logical)
        0x85.toByte(), 0x06.toByte(),               //   REPORT_ID (Feature)
        0x09.toByte(), 0x48.toByte(),        //         USAGE (Resolution Multiplier)

        0x15.toByte(), 0x00.toByte(),        //         LOGICAL_MINIMUM (0)
        0x25.toByte(), 0x01.toByte(),        //         LOGICAL_MAXIMUM (1)
        0x35.toByte(), 0x01.toByte(),        //         PHYSICAL_MINIMUM (1)
        0x45.toByte(), 0x04.toByte(),        //         PHYSICAL_MAXIMUM (4)
        0x75.toByte(), 0x02.toByte(),        //         REPORT_SIZE (2)
        0x95.toByte(), 0x01.toByte(),        //         REPORT_COUNT (1)

        0xb1.toByte(), 0x02.toByte(),        //         FEATURE (Data,Var,Abs)


        0x85.toByte(), 0x04.toByte(),               //   REPORT_ID (Mouse)
        //0x05.toByte(), 0x01.toByte(),                         //     USAGE_PAGE (Generic Desktop)
        0x09.toByte(), 0x38.toByte(),        //         USAGE (Wheel)

        0x15.toByte(), 0x81.toByte(),        //         LOGICAL_MINIMUM (-127)
        0x25.toByte(), 0x7f.toByte(),        //         LOGICAL_MAXIMUM (127)
        0x35.toByte(), 0x00.toByte(),        //         PHYSICAL_MINIMUM (0)        - reset physical
        0x45.toByte(), 0x00.toByte(),        //         PHYSICAL_MAXIMUM (0)
        0x75.toByte(), 0x08.toByte(),        //         REPORT_SIZE (8)                 //1字节 6个了 滚轮scroll 有符号数
        0x95.toByte(), 0x01.toByte(),                         //     REPORT_COUNT (1)
        0x81.toByte(), 0x06.toByte(),                         //     INPUT (Data,Var,Rel)
        0xc0.toByte(),              //       END_COLLECTION

        0xa1.toByte(), 0x02.toByte(),        //       COLLECTION (Logical)
        0x85.toByte(), 0x06.toByte(),               //   REPORT_ID (Feature)
        0x09.toByte(), 0x48.toByte(),        //         USAGE (Resolution Multiplier)

        0x15.toByte(), 0x00.toByte(),        //         LOGICAL_MINIMUM (0)
        0x25.toByte(), 0x01.toByte(),        //         LOGICAL_MAXIMUM (1)
        0x35.toByte(), 0x01.toByte(),        //         PHYSICAL_MINIMUM (1)
        0x45.toByte(), 0x04.toByte(),        //         PHYSICAL_MAXIMUM (4)
        0x75.toByte(), 0x02.toByte(),        //         REPORT_SIZE (2)
        0x95.toByte(), 0x01.toByte(),        //         REPORT_COUNT (1)

        0xb1.toByte(), 0x02.toByte(),        //         FEATURE (Data,Var,Abs)

        0x35.toByte(), 0x00.toByte(),        //         PHYSICAL_MINIMUM (0)        - reset physical
        0x45.toByte(), 0x00.toByte(),        //         PHYSICAL_MAXIMUM (0)
        0x75.toByte(), 0x04.toByte(),        //         REPORT_SIZE (4)
        0xb1.toByte(), 0x03.toByte(),        //         FEATURE (Cnst,Var,Abs)



        0x85.toByte(), 0x04.toByte(),               //   REPORT_ID (Mouse)         //显示鼠标 图标位图 bitmap
        0x05.toByte(), 0x0c.toByte(),        //         USAGE_PAGE (Consumer Devices)
        0x0a.toByte(), 0x38.toByte(), 0x02.toByte(),  //         USAGE (AC Pan)

        0x15.toByte(), 0x81.toByte(),        //         LOGICAL_MINIMUM (-127)
        0x25.toByte(), 0x7f.toByte(),        //         LOGICAL_MAXIMUM (127)
        0x75.toByte(), 0x08.toByte(),        //         REPORT_SIZE (8)
        0x95.toByte(), 0x01.toByte(),        //         REPORT_COUNT (1)  //7个字节
        0x81.toByte(), 0x06.toByte(),        //         INPUT (Data,Var,Rel)
        0xc0.toByte(),              //       END_COLLECTION
        0xc0.toByte(),              //       END_COLLECTION

        0xc0.toByte(),                               //   END_COLLECTION
        0xc0.toByte(),                                //END_COLLECTION

        0x05.toByte(), 0x01.toByte(),                         // USAGE_PAGE (Generic Desktop)

        0x09.toByte(), 0x06.toByte(),                         // Usage (Keyboard)
        0xA1.toByte(), 0x01.toByte(),                         // Collection (Application)
        0x85.toByte(), 0x08.toByte(),                           //   REPORT_ID (Keyboard)
        0x05.toByte(), 0x07.toByte(),                         //     Usage Page (Key Codes)
        0x19.toByte(), 0xe0.toByte(),                         //     Usage Minimum (224)
        0x29.toByte(), 0xe7.toByte(),                         //     Usage Maximum (231)
        0x15.toByte(), 0x00.toByte(),                         //     Logical Minimum (0)
        0x25.toByte(), 0x01.toByte(),                         //     Logical Maximum (1)
        0x75.toByte(), 0x01.toByte(),                         //     Report Size (1)
        0x95.toByte(), 0x08.toByte(),                         //     Report Count (8)
        0x81.toByte(), 0x02.toByte(),                         //     Input (Data, Variable, Absolute)

        0x95.toByte(), 0x01.toByte(),                         //     Report Count (1)
        0x75.toByte(), 0x08.toByte(),                         //     Report Size (8)
        0x81.toByte(), 0x01.toByte(),                         //     Input (Constant) reserved byte(1)



        0x95.toByte(), 0x01.toByte(),                         //     Report Count (1)
        0x75.toByte(), 0x08.toByte(),                         //     Report Size (8)
        0x15.toByte(), 0x00.toByte(),                         //     Logical Minimum (0)
        0x25.toByte(), 0x65.toByte(),                         //     Logical Maximum (101)
        0x05.toByte(), 0x07.toByte(),                         //     Usage Page (Key codes)
        0x19.toByte(), 0x00.toByte(),                         //     Usage Minimum (0)
        0x29.toByte(), 0x65.toByte(),                         //     Usage Maximum (101)
        0x81.toByte(), 0x00.toByte(),                         //     Input (Data, Array) Key array(6 bytes)
        0xc0.toByte()                               // End Collection (Application)

    )
键鼠设备向主机发送数据调用api  
hidDevice.sendReport(this.host, 4, mouseReport.bytes)

mouseReport.bytes 按照上图发送给主机的报告 构建  7个字节 

inline class ScrollableTrackpadMouseReport (
    val bytes: ByteArray = ByteArray(7) {0}
    ) {


        var leftButton: Boolean
        get() = bytes[0] and 0b1 != 0.toByte()
        set(value) {
            bytes[0] = if (value)
                bytes[0] or 0b1
            else
                bytes[0] and 0b110
        }

        var rightButton: Boolean
        get() = bytes[0] and 0b10 != 0.toByte()
        set(value) {
            bytes[0] = if (value)
                bytes[0] or 0b10
            else
                bytes[0] and 0b101
        }

  //X坐标变化量,,负数表示向左移,正数表右移。
        var dxLsb: Byte
        get() = bytes[1]
        set(value) { bytes[1] = value }

     
        var dxMsb: Byte
        get() = bytes[2]
        set(value) { bytes[2] = value }

    //Y坐标变化,负数表示向下移,正数表上移   
        var dyLsb: Byte
        get() = bytes[3]
        set(value) { bytes[3] = value }

    //y
        var dyMsb: Byte
        get() = bytes[4]
        set(value) { bytes[4] = value }

    // wheel

    var vScroll : Byte
        get() = bytes[5]
        set(value) {
            bytes[5]=value
        }

        var hScroll : Byte
        get() = bytes[6]
        set(value) {
            bytes[6]=value
        }



        fun reset() = bytes.fill(0)

        companion object {
            const val ID = 4
        }
}

HID TLC 描述

第1、2、3行组成一个TLC(Top Level Collection), 这个TLC是用来让主机加载相应的驱动的。也就是说主机收到前3行的数据后,它会在设备管理器里生成一个设备结点,并把蓝牙键盘鼠标的驱动挂到这个结点上。

HID Report Id

在报文描述符的第7 行有一个report id, 这个report id就代表这一组的报文。 上面的报文report id为1, 主机如果收到report id是 4 的话,它就知道它收到的报文是一个蓝牙键鼠的report. 总之一句话,report id跟这个TLC(或是说这个报文)是绑定在一起的。

比较重要的描述符  hid1_11.pdf (HID_USB )

 INPUT (Data,Var,Rel)   

Bit0位 =1 固定值 0  变化值  bit2 0 绝对值 在示例中 此致为 1表示 x y是相对值   0 则为鼠标绝对位置 

 Usages (HID Usage表)

参考

HID_USB

描述符 HID_v1.1.1.pdf

蓝牙BLE: GATT Profile 简介(GATT 与 GAP) - 夜行过客 - 博客园

https://usb.org/sites/default/files/hut1_22.pdf

蓝牙核心技术了解(蓝牙协议、架构、硬件和软件笔记) - 焦少 - 博客园

蓝牙概览  |  Android 开发者  |  Android Developers

  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值