WebUSB API を最大限に活用するデバイスを作成します。
この記事では、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 を指定できます。
バイナリ デバイス オブジェクト ストア(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_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
は、デバイスの 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 記述子を拡張します。これを考慮して、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 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 対応デバイスを実装するコードの例は、次のプロジェクトで確認できます。