Du traducteur :
Ceci est une traduction du manuel de programmation avec PyUSB 1.0
Ce guide a été écrit par les développeurs PyUSB, mais en parcourant rapidement les commits, je crois que walac est l'auteur principal.Laisse moi me présenter
PyUSB 1.0 est une bibliothèque
Python qui fournit un accès facile à l'
USB . PyUSB offre diverses fonctions:
- 100% écrit en Python:
Contrairement aux versions 0.x écrites en C, la version 1.0 est écrite en Python. Cela permet aux programmeurs Python sans expérience C de mieux comprendre le fonctionnement de PyUSB. - Neutralité de la plateforme:
La version 1.0 inclut un schéma de backend frontal. Il isole l'API des détails d'implémentation spécifiques au système. L'interface IBackend relie ces deux couches. PyUSB est livré avec des backends intégrés pour libusb 0.1, libusb 1.0 et OpenUSB. Vous pouvez écrire votre backend vous-même si vous le souhaitez. - Portabilité:
PyUSB devrait fonctionner sur n'importe quelle plate-forme avec Python> = 2.4, ctypes et au moins l'un des backends intégrés pris en charge. - Simplicité:
L'interaction avec un périphérique USB n'a jamais été aussi facile! L'USB est un protocole complexe et PyUSB a de bons préréglages pour les configurations les plus courantes. - Support d'engrenage isochrone:
PyUSB prend en charge les transferts isochrones si le backend sous-jacent les prend en charge.
Bien que PyUSB rend la programmation USB moins pénible, ce tutoriel suppose que vous avez une connaissance minimale du protocole USB. Si vous ne savez rien de l'USB, je recommande l'
excellent livre
USB Complete de Jan Axelson.
Assez parlé, écrivons le code!
Qui est qui
Pour commencer, donnons une description des modules PyUSB. Tous les modules PyUSB sont sous
usb , avec les modules suivants:
Module | La description |
---|
noyau | Le module USB principal. |
util | Fonctions auxiliaires. |
contrôle | Demandes de gestion standard. |
héritage | Couche de compatibilité version 0.x. |
backend | Un sous-package contenant des backends intégrés. |
Par exemple, pour importer un module
principal , entrez les informations suivantes:
>>> import usb.core >>> dev = usb.core.find()
Eh bien, commençons
Ce qui suit est un programme simple qui envoie la chaîne «test» à la première source de données trouvée (point final OUT):
import usb.core import usb.util
Les deux premières lignes importent les modules du package PyUSB.
usb.core est le module principal et
usb.util contient des fonctions d'assistance. La commande suivante recherche notre appareil et renvoie une instance de l'objet s'il le trouve. Sinon, renvoie
Aucun . Ensuite, nous définissons la configuration que nous utiliserons. Remarque: l'absence d'arguments signifie que la configuration souhaitée a été définie par défaut. Comme vous le verrez, de nombreuses fonctionnalités PyUSB ont des paramètres par défaut pour la plupart des appareils courants. Dans ce cas, la première configuration trouvée est définie.
Ensuite, nous recherchons le point final auquel nous sommes intéressés. Nous le recherchons dans la première interface que nous avons. Après avoir trouvé ce point, nous lui envoyons des données.
Si nous connaissons à l'avance l'adresse du point de terminaison, nous pouvons simplement appeler la fonction d'
écriture de l'objet périphérique:
dev.write(1, 'test')
Ici, nous écrivons la chaîne «test» au point d'arrêt à l'adresse
1 . Toutes ces fonctions seront mieux discutées dans les sections suivantes.
Qu'est-ce qui ne va pas?
Chaque fonction dans PyUSB lève une exception en cas d'erreur. Outre les
exceptions Python standard , PyUSB définit
usb.core.USBError pour les erreurs liées à l'USB.
Vous pouvez également utiliser les fonctions de journal PyUSB. Il utilise le module de
journalisation . Pour l'utiliser, définissez la
variable d' environnement
PYUSB_DEBUG avec l'un des niveaux de journalisation suivants:
critique ,
erreur ,
avertissement ,
info ou
débogage .
Par défaut, les messages sont envoyés à
sys.stderr . Si vous le souhaitez, vous pouvez rediriger les messages du journal vers un fichier en définissant la variable d'environnement
PYUSB_LOG_FILENAME . Si sa valeur est le chemin d'accès correct au fichier, les messages y seront écrits, sinon ils seront envoyés à
sys.stderr .
Où es tu
La fonction
find () du module
principal est utilisée pour rechercher et numéroter les périphériques connectés au système. Par exemple, supposons que notre appareil possède un ID de fournisseur avec une valeur de 0xfffe et un ID de produit de 0x0001. Si nous devons trouver cet appareil, nous le ferons:
import usb.core dev = usb.core.find(idVendor=0xfffe, idProduct=0x0001) if dev is None: raise ValueError('Our device is not connected')
C'est tout, la fonction renverra l'objet
usb.core.Device qui représente notre appareil. Si le périphérique n'est pas trouvé, il renverra
Aucun . En fait, vous pouvez utiliser n'importe quel champ de la classe Device
Descriptor de votre choix. Par exemple, que faire si nous voulons savoir si une imprimante USB est connectée au système? C'est très simple:
7 est le code de la classe d'imprimante selon la spécification USB. Oh, attendez, que faire si je veux numéroter toutes les imprimantes disponibles? Pas de problème:
Qu'est-il arrivé? Eh bien, il est temps pour une petite explication ...
find a un paramètre appelé
find_all et par défaut à False. Quand c'est faux
[1] ,
find renverra le premier appareil qui correspond aux critères spécifiés (nous en reparlerons bientôt). Si vous transmettez une valeur
vraie au paramètre,
find renverra à la place une liste de tous les périphériques correspondant aux critères. C'est tout! C'est simple, non?
Avons-nous fini? Non! Je n'ai pas encore tout dit: de nombreux appareils mettent en fait leurs informations de classe dans le
Descripteur d' interface au lieu du
Descripteur d' appareil. Donc, pour vraiment trouver toutes les imprimantes connectées au système, nous devrons passer par toutes les configurations, ainsi que toutes les interfaces, et vérifier si l'une des interfaces est définie sur bInterfaceClass 7. Si vous êtes un
programmeur comme moi, vous pouvez vous demander: existe-t-il un moyen plus simple de mettre cela en œuvre? Réponse: oui, il l'est. Pour commencer, regardons le code prêt à l'emploi pour trouver toutes les imprimantes connectées:
import usb.core import usb.util import sys class find_class(object): def __init__(self, class_): self._class = class_ def __call__(self, device):
Le paramètre
custom_match accepte tout objet appelé qui reçoit l'objet périphérique. Il doit renvoyer true pour un appareil approprié et false pour un appareil inapproprié. Vous pouvez également combiner
custom_match avec des champs d'appareil si vous le souhaitez:
Ici, nous nous intéressons aux imprimantes du fournisseur 0xfffe.
Décrivez-vous
Ok, nous avons trouvé notre appareil, mais avant d'interagir avec lui, nous aimerions en savoir plus. Eh bien, vous savez, les configurations, les interfaces, les points de terminaison, les types de flux de données ...
Si vous avez un périphérique, vous pouvez accéder à n'importe quel champ du descripteur de périphérique en tant que propriétés d'objet:
>>> dev.bLength >>> dev.bNumConfigurations >>> dev.bDeviceClass >>>
Pour accéder aux configurations disponibles dans l'appareil, vous pouvez itérer l'appareil:
for cfg in dev: sys.stdout.write(str(cfg.bConfigurationValue) + '\n')
De la même manière, vous pouvez itérer la configuration pour accéder aux interfaces, ainsi qu'itérer les interfaces pour accéder à leurs points de contrôle. Chaque type d'objet a des champs du descripteur correspondant comme attributs. Jetez un oeil à un exemple:
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')
Vous pouvez également utiliser des index pour un accès aléatoire aux descripteurs, comme ici:
>>>
Comme vous pouvez le voir, les indices sont comptés à partir de 0. Mais attendez! Il y a quelque chose d'étrange dans la façon dont j'accède à l'interface ... Oui, vous avez raison, l'index de configuration prend une série de deux valeurs, dont la première est l'index d'interface et la seconde est un paramètre alternatif. En général, pour accéder à la première interface, mais avec le second paramètre, nous écrirons
cfg [(0,1)] .
Il est maintenant temps d'apprendre une manière puissante de rechercher des descripteurs - une fonction
find_descriptor utile. Nous l'avons déjà vu dans l'exemple de recherche d'imprimante.
find_descriptor fonctionne presque de la même manière que
find , à deux exceptions près:
- find_descriptor reçoit comme premier paramètre le descripteur source que vous recherchez.
- Il n'y a pas de paramètre backend dedans [2] .
Par exemple, si nous avons un descripteur de configuration
cfg , et que nous voulons trouver tous les paramètres alternatifs pour l'interface 1, nous le ferons:
import usb.util alt = usb.util.find_descriptor(cfg, find_all=True, bInterfaceNumber=1)
Notez que
find_descriptor se trouve dans le module
usb.util . Il accepte également le paramètre
custom_match décrit précédemment.
Nous traitons avec plusieurs appareils identiquesParfois, vous pouvez connecter deux appareils identiques à un ordinateur. Comment pouvez-vous les distinguer?
Les objets de
périphérique sont livrés avec deux attributs supplémentaires qui ne font pas partie de la spécification USB, mais sont très utiles: les attributs de
bus et d'
adresse . Tout d'abord, il convient de dire que ces attributs proviennent du backend et que le backend peut ne pas les prendre en charge - dans ce cas, ils sont définis sur
Aucun . Cependant, ces attributs représentent le numéro et l'adresse du bus de périphérique et, comme vous l'avez peut-être deviné, peuvent être utilisés pour distinguer deux périphériques avec les mêmes valeurs d'attribut
idVendor et
idProduct .
Comment dois-je travailler?
Après la connexion, les périphériques USB doivent être configurés à l'aide de quelques requêtes standard. Lorsque j'ai commencé à étudier la spécification
USB , j'ai été découragé par les descripteurs, les configurations, les interfaces, les paramètres alternatifs, les types de transfert et tout ça ... Et pire que tout, vous ne pouvez pas simplement les ignorer: l'appareil ne fonctionne pas sans définir la configuration, même si elle en est une! PyUSB essaie de vous rendre la vie aussi simple que possible. Par exemple, après avoir reçu votre objet appareil, tout d'abord, avant d'interagir avec lui, vous devez envoyer une demande
set_configuration . Le paramètre de configuration de cette requête qui vous intéresse est
bConfigurationValue . La plupart des appareils n'ont pas plus d'une configuration, et le suivi de la valeur de configuration à utiliser est ennuyeux (bien que la plupart du code que j'ai vu soit codé en dur). Par conséquent, dans PyUSB, vous pouvez simplement envoyer une demande
set_configuration sans arguments. Dans ce cas, il installera la première configuration trouvée (si votre appareil n'en a qu'une, vous n'avez pas du tout à vous soucier de la valeur de configuration). Par exemple, supposons que vous ayez un périphérique avec un descripteur de configuration et que son champ bConfigurationValue soit 5
[3] , les requêtes suivantes fonctionneront de la même manière:
>>> dev.set_configuration(5)
Ouah! Vous pouvez utiliser l'objet
Configuration comme paramètre pour
set_configuration ! Oui, il a également une méthode
définie pour se configurer dans la configuration actuelle.
Une autre option dont vous avez besoin ou n'aurez pas besoin de configurer est l'option de changer les interfaces. Chaque périphérique ne peut avoir qu'une seule configuration activée à la fois, et chaque configuration peut avoir plusieurs interfaces, et vous pouvez utiliser toutes les interfaces en même temps. Vous comprenez mieux ce concept si vous considérez l'interface comme un périphérique logique. Par exemple, imaginons une imprimante multifonction, qui est à la fois une imprimante et un scanner. Afin de ne pas compliquer (ou du moins de le rendre aussi simple que possible), supposons qu'il n'a qu'une seule configuration. Parce que nous avons une imprimante et un scanner, la configuration a 2 interfaces: une pour l'imprimante et une pour le scanner. Un périphérique avec plusieurs interfaces est appelé périphérique composite. Lorsque vous connectez votre imprimante multifonction à votre ordinateur, le système d'exploitation charge deux pilotes différents: un pour chaque périphérique «logique» que vous avez
[4] .
Qu'en est-il des paramètres d'interface alternatifs? C'est une bonne chose que vous ayez demandé. Une interface a un ou plusieurs paramètres alternatifs. Une interface avec un seul paramètre alternatif est considérée comme n'ayant aucun paramètre alternatif
[5] . Paramètres alternatifs pour les interfaces en tant que configuration pour les appareils, c'est-à-dire que pour chaque interface, vous ne pouvez avoir qu'un seul paramètre alternatif actif. Par exemple, la spécification USB suggère qu'un appareil ne peut pas avoir de point de contrôle isochrone dans sa configuration alternative principale
[6] , de sorte que le dispositif de diffusion en continu doit avoir au moins deux paramètres alternatifs, un second paramètre ayant un point de contrôle isochrone. Mais, contrairement aux configurations, les interfaces avec une seule configuration alternative n'ont pas besoin d'être configurées
[7] . Vous sélectionnez un autre paramètre d'interface à l'aide de la fonction
set_interface_altsetting :
>>> dev.set_interface_altsetting(interface = 0, alternate_setting = 0)
AvertissementLa spécification USB indique que l'appareil est autorisé à renvoyer une erreur s'il reçoit une demande SET_INTERFACE pour une interface qui n'a pas de paramètres alternatifs supplémentaires. Donc, si vous n'êtes pas sûr que l'interface possède plusieurs paramètres alternatifs ou qu'elle accepte la demande SET_INTERFACE, la méthode la plus sûre consiste à appeler
set_interface_altsetting à l'intérieur du bloc try-except, comme ici:
try: dev.set_interface_altsetting(...) except USBError: pass
Vous pouvez également utiliser l'objet
Interface en tant que paramètre de fonction, l'
interface et
les paramètres de
remplacement sont automatiquement hérités des
champs bInterfaceNumber et
bAlternateSetting . Un exemple:
>>> intf = find_descriptor(...) >>> dev.set_interface_altsetting(intf) >>> intf.set_altsetting()
AvertissementL'objet
Interface doit appartenir au descripteur de configuration actif.
Parle-moi chérie
Et maintenant, il est temps pour nous de comprendre comment interagir avec les périphériques USB. L'USB propose quatre types de flux de données: transfert en masse, transfert d'interruption, transfert isochrone et transfert de contrôle. Je n'ai pas l'intention d'expliquer le but de chaque fil et les différences entre eux. Par conséquent, je suppose que vous avez au moins une connaissance de base des flux de données USB.
Le flux de données de contrôle est le seul flux dont la structure est décrite dans la spécification, le reste envoie et reçoit simplement des données brutes d'un point de vue USB. Par conséquent, vous disposez de diverses fonctions pour travailler avec des flux de contrôle et les autres flux sont traités par les mêmes fonctions.
Vous pouvez accéder au flux de données de contrôle à l'aide de la méthode
ctrl_transfer . Il est utilisé à la fois pour les flux sortants (OUT) et entrants (IN). Le sens du flux est déterminé par le paramètre
bmRequestType .
Les paramètres
ctrl_transfer coïncident presque avec la structure de la demande de contrôle. L'exemple suivant montre comment organiser un flux de données de contrôle.
[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
Cet exemple suppose que notre appareil comprend deux demandes de contrôle utilisateur qui agissent comme un canal de bouclage. Ce que vous écrivez avec le message
CTRL_LOOPBACK_WRITE , vous pouvez le lire avec le message
CTRL_LOOPBACK_READ .
Les quatre premiers paramètres -
bmRequestType ,
bmRequest ,
wValue et
wIndex - sont les champs de la structure standard du flux de contrôle. Le cinquième paramètre est soit les données transférées pour le flux de données sortant, soit le nombre de données lues dans le flux entrant. Les données transmises peuvent être n'importe quel type de séquence, qui peut être fournie en tant que paramètre à l'entrée de la méthode
__init__ pour la
matrice . Si aucune donnée n'est transférée, le paramètre doit être défini sur
Aucun (ou 0 dans le cas d'un flux de données entrant). Il existe un autre paramètre facultatif indiquant le délai d'expiration de l'opération. Si vous ne le réussissez pas, le délai d'expiration par défaut sera utilisé (plus à ce sujet plus tard). Dans le flux de données sortant, la valeur de retour est le nombre d'octets réellement envoyés au périphérique. Dans le flux entrant, la valeur de retour est un
tableau avec les données lues.
Pour les autres flux, vous pouvez utiliser les méthodes d'
écriture et de
lecture , respectivement, pour écrire et lire des données. Vous n'avez pas à vous soucier du type de flux - il est automatiquement détecté par l'adresse du point de contrôle. Voici notre exemple de bouclage, à condition que nous ayons un tuyau de bouclage au point d'arrêt 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
Les premier et troisième paramètres sont les mêmes pour les deux méthodes - il s'agit de l'adresse du point de contrôle et du délai d'expiration, respectivement. Le deuxième paramètre est la donnée transmise (écriture) ou le nombre d'octets à lire (lecture). Les données renvoyées seront soit une instance d'un objet
tableau pour la méthode de
lecture , soit le nombre d'octets écrits pour la méthode d'
écriture .
Avec les versions beta 2, au lieu du nombre d'octets, vous pouvez passer en
lecture ou
ctrl_transfer un objet
tableau vers lequel les données seront lues. Dans ce cas, le nombre d'octets à lire sera la longueur du tableau multipliée par la valeur de
array.itemsize .
Dans
ctrl_transfer , le paramètre
timeout est facultatif. Lorsque le
délai est omis, la propriété
Device.default_timeout est
utilisée comme délai opérationnel.
Contrôlez-vous
En plus des fonctions de
flux de données, le module
usb.control fournit des fonctions qui incluent des demandes de contrôle USB standard, et le module
usb.util a une fonction
get_string pratique
qui affiche spécifiquement les descripteurs de ligne.
Sujets supplémentaires
Derrière chaque grande abstraction se cache une grande réalisation
Auparavant, il n'y avait que
libusb . Puis vint libusb 1.0 et nous avons eu libusb 0.1 et 1.0. Après cela, nous avons créé
OpenUSB et maintenant nous vivons dans la
tour de Babel de la bibliothèque USB
[9] . Comment PyUSB gère-t-il cela? Eh bien, PyUSB est une bibliothèque démocratique, vous pouvez choisir la bibliothèque que vous souhaitez. En fait, vous pouvez écrire votre propre bibliothèque USB à partir de zéro et dire à PyUSB de l'utiliser.
La fonction
find a un autre paramètre, dont je ne vous ai pas parlé. Il s'agit du paramètre
backend . Si vous ne le transférez pas, l'un des backends intégrés sera utilisé. Un backend est un objet hérité de
usb.backend.IBackend , responsable de l'introduction de
fichiers indésirables USB spécifiques au système d'exploitation. Comme vous l'avez peut-être deviné, les libusb 0.1, libusb 1.0 et OpenUSB intégrés sont des backends.
Vous pouvez écrire votre propre backend et l'utiliser.
Héritez juste d'
IBackend et activez les méthodes nécessaires. Vous devrez peut-être consulter la documentation
usb.backend pour comprendre comment cela se fait.
Ne sois pas égoïste
Python possède ce que nous appelons
la gestion automatique de la mémoire . Cela signifie que la machine virtuelle décidera quand décharger les objets de la mémoire. Sous le capot, PyUSB gère toutes les ressources de bas niveau avec lesquelles vous devez travailler (approbation de l'interface, réglage de l'appareil, etc.) et la plupart des utilisateurs n'ont pas à s'en soucier. Mais, en raison de la nature non définie de la destruction automatique des objets par Python, les utilisateurs ne peuvent pas prédire quand les ressources allouées seront libérées. Certaines applications doivent allouer et libérer des ressources de manière déterministe. Pour de telles applications, le module
usb.util fournit des fonctions d'interaction avec la gestion des ressources.
Si vous souhaitez demander et libérer des interfaces manuellement, vous pouvez utiliser les fonctions
claim_interface et
release_interface .
La fonction claim_interface demandera l'interface spécifiée si le périphérique ne l'a pas encore fait. Si l'appareil a déjà demandé une interface, il ne fait rien. Juste release_interface libérera l'interface spécifiée, si elle est demandée. Si l'interface n'est pas demandée, elle ne fait rien. Vous pouvez utiliser l'interrogation manuelle de l'interface pour résoudre le problème de sélection de configuration décrit dans la documentation de libusb . Si vous souhaitez libérer toutes les ressources allouées par l'objet périphérique (y compris les interfaces demandées), vous pouvez utiliser la fonction dispose_resources. Il libère toutes les ressources allouées et place l'objet périphérique (mais pas dans le matériel du périphérique lui-même) dans l'état dans lequel il a été renvoyé après avoir utilisé la fonction find .Définition manuelle de la bibliothèque
En général, un backend est un wrapper sur une bibliothèque partagée qui implémente une API pour accéder à USB. Par défaut, les utilisations de back - end ctypes disposent find_library () . Sous Linux et d'autres systèmes d'exploitation de type Unix, find_library essaie d'exécuter des programmes externes (tels que / sbin / ldconfig , gcc et objdump ) afin de trouver le fichier de bibliothèque.Sur les systèmes dans lesquels ces programmes sont manquants et / ou le cache de bibliothèque est désactivé, cette fonction ne peut pas être utilisée. Pour surmonter ces limitations, PyUSB vous permet de soumettre la fonction personnalisée find_library () au backend.Un exemple d'un tel scénario serait: >>> 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)
Notez que find_library est un argument de la fonction get_backend () dans lequel vous fournissez la fonction chargée de trouver la bonne bibliothèque pour le backend.Règles de la vieille école
Si vous écrivez une application en utilisant les anciennes API PyUSB (0. quelque chose-là), vous vous demandez peut-être si vous devez mettre à jour votre code pour utiliser la nouvelle API. Eh bien, vous devriez le faire, mais ce n’est pas nécessaire. PyUSB 1.0 est fourni avec le module de compatibilité usb.legacy . Il inclut l'ancienne API basée sur la nouvelle API. "Eh bien, devrais-je simplement remplacer ma ligne USB d' importation par Import USB.legacy en tant qu'USB pour faire fonctionner ma demande?", Demandez-vous. La réponse est oui, cela fonctionnera, mais ce n'est pas nécessaire. Si vous exécutez votre application sans modification, cela fonctionnera car la ligne USB d' importation importe tous les symboles publics de usb.legacy. Si vous rencontrez un problème - vous avez probablement trouvé un bug.Aidez-moi s'il vous plaît
Si vous avez besoin d'aide, ne m'écrivez pas d'e-mail , il existe une liste de diffusion pour cela. Les instructions d'abonnement se trouvent sur le site Web de PyUSB .[1] Quand j'écris Vrai ou Faux (avec une majuscule), je veux dire les valeurs correspondantes du langage Python. Et quand je dis vrai (vrai) ou faux (faux), je veux dire toute expression Python qui est considérée comme vraie ou fausse. (Cette similitude s'est produite dans l'original et aide à comprendre les concepts de vrai et de faux en traduction. - Remarque. ) :[2] Voir la documentation spécifique pour le backend.[3] La spécification USB n'impose aucune valeur spécifique à la valeur de configuration. Il en va de même pour les numéros d'interface et les autres paramètres.[4] En fait, tout est un peu plus compliqué, mais ce n'est qu'une explication pour nous.[5] Je sais que cela semble bizarre.[6] En effet, s'il n'y a pas de bande passante pour les flux de données isochrones pendant la configuration de l'appareil, elle peut être numérotée avec succès.[7] Cela ne se produit pas pour la configuration car le périphérique est autorisé à être dans un état non configuré.[8] Dans PyUSB, les flux de données de contrôle accèdent au point de contrôle 0. Très très très rarement, un appareil dispose d'un point de contrôle de contrôle alternatif (je n'ai jamais rencontré un tel appareil).[9] Ce n'est qu'une blague, ne la prenez pas au sérieux. De grands choix valent mieux que pas de choix.