WebUSB API を最大限に活用するデバイスを構築する。
この記事では、WebUSB API を最大限に活用できるデバイスを作成する方法について説明します。API 自体の概要については、ウェブで USB デバイスにアクセスするをご覧ください。
背景
ユニバーサル シリアル バス(USB)は、周辺機器をデスクトップ コンピューティング デバイスやモバイル コンピューティング デバイスに接続するための最も一般的な物理インターフェースになっています。USB 仕様には、バスの電気特性とデバイスとの通信の一般的なモデルの定義に加えて、デバイスクラスの仕様が含まれています。これらは、デバイス メーカーが実装できる特定のデバイスクラス(ストレージ、オーディオ、ビデオ、ネットワーキングなど)の一般的なモデルです。これらのデバイスクラス仕様の利点は、オペレーティング システム ベンダーがクラス仕様に基づいて単一のドライバを実装できること(「クラスドライバ」)と、そのクラスを実装するすべてのデバイスがサポートされることです。これは、各メーカーが独自のデバイス ドライバを記述する必要があった場合と比べて大きな改善でした。
ただし、一部のデバイスは、これらの標準化されたデバイスクラスのいずれにも該当しません。メーカーは、ベンダー固有のクラスを実装しているデバイスとしてラベル付けすることもできます。この場合、OS はベンダーのドライバ パッケージに提供されている情報に基づいて、読み込むデバイス ドライバを選択します。通常は、特定のベンダー固有のプロトコルを実装することが知られているベンダー ID とプロダクト ID のセットです。
USB のもう 1 つの特徴は、デバイスが接続先のホストに複数のインターフェースを提供できることです。各インターフェースは、標準化されたクラスを実装することも、ベンダー固有のクラスを実装することもできます。オペレーティング システムがデバイスを処理する適切なドライバを選択すると、各インターフェースは異なるドライバによって要求できます。たとえば、USB ウェブカメラは通常、2 つのインターフェースを提供します。1 つは USB ビデオクラスを実装し(カメラ用)、もう 1 つは USB 音声クラスを実装します(マイク用)。オペレーティング システムは、単一の「ウェブカメラ ドライバ」を読み込むのではなく、デバイスの個別の機能を担当する独立したビデオ クラス ドライバとオーディオ クラス ドライバを読み込みます。このインターフェース クラスの構成により、柔軟性が向上します。
API の基本
標準の USB クラスの多くには、対応するウェブ API があります。たとえば、ページは getUserMedia()
を使用して動画クラスのデバイスから動画をキャプチャしたり、KeyboardEvents または PointerEvents をリッスンするか、Gamepad または WebHID API を使用して、ヒューマン インターフェース(HID)クラスのデバイスから入力イベントを受信したりできます。すべてのデバイスが標準化されたクラス定義を実装しているわけではないように、すべてのデバイスが既存のウェブ プラットフォーム API に対応する機能を実装しているわけではありません。このような場合、WebUSB API は、サイトがベンダー固有のインターフェースを宣言し、ページ内から直接そのサポートを実装できるようにすることで、そのギャップを埋めることができます。
WebUSB 経由でデバイスにアクセスするための具体的な要件は、オペレーティング システムが USB デバイスを管理する方法の違いにより、プラットフォームによって若干異なります。ただし、基本的な要件として、ページが制御するインターフェースをデバイスがすでに宣言していないことが挙げられます。これは、OS ベンダーが提供する汎用クラス ドライバまたはベンダーが提供するデバイス ドライバのいずれかです。USB デバイスは複数のインターフェースを提供でき、それぞれに独自のドライバがある場合があります。そのため、一部のインターフェースをドライバが要求し、他のインターフェースをブラウザがアクセスできるようにするデバイスを構築できます。
たとえば、ハイエンドの USB キーボードは、オペレーティング システムの入力サブシステムによって要求される HID クラス インターフェースと、WebUSB で使用可能で、構成ツールで使用できるベンダー固有のインターフェースを提供できます。このツールはメーカーのウェブサイトで提供され、ユーザーはプラットフォーム固有のソフトウェアをインストールしなくても、マクロキーや照明効果など、デバイスの動作の一部を変更できます。このようなデバイスの構成記述子は次のようになります。
値 | フィールド | 説明 |
---|---|---|
構成記述子 | ||
0x09 |
bLength | この記述子のサイズ |
0x02 |
bDescriptorType | 構成記述子 |
0x0039 |
wTotalLength | この記述子シリーズの合計長 |
0x02 |
bNumInterfaces | インターフェースの数 |
0x01 |
bConfigurationValue | 構成 1 |
0x00 |
iConfiguration | 構成名(なし) |
0b1010000 |
bmAttributes | リモート ウェイクアップ機能付きの電池駆動デバイス |
0x32 |
bMaxPower | 最大電力は 2 mA 単位で表されます |
インターフェース記述子 | ||
0x09 |
bLength | この記述子のサイズ |
0x04 |
bDescriptorType | インターフェース記述子 |
0x00 |
bInterfaceNumber | インターフェース 0 |
0x00 |
bAlternateSetting | 代替設定 0(デフォルト) |
0x01 |
bNumEndpoints | 1 個のエンドポイント |
0x03 |
bInterfaceClass | HID インターフェース クラス |
0x01 |
bInterfaceSubClass | ブート インターフェースのサブクラス |
0x01 |
bInterfaceProtocol | キーボード |
0x00 |
iInterface | インターフェース名(なし) |
HID 記述子 | ||
0x09 |
bLength | この記述子のサイズ |
0x21 |
bDescriptorType | HID 記述子 |
0x0101 |
bcdHID | HID バージョン 1.1 |
0x00 |
bCountryCode | ハードウェアの対象国 |
0x01 |
bNumDescriptors | 続く HID クラス記述子の数 |
0x22 |
bDescriptorType | レポート記述子のタイプ |
0x003F |
wDescriptorLength | レポート記述子の合計長 |
エンドポイント記述子 | ||
0x07 |
bLength | この記述子のサイズ |
0x05 |
bDescriptorType | エンドポイント記述子 |
0b10000001 |
bEndpointAddress | エンドポイント 1(インド) |
0b00000011 |
bmAttributes | 応答を中断する |
0x0008 |
wMaxPacketSize | 8 バイト パケット |
0x0A |
bInterval | 10 ミリ秒間隔 |
インターフェース記述子 | ||
0x09 |
bLength | この記述子のサイズ |
0x04 |
bDescriptorType | インターフェース記述子 |
0x01 |
bInterfaceNumber | インターフェース 1 |
0x00 |
bAlternateSetting | 代替設定 0(デフォルト) |
0x02 |
bNumEndpoints | 2 つのエンドポイント |
0xFF |
bInterfaceClass | ベンダー固有のインターフェース クラス |
0x00 |
bInterfaceSubClass | |
0x00 |
bInterfaceProtocol | |
0x00 |
iInterface | インターフェース名(なし) |
エンドポイント記述子 | ||
0x07 |
bLength | この記述子のサイズ |
0x05 |
bDescriptorType | エンドポイント記述子 |
0b10000010 |
bEndpointAddress | エンドポイント 1(インド) |
0b00000010 |
bmAttributes | 一括 |
0x0040 |
wMaxPacketSize | 64 バイトのパケット |
0x00 |
bInterval | 一括エンドポイントの場合はなし |
エンドポイント記述子 | ||
0x07 |
bLength | この記述子のサイズ |
0x05 |
bDescriptorType | エンドポイント記述子 |
0b00000011 |
bEndpointAddress | エンドポイント 3(OUT) |
0b00000010 |
bmAttributes | 一括 |
0x0040 |
wMaxPacketSize | 64 バイトのパケット |
0x00 |
bInterval | 一括エンドポイントの場合はなし |
構成記述子は、複数の記述子が連結されたものです。各フィールドは bLength
フィールドと bDescriptorType
フィールドで始まるため、識別できます。最初のインターフェースは、関連付けられた HID 記述子と、入力イベントをオペレーティング システムに配信するために使用される単一のエンドポイントを持つ HID インターフェースです。2 つ目のインターフェースはベンダー固有のインターフェースで、2 つのエンドポイントがあり、デバイスにコマンドを送信してレスポンスを受信するために使用できます。
WebUSB 記述子
WebUSB はファームウェアを変更しなくても多くのデバイスで動作しますが、追加機能を有効にするには、WebUSB のサポートを示す特定の記述子でデバイスをマークする必要があります。たとえば、デバイスが電源に接続されているときにブラウザがユーザーを誘導するランディング ページの URL を指定できます。
バイナリ デバイス オブジェクト ストア(BOS)は USB 3.0 で導入されたコンセプトですが、バージョン 2.1 の一部として USB 2.0 デバイスにもバックポートされています。WebUSB のサポートを宣言するには、まず BOS 記述子に次のプラットフォーム機能記述子を追加します。
値 | フィールド | 説明 |
---|---|---|
バイナリ デバイスのオブジェクト ストア記述子 | ||
0x05 |
bLength | この記述子のサイズ |
0x0F |
bDescriptorType | バイナリ デバイスのオブジェクト ストア記述子 |
0x001D |
wTotalLength | この記述子シリーズの合計長 |
0x01 |
bNumDeviceCaps | BOS 内のデバイス機能記述子の数 |
WebUSB プラットフォームの機能記述子 | ||
0x18 |
bLength | この記述子のサイズ |
0x10 |
bDescriptorType | デバイスの機能記述子 |
0x05 |
bDevCapabilityType | プラットフォームの機能記述子 |
0x00 |
bReserved | |
{0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47, 0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65} |
PlatformCapablityUUID | リトル エンディアン形式の WebUSB プラットフォーム機能記述子 GUID |
0x0100 |
bcdVersion | WebUSB 記述子 バージョン 1.0 |
0x01 |
bVendorCode | WebUSB の bRequest 値 |
0x01 |
iLandingPage | ランディング ページの URL |
プラットフォーム機能 UUID は、デバイスに関する基本情報を提供する WebUSB プラットフォーム機能記述子としてこれを識別します。ブラウザがデバイスの詳細情報を取得するには、bVendorCode
値を使用してデバイスに追加のリクエストを送信します。現在指定されているリクエストは、URL 記述子を返す GET_URL
のみです。これらは文字列記述子に似ていますが、URL を最小のバイト数でエンコードするように設計されています。"https://google.com"
の URL 記述子は次のようになります。
値 | フィールド | 説明 |
---|---|---|
URL 記述子 | ||
0x0D |
bLength | この記述子のサイズ |
0x03 |
bDescriptorType | URL 記述子 |
0x01 |
bScheme | https:// |
"google.com" |
URL | UTF-8 でエンコードされた URL コンテンツ |
デバイスを初めて接続すると、ブラウザは次の標準 GET_DESCRIPTOR
コントロール転送を発行して BOS 記述子を読み取ります。
bmRequestType | bRequest | wValue | wIndex | wLength | データ(レスポンス) |
---|---|---|---|---|---|
0b10000000 |
0x06 |
0x0F00 |
0x0000 |
* | BOS 記述子 |
このリクエストは通常 2 回行われます。最初のリクエストでは、ホストが大きな転送を commit せずに wTotalLength
フィールドの値を検出できるように十分な大きさの wLength
が使用され、2 回目のリクエストでは、完全な記述子の長さが判明したときに行われます。
WebUSB プラットフォーム キャパビティ記述子の iLandingPage
フィールドがゼロ以外の値に設定されている場合、ブラウザは、bRequest
をプラットフォーム キャパビティ記述子の bVendorCode
値に設定し、wValue
を iLandingPage
値に設定してコントロール転送を発行することで、WebUSB 固有の GET_URL
リクエストを実行します。GET_URL
のリクエスト コード(0x02
)は wIndex
に格納します。
bmRequestType | bRequest | wValue | wIndex | wLength | データ(レスポンス) |
---|---|---|---|---|---|
0b11000000 |
0x01 |
0x0001 |
0x0002 |
* | URL 記述子 |
このリクエストは、最初に読み取られる記述子の長さをプローブするために、2 回発行される場合があります。
プラットフォーム固有の考慮事項
WebUSB API は USB デバイスにアクセスするための一貫したインターフェースを提供しようとしていますが、デベロッパーは、デバイスにアクセスするためのウェブブラウザの要件など、アプリに課せられる要件に注意する必要があります。
macOS
macOS では特別な設定は必要ありません。WebUSB を使用するウェブサイトは、デバイスに接続し、カーネル ドライバまたは別のアプリケーションによって要求されていないインターフェースを要求できます。
Linux
Linux は macOS に似ていますが、ほとんどのディストリビューションでは、デフォルトで USB デバイスを開く権限を持つユーザー アカウントが設定されていません。udev というシステム デーモンが、デバイスにアクセスできるユーザーとグループを割り当てる役割を担っています。このようなルールでは、指定されたベンダー ID と製品 ID に一致するデバイスの所有権が、周辺機器にアクセスできるユーザーの共通グループである plugdev
グループに割り当てられます。
SUBSYSTEM=="usb", ATTR{idVendor}=="XXXX", ATTR{idProduct}=="XXXX", GROUP="plugdev"
XXXX
は、デバイスのベンダー ID とプロダクト ID の 16 進数に置き換えます。たとえば、ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e11"
は Nexus One スマートフォンに対応します。正しく認識されるようにするには、通常の「0x」接頭辞を付けて、すべて小文字で記述する必要があります。デバイスの ID を確認するには、コマンドライン ツール lsusb
を実行します。
このルールは /etc/udev/rules.d
ディレクトリのファイルに配置し、デバイスが接続されるとすぐに有効になります。udev を再起動する必要はありません。
Android
Android プラットフォームは Linux をベースとしていますが、システム構成を変更する必要はありません。デフォルトでは、オペレーティング システムにドライバが組み込まれていないデバイスは、ブラウザで使用できます。ただし、デバイスに接続する際にユーザーが追加の手順を行う必要があることに、デベロッパーは注意する必要があります。ユーザーが requestDevice()
の呼び出しに応答してデバイスを選択すると、Chrome にデバイスへのアクセスを許可するかどうかを尋ねるメッセージが Android に表示されます。このプロンプトは、デバイスへの接続権限がすでに付与されているウェブサイトにユーザーが戻り、ウェブサイトが open()
を呼び出す場合にも再び表示されます。
また、デフォルトで含まれるドライバが少ないため、デスクトップ Linux よりも Android でアクセスできるデバイスが増えます。たとえば、Android SDK にはシリアル デバイスと通信するための API がないため、USB-シリアル アダプタで一般的に実装されている USB CDC-ACM クラスが含まれていません。
ChromeOS
ChromeOS も Linux をベースとしており、システム構成の変更も必要ありません。permission_broker サービスは USB デバイスへのアクセスを制御し、未使用のインターフェースが 1 つ以上ある限り、ブラウザが USB デバイスにアクセスできるようにします。
Windows
Windows ドライバモデルには追加の要件があります。上記のプラットフォームとは異なり、ドライバが読み込まれていない場合でも、ユーザー アプリケーションから USB デバイスを開く機能はデフォルトではありません。代わりに、アプリケーションがデバイスにアクセスするために使用するインターフェースを提供するために、特別なドライバ(WinUSB)を読み込む必要があります。これは、システムにインストールされているカスタム ドライバ情報ファイル(INF)を使用するか、デバイス ファームウェアを変更して、列挙中に Microsoft OS 互換性記述子を提供する方法で行うことができます。
ドライバ情報ファイル(INF)
ドライバ情報ファイルは、デバイスを初めて検出したときに Windows に何をすべきかを指示します。ユーザーのシステムには WinUSB ドライバがすでに含まれているため、必要なのは、INF ファイルでベンダー ID とプロダクト ID をこの新しいインストール ルールに関連付けることだけです。以下のファイルは基本的な例です。.inf
拡張子のファイルに保存し、「X」でマークされたセクションを変更してから、右クリックしてコンテキスト メニューから [インストール] を選択します。
[Version]
Signature = "$Windows NT$"
Class = USBDevice
ClassGUID = {88BAE032-5A81-49f0-BC3D-A4FF138216D6}
Provider = %ManufacturerName%
CatalogFile = WinUSBInstallation.cat
DriverVer = 09/04/2012,13.54.20.543
; ========== Manufacturer/Models sections ===========
[Manufacturer]
%ManufacturerName% = Standard,NTx86,NTia64,NTamd64
[Standard.NTx86]
%USB\MyCustomDevice.DeviceDesc% = USB_Install,USB\VID_XXXX&PID_XXXX
[Standard.NTia64]
%USB\MyCustomDevice.DeviceDesc% = USB_Install,USB\VID_XXXX&PID_XXXX
[Standard.NTamd64]
%USB\MyCustomDevice.DeviceDesc% = USB_Install,USB\VID_XXXX&PID_XXXX
; ========== Class definition ===========
[ClassInstall32]
AddReg = ClassInstall_AddReg
[ClassInstall_AddReg]
HKR,,,,%ClassName%
HKR,,NoInstallClass,,1
HKR,,IconPath,%REG_MULTI_SZ%,"%systemroot%\system32\setupapi.dll,-20"
HKR,,LowerLogoVersion,,5.2
; =================== Installation ===================
[USB_Install]
Include = winusb.inf
Needs = WINUSB.NT
[USB_Install.Services]
Include = winusb.inf
Needs = WINUSB.NT.Services
[USB_Install.HW]
AddReg = Dev_AddReg
[Dev_AddReg]
HKR,,DeviceInterfaceGUIDs,0x10000,"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}"
; =================== Strings ===================
[Strings]
ManufacturerName = "Your Company Name Here"
ClassName = "Your Company Devices"
USB\MyCustomDevice.DeviceDesc = "Your Device Name Here"
[Dev_AddReg]
セクションでは、デバイスの DeviceInterfaceGUID のセットを構成します。アプリケーションが Windows API を介してデバイス インターフェースを見つけて接続するには、すべてのデバイス インターフェースに GUID が必要です。New-Guid
PowerShell コマンドレットまたはオンライン ツールを使用して、ランダムな GUID を生成します。
開発目的では、Zadig ツールを使用して、USB インターフェース用に読み込まれたドライバを WinUSB ドライバに簡単に置き換えることができます。
Microsoft OS 互換性記述子
上記の INF ファイル方式は、すべてのユーザーのマシンを事前に構成する必要があるため、煩雑です。Windows 8.1 以降では、カスタム USB 記述子を使用する方法もあります。これらの記述子は、デバイスが最初に接続されたときに Windows オペレーティング システムに情報を提供します。通常、この情報は INF ファイルに含まれます。
WebUSB 記述子を設定したら、Microsoft の OS 互換性記述子も簡単に追加できます。まず、この追加のプラットフォーム機能記述子を使用して BOS 記述子を拡張します。これを考慮して、wTotalLength
と bNumDeviceCaps
を更新してください。
値 | フィールド | 説明 |
---|---|---|
Microsoft OS 2.0 プラットフォームの機能記述子 | ||
0x1C |
bLength | この記述子のサイズ |
0x10 |
bDescriptorType | デバイスの機能記述子 |
0x05 |
bDevCapabilityType | プラットフォームの機能記述子 |
0x00 |
bReserved | |
{0xDF, 0x60, 0xDD, 0xD8, 0x89, 0x45, 0xC7, 0x4C, 0x9C, 0xD2, 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F} |
PlatformCapablityUUID | リトル エンディアン形式の Microsoft OS 2.0 プラットフォーム互換性記述子 GUID |
0x06030000 |
dwWindowsVersion | 対応している最小の Windows バージョン(Windows 8.1) |
0x00B2 |
wMSOSDescriptorSetTotalLength | 記述子セットの合計長 |
0x02 |
bMS_VendorCode | 追加の Microsoft 記述子を取得するための bRequest 値 |
0x00 |
bAltEnumCode | デバイスが代替の列挙に対応していない |
WebUSB デスクリプタと同様に、これらのデスクリプタに関連する制御転送で使用される bRequest
値を選択する必要があります。この例では 0x02
を選択しました。wIndex
に配置される 0x07
は、デバイスから Microsoft OS 2.0 記述子セットを取得するコマンドです。
bmRequestType | bRequest | wValue | wIndex | wLength | データ(レスポンス) |
---|---|---|---|---|---|
0b11000000 |
0x02 |
0x0000 |
0x0007 |
* | MS OS 2.0 記述子セット |
USB デバイスには複数の機能があるため、記述子セットの最初の部分には、続くプロパティが関連付けられている機能を記述します。次の例では、複合デバイスのインターフェース 1 を構成します。記述子には、このインターフェースに関する 2 つの重要な情報が OS に提供されます。互換 ID 記述子は、このデバイスが WinUSB ドライバと互換性があることを Windows に伝えます。レジストリ プロパティ記述子は、上記の INF の例の [Dev_AddReg]
セクションと同様に機能し、この関数にデバイス インターフェース GUID を割り当てるようにレジストリ プロパティを設定します。
値 | フィールド | 説明 |
---|---|---|
Microsoft OS 2.0 記述子セット ヘッダー | ||
0x000A |
wLength | この記述子のサイズ |
0x0000 |
wDescriptorType | 記述子セット ヘッダー記述子 |
0x06030000 |
dwWindowsVersion | 対応している最小の Windows バージョン(Windows 8.1) |
0x00B2 |
wTotalLength | 記述子セットの合計長 |
Microsoft OS 2.0 構成サブセット ヘッダー | ||
0x0008 |
wLength | この記述子のサイズ |
0x0001 |
wDescriptorType | 構成サブセット ヘッダーの説明。 |
0x00 |
bConfigurationValue | 構成 1 に適用されます(通常は 1 からインデックス付けされる構成ですが、0 からインデックス付けされます)。 |
0x00 |
bReserved | 0 に設定する必要があります |
0x00A8 |
wTotalLength | このヘッダーを含むサブセットの合計長 |
Microsoft OS 2.0 関数サブセット ヘッダー | ||
0x0008 |
wLength | この記述子のサイズ |
0x0002 |
wDescriptorType | 関数サブセット ヘッダー記述子 |
0x01 |
bFirstInterface | 関数の最初のインターフェース |
0x00 |
bReserved | 0 に設定する必要があります |
0x00A0 |
wSubsetLength | このヘッダーを含むサブセットの合計長 |
Microsoft OS 2.0 互換 ID 記述子 | ||
0x0014 |
wLength | この記述子のサイズ |
0x0003 |
wDescriptorType | 互換性のある ID 記述子 |
"WINUSB\0\0" |
CompatibileID | 8 バイトにパディングされた ASCII 文字列 |
"\0\0\0\0\0\0\0\0" |
SubCompatibleID | 8 バイトにパディングされた ASCII 文字列 |
Microsoft OS 2.0 レジストリ プロパティ記述子 | ||
0x0084 |
wLength | この記述子のサイズ |
0x0004 |
wDescriptorType | レジストリ プロパティ記述子 |
0x0007 |
wPropertyDataType | REG_MULTI_SZ |
0x002A |
wPropertyNameLength | プロパティ名の長さ |
"DeviceInterfaceGUIDs\0" |
PropertyName | UTF-16LE でエンコードされた null 終端子を含むプロパティ名 |
0x0050 |
wPropertyDataLength | プロパティ値の長さ |
"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}\0\0" |
PropertyData | UTF-16LE でエンコードされた GUID と 2 つの null 終端子 |
Windows は、この情報をデバイスにクエリするのは 1 回だけです。デバイスが有効な記述子で応答しなかった場合、次回デバイスが接続されたときに再度確認されることはありません。Microsoft は、デバイスの列挙時に作成されるレジストリ エントリを説明するUSB デバイス レジストリ エントリのリストを提供しています。テストでは、デバイス用に作成されたエントリを削除して、Windows が記述子の再読み取りを試行するようにします。
詳細については、これらの記述子の使用方法に関する Microsoft のブログ投稿をご覧ください。
例
WebUSB 記述子と Microsoft OS 記述子の両方を含む WebUSB 対応デバイスを実装するコードの例は、次のプロジェクトにあります。