PyUSB 1.0 を使ってプログラミング

手前味噌ですが…

PyUSB 1.0は、容易なる USB アクセスを可能にする Python ライブラリです。 PyUSBはいくつかの機能を提供します:

100% Python で書かれています:

Cで記述された0.xバージョンとは異なり、1.0バージョンはPythonで記述されています。 これにより、CのバックグラウンドがないPythonプログラマーはPyUSBがどのように機能するかをよりよく理解できます。

特定のプラットフォームに偏らず、中立です:

1.0バージョンは、フロントエンド-バックエンド スキーム 実装です。 これにより、システム固有の実装の詳細とAPIが分離されています。 2つの層の間の接着剤は IBackend インターフェースです。 PyUSBには、libusb 1.0、libusb 0.1、OpenUSB用の組み込みバックエンドが付属しています。 必要に応じて、独自のバックエンドを作成することもできます。

ポータビリティ:

PyUSBは、Python>=3.6 かつ ctypes かつ サポートされている組み込みバックエンドの少なくとも1つを備えたすべてのプラットフォームで実行可能です。

最強に簡単:

USB デバイスとの通信がこれまでになく簡単になりました。 USBは複雑なプロトコルですが、PyUSBにはほとんどの一般的な構成に適したデフォルトの構成があります。

アイソクロナス転送(isochronous transfers)のサポート:

基礎となるバックエンドがアイソクロナス転送(isochronous transfers)をサポートしている場合、PyUSBはアイソクロナス(isochronous transfers)転送をサポートします。

