Skip to content

Commit 5fca8d9

Browse files
alexnjreillyeon
andauthored
Add Bluetooth device emulation support (#630)
* Initial cut * Add semantics and examples * Review changes. * Curly brace for SimulateAdvertisementParameters * Add algorithm for adding a virtual peripheral Also remove simluateCentral implementation detail. * Add remote algorithm for simulateAdvertisement * Review changes * Review updates * Reintroduce simulateCentral + review feedback * Rename simulateCentral to simulateAdapter * Rename virtual prefix to simulated + review changes * Review changes * Formatting for scanRecord definitions * Apply suggestions from code review Co-authored-by: Reilly Grant <reillyeon@users.noreply.github.com> * Review edits * Update index.bs Co-authored-by: Reilly Grant <reillyeon@users.noreply.github.com> * Review edits + issues * Update changes from offline sync * An attempt at resolving the correct event loop * Add text to resolve navigator.bluetooth on navigable. * Revise spec to adjust for bidi navigable resolution * Review updates + CDDL changes to match the actual impl. * More updates. * Update steps for global param for scan for devices algorithm * Add a missing [=exists=]. * Correct lint errors + review changes * Review changes * Review changes. * Update data type to bstr --------- Co-authored-by: Reilly Grant <reillyeon@users.noreply.github.com>
1 parent ee320c9 commit 5fca8d9

File tree

1 file changed

+252
-6
lines changed

1 file changed

+252
-6
lines changed

index.bs

Lines changed: 252 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,12 @@ spec: html
127127
spec: webidl
128128
type: dfn
129129
text: resolve
130+
spec: webdriver
131+
type: dfn
132+
text: remote end steps
133+
type: dfn
134+
text: current browsing context
135+
130136
</pre>
131137

132138
<style>
@@ -1415,8 +1421,8 @@ steps:
14151421
devices, but for now it only ever returns a single one.
14161422
</div>
14171423

1418-
1. Let |document| be [=this=]'s [=relevant global object=]'s
1419-
[=associated Document=].
1424+
1. Let |global| be the [=relevant global object=] for |storage|.
1425+
1. Let |document| be |global|'s [=associated Document=].
14201426
1. If |document| is not [=allowed to use=] the [=policy-controlled feature=]
14211427
named "[=policy-controlled feature/bluetooth=]", throw a {{SecurityError}}
14221428
and abort these steps.
@@ -1476,8 +1482,8 @@ steps:
14761482
for example because there is no Bluetooth adapter with which to scan, or
14771483
because the filters can't be matched by any possible advertising packet, the
14781484
UA MAY return `[]` and abort these steps.
1479-
1. <a>Scan for devices</a> with <var>requiredServiceUUIDs</var> as the <i>set of
1480-
<a>Service</a> UUIDs</i>, and let <var>scanResult</var> be the result.
1485+
1. Let |scanResult| be the result of invoking [=scan for devices=] with |global| and
1486+
|requiredServiceUUIDs|.
14811487
1. If |filters| isn't `null`, do the following sub-steps:
14821488
1. Remove devices from <var>scanResult</var> if they do
14831489
not <a>match a filter</a> in <var>uuidFilters</var>.
@@ -1647,7 +1653,7 @@ returned from the following steps:
16471653
</div>
16481654

16491655
<div algorithm="scanning for Bluetooth devices">
1650-
To <dfn>scan for devices</dfn> with an optional <var>set of <a>Service</a>
1656+
To <dfn>scan for devices</dfn> with parameters <var>global</var> and an optional <var>set of <a>Service</a>
16511657
UUIDs</var>, defaulting to the set of all UUIDs, the UA MUST perform the
16521658
following steps:
16531659
1. If the UA has scanned for devices recently with a set of UUIDs that was a
@@ -1658,6 +1664,14 @@ following steps:
16581664
1. Let <var>nearbyDevices</var> be a set of <a>Bluetooth device</a>s, initially
16591665
equal to the set of devices that are connected (have an <a>ATT Bearer</a>)
16601666
to the UA.
1667+
1. Let |topLevelTraversable| be the |global|'s [=Window/navigable=]'s
1668+
[=navigable/top-level traversable=].
1669+
1. Let |simulatedBluetoothDevices| be an empty <a spec="infra">list</a>.
1670+
1. If |topLevelTraversable| has a [=simulated Bluetooth adapter=], let
1671+
|simulatedBluetoothDevices| be the result of [=getting the values=] of its [=simulated Bluetooth device mapping=].
1672+
1673+
Issue: Support asynchronous device discovery.
1674+
16611675
1. If the UA supports the LE transport, perform the <a>General Discovery
16621676
Procedure</a>, except that the UA may include devices that have no
16631677
<a>Discoverable Mode</a> flag set, and add the discovered <a>Bluetooth
@@ -1677,7 +1691,7 @@ following steps:
16771691
immutable device address.
16781692
1. Let <var>result</var> be a set of <a>Bluetooth device</a>s, initially empty.
16791693
1. For each <a>Bluetooth device</a> <var>device</var> in
1680-
<var>nearbyDevices</var>, do the following sub-steps:
1694+
<var>nearbyDevices</var> and <var>simulatedBluetoothDevices</var>, do the following sub-steps:
16811695
1. If <var>device</var>'s <a>supported physical transports</a> include LE
16821696
and its <a>Bluetooth Device Name</a> is partial or absent, the UA SHOULD
16831697
perform the <a>Name Discovery Procedure</a> to acquire a complete name.
@@ -1988,6 +2002,14 @@ steps <a>in parallel</a>:
19882002
Note: If the Web Bluetooth permission has been blocked by the user, the UA
19892003
may <a>resolve</a> |promise| with `false`.
19902004
</div>
2005+
2006+
1. Let |simulatedBluetoothAdapter| be [=this=]'s [=Window/navigable=]'s
2007+
[=navigable/top-level traversable=]'s <a>simulated Bluetooth adapter</a>.
2008+
1. If |simulatedBluetoothAdapter| is not empty,
2009+
1. If |simulatedBluetoothAdapter|'s [=adapter state=] is "absent", [=queue a task=] to [=resolve=] |promise| with `false`.
2010+
1. Otherwise, [=queue a task=] to [=resolve=] |promise| with `true`.
2011+
1. Abort these steps.
2012+
19912013
1. If the UA is running on a system that has a Bluetooth radio <a>queue a
19922014
task</a> to <a>resolve</a> |promise| with `true` regardless of the powered
19932015
state of the Bluetooth radio.
@@ -4835,6 +4857,10 @@ if the following steps return `blocked`:
48354857
};
48364858
</xmp>
48374859

