Programmieren mit PyUSB 1.0

Vom Übersetzer :
Dies ist eine Übersetzung des Handbuchs Programmieren mit PyUSB 1.0
Dieser Leitfaden wurde von den PyUSB-Entwicklern geschrieben, aber ich glaube, dass walac der Hauptautor des Leitfadens ist.

Darf ich mich vorstellen?


PyUSB 1.0 ist eine Python- Bibliothek, die einen einfachen Zugriff auf USB bietet. PyUSB bietet verschiedene Funktionen:

  • 100% in Python geschrieben:
    Im Gegensatz zu 0.x-Versionen, die in C geschrieben wurden, ist Version 1.0 in Python geschrieben. Dadurch können Python-Programmierer ohne C-Erfahrung besser verstehen, wie PyUSB funktioniert.
  • Plattformneutralität:
    Version 1.0 enthält ein Front-End-Backend-Schema. Es isoliert die API von systemspezifischen Implementierungsdetails. Die IBackend- Schnittstelle verbindet diese beiden Schichten. PyUSB verfügt über integrierte Backends für libusb 0.1, libusb 1.0 und OpenUSB. Sie können Ihr Backend selbst schreiben, wenn Sie möchten.
  • Portabilität:
    PyUSB sollte auf jeder Plattform mit Python> = 2.4, ctypes und mindestens einem der unterstützten integrierten Backends ausgeführt werden.
  • Einfachheit:
    Die Interaktion mit einem USB- Gerät war noch nie so einfach! USB ist ein komplexes Protokoll, und PyUSB verfügt über gute Voreinstellungen für die gängigsten Konfigurationen.
  • Isochrone Zahnradunterstützung:
    PyUSB unterstützt isochrone Übertragungen, wenn das zugrunde liegende Backend sie unterstützt.

Obwohl PyUSB die USB-Programmierung weniger schmerzhaft macht, wird in diesem Lernprogramm davon ausgegangen, dass Sie nur minimale Kenntnisse des USB-Protokolls haben. Wenn Sie nichts über USB wissen, empfehle ich Jan Axelsons ausgezeichnetes USB Complete- Buch.

Genug geredet, lasst uns den Code schreiben!


Wer ist wer


Lassen Sie uns zunächst eine Beschreibung der PyUSB-Module geben. Alle PyUSB-Module befinden sich unter USB mit den folgenden Modulen:
ModulBeschreibung
KernDas Haupt-USB-Modul.
utilHilfsfunktionen.
KontrolleStandardverwaltungsanforderungen.
VermächtnisKompatibilitätsschicht der Version 0.x.
BackendEin Unterpaket mit integrierten Backends.

Geben Sie beispielsweise Folgendes ein, um ein Kernmodul zu importieren:

>>> import usb.core >>> dev = usb.core.find() 

Nun, fangen wir an


Das folgende Programm sendet die Zeichenfolge 'test' an die erste gefundene Datenquelle (Endpunkt OUT):

  import usb.core import usb.util #    dev = usb.core.find(idVendor=0xfffe, idProduct=0x0001) #   ? if dev is None: raise ValueError('Device not found') #   .  ,   #    dev.set_configuration() #    cfg = dev.get_active_configuration() intf = cfg[(0,0)] ep = usb.util.find_descriptor( intf, #     custom_match = \ lambda e: \ usb.util.endpoint_direction(e.bEndpointAddress) == \ usb.util.ENDPOINT_OUT) assert ep is not None #   ep.write('test') 

Die ersten beiden Zeilen importieren die PyUSB-Paketmodule. usb.core ist das Hauptmodul und usb.util enthält Hilfsfunktionen . Der folgende Befehl sucht nach unserem Gerät und gibt eine Instanz des Objekts zurück, wenn es gefunden wird. Wenn nicht, wird None zurückgegeben . Als nächstes legen wir die Konfiguration fest, die wir verwenden werden. Hinweis: Das Fehlen von Argumenten bedeutet, dass die gewünschte Konfiguration standardmäßig festgelegt wurde. Wie Sie sehen werden, haben viele PyUSB-Funktionen Standardeinstellungen für die meisten gängigen Geräte. In diesem Fall wird die erste gefundene Konfiguration festgelegt.

