WebUSB 用デバイスの作成

WebUSB API を最大限に活用するデバイスを作成します。

Reilly Grant
Reilly Grant

この記事では、WebUSB API を最大限に活用するデバイスの作成方法について説明します。API 自体の概要については、ウェブ上の USB デバイスにアクセスするをご覧ください。

背景

ユニバーサル シリアル バス(USB)は、周辺機器をパソコンやモバイル コンピューティング デバイスに接続するための最も一般的な物理インターフェースです。USB の仕様には、バスの電気的特性とデバイスと通信するための一般的なモデルの定義に加えて、一連のデバイスクラス仕様が含まれています。これらは、デバイス メーカーが実装できる、ストレージ、オーディオ、動画、ネットワークなど、特定のクラスのデバイス向けの一般的なモデルです。これらのデバイスクラス仕様の利点は、オペレーティング システムのベンダーがクラス仕様に基づいて単一のドライバ(「クラスドライバ」)を実装でき、そのクラスを実装するすべてのデバイスがサポートされることです。これは、どのメーカーも独自のデバイス ドライバを作成する必要があったというより、大きな改善です。

ただし、これらの標準化されたデバイスクラスのいずれにも適合しないデバイスもあります。メーカーは、ベンダー固有のクラスを実装するものとしてデバイスにラベルを付けることもできます。この場合、オペレーティング システムは、ベンダーのドライバ パッケージで提供される情報(通常はベンダー固有のプロトコルの実装がわかっているベンダー ID とプロダクト ID のセット)に基づいて、読み込むデバイス ドライバを選択します。

USB のもう一つの特長は、デバイスが接続先のホストに複数のインターフェースを提供できることです。各インターフェースは、標準化されたクラスを実装することも、ベンダー固有にすることもできます。オペレーティング システムがデバイスを処理する適切なドライバを選択すると、各インターフェースを異なるドライバで要求できます。たとえば、USB ウェブカメラには通常、USB ビデオクラスを実装するインターフェース(カメラ用)と USB オーディオ クラスを実装するインターフェース(マイク用)の 2 つのインターフェースがあります。オペレーティング システムは、単一の「ウェブカメラ ドライバ」を読み込むのではなく、デバイスの個別の機能を担当する独立した動画クラスとオーディオ クラス ドライバを読み込みます。このようなインターフェース クラスの組み合わせにより、柔軟性が向上します。

API の基本

標準 USB クラスの多くには、対応するウェブ API があります。たとえば、ページでは、getUserMedia() を使用して動画クラス デバイスから動画をキャプチャしたり、KeyboardEvents または PointerEvents をリッスンするか、Gamepad API または 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(IN)
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(IN)
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 を指定できます。

Chrome の WebUSB 通知のスクリーンショット
WebUSB の通知。

バイナリ デバイス オブジェクト ストア(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 Platform Capability Descriptor として識別し、デバイスに関する基本情報を提供します。ブラウザはデバイスに関する詳細情報を取得するために、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 回行われます。1 回目は十分な大きさの wLength で行われ、1 回目は大規模な転送をコミットせずに wTotalLength フィールドの値が検出されます。2 回目は、記述子全体の長さが判明した時点で行われます。

WebUSB Platform Capability 記述子の iLandingPage フィールドが 0 以外の値に設定されている場合、ブラウザは、bRequest をプラットフォーム機能記述子の bVendorCode 値に設定し、wValue 値を iLandingPage 値に設定して、WebUSB 固有の GET_URL リクエストを実行します。GET_URL0x02)のリクエスト コードは 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 は、デバイスの 16 進数のベンダー ID とプロダクト ID に置き換えます。たとえば、ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e11" は Nexus One スマートフォンと一致します。正しく認識されるように、通常の「0x」接頭辞を付けずに記述し、すべて小文字にする必要があります。デバイスの ID を確認するには、コマンドライン ツール lsusb を実行します。

このルールは /etc/udev/rules.d ディレクトリのファイルに配置する必要があり、デバイスが電源に接続されるとすぐに有効になります。udev を再起動する必要はありません。

Android

Android プラットフォームは Linux をベースとしていますが、システム構成を変更する必要はありません。デフォルトでは、オペレーティング システムにドライバが組み込まれていないデバイスはすべてブラウザで使用できます。ただし、ユーザーがデバイスに接続すると追加の手順が必要になる点に注意する必要があります。requestDevice() の呼び出しに応じてユーザーがデバイスを選択すると、Chrome にデバイスへのアクセスを許可するかどうかを尋ねるメッセージが Android に表示されます。このプロンプトは、デバイスへの接続権限がすでにあるウェブサイトにユーザーが戻り、そのウェブサイトから open() が呼び出された場合にも再度表示されます。

さらに、デフォルトで含まれるドライバが少ないため、Android からアクセスできるデバイスはパソコン Linux よりも多くなります。たとえば、Android SDK にはシリアル デバイスと通信するための API がないため、通常は USB - シリアル アダプターによって実装される USB CDC-ACM クラスがあります。

ChromeOS

ChromeOS も Linux をベースとしており、システム構成を変更する必要もありません。permission_broker サービスは、USB デバイスへのアクセスを制御します。未申請のインターフェースが 1 つ以上ある限り、ブラウザはデバイスにアクセスできます。

ウィンドウ

Windows ドライバモデルでは、追加の要件が導入されています。上記のプラットフォームとは異なり、ドライバが読み込まれていなくても、ユーザーアプリから USB デバイスを開く機能はデフォルトではありません。代わりに、アプリがデバイスへのアクセスに使用するインターフェースを提供するために、特別なドライバである WinUSB を読み込む必要があります。そのためには、システムにカスタム ドライバ情報ファイル(INF)をインストールするか、列挙中に Microsoft OS 互換性記述子を提供するようにデバイスのファームウェアを変更します。

ドライバ情報ファイル(INF)

ドライバ情報ファイルは、デバイスに初めて接続したときにどう対処するかを Windows に指示します。ユーザーのシステムにはすでに WinUSB ドライバが含まれているため、必要なのは、INF ファイルでベンダーとプロダクト 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 記述子を使用する代わりに、これらの記述子は、通常は INF ファイルに含まれているようなデバイスが最初に接続されたときに Windows オペレーティング システムに情報を提供します。

WebUSB 記述子を設定すると、Microsoft の OS 互換性記述子も簡単に追加できます。まず、この追加のプラットフォーム機能記述子で BOS 記述子を拡張します。これを考慮して、wTotalLengthbNumDeviceCaps を必ず更新してください。

項目 説明
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 Descriptor Set を取得するコマンドです。

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 GUID と UTF-16LE でエンコードされた 2 つの null 終端子

Windows はこの情報をデバイスに 1 回だけクエリします。デバイスが有効な記述子を返さない場合、次にデバイスが接続されたときに再度確認されることはありません。Microsoft は、デバイスの列挙時に作成されるレジストリ エントリを記述した USB Device Registry エントリのリストを提供しています。テスト時に、デバイス用に作成されたエントリを削除して、Windows が記述子を再度読み取るようにします。

これらの記述子の使用方法について詳しくは、Microsoft のブログ投稿をご覧ください。

WebUSB 記述子と Microsoft OS 記述子の両方を含む WebUSB 対応デバイスを実装するコードの例は、次のプロジェクトで確認できます。