Arbeiten mit USB Custom HID unter Android

Entstehen von freepik
In modernen Android-Anwendungen für die Interaktion mit anderen Geräten werden am häufigsten drahtlose Datenübertragungsprotokolle wie Bluetooth verwendet. In den Jahren, in denen einige Geräte drahtlos aufgeladen werden, ist es schwierig, sich eine Reihe von Android-Geräten und Peripheriegeräten vorzustellen, für die kabelgebundene Schnittstellen erforderlich sind. Wenn jedoch ein solcher Bedarf besteht, fällt USB sofort ein.

Nehmen wir einen hypothetischen Fall mit. Stellen Sie sich vor, ein Kunde kommt zu Ihnen und sagt: „Ich benötige eine Android-Anwendung, um ein Datenerfassungsgerät zu steuern und diese Daten auf dem Bildschirm anzuzeigen. Es gibt einen ABER: Die Anwendung muss auf einem Einplatinencomputer mit dem Android-Betriebssystem geschrieben sein und das Peripheriegerät muss über USB angeschlossen sein. “

Es klingt fantastisch, aber es passiert manchmal. Und hier ist es sehr nützlich, ein tiefes Wissen über den USB-Stack und seine Protokolle zu haben, aber in diesem Artikel geht es nicht darum. In diesem Artikel wird erläutert, wie Sie ein Peripheriegerät mithilfe des benutzerdefinierten USB-HID-Protokolls von einem Android-Gerät aus steuern. Der Einfachheit halber schreiben wir eine Android-Anwendung (HOST), die die LED am Peripheriegerät (DEVICE) steuert und den Status der Taste empfängt (drücken). Ich werde den Code für die interessierte Peripheriekarte nicht geben - schreibe in die Kommentare.

Also fangen wir an.

Theorie So kurz wie möglich


Zunächst eine kleine Theorie, so kurz wie möglich. Dies ist ein vereinfachtes Minimum, das ausreicht, um den Code zu verstehen. Zum besseren Verständnis empfehle ich Ihnen jedoch, sich mit dieser Ressource vertraut zu machen.

Für die Kommunikation über USB an einem Peripheriegerät muss eine Interaktionsschnittstelle implementiert werden. Verschiedene Funktionen (z. B. USB HID, USB Mass Strorage oder USB CDC) implementieren ihre Schnittstellen, und einige verfügen über mehrere Schnittstellen. Jede Schnittstelle enthält Endpunkte - spezielle Kommunikationskanäle, eine Art Zwischenablage.

Mein Peripheriegerät verfügt über ein benutzerdefiniertes HID mit einer Schnittstelle und zwei Endpunkten, einem zum Empfangen und einem zum Senden. Normalerweise werden die Informationen mit den auf dem Gerät vorhandenen Schnittstellen und Endpunkten in die Spezifikation des Geräts geschrieben, andernfalls können sie über spezielle Programme, z. B. USBlyzer, ermittelt werden.

Geräte in USB HID kommunizieren über Berichte. Was sind Berichte? Da die Daten über die Endpunkte übertragen werden, müssen wir sie irgendwie identifizieren und gemäß dem Protokoll analysieren. Geräte werfen nicht nur Datenbytes ineinander, sondern tauschen Pakete mit einer klar definierten Struktur aus, die auf dem Gerät in einem speziellen Berichtsdeskriptor beschrieben wird. Auf diese Weise können wir gemäß dem Deskriptor des Berichts genau bestimmen, welche Kennung, Struktur, Größe und Häufigkeit der Übertragung bestimmter Daten vorliegt. Das Paket wird durch das erste Byte identifiziert, bei dem es sich um die Berichts-ID handelt. Zum Beispiel geht der Status der Schaltfläche zum Bericht mit ID = 1, und wir steuern die LED über den Bericht mit ID = 2.

Weg vom Eisen, näher an Android


In Android wurde die Unterstützung für USB-Geräte ab API-Version 12 (Android 3.1) angezeigt. Um mit einem Peripheriegerät arbeiten zu können, müssen wir den USB-Host-Modus implementieren. Die Arbeit mit USB ist in der Dokumentation ziemlich gut beschrieben.