PyUSBはUSBプログラミングの苦痛を軽減しますが、このチュートリアルでは、最小限のUSBプロトコルの予備知識があることを前提としています。 USBについて何も知らない場合は、優れたJan Axelsonの著書 USB Complete をお勧めします。(訳注: USB Complete: The Developer's Guide (Complete Guides series) (English Edition) https://www.amazon.co.jp/dp/B00U58R0FA/ 日本語版もあるが版数が古そう)

もうお話は充分、さぁコーディングしましょう!

紳士録

まず、PyUSBモジュールの概要を説明します。 PyUSBモジュールは usb パッケージの下にあり、以下のモジュールがあります:

モジュール

説明

core

メイン USB モジュール。

util

ユーティリティ関数。

control

Standard control requests.

legacy

バージョン 0.x 系 互換レイヤ。

backend

組み込みのバックエンドを含むサブパッケージ。

たとえば、coreモジュールをインポートするには、次のように入力します:

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

それじゃあ初めましょう

以下は、見つかった最初のOUTエンドポイント(OUT endpoint)に 'test' 文字列を送信する単純際まわりないプログラムです:

import usb.core
import usb.util

# find our device
dev = usb.core.find(idVendor=0xfffe, idProduct=0x0001)

# was it found?
if dev is None:
    raise ValueError('Device not found')

# set the active configuration. With no arguments, the first
# configuration will be the active one
dev.set_configuration()

# get an endpoint instance
cfg = dev.get_active_configuration()
intf = cfg[(0,0)]

ep = usb.util.find_descriptor(
    intf,
    # match the first OUT endpoint
    custom_match = \
    lambda e: \
        usb.util.endpoint_direction(e.bEndpointAddress) == \
        usb.util.ENDPOINT_OUT)

assert ep is not None

# write the data
ep.write('test')

最初の2行でPyUSBパッケージモジュールをインポートします。 usb.core がメインモジュールで、 usb.util はユーティリティ関数です。その次の命令はデバイスを検索し、見つかった場合はインスタンスオブジェクトを返します。 なければ None が返されます。その後、使用する構成(configuration)を設定します。必要な構成(configuration)を示す引数が指定されていないことに注意してください。 ご覧のとおり、多くのPyUSB関数には、ほとんどの一般的なデバイスのデフォルトがあります。この場合、その構成セット(configuration set)は最初に見つかったものです。

次に、私たちが関心のあるエンドポイント(endpoint)を探します。 私たちは最初のインターフェース(interface)内で検索します。 エンドポイント(endpoint)を見つけたら、データをエンドポイント(endpoint)に送信します。

私たちがエンドポイントアドレス(endpoint address)を事前に知っている場合は、デバイス(device)オブジェクトから write 関数を呼び出すだけです。

dev.write(1, 'test')

ここでは、私たちはエンドポイントアドレス(endpoint address) 1 に文字列 'test' を書き込みます。 これらすべての機能については、次節で詳しく説明します。

問題は何?

エラー発生時、PyUSBの全ての関数は1つの例外を発生させます。Python標準例外 に加えて、PyUSBはUSB関連エラーの usb.core.USBError を定義しています。

あなたはPyUSBログ機能を使用することもできます。 logging モジュールを使用します。 これを有効にするには、環境変数 PYUSB_DEBUG をレベル名 criticalerrorwarninginfodebug のいずれかで定義します。

デフォルトでは、メッセージは sys.stderr に送信されます。 必要に応じて、 PYUSB_LOG_FILENAME 環境変数を定義することで、ログメッセージをファイルにリダイレクトできます。 その値が有効なファイルパスである場合、メッセージはそれに書き込まれ、そうでない場合は、 sys.stderr に送信されます。

あなたはそこにいますか?

core モジュールの find() 関数は、システムに接続されたデバイスを見つけて列挙するために使用されます。 たとえば、デバイスのvendor ID(ベンダーID)が 0xfffe 、 product ID(製品ID)が 0x0001 であるとします。 それを探したい場合は、次のように進めます:

import usb.core

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

以上で、関数はデバイスを表す usb.core.Device オブジェクトを返します。 デバイスが見つからない場合、 None を返します。 実際には、デバイス・デスクリプタ( Device Descriptor )の任意のフィールドを使用できます。 たとえば、システムにUSBプリンターが接続されているかどうかを確認するにはどうすればよいでしょうか。 これはとても簡単です:

# actually this is not the whole history, keep reading
if usb.core.find(bDeviceClass=7) is None:
    raise ValueError('No printer found')

7は、USB仕様によるプリンタークラスのコードです。 ちょっと待てや!存在するすべてのプリンターを列挙したい場合はどうなりますか?問題ありません:

# this is not the whole history yet...
printers = usb.core.find(find_all=True, bDeviceClass=7)

# Python 2, Python 3, to be or not to be
import sys
sys.stdout.write('There are ' + len(printers) + ' in the system\n.')

何が起きたのでしょう? 少々説明する時がきたようです。 find には find_all というパラメータがあり、デフォルトはFalseです。 false 1 の場合、 find は指定された基準に一致する最初に見つかったデバイスを返します(詳細はもうちょい後で)。 もしあなたが true の値を与えると、 find は代わりに基準に一致するすべてのデバイスのイテレータを返します。 以上です!簡単でしょ?

これで終わり?いいえ。私はあなたにすべての歴史を話したわけではありません。多くのデバイスは実際にはクラス情報をデバイス・デスクリプタ( Device Descriptor )ではなくインターフェイス・デスクリプタ( Interface Descriptor )に入れます。 したがって、システムに接続されているすべてのプリンターを本当に見つけるには、すべての構成、次にすべてのインターフェイスを渡り歩き、いずれかのインターフェイスの bInterfaceClass フィールドが7に等しいかどうかを確認する必要があります。 私のような プログラマー なら、もっと簡単な方法があるのか疑問に思うでしょう。 もちろん答えはYES。 まず、接続されているすべてのプリンターを見つけるための最終的なコードを見てみましょう。

import usb.core
import usb.util
import sys

class find_class(object):
    def __init__(self, class_):
        self._class = class_
    def __call__(self, device):
        # first, let's check the device
        if device.bDeviceClass == self._class:
            return True
        # ok, transverse all devices to find an
        # interface that matches our class
        for cfg in device:
            # find_descriptor: what's it?
            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))

custom_match パラメータは、デバイスオブジェクトを受け取る呼び出し可能なオブジェクトを受け入れます。一致するデバイスの場合はtrueを返し、一致しないデバイスの場合はfalseを返す必要があります。 必要に応じて、 custom_match とデバイスフィールドを組み合わせることもできます:

# find all printers that belongs to our vendor:
printers = usb.core.find(find_all=1, custom_match=find_class(7), idVendor=0xfffe)

今ここでは、0xfffe ベンダーのプリンターのみに関心があります。

お主、何者じゃ!

やった!デバイスを見つけました。が、そのデバイス君とお話をする前に、構成、インターフェース、エンドポイント、転送タイプなどについて詳しく知りたいと思います…

