PyUSB 私家版日本語訳(3)

By ttakao, 2017年1月27日

USBを介して周辺装置と会話する

以下、USB接続、通信方法の基本をこちらあたりで学習されてから読まれることを強くお勧めします。

Pythonは3をお勧めします。(っていうか、2009年に発表なんだから、いい加減にpython2は終わってほしい。あちこちに残ってて邪魔)

とはいえ、pyusbは古くネットにもいいサンプルがありませんし、面倒くさいようです。
よほどの理由がないと使うことはお勧めしません。

 

pyUSBとは

もちろん、
PyUSBはPythonで書かれたUSBドライバーとコミュニケーションするパッケージです。
サポートされるドライバーはlibusb 0.1, libusb 1.0とOpen USBです。

 

パッケージの中には以下のモジュールが含まれます。

モジュール 役割
core メインのUSBのモジュール
util ユーティリティ機能
control コントロール
legacy 0.x時代の互換性のため
backend 組み込みバックエンドのはいったサブパッケージ

サンプル

サンプルコードは以下のとおり

最初の二行

PyUSBパッケージを持ち込む命令で、usb.coreはメインであり、usb.utilはユーティリティ機能を担うものです。

目的のデバイスを探し、見つけたらオブジェクトのインスタンスを作る命令です。
もし、見つからないとNoneが帰ってきます。

使うためのコンフィギュレーションをセットします。ここで引数がありませんが、PyUSBは多数あるコンフィギュレーションについて最も平均的なデバイス用に省略値をもっています。ここでは特にfindでみつけたコンフィグレーションがセットされます。

操作したいエンドポイントを探し、最初のインターフェースの中を調べます。エンドポイントをみつけた後、データをおくるのです。、エンドポイントとはデバイス側がもつ通信のためのFIFOバッファのことをいいます。

実はエンドポイントはUSBデバイスをホストに接続した途端に0はできます。
このエンドポイントを利用して、コンフィグレーションのやり取りをする必要があるからです。
これを「エンドポイント0」とか「FIFO0」とか言います。

エンドポイントは0以外は双方向通信できません。1-15 の番号と方向INかOUTを定義せねばなりません。
(USB3.0はIN/OUTの指定もできるようです。)

assertはPythonの命令で予期した値でなければ、例外を発生します。

もし、すでにエンドポイントのアドレスが決まっているならば、このようにwriteすればいいです。

この例ではエンドポイントは1です。

エラーハンドリング

PyUSBはどの機能についてもエラーならばエクセプションをあげます。エラーはusb.core.USBErrorにあります。

したがってエラーハンドリングコードは次のようになります。

また、PyUSBはログ機能もあります。loggingモジュールを使うためには、環境変数PYUSB_DEBUGをセットしてください。critical, error, warning, info, debugがあります。
省略値のメッセージはsys.stでっろrに送られます。ログメッセージをファイルにリダイレクトしたい場合は環境変数PYUSB_LOG_FILENAMEにファイル名を設定してください。

目的のUSBデバイスの見つけ方

coreモジュールにあるfind()関数は接続されているデバイスを列挙します。たとえば、Vender IDが0xfffeでProduct IDが0x0001ならば、こういうコードになります。

usb.core.Deviceオブジェクトが、そのデバイスを意味することになります。失敗するとNoneが入ります。
デバイスの詳細情報はDescriptorにあります。例えば、USBが接続されているかを調べるには次のようになります。

7はUSBの仕様でプリンタークラスを示すコードです。
逆にプリンターだけをリストすることもできます。

find関数のfind_allのパラメーターは省略値はFalseです。Falseの場合、findeは指定した(後述)条件に適合する最初のデバイスを戻してきます。Trueの場合はマッチしたデバイスをリストの形で戻します。

他にもfind関数で独自の条件でデバイスを探すことができます。同じプリンターを探すルーチンです。
find関数でcustom_matchにクラスを渡し、クラス内のbDeviceClassでプリンターをみつけているところに注意してください。

oxfffeベンダーのプリンターを見つけるには次のように書けばいいことになります。

USB装置を見つける単体プログラムとしては、これ。
これでつながっているUSBデバイスがリストされます。

デスクリプター(詳細情報)

さて、デバイスを見つけたら、コンフィグレーション、インターフェース、エンドポイント、転送方式などをしりたくなります。

対話式には次のように行うとフィールド値がわかります。

個々のデバイスのコンフィグレーションを知るためには次のようなループとなります。

同様にしてコンフィグレーションからインターフェースを知り、インターフェースからエンドポイントがわかります。
こんな感じ。

順々に見ていかなくても、値を指定することでデスクリプターを入手することもできます。

ご存知のようにインデックスは0から始まります。しかし、すでにインターフェースが他のものと接続されているかもしれません。そういう場合、二番目の代替アドレスをcfg[(0,1)]というようにセットできます。

ここまでくると、find_descriptorユーティリティ関数を使えます。
すでにプリンターの例を見ましたが、find_desciptorはfindと似た機能ですが、次の二点が違います。

  • find_descriptorは見つけた最初のデスクリプターを返します。
  • backendパラメタがない

たとえば、コンフィグレーションデスクリプターcfgがすでにあり、インターフェース1以外のセッティングすべてを見つけたいならば、

