Оптимізація роботи 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 для відправки даних на принтер
  • Опціональний in bulk 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 периферією досить проста і логічна. Безумовно, реалізація протоколів деяких конкретних пристроїв не вражає простотою — але це проявиться в будь-якій системі в однаковій мірі.
Всі наведені приклади я взяв з реального проекту, лише виключив очевидні перевірки, залишивши тільки ключові рядки.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.