4860+
Each {{Navigator}} has an <dfn>associated `Bluetooth`</dfn>, which is a {{Bluetooth}} object. Upon creation of the {{Navigator}} object, its <a>associated `Bluetooth`</a> must be set to a new {{Bluetooth}} object created in the {{Navigator}} object's [=relevant realm=].
4861+
4862+
{{Navigator}}'s <dfn attribute for="Navigator">bluetooth</dfn> getter steps are to return [=this=]'s <a>associated `Bluetooth`</a>.
4863+
48384864
# Integrations # {#integrations}
48394865

48404866
## Permissions Policy ## {#permissions-policy}
@@ -4850,11 +4876,31 @@ The <a>default allowlist</a> for this feature is <code>["self"]</code>.
48504876

48514877
For the purposes of user-agent automation and application testing, this document defines extensions to the [[WebDriver-BiDi]] specification.
48524878

4879+
The Web Bluetooth API and its extension specifications pose a challenge to test authors, as fully exercising those interfaces requires physical hardware devices that respond in predictable ways. To address this challenge this document defines a number of WebDriver-BiDi extension commands that allow defining and controlling simulated peripherals and advertisements that behave like physical device peripherals and their advertisements. These simulated peripherals and advertisements represent devices with particular properties and whose readings can be entirely defined by users.
4880+
4881+
Each [=navigable/top-level traversable=] may have a <dfn>simulated Bluetooth adapter</dfn>, that is a software defined Bluetooth adapter that has a set of discovered <a>simulated Bluetooth devices</a> and can assume roles like <a>Central</a>.
4882+
4883+
Each <a>simulated Bluetooth adapter</a> has a <dfn>simulated Bluetooth device mapping</dfn>, which is an <a>ordered map</a> of Bluetooth address {{strings}} to <a>simulated Bluetooth devices</a>.
4884+
4885+
Each <a>simulated Bluetooth adapter</a> has an <dfn>adapter state</dfn> that is a string enumeration describing the current state of the adapter. The possible enumeration values are:
4886+
* "powered-on"
4887+
* "powered-off"
4888+
* "absent"
4889+
4890+
A <dfn>simulated Bluetooth device</dfn> is a software defined [=Bluetooth device=] that behaves like a physical device, and may be attached to a <a>simulated Bluetooth adapter</a>, and may have associated properties like <a>Manufacturer Specific Data</a> and <a>Service UUIDs</a>.
4891+
48534892
Issue: CDDL snippetes use the "text" type instead of
48544893
"browsingContext.BrowsingContext" to allow indepedent programmatic
48554894
processing of CDDL snippets. Currently, other modules cannot be
48564895
referenced.
48574896

