- PVSM.RU - https://www.pvsm.ru -
В недавней статье на Geektimes в комментариях возник вопрос о поддержке в ОС Android периферии, подключенной к шине USB. Действительно, большинство вендорского ПО, к примеру, для работы с принтерами и МФУ, поддерживает только подключение по сети. Однако это не означает, что в самой ОС Android нет такой возможности — это означает лишь то, что большинство устройств не имеют полноценного USB хоста, и далеко не все имеют поддержку OTG. По сети же могут работать абсолютно все без исключения.
Большинство устройств на Android при наличии порта OTG поддерживают на уровне системы (ядра Linux или стандартных компонентов Android) следующие классы устройств:
Несколько реже:
Хабы поддерживаются при наличии полноценных хост-портов, но не поддерживаются на портах OTG.
Подробнее список устройств, поддерживаемых на уровне ядра Linux, можно получить в sysfs:
$ ls /sys/bus/usb/drivers
Если же модуль в принципе доступен в исходниках ядра Linux, но не включен в Android — не стоит рассчитывать на то, что его получится собрать и расставить на все целевые системы.
Однако, начиная с Android 3.1 (API 12), в системе содержатся средства, достаточные для поддержки на уровне приложения любой USB периферии. Данные средства описаны в разделе USB Host [1] руководства по Android API. Здесь же я хочу привести примеры реальной работы с некоторыми видами устройств.
Как и для прочих действий, Android требует, чтобы приложение получило разрешение на доступ к USB периферии. Существует 2 способа получить такое разрешение:
Поскольку для моих задач лишние вопросы к пользователю были нежелательны, я использовал первый способ.
Итак, нам необходимо добавить в манифест следующее:
<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;
Класс предельно простой. В рамках этого класса устройство должно поддерживать:
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);
2. Получить 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);
3. Непосредсвенно открыть устройство:
mConnection = mUsbManager.openDevice(mUsbDevice);
if (mConnection == null) throw new IOException("Can't open USB connection:" + deviceName);
mConnection.claimInterface (mUsbInterface, true);
4. После этого мы можем читать и писать в устройство:
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;
}
5. По завершении работы — закрыть устройство:
mConnection.close();
В отличие от притеров, преобразователи USB-Serial гораздо менее стандартизированы. Существует несколько распространенных чипов, для которых существенно отличается установка параметров последовательного порта — битрейта, чётности и проч. К счастью, есть библиотека github.com/mik3y/usb-serial-for-android [2], поддерживающая практически все существующие чипы. Библиотека полностью скрывает 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;
}
2. Установить параметры последовательного порта:
mUsbSerialPort.setParameters(baudRate, dataBits, stopBits, parity);
3. Читать и писать в порт:
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());
}
4. По завершении работы — закрыть порт:
mUsbSerialPort.close();
Надеюсь, что мне удалось показать, что работа с USB периферией достаточно проста и логична. Безусловно, реализация протоколов некоторых конкретных устройств не блещет простотой — но это проявится в любой системе в одинаковой степени.
Все приведенные примеры я взял из реального проекта, лишь исключил очевидные проверки, оставив только ключевые строки.
Автор: sandworm
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/android/112081
Ссылки в тексте:
[1] USB Host: http://developer.android.com/intl/ru/guide/topics/connectivity/usb/host.html
[2] github.com/mik3y/usb-serial-for-android: https://github.com/mik3y/usb-serial-for-android
[3] Источник: https://habrahabr.ru/post/277093/
Нажмите здесь для печати.