webhid-api-ja

WebHID 手引書

このドキュメントは、 WebHID API の手引書です。これは、Web ページが HID デバイスと通信できるようにするために提案された仕様です。

基本用語

HID ( Human Interface Device ) とは、人間から入力を受けたり、人間に出力を提供したりする装置のことです。また、HID プロトコルとは、インストール手順を簡略化するために設計された、ホストとデバイス間の双方向通信のための標準規格のことを指します。 HID プロトコルはもともと USB デバイス用に開発されたものですが、その後 Bluetooth をはじめとする多くのプロトコルで実装されています。

HID レポート は、ホストとデバイス間で通信されるバイナリデータパケットです。入力レポートはデバイスからホストに送信され、出力レポートはホストからデバイスに送信されます。HID レポートのフォーマットはデバイス固有のものです。

HID レポート記述子 は、デバイスの列挙中にホストから要求することができます。レポート記述子は、デバイスがサポートするレポートのバイナリ形式を記述します。記述子の構造は階層的で、レポートをトップレベルのコレクション内の別個のコレクションとしてグループ化することができます。記述子のフォーマットは HID 仕様で定義されています。

HID usage は、標準化された入力または出力を参照する数値です。HID usage のリストは、USB 仕様の一部として利用可能です。Usage の値を使用することで、デバイスはレポートの各フィールドの目的と同様に、デバイス自体の意図された使用法を記述することができます。 Usage は使用法ページに整理され、デバイスまたはレポートフィールドの高レベルカテゴリの表示を提供します。

ユースケース

Web プラットフォームはすでに HID からの入力をサポートしています。キーボード、ポインティングデバイス(マウス、タッチスクリーンなど)、およびゲームパッドはすべて HID プロトコルを使用して実装されています。しかし、このサポートは HID の入力をネイティブな入力 API に変換するオペレーティングシステムの HID ドライバに依存しています。 HID ドライバによって十分にサポートされていないデバイスは、Web ページにアクセスできないことがよくあります。同様に、ほとんどのデバイスの出力にもアクセスできません。

一般的ではない、変わった HID デバイスにアクセスできないのは、ゲームパッドのサポートに関しては特に痛いところです。PC 用に設計されたゲームパッドは、ゲームパッドの入力(ボタン、ジョイスティック、トリガー)と出力(LED、振動)に HID を使用することが多いです。しかし、ゲームパッドの入力と出力は十分に標準化されておらず、Web ブラウザは特定のデバイス用のカスタムロジックを必要とすることがよくあります。これは維持して行く事ができず、結果的に古いデバイスや一般的ではないデバイスの長期間のサポートが不十分になります。また、ブラウザが特定のデバイスの動作に存在する気まぐれに依存してしまう原因にもなります。 Web プラットフォーム用の HID 規格では、ブラウザでサポートされていないデバイスのために、デバイス固有のロジックを javascript に移すことができます。

HID プロトコルは、インストールが簡単であることから人気があり、デバイスメーカーは人間との通信以外の目的で HID を使用することを奨励しています。例えば HID レポートは、ベンダー独自の無線リンクを介してデバイスをペアリングしたり、デバイスのファームウェアを更新するために使用されることがあります。これらの機能は通常、処理を開始するためにホスト側で小さなネイティブアプリを必要としますが、API をサポートすることで、このアプリはウェブページとして実装することができます。

以下の例では、ベンダー ID と プロダクト ID を指定して HID デバイスを要求し、初期化パケットを送信し、入力レポートと接続イベントを受け取ります。

let deviceFilter = { vendorId: 0x1234, productId: 0xabcd };
let requestParams = { filters: [deviceFilter] };
let outputReportId = 0x01;
let outputReport = new Uint8Array([42]);

function handleConnectedDevice(e) {
  console.log("Device connected: " + e.device.productName);
}

function handleDisconnectedDevice(e) {
  console.log("Device disconnected: " + e.device.productName);
}

function handleInputReport(e) {
  console.log(e.device.productName + ": got input report " + e.reportId);
  console.log(new Uint8Array(e.data.buffer));
}

navigator.hid.addEventListener("connect", handleConnectedDevice);
navigator.hid.addEventListener("disconnect", handleDisconnectedDevice);

navigator.hid.requestDevice(requestParams).then((devices) => {
  if (devices.length == 0) return;
  devices[0].open().then(() => {
    console.log("Opened device: " + device.productName);
    device.addEventListener("inputreport", handleInputReport);
    device.sendReport(outputReportId, outputReport).then(() => {
      console.log("Sent output report " + outputReportId);
    });
  });
});

提案されたIDL

WebHID API のグローバルにアクセス可能なコンポーネントは、navigator のインターフェースに navigator.hid として追加されます。

partial interface Navigator {
    readonly attribute HID hid;
};

navigator.hid メンバーは、接続、切断イベントのイベントリスナーの登録をサポートしています。ページは getDevices でページが現在アクセス可能なすべてのデバイスの配列を要求することができます。セキュリティ上の理由から、デフォルトでは HID デバイスは利用できません。ページは requestDevice を呼び出して、接続された HID デバイスからユーザーが選択できる選択ダイアログを表示することができます。選択が完了すると、requestDevice が返す Promisesequence<HIDDevice> でリゾルブします。