4897+
## Definitions ## {#bluetooth-bidi-definitions}
4898+
4899+
<pre class="cddl remote-cddl local-cddl">
4900+
bluetooth.BluetoothServiceUuid = text;
4901+
bluetooth.BluetoothManufacturerData = { key: uint, data: bstr };
4902+
</pre>
4903+
48584904
## The bluetooth module ## {#bluetooth-module}
48594905

48604906
The bluetooth module contains commands for managing the remote end Bluetooth behavior.
@@ -4938,6 +4984,35 @@ To <dfn>serialize prompt devices</dfn> given [=device prompt=] |prompt|:
49384984

49394985
</div>
49404986

4987+
#### The bluetooth.ScanRecord Type #### {#bluetooth-scanrecord-type}
4988+
4989+
<pre highlight="cddl" class="cddl remote-cddl local-cddl">
4990+
4991+
bluetooth.ScanRecord = {
4992+
? name: text,
4993+
? uuids: [ * bluetooth.BluetoothServiceUuid ],
4994+
? appearance: number,
4995+
? manufacturerData: [ * bluetooth.BluetoothManufacturerData ],
4996+
}
4997+
</pre>
4998+
4999+
A `bluetooth.ScanRecord` represents data of the advertisement packet sent by a [=Bluetooth device=].
5000+
5001+
<dl>
5002+
<dt><code>name</code></dt>
5003+
<dd>is the [=Bluetooth device=]'s local name, or a prefix of it.</dd>
5004+
5005+
<dt><code>uuids</code></dt>
5006+
<dd>lists the Service UUIDs that this scan record says the [=Bluetooth device=]'s GATT server supports.</dd>
5007+
5008+
<dt><code>appearance</code></dt>
5009+
<dd>is an <a>Appearance</a>, one of the values defined by the {{gap.appearance}} characteristic.</dd>
5010+
5011+
<dt><code>manufacturerData</code></dt>
5012+
<dd>list of <code>bluetooth.BluetoothManufacturerData</code> that maps {{unsigned short}} Company Identifier Codes to [=lists=] of {{octet}}s.</dd>
5013+
</dl>
5014+
5015+
49415016
### Errors ### {#bidi-errors}
49425017

49435018
This specification extends the set of [=error codes=] from
@@ -5013,6 +5088,177 @@ A [=local end=] could dismiss a prompt by sending the following message:
50135088
</pre>
50145089
</div>
50155090