Dann suchen wir nach dem Endpunkt, an dem wir interessiert sind. Wir suchen es in der ersten Schnittstelle, die wir haben. Nachdem wir diesen Punkt gefunden haben, senden wir Daten an ihn.

Wenn wir die Endpunktadresse im Voraus kennen, können wir einfach die Schreibfunktion des Geräteobjekts aufrufen:

 dev.write(1, 'test') 

Hier schreiben wir den String 'test' am Haltepunkt an Adresse 1 . Alle diese Funktionen werden in den folgenden Abschnitten näher erläutert.

Was ist los?


Jede Funktion in PyUSB löst im Fehlerfall eine Ausnahme aus. Neben den Standard-Python-Ausnahmen definiert PyUSB usb.core.USBError für USB-bezogene Fehler.

Sie können auch die PyUSB-Protokollfunktionen verwenden. Es verwendet das Protokollierungsmodul . Um es zu verwenden, definieren Sie die Umgebungsvariable PYUSB_DEBUG mit einer der folgenden Protokollierungsstufen: kritisch , Fehler , Warnung , Info oder Debug .

Standardmäßig werden Nachrichten an sys.stderr gesendet. Wenn Sie möchten, können Sie Protokollnachrichten in eine Datei umleiten, indem Sie die Umgebungsvariable PYUSB_LOG_FILENAME definieren . Wenn der Wert der richtige Pfad zur Datei ist, werden dort Nachrichten geschrieben, andernfalls werden sie an sys.stderr gesendet.

Wo bist du?


Die Funktion find () im Kernmodul wird verwendet, um an das System angeschlossene Geräte zu finden und zu nummerieren. Angenommen, unser Gerät verfügt über eine Hersteller-ID mit dem Wert 0xfffe und einer Produkt-ID von 0x0001. Wenn wir dieses Gerät finden müssen, werden wir dies tun:

 import usb.core dev = usb.core.find(idVendor=0xfffe, idProduct=0x0001) if dev is None: raise ValueError('Our device is not connected') 

Das ist alles, die Funktion gibt das usb.core.Device- Objekt zurück, das unser Gerät darstellt. Wenn das Gerät nicht gefunden wird, wird None zurückgegeben . Tatsächlich können Sie jedes gewünschte Feld der Device Descriptor- Klasse verwenden. Was ist zum Beispiel, wenn wir herausfinden möchten, ob ein USB-Drucker an das System angeschlossen ist? Es ist sehr einfach:

 #      ,   if usb.core.find(bDeviceClass=7) is None: raise ValueError('No printer found') 

7 ist der Code für die Druckerklasse gemäß der USB-Spezifikation. Oh, warte, was ist, wenn ich alle verfügbaren Drucker nummerieren möchte? Kein Problem:

 #     ... printers = usb.core.find(find_all=True, bDeviceClass=7) # Python 2, Python 3,     import sys sys.stdout.write('There are ' + len(printers) + ' in the system\n.') 

Was ist passiert? Nun, Zeit für eine kleine Erklärung ... find hat einen Parameter namens find_all und ist standardmäßig False. Wenn es falsch ist [1] , find gibt das erste Gerät zurück, das den angegebenen Kriterien entspricht (wir werden bald darüber sprechen). Wenn Sie dem Parameter einen wahren Wert übergeben, gibt find stattdessen eine Liste aller Geräte zurück, die den Kriterien entsprechen. Das ist alles! Einfach, richtig?

