使用 Android 与 USB HID 设备通信

Posted

技术标签:

【中文标题】使用 Android 与 USB HID 设备通信【英文标题】:Using Android to Communicate with a USB HID Device 【发布时间】:2012-10-28 03:36:11 【问题描述】:

我是 USB 和 android 的新手,所以如果我没有清楚地解释自己,请原谅我。

我有一个可以在 Windows 中与之通信的 USB HID 设备。我正在尝试使用运行 Android 3.1 的 Acer Iconia A500 平板电脑建立通信。

我能够找到设备,枚举它,获取它唯一可用的接口,获取唯一可用的端点 (0),并确定它是什么类型的端点(从设备传输中断到主机)。

我对 USB 规范的理解是,所有 HID 设备都至少需要有一个控制端点(端点 0)和一个中断 IN 端点。但是这里的端点0似乎是interrupt In端点,而不是控制端点。

然而,为了让设备枚举,它必须成功地跨控制端点传输其描述符数据。我推断因此必须找到(并使用)控制端点,因为实际上主机确实枚举了设备。

据我所知,如上所述,在应用程序级别呈现给我的唯一接口/端点是从设备到主机的中断类型。从主机到设备、中断或控制的应用程序没有可用的端点。因此,设备等待被告知要做什么,而主机等待设备中发生某些事情。不是很刺激。

请记住,此设备在连接到 Windows 时会正确响应,例如我能够发送一个包含 13 个字节的数据的报告,该数据会导致设备点亮 LED。所以它似乎符合 USB HID 规范。作为一种绝望的行为,我尝试使用这个端点作为控制端点和中断 OUT 端点,使用 controltransfer() 和 UsbRequest() 将数据提交到设备,在任何一种情况下都没有响应。

所以我的问题是:“控制传输端点(?)被用来设置设备,为什么我无法找到并使用它?”

感谢您的任何见解,以下是相关代码,如果需要,我可以将其余部分全部包含在内:

private UsbManager mUsbManager;
private UsbDevice mDevice;
private UsbDeviceConnection mConnectionRead;
private UsbDeviceConnection mConnectionWrite;
private UsbEndpoint mEndpointRead;
private UsbEndpoint mEndpointWrite;

    // check for existing devices
    for (UsbDevice device :  mUsbManager.getDeviceList().values())
    
        //Need to filter for my device when other HIDs are also connected, but for now...           
        String devName = device.getDeviceName();
        if (DEBUG == 1)
        Toast.makeText(UsbHidDeviceTesterActivity.this, "My device got connected: " + devName, Toast.LENGTH_LONG).show();
        
        //mDevice = device;
        setHIDDevice(device);
    

