Оптимизация работы USB-устройств под Android

в 11:07, , рубрики: usb, Разработка под android

Большинство устройств на Android при наличии порта OTG поддерживают на уровне системы (ядра Linux или стандартных компонентов Android) следующие классы устройств:

  • Устройства ввода — клавиатуры, мыши, джойстики (HID);
  • Накопители (Mass Storage).

Несколько реже:

  • Сотовые модемы;
  • Сетевые адаптеры;
  • Вебкамеры.

Хабы поддерживаются при наличии полноценных хост-портов, но не поддерживаются на портах OTG. Подробнее список устройств, поддерживаемых на уровне ядра Linux, можно получить в sysfs:

$ ls /sys/bus/usb/drivers

Если же модуль в принципе доступен в исходниках ядра Linux, но не включен в Android — не стоит рассчитывать на то, что его получится собрать и расставить на все целевые системы.

Однако, начиная с Android 3.1 (API 12), в системе содержатся средства, достаточные для поддержки на уровне приложения любой USB периферии. Данные средства описаны в разделе USB Host руководства по Android API. Здесь же я хочу привести примеры реальной работы с некоторыми видами устройств.

Права доступа

Как и для прочих действий, Android требует, чтобы приложение получило разрешение на доступ к USB периферии. Существует 2 способа получить такое разрешение:

  • задекларировать список устройств в AndroidManifest;
  • явно показать пользователю диалог “разрешить”.

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

Итак, нам необходимо добавить в манифест следующее:

<activity ...>
...
    <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" />
</activity>
<uses-feature android:name="android.hardware.usb.host" />
<uses-sdk android:minSdkVersion="12" />

А в res/xml/device_filter.xml вписать следующее:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- Serial converters -->
    <!-- 0x0403 / 0x6001: FTDI FT232R UART -->
    <usb-device vendor-id="1027" product-id="24577" />
    <!-- … more devices … -->
</resources>

Отмечу, что хотя общепринято указывать VID:PID в 16-ричной системе счисления, здесь они должны быть указаны в десятичной. В документации заявляется, что возможно указание только класса, без VID и PID, но у меня это не стало работать.

Принтеры

На примере принтера я покажу, как непосредственно использовать API android.hardware.usb. На уровне передачи данных все принтеры поддерживают стандартый класс USB устройств:

int UsbConstants.USB_CLASS_PRINTER = 7;

Класс предельно простой. В рамках этого класса устройство должно поддерживать:

  • Обязательный bulk out endpoind для отправки данных на принтер
  • Опциональный bulk in endpoind для получения статуса принтера
  • 3 управляющих запроса

int GET_DEVICE_ID = 0;
int GET_PORT_STATUS = 1;
int SOFT_RESET = 2;

Код, приведенный ниже, предоставляет функциональность, аналогичную устройству /dev/usb/lp в Linux. Далее нам нужен фильтр, преобразующий исходный документ в пакет данных, понятный конкретной модели принтера. Но это тема иной статьи. Как один из вариантов — можно собрать ghostscript с помощью NDK.

Для работы с устройством нам в первую очередь нужно:

  1. Найти устройство. В примере для простоты я ищу первый попавшийся:

UsbDevice findDevice() {
    for (UsbDevice usbDevice: mUsbManager.getDeviceList().values()) {
        if (usbDevice.getDeviceClass() == UsbConstants.USB_CLASS_PRINTER) {
            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_PRINTER) {
            return usbInterface;
        }
    }
    return null;
}
UsbDevice mUsbDevice = findDevice();
UsbInterface mUsbInterface = findInterface(mUsbDevice);

  1. Получить endpoint’ы:

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) throw new IOException("No write endpoint: " + deviceName);

  1. Непосредсвенно открыть устройство:

mConnection = mUsbManager.openDevice(mUsbDevice);
if (mConnection == null) throw new IOException("Can't open USB connection:" + deviceName);
mConnection.claimInterface (mUsbInterface, true);

  1. После этого мы можем читать и писать в устройство:

public int read(final byte[] data) throws IOException {
    int size = Math.min(data.length, mInEndpoint.getMaxPacketSize());
    return mConnection.bulkTransfer(mInEndpoint, data, size, getReadTimeout());
}

public int write(final byte[] data, final int length) throws IOException {
    int offset = 0;

    while (offset < length) {
        int size = Math.min(length - offset, mInEndpoint.getMaxPacketSize());
        int bytesWritten = mConnection.bulkTransfer(mOutEndpoint,
            Arrays.copyOfRange(data, offset, offset + size), size, getWriteTimeout());

        if (bytesWritten <= 0) throw new IOException("None written");
        offset += bytesWritten;
    }
    return offset;
}

  1. По завершении работы — закрыть устройство:

mConnection.close();

Преобразователи USB-Serial

В отличие от притеров, преобразователи USB-Serial гораздо менее стандартизированы. Существует несколько распространенных чипов, для которых существенно отличается установка параметров последовательного порта — битрейта, чётности и проч. К счастью, есть библиотека github.com/mik3y/usb-serial-for-android, поддерживающая практически все существующие чипы. Библиотека полностью скрывает USB API, сводя все необходимые действия к минимуму вызовов с минимумом параметров.

  1. Найти и открыть устройство:

UsbSerialPort mUsbSerialPort;
UsbManager mUsbManager = (UsbManager) DEVICE.getSystemService(Context.USB_SERVICE);
String type = “FTDI”;

for (UsbDevice usbDevice: mUsbManager.getDeviceList().values()) {
    UsbSerialDriver usbSerialDriver = UsbSerialProber.probeSingleDevice(usbDevice);
    if (usbSerialDriver == null) continue;
    if (!type.equals(usbSerialDriver.getShortDeviceName())) continue;
    mUsbSerialPort = usbSerialDriver.getPort(0);
    mUsbSerialPort.open(mUsbManager);
    break;
}

  1. Установить параметры последовательного порта:

mUsbSerialPort.setParameters(baudRate, dataBits, stopBits, parity);

  1. Читать и писать в порт:

public int read(final byte[] data) throws IOException {
    return mUsbSerialPort.read(data, getReadTimeout());
}

public int write(final byte[] data, final int length) throws IOException {
    return mUsbSerialPort.write(data, length, getWriteTimeout());
}

  1. По завершении работы — закрыть порт:

mUsbSerialPort.close();

Резюме

Надеюсь, что мне удалось показать, что работа с USB периферией достаточно проста и логична. Безусловно, реализация протоколов некоторых конкретных устройств не блещет простотой — но это проявится в любой системе в одинаковой степени.

Все приведенные примеры я взял из реального проекта, лишь исключил очевидные проверки, оставив только ключевые строки.

Автор: Maxim007

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js