Sind wir fertig? Nein! Ich habe noch nicht alles erzählt: Viele Geräte legen ihre Klasseninformationen tatsächlich im Interface Descriptor anstelle des Device Descriptor ab . Um wirklich alle an das System angeschlossenen Drucker zu finden, müssen wir alle Konfigurationen sowie alle Schnittstellen durchgehen und prüfen, ob eine der Schnittstellen auf bInterfaceClass 7 eingestellt ist. Wenn Sie ein Programmierer wie ich sind, fragen Sie sich möglicherweise: Gibt es eine einfachere Möglichkeit, dies umzusetzen? Antwort: Ja, das ist er. Schauen wir uns zunächst den fertigen Code an, um alle angeschlossenen Drucker zu finden:

 import usb.core import usb.util import sys class find_class(object): def __init__(self, class_): self._class = class_ def __call__(self, device): #  ,   if device.bDeviceClass == self._class: return True # ,   ,   # ,     for cfg in device: # find_descriptor:  ? intf = usb.util.find_descriptor( cfg, bInterfaceClass=self._class ) if intf is not None: return True return False printers = usb.core.find(find_all=1, custom_match=find_class(7)) 

Der Parameter custom_match akzeptiert jedes aufgerufene Objekt, das das Geräteobjekt empfängt. Es sollte true für ein geeignetes Gerät und false für ein unangemessenes Gerät zurückgeben. Sie können custom_match auch mit Gerätefeldern kombinieren, wenn Sie möchten:

 #   ,    : printers = usb.core.find(find_all=1, custom_match=find_class(7), idVendor=0xfffe) 

Hier interessieren uns die Drucker des Lieferanten 0xfffe.

Beschreibe dich selbst


Ok, wir haben unser Gerät gefunden, aber bevor wir mit ihm interagieren, möchten wir mehr darüber erfahren. Nun, Sie wissen, Konfigurationen, Schnittstellen, Endpunkte, Arten von Datenströmen ...
Wenn Sie ein Gerät haben, können Sie auf jedes Feld des Gerätedeskriptors als Objekteigenschaften zugreifen:

 >>> dev.bLength >>> dev.bNumConfigurations >>> dev.bDeviceClass >>> # ... 

Um auf die verfügbaren Konfigurationen im Gerät zuzugreifen, können Sie das Gerät iterieren:

 for cfg in dev: sys.stdout.write(str(cfg.bConfigurationValue) + '\n') 

Auf die gleiche Weise können Sie die Konfiguration für den Zugriff auf die Schnittstellen sowie die Schnittstellen für den Zugriff auf ihre Kontrollpunkte iterieren. Jeder Objekttyp hat Felder des entsprechenden Deskriptors als Attribute. Schauen Sie sich ein Beispiel an:

 for cfg in dev: sys.stdout.write(str(cfg.bConfigurationValue) + '\n') for intf in cfg: sys.stdout.write('\t' + \ str(intf.bInterfaceNumber) + \ ',' + \ str(intf.bAlternateSetting) + \ '\n') for ep in intf: sys.stdout.write('\t\t' + \ str(ep.bEndpointAddress) + \ '\n') 

Sie können auch Indizes für den wahlfreien Zugriff auf Deskriptoren verwenden, wie hier:

 >>> #      >>> cfg = dev[1] >>> #      >>> intf = cfg[(0,0)] >>> #    >>> ep = intf[2] 

Wie Sie sehen können, werden die Indizes von 0 gezählt. Aber warten Sie! Die Art und Weise, wie ich auf die Schnittstelle zugreifen kann, ist etwas Seltsames ... Ja, Sie haben Recht, der Index für die Konfiguration besteht aus zwei Werten, von denen der erste der Schnittstellenindex und der zweite eine alternative Einstellung ist. Um auf die erste Schnittstelle zuzugreifen, aber mit der zweiten Einstellung, schreiben wir im Allgemeinen cfg [(0,1)] .

Jetzt ist es an der Zeit, eine leistungsstarke Methode zur Suche nach Deskriptoren zu erlernen - eine nützliche Funktion find_descriptor . Wir haben es bereits im Beispiel für die Druckersuche gesehen. find_descriptor funktioniert fast genauso wie find , mit zwei Ausnahmen:

  • find_descriptor erhält als ersten Parameter den Quelldeskriptor , den Sie durchsuchen möchten .
  • Es gibt keinen Backend- Parameter [2] .

