web-dev-qa-db-ja.com

Androidを使用してUSB HIDデバイスと通信する

私はUSBとAndroidを初めて使用するので、自分自身を明確に説明していない場合はご容赦ください。

Windowsで通信できるUSB HIDデバイスを持っています。 Android 3.1を実行しているAcer Iconia A500タブレットを使用して通信を確立しようとしています。

私はデバイスを見つけ、それを列挙し、その唯一の利用可能なインターフェースを取得し、唯一の利用可能なエンドポイント(0)を取得し、それがどのタイプのエンドポイントであるかを判断できます(デバイスからホストへの割り込みの転送)。

USB仕様についての私の理解は、すべてのHIDデバイスが制御エンドポイント(エンドポイント0)と割り込みINエンドポイントを持っている必要があります。しかし、ここのエンドポイント0は制御エンドポイントではなく、割り込みインエンドポイントのようです。

ただし、デバイスが列挙するには、記述子データを制御エンドポイントに正常に転送する必要があります。したがって、ホストが実際にデバイスを列挙するため、制御エンドポイントが検出(および使用)される必要があると推測します。

上記のように、これは私が続行できる限りです。アプリケーションレベルで提示される唯一のインターフェイス/エンドポイントは、デバイスからホストへの割り込みタイプです。アプリからホスト、デバイス、割り込み、制御へのエンドポイントがありません。したがって、デバイスは何をすべきかが通知されるのを待ち、ホストはデバイスで何かが発生するのを待ちます。あまり刺激的ではありません。

このデバイスは、Windowsに接続したときに適切に応答することに注意してください。デバイスにLEDを点灯させる13バイトのデータを含むレポートを送信できます。そのため、USB HID仕様に準拠しているようです。絶望の行為として、私はこの1つのエンドポイントを制御エンドポイントと割り込み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;
}   
16
DasBoos

それで、私は同じようなことを研究しています。確認できませんが、起こっていると私が信じているのは:

  1. Androidは、エンドポイントを列挙するときに、コントロールエンドポイントをリストしません。他のエンドポイントのみがリストされます。
  2. すべてのエンドポイントへの接続は、controlTransferメソッドを介してエンドポイント0に制御転送を送信できます。このメソッドは(apiから引用)、「このデバイスのエンドポイントゼロで制御トランザクションを実行します」。
  3. したがって、上記のコードでは、0番目のエンドポイントを割り込み入力エンドポイントとして使用しますが、それでも制御転送は可能です。
  4. HIDデバイスを使用する誰かの例はMissle Launcherデモです。それが使用するデバイスは、割り込みエンドポイントを持つHIDデバイスです。
9
zabuni

以下を使用して、インターフェースとエンドポイントの詳細の完全なリストを取得できます。

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.");
}
10
user1815293

制御転送はインターフェース記述子を表示せず、その転送のエンドポイント番号はデフォルトで0です。

他のインターフェースがある場合、それらのインターフェースのインデックスは0から開始する必要があります。つまり、デフォルトのコントロール転送インターフェースはカウントされません。

したがって、インターフェース0はエンドポイント1記述子を保持します。 UsbEndpointメソッドを使用して、割り込みタイプかどうかに関係なく、エンドポイントの属性を検索します。その場合、UsbEndpoint.getType()によるエンドポイントタイプは0x03を返し、UsbEndpoint.getEndpointNumber()によるエンドポイント番号は、エンドポイント1の通常の値である0x81を返す必要があります。

あなたのコードの下は間違っています:

//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);

タイプ2ビットは、クラス固有の要求を示すために使用されます。つまり、その値は01、0x09はHidクラス固有の要求であり、予約されていません。 valueはHIDクラスのレポートIDとして使用されるwValueです。HID記述子にレポートが1つしかない場合は、おそらく0です。 4番目のパラメーターはwIndexで、受信者を示すために使用する必要があります。この場合、受信者としてのインターフェースの場合は0x01にする必要があります。

したがって、デバイスからのデータの読み取りまたは受信の制御転送のコードは次のようになります。

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

ここで、2番目のパラメータの0x01はGET_REPORTであり、Hidは特定の要求を呼び出します。

また、書き込みまたはデバイスへのデータ送信の制御転送のコードは次のようになります。

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

インタラプトINエンドポイント1しかないため、バルク転送またはインタラプト転送は次のようになります。

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

割り込み出力エンドポイントを使用するには、デバイスのファームウェアのインターフェイス記述子にそのためのエンドポイント記述子が必要です。

4
Amin