Web Bluetooth

Draft Community Group Report,

※ この日本語ドキュメントは、公式ドキュメントではありません。翻訳による内容の正確さおよび最新の内容を保証するものではありませんので、あくまで参考として利用していただき、適宜、W3Cの原文を参照していただくようお願いします。(翻訳に対するフィードバックや、ご指摘があれば随時 github にて受け付けています。

最新版(公式):
https://webbluetoothcg.github.io/web-bluetooth/
課題一覧:
GitHub
Inline In Spec
編集:
See contributors on GitHub
参画する:
Join the W3C Community Group
Fix the text through GitHub (公式)
Fix the text through GitHub (本日本語ドキュメント)
public-web-bluetooth@w3.org (archives)
IRC: #web-bluetooth on W3C’s IRC

要約

本ドキュメントは、Bluetooth4.0 にて Generic Attribute Profile (GATT) を利用したデバイスの検索と通信を行うAPIについて説明します。

本ドキュメントの状況

本仕様書は Web Bluetooth Community Groupが公開したもので、W3C Standard および W3C Standards Track が公開したものではありません。 W3C Community Contributor License Agreement (CLA) に基づき、限定されたオプトアウトやその他条件を適用するものがあることに留意してください。 W3C Community and Business Groups についてを詳しく知りたい場合はこちらから。

本ドキュメントの変更履歴は https://github.com/WebBluetoothCG/web-bluetooth/commits/gh-pages から確認できます。

本ドキュメントにご意見のある方は public-web-bluetooth@w3.org (subscribe, archivesまでお送りください。

1. はじめに

このセクションは参考です。

Bluetoothは、機器間の短距離無線通信規格の1つです。クラシックBluetooth ( BR / EDR ) では、バイナリプロトコルの組を定義し、最大約24Mbpsの通信速度をサポートしています。 Bluetooth4.0 では Bluetooth Smart( BLE または LE ) と呼ばれる「省電力」モードが新たに導入されました。 通信速度は1Mbpsに制限されますが、ほとんどの時間は送信機の電源を切ったままにすることができます。 BLEでは、機能の大半を Generic Attribute Profile( GATT ) によるキー/バリューのペアで提供しています。

BLEでは、機器が行う複数の役割を定義しています。BroadcasterOvserver の役割 は、それぞれ送信専用、受信専用のアプリケーションです。 Peripheral の役割で動作するデバイスは接続を受信し、Central の役割で動作するデバイスは Peripheral 機器と接続できます。

Peripheral または Central の役割で動作しているデバイスは、GATT server のホストになることが可能で、サーバーは、ServiceCharacteristicDescriptor という階層になっています。この階層について、詳しくは §5.1 GATT Information Model を参照してください。BLEトランスポートをサポートするよう設計されていますが、GATTプロトコルはBR/EDRトランスポートでも動作します。

本仕様書の初版では、Central の役割を持つUAで実行すれば、ウェブページが BR/EDR または LEコネクション 経由で GATT server と接続できます。本仕様書では BLUETOOTH42 の仕様書を引用しますが、Bluetooth 4.0 or 4.1 だけ実装したデバイス間通信もサポートする予定です。

1.1. サンプル

標準的な心拍数モニターを探索しデータを取り出すには、以下のようなコードをウェブサイトでは使用するでしょう:

let chosenHeartRateService = null;

navigator.bluetooth.requestDevice({
  filters: [{
    services: ['heart_rate'],
  }]
}).then(device => device.gatt.connect())
.then(server => server.getPrimaryService('heart_rate'))
.then(service => {
  chosenHeartRateService = service;
  return Promise.all([
    service.getCharacteristic('body_sensor_location')
      .then(handleBodySensorLocationCharacteristic),
    service.getCharacteristic('heart_rate_measurement')
      .then(handleHeartRateMeasurementCharacteristic),
  ]);
});

function handleBodySensorLocationCharacteristic(characteristic) {
  if (characteristic === null) {
    console.log("Unknown sensor location.");
    return Promise.resolve();
  }
  return characteristic.readValue()
  .then(sensorLocationData => {
    let sensorLocation = sensorLocationData.getUint8(0);
    switch (sensorLocation) {
      case 0: return 'Other';
      case 1: return 'Chest';
      case 2: return 'Wrist';
      case 3: return 'Finger';
      case 4: return 'Hand';
      case 5: return 'Ear Lobe';
      case 6: return 'Foot';
      default: return 'Unknown';
    }
  }).then(location => console.log(location));
}

function handleHeartRateMeasurementCharacteristic(characteristic) {
  characteristic.addEventListener('characteristicvaluechanged', onHeartRateChanged);
  return characteristic.startNotifications();
}

function onHeartRateChanged(event) {
  let characteristic = event.target;
  console.log(parseHeartRate(characteristic.value));
}

BluetoothRemoteGATTCharacteristicvalue の中でストアされた DataView を読み取るために heart_rate_measurement ドキュメントの中で定義されている。

function parseHeartRate(data) {
  let flags = data.getUint8(0);
  let rate16Bits = flags & 0x1;
  let result = {};
  let index = 1;
  if (rate16Bits) {
    result.heartRate = data.getUint16(index, /*littleEndian=*/true);
    index += 2;
  } else {
    result.heartRate = data.getUint8(index);
    index += 1;
  }
  let contactDetected = flags & 0x2;
  let contactSensorPresent = flags & 0x4;
  if (contactSensorPresent) {
    result.contactDetected = !!contactDetected;
  }
  let energyPresent = flags & 0x8;
  if (energyPresent) {
    result.energyExpended = data.getUint16(index, /*littleEndian=*/true);
    index += 2;
  }
  let rrIntervalPresent = flags & 0x10;
  if (rrIntervalPresent) {
    let rrIntervals = [];
    for (; index + 1 < data.byteLength; index += 2) {
      rrIntervals.push(data.getUint16(index, /*littleEndian=*/true));
    }
    result.rrIntervals = rrIntervals;
  }
  return result;
}

onHeartRateChanged() はオブジェクトのように記録する:

{
  heartRate: 70,
  contactDetected: true,
  energyExpended: 750,     // Meaning 750kJ.
  rrIntervals: [890, 870]  // Meaning .87s and .85s.
}

もし心拍数モニターが energyExpended をレポートすれば、ウェブアプリケーションは heart_rate_control_point characteristic を書き換えることで、その値を 0 にリセットすることが出来る:

function resetEnergyExpended() {
  if (!chosenHeartRateService) {
    return Promise.reject(new Error('No heart rate sensor selected yet.'));
  }
  return chosenHeartRateService.getCharacteristic('heart_rate_control_point')
  .then(controlPoint => {
    let resetEnergyExpended = new Uint8Array([1]);
    return controlPoint.writeValue(resetEnergyExpended);
  });
}

2. セキュリティとプライバシーへの配慮

2.1. 強力なデバイスへのアクセス

ウェブサイトが requestDevice() を用いてデバイスへアクセス要求する時、呼び出しで指定された GATT service の全てにアクセスできるようになります。UA は、どのデバイスを信用するか確認する前に、このサービスがウェブサイトに何の機能を与えるのか、ユーザーに通知しなければなりません。UA がまだ知らない service がリストにある場合、UA は、デバイスの完全なコントロールをこのサイトに与えることを想定し、リスクをユーザーに通知しなければなりません。また、UA はどのサイトが何のデバイスにアクセスするのか、をユーザは調べることができ、ペアリングを取り消すことができるようにもしなければなりません。

UA は、ユーザーがデバイスの全クラスをウェブサイトとペアリングできるようにしてはいけません。個別デバイス毎に同一 Bluetooth レベルの識別情報を送信するデバイスクラスを構成することが可能です。UA はこの種の偽装を検出する必要はなく、ユーザーに疑似デバイスをウェブサイトとペアリングさせてもかまいません。

この仕様はセキュアなコンテクストだけが Bluetoothデバイス( requestDevice )にアクセス可能であることを要求することによって、ユーザーがアクセスを承認したエンティティのみ実際にアクセスできることを保証します。

2.2. Trusted servers can serve malicious code

This section is non-normative.

ユーザがオリジンを信頼した場合であっても、オリジンのサーバや開発者は危険にさらされる可能性があります、あるいはオリジンのサイトはXSS攻撃に遭う脆弱性を持つこともありえます。いずれも、大切なデバイスに、悪質なコードへのアクセスをユーザが許可してしまうことにつながる可能性があります。オリジンは、XSS攻撃のリスクを低減するために、コンテンツセキュリティポリシィ( Content Security Policy, [CSP3])を定義する必要があります。しかし、これはサーバや開発者を危険にさらす手助けにはなりません。

§3.1 Permission API Integration によって提供されるページのリロード後に承諾されたデバイスを取得する機能は、このリスクを悪化させます。サイトが危険にさらされている間、ユーザが単に訪問した場合に、ユーザがアクセスを承諾する代わりに、攻撃者は以前に承諾されたデバイスを利用することができます。一方で、サイトがデバイスの横断的なページのリロードへのアクセスを保持できる場合に、サイトは、ユーザがより注意を払って見るようにするために、多くのパーミッションプロンプトを示す必要はありません。

2.3. デバイスへの攻撃

このセクションは参考です。

ウェブサイトからの通信によりデバイスのセキュリティモデルが破たんする場合があります。リモートデバイスの信頼できるオペレーティングシステムからのメッセージを受信するだけの仮定をしているからです。ヒューマンインターフェースデバイスは顕著な事例で、ウェブサイトへの通信を許可すれば、そのサイトでキーストロークを採取できるようになるでしょう。ウェブサイトの悪用を防止するため、本仕様書にはそのような脆弱な Service、Characteristic、Discriptor の blacklist が含まれています。

予期しないデータを無線で送信する脆弱性があるデバイスが多数あると本仕様書では想定しています。これまでは、デバイス一台ずつ攻撃する必要がありましたが、本APIを利用すれば大規模に攻撃することも可能です。本仕様書は、そのような攻撃を困難にする試みをいくつか採用しています。:

  • デバイスクラスの代わりに個別デバイスをペアリングする際には、デバイスが悪用される前に少なくとも一回のユーザーアクションが必要になります。
  • 包括的なバイトストリームアクセスではなく、GATT へのアクセスに制限すれば、悪意あるウェブサイトからのデバイスの大半のパーサーへのアクセスを拒否できます。

    一方、GATT の characteristicdescriptor の値はいまだにバイト配列で、デバイスが想定しない長さ、形式でセットされる可能性があります。UA は、できれば、これらの値を確認することが推奨されます。

  • 本APIでは、ウェブサイトに Bluetooth のアドレス、署名データや暗号キー(キーと値の定義)を公開しません。この結果、無線で送信されるビットデータの予測が困難で、パケット・イン・パケット・インジェクション攻撃を阻止します。残念ながら、この方法は暗号化されたリンク上でのみ効果があり、BLEデバイス全てでサポートする必要はありません。

また、UA は、ユーザー保護のため、さらに対策を講じることも可能です:

  • 悪意あるウェブサイトと脆弱なデバイスの一覧をウェブサービスで収集することができます。悪意あるウェブサイトから全デバイスへのアクセス、全ウェブサイトから脆弱なデバイスへのアクセスを UA で拒否できます。

2.4. Bluetooth デバイス識別子

このセクションは参考です。

各 Bluetooth ( BR/EDR )デバイスには、BD_ADDR と呼ばれる48ビットのユニークなMACアドレスがあります。各BLEデバイスにはPublic Device Address が少なくとも1つと Static Device Address が1つあります。Public Device Address は MACアドレスです。Static Device Address はリスタート毎に生成されるかもしれません。BR/EDR/LEデバイスは、( BD_ADDR を読み取るコマンドで指定された) BD_ADDRPublic Device Address< は同一の値を使用します。

また、BLEデバイスには、ユニークな128ビットの IRK ( Identity Resolving Key )と呼ばれる鍵があり、ボンディングプロセス中に信頼されたデバイスに送信されます。永続的な識別子の漏えいを避けるため、BLEデバイスは、ランダムな 検証可能(Resolvable) または 検証不可能(Non-Resolvable) な Private Address を Static または Public Device Address の代わりに使用し、スキャンとアドバタイズすることが可能です。これらは周期的 (約15分毎)に生成されますが、ボンディングされたデバイスは、格納された IRK の中に検証可能なプライベートアドレスとマッチするものがあるか、 Resolvable Private Address Resolution Procedure を用いてのチェックが可能です。

また、各Bluetoothデバイスには、人間が読める形式の Bluetooth Device Name が定義されています。これらはユニークである保証はありませんが、デバイスタイプによっては多分ユニークでしょう。

2.4.1. リモート Bluetooth デバイスの識別子

このセクションは参考です。

ウェブサイトが永続的にデバイスIDを取得できる場合、周囲に存在するデバイスのカタログを作成という多大な努力を行えば、ユーザーの位置を発見するために使用できます。また、デバイスIDを使えば、異なる2つのウェブサイトに同じBluetoothデバイスでペアリングしているユーザーが同一ユーザーだと特定することも可能です。一方、 デバイスの身元を突き止めるために使用できる多数の GATT service が利用でき、この作業がさらに簡単になるよう、デバイスはカスタム GATT service を公開しています。

本仕様書では、ユーザは、スクリプトが単一のデバイスであると学習させようとしない場合、UA が異なるデバイス ID を単一のデバイスに使用することを提案し、ウェブサイトがデバイスアドレスを悪用するのを困難にしています。デバイスメーカーは、ユーザー追跡を助けるデバイスを設計できますが、ひと仕事が必要となります。

2.4.2. UA のBluetooth アドレス

このセクションは参考です。

BR/EDRモード または セキュリティ要件(Privacy Feature) なしでアクティブスキャニング中のLEモードでは、UA は永続的なIDを近隣のBluetooth無線にブロードキャストします。これは、周辺に敵意あるデバイスをばらまきやすくなり、 UA の追跡が簡単になります。2014年8月時点では、 セキュリティ要件(Privacy Feature) を実装したと文書化したプラットフォームはほとんどありません。従って、本仕様では推奨していますが、 これを使うことになるUA はほとんどなさそうです。本仕様では、ウェブサイトがスキャンを開始するにはユーザージェスチャーが必要で、スキャン頻度をいくらか少なくします。多くのプラットフォームでは、セキュリティ要件(Privacy Feature) を公開する方がもっと良いのでしょう。

3. デバイスの検索

dictionary BluetoothRequestDeviceFilter {
  sequence<BluetoothServiceUUID> services;
  DOMString name;
  DOMString namePrefix;
};

dictionary RequestDeviceOptions {
  required sequence<BluetoothRequestDeviceFilter> filters;
  sequence<BluetoothServiceUUID> optionalServices = [];
};

interface Bluetooth {
  [SecureContext]
  readonly attribute BluetoothDevice? referringDevice;
  [SecureContext]
  Promise<BluetoothDevice> requestDevice(RequestDeviceOptions options);
};
Bluetooth implements EventTarget;
Bluetooth implements BluetoothDeviceEventHandlers;
Bluetooth implements CharacteristicEventHandlers;
Bluetooth implements ServiceEventHandlers;
注記: Bluetooth members

referringDevice は、このページをユーザが開いて、そこからデバイスへのアクセスを提供することがあります。例えば、Eddystone ビーコンでは、UA は、ユーザが開くことができる URL をアドバライスするかもしれません。ビーコンを表す BluetoothDevicenavigator.bluetooth.referringDevice を通じて利用できるようになります。

requestDevice(options) は、options.filtersいずれのフィルターにマッチするデバイスへのオリジンアクセスを与えるよう、ユーザに求めます。フィルターにマッチするには、デバイスには以下が必要です:

  • 該当メンバーが存在する場合、services リストにある全ての GATT service UUID をサポートすること。
  • 該当メンバーが存在する場合、name と同じ名前があること。
  • 該当メンバーが存在する場合、namePrefix で始まる名前があること。

ユーザがこのオリジンとペアリングするためにデバイスを選択した後で、options.filters のいずれかのエレメントにある services リスト、または options.optionalServices にリストされている service で UUID を持つもの全部へのアクセスがオリジンに許可されます。

これは、開発者が名前だけでフィルターすれば、どんな service へアクセスするにも optionalServices を使用しなければならないことを意味します。

UA が次のデバイスの近くにあるとしましょう:

Device Advertised Services
D1 A, B, C, D
D2 A, B, E
D3 C, D
D4 E
D5 <none>

ウェブサイトが次のようにコールすると

navigator.bluetooth.requestDevice({
  filters: [ {services: [A, B]} ]
});

ユーザーにデバイスD1とD2を含んだダイアログが表示されます。ユーザーがD1を選択したら、ウェブサイトはサービスC、Dにはアクセスできません。ユーザーがD2を選択すれば、ウェブサイトはサービスEにはアクセスできません。

一方、ウェブサイトが次のようにコールすると

navigator.bluetooth.requestDevice({
  filters: [
    {services: [A, B]},
    {services: [C, D]}
  ]
});

ダイアログにはデバイスD1、D2、D3が含まれ、ユーザがD1を選択したら、ウェブサイトはサービスA、B、C、Dにアクセスできます。

optionalServices は、ユーザーが見ているダイアログにデバイスも追加せず、ユーザーが選んだデバイスからどのサービスをウェブサイトが使用できるかに影響を与えます。

navigator.bluetooth.requestDevice({
  filters: [ {services: [A, B]} ],
  optionalServices: [E]
});

D1、D2を含み、D4を含まないダイアログを表示します。D4は必要なサービスが含まないからです。ユーザーがD2を選択した場合、最初の例とは異なり、ウェブサイトはサービスA、B、Eにアクセスできます。

ユーザがアクセスを許可した後にデバイスが変化しても、許可されたサービスは適用されます。例えば、ユーザーが前の requestDevice() コールでD1を選択し、後でD1が新しいサービスEを追加した場合、serviceadded イベントが開始され、ウェブページはサービスEをアクセスすることができます。

前の例で、デバイスが以下のように名前もアドバタイズしたとします:

Device Advertised Device Name
D1 First De…
D2 <none>
D3 Device Third
D4 Device Fourth
D5 Unique Name

以下のテーブルには、navigator.bluetooth.requestDevice({filters: filters}) に渡された filters の値として、どのデバイスをユーザーが選択できるかを示しています。

filters Devices Notes
[{name: "Unique Name"}]
D5
[{namePrefix: "Device"}]
D3, D4
[{name: "First De"},
 {name: "First Device"}]
<none> D1 は地震の name の prefix のみをアドバタイズするので、name 全体とマッチさせようとすると失敗します。
[{namePrefix: "First"},
 {name: "Unique Name"}]
D1, D5
[{services: [C],
  namePrefix: "Device"},
 {name: "Unique Name"}]
D3, D5

可能なデバイスを全て accept または reject するフィルターは TypeError が発生します。

filters Notes
[]
フィルターリストが空であれば、デバイスを一切 accept しません。
[{}]
フィルターリストが空であれば、デバイス全てを accept するので、これも許可されません。
[{namePrefix: ""}]
namePrefix がある場合、デバイスをフィルターするため空でないものとします。

Bluetooth のインスタンスは、以下のテーブルで説明される内部スロットを用いて生成されます:

内部スロット 初期値 説明(参考)
[[deviceInstanceMap]] Bluetooth device から BluetoothDevice インスタンスへの空のマップ 1つのグローバルオブジェクト内で、各 Bluetooth デバイス を表す BluetoothDevice インスタンスはただ1つしかないことを保証します。
[[attributeInstanceMap]] Bluetooth cache エントリー から Promise への空のマップ。 この Promise は、BluetoothRemoteGATTServiceBluetoothRemoteGATTCharacteristicBluetoothRemoteGATTDescriptor のいずれかのインスタンスに resolve されます。
[[referringDevice]] null Document がデバイスから開かれている場合、Document オブジェクトを初期化している間に BluetoothDevice にセットします。

navigator.bluetooth.referringDevice を取得するには、[[referringDevice]] を必ず返さなければなりません。

いくつかの UA は、ユーザが Bluetooth device に応答して navigate するブラウジングコンテキスト( browsing context )を引き起こすことを可能にします。 例えば、Eddystone ビーコンは、URLをアドバタイズした場合、 UA は、ユーザがこの URL に移動することを可能にします。 これが発生した場合、新しい Document オブジェクトの初期化の一環として、UA は以下のステップを実行しなければなりません。:

  1. referringDevice を navigation を発生させたデバイスにします。
  2. navigator.bluetooth 内部の referringDevice を表す BluetoothDevice を取得し、referringDeviceObj を結果にします。
  3. 以前のステップで例外をスローした場合、以下のステップを中止します。

    注記: これは、UA は、ユーザが referringDevice に現在の realm のアクセス件を承諾するであろうと推測していなかったことを意味します。例えば、ユーザはグローバルに GATT へのアクセスを拒否された可能性があります。

  4. navigator.bluetooth@[[referringDevice]]referringDeviceObj にセットします。

以下のステップで match が返された場合、Bluetooth device filter#matches-a-filterReferenced in:3. Device Discovery (2) (3) にマッチします。:

  1. filter.name が存在し、deviceBluetooth Device Name が完全ではなく、filter.name と一致しない場合、mismatch を返します。
  2. filter.namePrefix が存在し、deviceBluetooth Device Name が存在しないまたは filter.namePrefix で始まらない場合、mismatch を返します。
  3. filter.services の各 uuid に対して、デバイスが UUID uuid を持つプライマリ(含まれているのではなく)サービスのサポートを示しているアドバタイジングデータ、拡張要求応答( extended inquiry response ) または サービス検索応答( service discovery response ) を UAが受信していない場合、mismatch を返します。
  4. match を返します。

デバイスがアドバタイズする Service UUID のリストには、デバイスがサポートする全ての UUID が含まれない場合があります。アドバタイズされるデータは、リストが完全かどうか指定します。ウェブサイトは、近隣のデバイスがサポートはするがアドバタイズしない UUID をフィルターする場合、そのデバイスはユーザに提供されるデバイスリストには含まれていない可能性があります。サポートサービスの完全なリストを検索するため、UA はデバイスと接続する必要がありますが、無線性能の劣化や遅延が起こり得るため本仕様では要求していません。

requestDevice(options) メソッドは、呼び出されると新しい promise promise を返し、同時に以下のステップを実行しなければなりません。:

  1. options.filtersoptions.optionalServices を渡しながら Bluetooth devices をリクエストし、devices を結果にします。
  2. 以前のステップで例外をスローした場合、その例外とともに promisereject し、以下のステップを中止します。
  3. devices[0] とともに promiseResolve します。

Bluetooth devices をリクエストするには、BluetoothRequestDeviceFilter のシーケンス、 filtersBluetoothServiceUUID のシーケンス、および optionalServices が与えられると、UA は以下のステップを実行しなけれなばりません。:

このアルゴリズムの呼び出しは、最終的に複数のデバイスをリクエストできるようになりますが、今のところは単一のデバイスのみを返します。

  1. アルゴリズムがユーザの動作によって引き起こされていない場合は、SecurityError をスローし、以下のステップを中止します。
  2. service 名とエイリアスからただの UUID に引数を変換するために、以下のサブステップを実行します。:
    1. filters.length === 0 の場合、TypeError をスローし、以下のステップを中止します。
    2. uuidFilters を新しい Array に、requiredServiceUUIDs を新しい Set にします。
    3. filters の各 filter に対して、以下のサブステップを実行します:
      1. filterservicesnamenamePrefix メンバーがいずれも存在しない場合、TypeError をスローし、以下のステップを中止します。
      2. canonicalizedFilter{} にします。
      3. filter.services が存在する場合、以下のサブステップを実行します。:
        1. filter.services.length === 0 の場合、TypeError をスローし、以下のステップを中止します。
        2. servicesArray.prototype.map.call(filter.services, BluetoothUUID.getService) にします。
        3. BluetoothUUID.getService() で例外が発生した場合、例外をスローし、ステップを中止します。
        4. servicesservice のいずれかが blacklisted の場合、SecurityError をスローし、ステップを中止します。
        5. canonicalizedFilter.servicesservices にセットします。
        6. services のエレメントを requiredServiceUUIDs に追加します。
      4. filter.name が存在する場合、以下のサブステップを実行します。
        1. filter.nameUTF-8 encoding が248バイトを超える場合、TypeError をスローし、以下のステップを中止します。

          248バイト は Bluetooth Device Name の UTF-8コードユニットの最大値です。

        2. canonicalizedFilter.namefilter.name にセットします。
      5. filter.namePrefix が存在する場合、以下のサブステップを実行します。
        1. filter.namePrefix.length === 0 または filter.namePrefixUTF-8 encoding が248バイトを超える場合、TypeError をスローし、以下のステップを中止します。

          248バイト は Bluetooth Device Name の UTF-8コードユニットの最大値です。

        2. canonicalizedFilter.namePrefixfilter.namePrefix にセットします。
      6. canonicalizedFilteruuidFilters にアペンドします。
    4. optionalServiceUUIDsArray.prototype.map.call(optionalServices, BluetoothUUID.getService) にします。
    5. BluetoothUUID.getService() で例外が発生した場合、例外をスローし、以下のステップを中止します。
    6. optionalServiceUUIDs からblacklisted となっている UUID を削除します。
  3. descriptor はこのようになります。
    {
      name: "bluetooth",
      filters: uuidFilters,
      optionalServices: optionalServiceUUIDs,
    }
    
  4. requiredServiceUUIDsService UUID の集合として持つデバイスをスキャンし、scanResult をその結果とします。
  5. uuidFilters でフィルターにマッチしない場合、デバイスを scanResult から削除します。
  6. scanResult が空の場合であっても、descriptor に関連付けられた scanResult 内のデバイスのいずれかを選択するようにユーザを促しdevice を結果にします。

    UA は、uuidFilters にマッチしない、近隣のデバイスをユーザーが選択できる場合があります。


    UA は、ユーザに書くデバイスの人間に可読な名前を表示する必要があります。例えば、この名前が使用不可の場合、UA の Bluetooth システムはプライバシーを有効にして実行するスキャンをサポートしていないため、UA は、ユーザが関心を示すことを許可します。また、名前を取得するために、プライバシーを無効にしてスキャンを実行する必要があります。

  7. device"denied" の場合、[] を返し、以下のステップを中止します。
  8. device を選択することは、おそらく、ユーザが、少なくとも、現在の設定オブジェクト( current settings object ) の "bluetooth" の追加パーミッションデータ( extra permission data ) の許可されたデバイスリスト( allowedDevices list ) に表示するようにしていることを示しています。そして、requiredServiceUUIDsoptionalServiceUUIDs の和集合内の全ての service を、すでに存在している任意の service に加えて、その許可されたデバイスリスト( allowedServices )に表示するようにしていることを示しています。
  9. UA は device 内の全ての Service とともに Bluetooth cache を格納することができます。このステップでのエラーは無視します。
  10. device 内の コンテキストオブジェクト( context object )を表す BluetoothDevice を取得し、任意の例外を伝搬させ、deviceObj を結果とします。
  11. [deviceObj] を返します。

オプションの Service UUID のセット(デフォルトは全UUIDの集合)を用いてデバイスのスキャンをするには、UA は以下のステップを実行しなければなりません。:

  1. UA が最近デバイスをスキャンしたことがある場合、TODO: 時間の分量を確定させる。 現在のスキャン用の UUID のスーパーセットである UUID の集合を用いてスキャンしている場合、UAはそのスキャン結果を返し、ステップを中止することができます。
  2. nearbyDevicesBluetooth device の集合とし、初期値は空です。
  3. UA が LE トランスポートをサポートする場合、General Discovery Procedure を実行し、発見した Bluetooth devicenearbyDevices に追加します。UA はPrivacy Feature を有効にする必要があります。

    passive scanningPrivacy Feature の両方で、ユニークかつ変更不可能なデバイスIDのリークを防止しています。どちらかを使用するよう UA に要求すべきですが、OS API にはどちらも公開するものがなさそうです。またBluetoothは passive scanning の使用が難しくなっています。Central デバイスが Observation Procedure をサポートする必要がないからです。

  4. UA が BR/EDR トランスポートをサポートする場合、Device Discovery Procedure を実行し、発見した Bluetooth devicenearbyDevices に追加します。

    BR/EDR inquiry/discovery の全形式で、ユニークかつ変更不可能なアドレスがリークするようです。

  5. resultBluetooth device の集合とし、初期値は空です。
  6. nearbyDevices の各 Bluetooth device device に対して以下のサブステップを実行します。:
    1. device の サポートされた物理トランスポート( supported physical transports )に LE が含まれ、その Bluetooth Device Name が部分的にしか存在しないまたは存在しない場合、UA は完全な名前を取得するために Name Discovery Procedure を実行する必要があります。
    2. アドバタイズされた deviceService UUIDService UUID の集合 と空でない共通部分がある場合、deviceresult に追加し、以下のサブステップを中止します。

      BR/EDRデバイスについて、拡張問合せ応答( Extended Inquiry Response )で GATT と non-GATT services を区別する方法はありません。あるサイトが non-GATT service の UUID にフィルターする場合、ユーザーは、本APIが交信する方法を持たないrequestDevice の結果としてデバイスを選択できる可能性があります。 


    3. UA は、device に接続し、Service UUID の集合にある全サービスを Bluetooth cache に格納 することができます。 device がサポートする物理トランスポート( supported physical transports ) に BR/EDR が含まれる場合、UA は standard GATT procedures に加え、キャッシュ格納時にサービス探索プロトコル( Service Discovery Protocol [Searching for Services] )を使用できます。

      近隣の全デバイスに接続するサービス検索は、電力を消費し、Bluetooth無線の他の利用を遅くする可能性があります。デバイスに関心が期待できる何かの理由があれば、UA はあるデバイスの特別なサービスだけを検索をすべきです。

      また UA は、開発者がこの特別な検索方法への依存を避けられるようにしなければなりません。例えば、開発者が以前あるデバイスに接続していれば、その UA はデバイスでサポートされるサービスをフルセットで知っています。そして、開発者が non-advertised UUID を用いてフィルターすれば、フィルターがユーザーのマシンでこのデバイスを除外しそうな場合でも、ダイアログにこのデバイスを含むことができます。このような場合、UA は、警告を出すかフィルターにマッチする時にアドバタイズされた service だけを含める、というオプションを開発者に提供できます。


    4. Bluetooth cacheService UUID 集合に、UUID を持ったデバイス内部の known-present service を含んでいる場合、UA は deviceresult に追加できます。
  7. スキャンから result を返します。

興味のあるデバイスが圏内に来た時に、イベントを受信するためサイトを登録する方法が必要になります。

3.1. Permission API Integration

この [permissions] API は、ウェブサイトが持っている権限を、ユーザやクエリーからリクエストするための統一的な方法を提供します。

サイトでは navigator.bluetooth.requestDevice() の代替スペルとして navigator.permissions.request({name: "bluetooth", ...}) を使用することができる。

navigator.permissions.request({
  name: "bluetooth",
  filters: [{
    services: ['heart_rate'],
  }]
}).then(result => {
  if (result.devices.length > 1) {
    return result.devices[0];
  } else {
    throw new DOMException("Chooser cancelled", "NotFoundError");
  }
}).then(device => {
  sessionStorage.lastDevice = device.id;
});

deviceId メンバーは request() の呼び出しでは無視されます。

サイトは、デバイスのセットへのアクセスを許可されたならば、リロード後にそれらのデバイスを取得するために navigator.permissions.query({name: "bluetooth", ...}) を使用することができます。

navigator.permissions.query({
  name: "bluetooth",
  deviceId: sessionStorage.lastDevice,
}).then(result => {
  if (result.devices.length > 1) {
    return result.devices[0];
  } else {
    throw new DOMException("Lost permission", "NotFoundError");
  }
}).then(...);

"bluetooth"強力な特徴である、パーミッションが関連するアルゴリズムおよびタイプは以下のように定義されています。:

permission descriptor type
dictionary BluetoothPermissionDescriptor : PermissionDescriptor {
  DOMString deviceId;
  // These match RequestDeviceOptions.
  sequence<BluetoothRequestDeviceFilter> filters;
  sequence<BluetoothServiceUUID> optionalServices = [];
};
extra permission data type

BluetoothPermissionData は以下のように定義されています。:

dictionary AllowedBluetoothDevice {
  required DOMString deviceId;
  // An allowedServices of "all" means all services are allowed.
  required (DOMString or sequence<UUID>) allowedServices;
};
dictionary BluetoothPermissionData {
  required sequence<AllowedBluetoothDevice> allowedDevices = [];
};

AllowedBluetoothDevice インスタンスは Bluetooth device を有する 内部スロット [[device]] を持っています。

allowedDevices の個別の要素は、異なる [[device]] および、異なる deviceId を持っている必要があります。

deviceId は、サイトに、 BluetoothDevice インスタンスと同一インスタンスを表す BluetoothDevice インスタンスの追跡を許可します、おそらく異なる realm です。 UA は、"bluetooth"extra permission data を返す時に、起こることまたは起きないことを、ユーザーが追跡しようとするかどうか、 を検討する必要があります。

例えば、一般的に、ユーザは、それらが同じデバイスと対話していることを知っているため、2つの異なるオリジンを意図しません。また、ユーザは、クッキーをクリアした後に持続するユニークな識別子も意図しません。

permission result type
interface BluetoothPermissionResult : PermissionStatus {
  attribute FrozenArray<BluetoothDevice> devices;
};
permission request algorithm

BluetoothPermissionDescriptor optionsBluetoothPermissionResult status が与えられ、UA は以下のステップを実行しなければなりません。:

  1. Bluetooth devices のリクエストでは options.filtersoptions.optionalServices を渡し、いずれの例外も伝搬させ、devices を結果とします。
  2. status.devices を、devices の要素を含む新しい FrozenArray にセットします。
permission query algorithm
BluetoothPermissionDescriptor descBluetoothPermissionResult status とともに"bluetooth" permission を問い合わせるためには、UAは以下のステップを実行しなければなりません。:
  1. globalstatus と関連した global object とします。
  2. status.statedescpermission state にセットします。
  3. status.state"denied" の場合、status.devices を空の FrozenArray にセットし、以下のステップを中止します。
  4. matchingDevices を新しい Array にします。
  5. data ( BluetoothPermissionData ) を、current settings object のために "bluetooth"extra permission data とします。
  6. data.allowedDevices の各 allowedDevice に対して、以下のサブステップを実行します。:
    1. desc.deviceId がセットされ、 allowedDevice.deviceId != desc.deviceId の場合、次の allowedDevice に進みます。
    2. desc.filters がセットされ、allowedDevice@[[device]]desc.filtersフィルターとマッチしない場合、次の allowedDevice に進みます。
    3. 注記: desc.optionalServices フィールドは結果には影響しません。
    4. global.navigator.bluetooth 内の allowedDevice@[[device]] を表す BluetoothDevice を取得し、結果に matchingDevices を加えます。
  7. status.devices を、コンテンツが matchingDevices である新しい FrozenArray にセットします。
permission revocation algorithm

ユーザはデバイスを公開しないよう Bluetooth access を取り消すためには、UA は以下のステップを実行しなければなりません。:

  1. data ( BluetoothPermissionData ) を、current settings object のために "bluetooth"extra permission data とします。
  2. current realm の各 BluetoothDevice instance deviceObj に対して、以下のサブステップを実行します。:
    1. data.allowedDevices の中に AllowedBluetoothDevice allowedDevice がある場合、つまり: その後、deviceObj.[[allowedServices]]allowedDevice.allowedServices に更新し、次の deviceObj に進みます。
    2. そうでない場合、残りのステップを実行して、そのデバイスから deviceObj を切り離します。
    3. deviceObj.gatt.disconnect() をコールします。

      注記: これは deviceObjgattserverdisconnected イベントを開始させます。

    4. deviceObj@[[representedDevice]]null にセットします。

4. デバイスの表現

UA は Bluetoothデバイスproperties をいくつかのレベルで追跡する必要があります。: グローバル、オリジン別、グローバルオブジェクト

4.1. グローバル Bluetooth デバイスプロパティ

物理的な Bluetooth デバイスは、UA が受信していない可能性があるプロパティ群を持つことが保証される場合があります。ここでは、そのようなプロパティ群をオプションと記載します。

Bluetooth device には、以下のようなプロパティがあります。別途説明がない限り、オプションのプロパティは存在せず、シーケンスおよびマッププロパティは空です。それ以外のプロパティは、指定デフォルト値があるか、デバイス導入時点で決定されます。

以下の場合に UA は2つの Bluetooth device同一の Bluetooth デバイスと判断する必要があります。2つのデバイスの、Public Bluetooth AddressStatic Address, Private Address, Identity Resolving Key のいずれかが同一の場合、あるいは、一方のデバイスのIRKと他方の検証可能な Private Address を使って Resolvable Private Address Resolution Procedure が成功した時に限り、同一のデバイスだと判断する必要があります。しかし、プラットフォームAPIでは、デバイスの同一性を決定する方法が文章化されていないので、UA は別のプロシージャを使用してもよいのです。

4.2. BluetoothDevice

BluetoothDevice インスタンスは、特定の global object (あるいは、同等に、特定の Realm または Bluetooth インスタンスに対して) の Bluetooth device を表しています。

interface BluetoothDevice {
  readonly attribute DOMString id;
  readonly attribute DOMString? name;
  readonly attribute BluetoothRemoteGATTServer gatt;
  readonly attribute FrozenArray<UUID> uuids;

  Promise<void> watchAdvertisements();
  void unwatchAdvertisements();
  readonly attribute boolean watchingAdvertisements;
};
BluetoothDevice implements EventTarget;
BluetoothDevice implements BluetoothDeviceEventHandlers;
BluetoothDevice implements CharacteristicEventHandlers;
BluetoothDevice implements ServiceEventHandlers;
注記: BluetoothDevice 属性

id は、UA が2つの Bluetooth 接続が同一デバイスであると判断できる程度にデバイスを識別することができます。また、id は、ユーザがスクリプトにその事項をスクリプトに公開したい程度にデバイスを識別することができます。

name は、人間が読める形式のデバイス名です。

gatt には、このデバイスの GATT services と通信する方法を与えます。

uuids は、このデバイス上にあることがわかっている GATT service の UUID 群で、現在のオリジンがアクセスを許可されているもののリストです。

UA がこのデバイスからアドバタイズメントをスキャンしており、イベントを開始した場合、watchingAdvertisements は true です。

BluetoothDevice インスタンスは、次のテーブルに記載された内部スロットを用いて作成されます。:

内部スロット 初期値 説明(参考)
[[context]] undefined この BluetoothDevice を返した BluetoothDevice.Bluetooth オブジェクト
[[representedDevice]] undefined このオブジェクトが表している Bluetooth デバイス
[[allowedServices]] undefined 全てのサービスが許可されている "all" の場合は、オリジンのための、このデバイスの allowedServices リスト。例えば、UA は、そのオリジンに URL がそのオリジンにアドバタイズされた referringDevice 上の全てのサービスへのアクセスをオリジンに許可することができます。
[[unfilteredUuids]] new Set() このデバイスの GATT Server にあることが既知のサービスUUID。 UA が全サービス検索を実行していない場合、この集合は不完全であるかもしれません。
[[cachedAllowedServices]] undefined [[filteredUuids]] が最後に実行された時点での [[allowedServices]] の値。
[[cachedUnfilteredUuids]] undefined [[filteredUuids]] が最後に更新された時点での [[unfilteredUuids]] の値。
[[filteredUuids]] undefined キャッシュされた uuids の値。[[cachedAllowedServices]][[allowedServices]] が等しい場合は、最新です。

Bluetooth インスタンスの context 内部で Bluetooth device device を表す BluetoothDevice を取得する representing#get-the-bluetoothdevice-representingReferenced in:3. Device Discovery5.3. BluetoothRemoteGATTService ためには、UA は以下のステップを実行しなければなりません。:

  1. device として 同一デバイスである context@[[deviceInstanceMap]] にキーが存在する場合、その値を返し、以下のステップを中止します。
  2. promise新しい promise にします。
  3. device から context@[[deviceInstanceMap]]promise へのマッピングを追加します。
  4. promise を返し、同時に以下のステップを実行します。
  5. data ( BluetoothPermissionData ) を current settings object による "bluetooth"extra permission data にします。
  6. data.allowedDevices の中から、allowedDevice@[[device]] で、device として同一デバイスを検索します。そのようなオブジェクトが存在しない場合、SecurityError をスローし、以下のステップを中止します。
  7. data.allowedDevices の中から、allowedDevice@[[device]] で、device として同一デバイスを検索します。そのようなオブジェクトが存在しない場合、SecurityError をスローし、以下のステップを中止します。
  8. device として同一デバイスである context@[[deviceInstanceMap]] にキーが無い場合、以下のサブステップを実行します。:
    1. result を新しい BluetoothDevice のインスタンスとします。
    2. result のオプションのフィールド全てを null に初期化します。
    3. result@[[context]]context に初期化します。
    4. result@[[representedDevice]]device に初期化します。
    5. Initialize result.idallowedDevice.deviceId に初期化し、result@[[allowedServices]]allowedDevice.allowedServices に初期化します。
    6. device が部分的または完全な Bluetooth Device Name を持つ場合、result.name にその文字列をセットします。
    7. result.watchingAdvertisementsfalse に初期化します。
    8. result.gatt.deviceresult に初期化します。
    9. result.gatt.connectedfalse に初期化します。
    10. device の アドバタイズされた Service UUIDsresult@[[unfilteredUuids]] に追加します。
    11. Bluetooth cache がデバイス内部の device の known-present サービスを含んでいる場合、これらのサービスに対する UUID群 をresult@[[unfilteredUuids]] に追加します。
    12. device から context@[[deviceInstanceMap]]result にマッピングを追加します。
  9. キーが device として 同一デバイスである context@[[deviceInstanceMap]] の中の値を返します。

uuids attribute を取得するには、以下のステップを実行しなければなりません。:

  1. [[cachedAllowedServices]][[allowedServices]] と異なる、あるいは [[cachedUnfilteredUuids]][[unfilteredUuids]] と異なる場合、以下のステップを実行します。:
    1. [[cachedAllowedServices]][[allowedServices]] のコピーをセットします。
    2. [[cachedUnfilteredUuids]][[unfilteredUuids]] のコピーをセットします。
    3. [[filteredUuids]] に、以下の要素からなる新しい FrozenArray をセットします。:
      [[allowedServices]]"all" である場合
      [[unfilteredUuids]] の全ての要素
      そうでない場合
      [[unfilteredUuids]][[allowedServices]] の共通部分
  2. [[filteredUuids]] を返します。

スキャンには電力が必要になるためウェブサイトは必要以上にアドバタイズメントを関しすることは避けてください。そして可能な限り早く電力の使用を停止する unwatchAdvertisements() をコールする必要があります。

watchAdvertisements() メソッドは、呼び出されると、新しい promise promise を返し、同時に以下のステップを実行しなければなりません。:

  1. UA は、このデバイスのアドバタイズメントをスキャンしていることを確認してください。UA は、同じデバイスの "重複した" アドバタイズメントを除外するべきではありません。
  2. UA がスキャンの有効化に失敗した場合は、次のいずれかのエラーとともに promisereject し、以下のステップを中止します。:
    UA がアドバタイズメントのスキャンをサポートしていない場合
    NotSupportedError
    Bluetooth がオフになっている場合
    InvalidStateError
    それ意外の場合
    UnknownError
  3. 以下のステップを実行するためにタスクをキューイングします。:
    1. this.watchingAdvertisementstrue にセットします。
    2. undefined とともに promise を Resolve します。

unwatchAdvertisements() メソッドは、呼び出されると、以下のステップを実行しなければなりません。:

  1. this.watchingAdvertisementsfalse にセットします。
  2. UA全体の中で BluetoothDevice にこれ以上 watchingAdvertisementsture がセットされていない場合、UA はアドバタイズメントのスキャンを停止する必要があります。同様に、this として同一デバイスを表す BluetoothDevicewatchingAdvertisementstrue がこれ以上セットされていない場合、UA はこのデバイスのレポートを受信しないようにスキャンを再構成する必要があります。

4.2.1. Responding to Advertising Events

アドバタイズイベントが watchingAdvertisements のセットとともに BluetoothDevice に届くと、UA は "advertisementreceived" を提供します。

interface BluetoothManufacturerDataMap {
  readonly maplike<unsigned short, DataView>;
};
interface BluetoothServiceDataMap {
  readonly maplike<UUID, DataView>;
};
[Constructor(DOMString type, BluetoothAdvertisingEventInit init)]
interface BluetoothAdvertisingEvent : Event {
  readonly attribute BluetoothDevice device;
  readonly attribute FrozenArray<UUID> uuids;
  readonly attribute DOMString? name;
  readonly attribute unsigned short? appearance;
  readonly attribute byte? txPower;
  readonly attribute byte? rssi;
  readonly attribute BluetoothManufacturerDataMap manufacturerData;
  readonly attribute BluetoothServiceDataMap serviceData;
};
dictionary BluetoothAdvertisingEventInit : EventInit {
  required BluetoothDevice device;
  sequence<(DOMString or unsigned long)> uuids;
  DOMString name;
  unsigned short appearance;
  byte txPower;
  byte rssi;
  Map manufacturerData;
  Map serviceData;
};

uuids が Service の UUID をリストすることは、このアドバタイズメントが device の GATT server をサポートしているということです。

namedevice のローカルネーム、またはそのプレフィックスです。

appearanceAppearanceorg.bluetooth.characteristic.gap.appearance characteristic によって定義される値の一つです。

txPower はデバイスがブロードキャストする時の送信電力で、単位はdBmで測定されます。この値は、this.txPower - this.rssi により、パス損失を計算するために使用します。

rssi はデバイスパケットが受診された時の電力で、単位はdBmで測定されます。この値は、this.txPower - this.rssi により、パス損失を計算するために使用します。

manufacturerData maps unsigned short は、unsigned short 会社識別コード(Company Identifier Code) をDataView にマップします。

serviceData は、UUID群を DataView にマップします。

デバイスを取得し、そこから iBeacon データを読み込むため、以下のコードを開発者は利用できます。このAPIは、現在のところ、ある manufacturer data をデバイスに要求する方法を提供していないことに注意してください。従って、ユーザーがこのデバイスを requestDevice ダイアログの中で選ぶには、iBeacon は既知サービスに含むためにアドバタイズメントを循環させて必要があるでしょう。

var known_service = "A service in the iBeacon’s GATT server";
return navigator.bluetooth.requestDevice({
  filters: [{services: [known_service]}]
}).then(device => {
  device.watchAdvertisements();
  device.addEventListener('advertisementreceived', interpretIBeacon);
});

function interpretIBeacon(event) {
  var rssi = event.rssi;
  var appleData = event.manufacturerData.get(0x004C);
  if (appleData.byteLength != 23 ||
    appleData.getUint16(0, false) !== 0x0215) {
    console.log({isBeacon: false});
  }
  var uuidArray = new Uint8Array(appleData.buffer, 2, 16);
  var major = appleData.getUint16(18, false);
  var minor = appleData.getUint16(20, false);
  var txPowerAt1m = -appleData.getInt8(22);
  console.log({
      isBeacon: true,
      uuidArray,
      major,
      minor,
      pathLossVs1m: txPowerAt1m - rssi});
});

iBeacon 形式のアドバタイズメントは、How do iBeacons work? (by Adam Warski) に由来しています。

UA は、(アドバタイジングパケットおよびオプションのスキャン応答からなる)アドバタイジングイベントを受信すると、以下のステップを実行する必要があります。:

  1. device を、アドバタイジングイベントを送信した Bluetooth device にします。
  2. UA の各 BluetoothDevice deviceObj について、device同一デバイスであるような deviceObj@[[representedDevice]] には、以下のサブステップを実行するために、deviceObjrelevant settings objectresponsible event loopタスクをキューイングします。:
    1. deviceObj.watchingAdvertisementsfalse の場合、以下のサブステップを中止します。
    2. deviceObj のアドバタイズイベント に対して advertisementreceived を開始します。

BluetoothDevice deviceObj の アドバタイズイベント adv に対して advertisementreceived イベントを開始するために、UA は以下のステップを実行しなければなりません。:

  1. event を以下のようにします。
    {
      bubbles: true,
      device: deviceObj,
      uuids: [],
      manufacturerData: new Map(),
      serviceData: new Map()
    }
    
  2. いずれの adv 内のパケットに対して received signal strength が利用可能の場合、event.rssi を dBm 単位でこの信号強度にセットします。
  3. adv のアドバタイズパケットとスキャン応答の各 AD structure に対して、AD タイプに応じて以下のステップから選択します。:
    Incomplete List of 16-bit Service UUIDs
    Complete List of 16-bit Service UUIDs
    Incomplete List of 32-bit Service UUIDs
    Complete List of 32-bit Service UUIDs
    Incomplete List of 128-bit Service UUIDs
    Complete List of 128-bit Service UUIDs
    リストされた UUID を event.uuids にアペンドします。
    Shortened Local Name
    Complete Local Name
    AD データにBOM 無しで UTF-8 デコードし、event.name を結果にセットします。

    注記: 私たちは、既存の API がこの情報を取得するために、生のアドバタイズメントを読み取ることを要求するため、name が完了したかどうか公開されていません。そして、私たちは、それが API にフィールドを追加する前に有用だという証拠をより多く求めています。

    Manufacturer Specific Data
    16ビットの固有識別コードから manufacturer 固有のデータを含む ArrayBuffer へのマッピングを event.manufacturerData に追加します。
    TX Power Level
    event.txPower を AD data にセットします。
    Service Data - 16 bit UUID
    Service Data - 32 bit UUID
    Service Data - 128 bit UUID
    event.serviceData a mapping from the UUID から サービスデータを含む ArrayBuffer へのマッピングを event.serviceData に追加します。
    Appearance
    event.appearance を AD data にセットします。
    そうでない場合
    次の AD structure にスキップします。
  4. deviceObj にて、その isTrusted 属性を true に初期化して、新たな BluetoothAdvertisingEvent("advertisementreceived", event) として初期化するイベントを開始します。

BluetoothAdvertisingEvent 内の全フィールドは、初期化またはセットされた最後の値を返します。

BluetoothAdvertisingEvent(type, init) constructor は以下のステップを実行する必要があります。:

  1. event を、uuidsmanufacturerData、および serviceData メンバーを除いた DOM §3.4 Constructing events からのステップを実行した結果とします。
  2. init.uuids がセットされている場合、event.uuidsinit.uuids.map(BluetoothUUID.getService) の要素を含む新しい FrozenArray に初期化します。 そうでない場合は、event.uuids を空の FrozenArray に初期化します。
  3. init.manufacturerData 内の各マッピングに対して:
    1. codeunsigned short に変換されたキーとします。
    2. value は値とします。
    3. valueBufferSource でない場合、TypeError をスローします。
    4. bytes を、value により保持されているバイトのコピー を含む read only ArrayBuffer にします。
    5. code から event.manufacturerData@[[data]]new DataView(bytes) へのマッピングを追加します。
  4. init.serviceData 内の各マッピングに対して:
    1. key をキーにします。
    2. serviceBluetoothUUID.getService(key). をコールした結果にします。
    3. value を値にします。
    4. valueBufferSource でない場合、TypeError をスローします。
    5. bytes を、value により保持されているバイトのコピー を含む read only ArrayBuffer にします。
    6. service から event.serviceData@[[data]]new DataView(bytes) へのマッピングを追加します。
  5. event を返します。
4.2.1.1. BluetoothManufacturerDataMap

次のテーブルで説明する内部スロットを使用して、BluetoothManufacturerDataMap インスタンスが作成されます。:

内部スロット 初期値 説明(参考)
[[data]] new Map() DataView に変換されているアドバタイズされた manufacturer data

BluetoothManufacturerDataMap インスタンスの map entries は、[[data]] にあるエントリーです。

4.2.1.2. BluetoothServiceDataMap

次のテーブルで説明する内部スロットを使用して、BluetoothServiceDataMap インスタンスが作成されます。:

内部スロット 初期値 説明(参考)
[[data]] new Map() DataView に変換されているアドバタイズされた service data

BluetoothServiceDataMap インスタンスの map entries は、[[data]] のエントリー群です。

5. GATT Interaction

5.1. GATT 情報モデル

GATT Profile Hierarchy は、どのように GATT Server が Profiles、Primary ServiceIncluded ServiceCharacteristicDescriptor の階層を格納しているかを記述します。

Profile 群は純粋に論理的です。: Profile の仕様書には、その Profile を含む、他の GATT エンティティ群との間と期待されるインタラクションが記述されていますが、デバイスがどの Profile をサポートしているか問い合わせることはできません。

GATT Client 群は、GATT procedures の集合を使っているデバイス上の Services、Characteristics、Descriptors を検索してインタラクションすることが可能です。本仕様ではこれらを総称して Attribute群 と呼んでいます。 全ての Attributes には、UUID で識別される型があります。また、各 Attribute は GATT Server 上で同じ型を持つ他の Attributes と区別する、16ビット Attribute Handle を持っています。Attributes は、概念的には GATT Server 内で Attribute Handle により順位付けられています。プラットフォームインタフェースは、これらのエンティティ群をある順序で提供していますが、Attribute Handle の順序と一致することは保証しません。

Service には、Included Service群と Characteristic群のコレクションが含まれます。Included Service群は、他の Service 群への参照で、1つの Service を他の1つ以上の Service に含めることができます。Service群は、 GATT Server に直接現れる場合、Primary Services と呼ばれます。また、他の Service に含まれるだけで Primary Services にも含まれる可能性がある場合、Secondary Services と呼ばれます。

Characteristic には(バイトの配列である)値と、Descriptor群のコレクションを格納します。 Characteristic の properties によっては、GATT Client は、値の読み書きができ、また、値が変化した時に通知を受け取るよう登録ができます。

最後に、Descriptor は、Characteristic を記述したり構成する(これもバイトの配列である)値を格納します。

5.1.1. The Bluetooth cache

UA は、これまでにデバイス上で検索した Services、Characteristics、Descriptors の階層の Bluetooth cache を保持しなければなりません。UA は、同一デバイスをアクセスする複数のオリジン間でこのキャッシュを共有できます。キャッシュ内のエンティティは、known-present 、known-absent、あるいはunknownのいずれかが可能です。cache には、同一属性に対してエンティティを2つ格納してはいけません。キャッシュの各 known-present エンティティは、各 Bluetooth インスタンスに対して Promise<BluetoothRemoteGATTService>Promise<BluetoothRemoteGATTCharacteristic>Promise<BluetoothRemoteGATTDescriptor> のインスタンスがオプションで関連付けられています。

例えば、ユーザーが serviceA.getCharacteristic(uuid1) 関数を、初期状態が空の Bluetooth cache でコールした場合、UA はUUIDプロシージャによって Discover Characteristics を仕様し、必要なキャッシュエントリ群を埋めます。返された Promise を埋めるには Characteristic が1つだけ必要で、serviceA の最初の Characteristic with UUID uuid1 は known-present で、その UUID を持つそれ以降の Characteristic 群は unknown のままです。そのため、この手続きは UA では早く終了します。ユーザが後で serviceA.getCharacteristics(uuid1) をコールすれば、UA は Discover Characteristics by UUID procedure を再開または再スタートする必要があります。serviceA だけが UUID uuid1 を持つ Characteristic を1つしか持たないことがわかった場合、 それ以降の Characteristic 群は known-absent になります。

Bluetooth cache 内の known-present エントリー群は順序付けられています。:Primary Service 群はデバイス内で、含まれている Services と Characteristics は Services 内で、Descriptors は Characteristics 内で、それぞれ、ある特定の順序で現れます。この順序は、デバイスのAttribute Handle 群の順序とマッチする必要がありますが、デバイスの順序が利用できない場合、UA は別の順序を使えます。

ある description とマッチするエントリーを Bluetooth cache に格納する#populate-the-bluetooth-cacheReferenced in:3. Device Discovery4.3. BluetoothDevice (2)5.1.1. The Bluetooth cache 場合、UA は以下のステップを実行しなければなりません。: 以下のステップはブロックされる可能性があることに注意してください。従って、このアルゴリズムを利用するには 並列に実行しなければなりません。

  1. [BLUETOOTH42] が規定する GATT procedures のいずれをシーケンスを用いて、キャッシュ内のマッチするエントリー全てを known-present または known-absent にすれば、十分な情報が返されるでしょう。§5.7 Error handling で記載されたようにエラーを処理します。
  2. 以前のステップでエラーが返された場合、このアルゴリズムからそのエラーを返してください。

ある description にマッチするエントリー群に対して、 BluetoothDevice インスタンスの deviceObjluetooth cache を問い合わせる#query-the-bluetooth-cacheReferenced in:5.1.2. Navigating the Bluetooth Hierarchy5.1.3. Identifying Services, Characteristics, and Descriptors には、UA は新しい promise promisedeviceObj.gatt-connection-checking wrapper を返し、同時に以下のステップを実行しなければなりません。:

  1. deviceObj.gatt.connected の値が false だった場合、 NetworkErrorpromisereject し、以下のステップを中止します。
  2. 記述とマッチするエントリーを Bluetooth cache に格納します。
  3. 以前のステップでエラーが返された場合、そのエラーで promisereject し、以下のステップを中止します。
  4. エントリー群を、記述とマッチする known-present キャッシュエントリーのシーケンスにします。
  5. contextdeviceObj@[[context]] にします。
  6. result を新しいシーケンスにします。
  7. entries の各 entryに対して:
    1. entrycontext@[[attributeInstanceMap]]Promise<BluetoothGATT*> インスタンスと関連付けられていない場合、エントリーが、Service, Characteristic, or Descriptor,に応じて、エントリーを表すBluetoothRemoteGATTService representing entry, create a BluetoothRemoteGATTCharacteristic representing entry, or create a BluetoothRemoteGATTDescriptor representing entry を作成します。そして、エントリーから結果として得られた context@[[attributeInstanceMap]]Promise へのマッピングを追加します。
    2. context@[[attributeInstanceMap]] のエントリーに関連した Promise<BluetoothGATT*> インスタンスを result にアペンドします。
  8. result の全エレメントを待っている結果を用いて、promiseresolve します。

GetGATTChildren(attribute: GATT Attribute,
single: boolean,
uuidCanonicalizer: function,
uuid: optional (DOMString or unsigned int),
allowedUuids: optional ("all" or Array<DOMString>),
child type: GATT declaration type),

のために、UA 以下のステップを実行しなければなりません。:

  1. uuid が存在する場合、それを uuidCanonicalizer(uuid) にセットします。uuidCanonicalizer で例外がスローされた場合、その例外で promise を rejecte して返し、以下のステップを中止します。
  2. uuid が存在し、blacklisted されている場合、SecurityErrorpromise を reject して返し、以下のステップを中止します。
  3. deviceObjattribute のタイプに応じて、以下のようにします。:
    BluetoothDevice
    attribute
    BluetoothRemoteGATTService
    attribute.device
    BluetoothRemoteGATTCharacteristic
    attribute.service.device
  4. 以下の条件に合うエントリーを、deviceObjBluetooth cache に問い合わせします。:
    • attribute により表現されている Bluetooth エンティティ内部にあること。
    • child type で記述された型を持っていること。
    • uuid が存在する場合、uuid の UUID を有していること。
    • allowedUuids が存在し、"all" でない場合に、allowedUuids に UUID があること。
    • single フラグがセットされている倍、その中で最初であること。
    promise を結果にセットします。
  5. 以下の動作をする fulfillment handler を使用して、transforming promise の結果を返します。:
    • 引数が空の場合、NotFoundError をスローします。
    • そうでない場合で、single フラグがセットされている場合、この引数の最初(だけ)のエレメントを返します。
    • そうでない場合、その引数を返します。

5.1.3. Services、Characteristics、Descriptors の識別子

2つの Services、Characteristics、Descriptors ab同一属性#same-attributeReferenced in:5.1.1. The Bluetooth cache5.1.3. Identifying Services, Characteristics, and Descriptors (2) (3)5.6.5. Responding to Service Changesかチェックする場合、 UA は ab同一デバイスの中にあり、同一の Attribute Handle を持つことを判断する必要があります。 しかし、ab が以下の条件のいずれかに適合する場合、同一属性とみなしてはいけない制約を持つ、UA が利用できる任意のアルゴリズムを使用することができます。:

  • 両方が Services でも Characteristics でも Descriptors でもない。
  • 両方が Services であるが、両方が、primary services または secondary services ではない。
  • 異なる UUID を持っている。
  • 親デバイスが同一デバイスではなく、親 Services または親 Characteristics が 同一属性ではない。

この定義は緩くなっています。Platform API 群は、同一性が Attribute Handle の等価性に基づくものなのかどうかを文章化しないまま、同一性の概念を公開しているからです。

Services、Characteristics、Descriptors を表現している xy という2つの Javascript object に対して、x === y は、Bluetooth cache に問い合わせをするアルゴリズムが新しいオブジェクトを作成し、キャッシュする方法に起因する、オブジェクトが 同一属性を表現するかどうかを返します。

5.2. BluetoothRemoteGATTServer

BluetoothRemoteGATTServer は、リモートデバイス上の GATT Server を表します。

interface BluetoothRemoteGATTServer {
  readonly attribute BluetoothDevice device;
  readonly attribute boolean connected;
  Promise<BluetoothRemoteGATTServer> connect();
  void disconnect();
  Promise<BluetoothRemoteGATTService> getPrimaryService(BluetoothServiceUUID service);
  Promise<sequence<BluetoothRemoteGATTService>>
    getPrimaryServices(optional BluetoothServiceUUID service);
};
注記: BluetoothRemoteGATTServer attributes

device は、このサーバーを実行しているデバイスです。

このインスタンスが this.device に接続されている間、connected は true です。UA が物理的に接続されている間で、例えば、他の global object 用に接続された他の BluetoothRemoteGATTServer インスタンスがある場合には false になることがあります。

ECMAScript コードが、BluetoothRemoteGATTServer server のインスタンスを観測できなくなった時、UA は server.disconnect() を実行する必要があります。 BluetoothDevice インスタンスは navigator.bluetooth.[[deviceInstanceMap]] に格納されているので、ナビゲーションが global object を開放するか、タブまたはウィンドウを閉じ、 browsing context が破壊されるまで、少なくともこのようなことは起こりえないのです。 ガベージコレクション時に切断されますが、UA が必要もないのにリモートデバイスのリソースを消費し続けることがないことの保証になっています。

次のテーブルで説明される内部スロットを使用して、BluetoothRemoteGATTServer のインスタンスが作成されます。:

内部スロット 初期値 説明(参考)
[[activeAlgorithms]] new Set() このサーバの接続を用いた各アルゴリズムに対応する Promise を含みます。disconnect() は、実行中にこの realm が切断されたことがあったかアルゴリズムで判断できるよう、この集合を空にします。

connect() メソッドは、呼び出されると、新しい promise promise を返し、同時に以下のステップを実行しなければなりません。:

  1. this.device@[[representedDevice]]null の場合、NetworkErrorpromisereject し、以下のステップを中止します。
  2. this.device@[[representedDevice]]ATT Bearer が無い場合、GAP Interoperability Requirements の「接続確立」で説明されるプロシージャを使って作成を試みます。
  3. この試行が失敗したら、NetworkErrorpromisereject し、以下のステップを中止します。
  4. 以下のサブステップを実行するためにタスクをキューイングします。:
    1. this.device@[[representedDevice]]null の場合、NetworkErrorpromisereject し、以下のステップを中止します。
    2. this.connectedtrue をセットします。
    3. this を使って promiseresolve します。

disconnect() メソッドは、呼び出されると、以下のステップを実行しなければなりません。:

  1. this.connectedfalse の場合、以下のステップを中止します。
  2. this@[[activeAlgorithms]] をクリアします。
  3. this.connectedfalse にします。
  4. this.devicebubbles 属性を true に初期化して、gattserverdisconnected という名前のイベントを開始します。

    このイベントは、BluetoothRemoteGATTServer では動作しません

  5. devicethis.device@[[representedDevice]] にします。
  6. 同時にdeviceObj@[[representedDevice]] をデバイスとして 同一デバイスを持つ UA 全体の 全 BluetoothDevices deviceObj に対して、 deviceObj.gatt.connectedfalse である場合、UA は deviceATT Bearer を破壊する必要があります。

実行中に BluetoothRemoteGATTServer が切断された場合、UA がずっと接続されており、終了前に BluetoothRemoteGATTServer がその後再接続されたとしても、アルゴリズムは fail する必要があります。これを達成するため、返された Promise のラッパーを作成します。

gattServer-connection-checking wrapper around a Promise promise を作成するには、UA は以下のことが必要です。:

  1. gattServer.connectedtrue の場合、promisegattServer@[[activeAlgorithms]] を追加します。
  2. transforming promise の結果を、以下のステップを実行する fulfillment ハンドラと rejection ハンドラと一緒に返します。:
    fulfillment handler
    1. promisegattServer@[[activeAlgorithms]] にある場合、それを取り除き、最初の引数を返します。
    2. そうでない場合には、NetworkError をスローします。 gattServer はメインアルゴリズム実行中に切断されたからです。
    rejection handler
    1. promisegattServer@[[activeAlgorithms]] にある場合、それを取り除き、最初の引数を返します。
    2. そうでない場合には、 NetworkError をスローします。 gattServer はメインアルゴリズム実行中に切断されたからです。

getPrimaryService(service) メソッドは、呼び出されると、以下のステップを実行しなければなりません。:

  1. this.device@[[allowedServices]]"all" でなく、servicethis.device@[[allowedServices]] に無い場合、SecurityError とともに promise を reject して返し、以下のステップを中止します。
  2. この値を返します。GetGATTChildren(attribute=this.device,
    single=true,
    uuidCanonicalizer=BluetoothUUID.getService,
    uuid=service,
    allowedUuids=this.device@[[allowedServices]],
    child type="GATT Primary Service")

getPrimaryServices(service) メソッドは、呼び出されると、以下のステップを実行しなければなりません。:

  1. this.device@[[allowedServices]]"all" ではなく、service が存在し、this.device@[[allowedServices]] の中に無い場合、SecurityError とともに promise を reject して返し、以下のステップを中止します。
  2. この値を返します。 GetGATTChildren(attribute=this.device@[[representedDevice]],
    single=false,
    uuidCanonicalizer=BluetoothUUID.getService,
    uuid=service,
    allowedUuids=this.device@[[allowedServices]],
    child type="GATT Primary Service")

5.3. BluetoothRemoteGATTService

BluetoothRemoteGATTService は GATT Service 、つまり、デバイスの一部の振る舞いをカプセル化する characteristic 群のコレクションと他の Service 群との関係を表わしています。

interface BluetoothRemoteGATTService {
  readonly attribute BluetoothDevice device;
  readonly attribute UUID uuid;
  readonly attribute boolean isPrimary;
  Promise<BluetoothRemoteGATTCharacteristic>
    getCharacteristic(BluetoothCharacteristicUUID characteristic);
  Promise<sequence<BluetoothRemoteGATTCharacteristic>>
    getCharacteristics(optional BluetoothCharacteristicUUID characteristic);
  Promise<BluetoothRemoteGATTService>
    getIncludedService(BluetoothServiceUUID service);
  Promise<sequence<BluetoothRemoteGATTService>>
    getIncludedServices(optional BluetoothServiceUUID service);
};
BluetoothRemoteGATTService implements EventTarget;
BluetoothRemoteGATTService implements CharacteristicEventHandlers;
BluetoothRemoteGATTService implements ServiceEventHandlers;
注記: BluetoothRemoteGATTService attributes

device は GATT service が属するリモート peripheral を表している BluetoothDevice です。

uuid は、service の UUID で、例えば、Heart Rate service に対しては '0000180d-0000-1000-8000-00805f9b34fb' です。

isPrimary#dom-bluetoothremotegattservice-isprimaryReferenced in:5.1. GATT Information Model5.3. BluetoothRemoteGATTService は、この service のタイプが primary か secondary かを示します。

Service service を表す BluetoothRemoteGATTService を作成する には、UA は新しい promise promise を返し、同時に以下のステップを実行しなければなりません。

  1. resultBluetoothRemoteGATTService の新しいインスタンスにします。
  2. service が現れるデバイスを表す BluetoothDevice を取得 し、device を結果とします。
  3. 以前のステップでエラーがスローされた場合、そのエラーとともに promisereject し、以下のステップを中止します。
  4. result.devicedevice から初期化します。
  5. result.uuidservice の UUID から初期化します。
  6. service が Primary Service の場合、result.isPrimary を true に初期化します。そうでない場合、result.isPrimary を false に初期化します。
  7. result を使って promiseresolve します。

getCharacteristic(characteristic) メソッドは、この Service の内部から Characteristic を取り出します。呼び出されると、次の値を返さなければなりません。

GetGATTChildren(attribute=this,
single=true,
uuidCanonicalizer=BluetoothUUID.getCharacteristic,
uuid=characteristic,
allowedUuids=undefined,
child type="GATT Characteristic")

getCharacteristics(characteristic) メソッドは、この Service の内部から Characteristic のリストを取り出します。呼び出されると、次の値を返さなければなりません。

GetGATTChildren(attribute=this,
single=false,
uuidCanonicalizer=BluetoothUUID.getCharacteristic,
uuid=characteristic,
allowedUuids=undefined,
child type="GATT Characteristic")

getIncludedService(service) メソッドは、この Service の内部から Included Service を取り出します。呼び出されると、次の値を返さなければなりません。

GetGATTChildren(attribute=this,
single=true,
uuidCanonicalizer=BluetoothUUID.getService,
uuid=service,
allowedUuids=undefined,
child type="GATT Included Service")

getIncludedServices(service) メソッドは、この Service の内部から Included Service のリストを取り出します。呼び出されると、次の値を返さなければなりません。

GetGATTChildren(attribute=this,
single=false,
uuidCanonicalizer=BluetoothUUID.getService,
uuid=service,
allowedUuids=undefined,
child type="GATT Included Service")

5.4. BluetoothRemoteGATTCharacteristic

BluetoothRemoteGATTCharacteristic は GATT Characteristic を表し、peripheral の service について詳細情報を提供する基本データエレメントです。

interface BluetoothRemoteGATTCharacteristic {
  readonly attribute BluetoothRemoteGATTService service;
  readonly attribute UUID uuid;
  readonly attribute BluetoothCharacteristicProperties properties;
  readonly attribute DataView? value;
  Promise<BluetoothRemoteGATTDescriptor> getDescriptor(BluetoothDescriptorUUID descriptor);
  Promise<sequence<BluetoothRemoteGATTDescriptor>>
    getDescriptors(optional BluetoothDescriptorUUID descriptor);
  Promise<DataView> readValue();
  Promise<void> writeValue(BufferSource value);
  Promise<void> startNotifications();
  Promise<void> stopNotifications();
};
BluetoothRemoteGATTCharacteristic implements EventTarget;
BluetoothRemoteGATTCharacteristic implements CharacteristicEventHandlers;

service は、この characteristic が属する GATT service です。

uuid は、characteristic の UUID で、例えば、Heart Rate Measurement であれば '00002a37-0000-1000-8000-00805f9b34fb' です。

properties は、この characteristic のプロパティを保持します。

value は、現在キャッシュされている characteristic の値です。この値は、characteristic の値が notification や indication 経由で読み出されたり更新される場合に更新されます。

Characteristic characteristic表す BluetoothRemoteGATTCharacteristic を作成するには、UA が新しい promise promise を返し、同時に以下のステップを実行しなければなりません。

  1. resultBluetoothRemoteGATTCharacteristic の新しいインスタンスにします。
  2. result.service を、characteristic が現れる Service を表す BluetoothRemoteGATTService インスタンスから初期化します。
  3. result.uuidcharacteristic の UUID から初期化します。
  4. BluetoothCharacteristicProperties インスタンスを Characteristic characteristic から作成し、propertiesPromise を結果とします。
  5. propertiesPromise が settle するまで待ちます。
  6. propertiesPromise が reject された場合、propertiesPromisepromiseresolve し、以下のステップを中止します。
  7. result.propertiespropertiesPromise が fulfilled された値から初期化します。
  8. result.valuenull に初期化します。UA は result.value を新しい DataView に初期化できます。この DataView は、characteristic から直近に読み込まれた値(値が利用可能な場合)を格納する新しい ArrayBuffer のラッパーです。
  9. result を使って promiseresolve します。

getDescriptor(descriptor) メソッドは、この Characteristic の内部から Descriptor を取り出します。呼び出されると、次の値を返さなければなりません。

GetGATTChildren(attribute=this,
single=true,
uuidCanonicalizer=BluetoothUUID.getDescriptor,
uuid=descriptor,
allowedUuids=undefined,
child type="GATT Descriptor")

getDescriptors(descriptor) メソッドは、この Characteristic の内部から Descriptor のリストを取り出します。呼び出されると、次の値を返さなければなりません。

GetGATTChildren(attribute=this,
single=false,
uuidCanonicalizer=BluetoothUUID.getDescriptor,
uuid=descriptor,
allowedUuids=undefined,
child type="GATT Descriptor")

readValue() メソッドは、呼び出されると、以下のステップを実行しなければなりません。:

  1. this.uuid読み込みのブラックリストにある場合、SecurityErrorpromise を reject して返し、以下のステップを中止します。
  2. this.characteristic.service.device.gatt.connectedfalse の場合、reject された promiseNetworkError で返し、以下のステップを中止します。
  3. characteristic を、this が表している Characteristic にします。
  4. 新しい promise promisethis.service.device.gatt-connection-checking wrapper を返し、同時に以下のステップを実行します。:
    1. Read ビットが characteristicproperties でセットされていない場合、NotSupportedErrorpromisereject し、以下のステップを中止します。
    2. characteristic の値を取り出すためには、Characteristic Value Read プロシージャ無いのサブプロシージャの組み合わせを使用します。§5.7 Error handling で記載されたようにエラーをハンドリングします。
    3. 以前のステップでエラーが返さえれた場合、エラーとともに promisereject し、以下のステップを中止します。
    4. 以下のステップを実行するためにタスクをキューインします:
      1. promisethis.service.device.gatt@[[activeAlgorithms]] の中に無い場合、NetworkErrorpromisereject し、以下のステップを中止します。
      2. buffer を、取り出された値を保持する ArrayBuffer とし、new DataView(buffer)this.value に割り当てます。
      3. ここで bubbles 属性を true に初期化して、characteristicvaluechanged という名前の イベントを開始します。
      4. this.value を使って promsieresolve します。

writeValue(value) メソッドは、呼び出されると、以下のステップを実行しなければなりません。:

  1. this.uuid書き込みのブラックリストにある場合、SecurityErrorpromise を reject して返し、以下のステップを中止します。
  2. characteristic を、this が表している Characteristic にします。
  3. bytes を、value により 保持されたバイトのコピー とします。
  4. bytes が 512 バイト(Long Attribute Values あたりの属性値の最大長)を超える場合、InvalidModificationError を使って promise を reject して返し、以下のステップを中止します。
  5. this.service.device.gatt.connectedfalse の場合、NetworkErrorpromise を reject して返し、以下のステップを中止します。
  6. 新しい promise promisethis.service.device.gatt-connection-checking wrapper を返し、同時に以下のステップを実行します。
    1. WriteWrite Without ResponseAuthenticated Signed Writes ビットのいずれも characteristicproperties で設定されていない場合、NotSupportedError とともに promisereject し、以下のステップを中止します。
    2. characteristicbytes を書き込むには、Characteristic Value Write procedure 内のサブプロシージャの組み合わせを使用します。§5.7 Error handling で記載されたようにエラーをハンドリングします。
    3. 以前のステップでエラーが返さえれた場合、エラーとともに promisereject し、以下のステップを中止します。
    4. 以下のステップを実行するためにタスクをキューイングします。:
      1. promisethis.service.device.gatt@[[activeAlgorithms]] に無い場合、NetworkError を使って promisereject し、以下のステップを中止します。
      2. this.value を、bytes を格納する新しい ArrayBuffer のラッパーである、新しい DataView にセットします。
      3. undefined を使って promiseresolve します。

UA は、既知の GATT Characteristic の アクティブな notification のコンテキストセット( active notification context set#active-notification-context-setReferenced in:5.4. BluetoothRemoteGATTCharacteristic (2) (3) (4)5.6.4. Responding to Notifications and Indications )と呼ばれる Bluetooth オブジェクト群の集合へのマッピングを保持しなければなりません。 既知の characteristic の集合は、notification に登録された各 Realm に対する navigator.bluetooth オブジェクト群を格納しています。

startNotifications() メソッド、呼び出されると、新しい promise promise を返し、同時に以下のステップを実行しなければなりません。notification の受信の詳細は §5.6.4 Responding to Notifications and Indications を参照してください。

  1. this.uuid読み込みのブラックリストにある場合、SecurityError を使って promisereject し、以下のステップを中止します。
  2. characteristicthis が表している GATT Characteristic にします。
  3. Notify または Indicate ビットのいずれかが characteristicproperties でオンでない場合、NotSupportedError を使って promisereject し、以下のステップを中止します。
  4. characteristic の アクティブな notification のコンテキストセット( active notification context set )が navigator.bluetooth を含む場合、undefined を使って promiseresolve し、以下のステップを中止します。
  5. this.service.device.gatt.connectedfalse の場合、NetworkError を使って promisereject し、以下のステップを中止します。
  6. Characteristic Descriptors procedures を使用して、characteristicClient Characteristic Configuration descriptor に Notification または Indication ビットの一つが確実にセットされ、characteristicproperties の制約と整合させてください。UA は、ビットを両方セットすることを避ける必要があり、両方のビットがセットされる場合は、value-change イベント群を分解しなければなりません。§5.7 Error handling で記載されたようにエラーをハンドリングします。
  7. 以前のステップでエラーが返さえれた場合、エラーとともに promisereject し、以下のステップを中止します。
  8. navigator.bluetoothcharacteristic のアクティブ notification コンテキストセット( active notification context set )に追加します。
  9. undefined を使って promiseresolve します。

notification が有効になった後、結果の value-change event 群は、現在のマイクロタスクのチェックポイント( microtask checkpoint. )が終了するまで配送されません。この結果、開発者は結果の promise の .then handler に、ハンドラを設定することができます。

stopNotifications() メソッドは、呼び出されると、新しい promise promise を返し、同時に以下のステップを実行しなければなりません。:

  1. characteristicthis が表している GATT Characteristic にします。
  2. characteristic の アクティブな notification コンテキストセット( active notification context set ) に navigator.bluetooth が含まれる場合、削除してください。
  3. characteristic の アクティブな notification コンテキストセット( active notification context set ) が空になった場合、characteristicClient Characteristic Configuration descriptor の Notification and Indication ビットをクリアするために、UA は Characteristic Descriptors procedures を使用する必要があります。
  4. undefinedpromiseresolve するために タスクをキューイングします。

promise を resolve するタスクをキューイングすれば、promise が resolve した後、notification の到着による value change events イベントがないことを保証できます。

5.4.1. BluetoothCharacteristicProperties

BluetoothRemoteGATTCharacteristic は、characteristic propertiesBluetoothCharacteristicProperties オブジェクトを通して公開します。このプロパティ群は、characteristic で何のオペレーションが有効なのかを表しています。

interface BluetoothCharacteristicProperties {
  readonly attribute boolean broadcast;
  readonly attribute boolean read;
  readonly attribute boolean writeWithoutResponse;
  readonly attribute boolean write;
  readonly attribute boolean notify;
  readonly attribute boolean indicate;
  readonly attribute boolean authenticatedSignedWrites;
  readonly attribute boolean reliableWrite;
  readonly attribute boolean writableAuxiliaries;
};

create a BluetoothCharacteristicProperties インスタンスを Characteristic characteristic から作成するために、UA は新しい promise promise を返し、同時に以下のステップを実行しなければなりません。:

  1. propertiesObjBluetoothCharacteristicProperties の新しいインスタンスにします。
  2. propertiescharacteristiccharacteristic properties にします。
  3. propertiesObj 属性を properties の応答ビットから初期化します。:
    Attribute Bit
    broadcast Broadcast
    read Read
    writeWithoutResponse Write Without Response
    write Write
    notify Notify
    indicate Indicate
    authenticatedSignedWrites Authenticated Signed Writes
  4. characteristic properties の拡張プロパティビットがセットされていない場合、propertiesObj.reliableWritepropertiesObj.writableAuxiliariesfalse に初期化します。そうでない場合、以下のステップを実行します。:
    1. characteristic に対する Characteristic Extended Properties descriptor を 発見し、その値を extendedProperties読み込みします。§5.7 Error handling で記載されたようにエラーをハンドリングします。

      Characteristic Extended Properties は、Characteristic に対して拡張往路パティが変更不可能であるかどうか明らかではありません。もしそうならば、UA はキャッシュを許可するようにすべきです。

    2. 以前のステップでエラーが返さえれた場合、エラーとともに promisereject し、以下のステップを中止します。
    3. propertiesObj.reliableWriteextendedProperties の Reliable Write ビットから初期化します。
    4. propertiesObj.writableAuxiliariesextendedProperties の Writable Auxiliaries ビットから初期化します。
  5. propertiesObj を使って promiseresolve します。

5.5. BluetoothRemoteGATTDescriptor

BluetoothRemoteGATTDescriptor は GATT Descriptor を表し、Characteristic の値についての詳細情報を提供します。

interface BluetoothRemoteGATTDescriptor {
  readonly attribute BluetoothRemoteGATTCharacteristic characteristic;
  readonly attribute UUID uuid;
  readonly attribute DataView? value;
  Promise<DataView> readValue();
  Promise<void> writeValue(BufferSource value);
};

characteristic は、この descriptor が属する GATT characteristic です。

uuid は characteristic descriptor の UUID です。例えば Client Characteristic Configuration descriptor であれば '00002902-0000-1000-8000-00805f9b34fb' です。

value は descriptor に現在キャッシュされている値です。この値は、descriptor が読み出される時に更新されます。

Descriptor descriptorBluetoothRemoteGATTDescriptor を作成するには、UA は新しい promise を返し、同時に以下のステップを実行しなければなりません。

  1. resultBluetoothRemoteGATTDescriptor の新しいインスタンスにします。
  2. result.characteristic を、descriptor が現れる Characteristic を表している BluetoothRemoteGATTCharacteristic インスタンスから初期化します。
  3. result.uuid を UUID の descriptor から初期化します。
  4. result.valuenull に初期化します。UA は、result.value を新しい DataView に初期化できます。この DataView は、descriptor から直近に読み込まれた(値が利用可能な場合)を格納する ArrayBuffer のラッパーです。
  5. result を使って promiseresolve します。

readValue()#dom-bluetoothremotegattdescriptor-readvalueReferenced in:5.5. BluetoothRemoteGATTDescriptor メソッドは、呼び出されると、以下のステップを実行しなければなりません。:

  1. this.uuid読み込みの blacklist にある場合、SecurityError を使って promise を reject して返し、以下のステップを中止しなければなりません。
  2. this.characteristic.service.device.gatt.connectedfalse の場合、NetworkError を使って promise を reject して返し、以下のステップを中止しなければなりません。
  3. descriptorthis が表わしている Descriptor にします。
  4. 新しい promise promisethis.characteristic.service.device.gatt-connection-checking wrapper を返し、同時に以下のステップを実行しなければなりません。:
    1. Read Characteristic Descriptors または Read Long Characteristic Descriptors サブプロシージャを用いて、descriptor の値を読みだしてください。§5.7 Error handling で記載されたようにエラーをハンドリングします。
    2. 以前のステップでエラーが返さえれた場合、エラーとともに promisereject し、以下のステップを中止します。
    3. 以下のステップを実行するためにタスクをキューイングします。:
      1. promisethis.characteristic.service.device.gatt@[[activeAlgorithms]] の中に無い場合、NetworkError を使って promisereject し、以下のステップを中止します。
      2. buffer を、読み出した値を保持する ArrayBuffer とし、new DataView(buffer)this.value に割り当てます。
      3. this.value を使って promiseresolve します。

writeValue(value) メソッドは、呼び出されると、以下のステップを実行しなければなりません。:

  1. this.uuid書き込みの blacklist にある場合、SecurityError を使って promise を reject して返し、以下のステップを中止しなければなりません。
  2. descriptorthis が表している Descriptor にします。
  3. bytes を、value により 保持されているバイトのコピー にします。
  4. bytes が 512バイト(Long Attribute Values あたりの属性値の最大長)を越える場合、InvalidModificationError とともに promise を reject して返し、以下のステップを中止します。
  5. this.characteristic.service.device.gatt.connectedfalse の場合、NetworkError とともに promise を reject して返し、以下のステップを中止します。
  6. 新しい promise promisethis.characteristic.service.device.gatt-connection-checking wrapper を返し、同時に以下のステップを実行します。
    1. Write Characteristic Descriptors または Write Long Characteristic Descriptors サブプロシージャを用いて descriptorbytes を書き込みます。§5.7 Error handling で記載されたようにエラーをハンドリングします。
    2. 以前のステップでエラーが返さえれた場合、エラーとともに promisereject し、以下のステップを中止します。
    3. 以下のステップを実行するためにタスクをキューイングします。:
      1. promisethis.characteristic.service.device.gatt@[[activeAlgorithms]] に無い場合、NetworkError とともに promisereject し、以下のステップを中止します。
      2. this.value を、bytes を含む新しい ArrayBuffer をラップする、新しい DataView にセットします。
      3. undefined とともに promiseresolve します。

5.6. イベント

5.6.1. Bluetooth ツリー

navigator.bluetooth および BluetoothDeviceBluetoothRemoteGATTServiceBluetoothRemoteGATTCharacteristic、dBluetoothRemoteGATTDescriptor インタフェースを実現しているオブジェクトは、Bluetooth tree と呼ばれるツリーに参加しています

5.6.2. イベントタイプ

advertisementreceived
アドバタイズイベントをそのデバイスから受信した時BluetoothDevice 上で開始されます。
gattserverdisconnected
アクティブな GATT connection を失った時BluetoothDevice 上で開始されます。
servicechanged
BluetoothRemoteGATTService リモートデバイスを発見して、Bluetooth tree に追加した直後に新しい BluetoothRemoteGATTService で開始されます。
servicechanged
状態が変化した時に、BluetoothRemoteGATTService 上で開始されます。サービスに追加またはサービスから削除される characteristic 群 および/または descriptor 群、およびリモートデバイスからの Service Changed インディケーションが含まれます。
serviceremoved
デバイスから切断されBluetooth tree から削除される直前に BluetoothRemoteGATTService で開始されます。

5.6.3. 切断への応答

Bluetooth device deviceATT Bearer が(例えば、リモートデバイスが圏外に移動した、またはユーザが切断するプラットフォーム機能を使用したなどの理由で)失われた時、UA は、各 BluetoothDevice deviceObj に対して、以下のステップを実行するために、deviceObj の関連する設定オブジェクト( relevant settings object’ )が責任を持つイベントループ( responsible event loop )にタスクをキューイングしなければなりません。:

  1. deviceObj@[[representedDevice]]device同一デバイスでない場合、以下のステップを中止します。
  2. deviceObj.gatt.connected が false の場合、以下のステップを中止します。
  3. deviceObj.gatt.connectedfalse をセットします。
  4. deviceObj.gatt@[[activeAlgorithms]] をクリアします。
  5. deviceObjbubbles 属性を true に初期化して、gattserverdisconnected という名前のイベントを開始します。

    このイベントは BluetoothRemoteGATTServer では開始されません

5.6.4. Notifications と Indications への応答

UA が Bluetooth Characteristic Value Notification または Indication を受信した時、以下のステップを実行しなければなりません。:

  1. Characteristic のアクティブな notification コンテキストセット( active notification context set )の中にある各 bluetoothGlobal に対して、以下のサブステップを実行するために、bluetoothGlobal のスクリプト設定オブジェクトのイベントループにタスクをキューイングします。:
    1. characteristicObject を、Characteristic を表している bluetoothGlobal をルートとする Bluetooth tree にある BluetoothRemoteGATTCharacteristic にセットします。
    2. characteristicObject.service.device.gatt.connectedfalse の場合、以下のサブステップを中止します。
    3. characteristicObject.value を、Characteristic の新しい値を保持する新しい ArrayBuffer のラッパーである、新しい DataView にセットします。
    4. characteristicObject で bubbles 属性を true に初期化して characteristicvaluechanged という名前のイベントを開始します。

5.6.5. Service Changes への応答

Bluetooth Attribute Caching では、クライアントが ServiceCharacteristicDescriptor に加えられた変更を追跡することが可能です。ウェブページで公開する目的でエンティティを検索する前に、UA は Service Changed characteristic からの Indication を(存在すれば)購読しなければなりません。UA が Service Changed characteristic の Indication を受信した場合、以下のステップを実行しなければなりません。:

  1. removedEntities を、Indication 前に UA が発見していた Service Changed characteristic により Indicate された範囲内のエンティティのリストとします。
  2. Primary Service DiscoveryRelationship DiscoveryCharacteristic DiscoveryCharacteristic Descriptor Discovery プロシージャを使用して、Service Changed characteristic で indicate される範囲内の entity を再検索します。発見の結果が、以下で開始されるイベントに影響を与えないことが分かる場合は、UA は、indicate された範囲の全てまたは一部の検索をスキップできます。
  3. addedEntities を、前のステップで発見された entity のリストとします。
  4. 同じ定義を持つ entity が( Characteristic と Descriptor の値を無視して)removedEntitiesaddedEntities の両方に現れた場合、その entity を両方から削除します。
  5. changedServicesService の集合(初期値は空)とします。
  6. 同一 ServiceremovedEntitiesaddedEntities の両方に現れた場合、その service を両方から削除し、changedServices に追加します。
  7. removedEntitiesaddedEntities の各 CharacteristicDescriptor について、オリジナルリストから削除し、その親 ServicechangedServices に追加します。これ以降では、removedEntitiesaddedEntitiesService 群しか含んでいません。
  8. addedEntities 内の Service が、これまでおこなった getPrimaryServicegetPrimaryServicesgetIncludedServicegetIncludedServices 呼び出しから返されていない場合、service が呼び出し時点で存在していれば、UA は addedEntities からこの Service を削除できます。
  9. changedDevicesremovedEntitiesaddedEntitieschangedServices にある任意の Service を含む Bluetooth device のセットとします。
  10. changedDevices に接続された各 BluetoothDevice deviceObj に対して、以下のステップを実行するために、global object が責任を持つイベントループ( responsible event loop )に タスクをキューイングします。:
    1. removedEntities の各 Service service に対して:
      1. deviceObj@[[representedDevice]] の残りの Service が、service として同一 UUID を持たない場合、UUID を deviceObj@[[unfilteredUuids]] から削除します。
      2. Service の UUID が deviceObj@[[allowedServices]] にある場合、Service を表す BluetoothRemoteGATTServicebubbles 属性を true に初期化して、serviceremoved という名前の イベントを開始します。
      3. このBluetoothRemoteGATTServiceBluetooth tree から削除します。
    2. addedEntities の 各 Service に対して、Service の UUID を deviceObj@[[unfilteredUuids]] に追加します。 Service の UUID が deviceObj@[[allowedServices]] にある場合、この Service を表す BluetoothRemoteGATTServiceBluetooth tree に追加し、BluetoothRemoteGATTServicebubbles 属性を true に初期化して、serviceadded という名前の イベントを開始します。
    3. changedServices にある各 Service に対して、Service の UUID が deviceObj@[[allowedServices]] にある場合、Service を表す BluetoothRemoteGATTServicebubbles 属性を true に初期化して、servicechanged という名前のイベントを開始します。

5.6.6. IDL イベントハンドラ

[NoInterfaceObject]
interface CharacteristicEventHandlers {
  attribute EventHandler oncharacteristicvaluechanged;
};

oncharacteristicvaluechangedcharacteristicvaluechanged イベントタイプ用のイベントハンドラIDL属性( Event handler IDL attribute )です。

[NoInterfaceObject]
interface BluetoothDeviceEventHandlers {
  attribute EventHandler ongattserverdisconnected;
};

ongattserverdisconnectedgattserverdisconnected イベントタイプ用のイベントハンドラIDL属性( Event handler IDL attribute )です。

[NoInterfaceObject]
interface ServiceEventHandlers {
  attribute EventHandler onserviceadded;
  attribute EventHandler onservicechanged;
  attribute EventHandler onserviceremoved;
};

onserviceaddedserviceadded イベントタイプ用のイベントハンドラIDL属性( Event handler IDL attribute )です。

onservicechangedservicechanged イベントタイプ用のイベントハンドラIDL属性( Event handler IDL attribute )です。

onserviceremovedserviceremoved イベントタイプ用のイベントハンドラIDL属性( Event handler IDL attribute )です。

5.7. エラーハンドリング

本セクションでは、主に、システムエラーから Javascript エラー名へのマッピングを定義し、UA がいくつかのオペレーションをリトライできるようにします。リトライのロジックならびに可能性があるエラーの区別は、オペレーティングシステムに大きく制約を受けます。従って、この要件が現実を反映していない所は、ブラウザのバグではなく仕様バグである可能性が高いです。

UA が GATT procedure を用いてアルゴリズムでステップを実行したり、Bluetooth cache への問合せを処理したり(ここでは、この二つを「ステップ」と呼びます)、GATT procedure が Error Response を返す場合、UA は以下のステップを実行しなければなりません。:

  1. procedure が タイムアウトする、または ATT Bearer ( Profile Fundamentals で記載) が存在しない、または何らかの理由で終了した場合、NetworkError を返し、以下のステップを終了します。
  2. Error Code によっては、以下の措置を行ってください。:
    Invalid Handle
    Invalid PDU
    Invalid Offset
    Attribute Not Found
    Unsupported Group Type
    これらのエラーコードは、プロトコルレイヤーで予想外の何かが発生したことを示しています。UA またはデバイスバグに可能性がありそうです。NotSupportedError を返します。
    Invalid Attribute Value Length
    InvalidModificationError を返します。
    Attribute Not Long

    "Long" サブプロシジャを使用していないのにこのエラーコードを受信した場合は、デバイスのバグの可能性があります。このステップから NotSupportedError を返します。

    そうでない場合、"Long" サブプロシジャを使用しないでリトライします。書き込まれた値の長さからこれが不可能な場合、このステップから InvalidModificationError を返します。

    Insufficient Authentication
    Insufficient Encryption
    Insufficient Encryption Key Size
    UA は、接続のセキュリティレベルを上げる必要があります。この試みが失敗する、または UA がこれより高度なセキュリティを提供していない場合は、このステップから SecurityError を返します。そうでない場合、新しく高度なセキュリティレベルで本ステップをリトライします。
    Insufficient Authorization
    SecurityError を返します。
    Application Error
    GATT procedure が Write である場合、このステップから InvalidModificationError を返します。そうでない場合、このステップから NotSupportedError を返します。
    Read Not Permitted
    Write Not Permitted
    Request Not Supported
    Prepare Queue Full
    Insufficient Resources
    Unlikely Error
    Anything else
    NotSupportedError を返します。

6. UUIDs

typedef DOMString UUID;

UUID 文字列は128ビットの UUID [RFC4122] を表現します。有効な UUID は次の正規表現とマッチする文字列です。 the [ECMAScript] regexp /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ つまり、有効な UUID は小文字で、Bluetooth標準で定義されている16ビット/32ビットの短縮版は使用しません。本仕様書の関数と属性から返却されるUUID全ては、有効な UUID でなければなりません。 本仕様書の関数に、UUID または UUID 属性を含む辞書を型とする引数があり、UUID スロットに渡される引数が有効な UUID でない場合、関数は TypeError とともに promise を reject して返し、他のステップを中止しなければなりません。

本標準では、16ビットまたは32ビットの Bluetooth UUID エイリアス を128ビット形式にマップする BluetoothUUID.canonicalUUID(alias) 関数を提供しています。

Bluetoothデバイス は、比較前に(属性タイプに記載されたように)16ビットまたは32ビットの UUID を 128ビットの UUID に変換する必要がありますが、全てのデバイスがそうしているわけではありません。このようなデバイスと相互運用するため、UA が (16-, 32-, または 128-ビット)形式のデバイスから UUID を受信した場合、その UUID の他のエイリアスを同一形式でデバイスに送り返さなければなりません。

6.1. 標準化された UUID

Bluetooth SIG は、UUID の [BLUETOOTH-ASSIGNED] に services、characteristics、descriptors および他のエンティティを識別するレジストリーを保持しています。本セクションでは、スクリプトがこれらの UUID を名前で検索する方法を提供し、各アプリケーションで複製を作らなくても済むようにします。

interface BluetoothUUID {
  static UUID getService((DOMString or unsigned long) name);
  static UUID getCharacteristic((DOMString or unsigned long) name);
  static UUID getDescriptor((DOMString or unsigned long) name);

  static UUID canonicalUUID([EnforceRange] unsigned long alias);
};

typedef (DOMString or unsigned long) BluetoothServiceUUID;
typedef (DOMString or unsigned long) BluetoothCharacteristicUUID;
typedef (DOMString or unsigned long) BluetoothDescriptorUUID;

静的な BluetoothUUID.canonicalUUID(alias) メソッドは、呼び出されると、16ビットまたは32ビット UUID エイリアスで表現された128ビットのUUIDを返さなければなりません。

このアルゴリズムは"00000000-0000-1000-8000-00805f9b34fb"の上位32ビットをエイリアスのビットで置換するよう構成されています。例えば canonicalUUID(0xDEADBEEF) であれば "deadbeef-0000-1000-8000-00805f9b34fb" を返します。

BluetoothServiceUUID は16ビットまたは32ビット UUID エイリアス、つまり有効なUUID[BLUETOOTH-ASSIGNED-SERVICES]の中で定義された名前を表しています。言い換えれば BluetoothUUID.getService() で例外を発生させない値です。

BluetoothCharacteristicUUID は16ビットまたは32ビット UUID エイリアス、つまり有効な UUID [BLUETOOTH-ASSIGNED-CHARACTERISTICS] の中で定義された名前を表しています。言い換えれば BluetoothUUID.getCharacteristic() で例外を発生させない値です。

BluetoothDescriptorUUID は16ビットまたは32ビット UUID エイリアス、つまり有効な UUID[BLUETOOTH-ASSIGNED-DESCRIPTORS] の中で定義された名前を表しています。言い換えれば BluetoothUUID.getDescriptor() で例外を発生させない値です。

ResolveUUIDName(name, assigned numbers table, prefix) を実行するためには、UA は以下のステップを実行しなければなりません:

  1. nameunsigned long の場合、BluetoothUUID.canonicalUUID(name) を返し、以下のステップを中止します。
  2. name有効な UUID の場合、name を返し、以下のステップを中止します。
  3. prefix + "." + name指定された番号テーブル に現れた場合、エイリアスを指定された番号にして BluetoothUUID.canonicalUUID(alias) を返します。
  4. それ以外の場合は SyntaxError をスローします。

静的な BluetoothUUID.getService(name) メソッドは、呼び出されると、ResolveUUIDName(name, [BLUETOOTH-ASSIGNED-SERVICES], "org.bluetooth.service") を返さなければいけません。

静的な BluetoothUUID.getCharacteristic(name) メソッドは、呼び出されると、ResolveUUIDName(name, [BLUETOOTH-ASSIGNED-CHARACTERISTICS], "org.bluetooth.characteristic") を返さなければいけません。

静的な BluetoothUUID.getDescriptor(name) メソッドは、呼び出されると、ResolveUUIDName(name, [BLUETOOTH-ASSIGNED-DESCRIPTORS], "org.bluetooth.descriptor") を返さなければいけません。

BluetoothUUID.getService("cycling_power") returns "00001818-0000-1000-8000-00805f9b34fb".

BluetoothUUID.getService("00001801-0000-1000-8000-00805f9b34fb") returns "00001801-0000-1000-8000-00805f9b34fb".

BluetoothUUID.getService("unknown-service") throws a SyntaxError.

BluetoothUUID.getCharacteristic("ieee_11073-20601_regulatory_certification_data_list") returns "00002a2a-0000-1000-8000-00805f9b34fb".

BluetoothUUID.getDescriptor("gatt.characteristic_presentation_format") returns "00002904-0000-1000-8000-00805f9b34fb".

7. GATTブラックリスト

本仕様は、ウェブサイトがアクセス可能な GATT 属性 の集合を制限するために https://github.com/WebBluetoothCG/registries にあるブラックリストファイルに依存しています。

URLにあるブラックリストをパース した結果は、以下のアルゴリズムにより生成される、有効な UUID からトークンまたはエラーへのマップです:

  1. URLを取得し、そのコンテンツをボディとし、UTF-8 としてデコードします。
  2. lines'\n' で分割された内容とします。
  3. result を空のマップにします。
  4. lines の各 lineに対して、以下のサブステップを実行します:
    1. line が空行か、先頭文字が '#' の場合、次の行に進みます。
    2. line有効な UUID の場合、uuid をその UUID とし、token を "exclude" にします。
    3. line有効なUUID、スペース(U+0020)、そして "exclude-reads" または "exclude-writes" のいずれかのトークンからなる場合、uuid をその UUID とし、token をそのトークンにします。
    4. そうでない場合は、エラーを返し、以下のステップを中止します。
    5. uuid がすでに result の中にある場合、エラーを返し、以下のステップを中止します。
    6. uuid から token へのマッピングを result に追加します。
  5. result を返します。

GATT blacklisthttps://github.com/WebBluetoothCG/registries/blob/master/gatt_blacklist.txt にある ブラックリストをパース した結果です。 UA は定期的にブラックリストを再読み込みすべきですが、頻度は規定されていません。

GATT blacklist の値がエラーか、UUIDが GATT blacklist で "exclude" にマップされる場合、UUIDブラックリストに加えられます。#blacklistedReferenced in:3. Device Discovery (2)5.1.2. Navigating the Bluetooth Hierarchy

GATT blacklist の値がエラーか、UUIDが GATT blacklist で "exclude" または "exclude-reads" にマップされる場合、UUID読み出し用ブラックリストに加えられます。#blacklisted-for-readsReferenced in:5.4. BluetoothRemoteGATTCharacteristic (2)5.5. BluetoothRemoteGATTDescriptor

GATT blacklist の値がエラーか、UUIDが GATT blacklist で "exclude" または "exclude-writes" にマップされる場合、UUID書き込み用ブラックリストに加えられます。#blacklisted-for-writesReferenced in:5.4. BluetoothRemoteGATTCharacteristic5.5. BluetoothRemoteGATTDescriptor

partial interface Navigator {
  readonly attribute Bluetooth bluetooth;
};

9. 用語と規則

本仕様書は、規約と他の仕様書からの用語をいくつか使用します。本セクションでは、これらと主要な定義へのリンクをリストアップします。

Streams仕様書に刺激を受け、本仕様書では、オブジェクトの内部スロットを参照するため、「内部スロットxの [[y]]」という代わりに[[[ x@[[y]] ]]] という表記を使用します。

本仕様書で用いるアルゴリズムが、この仕様書または別の仕様書で定義される名前を用いる場合、その名前は初期値で解決され、現在の実行環境で名前に加えられたいかなる変更も無視しなければなりません。 例えば、requestDevice() アルゴリズムが、Array.prototype.map.call(filter.services, BluetoothUUID.getService) を呼び出す場合、パラメータとアルゴリズムは §6.1 Standardized UUIDs 内の BluetoothUUID.getServicecallbackfn パラメータとして定義された、filter.services を使って [ECMAScript] で定義された Array.prototype.map アルゴリズムに適用されなければなりません。これはwindow, Array, Array.prototype, Array.prototype.map, Function, Function.prototype, BluetoothUUID, BluetoothUUID.getService またはその他のオブジェクトに加えられた修正とは関係ありません。

本仕様書では、WebIDLの FrozenArray と同様の読み出し専用タイプを使用しています。

[BLUETOOTH42]
  1. Architecture & Terminology Overview
    1. General Description
      1. Overview of Bluetooth Low Energy Operation (defines advertising events)
    2. Communication Topology and Operation
      1. Operational Procedures and Modes
        1. BR/EDR Procedures
          1. Inquiry (Discovering) Procedure
            1. Extended Inquiry Response
  2. Core System Package [BR/EDR Controller volume]
    1. Host Controller Interface Functional Specification
      1. HCI Commands and Events
        1. Informational Parameters
          1. Read BD_ADDR Command
        2. Status Parameters
          1. Read RSSI Command
  3. Core System Package [Host volume]
    1. Service Discovery Protocol (SDP) Specification
      1. Overview
        1. Searching for Services
          1. UUID (defines UUID aliases and the algorithm to compute the 128-bit UUID represented by a UUID alias)
    2. Generic Access Profile
      1. Profile Overview
        1. Profile Roles
          1. Roles when Operating over an LE Physical Transport
            1. Broadcaster Role
            2. Observer Role
            3. Peripheral Role
            4. Central Role
      2. User Interface Aspects
        1. Representation of Bluetooth Parameters
          1. Bluetooth Device Name (the user-friendly name)
      3. Idle Mode Procedures — BR/EDR Physical Transport
        1. Device Discovery Procedure
      4. Operational Modes and Procedures — LE Physical Transport
        1. Broadcast Mode and Observation Procedure
          1. Observation Procedure
        2. Discovery Modes and Procedures
          1. General Discovery Procedure
          2. Name Discovery Procedure
        3. Connection Modes and Procedures
      5. Security Aspects — LE Physical Transport
        1. Privacy Feature
        2. Random Device Address
          1. Static Address
          2. Private address
            1. Resolvable Private Address Resolution Procedure
      6. Advertising Data and Scan Response Data Format (defines AD structure)
      7. Bluetooth Device Requirements
        1. Bluetooth Device Address (defines BD_ADDR)
          1. Bluetooth Device Address Types
            1. Public Bluetooth Address
    3. Attribute Protocol (ATT)
      1. Protocol Requirements
        1. Basic Concepts
          1. Attribute Type
          2. Attribute Handle
          3. Long Attribute Values
        2. Attribute Protocol Pdus
          1. Error Handling
            1. Error Response
    4. Generic Attribute Profile (GATT)
      1. Profile Overview
        1. Configurations and Roles (defines GATT Client and GATT Server)
        2. Profile Fundamentals, defines the ATT Bearer
        3. Attribute Protocol
          1. Attribute Caching
        4. GATT Profile Hierarchy
          1. Service
          2. Included Services
          3. Characteristic
      2. Service Interoperability Requirements
        1. Characteristic Definition
          1. Characteristic Declaration
            1. Characteristic Properties
          2. Characteristic Descriptor Declarations
            1. Characteristic Extended Properties
            2. Client Characteristic Configuration
      3. GATT Feature Requirements — defines the GATT procedures.
        1. Primary Service Discovery
          1. Discover All Primary Services
          2. Discover Primary Service by Service UUID
        2. Relationship Discovery
          1. Find Included Services
        3. Characteristic Discovery
          1. Discover All Characteristics of a Service
          2. Discover Characteristics by UUID
        4. Characteristic Descriptor Discovery
          1. Discover All Characteristic Descriptors
        5. Characteristic Value Read
        6. Characteristic Value Write
        7. Characteristic Value Notification
        8. Characteristic Value Indications
        9. Characteristic Descriptors
          1. Read Characteristic Descriptors
          2. Read Long Characteristic Descriptors
          3. Write Characteristic Descriptors
          4. Write Long Characteristic Descriptors
        10. Procedure Timeouts
      4. GAP Interoperability Requirements
        1. BR/EDR GAP Interoperability Requirements
          1. Connection Establishment
        2. LE GAP Interoperability Requirements
          1. Connection Establishment
      5. Defined Generic Attribute Profile Service
        1. Service Changed
    5. Security Manager Specification
      1. Security Manager
        1. Security in Bluetooth Low Energy
          1. Definition of Keys and Values, defines the Identity Resolving Key (IRK)
  4. Core System Package [Low Energy Controller volume]
    1. Link Layer Specification
      1. General Description
        1. Device Address
          1. Public Device Address
          2. Random Device Address
            1. Static Device Address
      2. Air Interface Protocol
        1. Non-Connected States
          1. Scanning State
            1. Passive Scanning
[BLUETOOTH-SUPPLEMENT6]
  1. Data Types Specification
    1. Data Types Definitions and Formats
      1. Service UUID Data Type
      2. Local Name Data Type
      3. Flags Data Type
      4. Manufacturer Specific Data
      5. TX Power Level
      6. Service Data
      7. Appearance

規格適合性

規格適合性の要件は、記述的な主張とRFC 2119 の用語の組み合わせで表明されています。本ドキュメントの標準部分にあるキーワード “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, そして “OPTIONAL” は、RFC 2119 に述べられているように解釈されるものです。しかしながら、読みやすさのために、本仕様書ではこれらのキーワード全てが大文字で現れるとは限りません。

本仕様書の本文全ては規定です。ただし、参考(non-normative)、例(examples)、注記(notes)と明示されたセクションを除きます。[RFC2119]

本仕様書の例は、「例えば(for example)」という表現で導入されるか、このように class="example" を使って標準的なテキストから分離されています。:

This is an example of an informative example.

参考注記は、「注記(Note)」という表現で導入されるか、このように class="note" を使って標準的なテキストから分離されています。:

Note, this is an informative note.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[BLUETOOTH-ASSIGNED]
Assigned Numbers. Living Standard. URL: https://www.bluetooth.org/en-us/specification/assigned-numbers
[BLUETOOTH-ASSIGNED-CHARACTERISTICS]
Bluetooth GATT Specifications > Characteristics. Living Standard. URL: https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicsHome.aspx
[BLUETOOTH-ASSIGNED-DESCRIPTORS]
Bluetooth GATT Specifications > Descriptors. Living Standard. URL: https://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorsHomePage.aspx
[BLUETOOTH-ASSIGNED-SERVICES]
Bluetooth GATT Specifications > Services. Living Standard. URL: https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx
[BLUETOOTH-SUPPLEMENT6]
Supplement to the Bluetooth Core Specification Version 6. 14 July 2015. URL: https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=302735
[BLUETOOTH42]
BLUETOOTH SPECIFICATION Version 4.2. 2 December 2014. URL: https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=286439
[ECMAScript]
ECMAScript Language Specification. URL: https://tc39.github.io/ecma262/
[HTML]
Ian Hickson. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[PERMISSIONS]
Mounir Lamouri; Marcos Caceres. The Permissions API. 7 April 2015. WD. URL: https://w3c.github.io/permissions/
[PROMISES-GUIDE]
Domenic Denicola. Writing Promise-Using Specifications. 16 February 2016. Finding of the W3C TAG. URL: https://www.w3.org/2001/tag/doc/promises-guide
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119
[RFC4122]
P. Leach; M. Mealling; R. Salz. A Universally Unique IDentifier (UUID) URN Namespace. July 2005. Proposed Standard. URL: https://tools.ietf.org/html/rfc4122
[SECURE-CONTEXTS]
Mike West. Secure Contexts. 19 July 2016. WD. URL: https://w3c.github.io/webappsec-secure-contexts/
[WebIDL-1]
Cameron McCormack; Boris Zbarsky. WebIDL Level 1. 8 March 2016. CR. URL: https://heycam.github.io/webidl/
[WHATWG-DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[WHATWG-ENCODING]
Anne van Kesteren. Encoding Standard. Living Standard. URL: https://encoding.spec.whatwg.org/

Informative References

[CSP3]
Mike West. Content Security Policy Level 3. 21 June 2016. WD. URL: https://w3c.github.io/webappsec-csp/

IDL Index

dictionary BluetoothRequestDeviceFilter {
  sequence<BluetoothServiceUUID> services;
  DOMString name;
  DOMString namePrefix;
};

dictionary RequestDeviceOptions {
  required sequence<BluetoothRequestDeviceFilter> filters;
  sequence<BluetoothServiceUUID> optionalServices = [];
};

interface Bluetooth {
  [SecureContext]
  readonly attribute BluetoothDevice? referringDevice;
  [SecureContext]
  Promise<BluetoothDevice> requestDevice(RequestDeviceOptions options);
};
Bluetooth implements EventTarget;
Bluetooth implements BluetoothDeviceEventHandlers;
Bluetooth implements CharacteristicEventHandlers;
Bluetooth implements ServiceEventHandlers;

dictionary BluetoothPermissionDescriptor : PermissionDescriptor {
  DOMString deviceId;
  // These match RequestDeviceOptions.
  sequence<BluetoothRequestDeviceFilter> filters;
  sequence<BluetoothServiceUUID> optionalServices = [];
};

dictionary AllowedBluetoothDevice {
  required DOMString deviceId;
  // An allowedServices of "all" means all services are allowed.
  required (DOMString or sequence<UUID>) allowedServices;
};
dictionary BluetoothPermissionData {
  required sequence<AllowedBluetoothDevice> allowedDevices = [];
};

interface BluetoothPermissionResult : PermissionStatus {
  attribute FrozenArray<BluetoothDevice> devices;
};

interface BluetoothDevice {
  readonly attribute DOMString id;
  readonly attribute DOMString? name;
  readonly attribute BluetoothRemoteGATTServer gatt;
  readonly attribute FrozenArray<UUID> uuids;

  Promise<void> watchAdvertisements();
  void unwatchAdvertisements();
  readonly attribute boolean watchingAdvertisements;
};
BluetoothDevice implements EventTarget;
BluetoothDevice implements BluetoothDeviceEventHandlers;
BluetoothDevice implements CharacteristicEventHandlers;
BluetoothDevice implements ServiceEventHandlers;

interface BluetoothManufacturerDataMap {
  readonly maplike<unsigned short, DataView>;
};
interface BluetoothServiceDataMap {
  readonly maplike<UUID, DataView>;
};
[Constructor(DOMString type, BluetoothAdvertisingEventInit init)]
interface BluetoothAdvertisingEvent : Event {
  readonly attribute BluetoothDevice device;
  readonly attribute FrozenArray<UUID> uuids;
  readonly attribute DOMString? name;
  readonly attribute unsigned short? appearance;
  readonly attribute byte? txPower;
  readonly attribute byte? rssi;
  readonly attribute BluetoothManufacturerDataMap manufacturerData;
  readonly attribute BluetoothServiceDataMap serviceData;
};
dictionary BluetoothAdvertisingEventInit : EventInit {
  required BluetoothDevice device;
  sequence<(DOMString or unsigned long)> uuids;
  DOMString name;
  unsigned short appearance;
  byte txPower;
  byte rssi;
  Map manufacturerData;
  Map serviceData;
};

interface BluetoothRemoteGATTServer {
  readonly attribute BluetoothDevice device;
  readonly attribute boolean connected;
  Promise<BluetoothRemoteGATTServer> connect();
  void disconnect();
  Promise<BluetoothRemoteGATTService> getPrimaryService(BluetoothServiceUUID service);
  Promise<sequence<BluetoothRemoteGATTService>>
    getPrimaryServices(optional BluetoothServiceUUID service);
};

interface BluetoothRemoteGATTService {
  readonly attribute BluetoothDevice device;
  readonly attribute UUID uuid;
  readonly attribute boolean isPrimary;
  Promise<BluetoothRemoteGATTCharacteristic>
    getCharacteristic(BluetoothCharacteristicUUID characteristic);
  Promise<sequence<BluetoothRemoteGATTCharacteristic>>
    getCharacteristics(optional BluetoothCharacteristicUUID characteristic);
  Promise<BluetoothRemoteGATTService>
    getIncludedService(BluetoothServiceUUID service);
  Promise<sequence<BluetoothRemoteGATTService>>
    getIncludedServices(optional BluetoothServiceUUID service);
};
BluetoothRemoteGATTService implements EventTarget;
BluetoothRemoteGATTService implements CharacteristicEventHandlers;
BluetoothRemoteGATTService implements ServiceEventHandlers;

interface BluetoothRemoteGATTCharacteristic {
  readonly attribute BluetoothRemoteGATTService service;
  readonly attribute UUID uuid;
  readonly attribute BluetoothCharacteristicProperties properties;
  readonly attribute DataView? value;
  Promise<BluetoothRemoteGATTDescriptor> getDescriptor(BluetoothDescriptorUUID descriptor);
  Promise<sequence<BluetoothRemoteGATTDescriptor>>
    getDescriptors(optional BluetoothDescriptorUUID descriptor);
  Promise<DataView> readValue();
  Promise<void> writeValue(BufferSource value);
  Promise<void> startNotifications();
  Promise<void> stopNotifications();
};
BluetoothRemoteGATTCharacteristic implements EventTarget;
BluetoothRemoteGATTCharacteristic implements CharacteristicEventHandlers;

interface BluetoothCharacteristicProperties {
  readonly attribute boolean broadcast;
  readonly attribute boolean read;
  readonly attribute boolean writeWithoutResponse;
  readonly attribute boolean write;
  readonly attribute boolean notify;
  readonly attribute boolean indicate;
  readonly attribute boolean authenticatedSignedWrites;
  readonly attribute boolean reliableWrite;
  readonly attribute boolean writableAuxiliaries;
};

interface BluetoothRemoteGATTDescriptor {
  readonly attribute BluetoothRemoteGATTCharacteristic characteristic;
  readonly attribute UUID uuid;
  readonly attribute DataView? value;
  Promise<DataView> readValue();
  Promise<void> writeValue(BufferSource value);
};

[NoInterfaceObject]
interface CharacteristicEventHandlers {
  attribute EventHandler oncharacteristicvaluechanged;
};

[NoInterfaceObject]
interface BluetoothDeviceEventHandlers {
  attribute EventHandler ongattserverdisconnected;
};

[NoInterfaceObject]
interface ServiceEventHandlers {
  attribute EventHandler onserviceadded;
  attribute EventHandler onservicechanged;
  attribute EventHandler onserviceremoved;
};

typedef DOMString UUID;
interface BluetoothUUID {
  static UUID getService((DOMString or unsigned long) name);
  static UUID getCharacteristic((DOMString or unsigned long) name);
  static UUID getDescriptor((DOMString or unsigned long) name);

  static UUID canonicalUUID([EnforceRange] unsigned long alias);
};

typedef (DOMString or unsigned long) BluetoothServiceUUID;
typedef (DOMString or unsigned long) BluetoothCharacteristicUUID;
typedef (DOMString or unsigned long) BluetoothDescriptorUUID;

partial interface Navigator {
  readonly attribute Bluetooth bluetooth;
};

Issues Index

TODO: Nail down the amount of time.
Both passive scanning and the Privacy Feature avoid leaking the unique, immutable device ID. We ought to require UAs to use either one, but none of the OS APIs appear to expose either. Bluetooth also makes it hard to use passive scanning since it doesn’t require Central devices to support the Observation Procedure.
All forms of BR/EDR inquiry/discovery appear to leak the unique, immutable device address.
We need a way for a site to register to receive an event when an interesting device comes within range.
Characteristic Extended Properties isn’t clear whether the extended properties are immutable for a given Characteristic. If they are, the UA should be allowed to cache them.