Trabajando con USB HID personalizado en Android

Dise帽ado por Freepik
En las aplicaciones modernas de Android para interactuar con otros dispositivos, los protocolos de transferencia de datos inal谩mbricos, como Bluetooth, se usan con mayor frecuencia. En los a帽os en que algunos dispositivos tienen carga inal谩mbrica, es dif铆cil imaginar un mont贸n de dispositivos y perif茅ricos Android, que requieren el uso de interfaces cableadas. Sin embargo, cuando surge tal necesidad, USB viene inmediatamente a la mente.

Tomemos un caso hipot茅tico con usted. Imagine que un cliente acude a usted y le dice: 鈥淣ecesito una aplicaci贸n de Android para controlar un dispositivo de recopilaci贸n de datos y mostrar estos datos en la pantalla. Hay una PERO: la aplicaci贸n debe estar escrita en una computadora de una sola placa con el sistema operativo Android, y el dispositivo perif茅rico est谩 conectado a trav茅s de USB "

Suena fant谩stico, pero a veces sucede. Y aqu铆 es muy 煤til tener un conocimiento profundo de la pila USB y sus protocolos, pero este art铆culo no trata sobre eso. En este art铆culo, veremos c贸mo controlar un dispositivo perif茅rico utilizando el protocolo USB HID personalizado desde un dispositivo Android. Para simplificar, escribiremos una aplicaci贸n de Android (HOST) que controlar谩 el LED en el dispositivo perif茅rico (DISPOSITIVO) y recibir谩 el estado del bot贸n (presionar). No voy a dar el c贸digo de la placa perif茅rica, que est谩n interesados, escriba en los comentarios.

Entonces comencemos.

Teor铆a Lo m谩s corto posible


Primero, una peque帽a teor铆a, lo m谩s breve posible. Este es un m铆nimo simplificado, suficiente para comprender el c贸digo, pero para una mejor comprensi贸n, le aconsejo que se familiarice con este recurso .

Para comunicarse a trav茅s de USB en un dispositivo perif茅rico, debe implementar una interfaz de interacci贸n. Varias funciones (por ejemplo, USB HID, USB Mass Strorage o USB CDC) implementar谩n sus interfaces, y algunas tendr谩n varias interfaces. Cada interfaz contiene puntos finales: canales de comunicaci贸n especiales, una especie de portapapeles.

Mi perif茅rico tiene un HID personalizado con una interfaz y dos puntos finales, uno para recibir y otro para transmitir. Por lo general, la informaci贸n con las interfaces y los puntos finales existentes en el dispositivo se escribe en la especificaci贸n del dispositivo; de lo contrario, se puede determinar a trav茅s de programas especiales, por ejemplo, USBlyzer.

Los dispositivos en USB HID se comunican a trav茅s de informes. 驴Qu茅 son los informes? Dado que los datos se transmiten a trav茅s de los puntos finales, necesitamos identificarlos y analizarlos de alguna manera de acuerdo con el protocolo. Los dispositivos no solo lanzan bytes de datos entre s铆, sino que intercambian paquetes que tienen una estructura claramente definida, que se describe en el dispositivo en un descriptor de informe especial. Por lo tanto, de acuerdo con el descriptor del informe, podemos determinar con precisi贸n qu茅 identificador, estructura, tama帽o y frecuencia de transmisi贸n tienen ciertos datos. El paquete se identifica por el primer byte, que es el ID del informe. Por ejemplo, el estado del bot贸n va al informe con ID = 1, y controlamos el LED a trav茅s del informe con ID = 2.

Lejos del hierro, m谩s cerca de Android


En Android, el soporte para dispositivos USB apareci贸 a partir de la versi贸n 12 de la API (Android 3.1). Para trabajar con un dispositivo perif茅rico, necesitamos implementar el modo host USB. Trabajar con USB est谩 bastante bien descrito en la documentaci贸n.

Primero debe identificar su dispositivo conectado, entre toda la variedad de dispositivos USB. Los dispositivos USB se identifican mediante una combinaci贸n de vid (identificaci贸n del proveedor) y pid (identificaci贸n del producto). En la carpeta xml, cree el archivo device_filter.xml con el siguiente contenido:

<resources> <usb-device vendor-id="1155" product-id="22352" /> </resources> 

Ahora debe realizar los permisos y la acci贸n apropiados (si los necesita) en el manifiesto de la aplicaci贸n:

 <uses-permission android:name="android.permission.USB_PERMISSION" /> <uses-feature android:name="android.hardware.usb.host" /> <activity android:name=".MainActivity"> <intent-ilter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/device_filter" /> </activity> 

En android: resource especificamos un archivo con los filtros necesarios para dispositivos. Adem谩s, como dije anteriormente, puede asignar filtros de intenci贸n para iniciar la aplicaci贸n, por ejemplo, como resultado de conectar su dispositivo.