Wenn wir beispielsweise einen cfg- Konfigurationsdeskriptor haben und alle alternativen Einstellungen für Schnittstelle 1 finden möchten, gehen wir folgendermaßen vor:

 import usb.util alt = usb.util.find_descriptor(cfg, find_all=True, bInterfaceNumber=1) 

Beachten Sie, dass sich find_descriptor im Modul usb.util befindet. Es akzeptiert auch den zuvor beschriebenen Parameter custom_match .

Wir haben es mit mehreren identischen Geräten zu tun

Manchmal können zwei identische Geräte an einen Computer angeschlossen sein. Wie können Sie zwischen ihnen unterscheiden? Geräteobjekte verfügen über zwei zusätzliche Attribute, die nicht Teil der USB-Spezifikation sind, aber sehr nützlich sind: die Bus- und Adressattribute . Zunächst ist zu erwähnen, dass diese Attribute vom Backend stammen und vom Backend möglicherweise nicht unterstützt werden. In diesem Fall sind sie auf None festgelegt . Diese Attribute stellen jedoch die Nummer und Adresse des Gerätebusses dar und können, wie Sie vielleicht vermutet haben, verwendet werden, um zwischen zwei Geräten mit denselben Attributwerten idVendor und idProduct zu unterscheiden.

Wie soll ich arbeiten?


Nach dem Anschließen müssen USB-Geräte mit einigen Standardabfragen konfiguriert werden. Als ich anfing, die USB- Spezifikation zu studieren, war ich von den Deskriptoren, Konfigurationen, Schnittstellen, alternativen Einstellungen, Übertragungstypen und all dem entmutigt ... Und das Schlimmste ist, dass Sie sie nicht einfach ignorieren können: Das Gerät funktioniert nicht, ohne die Konfiguration festzulegen, selbst wenn es eine ist! PyUSB versucht, Ihr Leben so einfach wie möglich zu gestalten. Nachdem Sie beispielsweise Ihr Geräteobjekt empfangen haben, müssen Sie zunächst vor der Interaktion mit ihm eine set_configuration- Anforderung senden. Der Konfigurationsparameter für diese Abfrage, der Sie interessiert, ist bConfigurationValue . Die meisten Geräte haben nicht mehr als eine Konfiguration, und das Verfolgen des zu verwendenden Konfigurationswerts ist ärgerlich (obwohl der größte Teil des Codes, den ich gesehen habe, dies nur fest codiert hat). Daher können Sie in PyUSB einfach eine set_configuration- Anforderung ohne Argumente senden. In diesem Fall installiert er die erste gefundene Konfiguration (wenn Ihr Gerät nur eine hat, müssen Sie sich überhaupt nicht um den Konfigurationswert kümmern). Angenommen, Sie haben ein Gerät mit einem Konfigurationsdeskriptor und das Feld bConfigurationValue ist 5 [3] Nachfolgende Abfragen funktionieren genauso:

 >>> dev.set_configuration(5) #  >>> dev.set_configuration() #  ,   5 -  #  >>> cfg = util.find_descriptor(dev, bConfigurationValue=5) >>> cfg.set() #  >>> cfg = util.find_descriptor(dev, bConfigurationValue=5) >>> dev.set_configuration(cfg) 

Wow! Sie können das Konfigurationsobjekt als Parameter für set_configuration verwenden ! Ja, er hat auch eine festgelegte Methode, um sich in der aktuellen Konfiguration zu konfigurieren.