private boolean setHIDDevice(UsbDevice device)
    
    UsbInterface usbInterfaceRead = null;
    UsbInterface usbInterfaceWrite = null;
    UsbEndpoint ep1 = null;
    UsbEndpoint ep2 = null;
    boolean UsingSingleInterface = true;

    mDevice = device;

    //This HID device is using a single interface
    if (UsingSingleInterface)
    
        //usbInterfaceRead = device.getInterface(0x00);//only 1 EP on this interface
        usbInterfaceRead = findInterface(device);

        //Try getting an interface at next index
        //usbInterfaceWrite = device.getInterface(0x01);//throws exception

        // Try using the same interface for reading and writing
        usbInterfaceWrite = usbInterfaceRead;

        int endPointCount = usbInterfaceWrite.getEndpointCount();
        if (DEBUG == 2)
        
            Toast.makeText(UsbHidDeviceTesterActivity.this, "Endpoints: " + endPointCount, Toast.LENGTH_LONG).show();
            //Toast.makeText(UsbHidDeviceTesterActivity.this, "Interface: " + usbInterfaceRead, Toast.LENGTH_LONG).show();
        

        if (endPointCount == 1)//only getting 1 endpoint
        
            ep1 = usbInterfaceRead.getEndpoint(0);
            //As an act of desperation try equating ep2 to this read EP, so that we can later attempt to write to it anyway
            ep2 = usbInterfaceRead.getEndpoint(0);
        
        else if (endPointCount == 2)
        
            ep1 = usbInterfaceRead.getEndpoint(0);
            ep2 = usbInterfaceRead.getEndpoint(1);
        
    

    else        // ! UsingSingleInterface
    
        usbInterfaceRead = device.getInterface(0x00);
        usbInterfaceWrite = device.getInterface(0x01);
        if ((usbInterfaceRead.getEndpointCount() == 1) && (usbInterfaceWrite.getEndpointCount() == 1))
        
            ep1 = usbInterfaceRead.getEndpoint(0);
            ep2 = usbInterfaceWrite.getEndpoint(0);
        
        if (DEBUG == 3)
        
            Toast.makeText(UsbHidDeviceTesterActivity.this, "Using Dual Interface", Toast.LENGTH_LONG).show();
        
    

    //because ep1 = ep2 this will now not cause a return unless no ep is found at all
    if ((ep1 == null) || (ep2 == null))
    
        if (DEBUG == 4)
        
            Toast.makeText(UsbHidDeviceTesterActivity.this, "One EP is null", Toast.LENGTH_LONG).show();
        
        return false;
    

    // Determine which endpoint is the read, and which is the write
    if (ep1.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)//I am getting a return of 3, which is an interrupt transfer
    
        if (ep1.getDirection() == UsbConstants.USB_DIR_IN)//I am getting a return of 128, which is a device-to-host endpoint
        
            mEndpointRead = ep1;
            if (DEBUG == 5)
            
                Toast.makeText(UsbHidDeviceTesterActivity.this, "EP1 type: " + ep1.getType(), Toast.LENGTH_LONG).show();
            
        
        if (ep1.getDirection() == UsbConstants.USB_DIR_OUT)//nope
        
            mEndpointWrite = ep1;
            if (DEBUG == 6)
            
                Toast.makeText(UsbHidDeviceTesterActivity.this, "EP1 is a write", Toast.LENGTH_LONG).show();
            
        
    

    if (ep2.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)
    
        if (ep2.getDirection() == UsbConstants.USB_DIR_IN)
        
            //Try treating it as a write anyway             
            //mEndpointRead = ep2;
            mEndpointWrite = ep2;
        
        else if (ep2.getDirection() == UsbConstants.USB_DIR_OUT)
        
            //usbEndpointWrite = ep2;
            mEndpointWrite = ep2;
        
    

    //check that we should be able to read and write
    if ((mEndpointRead == null) || (mEndpointWrite == null))
    
        return false;
    
    if (device != null)
    
        UsbDeviceConnection connection = mUsbManager.openDevice(device);
        if (connection != null && connection.claimInterface(usbInterfaceRead, true))
        
            Log.d(TAG, "open SUCCESS");
            mConnectionRead = connection;
            // Start the read thread
            //Comment out while desperately attempting to write on this connection/interface
            //Thread thread = new Thread(this);
            //thread.start();

        
        else
        
            Log.d(TAG, "open FAIL");
            mConnectionRead = null;
        
     
    if (UsingSingleInterface)
    
        mConnectionWrite = mConnectionRead;
    
    else //! UsingSingleInterface
    
        mConnectionWrite = mUsbManager.openDevice(device);
        mConnectionWrite.claimInterface(usbInterfaceWrite, true);
    
    return true;


// searches for an interface on the given USB device
 private UsbInterface findInterface(UsbDevice device) 
    Log.d(TAG, "findInterface " + device);
    int count = device.getInterfaceCount();
    if (DEBUG == 7)
    
        Toast.makeText(UsbHidDeviceTesterActivity.this, "Interface count: " + count, Toast.LENGTH_LONG).show();
    

    for (int i = 0; i < count; i++) 
        UsbInterface intf = device.getInterface(i);
        String InterfaceInfo = intf.toString();
        Log.d(TAG, "Interface: " + InterfaceInfo);
        //Class below is 3 for USB_HID
        if (intf.getInterfaceClass() == 3 && intf.getInterfaceSubclass() == 0 &&
                intf.getInterfaceProtocol() == 0) 
            return intf;
        
        //....try just returning the interface regardless of class/subclass
        //return intf;
    

    return null;
 
 private boolean sendControlTransfer(byte[] dataToSend)
 
    synchronized (this)
     
    if (mConnectionRead != null)
      
        //byte[] message = new byte[13];  // or 14?
        byte[] message = dataToSend;
         if (DEBUG == 9)
         
             Toast.makeText(UsbHidDeviceTesterActivity.this, "Sending Control Transfer", Toast.LENGTH_LONG).show();
          

         //first field ox21 is bin 00100001 which splits into 0 01 00001 for direction(1bit)/type(2b)/recipient(5b)
         //To set direction as 'host to Device' we need 0, To set type to HID we need 11 (3), and for recipient we want 00001
         //second field 0x09 is class specific request code, 0x09 is listed as 'reserved for future use'
         //third field 0x200 is value
         //int transfer = mConnectionRead.controlTransfer(0x21, 0x9, 0x200, 0, message, message.length, 0);
         //try with type set to HID
         int transfer = mConnectionRead.controlTransfer(0xC1, 0x9, 0x200, 0, message, message.length, 0);
         if (DEBUG == 10)
         
             Toast.makeText(UsbHidDeviceTesterActivity.this, "Transfer returned " + transfer, Toast.LENGTH_LONG).show();
         
      
    
    return true;
 