interface HID : EventTarget {
    attribute EventHandler onconnect;
    attribute EventHandler ondisconnect;
    Promise<sequence<HIDDevice>> getDevices();
    Promise<sequence<HIDDevice>> requestDevice(HIDDeviceRequestOptions options);
};

interface HIDConnectionEvent : Event {
    readonly attribute HIDDevice device;
}

interface HIDInputReportEvent : Event {
    readonly attribute HIDDevice device;
    readonly attribute octet reportId;
    readonly attribute DataView data;
};

dictionary HIDDeviceRequestOptions {
    required sequence<HIDDeviceFilter> filters;
};

dictionary HIDDeviceFilter {
    unsigned long vendorId;
    unsigned short productId;
    unsigned short usagePage;
    unsigned short usage;
};

返された HIDDevice オブジェクトには、デバイスを識別するためのベンダー ID とプロダクト ID の値が含まれています。デバイスはクローズした状態で返され、データを送受信する前にオープンする必要があります。 collections 属性は、デバイスのレポート形式の階層的な記述で初期化されます。

一旦オープンされると、HIDDevice オブジェクトは sendReport で出力レポートを送信したり、oninputreport イベントリスナーを登録して入力レポートをリッスンしたりすることができます。フィーチャーレポートは sendFeatureReportreceiveFeatureReport で送受信することができます。いずれのメソッドも Promise を返しますが、これは操作が完了するとリゾルブされます。

interface HIDDevice {
    attribute EventHandler oninputreport;
    readonly attribute boolean opened;
    readonly attribute unsigned short vendorId;
    readonly attribute unsigned short productId;
    readonly attribute DOMString productName;
    readonly attribute FrozenArray<HIDCollectionInfo> collections;
    Promise<void> open();
    Promise<void> close();
    Promise<void> sendReport([EnforceRange] octet reportId, BufferSource data);
    Promise<void> sendFeatureReport([EnforceRange] octet reportId, BufferSource data);
    Promise<DataView> receiveFeatureReport([EnforceRange] octet reportId);
};

レポート記述子に含まれる情報は、デバイスと通信するために必要ではありませんが、サポートされているデバイスを認識するためにレポートの特性に依存するアプリケーションには有用かもしれません。(例えば、アプリは、デバイスに少なくとも1つのボタンがある限り、デバイスが他の入力を持っていても気にしないかもしれません)。また、入力レポートからデータを解析するためのガイドとしても役立ちます。

interface HIDCollectionInfo {
    readonly attribute unsigned short usagePage;
    readonly attribute unsigned short usage;
    readonly attribute octet type;
    readonly attribute FrozenArray<HIDCollectionInfo> children;
    readonly attribute FrozenArray<HIDReportInfo> inputReports;
    readonly attribute FrozenArray<HIDReportInfo> outputReports;
    readonly attribute FrozenArray<HIDReportInfo> featureReports;
};

interface HIDReportInfo {
    readonly attribute octet reportId;
    readonly attribute FrozenArray<HIDReportItem> items;
};

enum HIDUnitSystem {
    // No unit system in use.
    "none",
    // Centimeter, gram, seconds, kelvin, ampere, candela.
    "si-linear",
    // Radians, gram, seconds, kelvin, ampere, candela.
    "si-rotation",
    // Inch, slug, seconds, Fahrenheit, ampere, candela.
    "english-linear",
    // Degrees, slug, seconds, Fahrenheit, ampere, candela.
    "english-rotation",
    "vendor-defined",
    "reserved",
};

interface HIDReportItem {
    readonly attribute boolean isAbsolute;
    readonly attribute boolean isArray;
    readonly attribute boolean isBufferedBytes;
    readonly attribute boolean isConstant;
    readonly attribute boolean isLinear;
    readonly attribute boolean isRange;
    readonly attribute boolean isVolatile;
    readonly attribute boolean hasNull;
    readonly attribute boolean hasPreferredState;
    readonly attribute boolean wrap;
    readonly attribute FrozenArray<unsigned long> usages;
    readonly attribute unsigned long usageMinimum;
    readonly attribute unsigned long usageMaximum;
    readonly attribute unsigned short reportSize;
    readonly attribute unsigned short reportCount;
    readonly attribute byte unitExponent;
    readonly attribute HIDUnitSystem unitSystem;
    readonly attribute byte unitFactorLengthExponent;
    readonly attribute byte unitFactorMassExponent;
    readonly attribute byte unitFactorTimeExponent;
    readonly attribute byte unitFactorTemperatureExponent;
    readonly attribute byte unitFactorCurrentExponent;
    readonly attribute byte unitFactorLuminousIntensityExponent;
    readonly attribute long logicalMinimum;
    readonly attribute long logicalMaximum;
    readonly attribute long physicalMinimum;
    readonly attribute long physicalMaximum;
    readonly attribute FrozenArray<DOMString> strings;
};