Eine weitere Option, die Sie konfigurieren müssen oder nicht konfigurieren müssen, ist die Option zum Ändern von Schnittstellen. Jedes Gerät kann jeweils nur eine aktivierte Konfiguration haben, und jede Konfiguration kann mehr als eine Schnittstelle haben, und Sie können alle Schnittstellen gleichzeitig verwenden. Sie verstehen dieses Konzept besser, wenn Sie sich die Schnittstelle als logisches Gerät vorstellen. Stellen wir uns zum Beispiel einen Multifunktionsdrucker vor, der gleichzeitig Drucker und Scanner ist. Um nicht zu komplizieren (oder es zumindest so einfach wie möglich zu machen), nehmen wir an, dass er nur eine Konfiguration hat. Weil Wir haben einen Drucker und einen Scanner. Die Konfiguration hat zwei Schnittstellen: eine für den Drucker und eine für den Scanner. Ein Gerät mit mehr als einer Schnittstelle wird als zusammengesetztes Gerät bezeichnet. Wenn Sie Ihren Multifunktionsdrucker an Ihren Computer anschließen, lädt das Betriebssystem zwei verschiedene Treiber: einen für jedes „logische“ Peripheriegerät, über das Sie verfügen [4] .

Was ist mit alternativen Schnittstelleneinstellungen? Gut, dass du gefragt hast. Eine Schnittstelle verfügt über eine oder mehrere alternative Einstellungen. Eine Schnittstelle mit nur einer alternativen Einstellung hat keine alternativen Einstellungen [5] . Alternative Einstellungen für Schnittstellen als Konfiguration für Geräte, dh für jede Schnittstelle können Sie nur eine aktive alternative Einstellung haben. Beispielsweise legt die USB-Spezifikation nahe, dass ein Gerät in seiner primären alternativen Konfiguration keinen isochronen Prüfpunkt haben kann [6] , so dass das Streaming-Gerät mindestens zwei alternative Einstellungen haben muss, wobei eine zweite Einstellung einen isochronen Prüfpunkt hat. Im Gegensatz zu Konfigurationen müssen Schnittstellen mit nur einer alternativen Konfiguration jedoch nicht konfiguriert werden [7] . Sie wählen eine alternative Schnittstelleneinstellung mit der Funktion set_interface_altsetting aus:

 >>> dev.set_interface_altsetting(interface = 0, alternate_setting = 0) 

Warnung

Die USB-Spezifikation besagt, dass das Gerät einen Fehler zurückgeben darf, wenn es eine SET_INTERFACE-Anforderung für eine Schnittstelle empfängt, die keine zusätzlichen alternativen Einstellungen hat. Wenn Sie also nicht sicher sind, ob die Schnittstelle mehr als eine alternative Einstellung hat oder die SET_INTERFACE-Anforderung akzeptiert, ist es am sichersten, set_interface_altsetting innerhalb des Try- Except -Blocks aufzurufen , wie hier:

 try: dev.set_interface_altsetting(...) except USBError: pass 

Sie können das Interface- Objekt auch als Funktionsparameter verwenden. Die Parameter interface und alternative_setting werden automatisch von den Feldern bInterfaceNumber und bAlternateSetting übernommen . Ein Beispiel:

 >>> intf = find_descriptor(...) >>> dev.set_interface_altsetting(intf) >>> intf.set_altsetting() # !        

Warnung

Das Interface- Objekt muss zum aktiven Konfigurationsdeskriptor gehören.

Sprich mit mir, Schatz


Und jetzt ist es Zeit für uns zu verstehen, wie man mit USB-Geräten interagiert. USB verfügt über vier Arten von Datenströmen: Massenübertragung, Interrupt-Übertragung, isochrone Übertragung und Steuerungsübertragung. Ich habe nicht vor, den Zweck jedes Threads und die Unterschiede zwischen ihnen zu erklären. Daher gehe ich davon aus, dass Sie mindestens Grundkenntnisse in USB-Datenströmen haben.

Der Steuerdatenstrom ist der einzige Strom, dessen Struktur in der Spezifikation beschrieben ist, der Rest sendet und empfängt einfach Rohdaten aus USB-Sicht. Daher haben Sie verschiedene Funktionen zum Arbeiten mit Kontrollflüssen, und der Rest der Flüsse wird von denselben Funktionen verarbeitet.

