Web 蓝牙

社区组草案报告,

关于本文档的更多详情
本版本:
https://webbluetoothcg.github.io/web-bluetooth/
课题跟踪:
GitHub
规范内嵌问题
编辑:
(Google LLC)
(Google LLC)
在 GitHub 上查看贡献者
翻译 (非规范性)
日本語
参与方式:
加入 W3C 社区组
通过 GitHub 纠正文档
public-web-bluetooth@w3.org (邮件存档)
IRC: W3C 的 #web-bluetooth 频道

摘要

本文档描述了一个 API,可通过通用属性规范(GATT)基于 Bluetooth 4 无线标准,发现并与设备通信。

本文档状态

本规范由 Web 蓝牙社区组 发布。 它不是 W3C 标准,也不在 W3C 标准进程之中。 请注意,根据 W3C 社区贡献者许可协议(CLA) 的规定,有有限的选择退出权及其他条件。 了解更多关于 W3C 社区与业务组的信息。

可以在 https://github.com/WebBluetoothCG/web-bluetooth/commits/gh-pages 跟踪本文件的更新。

如果你希望对本文档提出意见,请发送邮件至 public-web-bluetooth@w3.org订阅邮件存档)。

1. 简介

本节为非规范性内容。

蓝牙是一种用于设备间短距离无线通信的标准。蓝牙“经典版”(BR/EDR) 定义了一套二进制协议,支持最高约24Mbps的速度。蓝牙4.0引入了一种新的“低功耗”模式,被称为“蓝牙智能”( BLE,或简称LE),其速率限制为约1Mbps,但可以让设备大部分时间关闭其发射器。BLE 主要通过 通用属性规范(GATT 以键/值对的方式提供大多数功能。

BLE 定义了多种设备角色。广播者观察者角色分别用于仅发射和仅接收的应用。 在从设备(Peripheral)角色下的设备可以被连接,而在 主设备(Central)角色下的设备可以连接到 从设备

作为从设备主设备角色的设备可以承载 GATT 服务器,其暴露一个 服务特征描述符的层级结构。更多关于该层级的信息见 § 6.1 GATT 信息模型。虽然该协议为BLE传输设计,GATT协议也可以运行在BR/EDR传输上。

本规范的第一个版本允许运行在UA中的网页以主设备角色,通过BR/EDR或LE连接, 连接到GATT 服务器。尽管本规范引用了 [BLUETOOTH42] 规范,它也兼容仅实现蓝牙4.0或4.1的设备间通信。

1.1. 示例

要发现和获取标准心率监测仪的数据,网站可以使用如下代码:
...(代码略,保持原样)...

parseHeartRate()会基于 heart_rate_measurement 文档 来读取存在于 DataView 类型的 BluetoothRemoteGATTCharacteristicvalue 字段的数据。

...(代码略,保持原样)...

onHeartRateChanged() 可能会打印如下对象:

{
  heartRate: 70,
  contactDetected: true,
  energyExpended: 750,     // 意味着750kJ。
  rrIntervals: [890, 870]  // 即.87s和.85s。
}

如果心率传感器报告了 energyExpended 字段,Web应用可以通过写入 heart_rate_control_point 特征,将其值重置为 0

...(代码略,保持原样)...

2. 安全性注意事项

参见 § 3 隐私注意事项 小节。 [Issue #575]

3. 隐私注意事项

3.1. 设备访问权限很强

当网站使用 requestDevice() 请求设备访问权限时,它将能访问调用中所有提及的 GATT 服务。 UA 在询问用户要将哪些设备授权给网站前,必须告知用户这些服务赋予了网页哪些能力。如果列表中有任何服务 UA 不识别,UA 必须假定网站可完全控制设备,并提醒用户相关风险。 UA 还应允许用户检查哪些网站能访问哪些设备,并撤销这些配对关系。

UA 不得允许用户将整个设备类别配对给一个网站。有可能构造出每台设备都提供相同标识信息的设备类别。UA 不要求检测这类伪造,可以允许用户将此类伪设备配对给网站。

为确保只有经用户批准的实体真正获得访问权限,本规范要求只有安全上下文可访问蓝牙设备。

3.2. 可信服务器有可能传递恶意代码

本节为非规范性内容。

即使用户信任某个源,该源的服务器或开发者也可能被攻破,网站还可能存在XSS漏洞。这两种情况都可能导致用户授权恶意代码访问宝贵设备。源站应定义内容安全策略([CSP3]),以降低XSS风险,但这对攻破服务器或开发者无效。

通过§ 4.1 权限API集成,允许页面重载后恢复已授权设备,这增加了风险。攻击者无需在网站被攻破时获授权,只要用户再次访问即可利用已授权设备。但另一方面,页面重载下保留设备访问权限,可以减少权限提示,使用户更可能关注每一次出现的提示。

3.3. 针对设备的攻击

本节为非规范性内容。

网站的通信可能会破坏部分设备的安全模型,这些设备假设只会收到可信远端设备操作系统发出的信息。典型如人机接口设备,允许网页通信等于允许网站窃取键盘输入。本规范通过GATT封锁列表限制此类易受攻击的服务、特征和描述符,防止网页滥用。

我们预计许多设备容易因无线信号收到异常数据而遭攻击。以往需一台台设备被攻破,而本API可能导致大规模攻击。本规范通过如下措施增加攻击难度:

UA 还可采取进一步措施保护用户:

3.4. 蓝牙设备标识符

本节为非规范性内容。

每个蓝牙BR/EDR设备有唯一的48位MAC地址,称为BD_ADDR。每个蓝牙LE设备至少有一个公用设备地址静态设备地址公用设备地址 是 MAC 地址。静态设备地址可在每次重启时刷新。BR/EDR/LE 设备的BD_ADDR公用设备地址 相同(见Read BD_ADDR 指令)。

LE设备还可能有唯一的128位身份解析密钥(IRK),会在配对过程中传给可信设备。为避免泄露持久标识,LE设备可以使用随机 可解析/不可解析私有地址 进行扫描与广播,而不是用静态或公用地址。这些私有地址会定期(大约每15分钟)刷新,但经过配对的设备可用存储的 IRK 检查任一可解析私有地址是否匹配,方法见可解析私有地址解析过程

每个蓝牙设备还有一个可读的人类友好蓝牙设备名。这不是唯一的,但根据设备类型有可能在实际中唯一。

3.4.1. 远程蓝牙设备的标识符

本节为非规范性内容。

一旦网页获取了持久设备ID,就能通过汇总附近设备目录大规模推断用户位置,还可分析某用户通过同一蓝牙设备配对多个网站的行为。另一方面,许多GATT服务可用于设备指纹,设备本身也能轻松暴露自定义GATT服务放大这一能力。

本规范建议 UA 对同一设备分别为用户未允许脚本发现是否为同一设备的情况生成不同ID,使网站难以滥用设备地址。设备制造商依旧可通过特殊方式追踪用户,但需花费额外精力。

3.4.2. UA 的蓝牙地址

本节为非规范性内容。

在BR/EDR模式,或LE模式下主动扫描且未启用隐私功能时,UA 会将其持久ID广播给附近所有蓝牙设备。这使得恶意设备可以分布到各区域追踪该UA。截至2014年8月,基本没有平台记录已实现 隐私功能,因此尽管本规范推荐,实际应用较少。规范还要求用户手势才可触发网站扫描,降低了扫描频率,但更多平台支持隐私功能会更理想。

3.5. 暴露蓝牙可用性

本节为非规范性内容。

navigator.bluetooth.getAvailability() 暴露了用户系统是否有蓝牙射频模块(无论其是否已通电)。如果用户配置了 UA 阻止网页蓝牙,则可用性也受影响。有些用户可能认为这属于隐私,但泄漏此信息可能带来的损害有限。该信息会略微提高UA的 指纹表面。该函数返回Promise, 因此UA可选择让用户指定返回值,但我们认为风险增量极小,UA 一般不会提示用户。

4. 设备发现

dictionary BluetoothDataFilterInit {
  BufferSource dataPrefix;
  BufferSource mask;
};

dictionary BluetoothManufacturerDataFilterInit : BluetoothDataFilterInit {
  required [EnforceRange] unsigned short companyIdentifier;
};

dictionary BluetoothServiceDataFilterInit : BluetoothDataFilterInit {
  required BluetoothServiceUUID service;
};

dictionary BluetoothLEScanFilterInit {
  sequence<BluetoothServiceUUID> services;
  DOMString name;
  DOMString namePrefix;
  sequence<BluetoothManufacturerDataFilterInit> manufacturerData;
  sequence<BluetoothServiceDataFilterInit> serviceData;
};

dictionary RequestDeviceOptions {
  sequence<BluetoothLEScanFilterInit> filters;
  sequence<BluetoothLEScanFilterInit> exclusionFilters;
  sequence<BluetoothServiceUUID> optionalServices = [];
  sequence<unsigned short> optionalManufacturerData = [];
  boolean acceptAllDevices = false;
};

[Exposed=Window, SecureContext]
interface Bluetooth : EventTarget {
  Promise<boolean> getAvailability();
  attribute EventHandler onavailabilitychanged;
  [SameObject]
  readonly attribute BluetoothDevice? referringDevice;
  Promise<sequence<BluetoothDevice>> getDevices();
  Promise<BluetoothDevice> requestDevice(optional RequestDeviceOptions options = {});
};

Bluetooth includes BluetoothDeviceEventHandlers;
Bluetooth includes CharacteristicEventHandlers;
Bluetooth includes ServiceEventHandlers;

本规范中定义的方法通常异步完成,会在Bluetooth 任务源上排队执行工作。

NOTE: Bluetooth 成员
说明:getAvailability() 会告知页面是否完全可用蓝牙功能。通过软件被禁用的适配器也应计为“可用”。可用性的变化(例如用户物理连接或拔下适配器)会通过 availabilitychanged 事件报告。

referringDevice 会提供用户打开此页面所用设备(若有)的访问。例如,Eddystone 信标可能广播一个 URL,UA 允许用户打开它。代表该信标的 BluetoothDevice 将可通过 navigator.bluetooth.referringDevice 获取。

getDevices() 使页面能够检索用户已授予访问权限的蓝牙设备。

requestDevice(options) 会请求用户授予此源对一个设备的访问权限,该设备需要匹配任一筛选器options.filters 中,但不能匹配任一筛选器options.exclusionFilters。 要匹配筛选器,设备需:

厂商特定数据服务数据都将键映射到字节数组。BluetoothDataFilterInit 对这些数组进行过滤。若存在一个prefix使得 prefix & mask 等于 dataPrefix & mask,则数组匹配。

注意:如果设备在连接时显著改变其行为,例如不再广播其识别性的厂商数据而是让客户端发现一些识别性的 GATT 服务,则网站可能需要同时包含两种行为的筛选器。

在少数情况下,设备可能不会广播足够的区分信息以让网站筛掉不感兴趣的设备。这种情况下,网站可以将 acceptAllDevices 设为 true 并省略 filtersexclusionFilters。 这将把选择正确设备的负担完全交给网站用户。如果网站使用了 acceptAllDevices, 则只能使用列在 optionalServices 中的服务。

在用户选择与此源配对的设备后,该源可以访问任一服务,其 UUID 出现在任意元素的 services 列表中,或出现在 options.filters 中,或出现在 options.optionalServices 中。 源还可以访问来自设备广播数据中由 options.optionalManufacturerData 定义的厂商代码的任一厂商数据。

这意味着如果开发者仅按名称筛选,则必须使用 optionalServices 才能访问任一服务。

假设 UA 附近有以下设备:
设备 已广播的服务
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。

如果网站随后调用

navigator.bluetooth.getDevices();

返回的 Promise 将解析为包含设备 D1 的数组,网站将能访问服务 A、B、C 和 D。

optionalServices 列表不会向用户所见对话框添加任何设备,但会影响用户所选设备上网站可用的服务。

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

显示包含 D1 和 D2 的对话框,但不包含 D4,因为 D4 不包含所需服务。若用户选择 D2,与第一个示例不同,网站将能访问服务 A、B 和 E。

如果网站再次调用

navigator.bluetooth.getDevices();

则返回的 Promise 会解析为包含设备 D1 和 D2 的数组。D1 上可访问 A、B、C、D 服务,而 D2 上可访问 A、B、E 服务。

若在用户授予访问后设备发生变化,允许的服务仍然适用。例如,如果用户在先前的 requestDevice() 调用中选择了 D1,而 D1 之后新增了 E 服务,将触发 serviceadded 事件,网页将能够访问服务 E。

假设前一个示例中的设备还广播了如下名称:
设备 广播的设备名
D1 First De…
D2 <none>
D3 Device Third
D4 Device Fourth
D5 Unique Name

下表展示了向 navigator.bluetooth.requestDevice({filters: filters}) 传入若干 filters 值时,用户可在其间进行选择的设备:

filters 设备 说明
[{name: "Unique Name"}]
D5
[{namePrefix: "Device"}]
D3, D4
[{name: "First De"},
  {name: "First Device"}]
<none> D1 仅广播了其名称的前缀,因此尝试匹配完整名称会失败。
[{namePrefix: "First"},
  {name: "Unique Name"}]
D1, D5
[{services: [C],
  namePrefix: "Device"},
  {name: "Unique Name"}]
D3, D5

下表展示了向 navigator.bluetooth.requestDevice({filters: filters, exclusionFilters: exclusionFilters}) 传入若干 filtersexclusionFilters 值时,用户可在其间进行选择的设备:

filters exclusionFilters 设备
[{namePrefix: "Device"}]  // D3, D4
[{name: "Device Third"}]   // D3
D4
[{namePrefix: "Device"}]  // D3, D4
[{namePrefix: "Device F"}] // D4
D3
[{services: [C]},         // D1, D3
  {namePrefix: "Device"}] // D3, D4
[{services: [A]},          // D1
  {name: "Device Fourth"}] // D4
D3
假设前一个示例中的设备还按如下方式广播厂商或服务数据:
设备 厂商数据 服务数据
D1 17: 01 02 03
D2 A: 01 02 03

下表展示了向 navigator.bluetooth.requestDevice({filters: filters}) 传入若干 filters 值时,用户可在其间进行选择的设备:

filters 设备
[{ manufacturerData: [{ companyIdentifier: 17 }] }]
D1
[{ serviceData: [{ service: "A" }] }]
D2
[
  { manufacturerData: [{ companyIdentifier: 17 }] },
  { serviceData: [{ service: "A" }] },
]
D1, D2
[
  {
    manufacturerData: [{ companyIdentifier: 17 }],
    serviceData: [{ service: "A" }],
  },
]
<none>
[
  {
    manufacturerData: [
      {
        companyIdentifier: 17,
        dataPrefix: new Uint8Array([1, 2, 3])
      },
    ],
  },
]
D1
[
  {
    manufacturerData: [
      {
        companyIdentifier: 17,
        dataPrefix: new Uint8Array([1, 2, 3, 4])
      },
    ],
  },
]
<none>
[
  {
    manufacturerData: [
      {
        companyIdentifier: 17,
        dataPrefix: new Uint8Array([1])
      },
    ],
  },
]
D1
[
  {
    manufacturerData: [
      {
        companyIdentifier: 17,
        dataPrefix: new Uint8Array([0x91, 0xAA]),
        mask: new Uint8Array([0x0f, 0x57]),
      },
    ],
  },
]
D1
[
  {
    manufacturerData: [
      { companyIdentifier: 17 },
      { companyIdentifier: 18 },
    ]
  }
]
<none>
接受或拒绝所有可能设备的筛选器会导致 TypeError。 若要接受所有设备,请改用 acceptAllDevices
调用 说明
requestDevice({})
无效:省略的筛选器列表不接受任何设备。
requestDevice({filters:[]})
无效:空的筛选器列表不接受任何设备。
requestDevice({filters:[{}]})
无效:空的筛选器会接受所有设备,因此也不允许。
requestDevice({
  acceptAllDevices:true
})
有效:通过 acceptAllDevices 明确接受所有设备。
requestDevice({
  filters: [...],
  acceptAllDevices:true
})
无效:acceptAllDevices 将覆盖任何 filters
requestDevice({
  exclusionFilters: [...],
  acceptAllDevices:true
})
无效:acceptAllDevices 将覆盖任何 exclusionFilters
requestDevice({
  exclusionFilters: [...]
})
无效:exclusionFilters 需要搭配 filters
requestDevice({
  filters: [...],
  exclusionFilters: []
})
无效:exclusionFilters 必须为非空才能排除设备。
requestDevice({
  filters: [{namePrefix: ""}]
})
无效:如果提供,namePrefix 必须为非空以筛选设备。
requestDevice({
  filters: [{manufacturerData: []}]
})
无效:若提供,manufacturerData 必须为非空才能筛选设备。
requestDevice({
  filters: [{serviceData: []}]
})
无效:若提供,serviceData 必须为非空才能筛选设备。

Bluetooth 实例会带有下表所述的 内部槽位创建:

内部槽位 初始值 描述(非规范性)
[[deviceInstanceMap]] 一个从蓝牙设备BluetoothDevice 实例的空映射。 确保在单个全局对象内,每个蓝牙设备仅由一个 BluetoothDevice 实例表示。
[[attributeInstanceMap]] 一个从蓝牙缓存条目到 Promise 的空映射。 这些 Promise 将解析为 BluetoothRemoteGATTServiceBluetoothRemoteGATTCharacteristicBluetoothRemoteGATTDescriptor 实例。
[[referringDevice]] null Document 是由该设备打开的,则在初始化 Document 对象时,设置为一个 BluetoothDevice
读取 navigator.bluetooth.referringDevice 必须返回 [[referringDevice]]
一些 UA 可能允许用户使某个浏览上下文在响应蓝牙设备导航
Note: 例如,若一个 Eddystone 信标广播一个 URL,UA 可能允许用户导航到该 URL。

如果发生这种情况,则作为初始化 Document 对象的一部分,UA 必须执行以下步骤:

  1. referringDevice 为导致该次导航的设备。

  2. 获取表示 BluetoothDevice 的对象 referringDevice,位于 navigator.bluetooth 内,并令 referringDeviceObj 为结果。

  3. 如果上一步抛出了异常,放弃这些步骤。

    Note: 这意味着 UA 并未推断用户意图授予当前领域访问 referringDevice 的权限。例如,用户可能已全局拒绝 GATT 访问。
  4. navigator.bluetooth.[[referringDevice]] 设为 referringDeviceObj

若一个蓝牙设备 device 依据以下步骤返回 match,则称其匹配某筛选器 filter
  1. 如果 filter.name 存在,则若 device蓝牙设备名不完整或不等于 filter.name,返回 mismatch

  2. 如果 filter.namePrefix 存在,则若 device蓝牙设备名不存在或不以 filter.namePrefix 开头,返回 mismatch

  3. 对于 filter.services 中的每个 uuid,若 UA 未接收到广告数据、扩展查询响应,或指示设备支持 UUID 为 uuid 的主(非包含)服务的服务发现响应,则返回 mismatch

  4. 对于 filter["manufacturerData"] 中的每个 manufacturerData,若 device 未广播厂商特定数据,其公司标识符等于 manufacturerData["companyIdentifier"],且数据匹配 manufacturerData,则返回 mismatch

  5. 对于 filter["serviceData"] 中的每个 serviceData,若 device 未广播服务数据,其 128 位形式的 UUID 等于 serviceData["service"], 且数据匹配 serviceData,则返回 mismatch

  6. 返回 match

若字节数组 data 依据以下步骤返回 match,则其匹配 一个 BluetoothDataFilterInit filter
Note: 该算法假设 filter 已经被规范化
  1. expectedPrefix由字节构成的拷贝,其来源为 filter.dataPrefix

  2. mask由字节构成的拷贝,其来源为 filter.mask

  3. 如果 data 的字节数少于 expectedPrefix,返回 mismatch

  4. 对于 mask 中的每个 1 位,若 data 中对应位不等于 expectedPrefix 中对应位,返回 mismatch

  5. 返回 match

BluetoothDataFilterInit filter1 依据以下步骤返回 true,则其为另一个 BluetoothDataFilterInit filter2严格子集
  1. 如果 filter1 的长度小于 filter2 的长度,返回 false

  2. byteIndex0

  3. byteIndex 小于 filter2 的长度时,执行以下子步骤:

    1. 如果 filter1.mask[byteIndex] & filter2.mask[byteIndex] 不等于 filter2.mask[byteIndex],返回 false

    2. 如果 filter1.dataPrefix[byteIndex] & filter2.mask[byteIndex] 不等于 filter2.dataPrefix[byteIndex] & filter2.mask[byteIndex],返回 false

    3. byteIndex 设为 byteIndex + 1

  4. 返回 true

设备广播的服务 UUID 列表可能不包含设备支持的所有 UUID。广告数据会指示该列表是否完整。如果网站按某个附近设备支持但未广播的 UUID 进行筛选,该设备可能不会出现在呈现给用户的设备列表中。UA 需要连接到设备以发现其完整支持的服务列表,这会影响射频性能并造成延迟,因此本规范不要求这样做。
当调用 getDevices() 方法并给定一个 BluetoothPermissionStorage storage 时,必须执行以下步骤:
  1. globalthis相关全局对象

  2. 如果 global关联的 Document未被允许使用名为 "bluetooth" 的策略控制特性,则返回SecurityError拒绝的 promise。

  3. promise一个新的 promise

  4. 并行执行以下步骤:

    1. devices 为新的空 Array

    2. 对于 storage.allowedDevices 中的每个 allowedDevice,将表示 allowedDevice@[[device]]BluetoothDevice 对象添加到 devices

    3. 针对给定的 globalBluetooth 任务源排队一个全局任务,用 devices 解析 promise

    Note: devices 中的 BluetoothDevice 可能不在蓝牙射频范围内。对于 devices 中的某个 device,可使用 watchAdvertisements() 方法在 device 处于范围内并广播广告包时进行观察。当在某个 device 上触发 advertisementreceived 事件 event 时,可能表示它距离足够近,可以通过调用 event.device.gatt.connect() 建立连接。
  5. 返回 promise

当调用 requestDevice(options) 方法时,必须执行以下步骤:
  1. 如果 options.exclusionFilters 存在而 options.filters 不存在,返回TypeError拒绝的 promise。

  2. 如果 options.filters 存在且 options.acceptAllDevicestrue,或 options.filters 不存在且 options.acceptAllDevicesfalse,则返回TypeError拒绝的 promise。

    Note: 这强制要求 filtersacceptAllDevices:true 必须且仅能有一个存在。
  3. promise一个新的 promise

  4. 并行执行以下步骤:

    1. 请求蓝牙设备,当 options.acceptAllDevicesfalse 时传入 options.filters;否则传入 null。若存在则传入 options.exclusionFilters,否则传入 null;同时传入 options.optionalServicesoptions.optionalManufacturerData,令结果为 devices

    2. Bluetooth 任务源上,针对this相关全局对象排队一个全局任务以运行以下步骤:

      1. 若上一步抛出异常,拒绝 promise 并终止这些步骤。

      2. 如果 devices 是空序列,拒绝 promise,错误为 NotFoundError, 并终止这些步骤。

      3. 解析 promise,值为 devices[0]

  5. 返回 promise

请求蓝牙设备时,给定 BluetoothPermissionStorage storage、一组可以为 null(表示所有设备可匹配)的 BluetoothLEScanFilterInit 序列 filters、一组可以为 null(表示未设置排除筛选器)的 BluetoothLEScanFilterInit 序列 exclusionFilters、一组 BluetoothServiceUUID 序列 optionalServices,以及一组 unsigned short 序列 optionalManufacturerData,UA 必须执行以下步骤:
Note: 这些步骤可能阻塞,因此对该算法的使用必须并行进行。
Note: 对该算法的调用未来将可以请求多个设备,但目前只返回单个设备。
  1. globalstorage相关全局对象

  2. documentglobal关联 Document

  3. 如果 document 未被允许使用名为 "bluetooth" 的策略控制特性,抛出 SecurityError 并终止这些步骤。

  4. 检查该算法是否在其相关全局对象具有瞬时激活期间触发;否则抛出 SecurityError 并终止这些步骤。

  5. 为将参数从服务名称与别名转换为仅UUID,执行以下子步骤:

    1. 如果 filters !== null && filters.length === 0,抛出 TypeError 并终止这些步骤。

    2. 如果 exclusionFilters !== null && exclusionFilters.length === 0,抛出 TypeError 并终止这些步骤。

    3. uuidFilters 为新的 ArrayuuidExclusionFilters 为新的 ArrayrequiredServiceUUIDs 为新的 Set

    4. 如果 filtersnull,则将 requiredServiceUUIDs 设为所有 UUID 的集合。

    5. 如果 filters 不为 null,则对于 filters 中的每个 filter,执行:

      1. canonicalFilter规范化 filter 的结果。

      2. canonicalFilter 追加到 uuidFilters

      3. canonicalFilter.services 中的内容添加到 requiredServiceUUIDs

    6. 如果 exclusionFilters 不为 null,则对于 exclusionFilter 属于 exclusionFilters 的每个元素,执行:

      1. canonicalExclusionFilter规范化 exclusionFilter 的结果。

      2. canonicalExclusionFilter 追加到 uuidExclusionFilters

    7. optionalServiceUUIDsArray.prototype.map.call(optionalServices, BluetoothUUID.getService)

    8. 如果任一 BluetoothUUID.getService() 调用抛出异常,抛出该异常并终止这些步骤。

    9. optionalServiceUUIDs 中移除任何在阻止列表中的 UUID。

  6. descriptor

    {
      name: "bluetooth",
      filters: uuidFilters
      optionalServices: optionalServiceUUIDs,
      optionalManufacturerData: optionalManufacturerData
      acceptAllDevices: filters !== null,
    }
    
  7. statedescriptor权限状态

    Note: 在非安全上下文中,state 将为 "denied",因为强能力不能在非安全上下文使用。
  8. 如果 state 为 "denied",返回 [] 并终止这些步骤。

  9. 如果 UA 能证明下一步不可能找到任何设备(例如没有用于扫描的蓝牙适配器,或筛选器不可能匹配任何广告包),UA 可以返回 [] 并终止这些步骤。

  10. scanResult 为以 globalrequiredServiceUUIDs 调用扫描设备的结果。

  11. 如果 filters 不为 null,则:

    1. scanResult 中移除未匹配 uuidFilters 中任一筛选器的设备。

    2. 如果 exclusionFilters 不为 null,从 scanResult 中移除 匹配 uuidExclusionFilters 中任一筛选器的设备。

  12. navigabledocumentnavigable

  13. promptId 为新的唯一不透明字符串。

实际上,设备列表会在提示框打开期间动态更新。规范文本目前尚未反映这一点,但该事件可能会带着相同的 promptId 和新的设备列表多次触发。参见 https://github.com/WebBluetoothCG/web-bluetooth/issues/621。

  1. 触发“提示已更新”事件,传入 navigablepromptIdscanResult

  2. 即使 scanResult 为空,也要提示用户进行选择, 从 scanResult 中选择一个设备,与 descriptor 关联,令结果为 device

    UA 可以允许用户选择一个不匹配 uuidFilters 的附近设备。

    Note: UA 应显示每台设备的人类可读名称。如果该名称不可用(例如 UA 的蓝牙系统不支持开启隐私的扫描),UA 应允许用户表示兴趣,然后执行不启用隐私的扫描以获取该名称。
  3. 移除可导航对象到设备提示的映射[navigablenavigable id]。

  4. UA 可以device 添加到 storage

    Note: 选择某个 device 可能表示用户意图让该设备出现在"bluetooth"额外权限数据allowedDevices 列表中(至少对当前设置对象),其 mayUseGATT 字段为 truerequiredServiceUUIDsoptionalServiceUUIDs 的并集中所有服务出现在其 allowedServices 列表中(以及任何已存在的服务);并且 optionalManufacturerData 中的厂商代码出现在其 allowedManufacturerData 列表中。
  5. 如果 device 为 "denied",返回 [] 并终止这些步骤。

  6. UA 可以选择 填充 Bluetooth 缓存,将 device 内的所有服务添加其中。本步骤中的任何错误均应被忽略。

  7. 获取表示 BluetoothDevice 的对象 devicethis 内,如有异常则向上传递,并令 deviceObj 为结果。

  8. 返回 [deviceObj]

规范化 BluetoothLEScanFilterInit filter 的结果,是按以下步骤返回的 BluetoothLEScanFilterInit
  1. 如果 filter 的所有成员均不存在,抛出 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. 如果 services 中的任一服务在阻止列表中,抛出 SecurityError 并终止这些步骤。

    5. canonicalizedFilter.services 设为 services

  4. 如果 filter.name 存在,执行以下子步骤。

    1. 如果 filter.nameUTF-8 编码超过 248 字节,抛出 TypeError 并终止这些步骤。

      Note: 248 是蓝牙设备名中 UTF-8 码元的最大数量。
    2. canonicalizedFilter.name 设为 filter.name

  5. 如果 filter.namePrefix 存在,执行以下子步骤。

    1. 如果 filter.namePrefix.length === 0,或其UTF-8 编码超过 248 字节,抛出 TypeError 并终止这些步骤。

      Note: 248 是蓝牙设备名中 UTF-8 码元的最大数量。
    2. canonicalizedFilter.namePrefix 设为 filter.namePrefix

  6. canonicalizedFilter["manufacturerData"] 设为 []

  7. 如果 filter["manufacturerData"] 存在且 filter["manufacturerData"].length === 0,抛出 TypeError 并终止这些步骤。

  8. 对于 filter["manufacturerData"] 中的每个 manufacturerData,执行以下子步骤:

    1. 如果 manufacturerData被阻止的厂商数据筛选器,抛出 SecurityError 并终止这些步骤。

    2. 如果在 canonicalizedFilter["manufacturerData"] 中存在对象 existing,且 existing["companyIdentifier"] === manufacturerData["companyIdentifier"],抛出 TypeError 并终止这些步骤。

    3. canonicalizedManufacturerDataFilter规范化 manufacturerData 的结果,并转换为 ECMAScript 值。若抛出异常,传播该异常并终止这些步骤。

    4. canonicalizedManufacturerDataFilter["companyIdentifier"] 设为 manufacturerData["companyIdentifier"]

    5. canonicalizedManufacturerDataFilter 追加到 canonicalizedFilter["manufacturerData"]

  9. canonicalizedFilter.serviceData 设为 []

  10. 如果 filter["serviceData"] 存在且 filter["serviceData"].length === 0,抛出 TypeError 并终止这些步骤。

  11. 对于 filter["serviceData"] 中的每个 serviceData,执行以下子步骤:

    1. serviceBluetoothUUID.getService(serviceData["service"])。 若抛出异常,传播该异常并终止这些步骤。

    2. 如果 service在阻止列表中,抛出 SecurityError 并终止这些步骤。

    3. canonicalizedServiceDataFilter规范化 serviceData 的结果,并转换为 ECMAScript 值。若抛出异常,传播该异常并终止这些步骤。

    4. canonicalizedServiceDataFilter["service"] 设为 service

    5. canonicalizedServiceDataFilter 追加到 canonicalizedFilter["serviceData"]

  12. 返回 canonicalizedFilter

规范化 BluetoothDataFilterInit filter 的结果,是按以下步骤返回的 BluetoothDataFilterInit
  1. 如果 filter.dataPrefix 不存在,则令 dataPrefix 为空字节序列。否则,执行以下子步骤:

    1. dataPrefix由字节构成的拷贝,其来源为 filter.dataPrefix

    2. 如果 dataPrefix 的长度为 0,抛出 TypeError 并终止这些步骤。

  2. 如果 filter.mask 存在,则令 mask由字节构成的拷贝,其来源为 filter.mask。否则,令 mask 为与 dataPrefix 相同长度的 0xFF 字节序列。

  3. 如果 mask 的长度与 dataPrefix 不同,抛出 TypeError 并终止这些步骤。

  4. 返回 {dataPrefix: new Uint8Array(|dataPrefix|), mask: new Uint8Array(|mask|)}

要以参数 global 和可选的 服务 UUID 集合(默认是所有 UUID 的集合)扫描设备,UA 必须执行以下步骤:
  1. 如果 UA 最近使用一个 UUID 集合扫描过设备,且该集合是当前扫描所用 UUID 集合的超集,则 UA 可以返回那次扫描的结果并中止这些步骤。

    TODO:确定具体的时间长度。

  2. nearbyDevices 为一组蓝牙设备,初始等于与 UA 已连接(具有 ATT 承载体)的设备集合。

  3. topLevelTraversableglobalnavigable顶层可遍历对象

  4. simulatedBluetoothDevices 为一个空列表

  5. 如果 topLevelTraversable 具有模拟蓝牙适配器,则令 simulatedBluetoothDevices 为其获取映射值后的 模拟蓝牙设备映射的结果。

    支持异步设备发现。

  6. 如果 UA 支持 LE 传输,执行常规发现流程(General Discovery Procedure),但 UA 可以包含未设置可发现模式(Discoverable Mode)标志的设备,并将发现的蓝牙设备加入 nearbyDevices。UA 应启用隐私功能(Privacy Feature)

    被动扫描(passive scanning)隐私功能都可避免泄露唯一且不可变的设备 ID。我们本应要求 UA 使用其中之一,但没有任何操作系统 API 似乎暴露了这两者。并且由于蓝牙不要求中心设备(Central)支持观察流程(Observation Procedure),因此也很难使用被动扫描

  7. 如果 UA 支持 BR/EDR 传输,执行设备发现流程(Device Discovery Procedure)并将发现的蓝牙设备加入 nearbyDevices

    所有形式的 BR/EDR 查询/发现似乎都会泄露唯一且不可变的设备地址。

  8. result 为一组蓝牙设备,初始为空。

  9. nearbyDevicessimulatedBluetoothDevices 中的每个蓝牙设备 device,执行以下子步骤:

    1. 如果 device支持的物理传输包含 LE,且其蓝牙设备名是不完整的或缺失,UA 应执行名称发现流程(Name Discovery Procedure)以获取完整名称。

    2. 如果 device 广播的服务 UUID服务 UUID 集合有非空交集,则将 device 加入 result 并中止这些子步骤。

      注意:对于 BR/EDR 设备,无法在扩展查询响应(Extended Inquiry Response)中区分 GATT 与非 GATT 服务。如果站点按某个非 GATT 服务的 UUID 进行过滤,用户可能会在 requestDevice 的结果中选择一个本 API 无法与之交互的设备。
    3. UA 可以连接到 device填充蓝牙缓存,包含所有 UUID 位于服务 UUID 集合中的服务。如果 device支持的物理传输包含 BR/EDR,则除了标准 GATT 流程外,UA 也可以在填充缓存时使用服务发现协议(Searching for Services)。

      注意:连接所有附近设备以发现服务会消耗电量并可能降低蓝牙射频的其它用途。UA 仅应在有理由认为设备值得关注时才对其进行额外服务发现。

      UA 也应帮助开发者避免依赖这种额外的发现行为。例如,假设开发者之前已连接过某设备,因此 UA 知道该设备完整的支持服务集合。若该开发者随后使用未广播的 UUID 进行过滤,即便在其他用户的机器上该过滤器很可能会将该设备排除,对话框中仍可能包含该设备。UA 可以提供一个开发者选项,在发生这种情况时进行警告,或在匹配过滤器时仅包含已广播的服务。

    4. 如果蓝牙缓存中包含 device 内已知存在、且其 UUID 位于服务 UUID 集合中的服务,UA 可以将 device 加入 result

  10. 从扫描中返回 result

我们需要一种方式让站点在有趣的设备进入范围时注册接收事件。

要将获准的 蓝牙 设备 device 添加到 BluetoothPermissionStorage storage,在给定一组 requiredServiceUUIDs 与一组 optionalServiceUUIDs 的情况下,UA 必须执行以下步骤:
  1. grantedServiceUUIDs 为新的 Set

  2. requiredServiceUUIDs 的内容添加到 grantedServiceUUIDs

  3. optionalServiceUUIDs 的内容添加到 grantedServiceUUIDs

  4. storage.allowedDevices 中查找元素 allowedDevice,其满足 device 等于 allowedDevice@[[device]]。 如果找到,执行以下子步骤:

    1. allowedDevice.allowedServices 的内容添加到 grantedServiceUUIDs

    如果未找到,执行以下子步骤:

    1. allowedDevice.deviceId 为一个唯一 ID,其唯一性取决于 UA 能在何种程度上判定两次蓝牙连接来自同一设备,以及 用户希望在何种程度上将这一事实暴露给脚本

  5. allowedDevice.allowedServices 设为 grantedServiceUUIDs

  6. allowedDevice.mayUseGATT 设为 true

要从存储中移除一个已获准的 蓝牙 设备 device,给定 BluetoothPermissionStorage storage,UA 必须执行以下步骤:

  1. storage.allowedDevices 中查找元素 allowedDevice,其满足 device 等于 allowedDevice@[[device]]。 若不存在此类元素,中止这些步骤。

  2. storage.allowedDevices 中移除 allowedDevice

4.1. 权限 API 集成

[permissions] API 为网站提供了统一的方式来查询其拥有哪些权限。

一旦站点被授予访问一组设备的权限,它可以使用 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(...);

Web Bluetooth API 是一个由 强能力(powerful feature)标识的功能,其 name"bluetooth"。其与权限相关的算法与类型定义如下:

权限描述符类型
dictionary BluetoothPermissionDescriptor : PermissionDescriptor {
  DOMString deviceId;
  // 这些与 RequestDeviceOptions 对应。
  sequence<BluetoothLEScanFilterInit> filters;
  sequence<BluetoothServiceUUID> optionalServices = [];
  sequence<unsigned short> optionalManufacturerData = [];
  boolean acceptAllDevices = false;
};
额外权限数据类型
BluetoothPermissionStorage, 定义如下:
dictionary AllowedBluetoothDevice {
  required DOMString deviceId;
  required boolean mayUseGATT;
  // allowedServices 为 "all" 表示允许所有服务。
  required (DOMString or sequence<UUID>) allowedServices;
  required sequence<unsigned short> allowedManufacturerData;
};
dictionary BluetoothPermissionStorage {
  required sequence<AllowedBluetoothDevice> allowedDevices;
};

AllowedBluetoothDevice 实例具有一个 内部槽位 [[device]],其保存一个 蓝牙设备

额外权限数据约束
allowedDevices 的各元素必须具有不同的 [[device]] 和不同的 deviceId

如果 mayUseGATTfalseallowedServicesallowedManufacturerData 都必须为 []

注意:deviceId 允许站点跟踪某一时刻看到的 BluetoothDevice 实例与另一个时刻(可能在不同的 realm)看到的 BluetoothDevice 实例是否代表同一设备。当返回 "bluetooth"额外权限数据时,UA 应考虑用户是否意图发生这种跟踪。

例如,用户通常并不希望两个不同的源知道他们正在与同一设备交互,也通常不希望在清除某个源的 cookie 后,唯一标识符仍然保留。

权限结果类型
[Exposed=Window]
interface BluetoothPermissionResult : PermissionStatus {
  attribute FrozenArray<BluetoothDevice> devices;
};
权限查询算法
要用 BluetoothPermissionDescriptor descBluetoothPermissionResult status查询 "bluetooth" 权限,UA 必须:
  1. globalstatus相关全局对象

  2. status.state 设为 desc权限状态

  3. 如果 status.state 为 "denied", 则将 status.devices 设为一个空的 FrozenArray 并终止这些步骤。

  4. matchingDevices 为一个新的 Array

  5. storage(一个 BluetoothPermissionStorage)为 "bluetooth" 针对当前设置对象额外权限数据

  6. 对于 storage.allowedDevices 中的每个 allowedDevice,执行以下子步骤:

    1. 如果 desc.deviceId 已设置且 allowedDevice.deviceId != desc.deviceId,继续下一个 allowedDevice

    2. 如果 desc.filters 已设置,执行以下子步骤:

      1. desc.filters 中的每个筛选器替换为对其进行规范化的结果。若任何规范化抛出错误,返回该错误并终止这些步骤。

      2. 如果 allowedDevice.[[device]]匹配任一筛选器desc.filters,则继续下一个 allowedDevice

    3. 获取表示 allowedDevice.[[device]]BluetoothDevice,位于 global.navigator.bluetooth 内,并将结果添加到 matchingDevices

    注意:desc.optionalServicesdesc.optionalManufacturerData 字段不影响结果。
  7. status.devices 设为一个新的 FrozenArray, 其内容为 matchingDevices

权限撤销算法
要对用户不再意图暴露的设备撤销蓝牙访问,UA 必须执行以下步骤:
  1. storage(一个 BluetoothPermissionStorage)为 "bluetooth" 针对当前设置对象额外权限数据

  2. 对于当前realm 中的每个 BluetoothDevice 实例 deviceObj,执行以下子步骤:

    1. 若在 storage.allowedDevices 中存在 AllowedBluetoothDevice allowedDevice,满足:

      则将 deviceObj.[[allowedServices]] 更新为 allowedDevice.allowedServices, 并继续下一个 deviceObj

    2. 否则,通过运行以下剩余步骤,将 deviceObj 与其设备分离。

    3. 调用 deviceObj.gatt.disconnect()

      注意:这会在 deviceObj 上触发 gattserverdisconnected 事件。
    4. deviceObj.[[representedDevice]] 设为 null

4.2. 整体蓝牙可用性

UA 可能运行在没有蓝牙射频的计算机上。 requestDevice() 在这种情况下将无法发现任何设备,从而导致 NotFoundError, 不过网站可以更优雅地处理这种情况。

仅向具有蓝牙适配器的用户显示蓝牙 UI:
const bluetoothUI = document.querySelector('#bluetoothUI');
navigator.bluetooth.getAvailability().then(isAvailable => {
  bluetoothUI.hidden = !isAvailable;
});
navigator.bluetooth.addEventListener('availabilitychanged', e => {
  bluetoothUI.hidden = !e.value;
});
当调用 getAvailability() 方法时,必须返回一个新的 promise promise,并并行运行以下步骤:
  1. globalthis相关全局对象

  2. 如果 global关联的 Document未被允许使用名为 "bluetooth" 的策略控制特性,则针对给定的 globalBluetooth 任务源上排队一个全局任务解析 promisefalse,并终止这些步骤。

  3. 如果用户已将 UA 配置为对当前来源从该函数返回特定答案,则排队一个任务解析 promise 为该配置的答案,并终止这些步骤。

    注意:如果 Web Bluetooth 权限已被用户阻止,UA 可以 promise 解析为 false
  4. simulatedBluetoothAdapterthisnavigable顶层可遍历对象模拟蓝牙适配器

  5. 如果 simulatedBluetoothAdapter 非空,

    1. 如果 simulatedBluetoothAdapter适配器状态为 "absent",则针对给定的 globalBluetooth 任务源上排队一个全局任务解析 promisefalse

    2. 如果 simulatedBluetoothAdapterLE 支持状态false,则针对给定的 globalBluetooth 任务源上排队一个全局任务解析 promisefalse

    3. 否则,针对给定的 globalBluetooth 任务源上排队一个全局任务解析 promisetrue

    4. 终止这些步骤。

  6. 如果 UA 运行在具有蓝牙射频的系统上,则针对给定的 globalBluetooth 任务源上排队一个全局任务解析 promisetrue,无论蓝牙射频的供电状态如何。

  7. 否则,针对给定的 globalBluetooth 任务源上排队一个全局任务解析 promisefalse

    注意:该 promise 被并行解析,以便 UA 调用其他系统来确定蓝牙是否可用。
如果用户已阻止该权限,且 UA 将 getAvailability 的 promise 解析为 false,则可以使用以下方法检测蓝牙何时再次可用以显示蓝牙 UI:
function checkAvailability() {
  const bluetoothUI = document.querySelector('#bluetoothUI');
  navigator.bluetooth.getAvailability().then(isAvailable => {
    bluetoothUI.hidden = !isAvailable;
  });
}

navigator.permissions.query({name: "bluetooth"}).then(status => {
  if (status.state !== 'denied') checkAvailability();

  // 蓝牙被阻止,监听 PermissionStatus 的变化。
  status.onchange = () => {
    if (this.state !== 'denied') checkAvailability();
  };
});
如果 UA 变得能够或不能使用蓝牙(例如物理连接或拔除了射频,或用户更改了 针对 getAvailability() 返回答案的配置), 则 UA 必须针对每个全局对象 globalBluetooth 任务源上排队一个全局任务以运行以下步骤:
  1. oldAvailability 为变更前 getAvailability() 将会返回的值。

  2. newAvailability 为变更后 getAvailability() 将会返回的值。

  3. 如果 oldAvailabilitynewAvailability 不同,

    1. navigatorglobal关联 Navigator

    2. bluetoothnavigator关联 Bluetooth

    3. 触发一个事件,名称为 availabilitychanged, 使用 ValueEvent 接口,在 bluetooth 上触发,并将其 value 属性初始化为 newAvailability

[
  Exposed=Window,
  SecureContext
]
interface ValueEvent : Event {
  constructor(DOMString type, optional ValueEventInit initDict = {});
  readonly attribute any value;
};

dictionary ValueEventInit : EventInit {
  any value = null;
};

ValueEvent 实例的构造遵循 DOM § 2.5 事件的构造

value 属性必须返回其被初始化时的值。

此类通用事件类型应属于 [HTML][DOM],而非本规范。

5. 设备表示

UA 需要在多个层级跟踪蓝牙设备属性:全局、按源(origin),以及按全局对象

5.1. 全局蓝牙设备属性

物理蓝牙设备可能保证具有某些 UA 尚未接收的属性。此处将这些属性描述为可选。

蓝牙设备具有以下属性。可选 属性在未另行说明之前不存在,序列与映射属性为空。其他属性具有指定的默认值,或在设备被引入时指定。

UA 应当仅当且仅当两个蓝牙设备具有相同的公共蓝牙地址静态地址私有地址身份解析密钥,或使用一方的 IRK 与另一方的可解析私有地址成功执行可解析私有地址解析过程时,认定它们是同一蓝牙设备。然而,由于平台 API 并未文档化其判定设备身份的方法,UA 可以采用其他过程。

5.2. BluetoothDevice

BluetoothDevice 实例表示某个特定蓝牙设备,对应某个特定的全局对象(或等价地,某个特定的RealmBluetooth 实例)。

[Exposed=Window, SecureContext]
interface BluetoothDevice : EventTarget {
  readonly attribute DOMString id;
  readonly attribute DOMString? name;
  readonly attribute BluetoothRemoteGATTServer? gatt;

  Promise<undefined> forget();
  Promise<undefined> watchAdvertisements(
      optional WatchAdvertisementsOptions options = {});
  readonly attribute boolean watchingAdvertisements;
};
BluetoothDevice includes BluetoothDeviceEventHandlers;
BluetoothDevice includes CharacteristicEventHandlers;
BluetoothDevice includes ServiceEventHandlers;

dictionary WatchAdvertisementsOptions {
  AbortSignal signal;
};
说明:BluetoothDevice 属性
id 在 UA 能够判定两次蓝牙连接指向同一设备且用户 希望将这一事实暴露给脚本的范围内,对设备进行唯一标识。

name 是设备的人类可读名称。

gatt 在站点有权限的前提下,提供与该设备的 GATT 服务器交互的方式。

forget() 使页面能够撤销用户已授予的该设备访问权限。

watchingAdvertisements 为 true 表示 UA 当前正在扫描来自该设备的广告并为其触发事件。

BluetoothDevice 实例在创建时携带如下内部槽位(见下表):

内部槽位 初始值 描述(非规范性)
[[context]] <总在正文中设定> 返回此 Bluetooth 对象的 BluetoothDevice
[[representedDevice]] <总在正文中设定> 此对象所表示的蓝牙设备; 若访问已被撤销,则为 null
[[gatt]] 一个新的 BluetoothRemoteGATTServer 实例,其 device 属性被初始化为 this,其 connected 属性被初始化为 false 不会改变。
[[allowedServices]] <总在正文中设定> 此设备在该源下的 allowedServices 列表;若允许所有服务,则为 "all"。例如,UA 可以为某源授予其在该源上通过 referringDevice 广告的 URL 上的所有服务访问权限。
[[allowedManufacturerData]] <总在正文中设定> 此设备在该源下的 allowedManufacturerData 列表。
[[watchAdvertisementsState]] 'not-watching' 一个字符串枚举,描述一次 watchAdvertisements() 操作的当前状态。可能的枚举值包括:
  • 'not-watching'

  • 'pending-watch'

  • 'watching'

获取表示该 BluetoothDevice 的对象:给定某个蓝牙设备 device,以及 Bluetooth 实例 context,UA 必须执行以下步骤:
  1. storage(一个 BluetoothPermissionStorage)为 "bluetooth" 针对额外权限数据当前设置对象

  2. storage.allowedDevices 中查找其 allowedDevice.[[device]]device同一设备allowedDevice。若没有此对象,则抛出 SecurityError 并中止这些步骤。

  3. 若在 context.[[deviceInstanceMap]] 中不存在与 device同一设备的键,则执行以下子步骤:

    1. resultBluetoothDevice 的新实例。

    2. result 的所有可选字段初始化为 null

    3. result.[[context]] 初始化为 context

    4. result.[[representedDevice]] 初始化为 device

    5. result.id 初始化为 allowedDevice.deviceId, 并将 result.[[allowedServices]] 初始化为 allowedDevice.allowedServices

    6. device 具有部分或完整蓝牙设备名,则将 result.name 设为该字符串。

    7. result.watchingAdvertisements 初始化为 false

    8. context.[[deviceInstanceMap]] 中添加从 deviceresult 的映射。

  4. 返回 context.[[deviceInstanceMap]] 中以与 device同一设备为键的值。

获取 gatt 属性必须执行以下步骤:
  1. 如果针对 this"bluetooth"额外权限数据(对应其相关设置对象)的 AllowedBluetoothDevice 列表 allowedDevices 中存在 allowedDevice,满足 allowedDevice.[[device]]this.[[representedDevice]]同一设备,且 allowedDevice.mayUseGATT 等于 true,则返回 this.[[gatt]]

  2. 否则,返回 null

当调用 forget() 方法时,必须返回一个新的 promise promise,并执行以下步骤:
  1. device 为目标 BluetoothDevice 对象。

  2. storage 为当前脚本执行环境中的 BluetoothPermissionStorage 对象。

  3. 使用 storage从存储中移除 device

  4. 解析 promise

用户代理具有关联的广告监视管理器,其为启动一个新的并行队列的结果。

当调用 watchAdvertisements(options) 方法时,必须返回一个新的 promise promise,并执行以下步骤:
  1. global相关全局对象,对应 this

  2. 如果 options.signal 存在,则执行以下子步骤:

    1. 如果 options.signal中止,则以 this为对象中止 watchAdvertisements 并中止这些步骤。

    2. options.signal 添加以下中止步骤

      1. this为对象中止 watchAdvertisements

      2. 拒绝 promise,错误为 AbortError

  3. this.[[watchAdvertisementsState]] 为:

    'not-watching'
    1. this.[[watchAdvertisementsState]] 设为 'pending-watch'

    2. 将以下步骤入队广告监视管理器,但当 this.[[watchAdvertisementsState]] 变为 not-watching 时中止:

      1. 确保 UA 正在扫描该设备的广告。UA 不应筛除同一设备的“重复”广告。

      2. 若 UA 启用扫描失败,则在给定 globalBluetooth 任务源上排队一个全局任务以执行以下步骤,并中止这些步骤:

        1. this.[[watchAdvertisementsState]] 设为 'not-watching'

        2. 拒绝 promise,错误为下列之一:

          UA 不支持扫描广告

          NotSupportedError

          蓝牙已关闭

          InvalidStateError

          其他原因

          UnknownError

      3. 在给定 globalBluetooth 任务源上排队一个全局任务以执行以下步骤,但当 满足中止条件(即 this.[[watchAdvertisementsState]] 变为 not-watching)时中止:

        1. this.[[watchAdvertisementsState]] 设为 watching

        2. this.watchingAdvertisements 设为 true

        3. 解析 promise,值为 undefined

    'pending-watch'
    1. 拒绝 promise,错误为 InvalidStateError

    'watching'
    1. 解析 promise,值为 undefined

  4. 若已中止拒绝 promise,错误为 AbortError

Note: 扫描会消耗电量,因此网站应避免不必要地监视广告,并应使用其 AbortController 尽快停止耗电。

要为某个 中止 watchAdvertisementsBluetoothDevice device,执行以下步骤:
  1. this.[[watchAdvertisementsState]] 设为 'not-watching'

  2. device.watchingAdvertisements 设为 false

  3. 将以下步骤入队广告监视管理器

    1. 如果整个 UA 中不再有任何 BluetoothDevicewatchingAdvertisementstrue,UA 应停止扫描广告。否则,若不再有任何与 this 表示同一设备的 BluetoothDevicewatchingAdvertisementstrue,UA 应重新配置扫描以避免接收该设备的报告。

要在给定 Bluetooth bluetooth 的情况下中止所有活动的 watchAdvertisements 操作,执行以下步骤:
  1. 对每个位于 bluetooth.[[deviceInstanceMap]] 中的 device 执行以下步骤:

    1. 如果 device.[[watchAdvertisementsState]]pending-watchwatching,则以 device为对象运行中止 watchAdvertisements

5.2.1. 处理可见性变化

启动蓝牙设备扫描的操作只能在可见的document 中运行。当visibility state不再为 "visible"时,需要中止该document的扫描操作。

本规范在给定 visibilityStatedocument 的情况下,定义以下页面可见性变化步骤
  1. globaldocument相关全局对象

  2. 在给定 globalBluetooth 任务源上排队一个全局任务以执行以下步骤:

    1. navigatorglobal关联 Navigator

    2. bluetoothnavigator关联 Bluetooth

    3. 如果 visibilityState 不为 "visible",则在 bluetooth中止所有活动的 watchAdvertisements 操作。

5.2.2. 处理文档失去完全活动状态

启动蓝牙设备扫描的操作只能在完全活动document中运行。当完全活动状态丢失时,需要中止该document的扫描操作。

当用户代理确定某个 Document 不再完全活动时,必须运行以下步骤:
  1. document 为该不再完全活动Document

  2. globaldocument相关全局对象

  3. 在给定 globalBluetooth 任务源上排队一个全局任务以执行以下步骤:

    1. navigatorglobal关联 Navigator

    2. bluetoothnavigator关联 Bluetooth

    3. bluetooth 上运行中止所有活动的 watchAdvertisements 操作。

5.2.3. 响应广播事件

当某个BluetoothDevice设置了 watchingAdvertisements 时收到advertising event,UA 将投递一个 "advertisementreceived" 事件。

[Exposed=Window, SecureContext]
interface BluetoothManufacturerDataMap {
  readonly maplike<unsigned short, DataView>;
};
[Exposed=Window, SecureContext]
interface BluetoothServiceDataMap {
  readonly maplike<UUID, DataView>;
};
[
  Exposed=Window,
  SecureContext
]
interface BluetoothAdvertisingEvent : Event {
  constructor(DOMString type, BluetoothAdvertisingEventInit init);
  [SameObject]
  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;
  [SameObject]
  readonly attribute BluetoothManufacturerDataMap manufacturerData;
  [SameObject]
  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;
  BluetoothManufacturerDataMap manufacturerData;
  BluetoothServiceDataMap serviceData;
};
device 是发送此广播的 BluetoothDevice

uuids 列出该广播声称 device 的 GATT 服务器所支持的服务 UUID。

namedevice 的本地名称,或其前缀。

appearance 是一个Appearance,取值为 gap.appearance 特征定义的值之一。

txPower 为设备进行广播时的发射功率(单位 dBm)。用于按 this.txPower - this.rssi 计算路径损耗。

rssi 为接收到该广播时的功率(单位 dBm)。用于按 this.txPower - this.rssi 计算路径损耗。

manufacturerDataunsigned short 公司标识符映射到 DataView

serviceDataUUID 映射到 DataView

为检索某个设备并读取其 iBeacon 数据,开发者可以使用如下代码。注意该 API 当前不提供按特定厂商数据请求设备的方式,因此 iBeacon 需要轮换其广播以包含某个已知服务,用户才能在 requestDevice 对话框中选择此设备。
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 广播的格式参考自 Adam Warski 的How do iBeacons work?

当 UA 接收到一个advertising event(由一个广播数据包和可选的扫描响应组成)时,必须运行以下步骤:
  1. device 为发送该广播事件的蓝牙设备

  2. 对于 UA 中的每个 BluetoothDevice deviceObj,使得 devicedeviceObj.[[representedDevice]]同一设备,在 deviceObj相关设置对象负责的事件循环排队一个任务以执行以下子步骤:

    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 中任一数据包可用接收信号强度,则将 event.rssi 设为该以 dBm 计的信号强度。

  3. 对于 adv 的广播数据包和扫描响应中的每个AD 结构,根据 AD 类型按以下步骤进行:

    16 位服务 UUID不完整列表
    16 位服务 UUID完整列表
    32 位服务 UUID不完整列表
    32 位服务 UUID完整列表
    128 位服务 UUID不完整列表
    128 位服务 UUID完整列表
    对列出的每个 uuid,如果其包含在 this.device.[[allowedServices]] 中,则将 uuid追加到 event.uuids
    缩短的本地名称
    完整本地名称
    对 AD 数据进行无 BOM 的 UTF-8 解码,并将 event.name 设为结果。
    注意:我们不暴露名称是否完整,因为现有 API 需要读取原始广播才能获知该信息;在将该字段加入 API 前,我们希望获得更多其有用性的证据。
    厂商特定数据
    对每个 16 位公司标识符 manufacturerCode,如果其包含在 this.device.[[allowedManufacturerData]] 中,且该厂商数据不是阻止列表中的厂商数据,则将 manufacturerCode 映射到一个包含该厂商特定数据的 ArrayBuffer 并加入到 event.manufacturerData
    TX 发射功率等级
    event.txPower 设为 AD 数据。
    服务数据 - 16 位 UUID
    服务数据 - 32 位 UUID
    服务数据 - 128 位 UUID
    对服务数据中的每个UUID uuid,如果其包含在 this.device.[[allowedServices]] 中,则将 uuid 映射到一个包含该服务数据的 ArrayBuffer 并加入到 event.serviceData
    外观(Appearance)
    event.appearance 设为 AD 数据。
    否则
    跳过到下一个 AD 结构。
  4. deviceObj 上,使用以 event 初始化、其 isTrusted 属性初始化为 trueBluetoothAdvertisingEvent触发一个事件,名称为 "advertisementreceived"。

BluetoothAdvertisingEvent 中的所有字段都会返回它们上次被初始化或设置的值。

BluetoothAdvertisingEvent(type, init) 构造函数必须执行以下步骤:
  1. event 为运行 DOM § 2.5 事件的构造步骤(但不包括 uuidsmanufacturerDataserviceData 成员)所得的结果。

  2. 如果 init.uuids 已设置,则将 event.uuids 初始化为一个新的 FrozenArray,其元素为 init.uuids.map( BluetoothUUID.getService) 的结果。否则,将 event.uuids 初始化为一个空的 FrozenArray

  3. 对于 init.manufacturerData 中的每个映射:

    1. code 为将键转换为 unsigned short 的结果。

    2. value 为该值。

    3. 如果 value 不是 BufferSource,抛出 TypeError

    4. bytes 为一个新的只读 ArrayBuffer,其中包含value持有字节的拷贝。

    5. event.manufacturerData.[[BackingMap]] 中添加从 codenew DataView(bytes) 的映射。

  4. 对于 init.serviceData 中的每个映射:

    1. key 为该键。

    2. service 为调用 BluetoothUUID.getService(key). 的结果。

    3. value 为该值。

    4. 如果 value 不是 BufferSource,抛出 TypeError

    5. bytes 为一个新的只读 ArrayBuffer,其中包含value持有字节的拷贝。

    6. event.serviceData.[[BackingMap]] 中添加从 servicenew DataView(bytes) 的映射。

  5. 返回 event

5.2.3.1. BluetoothManufacturerDataMap

BluetoothManufacturerDataMap 实例具有一个 [[BackingMap]] 槽位,因为它们是maplike,该槽位将厂商代码映射到厂商数据,并转换为 DataView

5.2.3.2. BluetoothServiceDataMap

BluetoothServiceDataMap 实例具有一个 [[BackingMap]] 槽位,因为它们是maplike,该槽位将服务 UUID 映射到服务数据,并转换为 DataView

6. GATT 交互

6.1. GATT 信息模型

GATT 配置文件层级描述了 GATT 服务器如何包含一个由配置文件(Profile)、主服务(Service)包含的服务(Included Service)特征值(Characteristic)以及描述符(Descriptor)组成的层级结构。

配置文件(Profile)是纯逻辑的:Profile 的规范描述了该 Profile 所包含的其它 GATT 实体之间期望的交互,但无法查询设备支持哪些 Profile。

GATT 客户端可以通过一组GATT 流程来发现并与设备上的服务(Services)、特征值(Characteristics)和描述符(Descriptors)交互。本文将服务、特征值与描述符统称为属性(Attribute)。所有属性都有由UUID标识的类型。每个属性还有一个 16 位的属性句柄(Attribute Handle),用于将其与同一GATT 服务器上相同类型的其他属性区分开来。属性在其GATT 服务器内名义上按其属性句柄排序,但尽管平台接口会以某种顺序提供属性,它们并不保证该顺序与属性句柄顺序一致。

一个服务(Service)包含一组包含的服务(Included Service)特征值(Characteristic)。包含的服务是对其他服务的引用,单个服务可以被多个其他服务包含。服务若直接位于GATT 服务器之下,则称为主服务(Primary Services);如果仅被其他服务包含,则为次级服务(Secondary Services),但主服务也可以被包含。

一个特征值(Characteristic)包含一个字节数组形式的值,以及一组描述符(Descriptor)。根据该特征值的属性(properties)GATT 客户端可以读取或写入其值,或注册在其值发生变化时得到通知。

最后,描述符(Descriptor)包含一个(同样是字节数组的)值,用于描述或配置其所属的特征值(Characteristic)

6.1.1. 跨连接的持久性

蓝牙的属性缓存(Attribute Caching)系统允许已绑定(bonded)的客户端在一次连接到下一次连接之间保存对属性的引用。Web Bluetooth 将网站视为与其有权限访问的设备绑定BluetoothRemoteGATTServiceBluetoothRemoteGATTCharacteristic, 以及BluetoothRemoteGATTDescriptor 对象在断开连接时变为无效,站点在再次连接时必须重新获取它们。

6.1.2. 蓝牙缓存

UA 必须维护一个关于其在某设备上发现的服务、特征值与描述符层级结构的蓝牙缓存。UA 可以在多个访问同一设备的源(origin)之间共享该缓存。缓存中的每个潜在条目要么是“已知存在(known-present)”,要么是“已知不存在(known-absent)”,要么是“未知(unknown)”。缓存不得包含两个针对同一属性的条目。缓存中每个已知存在的条目,在每个 Bluetooth 实例下,都关联一个可选的 Promise<BluetoothRemoteGATTService>Promise<BluetoothRemoteGATTCharacteristic>, 或 Promise<BluetoothRemoteGATTDescriptor> 实例。

注意:例如,如果用户在初始为空的蓝牙缓存下调用 serviceA.getCharacteristic(uuid1),UA 会使用按 UUID 发现特征值(Discover Characteristics by UUID)流程来填充所需的缓存条目,并且因为只需要一个特征值来履行返回的Promise,UA 可能会提前结束该流程,那么 serviceA 内第一个 UUID 为 uuid1 的特征值就变为已知存在,而该 UUID 的后续特征值仍保持未知。如果用户稍后调用 serviceA.getCharacteristics(uuid1),UA 需要恢复或重启按 UUID 发现特征值流程。如果最终发现 serviceA 只有一个 UUID 为 uuid1 的特征值,那么后续的特征值将变为已知不存在。

蓝牙缓存中的已知存在条目是有序的:主服务在设备内部具有特定顺序;包含的服务与特征值在其服务内部具有特定顺序;描述符在其特征值内部具有特定顺序。该顺序应当与设备上的属性句柄顺序匹配,但如果设备的顺序不可用,UA 可以使用其他顺序。

为了填充蓝牙缓存,使其包含与某种描述相匹配的条目,UA 必须运行以下步骤。
注意:这些步骤可能会阻塞,因此对本算法的使用必须并行进行。
  1. 尝试使用任何GATT 流程的序列,只要[BLUETOOTH42]规定它们将返回足够的信息,即可将缓存中所有匹配的条目标记为已知存在或已知不存在。按§ 6.7 错误处理所述处理错误。

  2. 如果上一步返回错误,则从本算法返回该错误。

为了在某个 BluetoothDevice 实例 deviceObj查询蓝牙缓存以获取与某种描述相匹配的条目,UA 必须返回一个包裹在 deviceObj.gatt-连接检查包装器内的新 promisepromise,并并行运行以下步骤:
  1. globaldeviceObj相关全局对象

  2. 填充蓝牙缓存,使其包含匹配该描述的条目。

  3. 如果上一步返回错误,则在给定 globalBluetooth 任务源上排队一个全局任务拒绝promise并附带该错误,然后中止这些步骤。

  4. entries 为与该描述匹配的“已知存在”的缓存条目序列。

  5. contextdeviceObj.[[context]]

  6. result 为一个新的序列。

  7. 对于 entries 中的每个 entry

    1. 如果 entrycontext.[[attributeInstanceMap]] 中没有关联的 Promise<BluetoothGATT*> 实例,则视 entry 的类型(服务、特征值或描述符),分别创建一个表示该条目的 BluetoothRemoteGATTService创建一个表示该条目的 BluetoothRemoteGATTCharacteristic,或者创建一个表示该条目的 BluetoothRemoteGATTDescriptor,并在 context.[[attributeInstanceMap]] 中添加从 entry 到所得到的Promise的映射。

    2. 将与 entry 关联的 Promise<BluetoothGATT*> 实例追加到 context.[[attributeInstanceMap]] 中对应的 result

  8. 在给定 globalBluetooth 任务源上排队一个全局任务,以解析promise,其值为等待所有result元素完成后的结果。

Represented(obj: Device or GATT Attribute) 根据 obj 的类型返回:
BluetoothDevice
obj.[[representedDevice]]
BluetoothRemoteGATTService
obj.[[representedService]]
BluetoothRemoteGATTCharacteristic
obj.[[representedCharacteristic]]
BluetoothRemoteGATTDescriptor
obj.[[representedDescriptor]]
为了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,并中止这些步骤。

  2. 如果存在 uuid 且其在阻止列表中,返回一个被拒绝的 promise,错误为SecurityError,并中止这些步骤。

  3. deviceObj 为(取决于 attribute 的类型):

    BluetoothDevice
    attribute
    BluetoothRemoteGATTService
    attribute.device
    BluetoothRemoteGATTCharacteristic
    attribute.service.device
  4. 如果 deviceObj.gatt.connectedfalse,返回一个被拒绝的 promise,错误为NetworkError,并中止这些步骤。

  5. 如果 Represented(attribute) 为 null,返回一个被拒绝的 promise,错误为InvalidStateError,并中止这些步骤。

    Note: 当某个服务或特征值从设备上被移除或因断开连接而失效后又被再次使用时,就会出现这种情况。

  6. deviceObj查询蓝牙缓存,查找满足以下条件的条目:

    • 位于 Represented(attribute) 之内,

    • 其类型由 child type 描述,

    • 其 UUID 不在阻止列表中,

    • 如果存在 uuid,则其 UUID 等于 uuid

    • 如果存在 allowedUuids 且不为 "all",则其 UUID 在 allowedUuids 中,且

    • 如果设置了 single 标志,则只取这些中的第一个。

    promise 为该结果。

  7. promise完成并给出 result 时,运行以下步骤:

    • 如果 result 为空,抛出NotFoundError

    • 否则,如果设置了 single 标志,返回 result 的第一个(也是唯一一个)元素。

    • 否则,返回 result

6.1.4. 标识服务、特征值和描述符

在检查两个服务、特征值或描述符 ab 是否为同一属性时,UA 应当在 ab 位于同一设备且具有相同属性句柄时认定它们相同,但 UA 可以使用任何算法,只要满足以下约束:若符合下列任一条件,ab不得被认为是同一属性

注意:该定义较为宽松,因为各平台 API 会暴露它们自己对身份的概念,但不会文档化该概念是否基于属性句柄的相等性。
注意:对于表示服务、特征值或描述符的两个 JavaScript 对象 xyx === y 会返回这两个对象是否表示同一属性,这是因为查询蓝牙缓存算法创建并缓存新对象的方式所致。

6.2. BluetoothRemoteGATTServer

BluetoothRemoteGATTServer 表示远程设备上的GATT 服务器

[Exposed=Window, SecureContext]
interface BluetoothRemoteGATTServer {
  [SameObject]
  readonly attribute BluetoothDevice device;
  readonly attribute boolean connected;
  Promise<BluetoothRemoteGATTServer> connect();
  undefined disconnect();
  Promise<BluetoothRemoteGATTService> getPrimaryService(BluetoothServiceUUID service);
  Promise<sequence<BluetoothRemoteGATTService>>
    getPrimaryServices(optional BluetoothServiceUUID service);
};
device 是运行该服务器的设备。

connected 在该实例与 this.device 相连期间为 true。当 UA 在物理层面保持连接但对其他BluetoothRemoteGATTServer实例(位于其他全局对象)也建立连接时,该值可能为 false。

当没有 ECMAScript 代码能够再观察到某个 BluetoothRemoteGATTServer 实例 server 时,UA 应当运行 server.disconnect()

注意:由于 BluetoothDevice 实例保存在 navigator.bluetooth.[[deviceInstanceMap]] 中,这至少要等到导航释放该全局对象,或者关闭标签页或窗口销毁浏览上下文后才能发生。
注意:在垃圾回收时断开连接可确保 UA 不会在远程设备上不必要地持续消耗资源。

BluetoothRemoteGATTServer 实例按下表所述,带有内部槽位

内部槽位 初始值 描述(非规范性)
[[activeAlgorithms]] new Set() 包含与使用该服务器连接的每个算法对应的Promisedisconnect() 会清空该集合,以便算法可以判断其realm在运行期间是否曾被断开。
[[automatedGATTConnectionResponse]] "not-expected" 针对一次 GATT 连接尝试的模拟 GATT 连接响应码。
connect() 方法在被调用时,必须执行以下步骤:
  1. globalthis相关全局对象

  2. 如果this.device.[[representedDevice]]null,返回一个被拒绝的 promise,错误为“NetworkError”的 DOMException

  3. 如果 UA 当前正在使用蓝牙系统,它可以返回一个被拒绝的 promise,错误为“NetworkError”的 DOMException

    实现可能能够避免该NetworkError,但目前站点需要串行化对该 API 的使用,和/或为用户提供重试失败操作的方法。[Issue #188]

  4. promise一个新的 promise

  5. 如果this.connectedtrue,则解析promise,其值为this,并返回 promise

  6. promise 添加到this.[[activeAlgorithms]]

  7. 并行运行以下步骤:

    1. 如果 globalnavigable顶层可遍历对象具有模拟蓝牙适配器,则运行以下步骤:

      1. 触发一次 gatt 连接尝试事件,传入 globalnavigablethis.device

      2. 如果this.[[automatedGATTConnectionResponse]]"not-expected",将其设为 "expected"

      3. 如果this.[[automatedGATTConnectionResponse]]"expected",则等待其改变。

      4. responsethis.[[automatedGATTConnectionResponse]]

      5. this.[[automatedGATTConnectionResponse]] 设为 "not-expected"

      6. 如果 response 不为 0,执行以下子步骤:

        1. this.[[activeAlgorithms]] 中移除 promise

        2. 在给定 globalBluetooth 任务源上排队一个全局任务,以拒绝promise,错误为“NetworkError”的 DOMException,并中止这些步骤。

    2. 否则,运行以下步骤:

      1. 如果this.device.[[representedDevice]] 尚无ATT 承载体,执行以下子步骤:

        1. 尝试使用GAP 互操作性要求中的“连接建立”所述流程创建一个ATT 承载体。如果 promisethis.[[activeAlgorithms]] 中被移除,则中止此次尝试。

          注意:如果未接收到可连接的广播,这些流程可能会无限等待。如果网站不再希望连接,应调用disconnect()
        2. 如果此次尝试因 promisethis.[[activeAlgorithms]] 中移除而被中止,则在给定 globalBluetooth 任务源上排队一个全局任务拒绝promise,错误为“AbortError”的 DOMException,并中止这些步骤。

        3. 如果此次尝试因其他原因失败,则在给定 globalBluetooth 任务源上排队一个全局任务拒绝promise,错误为“NetworkError”的 DOMException,并中止这些步骤。

        4. 使用交换 MTU(Exchange MTU)流程来协商最大的支持 MTU。忽略此步骤中的任何错误。

        5. UA 可以尝试使用BR/EDR 绑定流程LE 绑定流程与远程设备建立绑定。

          Note: 通常我们更希望由网站控制是否以及何时进行绑定,但 Core Bluetooth 平台 API 不提供让 UA 实现此控制的方式。拥有绑定比没有绑定更安全,因此本规范允许 UA 在可能的平台上机会性地创建绑定。这可能会导致在建立连接时(而不是访问受限特征值时)出现一个用户可见的配对对话框。

    3. 在给定 globalBluetooth 任务源上排队一个全局任务,以执行以下子步骤:

      1. 如果 promise 不在this.[[activeAlgorithms]] 中,拒绝promise,错误为“AbortError”的 DOMException回收该连接(位于 this.device.[[representedDevice]]),并中止这些步骤。

      2. this.[[activeAlgorithms]] 中移除 promise

      3. 如果this.device.[[representedDevice]]null拒绝promise,错误为“NetworkError”的 DOMException回收该连接(位于 this.device.[[representedDevice]]),并中止这些步骤。

      4. this.connected 设为 true

      5. 解析promise,其值为this

  8. 返回 promise

disconnect() 方法在被调用时,必须执行以下步骤:
  1. 清空 this.[[activeAlgorithms]],以中止任何活动的 connect() 调用

  2. 如果 this.connectedfalse,中止这些步骤。

  3. 清理已断开连接的设备 this.device

  4. devicethis.device.[[representedDevice]]

  5. 回收device 的连接。

为了回收连接(针对某个 device),UA 必须并行执行以下步骤:
  1. 如果 UA 内外部的非本 API 系统正在使用 deviceATT 承载体,则中止本算法。

  2. 对于整个 UA 中的所有 BluetoothDevice deviceObj

    1. 如果 deviceObj.[[representedDevice]] 不是与 device同一设备,则继续下一个 deviceObj

    2. 如果 deviceObj.gatt.connectedtrue,中止本算法。

    3. 如果 deviceObj.gatt.[[activeAlgorithms]] 包含一次connect()调用的Promise,中止本算法。

  3. 销毁 deviceATT 承载体

注意:算法需要在其关联的BluetoothRemoteGATTServer 在执行期间被断开时失败,即便 UA 全程保持连接,且该BluetoothRemoteGATTServer 在结束前又重新连接。我们通过包装返回的Promise来实现这一点。

为了在 promise 周围创建一个 gattServer-连接检查包装器,UA 必须:

  1. 如果 gattServer.connectedtrue,将 promise 添加到 gattServer.[[activeAlgorithms]]

  2. promise做出反应:

    • 如果 promise 以值 result 得到满足,则:

      1. 如果 promisegattServer.[[activeAlgorithms]] 中,移除它并返回 result

      2. 否则,抛出NetworkError

        注意:抛出该错误是因为 gattServer 在主算法执行期间被断开。
    • 如果 promise 以原因 error 被拒绝,则:

      1. 如果 promisegattServer.[[activeAlgorithms]] 中,移除它并抛出 error

      2. 否则,抛出NetworkError

        注意:抛出该错误是因为 gattServer 在主算法执行期间被断开。
getPrimaryService(service) 方法在被调用时,必须执行以下步骤:
  1. 如果 this.device.[[allowedServices]] 不为 "all",且 service 不在 this.device.[[allowedServices]] 中,则返回一个被拒绝的 promise,错误为SecurityError,并中止这些步骤。

  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]] 中,则返回一个被拒绝的 promise,错误为SecurityError,并中止这些步骤。

  2. 返回 GetGATTChildren(attribute=this.device,
    single=false,
    uuidCanonicalizer=BluetoothUUID.getService,
    uuid=service,
    allowedUuids=this.device.[[allowedServices]],
    child type="GATT Primary Service")

6.3. BluetoothRemoteGATTService

BluetoothRemoteGATTService 表示一个 GATT Service,即由若干特征值以及与其他服务的关系组成的集合,用于封装设备某一部分的行为。

[Exposed=Window, SecureContext]
interface BluetoothRemoteGATTService : EventTarget {
  [SameObject]
  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 includes CharacteristicEventHandlers;
BluetoothRemoteGATTService includes ServiceEventHandlers;
device 是表示该 GATT 服务所属的远程外围设备的 BluetoothDevice

uuid 是该服务的 UUID,例如 '0000180d-0000-1000-8000-00805f9b34fb' 对应 心率(Heart Rate) 服务。

isPrimary 指示该服务的类型是主服务还是次级服务。

BluetoothRemoteGATTService 实例按下表所述带有内部槽位

内部槽位 初始值 描述(非规范性)
[[representedService]] <总在正文中设定> 此对象所表示的Service;若该 Service 已被移除或以其他方式失效,则为 null
创建一个表示 BluetoothRemoteGATTService 的 Service service,UA 必须运行以下步骤:
  1. globalthis相关全局对象

  2. promise一个新的 promise

  3. 并行运行以下步骤:

    1. resultBluetoothRemoteGATTService 的新实例,其 [[representedService]] 槽位初始化为 service

    2. 获取表示该设备的 BluetoothDevice,即 service 所在的设备,并令 device 为结果。

    3. 如果上一步抛出错误,则在给定 globalBluetooth 任务源排队一个全局任务拒绝promise 并附带该错误,然后中止这些步骤。

    4. device 初始化 result.device

    5. service 的 UUID 初始化 result.uuid

    6. 如果 service 是主服务,则将 result.isPrimary 设为 true;否则设为 false

    7. 在给定 globalBluetooth 任务源排队一个全局任务,以解析promise,其值为 result

  4. 返回 promise

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")

6.4. BluetoothRemoteGATTCharacteristic

BluetoothRemoteGATTCharacteristic 表示一个 GATT Characteristic,它是提供关于外围设备服务的进一步信息的基础数据单元。

[Exposed=Window, SecureContext]
interface BluetoothRemoteGATTCharacteristic : EventTarget {
  [SameObject]
  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<undefined> writeValue(BufferSource value);
  Promise<undefined> writeValueWithResponse(BufferSource value);
  Promise<undefined> writeValueWithoutResponse(BufferSource value);
  Promise<BluetoothRemoteGATTCharacteristic> startNotifications();
  Promise<BluetoothRemoteGATTCharacteristic> stopNotifications();
};
BluetoothRemoteGATTCharacteristic includes CharacteristicEventHandlers;
service 是该特征值所属的 GATT 服务。

uuid 是该特征值的 UUID,例如 '00002a37-0000-1000-8000-00805f9b34fb' 对应 心率测量(Heart Rate Measurement)特征值。

properties 保存该特征值的属性。

value 是当前缓存的特征值;当通过读取或通知/指示更新该特征值时,这个值会被更新。

BluetoothRemoteGATTCharacteristic 实例按下表所述带有内部槽位

内部槽位 初始值 描述(非规范性)
[[representedCharacteristic]] <总在正文中设定> 此对象所表示的Characteristic;若该 Characteristic 已被移除或以其他方式失效,则为 null
[[automatedCharacteristicReadResponse]] "not-expected" 针对一次 GATT 特征值读取尝试的模拟 GATT 特征值响应码。
[[automatedCharacteristicReadResponseData]] 空的字节序列 针对一次 GATT 特征值读取尝试的模拟 GATT 特征值响应数据。
[[automatedCharacteristicWriteResponse]] "not-expected" 针对一次 GATT 特征值写入尝试的模拟 GATT 特征值响应码。
[[automatedCharacteristicSubscribeToNotificationsResponse]] "not-expected" 针对一次订阅 GATT 特征值通知尝试的模拟 GATT 特征值响应码。
[[automatedCharacteristicUnsubscribeFromNotificationsResponse]] "not-expected" 针对一次取消订阅 GATT 特征值通知尝试的模拟 GATT 特征值响应码。
创建一个表示 BluetoothRemoteGATTCharacteristic 的实例来表示特征值 characteristic,UA 必须运行以下步骤:
  1. globalthis相关全局对象

  2. promise一个新的 promise

  3. 并行运行以下步骤:

    1. resultBluetoothRemoteGATTCharacteristic 的新实例,其 [[representedCharacteristic]] 槽位初始化为 characteristic

    2. 用出现 characteristic 的 Service 的表示实例,初始化 result.service<。

    3. characteristic 的 UUID 初始化 result.uuid

    4. 基于特征值创建一个 BluetoothCharacteristicProperties 实例 characteristic,并令 properties 为其结果。

    5. 如果上一步返回错误,则在给定 globalBluetooth 任务源排队一个全局任务拒绝promise 并附带该错误,然后中止这些步骤。

    6. result.properties 初始化为 properties

    7. result.value 初始化为 null。如果可用,UA 可以将 result.value 初始化为一个新的 DataView,其封装了一个新的 ArrayBuffer,包含最近一次从 characteristic 读取到的值。

    8. 在给定 globalBluetooth 任务源排队一个全局任务,以解析promise,其值为 result

  4. 返回 promise

getDescriptor(descriptor) 方法在此特征值内检索一个 Descriptor。被调用时,必须返回:

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

getDescriptors(descriptor) 方法在此特征值内检索 Descriptor 列表。被调用时,必须返回:

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

readValue() 方法被调用时,必须运行以下步骤:
  1. globalthis相关全局对象

  2. gattthis.service.device.gatt

  3. 如果this.uuid 被列入读取阻止列表,则返回一个被拒绝的 promise,错误为“SecurityError” 的 DOMException,并中止这些步骤。

  4. 如果 gatt.connectedfalse,则返回一个被拒绝的 promise,错误为“NetworkError” 的 DOMException,并中止这些步骤。

  5. characteristicthis.[[representedCharacteristic]]

  6. 如果 characteristicnull,则返回一个被拒绝的 promise,错误为“InvalidStateError” 的 DOMException,并中止这些步骤。

  7. 返回一个围绕新 promise promisegatt-连接检查包装器,并并行运行以下步骤:

    1. 如果 characteristic属性中未设置 Read 位,则在给定 globalBluetooth 任务源排队一个全局任务拒绝promise,错误为“NotSupportedError” 的 DOMException,并中止这些步骤。

    2. 如果 globalnavigable顶层可遍历对象具有模拟蓝牙适配器,则运行以下步骤:

      1. 如果this.[[automatedCharacteristicReadResponse]] 不为 "not-expected",则在给定 globalBluetooth 任务源排队一个全局任务拒绝 promise,错误为“InvalidStateError” 的 DOMException,并中止这些步骤。

      2. 触发一个模拟的特征值事件,传入 globalnavigablethis.devicecharacteristicread

      3. this.[[automatedCharacteristicReadResponse]] 设为 "expected",并等待其改变。

      4. responsethis.[[automatedCharacteristicReadResponse]]

      5. this.[[automatedCharacteristicReadResponse]] 设为 "not-expected"

      6. 如果 response 不为 0,则执行以下子步骤:

        1. 在给定 globalBluetooth 任务源排队一个全局任务拒绝 promise,错误为“NetworkError” 的 DOMException,并中止这些步骤。

      7. 否则,令 buffer 为一个新的 ArrayBuffer,其中包含 this.[[automatedCharacteristicReadResponseData]]

    3. 否则,运行以下步骤:

      1. 如果 UA 当前正在使用蓝牙系统,它可以在给定 globalBluetooth 任务源排队一个全局任务拒绝 promise,错误为“NetworkError” 的 DOMException,并中止这些步骤。

        实现可能能够避免该 NetworkError,但目前站点需要串行化使用该 API,和/或为用户提供重试失败操作的方法。[Issue #188]

      2. 使用特征值读取(Characteristic Value Read)流程中的任意子流程来获取 characteristic 的值,并令 buffer 为一个新的 ArrayBuffer,其中保存所取回的值。 错误按§ 6.7 错误处理进行处理。

    4. 在给定 globalBluetooth 任务源排队一个全局任务以执行以下步骤:

      1. 如果 promise 不在 gatt.[[activeAlgorithms]] 中,则拒绝 promise,错误为“NetworkError” 的 DOMException,并中止这些步骤。

      2. 如果上述子流程返回错误,则拒绝 promise 并附带该错误,中止这些步骤。

      3. 将用 buffer 创建的新的 DataView 赋给 this.value

      4. 触发一个事件,名称为“characteristicvaluechanged”,其 bubbles 属性初始化为 true,目标为 this

      5. 解析 promise,其值为 this.value

WriteCharacteristicValue( this: BluetoothRemoteGATTCharacteristic,
value: BufferSource,
response: string),

UA 必须执行以下步骤:
  1. globalthis相关全局对象

  2. gattthis.service.device.gatt

  3. 如果 this.uuid 被列入写入阻止列表,则返回一个被拒绝的 promise,错误为“SecurityError” 的 DOMException,并中止这些步骤。

  4. bytesvalue 持有字节的拷贝

  5. 如果 bytes 长度超过 512 字节(参见长属性值中的属性最大长度),则返回一个被拒绝的 promise,错误为“InvalidModificationError” 的 DOMException,并中止这些步骤。

  6. 如果 gatt.connectedfalse,则返回一个被拒绝的 promise,错误为“NetworkError” 的 DOMException,并中止这些步骤。

  7. characteristicthis.[[representedCharacteristic]]

  8. 如果 characteristicnull,则返回一个被拒绝的 promise,错误为“InvalidStateError” 的 DOMException,并中止这些步骤。

  9. 返回一个围绕新 promise promisegatt-连接检查包装器,并并行运行以下步骤。

    1. 断言:response 取值为 "required"、"never" 或 "optional" 之一。

    2. 如果 globalnavigable顶层可遍历对象具有模拟蓝牙适配器,则运行以下步骤:

      1. 如果this.[[automatedCharacteristicWriteResponse]] 不为 "not-expected",则在给定 globalBluetooth 任务源排队一个全局任务拒绝 promise,错误为“InvalidStateError” 的 DOMException,并中止这些步骤。

      2. 触发一个模拟的特征值事件,传入 globalnavigablethis.devicecharacteristicwritebytes

      3. this.[[automatedCharacteristicWriteResponse]] 设为 "expected",并等待其改变。

      4. responsethis.[[automatedCharacteristicWriteResponse]]

      5. this.[[automatedCharacteristicWriteResponse]] 设为 "not-expected"

      6. 如果 response 不为 0,则执行以下子步骤:

        1. 在给定 globalBluetooth 任务源排队一个全局任务拒绝 promise,错误为“NetworkError” 的 DOMException,并中止这些步骤。

    3. 否则,运行以下步骤:

      1. 如果 UA 当前正在使用蓝牙系统,它可以在给定 globalBluetooth 任务源排队一个全局任务拒绝 promise,错误为“NetworkError” 的 DOMException,并中止这些步骤。

        实现可能能够避免该 NetworkError,但目前站点需要串行化使用该 API,和/或为用户提供重试失败操作的方法。[Issue #188]

      2. 通过执行以下步骤将 bytes 写入 characteristic

        如果 response 为 "required"
        使用 写特征值(Write Characteristic Value) 流程。
        如果 response 为 "never"
        使用 无响应写(Write Without Response) 流程。
        否则
        使用特征值写入(Characteristic Value Write)流程中的任意子流程组合。
        错误按§ 6.7 错误处理进行处理。
    4. global 上使用 Bluetooth 任务源排队一个全局任务以执行以下步骤:

      1. 如果 promise 不在 gatt.[[activeAlgorithms]] 中,则拒绝 promise,错误为“NetworkError” 的 DOMException,并中止这些步骤。

      2. 如果上述流程返回错误,则拒绝 promise 并附带该错误,中止这些步骤。

      3. this.value 设为一个新的 DataView,其封装了一个新的 ArrayBuffer,其中包含 bytes

      4. 解析 promise,其值为 undefined

已废弃。 请改用 writeValueWithResponse()writeValueWithoutResponse()

writeValue(value) 方法被调用时,必须返回:

WriteCharacteristicValue( this=this,
value=value,
response="optional")

此方法仅用于向后兼容。新的实现不应实现此方法。[Issue #238]

writeValueWithResponse(value) 方法被调用时,必须返回:

WriteCharacteristicValue( this=this,
value=value,
response="required")

writeValueWithoutResponse(value) 方法被调用时, 必须返回:

WriteCharacteristicValue( this=this,
value=value,
response="never")

UA 必须维护一个从每个已知 GATT Characteristic 到一组 Bluetooth 对象的映射,该组被称为该特征值的活动通知上下文集合

注意:某个特定特征值对应的集合,持有每个已注册通知的Realmnavigator.bluetooth 对象。当设备断开连接时,所有通知都会变为非活动状态。若站点希望在重新连接后继续接收通知,需要再次调用 startNotifications(),并且在 startNotifications() 生效前的间隙里不可避免会丢失部分通知。
startNotifications() 方法被调用时,必须运行以下步骤。关于接收通知的详细信息,参见 § 6.6.4 响应通知与指示
  1. globalthis相关全局对象

  2. gattthis.service.device.gatt

  3. 如果this.uuid 被列入读取阻止列表,则返回一个被拒绝的 promise,错误为“SecurityError” 的 DOMException

  4. 如果 gatt.connectedfalse,则返回一个被拒绝的 promise,错误为“NetworkError” 的 DOMException

  5. characteristicthis.[[representedCharacteristic]]

  6. 如果 characteristicnull,则返回一个被拒绝的 promise,错误为“InvalidStateError” 的 DOMException

  7. 返回一个围绕新 promise promisegatt-连接检查包装器,并并行运行以下步骤。

    1. 如果 characteristic属性中既未设置 Notify 位也未设置 Indicate 位,则在给定 globalBluetooth 任务源排队一个全局任务拒绝 promise,错误为 NotSupportedError,并中止这些步骤。

    2. 如果 characteristic活动通知上下文集合包含 navigator.bluetooth,则在给定 globalBluetooth 任务源排队一个全局任务解析 promise,其值为this,并中止这些步骤。

    3. 如果 globalnavigable顶层可遍历对象具有模拟蓝牙适配器,则运行以下步骤:

      1. 如果this.[[automatedCharacteristicSubscribeToNotificationsResponse]] 不为 "not-expected",则在给定 globalBluetooth 任务源排队一个全局任务拒绝 promise,错误为“InvalidStateError” 的 DOMException,并中止这些步骤。

      2. 触发一个模拟的特征值事件,传入 globalnavigablethis.devicecharacteristicsubscribe-to-notifications

      3. this.[[automatedCharacteristicSubscribeToNotificationsResponse]] 设为 "expected",并等待其改变。

      4. responsethis.[[automatedCharacteristicSubscribeToNotificationsResponse]]

      5. this.[[automatedCharacteristicSubscribeToNotificationsResponse]] 设为 "not-expected"

      6. 如果 response 不为 0,则执行以下子步骤:

        1. 在给定 globalBluetooth 任务源排队一个全局任务拒绝 promise,错误为“NetworkError” 的 DOMException,并中止这些步骤。

      7. 否则,令 successtrue

    4. 否则,运行以下步骤:

      1. 如果 UA 当前正在使用蓝牙系统,它可以在给定 globalBluetooth 任务源排队一个全局任务拒绝 promise,错误为“NetworkError” 的 DOMException,并中止这些步骤。

        实现可能能够避免该 NetworkError,但目前站点需要串行化使用该 API,和/或为用户提供重试失败操作的方法。[Issue #188]

      2. 如果该特征值具有客户端特征值配置(Client Characteristic Configuration) 描述符,则使用任意特征值描述符(Characteristic Descriptors)流程,确保在 characteristic客户端特征值配置描述符中设置了 NotificationIndication 位之一,并与 characteristic属性中的约束相匹配。UA 应避免同时设置两位;若两位均被设置,则必须对值变更事件去重。错误按§ 6.7 错误处理进行处理。

        注意:某些设备具有包含 Notify 或 Indicate 位的特征值属性,但却没有客户端特征值配置描述符。这些不符合标准的特征值往往无条件地发送通知或指示,因此本规范允许应用直接订阅其消息。
      3. 如果上述流程成功,则令 successtrue

    5. 如果 successtrue,则将 navigator.bluetooth 加入 characteristic活动通知上下文集合

    6. 在给定 globalBluetooth 任务源排队一个全局任务以执行以下步骤:

      1. 如果 promise 不在 gatt.[[activeAlgorithms]] 中,则拒绝 promise,错误为“NetworkError” 的 DOMException,并中止这些步骤。

      2. 如果上述流程返回错误,则拒绝 promise 并附带该错误,中止这些步骤。

      3. 解析 promise,其值为this

注意:启用通知后,产生的值变更事件会在当前微任务检查点之后才会投递。这使得开发者可以在结果 promise 的 .then 处理器中设置事件处理器。
stopNotifications() 方法被调用时,必须返回 一个新的 promise promise并行运行以下步骤:
  1. characteristicthis.[[representedCharacteristic]]

  2. 如果 characteristicnull,返回一个被拒绝的 promise,错误为 InvalidStateError,并中止这些步骤。

  3. 如果 characteristic活动通知上下文集合(active notification context set) 包含 navigator.bluetooth, 则将其移除。

    1. 如果 globalnavigable顶层可遍历对象模拟蓝牙适配器 非空,则运行以下步骤:

      1. 如果 this[[automatedCharacteristicUnsubscribeFromNotificationsResponse]] 不为 "not-expected", 则在给定 global全局任务队列中,基于 Bluetooth 任务源 拒绝 promise,错误为“InvalidStateError” 的 DOMException,并中止这些步骤。

      2. 触发一个模拟的特征值事件,传入 globalnavigablethisdevicecharacteristic 以及 unsubscribe-from-notifications

      3. this[[automatedCharacteristicUnsubscribeFromNotificationsResponse]] 设为 "expected", 并等待其改变。

      4. responsethis[[automatedCharacteristicUnsubscribeFromNotificationsResponse]]

      5. this[[automatedCharacteristicUnsubscribeFromNotificationsResponse]] 设为 "not-expected"

      6. 如果 response 不为 0,则执行以下子步骤:

        1. 在给定 global全局任务队列中,基于 Bluetooth 任务源 拒绝 promise,错误为“NetworkError” 的 DOMException,并中止这些步骤。

    2. 否则,运行以下步骤:

      1. 如果 characteristic活动通知上下文集合 变为空,且该特征值具有 客户端特征值配置(Client Characteristic Configuration) 描述符,则 UA 应使用任意 特征值描述符(Characteristic Descriptors) 流程清除 characteristic客户端特征值配置 描述符中的 NotificationIndication 位。

  4. 在全局任务队列中排队一个任务,基于 Bluetooth 任务源,给定 this相关全局对象,以解析 promise,其值为 this

注意:将任务排队以解析该 promise,可确保由于通知而产生的值变更事件 不会在该 promise 解析之后才到达。

6.4.1. BluetoothCharacteristicProperties

每个 BluetoothRemoteGATTCharacteristic 都通过一个 BluetoothCharacteristicProperties 对象公开其特征值属性(characteristic properties)。这些属性表示在该特征值上哪些操作是有效的。

[Exposed=Window, SecureContext]
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;
};
为了从某个 Characteristic 创建一个 BluetoothCharacteristicProperties 实例 characteristic,UA 必须运行以下步骤:
  1. propertiesObjBluetoothCharacteristicProperties 的一个新实例。

  2. propertiescharacteristic特征值属性

  3. 根据 properties 中相应的比特,初始化 propertiesObj 的各属性:

    Attribute Bit
    broadcast Broadcast
    read Read
    writeWithoutResponse Write Without Response
    write Write
    notify Notify
    indicate Indicate
    authenticatedSignedWrites Authenticated Signed Writes
  4. 如果特征值属性的 Extended Properties 位未设置,则将 propertiesObj.reliableWritepropertiesObj.writableAuxiliaries 初始化为 false。否则,运行以下步骤:

    1. 发现(Discover) Characteristic Extended Properties 描述符, 针对 characteristic读取其值extendedProperties。 错误按 § 6.7 错误处理 中所述进行处理。

      Characteristic Extended Properties 尚不明确扩展属性对于某个特定 Characteristic 是否不可变。 如果不可变,UA 应被允许对其进行缓存。

    2. 如果上一步返回了错误,则返回该错误。

    3. extendedProperties 的 Reliable Write 位初始化 propertiesObj.reliableWrite

    4. extendedProperties 的 Writable Auxiliaries 位初始化 propertiesObj.writableAuxiliaries

  5. 返回 propertiesObj

6.5. BluetoothRemoteGATTDescriptor

BluetoothRemoteGATTDescriptor 表示一个 GATT Descriptor,它为某个Characteristic 的值提供更多信息。

[Exposed=Window, SecureContext]
interface BluetoothRemoteGATTDescriptor {
  [SameObject]
  readonly attribute BluetoothRemoteGATTCharacteristic characteristic;
  readonly attribute UUID uuid;
  readonly attribute DataView? value;
  Promise<DataView> readValue();
  Promise<undefined> writeValue(BufferSource value);
};
characteristic 是该描述符所属的 GATT 特征值。

uuid 是该特征值描述符的 UUID,例如 '00002902-0000-1000-8000-00805f9b34fb' 对应 Client Characteristic Configuration 描述符。

value 是当前缓存的描述符值。该值在读取描述符的值时会被更新。

BluetoothRemoteGATTDescriptor 实例创建时带有如下表所述的内部槽位(internal slots)

Internal Slot Initial Value Description (non-normative)
[[representedDescriptor]] <always set in prose> 此对象所表示的Descriptor;若该描述符已被移除或以其他方式失效,则为 null
[[automatedDescriptorReadResponse]] "not-expected" 针对一次 GATT 描述符读取尝试的模拟 GATT 描述符响应码。
[[automatedDescriptorReadResponseData]] 空的字节序列 针对一次 GATT 描述符读取尝试的模拟 GATT 描述符响应数据。
[[automatedDescriptorWriteResponse]] "not-expected" 针对一次 GATT 描述符写入尝试的模拟 GATT 描述符响应码。
为了创建一个表示 BluetoothRemoteGATTDescriptor 的实例 用于表示一个 Descriptor descriptor, UA 必须运行以下步骤:
  1. promise一个新的 promise

  2. 并行运行以下步骤:

    1. resultBluetoothRemoteGATTDescriptor 的新实例,其 [[representedDescriptor]] 槽位初始化为 descriptor

    2. 用表示 descriptor 所在 Characteristic 的 BluetoothRemoteGATTCharacteristic 实例初始化 result.characteristic

    3. descriptor 的 UUID 初始化 result.uuid

    4. result.value 初始化为 null。 如果可用,UA 可以将 result.value 初始化为一个新的 DataView,其封装了一个新的 ArrayBuffer,包含最近一次从 descriptor 读取到的值。

    5. 在给定 this相关全局对象的前提下,在 Bluetooth 任务源排队一个全局任务,以解析 promise,其值为 result

  3. 返回 promise

readValue() 方法被调用时,必须运行以下步骤:
  1. globalthis相关全局对象

  2. gattthis.characteristic.service.device.gatt

  3. 如果this.uuid 被列入读取阻止列表,则返回一个被拒绝的 promise,错误为“SecurityError” 的 DOMException

  4. 如果 gatt.connectedfalse,则返回一个被拒绝的 promise,错误为“NetworkError” 的 DOMException

  5. descriptorthis.[[representedDescriptor]]

  6. 如果 descriptornull,则返回一个被拒绝的 promise,错误为“InvalidStateError” 的 DOMException

  7. 返回一个围绕新 promise promisegatt-连接检查包装器,并并行运行以下步骤:

    1. 如果 globalnavigabletop-level traversable模拟蓝牙适配器 非空,则运行以下步骤:

      1. 如果this.[[automatedDescriptorReadResponse]] 不为 "not-expected", 则在给定 global全局任务队列中,基于 Bluetooth 任务源 拒绝 promise,错误为“InvalidStateError” 的 DOMException,并中止这些步骤。

      2. 触发一个模拟的描述符事件,传入 globalnavigablethis.devicedescriptorread

      3. this.[[automatedDescriptorReadResponse]] 设为 "expected", 并等待其改变。

      4. responsethis.[[automatedDescriptorReadResponse]]

      5. this.[[automatedDescriptorReadResponse]] 设为 "not-expected"

      6. 如果 response 不为 0,则执行以下子步骤:

        1. 在给定 global全局任务队列中,基于 Bluetooth 任务源 拒绝 promise,错误为“NetworkError” 的 DOMException,并中止这些步骤。

      7. 否则,令 buffer 为一个新的 ArrayBuffer,其中包含 this.[[automatedDescriptorReadResponseData]]

    2. 否则,运行以下步骤:

      1. 如果 UA 当前正在使用蓝牙系统,它可以在给定 global全局任务队列中,基于 Bluetooth 任务源 拒绝 promise,错误为“NetworkError” 的 DOMException,并中止这些步骤。

        实现可能能够避免该 NetworkError, 但目前站点需要串行化使用该 API,和/或为用户提供重试失败操作的方法。[Issue #188]

      2. 使用 Read Characteristic DescriptorsRead Long Characteristic Descriptors 子流程获取 descriptor 的值,并令 buffer 为一个新的 ArrayBuffer,其中保存所取回的值。错误按 § 6.7 错误处理进行处理。

    3. 在给定 globalBluetooth 任务源排队一个全局任务以执行以下步骤:

      1. 如果 promise 不在 gatt.[[activeAlgorithms]] 中,则拒绝 promise,错误为“NetworkError” 的 DOMException,并中止这些步骤。

      2. 如果上述子流程返回错误,则拒绝 promise 并附带该错误,中止这些步骤。

      3. 将用 buffer 创建的新的 DataView 赋给 this.value

      4. 解析 promise,其值为 this.value

writeValue(value) 方法被调用时,必须运行以下步骤:
  1. globalthis相关全局对象

  2. gattthis.characteristic.service.device.gatt

  3. 如果this.uuid 被列入写入阻止列表,则返回一个被拒绝的 promise,错误为“SecurityError” 的 DOMException

  4. bytesvalue 持有字节的拷贝

  5. 如果 bytes 长度超过 512 字节(参见Long Attribute Values 中属性的最大长度),则返回一个被拒绝的 promise,错误为“InvalidModificationError” 的 DOMException

  6. 如果 gatt.connectedfalse,则返回一个被拒绝的 promise,错误为“NetworkError” 的 DOMException

  7. descriptorthis.[[representedDescriptor]]

  8. 如果 descriptornull,则返回一个被拒绝的 promise,错误为“InvalidStateError” 的 DOMException

  9. 返回一个围绕新 promise promisegatt-连接检查包装器,并并行运行以下步骤。

    1. 如果 globalnavigabletop-level traversable模拟蓝牙适配器 非空,则运行以下步骤:

      1. 如果this.[[automatedDescriptorWriteResponse]] 不为 "not-expected", 则在给定 global全局任务队列中,基于 Bluetooth 任务源 拒绝 promise,错误为“InvalidStateError” 的 DOMException,并中止这些步骤。

      2. 触发一个模拟的描述符事件,传入 globalnavigablethis.devicedescriptorwritebytes

      3. this.[[automatedDescriptorWriteResponse]] 设为 "expected", 并等待其改变。

      4. responsethis.[[automatedDescriptorWriteResponse]]

      5. this.[[automatedDescriptorWriteResponse]] 设为 "not-expected"

      6. 如果 response 不为 0,则执行以下子步骤:

        1. 在给定 global全局任务队列中,基于 Bluetooth 任务源 拒绝 promise,错误为“NetworkError” 的 DOMException,并中止这些步骤。

    2. 否则,运行以下步骤:

      1. 如果 UA 当前正在使用蓝牙系统,它可以在给定 global全局任务队列中,基于 Bluetooth 任务源 拒绝 promise,错误为“NetworkError” 的 DOMException,并中止这些步骤。

        实现可能能够避免该 NetworkError, 但目前站点需要串行化使用该 API,和/或为用户提供重试失败操作的方法。[Issue #188]

      2. 使用 Write Characteristic DescriptorsWrite Long Characteristic Descriptors 子流程将 bytes 写入 descriptor。错误按 § 6.7 错误处理进行处理。

    3. 在给定 globalBluetooth 任务源排队一个全局任务以执行以下步骤:

      1. 如果 promise 不在 gatt.[[activeAlgorithms]] 中,则拒绝 promise,错误为“NetworkError” 的 DOMException,并中止这些步骤。

      2. 如果上述子流程返回错误,则拒绝 promise 并附带该错误,中止这些步骤。

      3. this.value 设为一个新的 DataView,其封装了一个新的 ArrayBuffer,其中包含 bytes

      4. 解析 promise,其值为 undefined

6.6. 事件

6.6.1. Bluetooth 树

Bluetooth 树 指的是 navigator.bluetooth 以及实现 BluetoothDeviceBluetoothRemoteGATTServiceBluetoothRemoteGATTCharacteristicBluetoothRemoteGATTDescriptor 接口并参与到一棵树中的对象的统称。

6.6.2. 事件类型

advertisementreceived
当从该设备接收到广播事件时, 在 BluetoothDevice 上触发。
availabilitychanged
整个蓝牙系统对 UA 变为可用或不可用时, 在 navigator.bluetooth 上触发。
characteristicvaluechanged
BluetoothRemoteGATTCharacteristic 的值发生变化时触发, 可能源于一次 读取请求,或 值变更通知/指示
gattserverdisconnected
一个活动的 GATT 连接丢失时, 在 BluetoothDevice 上触发。
serviceadded
当一个新的 BluetoothRemoteGATTService 在远程设备上被发现后、刚被添加到 Bluetooth 树 时触发。
servicechanged
BluetoothRemoteGATTService状态发生变化时触发。 这包括服务中任何被添加或移除的特征值和/或描述符,以及来自远程设备的 Service Changed 指示。
serviceremoved
BluetoothRemoteGATTService 从其设备上被移除时触发,就在它从 Bluetooth 树中被删除之前。

6.6.3. 响应断开连接

当某个蓝牙设备 deviceATT 承载 丢失(例如远程设备离开范围,或用户使用平台功能将其断开)时,UA 必须针对每个 BluetoothDevice deviceObj,在给定 deviceObj相关全局对象的前提下,基于 Bluetooth 任务源 排队一个全局任务以执行以下步骤:
  1. 如果 deviceObj.[[representedDevice]]device 不是同一设备,则中止这些步骤。

  2. 如果 !deviceObj.gatt.connected, 则中止这些步骤。

  3. 清理已断开连接的设备 deviceObj

为了清理已断开连接的设备 deviceObj,UA 必须:
  1. deviceObj.gatt.connected 设为 false

  2. 清空 deviceObj.gatt.[[activeAlgorithms]]

  3. deviceObj.gatt.[[automatedGATTConnectionResponse]] 设为 "not-expected"

  4. contextdeviceObj.[[context]]

  5. context.[[attributeInstanceMap]] 中移除所有键位于 deviceObj.[[representedDevice]] 范围内的条目。

  6. 对于 deviceObjrealm 中的每个 BluetoothRemoteGATTService service,将 service.[[representedService]] 设为 null

  7. 对于 deviceObjrealm 中的每个 BluetoothRemoteGATTCharacteristic characteristic,执行以下子步骤:

    1. notificationContextscharacteristic.[[representedCharacteristic]]活动通知上下文集合

    2. notificationContexts 中移除 context

    3. 如果 notificationContexts 变为空,并且仍然存在到 deviceObj.[[representedDevice]]ATT 承载, 且 characteristic 具有 客户端特征值配置(Client Characteristic Configuration) 描述符,则 UA 应使用任意 特征值描述符流程清除 characteristic客户端特征值配置 描述符中的 NotificationIndication 位。

    4. characteristic.[[representedCharacteristic]] 设为 null

  8. 对于 deviceObjrealm 中的每个 BluetoothRemoteGATTDescriptor descriptor,将 descriptor.[[representedDescriptor]] 设为 null

  9. 触发一个事件,名称为 gattserverdisconnected, 其 bubbles 属性初始化为 true,在 deviceObj 上触发。

    注意:该事件不会BluetoothRemoteGATTServer 上触发。

6.6.4. 响应通知与指示

当 UA 收到蓝牙特征值通知(Characteristic Value Notification)指示(Indication)时, 必须执行以下步骤:
  1. 对于特征值的活动通知上下文集合中的每个 bluetoothGlobal,在给定 bluetoothGlobal相关全局对象的前提下,基于 Bluetooth 任务源 排队一个全局任务以执行以下子步骤:

    1. characteristicObject 为以 bluetoothGlobal 为根的 Bluetooth 树中,表示该 CharacteristicBluetoothRemoteGATTCharacteristic

    2. 如果 characteristicObject .service.device.gatt.connectedfalse,则中止这些子步骤。

    3. characteristicObject.value 设为一个新的 DataView, 其封装一个新的 ArrayBuffer, 其中保存该Characteristic 的新值。

    4. 触发一个事件,名称为 characteristicvaluechanged, 其 bubbles 属性初始化为 true,在 characteristicObject 上触发。

6.6.5. 响应服务变更

蓝牙的属性缓存(Attribute Caching)系统允许客户端跟踪 ServiceCharacteristicDescriptor 的变更。在为网页暴露这些属性之前,UA 必须订阅 存在的话,来自 Service Changed 特征值的指示(Indications)。当 UA 在 Service Changed 特征值上收到指示时,必须执行以下步骤。
  1. removedAttributes 为 Service Changed 特征值所指示范围内,UA 在该指示之前已发现的属性列表。

  2. 使用 主服务发现(Primary Service Discovery)关系发现(Relationship Discovery)特征值发现(Characteristic Discovery)特征值描述符发现(Characteristic Descriptor Discovery) 过程重新发现 Service Changed 特征值所指示范围内的属性。若 UA 能够证明跳过全部或部分范围的发现不会影响下面将触发的事件,则可以跳过。

  3. addedAttributes 为上一步中发现的属性列表。

  4. 如果在忽略特征值与描述符的值后,具有相同定义(参见 服务互操作性要求)的某个属性同时出现在 removedAttributesaddedAttributes 中,则将其从两者中移除。

    给定如下设备状态:
    状态 1
    • Service A
      • Characteristic C: value [1, 2, 3]
    • Service B
    状态 2
    • Service A
      • Characteristic C: value [3, 2, 1]
    • Service B
    状态 3
    • Service A
      • Characteristic D: value [3, 2, 1]
    • Service B
    状态 4
    • Service A
      • Characteristic C: value [1, 2, 3]
    • Service B
      • Include Service A

    从状态 1 到 2 的转换,使得服务 A 具有“相同的定义(忽略特征值与描述符的值)”,因此它会从 removedAttributesaddedAttributes 中被移除,也就不会触发任何 servicechanged 事件。

    从状态 1 到 3 的转换,使得服务 A 的定义不同,因为 服务定义 包含其特征值定义,所以它会同时保留在 removedAttributesaddedAttributes 中。随后在步骤 8,该服务被移动到 changedServices,从而只触发一个 servicechanged 事件,而不是同时触发 serviceaddedserviceremoved步骤 9 也会将服务 A 添加到 changedServices 中,因为特征值 C 被移除而特征值 D 被添加。

    从状态 1 到 4 的转换与 1→3 类似。服务 B 在 步骤 8 被移动到 changedServices, 但由于没有特征值或描述符发生变化,所以在 步骤 9 中不会被重复添加。

  5. invalidatedAttributesremovedAttributes 中但不在 addedAttributes 中的属性。

  6. 对于 UA 中的每个 环境设置对象 settings,在其 对应的事件循环排队一个任务以执行以下子步骤:

    1. 对于每个其相关设置对象settingsBluetoothRemoteGATTService service,若 service.[[representedService]] 位于 invalidatedAttributes 中,则将其设为 null

    2. 对于每个其相关设置对象settingsBluetoothRemoteGATTCharacteristic characteristic,若 characteristic.[[representedCharacteristic]] 位于 invalidatedAttributes 中,则将其设为 null

    3. 对于每个其相关设置对象settingsBluetoothRemoteGATTDescriptor descriptor,若 descriptor.[[representedDescriptor]] 位于 invalidatedAttributes 中,则将其设为 null

    4. globalsettings全局对象

    5. global.navigator.bluetooth.[[attributeInstanceMap]] 中移除每个表示位于 invalidatedAttributes 中的属性的条目。

  7. changedServices 为一个 Service 集合,初始为空。

  8. 如果某个相同Service 同时出现在 removedAttributesaddedAttributes 中,则将其从两者中移除,并添加到 changedServices 中。

  9. 对于 removedAttributesaddedAttributes 中的每个 CharacteristicDescriptor,将其从原列表中移除,并将其父 Service 添加到 changedServices 中。

    注意:从此之后,removedAttributesaddedAttributes 仅包含 Service
  10. 如果 addedAttributes 中的某个 Service 在之前的任一调用 getPrimaryServicegetPrimaryServicesgetIncludedServicegetIncludedServices(若当时已经存在)时都不会被返回,则 UA 可以从 addedAttributes 中移除该 Service

  11. changedDevices 为包含 removedAttributesaddedAttributeschangedServices 中任意 Service蓝牙设备 集合。

  12. 对于每个与 changedDevices 中的设备相连的 BluetoothDevice deviceObj,在给定其相关全局对象的前提下,基于 Bluetooth 任务源 排队一个全局任务以执行以下步骤:

    1. 对于 removedAttributes 中的每个 Service service

      1. 如果 deviceObj.[[allowedServices]]"all" 或包含该服务的 UUID,则在表示该 ServiceBluetoothRemoteGATTService 上,触发一个事件,名称为 serviceremoved, 其 bubbles 属性初始化为 true

      2. 将该 BluetoothRemoteGATTServiceBluetooth 树中移除。

    2. 对于 addedAttributes 中的每个 Service,如果 deviceObj.[[allowedServices]]"all" 或包含该服务的 UUID,则将表示该 ServiceBluetoothRemoteGATTService 添加到Bluetooth 树,随后 触发一个事件,名称为 serviceadded, 其 bubbles 属性初始化为 true,在该 BluetoothRemoteGATTService 上触发。

    3. 对于 changedServices 中的每个 Service,如果 deviceObj.[[allowedServices]]"all" 或包含该服务的 UUID,则 触发一个事件,名称为 servicechanged, 其 bubbles 属性初始化为 true,在表示该 ServiceBluetoothRemoteGATTService 上触发。

6.6.6. IDL 事件处理器

[SecureContext]
interface mixin CharacteristicEventHandlers {
  attribute EventHandler oncharacteristicvaluechanged;
};

oncharacteristicvaluechanged事件处理程序 IDL 属性,用于 characteristicvaluechanged 事件类型。

[SecureContext]
interface mixin BluetoothDeviceEventHandlers {
  attribute EventHandler onadvertisementreceived;
  attribute EventHandler ongattserverdisconnected;
};

onadvertisementreceived事件处理程序 IDL 属性,用于 advertisementreceived 事件类型。

ongattserverdisconnected事件处理程序 IDL 属性,用于 gattserverdisconnected 事件类型。

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

onserviceadded事件处理程序 IDL 属性,用于 serviceadded 事件类型。

onservicechanged事件处理程序 IDL 属性,用于 servicechanged 事件类型。

onserviceremoved事件处理程序 IDL 属性,用于 serviceremoved 事件类型。

6.7. 错误处理

注意:本节主要定义系统错误到 JavaScript 错误名称的映射,并允许 UA 重试某些操作。重试逻辑与可能的错误区分高度受操作系统约束,因此当这些要求与现实不符时,很可能是 规范缺陷(spec bugs) 而非浏览器缺陷。
当 UA 使用某个GATT 流程来执行算法中的某一步骤, 或处理对Bluetooth 缓存的查询(两者在此统称为“步骤”)时,如果该 GATT 流程返回 错误响应(Error Response),UA 必须执行以下步骤:
  1. 如果流程超时, 或 配置文件基础中描述的 ATT 承载缺失或因任何原因被终止, 则从该步骤返回一个 NetworkError, 并中止这些步骤。

  2. 根据 Error Code 的不同,采取如下动作:

    Invalid PDU
    Invalid Offset
    Attribute Not Found
    Unsupported Group Type
    这些错误码表明在协议层发生了意外情况,可能是 UA 或设备的缺陷。从该步骤返回 NotSupportedError
    Invalid Handle
    从该步骤返回 InvalidStateError
    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 流程是写入操作(Write),则从该步骤返回 InvalidModificationError; 否则,从该步骤返回 NotSupportedError
    Read Not Permitted
    Write Not Permitted
    Request Not Supported
    Prepare Queue Full
    Insufficient Resources
    Unlikely Error
    其他任何情况
    从该步骤返回 NotSupportedError

7. UUID

typedef DOMString UUID;

UUID 字符串表示一个 128 位的 [RFC4122] UUID。 有效 UUID 是一个匹配 [ECMAScript] 正则表达式 /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ 的字符串。 即,有效 UUID 必须为小写,且不使用蓝牙标准定义的 16 位或 32 位缩写。 本规范中函数与属性返回的所有 UUID 必须是有效 UUID。 如果本规范中的某个函数的参数类型为 UUID 或包含 UUID 属性的字典,而传入的任何 UUID 槽位不是有效 UUID, 则该函数必须返回一个被拒绝的 promise, 错误为 TypeError,并中止其余步骤。

注意:该标准提供 BluetoothUUID.canonicalUUID(alias) 函数,用于将 16 位或 32 位的蓝牙UUID 别名映射为其 128 位形式。
注意:蓝牙设备在比较 UUID(如 属性类型 中所述)之前,需要将 16 位与 32 位 UUID 转换为 128 位 UUID, 但并非所有设备都会这么做。为与这些设备互操作,若 UA 从设备收到某种形式(16、32 或 128 位)的 UUID,则应以相同形式将该 UUID 的其他别名发送回设备。

7.1. 标准化 UUID

蓝牙 SIG 在 [BLUETOOTH-ASSIGNED] 维护一个用于标识服务、特征值、描述符及其他实体的 UUID 注册表。本节为脚本提供按名称查找这些 UUID 的方式,从而无需在每个应用中重复。

有效名称 是一个匹配 [ECMAScript] 正则表达式 /^[a-z0-9_-.]+$/ 的字符串。

[Exposed=Window]
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 别名 alias 所表示的 128 位 UUID

注意:该算法相当于将 "00000000-0000-1000-8000-00805f9b34fb" 的最高 32 位替换为别名的比特。 例如,canonicalUUID(0xDEADBEEF) 返回 "deadbeef-0000-1000-8000-00805f9b34fb"

BluetoothServiceUUID 表示 16 位与 32 位 UUID 别名、 有效 UUID 以及来自 GATT 指定服务键的 有效名称;或等价地,表示不会导致 BluetoothUUID.getService() 抛出异常的取值。

BluetoothCharacteristicUUID 表示 16 位与 32 位 UUID 别名、 有效 UUID 以及来自 GATT 指定特征值键的 有效名称;或等价地,表示不会导致 BluetoothUUID.getCharacteristic() 抛出异常的取值。

BluetoothDescriptorUUID 表示 16 位与 32 位 UUID 别名、 有效 UUID 以及来自 GATT 指定描述符键的 有效名称;或等价地,表示不会导致 BluetoothUUID.getDescriptor() 抛出异常的取值。

为了ResolveUUIDName(name, GATT assigned numbers),UA 必须执行以下步骤:
  1. 如果 nameunsigned long,则返回 BluetoothUUID.canonicalUUID(name),并中止这些步骤。

  2. 如果 name有效 UUID, 则返回 name 并中止这些步骤。

  3. 如果 name有效名称, 且在 GATT assigned numbers 中映射到一个有效 UUID, 则令 alias 为其指定编号,并返回 BluetoothUUID.canonicalUUID(alias)。

  4. 否则,抛出 TypeError

当调用静态方法 BluetoothUUID. getService(name) 时,必须返回 ResolveUUIDName(nameGATT 指定服务)。

当调用静态方法 BluetoothUUID. getCharacteristic(name) 时,必须返回 ResolveUUIDName(nameGATT 指定特征值)。

当调用静态方法 BluetoothUUID. getDescriptor(name) 时,必须返回 ResolveUUIDName(nameGATT 指定描述符)。

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

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

BluetoothUUID.getService("unknown-service") 会抛出 TypeError

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

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

7.2. GATT 指定编号

本规范为 GATT 指定编号提供了便于阅读的人类可读名称,以提升使用标准化 GATT 服务、特征值与描述符的开发者的可读性。GATT 指定编号文件存放在 https://github.com/WebBluetoothCG/registries 仓库中。

本规范先前使用由 Bluetooth SIG 提供的映射表来定义这些人类可读名称。当发现该表不再维护后,映射表被直接写入规范中进行定义。
在 URL url 处,解析 GATT 指定编号 的结果是一个从有效名称有效 UUID的映射,或一个错误,由以下算法产生:
  1. 获取 url,并令 contents 为其主体,以 UTF-8 解码。

  2. lines 为将 contents'\n' 分割后的结果。

  3. result 为空映射。

  4. lines 中的每一行 line,执行以下子步骤:

    1. 如果 line 为空或其首字符是 '#',继续到下一行。

    2. 如果 line 由一个有效名称、一个空格(U+0020)以及一个有效 UUID构成,则令 name 为该名称,令 uuid 为该 UUID。

    3. 否则,返回错误并中止这些步骤。

    4. 如果 name 已存在于 result 中,返回错误并中止这些步骤。

    5. result 中添加从 nameuuid 的映射。

  5. 返回 result

GATT 指定服务是在 解析 GATT 指定编号 https://github.com/WebBluetoothCG/registries/blob/master/gatt_assigned_services.txt 的结果。 UA 应定期重新获取该文件,但频率未作规定。

GATT 指定特征值是在 解析 GATT 指定编号 https://github.com/WebBluetoothCG/registries/blob/master/gatt_assigned_characteristics.txt 的结果。 UA 应定期重新获取该文件,但频率未作规定。

GATT 指定描述符是在 解析 GATT 指定编号 https://github.com/WebBluetoothCG/registries/blob/master/gatt_assigned_descriptors.txt 的结果。 UA 应定期重新获取该文件,但频率未作规定。

8. 广告数据过滤器

广告数据过滤器表示一种匹配制造商数据或服务数据的方式。

要从字符串 input 解析广告数据过滤器,执行以下步骤:
  1. wordsinput严格分割后的结果,分隔符为 /

  2. 如果 words 的长度不等于 2,返回错误并中止这些步骤。

  3. 如果 words[0] 的长度不等于 words[1] 的长度,返回错误并中止这些步骤。

  4. prefixDatawords[0]。

  5. prefixMaskwords[1]。

  6. 如果 prefixDataprefixMask 不是小写 ASCII 十六进制数字序列,返回错误。

  7. prefixIndex0

  8. dataList 为空列表。

  9. maskList 为空列表。

  10. prefixIndex 小于 prefixData 的长度时,执行以下子步骤:

    1. data 为将 prefixData 中索引 prefixIndexprefixIndex + 1 处的字符解释为十六进制数的结果。

    2. mask 为将 prefixMask 中索引 prefixIndexprefixIndex + 1 处的字符解释为十六进制数的结果。

    3. data 追加到 dataList

    4. mask 追加到 maskList

    5. prefixIndex 设为 |prefixIndex| + 2

  11. result 为一个新的 BluetoothDataFilterInit 字典。

  12. result[dataPrefix] 设为以 dataList 构造的 Uint8Array

  13. result[mask] 设为以 maskList 构造的 Uint8Array

  14. 返回 result

9. 阻止列表

本规范依赖 https://github.com/WebBluetoothCG/registries 仓库中的阻止列表文件,以限制网站可访问的 GATT 属性与制造商数据的集合。

有效公司标识符字符串小写 ASCII 十六进制数字的序列,其长度大于 0 且小于 5。公司标识符的官方列表可在 Bluetooth Assigned Numbers 网站找到。

在 URL url 处,解析制造商数据阻止列表 的结果是从公司标识符代码到 BluetoothDataFilterInit 列表的映射,或一个错误,由以下算法产生:
  1. 获取 url,并令 contents 为其主体,以 UTF-8 解码。

  2. lines 为在 contents 上调用 split(separator, limit) 且分隔符为 '\n' 的结果。

  3. result 为空映射。

  4. lines 中的每一行 line,执行以下子步骤:

    1. 如果 line 为空或其首字符为 '#',继续到下一行。

    2. regExp 为以 'manufacturer\ ([0-9a-f]+)\ ([0-9a-f]+\/[0-9a-f]+)' 构造的 RegExp

    3. matchResult 为调用 regExp.exec(string)line 的结果;若 matchResultnull 或其长度不等于 3,则返回错误。

    4. matchResult[1] 是有效公司标识符字符串,则令 companyIdentifierStrmatchResult[1];否则返回错误。

    5. companyIdentifier 为将 companyIdentifierStr 解释为十六进制数的结果。

    6. dataPrefixStrmatchResult[2]。

    7. 如果 companyIdentifier 不在 result 中,则设 result[companyIdentifier] 为一个空列表。

    8. 若不是错误,则令 dataFilter 为在 dataPrefixStr解析广告数据过滤器的结果;否则返回错误。

    9. dataFilter 追加到 result[companyIdentifier]。

  5. 返回 result

在 URL url 处,解析 GATT 阻止列表 的结果是从有效 UUID到标记的映射,或一个错误,由以下算法产生:
  1. 获取 url,并令 contents 为其主体,以 UTF-8 解码。

  2. lines 为将 contents'\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. result 中添加从 uuidtoken 的映射。

  5. 返回 result

GATT 阻止列表 是在 解析 GATT 阻止列表 https://github.com/WebBluetoothCG/registries/blob/master/gatt_blocklist.txt 的结果。 制造商数据阻止列表 是在 解析制造商数据阻止列表 https://github.com/WebBluetoothCG/registries/blob/master/manufacturer_data_blocklist.txt 的结果。 UA 应定期重新获取阻止列表,但频率未作规定。

UUIDGATT 阻止列表 值为错误,或该 UUID 在 GATT 阻止列表 中映射到 "exclude",则该 UUID 为 被阻止

UUIDGATT 阻止列表 值为错误,或该 UUID 在 GATT 阻止列表 中映射到 "exclude" 或 "exclude-reads",则该 UUID 被阻止读取

UUIDGATT 阻止列表 值为错误,或该 UUID 在 GATT 阻止列表 中映射到 "exclude" 或 "exclude-writes",则该 UUID 被阻止写入

当以下步骤返回 blocked 时,制造商数据 manufacturerData被阻止的制造商数据
  1. 如果 制造商数据阻止列表 的值为错误,返回 blocked

  2. manufacturerBlocklist制造商数据阻止列表 的值。

  3. companyIdentifiermanufacturerData 的公司标识符。

  4. 如果 companyIdentifier 不在 manufacturerBlocklist 中,返回 unblocked

  5. manufacturerBlocklist[companyIdentifier] 中的每个 dataFilter,执行以下子步骤:

    1. 如果 manufacturerData 的广告数据匹配 dataFilter,返回 blocked

  6. 返回 unblocked

当以下步骤返回 blocked 时,制造商数据过滤器 manufacturerDataFilter被阻止的制造商数据过滤器
  1. 如果 制造商数据阻止列表 的值为错误,返回 blocked

  2. manufacturerBlocklist制造商数据阻止列表 的值。

  3. companyIdentifiermanufacturerDataFilter["companyIdentifier"]。

  4. 如果 companyIdentifier 不在 manufacturerBlocklist 中,返回 unblocked

  5. manufacturerBlocklist[companyIdentifier] 中的每个 dataFilter,执行以下子步骤:

    1. 如果 manufacturerDataFilter严格子集dataFilter,则返回 blocked

  6. 返回 unblocked

[SecureContext]
partial interface Navigator {
  [SameObject]
  readonly attribute Bluetooth bluetooth;
};

每个 Navigator 都有一个关联的 Bluetooth,它是一个 Bluetooth 对象。在创建 Navigator 对象时,其关联的 Bluetooth必须被设为在该 Navigator 对象的相关领域中创建的新 Bluetooth 对象。

Navigatorbluetooth 取值步骤为返回 this关联的 Bluetooth

11. 集成

11.1. 权限策略

本规范定义了一个策略控制的特性,其标识符为 标记 "bluetooth", 用于控制是否允许 bluetooth 属性所暴露的方法在 Navigator 对象上被使用。

该特性的默认允许列表["self"]

12. 自动化测试

就用户代理自动化与应用测试而言,本文档对 [WebDriver-BiDi] 规范进行了扩展。

Web Bluetooth API 及其扩展规范给测试作者带来挑战,因为要充分验证这些接口需要能以可预期方式响应的物理硬件设备。为解决这一挑战,本文档定义了一系列 WebDriver-BiDi 扩展命令,用于定义与控制模拟的外设与广播,使其表现如同物理设备外设及其广播。这些模拟外设与广播代表具有特定属性的设备,其读数可由用户完全定义。

每个顶层可遍历对象可以有一个模拟 Bluetooth 适配器,即软件定义的 Bluetooth 适配器,拥有一组已发现的模拟 Bluetooth 设备,并可承担如 Central 等角色。

每个模拟 Bluetooth 适配器都有一个模拟 Bluetooth 设备映射,它是从 Bluetooth 地址 字符串模拟 Bluetooth 设备有序映射

每个模拟 Bluetooth 适配器有一个适配器状态,是描述适配器当前状态的字符串枚举。可用的枚举值为:

每个模拟 Bluetooth 适配器有一个低功耗支持状态,这是一个布尔值,描述该适配器是否支持 Bluetooth Low Energy。

模拟 Bluetooth 设备是软件定义的Bluetooth 设备,其行为类似物理设备,可附加到模拟 Bluetooth 适配器, 可具有如制造商特定数据服务 UUID等关联属性, 并具有一个模拟 GATT 服务映射,它是从 Bluetooth UUID 字符串到模拟 GATT 服务有序映射

模拟 GATT 服务是软件定义的Service,属于一个 模拟 Bluetooth 设备,具有 UUID 属性,在 Bluetooth 缓存中为已知存在,并具有一个模拟 GATT 特征值映射, 它是从 Bluetooth UUID 字符串到模拟 GATT 特征值有序映射

模拟 GATT 特征值是软件定义的Characteristic,属于一个 模拟 GATT 服务,具有 UUID 属性与特征值属性属性, 在 Bluetooth 缓存中为已知存在,并具有一个模拟 GATT 描述符映射, 它是从 Bluetooth UUID 字符串到模拟 GATT 描述符有序映射

模拟 GATT 特征值属性是软件定义的特征值属性,属于一个 模拟 GATT 特征值,并在 Bluetooth 缓存中为已知存在。

模拟 GATT 描述符是软件定义的Descriptor,属于一个 模拟 GATT 特征值,具有 UUID 属性,并在 Bluetooth 缓存中为已知存在。

CDDL 片段使用 “text” 类型而不是 “browsingContext.BrowsingContext”,以便对 CDDL 片段进行独立的程序化处理。目前,不能引用其他模块。

12.1. 定义

bluetooth.BluetoothUuid = text;
bluetooth.BluetoothManufacturerData = { key: uint, data: tstr };
bluetooth.CharacteristicProperties = {
  ? broadcast: bool,
  ? read: bool,
  ? writeWithoutResponse: bool,
  ? write: bool,
  ? notify: bool,
  ? indicate: bool,
  ? authenticatedSignedWrites: bool,
  ? extendedProperties: bool
}
key
公司标识符代码(Company Identifier Code)。
data
制造商数据的字节序列,以 base64 编码。

12.2. bluetooth 模块

bluetooth 模块包含用于管理远端 Bluetooth 行为的命令。

12.2.1. 类型

12.2.1.1. bluetooth.RequestDevice 类型
bluetooth.RequestDevice = text

bluetooth.RequestDevice 是请求设备提示框中单个设备的标识符。

设备提示是一个由设备提示 id(字符串)与设备集合(由 集合构成,元素为 BluetoothDevice 对象)的元组。它表示一个允许用户选择Bluetooth 设备的提示。

12.2.1.2. bluetooth.RequestDeviceInfo 类型
bluetooth.RequestDeviceInfo = {
   id: bluetooth.RequestDevice,
   name: text / null,
}

bluetooth.RequestDeviceInfo 表示请求设备提示框中的单个设备。

给定 BluetoothDevice device序列化设备
  1. iddevice.id

  2. namedevice.name

  3. 返回一个匹配 bluetooth.RequestDeviceInfo 产生式的映射,其中 "id" 设为 id"name" 设为 name

12.2.1.3. bluetooth.RequestDevicePrompt 类型
bluetooth.RequestDevicePrompt = text

bluetooth.RequestDevicePrompt 是单个提示框的标识符。

远端拥有一个可导航到设备提示的映射,它是一个映射,键为可导航 id,值为设备提示

给定 navigableIdpromptId获取一个提示
  1. promptMap可导航到设备提示的映射

  2. 如果 promptMap[navigableId]不存在

    1. 返回错误,其错误码no such prompt

  3. prompt可导航到设备提示的映射[navigableId]。

  4. 如果 prompt设备提示 id不是 promptId

    1. 返回错误,其错误码no such prompt

  5. 返回成功,其数据为 prompt

给定设备提示 promptdeviceId在提示中匹配设备
  1. prompt设备集合中的每个 device

    1. 如果 device.id 等于 deviceId,则返回成功,其数据为 device

  2. 否则:

    1. 返回错误,其错误码no such device

给定设备提示 prompt序列化提示中的设备
  1. devices 为空列表

  2. prompt设备集合中的每个 device

    1. 追加 序列化 device 的结果到 devices

  3. 返回 devices

12.2.1.4. bluetooth.ScanRecord 类型
bluetooth.ScanRecord = {
  ? name: text,
  ? uuids: [ * bluetooth.BluetoothUuid ],
  ? appearance: number,
  ? manufacturerData: [ * bluetooth.BluetoothManufacturerData ],
}

bluetooth.ScanRecord 表示由Bluetooth 设备发送的广播包数据。

name
Bluetooth 设备的本地名称或其前缀。
uuids
列出该扫描记录所指示的Bluetooth 设备的 GATT 服务器所支持的服务 UUID。
appearance
Appearance,对应于 gap.appearance 特征值定义的值之一。
manufacturerData
BluetoothManufacturerData 的列表,将 unsigned short 的公司标识符代码映射到以 base64 编码的制造商数据字节序列

12.2.2. 错误

本规范在 WebDriver BiDi错误码集合基础上,扩展了以下附加错误码:

no such device
尝试引用未知的 BluetoothDevice
no such prompt
尝试引用未知的设备提示

12.2.3. 命令

BluetoothCommand = (
  bluetooth.HandleRequestDevicePrompt //
  bluetooth.SimulateAdapter //
  bluetooth.DisableSimulation //
  bluetooth.SimulatePreconnectedPeripheral //
  bluetooth.SimulateAdvertisement //
  bluetooth.SimulateGattConnectionResponse //
  bluetooth.SimulateGattDisconnection //
  bluetooth.SimulateService //
  bluetooth.SimulateCharacteristic //
  bluetooth.SimulateCharacteristicResponse //
  bluetooth.SimulateDescriptor //
  bluetooth.SimulateDescriptorResponse
)
12.2.3.1. bluetooth.handleRequestDevicePrompt 命令
bluetooth.HandleRequestDevicePrompt = (
   method: "bluetooth.handleRequestDevicePrompt",
   params: bluetooth.HandleRequestDevicePromptParameters,
)

bluetooth.HandleRequestDevicePromptParameters = {
   context: text,
   prompt: bluetooth.RequestDevicePrompt,
   (
       bluetooth.HandleRequestDevicePromptAcceptParameters //
       bluetooth.HandleRequestDevicePromptCancelParameters
   )
}

bluetooth.HandleRequestDevicePromptAcceptParameters = (
   accept: true,
   device: bluetooth.RequestDevice,
)

bluetooth.HandleRequestDevicePromptCancelParameters = (
   accept: false,
)
具有 command parameters远端步骤(remote end steps)为:
  1. contextIdparams["context"]。

  2. promptIdparams["prompt"]。

  3. prompt尝试获取一个提示时,给定 contextIdpromptId 的结果。

  4. acceptcommand parametersaccept 字段之值。

  5. 如果 accept 为 true:

    1. deviceIdcommand parametersdevice 字段之值。

    2. device尝试在给定 promptdeviceId 的情况下在提示中匹配设备的结果。

    3. device 确认 prompt

  6. 否则:

    1. 取消 prompt

  7. 返回携带数据 null成功

一个本地端(local end)可以通过发送如下消息来关闭一个提示:
{
  "method": "bluetooth.handleRequestDevicePrompt",
  "params": {
    "context": "cxt-d03fdd81",
    "prompt": "pmt-e0a234b",
    "accept": true,
    "device": "dvc-9b3b872"
  }
}
12.2.3.2. bluetooth.simulateAdapter 命令
bluetooth.SimulateAdapter = (
   method: "bluetooth.simulateAdapter",
   params: bluetooth.SimulateAdapterParameters,
)

bluetooth.SimulateAdapterParameters = {
   context: text,
   ? leSupported: bool,
   state: "absent" / "powered-off" / "powered-on"
}
具有命令参数 params远端步骤(remote end steps)为:
  1. contextIdparams["context"]。

  2. navigable尝试contextId获取一个可导航对象的结果。

  3. 如果 navigable 不是顶层可遍历对象,则返回错误,其错误码invalid argument

  4. simulatedBluetoothAdapternavigable模拟 Bluetooth 适配器

  5. 如果 simulatedBluetoothAdapter 为空,执行以下步骤:

    1. 如果 params["leSupported"]不存在,则将 params["leSupported"] 设为 true

    2. simulatedBluetoothAdapter 为新的模拟 Bluetooth 适配器

    3. simulatedBluetoothAdapterLE 支持状态设为 params["leSupported"]。

    4. simulatedBluetoothAdapter适配器状态设为 params["state"]。

    5. navigable模拟 Bluetooth 适配器设为 simulatedBluetoothAdapter

    6. 返回携带数据 null成功

  6. 如果 simulatedBluetoothAdapter 不为空,执行以下步骤:

    1. 如果 params["leSupported"]存在,则返回错误,其错误码invalid argument

    2. simulatedBluetoothAdapter适配器状态设为 params["state"]。

    3. 返回携带数据 null成功

一个本地端(local end)可以通过发送如下消息来模拟一个支持 Bluetooth Low Energy 的适配器:
{
  "method": "bluetooth.simulateAdapter",
  "params": {
    "context": "cxt-d03fdd81",
    "leSupported": true,
    "state": "powered-on",
  }
}
一个本地端(local end)可以通过发送如下消息来更新现有适配器的适配器状态
{
  "method": "bluetooth.simulateAdapter",
  "params": {
    "context": "cxt-d03fdd81",
    "state": "powered-off",
  }
}
12.2.3.3. bluetooth.disableSimulation 命令
bluetooth.DisableSimulation = (
   method: "bluetooth.disableSimulation",
   params: bluetooth.DisableSimulationParameters,
)

bluetooth.DisableSimulationParameters = {
   context: text
}
具有命令参数 params远端步骤(remote end steps)为:
  1. contextIdparams["context"]。

  2. navigable尝试contextId获取一个可导航对象的结果。

  3. 如果 navigable 不是顶层可遍历对象,则返回错误,其错误码invalid argument

  4. navigable模拟 Bluetooth 适配器设为空。

  5. 返回携带数据 null成功

一个本地端(local end)可以通过发送如下消息来禁用现有的模拟:
{
  "method": "bluetooth.disableSimulation",
  "params": {
    "context": "cxt-d03fdd81"
  }
}
12.2.3.4. bluetooth.simulatePreconnectedPeripheral 命令
bluetooth.SimulatePreconnectedPeripheral = (
   method: "bluetooth.simulatePreconnectedPeripheral",
   params: bluetooth.SimulatePreconnectedPeripheralParameters,
)

bluetooth.SimulatePreconnectedPeripheralParameters = {
   context: text,
   address: text,
   name: text,
   manufacturerData: [ * bluetooth.BluetoothManufacturerData ],
   knownServiceUuids: [ * bluetooth.BluetoothUuid ]
}
具有命令参数 params远端步骤(remote end steps)为:
  1. contextId 为 params["context"]。

  2. navigable尝试contextId获取一个可导航对象的结果。

  3. 如果 navigable 不是顶层可遍历对象,则返回错误,其错误码invalid argument

  4. simulatedBluetoothAdapternavigable模拟 Bluetooth 适配器

  5. 如果 simulatedBluetoothAdapter 为空,则返回错误,其错误码invalid argument

  6. deviceAddressparams["address"]。

  7. deviceMappingsimulatedBluetoothAdapter模拟 Bluetooth 设备映射

  8. 如果 deviceMapping[deviceAddress]存在,则返回错误,其错误码invalid argument

  9. simulatedBluetoothDevice 为新的模拟 Bluetooth 设备

  10. simulatedBluetoothDevice 的名称设为 params["name"]。

  11. simulatedBluetoothDevice 的地址设为 params["address"]。

  12. simulatedBluetoothDevice制造商特定数据设为对 params["manufacturerData"] 执行宽松 base64 解码(forgiving-base64 decode)的输出。

  13. simulatedBluetoothDevice服务 UUID设为 params["knownServiceUuids"]。

  14. deviceMapping[deviceAddress] 设为 simulatedBluetoothDevice

  15. 返回携带数据 null成功

一个本地端(local end)可以通过发送如下消息来模拟一个已预连接的外设:
{
  "method": "bluetooth.simulatePreconnectedPeripheral",
  "params": {
    "context": "cxt-d03fdd81",
    "address": "09:09:09:09:09:09",
    "name": "Some Device",
    "manufacturerData": [ { key: 17, data: "AP8BAX8=" } ],
    "knownServiceUuids": [
      "12345678-1234-5678-9abc-def123456789",
    ],
  }
}
12.2.3.5. bluetooth.simulateAdvertisement 命令
bluetooth.SimulateAdvertisement = (
   method: "bluetooth.simulateAdvertisement",
   params: bluetooth.SimulateAdvertisementParameters,
)

bluetooth.SimulateAdvertisementParameters = {
   context: text,
   scanEntry: bluetooth.SimulateAdvertisementScanEntryParameters
}

bluetooth.SimulateAdvertisementScanEntryParameters = {
   deviceAddress: text,
   rssi: number,
   scanRecord: bluetooth.ScanRecord
}

具有命令参数 params远端步骤(remote end steps)为:
  1. contextIdparams["context"]。

  2. topLevelNavigable尝试contextId获取一个可导航对象的结果。

  3. 如果 topLevelNavigable 不是顶层可遍历对象,则返回错误,其错误码invalid argument

  4. scanEntryparams["scanEntry"]。

  5. deviceAddressscanEntry["deviceAddress"]。

  6. simulatedBluetoothAdaptertopLevelNavigable模拟 Bluetooth 适配器

  7. 如果 simulatedBluetoothAdapter 为空,则返回错误,其错误码invalid argument

  8. deviceMappingsimulatedBluetoothAdapter模拟 Bluetooth 设备映射

  9. 如果 deviceMapping[deviceAddress]存在,则令 simulatedDevicedeviceMapping[deviceAddress]。否则,令 simulatedDevice 为带有 deviceAddress 的新的模拟 Bluetooth 设备,并将 deviceMapping[deviceAddress] 设为 simulatedDevice

  10. 如果 topLevelNavigable 目前正在执行扫描设备(scan for devices)算法, 则将 simulatedDevice 插入该算法中的 simulatedBluetoothDevices 变量。

    从另一个算法向变量中插入数据的规范性定义尚不明确。 扫描设备算法需要定义异步设备发现,以匹配实现行为。

  11. navigablestopLevelNavigable包含式后代可导航对象集合, 基于其活动文档

  12. navigables 中的每个 navigable

    1. documentnavigable活动文档

    2. document相关设置对象负责事件循环排队一个任务以执行以下子步骤:

      1. simulatedDeviceInstance 为在 navigable活动窗口关联 Navigator关联 Bluetooth内, 获取表示该 BluetoothDevice simulatedDevice 的结果。

      2. 如果 simulatedDeviceInstance.[[watchAdvertisementsState]]not-watching,则中止这些子步骤。

      3. 为由 scanEntry["scanRecord"] 表示的广播事件,触发一个 advertisementreceived 事件,目标为 simulatedDeviceInstance

  13. 返回携带数据 null成功

一个本地端(local end)可以通过发送如下消息来模拟一个设备广播:
{
  "method": "bluetooth.simulateAdvertisement",
  "params": {
    "context": "cxt-d03fdd81",
    "scanEntry": {
      "deviceAddress": "08:08:08:08:08:08",
      "rssi": -10,
      "scanRecord": {
        "name": "Heart Rate",
        "uuids": ["0000180d-0000-1000-8000-00805f9b34fb"],
        "manufacturerData": [ { key: 17, data: "AP8BAX8=" } ],
        "appearance": 1,
        "txPower": 1
      }
    }
  }
}
12.2.3.6. bluetooth.simulateGattConnectionResponse 命令
bluetooth.SimulateGattConnectionResponse = (
   method: "bluetooth.simulateGattConnectionResponse",
   params: bluetooth.SimulateGattConnectionResponseParameters,
)

bluetooth.SimulateGattConnectionResponseParameters = {
   context: text,
   address: text,
   code: uint
}
具有命令参数 params远端步骤(remote end steps)为:
  1. contextIdparams["context"]。

  2. navigable尝试contextId获取一个可导航对象的结果。

  3. deviceAddressparams["address"]。

  4. simulatedBluetoothAdapternavigable模拟 Bluetooth 适配器

  5. 如果 simulatedBluetoothAdapter 为空,则返回错误,其错误码invalid argument

  6. deviceMappingsimulatedBluetoothAdapter模拟 Bluetooth 设备映射

  7. 如果 deviceMapping[deviceAddress]存在,令 simulatedDevicedeviceMapping[deviceAddress]; 否则,返回错误,其错误码invalid argument

  8. simulatedDeviceInstance 为在 navigable活动窗口关联 Navigator关联 Bluetooth内, 获取表示该 BluetoothDevice simulatedDevice 的结果。

  9. 如果 simulatedDeviceInstance.[[gatt]].[[automatedGATTConnectionResponse]]"expected", 则将 simulatedDeviceInstance.[[gatt]].[[automatedGATTConnectionResponse]] 设为 params["code"]。

  10. 否则,返回错误,其错误码invalid element state

一个本地端(local end)可以通过发送如下消息来模拟设备 GATT 连接成功的响应(依据错误码列表,错误码为 0x00):
{
  "method": "bluetooth.simulateGattConnectionResponse",
  "params": {
    "context": "cxt-d03fdd81",
    "address": "09:09:09:09:09:09",
    "code": 0
  }
}
12.2.3.7. bluetooth.simulateGattDisconnection 命令
bluetooth.SimulateGattDisconnection = (
   method: "bluetooth.simulateGattDisconnection",
   params: bluetooth.SimulateGattDisconnectionParameters,
)

bluetooth.SimulateGattDisconnectionParameters = {
   context: text,
   address: text,
}
具有命令参数 params远端步骤为:
  1. contextIdparams["context"]。

  2. navigable尝试contextId获取一个可导航对象的结果。

  3. deviceAddressparams["address"]。

  4. simulatedBluetoothAdapternavigable模拟 Bluetooth 适配器

  5. simulatedBluetoothAdapter 为空,返回错误,其错误码invalid argument

  6. deviceMappingsimulatedBluetoothAdapter模拟 Bluetooth 设备映射

  7. deviceMapping[deviceAddress]存在,令 simulatedDevicedeviceMapping[deviceAddress];否则,返回错误,其错误码invalid argument

  8. simulatedDeviceInstance 为在 navigable活动窗口关联 Navigator关联 Bluetooth 内,获取表示 simulatedDeviceBluetoothDevice 的结果。

  9. simulatedDeviceInstance.[[gatt]].[[automatedGATTConnectionResponse]]"expected",则将 simulatedDeviceInstance.[[gatt]].[[automatedGATTConnectionResponse]] 设为 0x15

    根据 错误码列表(List of Error Codes)0x15 表示 "Remote Device Terminated Connection due to Power Off"。这模拟了设备因断电而无法响应 GATT 连接尝试的场景。
  10. 否则,清理已断开连接的设备 simulatedDeviceInstance

一个本地端可以通过发送如下消息来模拟设备的 GATT 断开连接:
{
  "method": "bluetooth.simulateGattDisconnection",
  "params": {
    "context": "cxt-d03fdd81",
    "address": "09:09:09:09:09:09",
  }
}
12.2.3.8. bluetooth.simulateService 命令
bluetooth.SimulateService = (
   method: "bluetooth.simulateService",
   params: bluetooth.SimulateServiceParameters,
)

bluetooth.SimulateServiceParameters = {
   context: text,
   address: text,
   uuid: bluetooth.BluetoothUuid,
   type: "add" / "remove",
}
具有命令参数 params远端步骤为:
  1. contextIdparams["context"]。

  2. navigable尝试contextId获取一个可导航对象的结果。

  3. deviceAddressparams["address"]。

  4. simulatedBluetoothAdapternavigable模拟 Bluetooth 适配器

  5. simulatedBluetoothAdapter 为空,返回错误,其错误码invalid argument

  6. deviceMappingsimulatedBluetoothAdapter模拟 Bluetooth 设备映射

  7. deviceMapping[deviceAddress]存在,令 simulatedDevicedeviceMapping[deviceAddress]。

  8. 否则,返回错误,其错误码invalid argument

  9. simulatedDeviceInstance 为在 navigable活动窗口关联 Navigator关联 Bluetooth 内,获取表示 simulatedDeviceBluetoothDevice 的结果。

  10. serviceMappingsimulatedDevice模拟 GATT 服务映射

  11. uuidparams["uuid"]。

  12. params["type"] 为 "add"

    1. serviceMapping[uuid]存在,返回错误,其错误码invalid element state

    2. simulatedGattService 为新的 模拟 GATT 服务

    3. simulatedGattServiceUUID 设为 uuid

    4. serviceMapping[uuid] 设为 simulatedGattService

    5. 创建表示 simulatedGattServiceBluetoothRemoteGATTService,并在 simulatedDeviceInstance.[[context]].[[attributeInstanceMap]] 中添加从 simulatedGattService 到所得 Promise 的映射。

    6. 返回携带数据 null成功

  13. params["type"] 为 "remove"

    1. serviceMapping[uuid]存在,令 simulatedGattServiceserviceMapping[uuid]。

    2. 否则,返回错误,其错误码invalid element state

    3. simulatedDeviceInstance.[[context]].[[attributeInstanceMap]] 中移除 simulatedGattService

    4. serviceMapping 中移除 uuid

    5. 返回携带数据 null成功

  14. 返回错误,其错误码invalid argument

一个本地端可以通过发送如下消息来模拟添加一个 GATT 服务:
{
  "method": "bluetooth.simulateService",
  "params": {
    "context": "cxt-d03fdd81",
    "address": "09:09:09:09:09:09",
    "uuid": "0000180d-0000-1000-8000-00805f9b34fb",
    "type": "add"
  }
}
一个本地端可以通过发送如下消息来模拟移除一个 GATT 服务:
{
  "method": "bluetooth.simulateService",
  "params": {
    "context": "cxt-d03fdd81",
    "address": "09:09:09:09:09:09",
    "uuid": "0000180d-0000-1000-8000-00805f9b34fb",
    "type": "remove"
  }
}
12.2.3.9. bluetooth.simulateCharacteristic 命令
bluetooth.SimulateCharacteristic = (
   method: "bluetooth.simulateCharacteristic",
   params: bluetooth.SimulateCharacteristicParameters,
)
bluetooth.SimulateCharacteristicParameters = {
   context: text,
   address: text,
   serviceUuid: bluetooth.BluetoothUuid,
   characteristicUuid: bluetooth.BluetoothUuid,
   ? characteristicProperties: bluetooth.CharacteristicProperties,
   type: "add" / "remove"
}
具有命令参数 params远端步骤为:
  1. contextIdparams["context"]。

  2. navigable尝试contextId获取一个可导航对象的结果。

  3. deviceAddressparams["address"]。

  4. simulatedBluetoothAdapternavigable模拟 Bluetooth 适配器

  5. simulatedBluetoothAdapter 为空,返回错误,其错误码invalid argument

  6. deviceMappingsimulatedBluetoothAdapter模拟 Bluetooth 设备映射

  7. deviceMapping[deviceAddress]存在,令 simulatedDevicedeviceMapping[deviceAddress]。

  8. 否则,返回错误,其错误码invalid argument

  9. simulatedDeviceInstance 为在 navigable活动窗口关联 Navigator关联 Bluetooth 内,获取表示该 BluetoothDevice simulatedDevice 的结果。

  10. serviceMappingsimulatedDevice模拟 GATT 服务映射

  11. serviceUuidparams["serviceUuid"]。

  12. serviceMapping[serviceUuid]存在,令 simulatedServiceserviceMapping[serviceUuid]。

  13. 否则,返回错误,其错误码invalid argument

  14. characteristicMappingsimulatedService模拟 GATT 特征值映射

  15. characteristicUuidparams["characteristicUuid"]。

  16. params["type"] 为 "add"

    1. characteristicMapping[characteristicUuid]存在,返回错误,其 错误码invalid element state

    2. params["characteristicProperties"]不存在,返回错误,其错误码invalid argument

    3. simulatedGattCharacteristicProperties 为新的模拟 GATT 特征值属性,并执行以下步骤:

      1. propertiesparams["characteristicProperties"]。

      2. properties["broadcast"]存在,且 properties["broadcast"] 为 true,则设置 simulatedGattCharacteristicPropertiesBroadcast 位。

      3. properties["read"]存在,且 properties["read"] 为 true,则设置 simulatedGattCharacteristicPropertiesRead 位。

      4. properties["writeWithoutResponse"]存在,且 properties["writeWithoutResponse"] 为 true,则设置 simulatedGattCharacteristicPropertiesWrite Without Response 位。

      5. properties["write"]存在,且 properties["write"] 为 true,则设置 simulatedGattCharacteristicPropertiesWrite 位。

      6. properties["notify"]存在,且 properties["notify"] 为 true,则设置 simulatedGattCharacteristicPropertiesNotify 位。

      7. properties["indicate"]存在,且 properties["indicate"] 为 true,则设置 simulatedGattCharacteristicPropertiesIndicate 位。

      8. properties["authenticatedSignedWrites"]存在,且 properties["authenticatedSignedWrites"] 为 true,则设置 simulatedGattCharacteristicPropertiesAuthenticated Signed Writes 位。

      9. properties["extendedProperties"]存在,且 properties["extendedProperties"] 为 true,则设置 simulatedGattCharacteristicPropertiesExtended Properties 位。

    4. simulatedGattCharacteristic 为新的模拟 GATT 特征值

    5. simulatedGattCharacteristicUUID 设为 characteristicUuid

    6. simulatedGattCharacteristic特征值属性设为 simulatedGattCharacteristicProperties

    7. characteristicMapping[characteristicUuid] 设为 simulatedGattCharacteristic

    8. 创建表示 simulatedGattCharacteristicBluetoothRemoteGATTCharacteristic, 并在 simulatedDeviceInstance.[[context]].[[attributeInstanceMap]] 中添加从 simulatedGattCharacteristic 到所得 Promise 的映射。

    9. 返回携带数据 null成功

  17. params["type"] 为 "remove"

    1. params["characteristicProperties"]存在,返回错误,其错误码invalid argument

    2. characteristicMapping[characteristicUuid]存在,令 simulatedGattCharacteristiccharacteristicMapping[characteristicUuid]。

    3. 否则,返回错误,其错误码invalid element state

    4. simulatedDeviceInstance.[[context]].[[attributeInstanceMap]] 中移除 simulatedGattCharacteristic

    5. characteristicMapping 中移除 characteristicUuid

    6. 返回携带数据 null成功

  18. 返回错误,其错误码invalid argument

一个本地端可以通过发送如下消息来模拟添加一个具有读取、写入与通知属性的 GATT 特征值:
{
  "method": "bluetooth.simulateCharacteristic",
  "params": {
    "context": "cxt-d03fdd81",
    "address": "09:09:09:09:09:09",
    "serviceUuid": "0000180d-0000-1000-8000-00805f9b34fb",
    "characteristicUuid": "00002a21-0000-1000-8000-00805f9b34fb",
    "characteristicProperties": {
      "read": true,
      "write": true,
      "notify": true
    },
    "type": "add"
  }
}
一个本地端可以通过发送如下消息来模拟移除一个 GATT 特征值:
{
  "method": "bluetooth.simulateCharacteristic",
  "params": {
    "context": "cxt-d03fdd81",
    "address": "09:09:09:09:09:09",
    "serviceUuid": "0000180d-0000-1000-8000-00805f9b34fb",
    "characteristicUuid": "00002a21-0000-1000-8000-00805f9b34fb",
    "type": "remove"
  }
}
12.2.3.10. bluetooth.simulateCharacteristicResponse 命令
bluetooth.SimulateCharacteristicResponse = (
   method: "bluetooth.simulateCharacteristicResponse",
   params: bluetooth.SimulateCharacteristicResponseParameters,
)
bluetooth.SimulateCharacteristicResponseParameters = {
   context: text,
   address: text,
   serviceUuid: bluetooth.BluetoothUuid,
   characteristicUuid: bluetooth.BluetoothUuid,
   type: "read" / "write" / "subscribe-to-notifications" / "unsubscribe-from-notifications",
   code: uint,
   ? data: [ * uint ]
}
具有命令参数 params远端步骤为:
  1. contextIdparams["context"]。

  2. navigable尝试contextId获取一个可导航对象的结果。

  3. deviceAddressparams["address"]。

  4. simulatedBluetoothAdapternavigable模拟 Bluetooth 适配器

  5. simulatedBluetoothAdapter 为空,返回错误,其错误码invalid argument

  6. deviceMappingsimulatedBluetoothAdapter模拟 Bluetooth 设备映射

  7. deviceMapping[deviceAddress]存在,令 simulatedDevicedeviceMapping[deviceAddress];否则,返回错误,其错误码invalid argument

  8. serviceMappingsimulatedDevice模拟 GATT 服务映射

  9. serviceUuidparams["serviceUuid"]。

  10. serviceMapping[serviceUuid]存在,令 simulatedServiceserviceMapping[serviceUuid]。

  11. 否则,返回错误,其错误码invalid argument

  12. characteristicMappingsimulatedService模拟 GATT 特征值映射

  13. characteristicUuidparams["characteristicUuid"]。

  14. characteristicMapping[characteristicUuid]存在,令 simulatedGattCharacteristiccharacteristicMapping[characteristicUuid]。

  15. 否则,返回错误,其错误码invalid element state

  16. simulatedDeviceInstance 为在 navigable活动窗口关联 Navigator关联 Bluetooth 内,获取表示该 BluetoothDevice simulatedDevice 的结果。

  17. promisesimulatedDeviceInstance.[[context]].[[attributeInstanceMap]][simulatedGattCharacteristic]。

  18. Upon fulfillment of promise with characteristic,执行以下步骤:

    1. params["type"] 为 read,执行以下步骤:

      1. characteristic.[[automatedCharacteristicReadResponse]]expected, 则将 characteristic.[[automatedCharacteristicReadResponse]] 设为 params["code"],并将 characteristic.[[automatedCharacteristicReadResponseData]] 设为所持字节的副本, 这些字节来自 params["data"]。

      2. 否则,返回错误,其错误码invalid element state

    2. params["type"] 为 write,执行以下步骤:

      1. characteristic.[[automatedCharacteristicWriteResponse]]expected, 则将 characteristic.[[automatedCharacteristicWriteResponse]] 设为 params["code"]。

      2. 否则,返回错误,其错误码invalid element state

    3. params["type"] 为 subscribe-to-notifications,执行以下步骤:

      1. characteristic.[[automatedCharacteristicSubscribeToNotificationsResponse]]expected, 则将 characteristic.[[automatedCharacteristicSubscribeToNotificationsResponse]] 设为 params["code"]。

      2. 否则,返回错误,其错误码invalid element state

    4. params["type"] 为 unsubscribe-from-notifications,执行以下步骤:

      1. characteristic.[[automatedCharacteristicUnsubscribeFromNotificationsResponse]]expected, 则将 characteristic.[[automatedCharacteristicUnsubscribeFromNotificationsResponse]] 设为 params["code"]。

      2. 否则,返回错误,其错误码invalid element state

    5. 否则,返回错误,其 错误码invalid argument

一个本地端可以通过发送如下消息来模拟一次特征值读取操作成功的响应(依据错误响应(Error Response),错误码 0x00),并携带数据:
{
  "method": "bluetooth.simulateCharacteristicResponse",
  "params": {
    "context": "cxt-d03fdd81",
    "address": "09:09:09:09:09:09",
    "serviceUuid": "0000180d-0000-1000-8000-00805f9b34fb",
    "characteristicUuid": "00002a21-0000-1000-8000-00805f9b34fb",
    "type": "read",
    "code": 0,
    "data": [1, 2]
  }
}
12.2.3.11. bluetooth.simulateDescriptor 命令
bluetooth.SimulateDescriptor = (
   method: "bluetooth.simulateDescriptor",
   params: bluetooth.SimulateDescriptorParameters,
)
bluetooth.SimulateDescriptorParameters = {
   context: text,
   address: text,
   serviceUuid: bluetooth.BluetoothUuid,
   characteristicUuid: bluetooth.BluetoothUuid,
   descriptorUuid: bluetooth.BluetoothUuid,
   type: "add" / "remove"
}
  1. contextIdparams["context"]。

  2. navigable尝试contextId获取一个可导航对象的结果。

  3. deviceAddressparams["address"]。

  4. simulatedBluetoothAdapternavigable模拟 Bluetooth 适配器

  5. simulatedBluetoothAdapter 为空,返回错误,其错误码invalid argument

  6. deviceMappingsimulatedBluetoothAdapter模拟 Bluetooth 设备映射

  7. deviceMapping[deviceAddress]存在,令 simulatedDevicedeviceMapping[deviceAddress]。

  8. 否则,返回错误,其错误码invalid argument

  9. simulatedDeviceInstance 为在 navigable活动窗口关联 Navigator关联 Bluetooth 内,获取表示该 BluetoothDevice simulatedDevice 的结果。

  10. serviceMappingsimulatedDevice模拟 GATT 服务映射

  11. serviceUuidparams["serviceUuid"]。

  12. serviceMapping[serviceUuid]存在,令 simulatedServiceserviceMapping[serviceUuid]。

  13. 否则,返回错误,其错误码invalid argument

  14. characteristicMappingsimulatedService模拟 GATT 特征值映射

  15. characteristicUuidparams["characteristicUuid"]。

  16. characteristicMapping[characteristicUuid]存在,令 simulatedCharacteristiccharacteristicMapping[characteristicUuid]。

  17. 否则,返回错误,其错误码invalid argument

  18. descriptorMappingsimulatedCharacteristic模拟 GATT 描述符映射

  19. descriptorUuidparams["descriptorUuid"]。

  20. params["type"] 为 "add"

    1. descriptorMapping[descriptorUuid]存在,返回错误,其 错误码invalid element state

    2. simulatedGattDescriptor 为新的模拟 GATT 描述符

    3. simulatedGattDescriptorUUID 设为 descriptorUuid

    4. descriptorMapping[descriptorUuid] 设为 simulatedGattDescriptor

    5. 创建表示 simulatedGattDescriptorBluetoothRemoteGATTDescriptor, 并在 simulatedDeviceInstance.[[context]].[[attributeInstanceMap]] 中添加从 simulatedGattDescriptor 到所得 Promise 的映射。

    6. 返回携带数据 null成功

  21. params["type"] 为 "remove"

    1. descriptorMapping[descriptorUuid]存在,令 simulatedGattDescriptordescriptorMapping[descriptorUuid]。

    2. 否则,返回错误,其错误码invalid element state

    3. simulatedDeviceInstance.[[context]].[[attributeInstanceMap]] 中移除 simulatedGattDescriptor

    4. descriptorMapping 中移除 descriptorUuid

    5. 返回携带数据 null成功

  22. 返回错误,其错误码invalid argument

一个本地端可以通过发送如下消息来模拟添加一个 GATT 描述符:
{
  "method": "bluetooth.simulateDescriptor",
  "params": {
    "context": "cxt-d03fdd81",
    "address": "09:09:09:09:09:09",
    "serviceUuid": "0000180d-0000-1000-8000-00805f9b34fb",
    "characteristicUuid": "00002a21-0000-1000-8000-00805f9b34fb",
    "descriptorUuid": "00002901-0000-1000-8000-00805f9b34fb",
    "type": "add"
  }
}
一个本地端可以通过发送如下消息来模拟移除一个 GATT 描述符:
{
  "method": "bluetooth.simulateDescriptor",
  "params": {
    "context": "cxt-d03fdd81",
    "address": "09:09:09:09:09:09",
    "serviceUuid": "0000180d-0000-1000-8000-00805f9b34fb",
    "characteristicUuid": "00002a21-0000-1000-8000-00805f9b34fb",
    "descriptorUuid": "00002901-0000-1000-8000-00805f9b34fb",
    "type": "remove"
  }
}
12.2.3.12. bluetooth.simulateDescriptorResponse 命令
bluetooth.SimulateDescriptorResponse = (
   method: "bluetooth.simulateDescriptorResponse",
   params: bluetooth.SimulateDescriptorResponseParameters,
)
bluetooth.SimulateDescriptorResponseParameters = {
   context: text,
   address: text,
   serviceUuid: bluetooth.BluetoothUuid,
   characteristicUuid: bluetooth.BluetoothUuid,
   descriptorUuid: bluetooth.BluetoothUuid,
   type: "read" / "write",
   code: uint,
   ? data: [ * uint ]
}
  1. contextIdparams["context"]。

  2. navigable尝试contextId获取一个可导航对象的结果。

  3. deviceAddressparams["address"]。

  4. simulatedBluetoothAdapternavigable模拟 Bluetooth 适配器

  5. simulatedBluetoothAdapter 为空,返回错误,其错误码invalid argument

  6. deviceMappingsimulatedBluetoothAdapter模拟 Bluetooth 设备映射

  7. deviceMapping[deviceAddress]存在,令 simulatedDevicedeviceMapping[deviceAddress];否则,返回错误,其错误码invalid argument

  8. serviceMappingsimulatedDevice模拟 GATT 服务映射

  9. serviceUuidparams["serviceUuid"]。

  10. serviceMapping[serviceUuid]存在,令 simulatedServiceserviceMapping[serviceUuid]。

  11. 否则,返回错误,其错误码invalid argument

  12. characteristicMappingsimulatedService模拟 GATT 特征值映射

  13. characteristicUuidparams["characteristicUuid"]。

  14. characteristicMapping[characteristicUuid]存在,令 simulatedCharacteristiccharacteristicMapping[characteristicUuid]。

  15. 否则,返回错误,其错误码invalid element state

  16. descriptorMappingsimulatedCharacteristic模拟 GATT 描述符映射

  17. descriptorUuidparams["descriptorUuid"]。

  18. descriptorMapping[descriptorUuid]存在,令 simulatedDescriptordescriptorMapping[descriptorUuid]。

  19. 否则,返回错误,其错误码invalid element state

  20. simulatedDeviceInstance 为在 navigable活动窗口关联 Navigator关联 Bluetooth 内,获取表示该 BluetoothDevice simulatedDevice 的结果。

  21. promisesimulatedDeviceInstance.[[context]].[[attributeInstanceMap]][simulatedDescriptor]。

  22. Upon fulfillment of promise with descriptor,执行以下步骤:

    1. params["type"] 为 read,执行以下步骤:

      1. descriptor.[[automatedDescriptorReadResponse]]expected, 则将 descriptor.[[automatedDescriptorReadResponse]] 设为 params["code"],并将 descriptor.[[automatedDescriptorReadResponseData]] 设为所持字节的副本, 这些字节来自 params["data"]。

      2. 否则,返回错误,其错误码invalid element state

    2. params["type"] 为 write,执行以下步骤:

      1. characteristic.[[automatedDescriptorWriteResponse]]expected, 则将 characteristic.[[automatedDescriptorWriteResponse]] 设为 params["code"]。

      2. 否则,返回错误,其错误码invalid element state

    3. 否则,返回错误,其 错误码invalid argument

一个本地端可以通过发送如下消息来模拟一次描述符读取操作成功的响应(依据错误响应(Error Response),错误码 0x00),并携带数据:
{
  "method": "bluetooth.simulateDescriptorResponse",
  "params": {
    "context": "cxt-d03fdd81",
    "address": "09:09:09:09:09:09",
    "serviceUuid": "0000180d-0000-1000-8000-00805f9b34fb",
    "characteristicUuid": "00002a21-0000-1000-8000-00805f9b34fb",
    "descriptorUuid": "00002901-0000-1000-8000-00805f9b34fb",
    "type": "read",
    "code": 0,
    "data": [1, 2]
  }
}

12.2.4. 事件

BluetoothEvent = (
  bluetooth.RequestDevicePromptUpdated //
  bluetooth.GattConnectionAttempted
)
12.2.4.1. bluetooth.requestDevicePromptUpdated 事件
bluetooth.RequestDevicePromptUpdated = (
   method: "bluetooth.requestDevicePromptUpdated",
   params: bluetooth.RequestDevicePromptUpdatedParameters
)

bluetooth.RequestDevicePromptUpdatedParameters = {
   context: text,
   prompt: bluetooth.RequestDevicePrompt,
   devices: [* bluetooth.RequestDeviceInfo],
}
要在给定一个navigable navigable、字符串 promptId,以及集合devices(由Bluetooth 设备组成)时,触发一个提示更新事件
  1. navigableIdnavigablenavigable id

  2. prompt设备提示promptId, devices)。

  3. serialized devices 为以 prompt 作为输入序列化提示中的设备的结果。

  4. navigable 到设备提示的映射[navigableId] 设为 prompt

  5. params 为一个与 bluetooth.RequestDevicePromptUpdatedParameters 产生式匹配的映射,其中 context 字段设为 navigableIdprompt 字段设为 promptIddevices 字段设为 serialized devices

  6. body 为一个与 bluetooth.RequestDevicePromptUpdated 产生式匹配的映射,其中 params 字段设为 params

  7. relatedNavigables 为包含 navigable集合

  8. 对于给定 "bluetooth.requestDevicePromptUpdated" 与 relatedNavigables已启用该事件的会话集合中的每个 session

    1. 触发事件(Emit an event),携带 sessionbody

12.2.4.2. bluetooth.gattConnectionAttempted 事件
bluetooth.GattConnectionAttempted = (
   method: "bluetooth.gattConnectionAttempted",
   params: bluetooth.GattConnectionAttemptedParameters
)

bluetooth.GattConnectionAttemptedParameters = {
   context: text,
   address: text
}
要在给定一个navigable navigable 与一个 BluetoothDevice device 的情况下,触发一个 gatt 连接尝试事件
  1. navigableIdnavigablenavigable id

  2. params 为一个与 bluetooth.GattConnectionAttemptedParameters 产生式匹配的映射,其中 context 字段设为 navigableIdaddress 字段设为 device.[[representedDevice]] 的地址。

  3. body 为一个与 bluetooth.GattConnectionAttempted 产生式匹配的映射,其中 params 字段设为 params

  4. relatedNavigables 为包含 navigable集合

  5. 对于给定 "bluetooth.gattEventGenerated" 与 relatedNavigables已启用该事件的会话集合中的每个 session

    1. 触发事件(Emit an event),携带 sessionbody

12.2.4.3. bluetooth.characteristicEventGenerated 事件
bluetooth.CharacteristicEventGenerated = (
   method: "bluetooth.characteristicEventGenerated",
   params: bluetooth.CharacteristicEventGeneratedParameters
)

bluetooth.CharacteristicEventGeneratedParameters = {
  context: text,
  address: text,
  serviceUuid: bluetooth.BluetoothUuid,
  characteristicUuid: bluetooth.BluetoothUuid,
  type: "read" / "write-with-response" / "write-without-response" / "subscribe-to-notifications" / "unsubscribe-from-notifications",
  ? data: [ * uint ]
}

要在给定一个navigable navigable、一个 BluetoothDevice device、一个模拟 GATT 特征值 characteristic字符串 type,以及可选的字节序列 bytes 的情况下,触发一个模拟的特征值事件

  1. navigableIdnavigablenavigable id

  2. params 为一个与 bluetooth.CharacteristicEventGeneratedParameters 产生式匹配的映射,并执行以下步骤:

    1. params["context"] 为 navigableId

    2. params["address"] 为 device.[[representedDevice]] 的地址。

    3. service 为包含 characteristic模拟 GATT 服务

    4. params["serviceUuid"] 为 serviceUUID

    5. params["characteristicUuid"] 为 characteristicUUID

    6. params["type"] 为 type

    7. 如果 typewrite,执行以下步骤:

      1. data 为空列表。

      2. 对于 bytes 中的每个 byte

        1. byte追加到 data

      3. params["data"] 为 data

  3. body 为一个与 bluetooth.CharacteristicEventGenerated 产生式匹配的映射,其中 params 字段设为 params

  4. relatedNavigables 为包含 navigable集合

  5. 对于给定 "bluetooth.characteristicEventGenerated" 与 relatedNavigables已启用该事件的会话集合中的每个 session

    1. 触发事件(Emit an event),携带 sessionbody

12.2.4.4. bluetooth.descriptorEventGenerated 事件
bluetooth.DescriptorEventGenerated = (
   method: "bluetooth.descriptorEventGenerated",
   params: bluetooth.DescriptorEventGeneratedParameters
)

bluetooth.DescriptorEventGeneratedParameters = {
  context: text,
  address: text,
  serviceUuid: bluetooth.BluetoothUuid,
  characteristicUuid: bluetooth.BluetoothUuid,
  descriptorUuid: bluetooth.BluetoothUuid,
  type: "read" / "write",
  ? data: [ * uint ]
}

要在给定一个navigable navigable、一个 BluetoothDevice device、一个模拟 GATT 描述符 descriptor字符串 type,以及可选的字节序列 bytes 的情况下,触发一个模拟的描述符事件

  1. navigableIdnavigablenavigable id

  2. params 为一个与 bluetooth.DescriptorEventGeneratedParameters 产生式匹配的映射,并执行以下步骤:

    1. params["context"] 为 navigableId

    2. params["address"] 为 device.[[representedDevice]] 的地址。

    3. characteristic 为包含 descriptor模拟 GATT 特征值

    4. service 为包含 characteristic模拟 GATT 服务

    5. params["serviceUuid"] 为 serviceUUID

    6. params["characteristicUuid"] 为 characteristicUUID

    7. params["descriptorUuid"] 为 descriptorUUID

    8. params["type"] 为 type

    9. 如果 typewrite,执行以下步骤:

      1. data 为空列表。

      2. 对于 bytes 中的每个 byte

        1. byte追加到 data

      3. params["data"] 为 data

  3. body 为一个与 bluetooth.DescriptorEventGenerated 产生式匹配的映射,其中 params 字段设为 params

  4. relatedNavigables 为包含 navigable集合

  5. 对于给定 "bluetooth.descriptorEventGenerated" 与 relatedNavigables已启用该事件的会话集合中的每个 session

    1. 触发事件(Emit an event),携带 sessionbody

13. 术语与约定

本规范使用了一些约定以及来自其他规范的若干术语。本节列出了这些内容并链接到它们的主要定义。

当本规范中的算法使用在本规范或其他规范中定义的名称时,该名称必须解析为其初始值,忽略在当前执行环境中对该名称所做的任何更改。例如,当 requestDevice() 算法要求调用 Array.prototype.map.call(filter.services, BluetoothUUID.getService) 时,必须应用 [ECMAScript] 中定义的 Array.prototype.map 算法,以 filter.services 作为其 this 参数,并以 § 7.1 标准化 UUID 中为 BluetoothUUID.getService 定义的算法作为其 callbackfn 参数,而不论对 windowArrayArray.prototypeArray.prototype.mapFunctionFunction.prototypeBluetoothUUIDBluetoothUUID.getService 或其他对象进行了怎样的修改。

本规范使用一种只读类型,其类似于 WebIDL 的 FrozenArray

[BLUETOOTH42]
  1. 架构与术语概览
    1. 概述
      1. Bluetooth 低功耗(BLE)操作概览 (定义了 广播事件
    2. 通信拓扑与操作
      1. 运行过程与模式
        1. BR/EDR 过程
          1. Inquiry(发现)过程
            1. 扩展 Inquiry 响应(Extended Inquiry Response)
  2. 核心系统包 [BR/EDR 控制器卷]
    1. 错误码
      1. 错误码概览
        1. 错误码列表
    2. 主机控制器接口(HCI)功能规范
      1. HCI 命令与事件
        1. 信息参数
          1. 读取 BD_ADDR 命令
        2. 状态参数
          1. 读取 RSSI 命令
  3. 核心系统包 [主机卷]
    1. 服务发现协议(SDP)规范
      1. 概览
        1. 服务搜索
          1. UUID (定义了 UUID 别名以及由 UUID 别名计算 所表示的 128 位 UUID 的算法)
    2. 通用接入配置文件(GAP)
      1. 配置文件概览
        1. 配置文件角色
          1. 在 LE 物理传输上的角色
            1. 广播者(Broadcaster)角色
            2. 观察者(Observer)角色
            3. 外设(Peripheral)角色
            4. 中心(Central)角色
      2. 用户界面方面
        1. Bluetooth 参数的表示
          1. Bluetooth 设备名称(用户可读名称)
      3. 空闲模式过程 — BR/EDR 物理传输
        1. 设备发现过程
        2. BR/EDR 绑定过程
      4. 运行模式与过程 — LE 物理传输
        1. 广播模式与观察过程
          1. 观察过程
        2. 发现模式与过程
          1. 一般发现过程
          2. 名称发现过程
        3. 连接模式与过程
        4. 绑定模式与过程
          1. LE 绑定过程
      5. 安全方面 — LE 物理传输
        1. 隐私特性
        2. 随机设备地址
          1. 静态地址
          2. 私有地址
            1. 可解析私有地址解析过程
      6. 广播数据(Advertising Data)与扫描响应数据格式 (定义了 AD 结构
      7. Bluetooth 设备需求
        1. Bluetooth 设备地址(定义了 BD_ADDR
          1. Bluetooth 设备地址类型
            1. 公共 Bluetooth 地址
      8. 定义 (定义了 绑定(bond)
    3. 属性协议(ATT)
      1. 协议需求
        1. 基本概念
          1. 属性类型
          2. 属性句柄
          3. 长属性值
        2. 属性协议 PDU
          1. 错误处理
            1. 错误响应(Error Response)
    4. 通用属性配置文件(GATT)
      1. 配置文件概览
        1. 配置与角色 (定义了 GATT 客户端GATT 服务端
        2. 配置文件基础, 定义了 ATT 承载(Bearer)
        3. 属性协议
          1. 属性缓存
        4. GATT 配置文件层级
          1. 服务(Service)
          2. 包含的服务(Included Service)
          3. 特征值(Characteristic)
      2. 服务互操作性需求
        1. 服务定义
        2. 特征值定义
          1. 特征值声明
            1. 特征值属性
          2. 特征值 描述符(Descriptor) 声明
            1. 特征值扩展属性
            2. 客户端特征值配置
      3. GATT 功能需求 — 定义了 GATT 过程
        1. 服务器配置
          1. 交换 MTU(Exchange MTU)
        2. 主服务发现
          1. 发现所有主服务
          2. 根据服务 UUID 发现主服务
        3. 关系发现
          1. 查找包含的服务
        4. 特征值发现
          1. 发现服务的所有特征值
          2. 按 UUID 发现特征值
        5. 特征值描述符发现
          1. 发现所有特征值描述符
        6. 特征值读取
        7. 特征值写入
          1. 无响应写入
          2. 写入特征值
        8. 特征值通知
        9. 特征值指示
        10. 特征值描述符
          1. 读取特征值描述符
          2. 读取长特征值描述符
          3. 写入特征值描述符
          4. 写入长特征值描述符
        11. 过程超时
      4. GAP 互操作性需求
        1. BR/EDR GAP 互操作性需求
          1. 建立连接
        2. LE GAP 互操作性需求
          1. 建立连接
      5. 已定义的通用属性配置文件服务
        1. Service Changed
    5. 安全管理器规范
      1. 安全管理器
        1. Bluetooth 低功耗中的安全
          1. 密钥与取值定义, 定义了 身份解析密钥IRK
  4. 核心系统包 [低功耗控制器卷]
    1. 链路层规范
      1. 概述
        1. 设备地址
          1. 公共设备地址
          2. 随机设备地址
            1. 静态设备地址
      2. 空中接口协议
        1. 非连接状态
          1. 扫描状态
            1. 被动扫描
[BLUETOOTH-SUPPLEMENT6]
  1. 数据类型规范
    1. 数据类型定义与格式
      1. 服务 UUID 数据类型
      2. 本地名称数据类型
      3. 标志数据类型 (定义了 可发现模式 标志)
      4. 厂商特定数据
      5. 发射功率级别(TX Power Level)
      6. 服务数据
      7. 外观(Appearance)

一致性

文档约定

一致性要求由描述性断言与 RFC 2119 术语的组合来表达。规范性部分中的关键字 “MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY” 与 “OPTIONAL” 应按 RFC 2119 中的描述进行解释。不过,为了可读性,本规范中这些词并非全部以大写字母出现。

除明确标注为非规范性的章节、示例与注记外,本规范的所有文本均为规范性内容。[RFC2119]

本规范中的示例以 “for example” 引出,或通过 class="example" 与规范性文本区分开来,如下所示:

这是一个信息性的示例。

信息性注记以 “Note” 开头,并通过 class="note" 与规范性文本区分开来,如下所示:

Note,本段为信息性注记。

索引

本规范定义的术语

引用定义的术语

参考文献

规范性引用

[BLUETOOTH-ASSIGNED]
Assigned Numbers. Living Standard. URL: https://www.bluetooth.com/specifications/assigned-numbers/
[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
[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[ECMAScript]
ECMAScript Language Specification. URL: https://tc39.es/ecma262/multipage/
[ENCODING]
Anne van Kesteren. Encoding Standard. Living Standard. URL: https://encoding.spec.whatwg.org/
[FINGERPRINTING-GUIDANCE]
Nick Doty; Tom Ritter. Mitigating Browser Fingerprinting in Web Specifications. URL: https://w3c.github.io/fingerprinting-guidance/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[PAGE-VISIBILITY]
Jatinder Mann; Arvind Jain. Page Visibility (Second Edition). 29 October 2013. REC. URL: https://www.w3.org/TR/page-visibility/
[PERMISSIONS]
Marcos Caceres; Mike Taylor. Permissions. URL: https://w3c.github.io/permissions/
[PERMISSIONS-POLICY-1]
Ian Clelland. Permissions Policy. URL: https://w3c.github.io/webappsec-permissions-policy/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[RFC4122]
P. Leach; M. Mealling; R. Salz. A Universally Unique IDentifier (UUID) URN Namespace. July 2005. Proposed Standard. URL: https://www.rfc-editor.org/rfc/rfc4122
[RFC8610]
H. Birkholz; C. Vigano; C. Bormann. Concise Data Definition Language (CDDL): A Notational Convention to Express Concise Binary Object Representation (CBOR) and JSON Data Structures. June 2019. Proposed Standard. URL: https://www.rfc-editor.org/rfc/rfc8610
[WEBDRIVER-BIDI]
James Graham; Alex Rudenko; Maksim Sadym. WebDriver BiDi. URL: https://w3c.github.io/webdriver-bidi/
[WEBDRIVER1]
Simon Stewart; David Burns. WebDriver. URL: https://w3c.github.io/webdriver/
[WEBDRIVER2]
Simon Stewart; David Burns. WebDriver. URL: https://w3c.github.io/webdriver/
[WEBHID]
WebHID API. Draft Community Group Report. URL: https://wicg.github.io/webhid/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

信息性引用

[CSP3]
Mike West; Antonio Sartori. Content Security Policy Level 3. URL: https://w3c.github.io/webappsec-csp/

IDL 索引

dictionary BluetoothDataFilterInit {
  BufferSource dataPrefix;
  BufferSource mask;
};

dictionary BluetoothManufacturerDataFilterInit : BluetoothDataFilterInit {
  required [EnforceRange] unsigned short companyIdentifier;
};

dictionary BluetoothServiceDataFilterInit : BluetoothDataFilterInit {
  required BluetoothServiceUUID service;
};

dictionary BluetoothLEScanFilterInit {
  sequence<BluetoothServiceUUID> services;
  DOMString name;
  DOMString namePrefix;
  sequence<BluetoothManufacturerDataFilterInit> manufacturerData;
  sequence<BluetoothServiceDataFilterInit> serviceData;
};

dictionary RequestDeviceOptions {
  sequence<BluetoothLEScanFilterInit> filters;
  sequence<BluetoothLEScanFilterInit> exclusionFilters;
  sequence<BluetoothServiceUUID> optionalServices = [];
  sequence<unsigned short> optionalManufacturerData = [];
  boolean acceptAllDevices = false;
};

[Exposed=Window, SecureContext]
interface Bluetooth : EventTarget {
  Promise<boolean> getAvailability();
  attribute EventHandler onavailabilitychanged;
  [SameObject]
  readonly attribute BluetoothDevice? referringDevice;
  Promise<sequence<BluetoothDevice>> getDevices();
  Promise<BluetoothDevice> requestDevice(optional RequestDeviceOptions options = {});
};

Bluetooth includes BluetoothDeviceEventHandlers;
Bluetooth includes CharacteristicEventHandlers;
Bluetooth includes ServiceEventHandlers;

dictionary BluetoothPermissionDescriptor : PermissionDescriptor {
  DOMString deviceId;
  // These match RequestDeviceOptions.
  sequence<BluetoothLEScanFilterInit> filters;
  sequence<BluetoothServiceUUID> optionalServices = [];
  sequence<unsigned short> optionalManufacturerData = [];
  boolean acceptAllDevices = false;
};

dictionary AllowedBluetoothDevice {
  required DOMString deviceId;
  required boolean mayUseGATT;
  // An allowedServices of "all" means all services are allowed.
  required (DOMString or sequence<UUID>) allowedServices;
  required sequence<unsigned short> allowedManufacturerData;
};
dictionary BluetoothPermissionStorage {
  required sequence<AllowedBluetoothDevice> allowedDevices;
};

[Exposed=Window]
interface BluetoothPermissionResult : PermissionStatus {
  attribute FrozenArray<BluetoothDevice> devices;
};

[
  Exposed=Window,
  SecureContext
]
interface ValueEvent : Event {
  constructor(DOMString type, optional ValueEventInit initDict = {});
  readonly attribute any value;
};

dictionary ValueEventInit : EventInit {
  any value = null;
};

[Exposed=Window, SecureContext]
interface BluetoothDevice : EventTarget {
  readonly attribute DOMString id;
  readonly attribute DOMString? name;
  readonly attribute BluetoothRemoteGATTServer? gatt;

  Promise<undefined> forget();
  Promise<undefined> watchAdvertisements(
      optional WatchAdvertisementsOptions options = {});
  readonly attribute boolean watchingAdvertisements;
};
BluetoothDevice includes BluetoothDeviceEventHandlers;
BluetoothDevice includes CharacteristicEventHandlers;
BluetoothDevice includes ServiceEventHandlers;

dictionary WatchAdvertisementsOptions {
  AbortSignal signal;
};

[Exposed=Window, SecureContext]
interface BluetoothManufacturerDataMap {
  readonly maplike<unsigned short, DataView>;
};
[Exposed=Window, SecureContext]
interface BluetoothServiceDataMap {
  readonly maplike<UUID, DataView>;
};
[
  Exposed=Window,
  SecureContext
]
interface BluetoothAdvertisingEvent : Event {
  constructor(DOMString type, BluetoothAdvertisingEventInit init);
  [SameObject]
  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;
  [SameObject]
  readonly attribute BluetoothManufacturerDataMap manufacturerData;
  [SameObject]
  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;
  BluetoothManufacturerDataMap manufacturerData;
  BluetoothServiceDataMap serviceData;
};

[Exposed=Window, SecureContext]
interface BluetoothRemoteGATTServer {
  [SameObject]
  readonly attribute BluetoothDevice device;
  readonly attribute boolean connected;
  Promise<BluetoothRemoteGATTServer> connect();
  undefined disconnect();
  Promise<BluetoothRemoteGATTService> getPrimaryService(BluetoothServiceUUID service);
  Promise<sequence<BluetoothRemoteGATTService>>
    getPrimaryServices(optional BluetoothServiceUUID service);
};

[Exposed=Window, SecureContext]
interface BluetoothRemoteGATTService : EventTarget {
  [SameObject]
  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 includes CharacteristicEventHandlers;
BluetoothRemoteGATTService includes ServiceEventHandlers;

[Exposed=Window, SecureContext]
interface BluetoothRemoteGATTCharacteristic : EventTarget {
  [SameObject]
  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<undefined> writeValue(BufferSource value);
  Promise<undefined> writeValueWithResponse(BufferSource value);
  Promise<undefined> writeValueWithoutResponse(BufferSource value);
  Promise<BluetoothRemoteGATTCharacteristic> startNotifications();
  Promise<BluetoothRemoteGATTCharacteristic> stopNotifications();
};
BluetoothRemoteGATTCharacteristic includes CharacteristicEventHandlers;

[Exposed=Window, SecureContext]
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;
};

[Exposed=Window, SecureContext]
interface BluetoothRemoteGATTDescriptor {
  [SameObject]
  readonly attribute BluetoothRemoteGATTCharacteristic characteristic;
  readonly attribute UUID uuid;
  readonly attribute DataView? value;
  Promise<DataView> readValue();
  Promise<undefined> writeValue(BufferSource value);
};

[SecureContext]
interface mixin CharacteristicEventHandlers {
  attribute EventHandler oncharacteristicvaluechanged;
};

[SecureContext]
interface mixin BluetoothDeviceEventHandlers {
  attribute EventHandler onadvertisementreceived;
  attribute EventHandler ongattserverdisconnected;
};

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

typedef DOMString UUID;

[Exposed=Window]
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;

[SecureContext]
partial interface Navigator {
  [SameObject]
  readonly attribute Bluetooth bluetooth;
};

CDDL 索引

问题索引

参见 § 3 隐私注意事项 部分。 [Issue #575]
在实践中,提示打开期间设备列表会动态更新。规范文本目前尚未反映这一点,但该事件可能会以相同的 promptId 和最新的设备列表多次触发。参见 https://github.com/WebBluetoothCG/web-bluetooth/issues/621。
TODO:确定具体时间长度。
支持异步设备发现。
被动扫描隐私特性 都可避免 泄露唯一且不可变的设备 ID。我们应要求 UA 使用其中之一,但操作系统的 API 似乎都不提供。Bluetooth 也使使用 被动扫描 变得困难,因为它并不要求 Central 设备支持 观察过程
所有形式的 BR/EDR 询问/发现都似乎会泄露唯一且不可变的设备地址。
我们需要一种方式,使站点能够注册在有感兴趣的设备进入范围时接收事件。
此类通用事件类型应归属于 [HTML][DOM],而不是这里。
实现也许能够避免此 NetworkError, 但目前站点需要串行化对该 API 的使用,和/或为用户提供重试失败操作的方式。 [Issue #188]
实现也许能够避免此 NetworkError, 但目前站点需要串行化对该 API 的使用,和/或为用户提供重试失败操作的方式。 [Issue #188]
实现也许能够避免此 NetworkError, 但目前站点需要串行化对该 API 的使用,和/或为用户提供重试失败操作的方式。 [Issue #188]
此方法仅用于向后兼容。新的实现不应实现此方法。 [Issue #238]
实现也许能够避免此 NetworkError, 但 目前站点需要串行化对该 API 的使用,和/或为用户提供重试失败操作的方式。 [Issue #188]
特征值扩展属性 并未明确 这些扩展属性对于给定的特征值是否不可变。若不可变,UA 应当被允许对其进行缓存。
实现也许能够避免此 NetworkError, 但目前站点需要串行化对该 API 的使用,和/或为用户提供重试失败操作的方式。 [Issue #188]
实现也许能够避免此 NetworkError, 但目前站点需要串行化对该 API 的使用,和/或为用户提供重试失败操作的方式。 [Issue #188]
CDDL 片段使用 "text" 类型而不是 "browsingContext.BrowsingContext",以便允许对 CDDL 片段进行独立的程序化处理。目前,无法引用其他模块。
将另一个算法中的数据插入变量的方式定义不明确。为了与实现保持一致,扫描设备 算法需要定义异步设备发现。
MDN

Bluetooth/availabilitychanged_event

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for AndroidNoneAndroid WebView?Samsung Internet?Opera Mobile?
MDN

Bluetooth/availabilitychanged_event

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for AndroidNoneAndroid WebView?Samsung Internet?Opera Mobile?
MDN

Bluetooth/getAvailability

In only one current engine.

FirefoxNoneSafariNoneChrome78+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

Bluetooth/getDevices

In only one current engine.

FirefoxNoneSafariNoneChrome🔰 85+
Opera?Edge🔰 85+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

Bluetooth/requestDevice

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

Bluetooth

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android56+Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothCharacteristicProperties/authenticatedSignedWrites

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothCharacteristicProperties/broadcast

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothCharacteristicProperties/indicate

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothCharacteristicProperties/notify

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothCharacteristicProperties/read

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothCharacteristicProperties/reliableWrite

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothCharacteristicProperties/writableAuxiliaries

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothCharacteristicProperties/write

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothCharacteristicProperties/writeWithoutResponse

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothCharacteristicProperties

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android56+Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothDevice/gatt

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothDevice/id

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothDevice/name

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothDevice

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android56+Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTCharacteristic/getDescriptor

In only one current engine.

FirefoxNoneSafariNoneChrome57+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTCharacteristic/getDescriptors

In only one current engine.

FirefoxNoneSafariNoneChrome57+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTCharacteristic/properties

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTCharacteristic/readValue

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTCharacteristic/service

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTCharacteristic/startNotifications

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTCharacteristic/stopNotifications

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTCharacteristic/uuid

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTCharacteristic/value

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTCharacteristic/writeValueWithoutResponse

In only one current engine.

FirefoxNoneSafariNoneChrome85+
Opera?Edge85+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTCharacteristic/writeValueWithResponse

In only one current engine.

FirefoxNoneSafariNoneChrome85+
Opera?Edge85+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTCharacteristic

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android56+Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTDescriptor/characteristic

In only one current engine.

FirefoxNoneSafariNoneChrome57+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile44+
MDN

BluetoothRemoteGATTDescriptor/readValue

In only one current engine.

FirefoxNoneSafariNoneChrome57+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile44+
MDN

BluetoothRemoteGATTDescriptor/uuid

In only one current engine.

FirefoxNoneSafariNoneChrome57+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile44+
MDN

BluetoothRemoteGATTDescriptor/value

In only one current engine.

FirefoxNoneSafariNoneChrome57+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile44+
MDN

BluetoothRemoteGATTDescriptor/writeValue

In only one current engine.

FirefoxNoneSafariNoneChrome57+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile44+
MDN

BluetoothRemoteGATTDescriptor

In only one current engine.

FirefoxNoneSafariNoneChrome57+
Opera?Edge79+
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android57+Android WebViewNoneSamsung Internet?Opera Mobile44+
MDN

BluetoothRemoteGATTServer/connect

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTServer/connected

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTServer/device

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTServer/disconnect

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTServer/getPrimaryService

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTServer/getPrimaryServices

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTServer

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android56+Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTService/device

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTService/getCharacteristic

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTService/getCharacteristics

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTService/isPrimary

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTService/uuid

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothRemoteGATTService

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android56+Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothUUID/canonicalUUID_static

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothUUID/getCharacteristic_static

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothUUID/getDescriptor_static

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothUUID/getService_static

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebViewNoneSamsung Internet?Opera Mobile?
MDN

BluetoothUUID

In only one current engine.

FirefoxNoneSafariNoneChrome56+
Opera?Edge79+
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android56+Android WebViewNoneSamsung Internet?Opera Mobile?