あなたがデバイスを持っている場合は、あなたはオブジェクトのプロパティとして任意のデバイス・デスクリプタ・フィールドにアクセスできます:

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

デバイスで有効な構成にアクセスするときは、あなたはデバイスを反復処理(iterate)できます:

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

同様に、構成を反復(iterate)してインターフェイスにアクセスしたり、インターフェイスを反復(iterate)してエンドポイントにアクセスしたりできます。 各種類のオブジェクトには、それぞれのデスクリプタのフィールドが属性として含まれています。 例を見てみましょう:

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

次のように、添え字を使用してデスクリプタにランダムにアクセスすることもできます:

>>> # access the second configuration
>>> cfg = dev[1]
>>> # access the first interface
>>> intf = cfg[(0,0)]
>>> # third endpoint
>>> ep = intf[2]

ご覧のとおり、インデックスはゼロベースです。 ちょっと待って! 私がインターフェイスにアクセスする方法に奇妙な点があります…はい、そうです、構成の添え字は2つのアイテムのシーケンスを受け入れます。最初のアイテムはインターフェイスのインデックスで、2番目のアイテムは代替設定です。したがって、最初のインターフェイスにアクセスするが、2番目の代替設定には、 cfg[(0,1)] と書き込みます。

それでは、デスクリプタを見つける強力な方法である find_descriptor ユーティリティ関数について学びましょう。 私たちはプリンター検索の例でそれをすでに見ています。 find_descriptorfind とほぼ同じように動作しますが、2つの例外があります:

  • find_descriptor は、最初のパラメータとして、あなたが検索したいデスクリプタの親デスクリプタを受け取ります。

  • backend 2 パラメータはありません。

たとえば、構成デスクリプタ cfg があり、インターフェース1のすべての代替設定を検索したい場合は、次のようにします:

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

find_descriptorusb.util モジュールにあることに注意してください。 また、前述の custom_match パラメータも受け入れます。

同じデバイスを複数扱うには

コンピューター同じデバイスが2接続されていることがあります。それらをどのように区別すればよいでしょうか? Device オブジェクトには、USB仕様の一部ではないが、非常に便利な2つの追加属性があります。それは busaddress 属性です。 ただし、これらの属性はバックエンドからのものであり、バックエンドがそれらをサポートしないことは自由です。その場合、それらは None に設定されます。そうじゃない場合は、これらの属性はデバイスのバス番号とバスアドレスを表し、あなたのご想像どおり、同じ idVendoridProduct 属性を持つ2つのデバイスを区別するために使用できます。

私はどのようにすればいいですか?

接続されたUSBデバイスは、いくつかの標準的なリクエストを使って構成する必要があります。私は USB 仕様の調査を始めたとき、デスクリプタ(descriptors)、構成(configurations)、インターフェイス(interfaces)、代替設定(alternate settings)、転送タイプ(trannsfer types)、その他もろもろで混乱していました。そして最悪なことに、たったひとつの構成設定でも無視したら動かないのです。 PyUSBはあなたがこれをできるだけ簡単にできるようにすることを目指します。たとえば、デバイス・オブジェクトを取得した後、それと通信する前に最初に行う必要があることの1つは、 set_configuration リクエストを発行することです。 このリクエストのパラメータは、あなたが関心のある構成(configuration)にある bConfigurationValue です。 ほとんどのデバイスには構成(configuration)が1つしかなく、使用する構成値を追跡するのは面倒です(私が見たほとんどは単純にハードコーディングしています)。それゆえ PyUSBでは、あなたが引数なしで set_configuration を呼び出すだけで済むようにしてあります。 この場合、最初に見つかった構成(configuration)が設定されます(デバイスに1つしかない場合は、その構成値を気にする必要はありません)。 たとえば、 bConfigurationValue フィールドが5 3 に等しい構成(configuration)デスクリプタが1つあるデバイスがあるとします。以下の呼び出しはいずれも同様に機能します:

>>> dev.set_configuration(5)
# or
>>> dev.set_configuration() # we assume the configuration 5 is the first one
# or
>>> cfg = util.find_descriptor(dev, bConfigurationValue=5)
>>> cfg.set()
# or
>>> cfg = util.find_descriptor(dev, bConfigurationValue=5)
>>> dev.set_configuration(cfg)