Zuerst müssen Sie Ihr angeschlossenes Gerät unter den verschiedenen USB-Geräten identifizieren. USB-Geräte werden durch eine Kombination aus vid (Hersteller-ID) und pid (Produkt-ID) identifiziert. Erstellen Sie im XML-Ordner die Datei device_filter.xml mit den folgenden Inhalten:

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

Jetzt müssen Sie die entsprechenden Berechtigungen und Aktionen (falls erforderlich) im Anwendungsmanifest vornehmen:

 <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> 

In android: resource geben wir eine Datei mit den notwendigen Filtern für Geräte an. Wie bereits erwähnt, können Sie auch Absichtsfilter zuweisen, um die Anwendung zu starten, z. B. als Ergebnis der Verbindung Ihres Geräts.

Zuerst müssen Sie den UsbManager herunterladen, das Gerät, die Schnittstelle und die Endpunkte des Geräts suchen. Dies muss jedes Mal erfolgen, wenn das Gerät angeschlossen wird.

 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 } 

Hier sehen wir genau die Schnittstellen und Endpunkte, die im letzten Abschnitt besprochen wurden. Wenn wir die Schnittstellennummer kennen, finden wir beide Endpunkte zum Empfangen und Senden und initiieren eine USB-Verbindung. Das ist alles, jetzt können Sie die Daten lesen.

Wie ich bereits sagte, kommunizieren Geräte über Berichte.

  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 } 

Wir senden ein Array von Bytes an die sendReport-Methode, wobei das Null-Byte die Berichts-ID ist. Wir nehmen die aktuelle USB-Verbindung zum Gerät und führen die Übertragung durch. Als Parameter übertragen wir die Endpunktnummer, die Daten, ihre Größe und das Übertragungszeitlimit an die BulkTransfer-Methode. Es ist erwähnenswert, dass die UsbDeviceConnection-Klasse Methoden zum Implementieren des Datenaustauschs mit einem USB-Gerät enthält - BulkTransfer- und ControlTransfer-Methoden. Ihre Verwendung hängt von der Art der Übertragung ab, die ein Endpunkt unterstützt. In diesem Fall verwenden wir BulkTransfer, obwohl die Verwendung von Endpunkten mit dem Steuerelementtyp für HIDs am typischsten ist. Aber wir haben Custom HID, also machen wir, was wir wollen. Ich empfehle Ihnen, die Art der Übertragung separat zu lesen, da das Volumen und die Häufigkeit der übertragenen Daten davon abhängen.

Um Daten zu empfangen, müssen Sie die Größe der Daten kennen, die abgerufen werden können, wie Sie sie im Voraus kennen und vom Endpunkt abrufen können.

Die Methode zum Empfangen von Daten über USB HID ist synchron und blockiert und muss in einem anderen Thread ausgeführt werden. Außerdem können Berichte vom Gerät ständig oder jederzeit empfangen werden. Daher ist es erforderlich, eine ständige Abfrage des Berichts durchzuführen, um keine Daten zu verpassen. Machen wir es mit 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 */ }) } 

Nachdem wir ein Array von Bytes empfangen haben, müssen wir das Null-Byte überprüfen, da es sich um eine Berichts-ID handelt, und dementsprechend die empfangenen Daten analysieren.

Nach Abschluss aller Aktionen mit USB müssen Sie die Verbindung schließen. Sie können dies in der Aktivität onDestroy oder in onCleared in ViewModel tun.

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

Fazit


Der Artikel beschreibt einen sehr kleinen und primitiven, äußerst demonstrativen Code mit Implementierung für ein bestimmtes Gerät. Natürlich gibt es viele USB-Klassen, nicht nur HID, und für sie wird die Implementierung natürlich anders sein. Alle Methoden sind jedoch ziemlich gut dokumentiert und mit einem guten Verständnis des USB-Stacks können Sie leicht herausfinden, wie sie verwendet werden.

X. Nützliche Materialien


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


All Articles