5091+
#### The bluetooth.simulateAdapter Command #### {#bluetooth-simulateAdapter-command}
5092+
5093+
<pre highlight="cddl" class="cddl remote-cddl local-cddl">
5094+
bluetooth.simulateAdapter = (
5095+
method: "bluetooth.simulateAdapter",
5096+
params: bluetooth.SimulateAdapterParameters,
5097+
)
5098+
5099+
bluetooth.SimulateAdapterParameters = {
5100+
context: text,
5101+
state: "absent" / "powered-off" / "powered-on"
5102+
}
5103+
</pre>
5104+
5105+
<div algorithm="remote end steps for bluetooth.simulateAdapter">
5106+
The [=remote end steps=] with command parameters |params| are:
5107+
5108+
1. Let |contextId| be params["context"].
5109+
1. Let |navigable| be the result of [=trying=] to [=get a navigable=] with |contextId|.
5110+
1. If |navigable| is not a [=navigable/top-level traversable=], return [=error=] with [=error code=] [=invalid argument=].
5111+
1. Let |simulatedBluetoothAdapter| be a new [=simulated Bluetooth adapter=].
5112+
1. Set |simulatedBluetoothAdapter|'s <a>adapter state</a> to |params|[`"state"`].
5113+
1. Set |navigable|'s <a>simulated Bluetooth adapter</a> to |simulatedBluetoothAdapter|.
5114+
1. Return [=success=] with data `null`.
5115+
5116+
</div>
5117+
5118+
<div class="example">
5119+
A [=local end=] could simulate an adapter by sending the following message:
5120+
5121+
<pre highlight="json">
5122+
{
5123+
"method": "bluetooth.simulateAdapter",
5124+
"params": {
5125+
"context": "cxt-d03fdd81",
5126+
"state": "powered-on",
5127+
}
5128+
}
5129+
</pre>
5130+
</div>
5131+
5132+
#### The bluetooth.simulatePreconnectedPeripheral Command #### {#bluetooth-simulateconnectedperipheral-command}
5133+
5134+
<pre highlight="cddl" class="cddl remote-cddl local-cddl">
5135+
bluetooth.SimulatePreconnectedPeripheral = (
5136+
method: "bluetooth.simulatePreconnectedPeripheral",
5137+
params: bluetooth.SimulatePreconnectedPeripheralParameters,
5138+
)
5139+
5140+
bluetooth.SimulatePreconnectedPeripheralParameters = {
5141+
context: text,
5142+
address: text,
5143+
name: text,
5144+
manufacturerData: [ * bluetooth.BluetoothManufacturerData ],
5145+
knownServiceUuids: [ * bluetooth.BluetoothServiceUuid ]
5146+
}
5147+
</pre>
5148+
5149+
<div algorithm="remote end steps for bluetooth.simulatePreconnectedPeripheral">
5150+
The [=remote end steps=] with command parameters |params| are:
5151+
5152+
1. Let |contextId| be params["context"].
5153+
1. Let |navigable| be the result of [=trying=] to [=get a navigable=] with |contextId|.
5154+
1. If |navigable| is not a [=navigable/top-level traversable=], return [=error=] with [=error code=] [=invalid argument=].
5155+
1. Let |simulatedBluetoothAdapter| be |navigable|'s <a>simulated Bluetooth adapter</a>.
5156+
1. If |simulatedBluetoothAdapter| is empty, return [=error=] with [=error code=] [=invalid argument=].
5157+
1. Let |deviceAddress| be |params|[`"address"`].
5158+
1. Let |deviceMapping| be |simulatedBluetoothAdapter|'s <a>simulated Bluetooth device mapping</a>.
5159+
1. If |deviceMapping|[|deviceAddress|] [=map/exists=], return [=error=] with [=error code=] [=invalid argument=].
5160+
1. Let |simulatedBluetoothDevice| be a new [=simulated Bluetooth device=].
5161+
1. Set |simulatedBluetoothDevice|'s name to |params|[`"name"`].
5162+
1. Set |simulatedBluetoothDevice|'s address to |params|[`"address"`].
5163+
1. Set |simulatedBluetoothDevice|'s <a>manufacturer specific data</a> to |params|[`"manufacturerData"`].
5164+
1. Set |simulatedBluetoothDevice|'s <a>service UUIDs</a> to |params|[`"knownServiceUuids"`].
5165+
1. Set |deviceMapping|[|deviceAddress|] to |simulatedBluetoothDevice|.
5166+
1. Return [=success=] with data `null`.
5167+
5168+
</div>
5169+
5170+
<div class="example">
5171+
A [=local end=] could simulate a preconnected peripheral by sending the following message:
5172+
5173+
<pre highlight="json">
5174+
{
5175+
"method": "bluetooth.simulatePreconnectedPeripheral",
5176+
"params": {
5177+
"context": "cxt-d03fdd81",
5178+
"address": "09:09:09:09:09:09",
5179+
"name": "Some Device",
5180+
"manufacturerData": [ { key: 17, data: [0, 255, 1, 1, 127] } ],
5181+
"knownServiceUuids": [
5182+
"12345678-1234-5678-9abc-def123456789",
5183+
],
5184+
}
5185+
}
5186+
</pre>
5187+
</div>
5188+
5189+
#### The bluetooth.simulateAdvertisement Command #### {#bluetooth-simulateadvertisement-command}
5190+
5191+
<pre highlight="cddl" class="cddl remote-cddl local-cddl">
5192+
bluetooth.SimulateAdvertisement = (
5193+
method: "bluetooth.simulateAdvertisement",
5194+
params: bluetooth.SimulateAdvertisementParameters,
5195+
)
5196+
5197+
bluetooth.SimulateAdvertisementParameters = {
5198+
context: text,
5199+
scanEntry: bluetooth.SimulateAdvertisementScanEntryParameters
5200+
}
5201+
5202+
bluetooth.SimulateAdvertisementScanEntryParameters = {
5203+
deviceAddress: text,
5204+
rssi: number,
5205+
scanRecord: bluetooth.ScanRecord
5206+
}
5207+
5208+
</pre>
5209+
5210+
<div algorithm="remote end steps for bluetooth.simulateAdvertisement">
5211+
The [=remote end steps=] with command parameters |params| are:
5212+
5213+
1. Let |contextId| be params["context"].
5214+
1. Let |topLevelNavigable| be the result of [=trying=] to [=get a navigable=] with |contextId|.
5215+
1. If |topLevelNavigable| is not a [=navigable/top-level traversable=], return [=error=] with [=error code=] [=invalid argument=].
5216+
1. Let |scanEntry| be |params|[`"scanEntry"`].
5217+
1. Let |deviceAddress| be |scanEntry|[`"deviceAddress"`].
5218+
1. Let |simulatedBluetoothAdapter| be |topLevelNavigable|'s <a>simulated Bluetooth adapter</a>.
5219+
1. If |simulatedBluetoothAdapter| is empty, return [=error=] with [=error code=] [=invalid argument=].
5220+
1. Let |deviceMapping| be |simulatedBluetoothAdapter|'s <a>simulated Bluetooth device mapping</a>.
5221+
1. If |deviceMapping|[|deviceAddress|] [=map/exists=], let |simulatedDevice| be |deviceMapping|[|deviceAddress|]. Otherwise, let |simulatedDevice| be a new <a>simulated Bluetooth device</a> with |deviceAddress| and set |deviceMapping|[|deviceAddress|] to |simulatedDevice|.
5222+
1. If |topLevelNavigable| is currently executing the [=scan for devices=] algorithm,
5223+
insert <var>simulatedDevice</var> into
5224+
the <em>simulatedBluetoothDevices</em> variable within that algorithm.
5225+
5226+
Issue: Inserting data into variables from another algorithm is not well defined. The <a>scan for devices</a> algorithm needs to define asynchronous device discovery in order to match implementations.
5227+
1. Let |navigables| be the <a>inclusive descendant navigables</a> of |topLevelNavigable|'s <a>active document</a>.
5228+
1. For each |navigable| of |navigables|:
5229+
1. Let |document| be |navigable|'s <a>active document</a>.
5230+
1. <a>Queue a task</a> on |document|'s <a>relevant settings object</a>'s <a>responsible event loop</a> to do the following sub-steps:
5231+
1. Let |simulatedDeviceInstance| be the result of <a>get the <code>BluetoothDevice</code> representing</a> |simulatedDevice| inside |navigable|'s <a>active window</a>'s <a spec=HTML>associated <code>Navigator</code></a>'s [=associated Bluetooth=].
5232+
1. If |simulatedDeviceInstance|.{{[[watchAdvertisementsState]]}} is `not-watching`, abort these sub-steps.
5233+
1. <a>Fire an `advertisementreceived` event</a> for the advertising event represented by |scanEntry|[`"scanRecord"`], at |simulatedDeviceInstance|.
5234+
1. Return [=success=] with data `null`.
5235+
5236+
</div>
5237+
5238+
<div class="example">
5239+
A [=local end=] could simulate a device advertisement by sending the following message:
5240+
5241+
<pre highlight="json">
5242+
{
5243+
"method": "bluetooth.simulateAdvertisement",
5244+
"params": {
5245+
"context": "cxt-d03fdd81",
5246+
"scanEntry": {
5247+
"deviceAddress": "08:08:08:08:08:08",
5248+
"rssi": -10,
5249+
"scanRecord": {
5250+
"name": "Heart Rate",
5251+
"uuids": ["0000180d-0000-1000-8000-00805f9b34fb"],
5252+
"manufacturerData": [ { key: 17, data: [0, 255, 1, 1, 127] } ],
5253+
"appearance": 1,
5254+
"txPower": 1
5255+
}
5256+
}
5257+
}
5258+
}
5259+
</pre>
5260+
</div>
5261+
50165262
### Events ### {#bidi-events}
50175263

50185264
#### The bluetooth.requestDevicePromptUpdated Event #### {#bluetooth-requestdevicepromptupdated-event}

0 commit comments

Comments
 (0)