Sie können mit der Methode ctrl_transfer auf den Steuerdatenstrom zugreifen . Es wird sowohl für ausgehende (OUT) als auch für eingehende (IN) Streams verwendet. Die Durchflussrichtung wird durch den Parameter bmRequestType bestimmt.

Die Parameter ctrl_transfer stimmen fast mit der Struktur der Steuerungsanforderung überein. Das folgende Beispiel zeigt, wie ein Steuerdatenstrom organisiert wird. [8] :

 >>> msg = 'test' >>> assert dev.ctrl_transfer(0x40, CTRL_LOOPBACK_WRITE, 0, 0, msg) == len(msg) >>> ret = dev.ctrl_transfer(0xC0, CTRL_LOOPBACK_READ, 0, 0, len(msg)) >>> sret = ''.join([chr(x) for x in ret]) >>> assert sret == msg 

In diesem Beispiel wird davon ausgegangen, dass unser Gerät zwei Benutzersteuerungsanforderungen enthält, die sich wie eine Loopback-Pipe verhalten. Was Sie mit der Nachricht CTRL_LOOPBACK_WRITE schreiben, können Sie mit der Nachricht CTRL_LOOPBACK_READ lesen.

Die ersten vier Parameter - bmRequestType , bmRequest , wValue und wIndex - sind die Felder der Standardstruktur des Steuerungsstroms. Der fünfte Parameter ist entweder die Datenübertragung für den ausgehenden Datenstrom oder die Anzahl der Daten, die im eingehenden Datenstrom gelesen werden. Die übertragenen Daten können jede Art von Sequenz sein, die als Parameter in die Eingabe der __init__ -Methode für das Array eingespeist werden kann. Wenn keine Daten übertragen werden, muss der Parameter auf Keine gesetzt werden (oder 0 bei einem eingehenden Datenstrom). Es gibt einen weiteren optionalen Parameter, der das Betriebszeitlimit angibt. Wenn Sie es nicht bestehen, wird das Standardzeitlimit verwendet (dazu später mehr). Im ausgehenden Datenstrom ist der Rückgabewert die Anzahl der tatsächlich an das Gerät gesendeten Bytes. Im eingehenden Stream ist der Rückgabewert ein Array mit den gelesenen Daten.

Für andere Streams können Sie die Schreib- bzw. Lesemethoden zum Schreiben und Lesen von Daten verwenden. Sie müssen sich keine Gedanken über die Art des Streams machen - er wird automatisch von der Adresse des Prüfpunkts erkannt. Hier ist unser Loopback-Beispiel, vorausgesetzt, wir haben eine Loopback-Pipe am Haltepunkt 1:

 >>> msg = 'test' >>> assert len(dev.write(1, msg, 100)) == len(msg) >>> ret = dev.read(0x81, len(msg), 100) >>> sret = ''.join([chr(x) for x in ret]) >>> assert sret == msg 

Der erste und der dritte Parameter sind für beide Methoden gleich - dies ist die Prüfpunktadresse bzw. das Zeitlimit. Der zweite Parameter sind die übertragenen Daten (Schreiben) oder die Anzahl der zu lesenden Bytes (Lesen). Die zurückgegebenen Daten sind entweder eine Instanz eines Array- Objekts für die Lesemethode oder die Anzahl der für die Schreibmethode geschriebenen Bytes.

Bei Beta 2-Versionen können Sie anstelle der Anzahl der Bytes ein Array- Objekt, zu dem Daten gelesen werden sollen, zum Lesen oder Strg_transfer übergeben . In diesem Fall entspricht die Anzahl der zu lesenden Bytes der Länge des Arrays mal dem Wert von array.itemsize .

In ctrl_transfer ist der Timeout- Parameter optional. Wenn das Zeitlimit weggelassen wird, wird die Device.default_timeout- Eigenschaft als Betriebszeitlimit verwendet.

Kontrolliere dich


