Как то-однажды мне понадобилось, чтобы один прибор, который управлялся через USB c десктопной программы, также мог управляться через Android приложение. Особенность была в том, что не использовались HID, CDC и прочие стандартные классы устройств. Передача данных осуществлялась через Bulk transfer и конечные точки. В основе работы с usb лежала библиотека libusb.

Сделаем тестовое приложение, с помощью которого можно будет передать и получить произвольные данные с прибора.

Я затрону ключевые моменты, а ссылка на полный код будет в конце.

Для начала действуем согласно официальной документации для работы с UsbHost.
В манифест добавляем строку

<uses-feature android:name="android.hardware.usb.host"/>

Если мы хотим, чтобы устройство автоматически определялось и запускалось приложение, тогда в манифесте для главной активити пишем следующее.

<intent-filter>
   <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>
</intent-filter>
<meta-data
   android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
   android:resource="@xml/device_filter"/>

Указываем для нашего устройства его VID и PID в файле с ресурсами device_filter.xml

<resources>
   <usb-device
       product-id="0037"
       vendor-id="8742"/>
</resources>

Usb менеджер мы получаем в методе onCreate.

mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);

Далее получаем список подключенных к USB устройств, и среди них ищем нужное нам устройство класса USB_CLASS_PER_INTERFACE.

UsbDevice findDevice(UsbManager usbManager) {
   for (UsbDevice usbDevice : usbManager.getDeviceList().values()) {
       if (usbDevice.getDeviceClass() == UsbConstants.USB_CLASS_PER_INTERFACE) {
           return usbDevice;
       } else {
           UsbInterface usbInterface = findInterface(usbDevice);
           if (usbInterface != null) return usbDevice;
       }
   }
   return null;
}

UsbInterface findInterface(UsbDevice usbDevice) {
   for (int nIf = 0; nIf < usbDevice.getInterfaceCount(); nIf++) {
       UsbInterface usbInterface = usbDevice.getInterface(nIf);
       if (usbInterface.getInterfaceClass() == 
           UsbConstants.USB_CLASS_PER_INTERFACE) {
           return usbInterface;
       }
   }
   return null;
}

Далее мы находим нужный нам интерфейс, не забываем проверить разрешения, перебираем все его контрольные точки, среди них выбираем точки на чтение и на запись и, наконец, подключаемся.

private void initUsbDevice() {
   PendingIntent mPermissionIntent = 
       PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);

   IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
   registerReceiver(mUsbReceiver, filter);
   mUsbManager.requestPermission(mUsbDevice, mPermissionIntent);

   mUsbInterface = findInterface(mUsbDevice);

   for (int nEp = 0; nEp < mUsbInterface.getEndpointCount(); nEp++) {
       UsbEndpoint tmpEndpoint = mUsbInterface.getEndpoint(nEp);
       if (tmpEndpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_BULK) 
           continue;
       if ((mOutEndpoint == null)
               && (tmpEndpoint.getDirection() == UsbConstants.USB_DIR_OUT)) {
           mOutEndpoint = tmpEndpoint;
       } else if ((mInEndpoint == null)
               && (tmpEndpoint.getDirection() == UsbConstants.USB_DIR_IN)) {
           mInEndpoint = tmpEndpoint;
       }
   }
   if (mOutEndpoint == null) {
       Toast.makeText(this, "no endpoints", Toast.LENGTH_LONG).show();
   }
   mConnection = mUsbManager.openDevice(mUsbDevice);
   if (mConnection == null) {
       Toast.makeText(this, "can't open device", Toast.LENGTH_SHORT).show();
       return;
   }
   mConnection.claimInterface(mUsbInterface, true);
   startIoManager();
}

Для работы с операциями ввода-вывода, буферизации данных, асинхронности и прочих удобных вещей используется вспомогательный класс-менеджер, но по сути обмен данными сводится к следующему коду.

Чтение:

mConnection.bulkTransfer(mInEndpoint, data, size, READ_TIMEOUT);

Запись:

int bytesWritten = mConnection.bulkTransfer(mOutEndpoint,
       Arrays.copyOfRange(data, offset, offset + size), size, WRITE_TIMEOUT);

Таким образом передаются и принимаются BulkTransfer пакеты.

В итоге наше простое приложение может передавать и принимать произвольные данные.





Работает!

Проект данного тестового приложения на GitHub  

Полезные ссылки:

  1. Библиотека для работы с FT232 и прочими CDC устройствами на Android.
  2. Официальная Android документация по работе с UsbHost.

Комментарии (4)


  1. Shtucer
    17.10.2018 11:11

    if (usbDevice.getDeviceClass() == UsbConstants.USB_CLASS_PER_INTERFACE ||
         findInterface(usbDevice) != null) {
               return usbDevice;
           }

    Не? Вкусовщина, конечно, но в ветке else всё равно ничего полезного с результатом findInterface(usbDevice) не происходит.


  1. gabin8
    17.10.2018 16:41

    А LibUsb то где? В коде только апи андроида для работы с usb


    1. progchip666
      17.10.2018 19:20

      На стороне десктопного приложения, если я правильно понял.


  1. DmitrySpb79
    18.10.2018 01:00

    Вот за что я люблю Android. На iOS такое хрен сделаешь :)