えっへん! Configuration オブジェクトを set_configuration のパラメータとして使用できます! はい、そしてそれ自身を当座(current)の構成(configuration)として設定するための set メソッドもあります。

構成する必要がある場合と必要ない場合があるもう1つの設定は、インターフェイスの代替設定です。各デバイスは一度に1つのアクティブ化された構成(configration)のみを持つことができ、各構成(configuration)には複数のインターフェイスがあり、すべてのインターフェイスを同時に使用できます。 インターフェイスを論理デバイスと考えると、この概念をよりよく理解できます。 たとえば、多機能プリンターを想像してみましょう。これは同時にプリンターとスキャナーです。 物事をシンプルに(または少なくともできるだけシンプルに)保つために、構成(configuration)が1つしかないと考えてみましょう。 プリンターとスキャナーがあるため、構成には2つのインターフェースがあり、1つはプリンター用、もう1つはスキャナー用です。 複数のインターフェースを持つデバイスは、複合デバイスと呼ばれます。 多機能プリンターをコンピューターに接続すると、オペレーティングシステムは2つの異なるドライバーをロードします。すなわち、あなたがお持ちの「論理」機器ごとに1つです。 4

じゃあ代替設定(alternate setting)の方はどうなの? ええ、インターフェイスには1つ以上の代替設定があります。 代替設定が1つしかないインターフェイスは、代替設定がないと見なされます( 5 )。 代替設定(alternate setting)はインターフェイス用であり、構成(configuration)はデバイス用です。つまり、各インターフェイス対して、アクティブにできる代替設定(alternate setting)は1つだけです。 たとえば、USB仕様では、デバイスのプライマリ代替設定(alternate setting)にアイソクロナス・エンドポイント(isochronous endpoint)を含めることはできないため( 6 ) 、ストリーミングデバイスには少なくとも2つの代替設定(alternate setting)が必要で、2番目の設定にはアイソクロナス・エンドポイント(isochronous endpoint)があります。 ただし、構成(configuration)とは異なり、代替設定(alternate setting)が1つだけのインターフェイスを設定する必要はありません( 7 )。 set_interface_altsetting 関数でインターフェイスの代替設定(alternate setting)を選択します:

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

警告

USB仕様では、追加の代替設定(alternate settings)のないインターフェイスに対するSET_INTERFACEリクエストを受信した場合、デバイスはエラーを返すことが許可されているとされています。したがって、インターフェースに複数の代替設定(alternate settings)があるか、またはSET_INTERFACEリクエストを受け入れるかどうか不明な場合は、次のようにtry-exceptブロック内で set_interface_altsetting を呼び出すのが最も安全な方法です:

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

あなたは関数のパラメータとして Interface オブジェクトを使用することもでき、 その際、 interfacealternate_setting パラメータは bInterfaceNumberbAlternateSetting フィールドから自動的に推論されます。 例えば:

>>> intf = find_descriptor(...)
>>> dev.set_interface_altsetting(intf)
>>> intf.set_altsetting() # wow! Interface also has a method for it

警告

その Interface オブジェクトはアクティブな構成(configuration)デスクリプタに属している必要があります。

私とお話して頂戴

いよいよUSBデバイスと通信する方法を学ぶ時が来ました。 USBには、バルク(bulk)と割り込み(interrupt)とアイソクロナス(isochronous)と制御(control)の、4種類の転送方法があります。 私は各転送方法の目的とそれらの間の違いを説明するつもりはありません。私は、あなたが少なくともUSB転送の基本を知っているものとして扱います。

制御(control)転送は、仕様に記載されている構造化データを持つ唯一の転送です。他の転送は、USBの観点からは生データを送受信するだけです。 そのため、制御(control)転送を処理するためだけに別の関数があり、他のすべての転送は同じ関数によって管理されます。

あなたは ctrl_transfer メソッドを介して制御(control)転送を発行します。それはOUT転送とIN転送の両方に使用されます。転送方向は bmRequestType パラメータによって決定されます。