Zusätzlich zu den Datenstromfunktionen bietet das usb.control- Modul Funktionen, die Standard-USB-Steuerungsanforderungen enthalten, und das usb.util- Modul verfügt über eine praktische get_string- Funktion , die speziell Zeilendeskriptoren anzeigt.

Zusätzliche Themen


Hinter jeder großen Abstraktion steht eine große Erkenntnis


Bisher gab es nur libusb . Dann kam libusb 1.0 und wir hatten libusb 0.1 und 1.0. Danach haben wir OpenUSB erstellt und leben jetzt im Tower of Babel der USB Library [9] . Wie geht PyUSB damit um? Nun, PyUSB ist eine demokratische Bibliothek. Sie können wählen, welche Bibliothek Sie möchten. Tatsächlich können Sie Ihre eigene USB-Bibliothek von Grund auf neu schreiben und PyUSB anweisen, sie zu verwenden.

Die Suchfunktion hat einen anderen Parameter, von dem ich Ihnen nichts erzählt habe. Dies ist der Backend- Parameter. Wenn Sie es nicht übertragen, wird eines der integrierten Backends verwendet. Ein Backend ist ein von usb.backend.IBackend geerbtes Objekt , das für die Einführung betriebssystemspezifischer USB-Junk- Dateien verantwortlich ist. Wie Sie vielleicht vermutet haben, sind die integrierten Funktionen libusb 0.1, libusb 1.0 und OpenUSB Backends.

Sie können Ihr eigenes Backend schreiben und verwenden. Erben Sie einfach von IBackend und aktivieren Sie die erforderlichen Methoden. Möglicherweise müssen Sie die Dokumentation zum usb.backend lesen, um zu verstehen, wie dies gemacht wird.

Sei nicht egoistisch


Python hat das, was wir automatische Speicherverwaltung nennen. Dies bedeutet, dass die virtuelle Maschine entscheidet, wann Objekte aus dem Speicher entladen werden sollen. Unter der Haube verwaltet PyUSB alle Ressourcen auf niedriger Ebene, mit denen Sie arbeiten müssen (Genehmigen der Benutzeroberfläche, Anpassen des Geräts usw.), und die meisten Benutzer müssen sich darüber keine Gedanken machen. Aufgrund der Undefiniertheit der automatischen Zerstörung von Objekten durch Python können Benutzer jedoch nicht vorhersagen, wann die zugewiesenen Ressourcen freigegeben werden. Einige Anwendungen müssen Ressourcen deterministisch zuweisen und freigeben. Für solche Anwendungen bietet das Modul usb.util Funktionen für die Interaktion mit dem Ressourcenmanagement.

Wenn Sie Schnittstellen manuell anfordern und freigeben möchten, können Sie die Funktionen Claim_interface und Release_interface verwenden .Die Funktion Claim_Interface fordert die angegebene Schnittstelle an, wenn das Gerät dies noch nicht getan hat. Wenn das Gerät bereits eine Schnittstelle angefordert hat, führt es nichts aus. Außerdem gibt release_interface auf Anfrage die angegebene Schnittstelle frei. Wenn die Schnittstelle nicht angefordert wird, tut sie nichts. Sie können die manuelle Schnittstellenabfrage verwenden, um das in der libusb- Dokumentation beschriebene Problem bei der Konfigurationsauswahl zu lösen .

Wenn Sie alle vom Geräteobjekt zugewiesenen Ressourcen (einschließlich der angeforderten Schnittstellen) freigeben möchten , können Sie die Funktion dispose_resources verwenden. Es gibt alle zugewiesenen Ressourcen frei und überträgt das Geräteobjekt (jedoch nicht an die Hardware des Geräts selbst) in den Zustand, in dem es nach Verwendung der Suchfunktion zurückgegeben wurde .

Manuelle Bibliotheksdefinition