Primero debe obtener el UsbManager, encontrar el dispositivo, la interfaz y los puntos finales del dispositivo. Esto debe hacerse cada vez que se conecta el dispositivo.

 val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager private var usbConnection: UsbDeviceConnection? = null private var usbInterface: UsbInterface? = null private var usbRequest: UsbRequest? = null private var usbInEndpoint: UsbEndpoint? = null private var usbOutEndpoint: UsbEndpoint? = null fun enumerate(): Boolean { val deviceList = usbManager.deviceList for (device in deviceList.values) { /*      VID  PID */ if ((device.vendorId == VENDOR_ID) and (device.productId == PRODUCT_ID)) { /*      */ usbInterface = device.getInterface(CUSTOM_HID_INTERFACE) /*            */ for (idx in 0 until usbInterface!!.endpointCount) { if (usbInterface?.getEndpoint(idx)?.direction == USB_DIR_IN) usbInEndpoint = usbInterface?.getEndpoint(idx) else usbOutEndpoint = usbInterface?.getEndpoint(idx) } usbConnection = usbManager.openDevice(device) usbConnection?.claimInterface(usbInterface, true) usbRequest = UsbRequest() usbRequest?.initialize(usbConnection, usbInEndpoint) } } /*    */ return usbConnection != null } 

Aqu铆 vemos las mismas interfaces y puntos finales que se discutieron en la 煤ltima secci贸n. Conociendo el n煤mero de interfaz, encontramos ambos puntos finales, para recibir y transmitir, e iniciar una conexi贸n usb. Eso es todo, ahora puedes leer los datos.

Como dije anteriormente, los dispositivos se comunican a trav茅s de informes.

  fun sendReport(data: ByteArray) { usbConnection?.bulkTransfer(usbOutEndpoint, data, data.size, 0) } fun getReport(): ByteArray { val buffer = ByteBuffer.allocate(REPORT_SIZE) val report = ByteArray(buffer.remaining()) if (usbRequest.queue(buffer, REPORT_SIZE)) { usbConnection?.requestWait() buffer.rewind() buffer.get(report, 0, report.size) buffer.clear() } return report } 

Enviamos una matriz de bytes al m茅todo sendReport, en el que el byte cero es el ID del informe, tomamos la conexi贸n USB actual al dispositivo y realizamos la transferencia. Como par谩metros, transferimos el n煤mero de punto final, los datos, su tama帽o y el tiempo de espera de transmisi贸n al m茅todo BulkTransfer. Vale la pena se帽alar que la clase UsbDeviceConnection tiene m茅todos para implementar el intercambio de datos con un dispositivo USB: m茅todos bulkTransfer y controlTransfer. Su uso depende del tipo de transmisi贸n que admita un punto final. En este caso, usamos bulkTransfer, aunque el uso de puntos finales con el tipo de control es m谩s t铆pico para los HID. Pero tenemos HID personalizado, por lo que hacemos lo que queremos. Le aconsejo que lea sobre el tipo de transmisi贸n por separado, ya que el volumen y la frecuencia de los datos transmitidos dependen de ello.

Para recibir datos, debe conocer el tama帽o de los datos que se pueden obtener, c贸mo saber de antemano y c贸mo llegar desde el punto final.

El m茅todo de recepci贸n de datos a trav茅s de USB HID es sincr贸nico y de bloqueo y debe realizarse en un hilo diferente, adem谩s, los informes del dispositivo pueden recibirse constantemente o en cualquier momento, por lo tanto, es necesario implementar una encuesta constante del informe para no perder los datos. Hag谩moslo con RxJava:

 fun receive() { Observable.fromCallable<ByteArray> { getReport() } .subscribeOn(Schedulers.io()) .observeOn(Schedulers.computation()) .repeat() .subscribe({ /* check it[0] (this is report id) and handle data */ },{ /* handle exeption */ }) } 

Habiendo recibido una matriz de bytes, debemos verificar el byte cero, ya que es un ID de informe y, de acuerdo con 茅l, analizar los datos recibidos.

Una vez completadas todas las acciones con USB, debe cerrar la conexi贸n. Puede hacer esto en la actividad onDestroy o en onCleared en ViewModel.

  fun close() { usbRequest?.close() usbConnection?.releaseInterface(usbInterface) usbConnection?.close() } 

Conclusi贸n


El art铆culo analiza un c贸digo muy peque帽o y primitivo, extremadamente demostrativo con implementaci贸n para un dispositivo espec铆fico. Por supuesto, hay muchas clases de USB, no solo HID, y para ellos, por supuesto, la implementaci贸n ser谩 diferente. Sin embargo, todos los m茅todos est谩n bastante bien documentados y, al tener una buena comprensi贸n de la pila USB, puede descubrir f谩cilmente c贸mo usarlos.

X. Materiales 煤tiles.


Source: https://habr.com/ru/post/470365/


All Articles