ctrl_transfer パラメータは制御要求構造(control request structure)とほとんど同じです。 以下は、制御(control)転送を行う方法の例です(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

この例では、デバイスがループバック・パイプとして機能する2つのカスタム制御要求(custom control request)を実装していると想定しています。 CTRL_LOOPBACK_WRITE メッセージで書き込んだものは、 CTRL_LOOPBACK_READ メッセージで読み取ることができます。

最初の4つのパラメータは、標準制御転送構造(standard control transfer)のフィールド bmRequestTypebmRequestwValuewIndex です。 5番目のパラメーター(訳注:data_or_wLength=None)は、OUT転送のデータ・ペイロード、またはIN転送で読み取るバイト数です。 データペイロードは、array __init__ メソッドのパラメーターとして使用できる任意のシーケンス型にすることができます。 データ・ペイロードがない場合、パラメータは None (またはIN転送の場合は0)でなければなりません。 操作のタイムアウトを指定する最後のオプションパラメータ(訳注:timeout=None)が1つあります。 指定しない場合は、デフォルトのタイムアウトが使用されます(詳しくは後で説明します)。 OUT転送では、戻り値は実際にデバイスに送信されたバイト数です。IN転送では、戻り値はデータが読み込まれた array オブジェクトです。

他の転送については、メソッド writeread を使用して、データの書き込みと読み取りを行います。転送タイプを気にする必要はありません。転送タイプはエンドポイント・アドレスから自動的に決定されます。エンドポイント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

最初(endpoint)と3番目(timeout)のパラメーターはread/write両方のメソッドで等しくそれぞれエンドポイント・アドレスとタイムアウトです。 2番目のパラメーターは、データ・ペイロード(write)または読み取るバイト数(read)です。 read メソッドの戻り値は array オブジェクトのインスタンスで、write メソッドの戻り値はで書き込んだバイト数です。

ベータ2バージョン以降、バイト数の代わりに、データが読み込まれる array オブジェクトを readctrl_transfer に渡すこともできます。 この場合、読み込むバイト数は、配列の長さと array.itemsize 値の積になります。

ctrl_transfer と同様に、 timeout パラメータはオプションです。 timeout が省略された場合、操作のタイムアウト値として Device.default_timeout プロパティが使用されます。

自分自身を制御する

転送関数に加えて、モジュール usb.control は、標準のUSB制御要求(standard USB control request)を実装する関数を提供し、 usb.util モジュールには、文字列デスクリプタを返すための便利な関数 get_string があります。

オマケ

優れた抽象化の背後には、優れた実装があります

初期の頃は libusb しかありませんでした。次にlibusb 1.0が登場し、libusb 0.1と1.0がリリースされました。 その後、OpenUSB が開発され、現在私たちは、USBライブラリのバベルの塔( Tower of Babel )に住んでいます(9) 。 PyUSBはそれをどのように扱いますか? ええ、PyUSBは民主的なライブラリです。あなたは好きなライブラリを選択できます。 実際、独自のUSBライブラリを最初から作成して、PyUSBに使用するように指示することができます。 しかし、あなたはたぶんlibusb 1.0を使用するのがいいでしょう。

find 関数には、まだ説明していないパラメータがあります。これは backend パラメータです。 指定しない場合は、組み込みのバックエンドの1つが使用されます。 バックエンドは usb.backend.IBackend から継承されたオブジェクトで、オペレーティング・システム固有のUSBの実装を担当します。ご想像のとおり、既にPyUSBに組み込み済なのはlibusb 1.0(デフォルト)とlibusb 0.1とOpenUSB(非推奨)バックエンドです。

あなたは独自のバックエンドを作成・使用することができます。 IBackend から継承し、必要なメソッドを実装するだけです。 方法については、 usb.backend パッケージのドキュメントをご覧ください。

ホンマわがままやなぁ…

Pythonには、自動メモリ管理 (automatic memory management)と呼ばれるものがあります。 これは、仮想マシンがオブジェクトをメモリから解放するタイミングを決定することを意味します。 内部的には、PyUSBは動作する必要のあるすべての低レベルのリソース(インターフェイスの要求、デバイス・ハンドルなど)を管理し、ほとんどのユーザーはそのことを心配する必要はありません。ただし、Pythonのオブジェクトの自動破棄には非決定的(nondetrministic)な性質があるため、ユーザーは割り当てられたリソースがいつ解放されるかを予測できません。一部のアプリケーションは、確定的にリソースを割り当てて解放する必要があります。これらの種類のアプリケーションのために、 usb.util モジュールはリソース管理を扱うための一連の関数を持っています。

あなたがインターフェイス(interface)を手動で要求(claim)および解放(release)したい場合、あなたは claim_interface 関数と release_interface 関数を使用できます。 claim_interface は、デバイスが、あなたが指定したインターフェイスをまだ要求していない場合、それを要求します。 デバイスが既にインターフェイスを要求済であある場合は何もしません。 同様に、 release_interface は、あなたが指定のインターフェイスが既に要求(claim)済の場合、それを解放します。まだインターフェイスが要求されていない場合は何もしません。あなたはこの手動インターフェイス要求(manual interface claim)を使用して、libusb のドキュメントに記載されている構成選択の問題( configuration selection problem )を解決できます。

あなたが、デバイス・オブジェクトによって割り当てられたすべてのリソース(要求済のインターフェースを含む)を解放したい場合は、 dispose_resources 関数を使用できます。 割り当てられたすべてのリソースを解放し、デバイス・オブジェクト(デバイス・ハードウェア自体ではない)を、find 関数呼び出しから戻ってきた直後の状態にします。

ライブラリを手動で指定する

一般に、バックエンドは、USBアクセスAPIを実装する共有ライブラリのラッパーです。 デフォルトでは、バックエンドは find_library() ctypes 関数を使用します。 Linuxや他のUNIX風オペレーティングシステムでは、 find_library は外部プログラム( /sbin/ldconfiggccobjdump など)を実行してライブラリファイルを見つけようとします。

これらのプログラムがないか、ライブラリ・キャッシュが無効になっているシステムでは、この機能は使用できません。 この制限を克服するために、PyUSBではカスタムの find_library() 関数をバックエンドに提供できます。

このようなシナリオの例です:

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

get_backend() 関数の find_library 引数に注意してください。この引数では、バックエンドの正しいライブラリを見つける責任がある関数を指定します。

古臭いルール

あなたが古いPyUSB API(バージョン 0.X 系統)を使用してアプリケーションを作成していた場合、新しいAPIを使用するようにコードを更新する必要があるかどうかを悩んでいるかもしれません。まあ、できればそうすべきですが、そうする必要はありません。 PyUSB 1.0には usb.legacy 互換モジュールが付属しています。新しいAPIの上に古いAPIを実装します。「では、アプリケーションを機能させるために、import usbimport usb.legacy as usb に置き換えるだけでよいですか?」答えは「はい」です。これでうまくいきますが、でも、これすらも必要はありません。 アプリケーションをそのまま実行すると、 usb.legacy からすべてのパブリック・シンボルが import usb ステートメントによってインポートされるため、そのまま機能します。問題が発生した場合は、おそらくあなたは何かしらバグを発見したということです。(訳注:とは言え、互換なのはあくまでPyUSBだけであってPyUSBが参照するPython標準ライブラリが変更になっていることがある。手元ではUSBError.messageをUSBError.strerrorに修正する必要があった。USBErrorがIOErrorを継承しているため。)

1

TrueまたはFalse(先頭大文字)とは、Python言語のそういう値を意味します。そして私がtrueやfalseを言うとき、それはtrueとfalseに評価されるPythonのあらゆる表現を意味します。

2

各々のバックエンドのドキュメントを参照してください。

3

USB仕様では、構成(configuration)に順番になるような値を義務付けていません(訳注:次の値が+1で得られると期待するな、飛び飛びバラバラを覚悟せよということ)。 インターフェイスと代替設定(alternate setting)番号についても同様です。

4

実際はもう少し複雑ですが、ここでの説明はとしては十分です。

5

私もそれが奇妙に聞こえることは知っています。

6

これは、デバイスの構成(configuration)時にアイソクロナス転送用の帯域幅がない場合、デバイスを成功裏に列挙できるためです。

7

デバイスは未構成の状態(unconfigured state)であることが許可されているため、これは構成(configration)では発生しません。

8

PyUSBでは、制御(control)転送はエンドポイント0でのみ発行されます。代替制御エンドポイント(alternate control endpoint)を持つデバイスは非常に非常にまれです(私はそのようなデバイスを全く見たことがありません)。

9

単なる冗談です。真剣に受け止めないでください。 でも、多くの選択肢は、選択肢がないよりも優れています。