private boolean sendInterruptTransfer(byte[] dataToSend)
 
    int bufferDataLength = mEndpointWrite.getMaxPacketSize();//The write endpoint is null unless we just copy the read endpoint
    if (DEBUG == 12)
    
        Toast.makeText(UsbHidDeviceTesterActivity.this, "Max Packet Size: " + bufferDataLength, Toast.LENGTH_LONG).show();
    

    ByteBuffer buffer = ByteBuffer.allocate(bufferDataLength + 1);
    UsbRequest request = new UsbRequest();
    buffer.put(dataToSend);

    request.initialize(mConnectionWrite, mEndpointWrite);
    request.queue(buffer, bufferDataLength);

    try
    
        /* only use requestwait on a read
        if (request.equals(mConnectionWrite.requestWait()))
        
            return true;
        
        */
    
    catch (Exception ex)
    
        // An exception has occurred
        if (DEBUG == 13)
        
            Toast.makeText(UsbHidDeviceTesterActivity.this, "Caught Write Exception", Toast.LENGTH_LONG).show();
        
    

    return true;
   

【问题讨论】:

【参考方案1】:

您可以使用以下方法获取接口和端点详细信息的完整列表:

UsbManager mManager = (UsbManager) getSystemService(Context.USB_SERVICE);
HashMap<String, UsbDevice> deviceList = mManager.getDeviceList();
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();

while (deviceIterator.hasNext())
    
        UsbDevice device = deviceIterator.next();
        Log.i(TAG,"Model: " + device.getDeviceName());
        Log.i(TAG,"ID: " + device.getDeviceId());
        Log.i(TAG,"Class: " + device.getDeviceClass());
        Log.i(TAG,"Protocol: " + device.getDeviceProtocol());
        Log.i(TAG,"Vendor ID " + device.getVendorId());
        Log.i(TAG,"Product ID: " + device.getProductId());
        Log.i(TAG,"Interface count: " + device.getInterfaceCount());
        Log.i(TAG,"---------------------------------------");
   // Get interface details
        for (int index = 0; index < device.getInterfaceCount(); index++)
        
        UsbInterface mUsbInterface = device.getInterface(index);
        Log.i(TAG,"  *****     *****");
        Log.i(TAG,"  Interface index: " + index);
        Log.i(TAG,"  Interface ID: " + mUsbInterface.getId());
        Log.i(TAG,"  Inteface class: " + mUsbInterface.getInterfaceClass());
        Log.i(TAG,"  Interface protocol: " + mUsbInterface.getInterfaceProtocol());
        Log.i(TAG,"  Endpoint count: " + mUsbInterface.getEndpointCount());
    // Get endpoint details 
            for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)
        
            UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);
            Log.i(TAG,"    ++++   ++++   ++++");
            Log.i(TAG,"    Endpoint index: " + epi);
            Log.i(TAG,"    Attributes: " + mEndpoint.getAttributes());
            Log.i(TAG,"    Direction: " + mEndpoint.getDirection());
            Log.i(TAG,"    Number: " + mEndpoint.getEndpointNumber());
            Log.i(TAG,"    Interval: " + mEndpoint.getInterval());
            Log.i(TAG,"    Packet size: " + mEndpoint.getMaxPacketSize());
            Log.i(TAG,"    Type: " + mEndpoint.getType());
        
        
    
    Log.i(TAG," No more devices connected.");

【讨论】:

@user1815293- 感谢您的回复。你的代码是我分散在我的代码中的 DEBUG 的一个更干净的版本。但是,当我运行您的代码时,它确认了我已经发现的内容:只有一个接口与单个端点,并且该端点是一种在从设备到主机的方向上运行的中断类型。总而言之:在 Android 中,我没有获得向设备发送消息的端点。但是我知道端点必须存在,因为它在 Windows 中可用并且可以工作。因此,Android 一定不能正确支持 USB HID 是控制端点还是中断端点不可用尚不清楚。对此问题的任何其他见解将不胜感激,即使它只是为了证实或反驳我的结果。 您如何获得执行此功能的权限?我插入了一个设备,但列表对我来说是空的。 你需要将这个添加到AndroidManifest.xml文件中:【参考方案2】:

所以,我一直在研究类似的东西。我无法确认,但我认为正在发生的是:

    Android 在枚举其端点时不会列出控制端点。它仅列出其他端点。 到任何端点的连接都可以通过 controlTransfer 方法向端点 0 发送控制传输,该方法(引用自 api)“在端点 0 上为此设备执行控制事务。” 因此,在您的上述代码中,我将使用第 0 个端点作为中断输入端点,但它仍然允许控制传输。 使用 HID 设备的示例是 Missle Launcher 演示,它使用的设备是带有中断端点的 HID 设备。

【讨论】:

@zabuni- 你是完全正确的,即使单个端点(EP0)被识别为中断端点,如果我只是将其视为写入并对其执行控制传输,那么它工作。【参考方案3】:

控制传输不显示任何接口描述符,默认情况下其端点号为0,无论是输入还是输出。

如果您有其他接口,这些接口的索引应该从 0 开始,即默认控制传输接口不计算在内。

所以你的接口 0 持有端点 1 描述符。使用 UsbEndpoint 方法查找端点的属性,无论它是否为中断类型。如果是,则 UsbEndpoint.getType() 的端点类型应返回 0x03,UsbEndpoint.getEndpointNumber() 的端点编号应返回 0x81,这是端点 1 的常用值。

你的代码下面是错误的:

//first field ox21 is bin 00100001 which splits into 0 01 00001 for direction(1bit)/type(2b)/recipient(5b)
     //To set direction as 'host to Device' we need 0, **To set type to HID we need 11 (3)**, and for recipient we want 00001
     //second field 0x09 is class specific request code, **0x09 is listed as 'reserved for future use'**
     //**third field 0x200 is value**
     //int transfer = mConnectionRead.controlTransfer(0x21, 0x9, 0x200, 0, message, message.length, 0);
     //try with type set to HID
     int transfer = mConnectionRead.controlTransfer(0xC1, 0x9, 0x200, 0, message, message.length, 0);

Type 2 bits 用于表示特定类别的请求,即其值为 01, 0x09 是隐藏类特定请求 SET_REPORT,未保留。 value 是用作 Hid 类的报告 ID 的 wValue,对于您的情况,如果您的 HID 描述符中只有一个报告,则它可能为 0。 第 4 个参数是 wIndex,它应该用于指示接收者,对于您的情况,它应该是 0x01,作为接收者的接口。

所以您的读取或接收数据表单设备的控制传输代码应该是:

int transfer = mConnectionRead.controlTransfer(0xA1, 0x01, 0x00, 0x01, message, message.length, 0);

其中第二个参数中的 0x01 是 GET_REPORT 是隐藏调用特定请求。

您的用于向设备写入或发送数据的控制传输代码应该是:

int transfer = mConnectionWrite.controlTransfer(0x21, 0x09, 0x00, 0x01, message, message.length, 0);

由于您只有中断 IN 端点 1,批量或中断传输应该是这样的:

int transfer = bulkTransfer (ep1, message, message.length, 0);

要拥有中断输出端点,应该在设备固件的接口描述符处有一个端点描述符。

【讨论】:

伟大的破败。谢谢你!

以上是关于使用 Android 与 USB HID 设备通信的主要内容,如果未能解决你的问题,请参考以下文章

Windows与自定义USB HID设备通信说明.

求救!!usb hid设备(模拟键盘)遇到的问题

带串口 UPS 的 HID 电源设备驱动程序

USB-HID的介绍

USB驱动之Android usb鼠标驱动

usb蓝牙转hid