Im Allgemeinen ist ein Backend ein Wrapper über eine gemeinsam genutzte Bibliothek, der eine API für den Zugriff auf USB implementiert. Standardmäßig verwendet das Backend die ctypes- Funktion find_library () . Unter Linux und anderen Unix-ähnlichen Betriebssystemen versucht find_library , externe Programme (wie / sbin / ldconfig , gcc und objdump ) auszuführen , um die Bibliotheksdatei zu finden.

Auf Systemen, auf denen diese Programme fehlen und / oder der Bibliothekscache deaktiviert ist, kann diese Funktion nicht verwendet werden. Um die Einschränkungen zu überwinden, können Sie mit PyUSB die benutzerdefinierte Funktion find_library () an das Backend senden.

Ein Beispiel für ein solches Szenario wäre:

 >>> import usb.core >>> import usb.backend.libusb1 >>> >>> backend = usb.backend.libusb1.get_backend(find_library=lambda x: "/usr/lib/libusb-1.0.so") >>> dev = usb.core.find(..., backend=backend) 

Beachten Sie, dass find_library ein Argument für die Funktion get_backend () ist, in der Sie die Funktion angeben, die für das Finden der richtigen Bibliothek für das Backend verantwortlich ist.

Regeln der alten Schule


Wenn Sie eine Anwendung mit den alten PyUSB-APIs (0. Something-There) schreiben, fragen Sie sich möglicherweise, ob Sie Ihren Code aktualisieren müssen, um die neue API zu verwenden. Nun, Sie sollten es tun, aber es ist nicht notwendig. PyUSB 1.0 wird mit dem Kompatibilitätsmodul usb.legacy geliefert . Es enthält die alte API basierend auf der neuen API. "Nun, sollte ich einfach meine Import-USB- Leitung durch Import usb.legacy als USB ersetzen , damit meine Anwendung funktioniert?", Fragen Sie. Die Antwort ist ja, es wird funktionieren, aber es ist nicht notwendig. Wenn Sie Ihre Anwendung unverändert ausführen, funktioniert dies, da die USB-Importzeile alle öffentlichen Symbole aus usb.legacy importiert. Wenn Sie auf ein Problem stoßen, haben Sie höchstwahrscheinlich einen Fehler gefunden.

Helfen Sie mir bitte


Wenn Sie Hilfe benötigen, schreiben Sie mir keine E-Mail , dafür gibt es eine Mailingliste. Anweisungen zum Abonnement finden Sie auf der PyUSB- Website .

[1] Wenn ich Wahr oder Falsch (mit einem Großbuchstaben) schreibe, meine ich die entsprechenden Werte der Python-Sprache. Und wenn ich wahr (wahr) oder falsch (falsch) sage, meine ich jeden Python-Ausdruck, der als wahr oder falsch angesehen wird. (Diese Ähnlichkeit trat im Original auf und hilft, die Konzepte von wahr und falsch in der Übersetzung zu verstehen. - Hinweis. ) :

[2] Siehe die spezifische Dokumentation für das Backend.

[3] Die USB-Spezifikation legt dem Konfigurationswert keinen bestimmten Wert fest. Gleiches gilt für Schnittstellennummern und alternative Einstellungen.

[4] Eigentlich ist alles etwas komplizierter, aber dies ist nur eine Erklärung für uns.

[5] Ich weiß, das klingt komisch.

[6] Dies liegt daran, dass wenn während der Gerätekonfiguration keine Bandbreite für isochrone Datenströme vorhanden ist, diese erfolgreich nummeriert werden kann.

[7] Dies geschieht nicht für die Konfiguration, da sich das Gerät in einem nicht konfigurierten Zustand befinden darf.

[8] In PyUSB greifen Steuerdatenströme auf den Kontrollpunkt 0 zu. Sehr, sehr, sehr selten verfügt ein Gerät über einen alternativen Kontrollkontrollpunkt (ich habe ein solches Gerät noch nie getroffen).

[9] Dies ist nur ein Witz, nimm es nicht ernst. Gute Entscheidungen sind besser als keine Entscheidungen.

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


All Articles