usb.utilモジュール内のfind_descriptorは先に書いたcustom_matchパラメーターと似たことができます。

複数のデバイスを扱う場合、、、(省略)

注意

USBデバイスは仕様で、追加の設定なしでSET_UNTERFACE要求を受け取るとエラーを返します。したがってひとつ以上の別の設定のないインターフェースを設定する場合、try-exceptブロックにしておくことです。

また、関数のパラメータとしてIntrerfaceオブジェクトを使うと、interfaceとalternate_settingパラメータは自動的にbInterfaceNumberとbAlternateSettingフィールドから推測されます。

Interfaceオブジェクトは現在アクティブなコンフィグレーションデスクリプターに属していなければなりません。

転送方式

ここまで来るとデータの転送ができます。転送方式はコントロール転送、バルク転送、インタラプト転送、アイソクロナス転送があります。

コントロール転送は仕様に基づく構造化されたデータを転送する唯一の方法です。他の転送方式はデータを送受信するだけの方法です。

コントロール転送にはctrl_transferメソッドを使います。INでもOUTでも使います。方向はbmRequestTypeパラメーターで決めます。

ctrl_transferパラメーターはコントロール要求の構造とほぼおなじパラメーター群です。以下は例です。

この例ではふたつのカスタムコントロールがループバックのパイプとして定義されています。CTRL_LOOPBACK_WRITEでメッセージをかきだすと、CTRL_LOOPBACK_READでメッセージを読めます。

最初の4つのパラメーターはbmRequestType, bmRequest, wValue, wIndexです。
これらはコントロール転送で必要な標準の構造です。5番目のパラメーターはOUTで送出するデータ自身か、INでよみこむバイト数です。

送出するデータはどんな__init__メソッドで示された配列で使えるタイプならどれでも可能です。
もし、送出データがないならばNone(もしくは、INの場合は0)を設定します。
最後にタイムアウト時を設定できます。設定しなければ、デフォルトのタイムアウト値になります。
OUT転送において、戻る値は送出したデータのバイト数です。IN転送では読んだデータの配列オブジェクトが戻ります。

ほかのタイプの転送ではwriteとreadが使えます。read,writeの場合は転送タイプをきにすることはなく、エンドポイントのアドレスから自動的に転送モードは決まります。
このサンプルはエンドポイント11のパイプのループバックを使った例です。

最初と三番目のパラメータはどちらのメソッドでも同じで、エンドポイントのアドレスとタイムアウト値です。二番目のパラメタはwriteの場合はデータでreadの場合は読むデータの長さです。
戻り値はreadメソッドの場合は配列オブジェクトでwriteの場合はバイト数です。

バージョン2のベータ依頼、バイト数の変わりにreadの時にctrl_transferにデータが読み込まれる配列オブジェクトを用意して渡すことができるようになりました。この場合、読んだバイト数はarray.itmsize値の配列の数となります。

ctrl_transferではtimeoutパラメーターはオプションです。ない時はDevice.default_timeoutプロパティが使われます。

以下、再び省略。

 

実際に書くためには

あんまり面倒くさいことを知らなくてもなんとかなった。
ネットにあるサンプルをふたつ掲載しておくが、どうも古いようだ。
理由はpyusbでbulkread, bulkwriteはlegacyフォルダーにあり、古いインターフェースであるらしい。
endpointクラスのソースコードを読むとwrite, read, clear_haltしかない。

write(self, data, timeout=None)
エンドポイントにデータを送出します。データとタイムアウトを設定します。転送タイプとエンドポイントアドレスは自動的に継承されます。
戻り値は書き込んだバイト数です。(selfはpythonのクラスを参照)
read(self, size_or_buffer, timeout=None)
エンドポイントから読み込みます。バッファーサイズかバッファー配列を指定しておきます。
clear_halt(self)
エンドポイントのhalt/statusコンディションをクリアします。

サンプル

サンプル2

 

サンプル3(SCPIで測定器と会話する。どこにもサンプルがなかった。俺すごい)
SCPIは測定器を操作するための共通のコマンドです。かなりの装置がコントロールできます。

このサンプルを動かすための手順(以下全部、環境はWindowsです)

1.準備
USBドライバーとしてlibusb-win32をインストールします。
私はOWONのAG-015Fで試しましたが、添付のwaveform generatorをインストールすることで同時にインストールできました。
waveform generatorにはSCPIコマンドを試せる機能がありますから、テストして動作を確認できます。

Pythonの準備をします。
Pythonからpyusbパッケージをインストールしてください。
コマンド例:
>>>pip install pyusb

2.設定
下のプログラムの中に
dev = usb.core.find(idVendor=0xxxxx, idProduct=0xxxxx)
があります。
ここで目的の装置のidVendorとidProductを見つける必要があります。

上に記載している「USB装置を見つける単体プログラム」を実行し、idVendorとidProductを見つけます。
実行するとパソコンに接続されているUSB装置のidvenderとidProductがリストされます。
複数接続されていて、どれが目的の装置かわからない場合は一度目的の装置をはずしてfindusb.pyを実行し、接続した時に実行したリストとくらべてください。

3.実行
以下のサンプルの中のidVendorとidProductを書き換えます。
それだけで実行可能です。
SCPIコマンド、*IDN? の答えが目的の装置から戻ります。

What do you think?

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です