Title: Web Bluetooth Repository: WebBluetoothCG/web-bluetooth Status: CG-DRAFT ED: https://webbluetoothcg.github.io/web-bluetooth/ Shortname: web-bluetooth Level: 1 Translation: ja https://tkybpp.github.io/web-bluetooth-jp/ Editor: Reilly Grant 83788, Google LLC https://www.google.com, reillyg@google.com Editor: Ovidio Ruiz-Henríquez 106543, Google LLC https://www.google.com, odejesush@google.com Editor: See contributors on GitHub, , https://github.com/WebBluetoothCG/web-bluetooth/graphs/contributors Abstract: This document describes an API to discover and communicate with devices Abstract: over the Bluetooth 4 wireless standard using the Generic Attribute Profile (GATT). Group: web-bluetooth-cg !Participate: Join the W3C Community Group !Participate: Fix the text through GitHub !Participate: public-web-bluetooth@w3.org (archives) !Participate: IRC: #web-bluetooth on W3C's IRC Markup Shorthands: css no, markdown yes
{
"BLUETOOTH42": {
"href": "https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=286439",
"title": "BLUETOOTH SPECIFICATION Version 4.2",
"publisher": "Bluetooth SIG",
"date": "2 December 2014"
},
"BLUETOOTH-ASSIGNED": {
"href": "https://www.bluetooth.com/specifications/assigned-numbers/",
"title": "Assigned Numbers",
"status": "Living Standard",
"publisher": "Bluetooth SIG"
},
"BLUETOOTH-SUPPLEMENT6": {
"href": "https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=302735",
"title": "Supplement to the Bluetooth Core Specification Version 6",
"date": "14 July 2015",
"publisher": "Bluetooth SIG"
}
}
spec: assigned-numbers
type: enum; urlPrefix: https://github.com/WebBluetoothCG/registries/
urlPrefix: blob/master/gatt_assigned_characteristics.txt
text: body_sensor_location; url: #:~:text=body_sensor_location
text: gap.appearance; url: #:~:text=gap.appearance
text: heart_rate_control_point; url: #:~:text=heart_rate_control_point
text: heart_rate_measurement; url: #:~:text=heart_rate_measurement
text: ieee_11073-20601_regulatory_certification_data_list; url: #:~:text=ieee_11073-20601_regulatory_certification_data_list
urlPrefix: blob/master/gatt_assigned_descriptors.txt
text: gatt.characteristic_presentation_format; url: #:~:text=gatt.characteristic_presentation_format
text: gatt.client_characteristic_configuration; url: #:~:text=gatt.client_characteristic_configuration
urlPrefix: blob/master/gatt_assigned_services.txt
text: cycling_power; url: #:~:text=cycling_power
text: heart_rate; url: #:~:text=heart_rate
spec: BLUETOOTH-ASSIGNED
type: dfn
text: Shortened Local Name; url: https://www.bluetooth.com/specifications/assigned-numbers/#:~:text=Generic%20Access%20Profile
spec: ECMAScript; urlPrefix: https://tc39.github.io/ecma262/#
type: dfn
text: current realm; url: current-realm
text: fulfilled; url: sec-promise-objects
text: internal slot; url: sec-object-internal-methods-and-internal-slots
text: realm; url: sec-code-realms
type: method
text: Array.prototype.map; url: sec-array.prototype.map
type: interface
text: Array; url: sec-array-objects
text: ArrayBuffer; url: sec-arraybuffer-constructor
text: DataView; url: sec-dataview-constructor
text: Map; url: sec-map-constructor
text: Promise; url:sec-promise-objects
text: Set; url: sec-set-objects
text: TypeError; url: sec-native-error-types-used-in-this-standard-typeerror
text: TypedArray; url: sec-typedarray-constructors
spec: fingerprinting-guidance; urlPrefix: https://w3c.github.io/fingerprinting-guidance/#
type: dfn
text: fingerprinting surface; url: dfn-fingerprinting-surface
spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/browsing-the-web.html#
type: dfn
text: initializing the Document object; url: initialise-the-document-object
spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/document-sequences.html#
type: dfn
text: navigable; url: navigable
spec: WebIDL; urlPrefix: https://heycam.github.io/webidl/#
type: dfn
text: a copy of the bytes held; url: dfn-get-buffer-source-copy
spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/browsing-the-web.html/#
type: dfn
text: initializing a new Document object; url: dfn-initialise-the-document-object
spec: PAGE-VISIBILITY; urlPrefix: https://www.w3.org/TR/page-visibility-2/#
type: dfn
text: document visibility state; url: dom-visibilitystate
spec: WEBDRIVER; urlPrefix: https://w3c.github.io/webdriver/
type: dfn
text: local end; url: dfn-local-ends
text: invalid element state; url: dfn-invalid-element-state
spec:dom
type:dfn
text:children
for:/
text:document
spec: fingerprinting-guidance
type: dfn
text: fingerprinting surface
spec: html
type: dfn
text: allowed to use; for :/
text: associated navigator; for: /
text: browsing context; for: /
text: global object; for: /
text: navigable; for: /
spec: webidl
type: dfn
text: resolve
spec: webdriver
type: dfn
text: remote end steps
type: dfn
text: current browsing context
spec: webdriver2
type: dfn
text: success
text: error
# Introduction # {#introduction}
*This section is non-normative.*
Bluetooth is a standard for
short-range wireless communication between devices. Bluetooth "Classic" (BR/EDR)
defines a set of binary protocols and supports speeds up to about 24Mbps.
Bluetooth 4.0 introduced a new "Low Energy" mode known as "Bluetooth Smart",
BLE, or just LE
which is limited to about 1Mbps but allows devices to leave their transmitters
off most of the time. BLE provides most of its functionality through key/value
pairs provided by the Generic Attribute
Profile (GATT).
BLE defines multiple roles that devices can play. The Broadcaster and
Observer roles are for transmitter- and receiver-only applications,
respectively. Devices acting in the Peripheral role can receive
connections, and devices acting in the Central role can connect to
Peripheral devices.
A device acting in either the Peripheral or Central role can host
a GATT Server, which exposes a hierarchy of Services,
Characteristics, and Descriptors. See [[#information-model]] for
more details about this hierarchy. Despite being designed to support BLE
transport, the GATT protocol can also run over BR/EDR transport.
The first version of this specification allows web pages, running on a UA in the
Central role, to connect to GATT Servers over either a BR/EDR or
LE connection. While this specification cites the [[BLUETOOTH42]] specification,
it intends to also support communication among devices that only implement
Bluetooth 4.0 or 4.1.
## Examples ## {#introduction-examples}
let chosenHeartRateService = null; navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'], }] }).then(device => device.gatt.connect()) .then(server => server.getPrimaryService('heart_rate')) .then(service => { chosenHeartRateService = service; return Promise.all([ service.getCharacteristic('body_sensor_location') .then(handleBodySensorLocationCharacteristic), service.getCharacteristic('heart_rate_measurement') .then(handleHeartRateMeasurementCharacteristic), ]); }); function handleBodySensorLocationCharacteristic(characteristic) { if (characteristic === null) { console.log("Unknown sensor location."); return Promise.resolve(); } return characteristic.readValue() .then(sensorLocationData => { const sensorLocation = sensorLocationData.getUint8(0); switch (sensorLocation) { case 0: return 'Other'; case 1: return 'Chest'; case 2: return 'Wrist'; case 3: return 'Finger'; case 4: return 'Hand'; case 5: return 'Ear Lobe'; case 6: return 'Foot'; default: return 'Unknown'; } }).then(location => console.log(location)); } function handleHeartRateMeasurementCharacteristic(characteristic) { return characteristic.startNotifications() .then(char => { characteristic.addEventListener('characteristicvaluechanged', onHeartRateChanged); }); } function onHeartRateChanged(event) { const characteristic = event.target; console.log(parseHeartRate(characteristic.value)); }
parseHeartRate() would be defined using the
heart_rate_measurement documentation
to read the {{DataView}} stored in a {{BluetoothRemoteGATTCharacteristic}}'s
{{BluetoothRemoteGATTCharacteristic/value}} field.
function parseHeartRate(data) {
const flags = data.getUint8(0);
const rate16Bits = flags & 0x1;
const result = {};
let index = 1;
if (rate16Bits) {
result.heartRate = data.getUint16(index, /*littleEndian=*/true);
index += 2;
} else {
result.heartRate = data.getUint8(index);
index += 1;
}
const contactDetected = flags & 0x2;
const contactSensorPresent = flags & 0x4;
if (contactSensorPresent) {
result.contactDetected = !!contactDetected;
}
const energyPresent = flags & 0x8;
if (energyPresent) {
result.energyExpended = data.getUint16(index, /*littleEndian=*/true);
index += 2;
}
const rrIntervalPresent = flags & 0x10;
if (rrIntervalPresent) {
const rrIntervals = [];
for (; index + 1 < data.byteLength; index += 2) {
rrIntervals.push(data.getUint16(index, /*littleEndian=*/true));
}
result.rrIntervals = rrIntervals;
}
return result;
}
onHeartRateChanged() might log an object like
{
heartRate: 70,
contactDetected: true,
energyExpended: 750, // Meaning 750kJ.
rrIntervals: [890, 870] // Meaning .87s and .85s.
}
If the heart rate sensor reports the energyExpended field, the web
application can reset its value to 0 by writing to the
{{heart_rate_control_point}}
characteristic:
function resetEnergyExpended() {
if (!chosenHeartRateService) {
return Promise.reject(new Error('No heart rate sensor selected yet.'));
}
return chosenHeartRateService.getCharacteristic('heart_rate_control_point')
.then(controlPoint => {
const resetEnergyExpended = new Uint8Array([1]);
return controlPoint.writeValue(resetEnergyExpended);
});
}
navigator.bluetooth.{{getAvailability()}} exposes whether a
Bluetooth radio is available on the user's system, regardless of whether it is
powered on or not. The availability is also affected if the user has configured
the UA to block Web Bluetooth. Some users might consider this private, although
it's hard to imagine the damage that would result from revealing it. This
information also increases the UA's fingerprinting surface by a bit. This
function returns a {{Promise}}, so UAs have the option of asking the user what
value they want to return, but we expect the increased risk to be small enough
that UAs will choose not to prompt.
# Device Discovery # {#device-discovery}
{{Bluetooth/referringDevice}} gives access to the device from which the user
opened this page, if any. For example, an Eddystone beacon
might advertise a URL, which the UA allows the user to open. A
{{BluetoothDevice}} representing the beacon would be available through
navigator.bluetooth.{{referringDevice}}.
{{Bluetooth/getDevices()}} enables the page to retrieve Bluetooth devices that the user has granted access to.
{{Bluetooth/requestDevice(options)}} asks the user to grant this origin access to a device that matches any filter inoptions.filters but does not match any filter
in options.exclusionFilters.
To match a filter, the device has to:
* support all the GATT service UUIDs in the services list if that member is
present,
* have a name equal to name if that member is present,
* have a name starting with namePrefix if that member is present,
* advertise manufacturer specific data matching all of the values in
manufacturerData if
that member is present, and
*
Both Manufacturer Specific Data and Service Data map a key to an
array of bytes. {{BluetoothDataFilterInit}} filters these arrays. An array
matches if it has a |prefix| such that |prefix| & {{mask}} is
equal to {{dataPrefix}} & {{mask}}.
options.filters or in options.optionalServices. The origin is also
allowed to access any manufacturer data from manufacturer codes defined in
options.optionalManufacturerData from the
device's advertisement data.
This implies that if developers filter just by name, they must use
{{RequestDeviceOptions/optionalServices}} to get access to any services.
| Device | Advertised Services |
|---|---|
| D1 | A, B, C, D |
| D2 | A, B, E |
| D3 | C, D |
| D4 | E |
| D5 | <none> |
navigator.bluetooth.requestDevice({
filters: [ {services: [A, B]} ]
});
the user will be shown a dialog containing devices D1 and D2. If the user
selects D1, the website will not be able to access services C or D. If the user
selects D2, the website will not be able to access service E.
On the other hand, if the website calls
navigator.bluetooth.requestDevice({
filters: [
{services: [A, B]},
{services: [C, D]}
]
});
the dialog will contain devices D1, D2, and D3, and if the user selects D1, the
website will be able to access services A, B, C, and D.
If the website then calls
navigator.bluetooth.getDevices();the resulting {{Promise}} will resolve into an array containing device D1, and the website will be able to access services A, B, C, and D. The
optionalServices list doesn't add any devices to the dialog the
user sees, but it does affect which services the website can use from the device
the user picks.
navigator.bluetooth.requestDevice({
filters: [ {services: [A, B]} ],
optionalServices: \[E]
});
Shows a dialog containing D1 and D2, but not D4, since D4 doesn't contain the
required services. If the user selects D2, unlike in the first example, the
website will be able to access services A, B, and E.
If the website calls
navigator.bluetooth.getDevices();again, then the resulting {{Promise}} will resolve into an array containing the devices D1 and D2. The A, B, C, and D services will be accessible on device D1, while A, B, and E services will be accessible on device D2. The allowed services also apply if the device changes after the user grants access. For example, if the user selects D1 in the previous
requestDevice() call, and D1 later adds a new E service, that will
fire the {{serviceadded}} event, and the web page will be able to access service
E.
| Device | Advertised Device Name |
|---|---|
| D1 | First De… |
| D2 | <none> |
| D3 | Device Third |
| D4 | Device Fourth |
| D5 | Unique Name |
navigator.bluetooth.requestDevice({filters: filters}).
| filters | Devices | Notes |
|---|---|---|
[{name: "Unique Name"}]
|
D5 | |
[{namePrefix: "Device"}]
|
D3, D4 | |
[{name: "First De"},
{name: "First Device"}]
|
<none> | D1 only advertises a prefix of its name, so trying to match its whole name fails. |
[{namePrefix: "First"},
{name: "Unique Name"}]
|
D1, D5 | |
[{services: \[C],
namePrefix: "Device"},
{name: "Unique Name"}]
|
D3, D5 |
navigator.bluetooth.requestDevice({filters: filters,
exclusionFilters: exclusionFilters}).
| filters | exclusionFilters | Devices |
|---|---|---|
[{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 |
| Device | Manufacturer Data | Service Data |
|---|---|---|
| D1 | 17: 01 02 03 | |
| D2 | A: 01 02 03 |
navigator.bluetooth.requestDevice({filters: filters}).
| filters | Devices |
|---|---|
[{ 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> |
| Call | Notes |
|---|---|
requestDevice({})
|
Invalid: An absent list of filters doesn't accept any devices. |
requestDevice({filters:[]})
|
Invalid: An empty list of filters doesn't accept any devices. |
requestDevice({filters:[{}]})
|
Invalid: An empty filter accepts all devices, and so isn't allowed either. |
requestDevice({
acceptAllDevices:true
})
|
Valid: Explicitly accept all devices with {{acceptAllDevices}}. |
requestDevice({
filters: [...],
acceptAllDevices:true
})
|
Invalid: {{acceptAllDevices}} would override any {{filters}}. |
requestDevice({
exclusionFilters: [...],
acceptAllDevices:true
})
|
Invalid: {{acceptAllDevices}} would override any {{exclusionFilters}}. |
requestDevice({
exclusionFilters: [...]
})
|
Invalid: {{exclusionFilters}} require {{filters}}. |
requestDevice({
filters: [...],
exclusionFilters: []
})
|
Invalid: {{exclusionFilters}} must be non-empty to exclude devices. |
requestDevice({
filters: [{namePrefix: ""}]
})
|
Invalid: `namePrefix`, if present, must be non-empty to filter devices. |
requestDevice({
filters: [{manufacturerData: []}]
})
|
Invalid: {{BluetoothLEScanFilterInit/manufacturerData}}, if present, must be non-empty to filter devices. |
requestDevice({
filters: [{serviceData: []}]
})
|
Invalid: {{BluetoothLEScanFilterInit/serviceData}}, if present, must be non-empty to filter devices. |
| Internal Slot | Initial Value | Description (non-normative) |
|---|---|---|
| \[[deviceInstanceMap]] |
An empty map from Bluetooth devices
to {{BluetoothDevice}} instances.
|
Ensures only one {{BluetoothDevice}} instance represents each Bluetooth device inside a single global object. |
| \[[attributeInstanceMap]] | An empty map from Bluetooth cache entries to {{Promise}}s. | The {{Promise}}s resolve to either {{BluetoothRemoteGATTService}}, {{BluetoothRemoteGATTCharacteristic}}, or {{BluetoothRemoteGATTDescriptor}} instances. |
| \[[referringDevice]] | `null` | Set to a {{BluetoothDevice}} while initializing the `Document` object if the `Document` was opened from the device. |
navigator.bluetooth.referringDevice must return
{{[[referringDevice]]}}.
BluetoothDevice representing
referringDevice inside `navigator.bluetooth`, and let
|referringDeviceObj| be the result.
1. If the previous step threw an exception, abort these steps.
navigator.bluetooth.{{[[referringDevice]]}} to
|referringDeviceObj|.
filter.{{BluetoothLEScanFilterInit/name}} is
present then, if device's Bluetooth Device Name isn't
complete and equal to filter.name, return
`mismatch`.
1. If filter.{{BluetoothLEScanFilterInit/namePrefix}} is
present then if device's Bluetooth Device Name isn't
present or doesn't start with filter.namePrefix,
return `mismatch`.
1. For each uuid in
filter.{{BluetoothLEScanFilterInit/services}}, if
the UA has not received advertising data, an extended inquiry
response, or a service discovery response indicating that the device
supports a primary (vs included) service with UUID uuid, return
`mismatch`.
1. For each manufacturerData in
|filter|["{{BluetoothLEScanFilterInit/manufacturerData}}"], if
device hasn't advertised manufacturer specific data with a
company identifier code equal to
|manufacturerData|["{{BluetoothManufacturerDataFilterInit/companyIdentifier}}"]
and with data that matches
|manufacturerData| return `mismatch`.
1. For each serviceData in
|filter|["{{BluetoothLEScanFilterInit/serviceData}}"],
if device hasn't advertised service data with a UUID whose
128-bit form is
|serviceData|["{{BluetoothServiceDataFilterInit/service}}"] and
with data that matches
|serviceData| return `mismatch`.
1. Return `match`.
|filter|.{{BluetoothDataFilterInit/dataPrefix}}.
1. Let |mask| be a copy of the bytes held by
|filter|.{{BluetoothDataFilterInit/mask}}.
1. If |data| has fewer bytes than |expectedPrefix|, return `mismatch`.
1. For each `1` bit in |mask|, if the corresponding bit in |data| is not equal
to the corresponding bit in |expectedPrefix|, return `mismatch`.
1. Return `match`.
|filter1|.{{BluetoothDataFilterInit/mask}}[|byteIndex|] &
|filter2|.{{BluetoothDataFilterInit/mask}}[|byteIndex|]
is not equal to |filter2|.{{BluetoothDataFilterInit/mask}}[|byteIndex|], return `false`.
1. If |filter1|.{{BluetoothDataFilterInit/dataPrefix}}[|byteIndex|] &
|filter2|.{{BluetoothDataFilterInit/mask}}[|byteIndex|]
it not equal to |filter2|.{{BluetoothDataFilterInit/dataPrefix}}[|byteIndex|] &
|filter2|.{{BluetoothDataFilterInit/mask}}[|byteIndex|], return `false`.
1. Set |byteIndex| to |byteIndex| + 1.
1. Return `true`.
getDevices() method, when
invoked and given a {{BluetoothPermissionStorage}} |storage|, MUST run the
following steps:
1. Let |global| be [=this=]'s [=relevant global object=].
1. If |global|'s [=associated Document=] is not [=allowed to use=] the
[=policy-controlled feature=] named
"[=policy-controlled feature/bluetooth=]", return [=a promise rejected
with=] a {{SecurityError}}.
1. Let |promise| be [=a new promise=].
1. Run the following steps [=in parallel=]:
1. Let |devices| be a new empty {{Array}}.
1. For each |allowedDevice| in
|storage|.{{BluetoothPermissionStorage/allowedDevices}},
add the {{BluetoothDevice}} object representing
|allowedDevice|@{{[[device]]}} to |devices|.
1. [=Queue a global task=] on the [=Bluetooth task source=] given |global|
to [=resolve=] |promise| with |devices|.
|event|.device.gatt.{{BluetoothRemoteGATTServer/connect()}}.
requestDevice(options)
method, when invoked, MUST run the following steps:
1. If |options|.{{RequestDeviceOptions/exclusionFilters}} is
present and |options|.{{RequestDeviceOptions/filters}} is not present,
return [=a promise rejected with=] a {{TypeError}}.
1. If |options|.{{RequestDeviceOptions/filters}} is present and
|options|.{{RequestDeviceOptions/acceptAllDevices}} is `true`,
or if |options|.{{RequestDeviceOptions/filters}} is not present
and |options|.{{RequestDeviceOptions/acceptAllDevices}} is
`false`, return [=a promise rejected with=] a {{TypeError}}.
{{RequestDeviceOptions/acceptAllDevices}}:true is present.
|options|.{{RequestDeviceOptions/filters}} if
|options|.{{RequestDeviceOptions/acceptAllDevices}} is
`false` or `null` otherwise, passing
|options|.{{RequestDeviceOptions/exclusionFilters}} if it
exists or `null` otherwise, passing
|options|.{{RequestDeviceOptions/optionalServices}}, and
passing
|options|.{{RequestDeviceOptions/optionalManufacturerData}},
and let |devices| be the result.
1. [=Queue a global task=] on the [=Bluetooth task source=] given [=this=]'s
[=relevant global object=] to run the following steps:
1. If the previous step threw an exception, [=reject=] |promise| with that
exception and abort these steps.
1. If |devices| is an empty sequence, [=reject=] |promise| with a
{{NotFoundError}} and abort these steps.
1. [=Resolve=] |promise| with |devices|[0].
1. Return |promise|.
Check that the algorithm is triggered while its [=relevant global object=] has a transient activation, otherwise throw a {{SecurityError}} and abort these steps.
1. In order to convert the arguments from service names and aliases to just UUIDs, do the following sub-steps: 1. If|filters| !== null && |filters|.length === 0, throw a
{{TypeError}} and abort these steps.
1. If |exclusionFilters| !== null && |exclusionFilters|.length === 0, throw a
{{TypeError}} and abort these steps.
1. Let uuidFilters be a new {{Array}},
uuidExclusionFilters be a new {{Array}}, and
requiredServiceUUIDs be a new {{Set}}.
1. If |filters| is `null`, then set |requiredServiceUUIDs| to the set of all
UUIDs.
1. If |filters| isn't `null`, then for each |filter| in |filters|, do the
following steps:
1. Let |canonicalFilter| be the result of canonicalizing |filter|.
1. Append |canonicalFilter| to |uuidFilters|.
1. Add the contents of |canonicalFilter|.services to
|requiredServiceUUIDs|.
1. If |exclusionFilters| isn't `null`, then for each |exclusionFilter| in
|exclusionFilters|, do the following steps:
1. Let |canonicalExclusionFilter| be the result of canonicalizing |exclusionFilter|.
1. Append |canonicalExclusionFilter| to |uuidExclusionFilters|.
1. Let optionalServiceUUIDs be
{{Array.prototype.map}}.call(|optionalServices|,
{{BluetoothUUID/getService()|BluetoothUUID.getService}}).
1. If any of the {{BluetoothUUID/getService()|BluetoothUUID.getService()}}
calls threw an exception, throw that exception and abort these steps.
1. Remove from optionalServiceUUIDs any UUIDs that are
blocklisted.
1. Let |descriptor| be
{
name: "bluetooth",
filters: uuidFilters
optionalServices: optionalServiceUUIDs,
optionalManufacturerData: optionalManufacturerData
acceptAllDevices: filters !== null,
}
1. Let |state| be |descriptor|'s permission state.
Even if scanResult is empty, prompt the user to choose one of the devices in |scanResult|, associated with |descriptor|, and let |device| be the result. The UA MAY allow the user to select a nearby device that does not match uuidFilters.
BluetoothDevice representing device
inside [=this=], propagating any exception, and let
|deviceObj| be the result.
1. Return [|deviceObj|].
filter.{{BluetoothLEScanFilterInit/services}} is
present, do the following sub-steps:
1. If filter.services.length === 0, throw a
{{TypeError}} and abort these steps.
1. Let services be
{{Array.prototype.map}}.call(filter.services,
{{BluetoothUUID/getService()|BluetoothUUID.getService}}).
1. If any of the {{BluetoothUUID/getService()|BluetoothUUID.getService()}}
calls threw an exception, throw that exception and abort these steps.
1. If any service in services is blocklisted, throw a
{{SecurityError}} and abort these steps.
1. Set canonicalizedFilter.services to
services.
1. If filter.{{BluetoothLEScanFilterInit/name}} is
present, do the following sub-steps.
1. If the UTF-8 encoding of
filter.name is more than 248 bytes long, throw a
{{TypeError}} and abort these steps.
canonicalizedFilter.name to
filter.name.
1. If filter.{{BluetoothLEScanFilterInit/namePrefix}} is
present, do the following sub-steps.
1. If filter.namePrefix.length === 0 or if the UTF-8 encoding of
filter.namePrefix is more than 248 bytes long,
throw a {{TypeError}} and abort these steps.
canonicalizedFilter.namePrefix to
filter.namePrefix.
1. Set |canonicalizedFilter|["manufacturerData"] to `[]`.
1. If |filter|["{{BluetoothLEScanFilterInit/manufacturerData}}"] is
present and |filter|["manufacturerData"].length === 0,
throw a {{TypeError}} and abort these steps.
1. For each |manufacturerData| in
|filter|["{{BluetoothLEScanFilterInit/manufacturerData}}"],
do the following sub-steps:
1. If |manufacturerData| is a blocklisted manufacturer data filter, throw a
{{SecurityError}} and abort these steps.
1. If there exists an object |existing| in |canonicalizedFilter|["manufacturerData"]
where |existing|["companyIdentifier"] === |manufacturerData|["{{BluetoothManufacturerDataFilterInit/companyIdentifier}}"],
throw a {{TypeError}} and abort these steps.
1. Let |canonicalizedManufacturerDataFilter| be the result of canonicalizing |manufacturerData|,
converted to an ECMAScript value. If this throws an exception,
propagate that exception and abort these steps.
1. Set |canonicalizedManufacturerDataFilter|["companyIdentifier"] to
|manufacturerData|["{{BluetoothManufacturerDataFilterInit/companyIdentifier}}"].
1. Append |canonicalizedManufacturerDataFilter| to
|canonicalizedFilter|["manufacturerData"].
1. Set |canonicalizedFilter|.serviceData to `[]`.
1. If |filter|["{{BluetoothLEScanFilterInit/serviceData}}"] is
present and |filter|["serviceData"].length === 0,
throw a {{TypeError}} and abort these steps.
1. For each |serviceData| in
|filter|["{{BluetoothLEScanFilterInit/serviceData}}"],
do the following sub-steps:
1. Let |service| be
{{BluetoothUUID/getService()|BluetoothUUID.getService}}(|serviceData|["{{BluetoothServiceDataFilterInit/service}}"]).
If this throws an exception, propagate that exception and abort these
steps.
1. If |service| is blocklisted, throw a {{SecurityError}} and abort
these steps.
1. Let |canonicalizedServiceDataFilter| be the result of canonicalizing |serviceData|,
converted to an ECMAScript value. If this throws an exception,
propagate that exception and abort these steps.
1. Set |canonicalizedServiceDataFilter|["service"] to |service|.
1. Append |canonicalizedServiceDataFilter| to
|canonicalizedFilter|["serviceData"].
1. Return |canonicalizedFilter|.
|filter|.{{BluetoothDataFilterInit/dataPrefix}} is not
present, let |dataPrefix| be an empty sequence of bytes. Otherwise,
do the following sub-steps:
1. Let |dataPrefix| be a copy of the bytes held by
|filter|.dataPrefix.
1. If the length of |dataPrefix| is zero, throw a {{TypeError}} and abort
these steps.
1. If |filter|.{{BluetoothDataFilterInit/mask}} is present, let
|mask| be a copy of the bytes held by |filter|.mask.
Otherwise, let |mask| be a sequence of `0xFF` bytes the same length as
|dataPrefix|.
1. If |mask| is not the same length as |dataPrefix|, throw a {{TypeError}} and
abort these steps.
1. Return `{dataPrefix: new Uint8Array(|dataPrefix|), mask: new
Uint8Array(|mask|)}`.
requestDevice that this
API provides no way to interact with.
|storage|.{{BluetoothPermissionStorage/allowedDevices}} where
|device| is equal to |allowedDevice|@{{[[device]]}}. If one is
found, perform the following sub-steps:
1. Add the contents of
|allowedDevice|.{{AllowedBluetoothDevice/allowedServices}}
to |grantedServiceUUIDs|.
If one is not found, perform the following sub-steps:
1. Let |allowedDevice|.{{AllowedBluetoothDevice/deviceId}} be a unique ID to
the extent that the UA can determine that two Bluetooth connections are
the same device and to the extent that the
user wants to expose that fact to
script.
1. Set |allowedDevice|.{{AllowedBluetoothDevice/allowedServices}} to
|grantedServiceUUIDs|.
1. Set |allowedDevice|.{{AllowedBluetoothDevice/mayUseGATT}} to `true`.
|storage|.{{BluetoothPermissionStorage/allowedDevices}} where
|device| is equal to |allowedDevice|@{{[[device]]}}. If no such
element exists, abort these steps.
1. Remove |allowedDevice| from
|storage|.{{BluetoothPermissionStorage/allowedDevices}}.
navigator.permissions.{{Permissions/query()|query}}({name: "bluetooth", ...}) to retrieve those
devices after a reload.
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(...);
status.{{PermissionStatus/state}} to |desc|'s
permission state.
1. If |status|.{{PermissionStatus/state}} is "{{PermissionState/denied}}", set
|status|.devices to an empty {{FrozenArray}} and abort
these steps.
1. Let matchingDevices be a new {{Array}}.
1. Let |storage|, a {{BluetoothPermissionStorage}}, be "bluetooth"'s
extra permission data for the current settings object.
1. For each |allowedDevice| in |storage|.allowedDevices, run the
following sub-steps:
1. If desc.deviceId is set and
allowedDevice.deviceId !=
desc.deviceId, continue to the next
allowedDevice.
1. If desc.filters is set, do the following sub-steps:
1. Replace each filter in |desc|.filters with the
result of canonicalizing
it. If any of these canonicalizations throws an error, return
that error and abort these steps.
1. If allowedDevice.{{[[device]]}} does not
match a filter in |desc|.filters, continue
to the next allowedDevice.
1. Get the `BluetoothDevice` representing
allowedDevice.{{[[device]]}} within
global.navigator.bluetooth, and add the
result to matchingDevices.
|desc|.optionalServices and
|desc|.optionalManufacturerData fields do not affect
the result.
status.devices to a new {{FrozenArray}} whose
contents are matchingDevices.
|storage|.{{allowedDevices}} such that:
* |allowedDevice|.{{[[device]]}} is the same
device as
|deviceObj|.{{[[representedDevice]]}}, and
* |allowedDevice|.{{AllowedBluetoothDevice/deviceId}} ===
|deviceObj|.{{BluetoothDevice/id}},
then update |deviceObj|.{{[[allowedServices]]}} to be
|allowedDevice|.{{allowedServices}}, and continue to
the next |deviceObj|.
1. Otherwise, detach |deviceObj| from its device by running the
remaining steps.
1. Call deviceObj.gatt.{{BluetoothRemoteGATTServer/disconnect()}}.
deviceObj.{{[[representedDevice]]}} to
`null`.
const bluetoothUI = document.querySelector('#bluetoothUI');
navigator.bluetooth.getAvailability().then(isAvailable => {
bluetoothUI.hidden = !isAvailable;
});
navigator.bluetooth.addEventListener('availabilitychanged', e => {
bluetoothUI.hidden = !e.value;
});
getAvailability() method,
when invoked, MUST return a new promise |promise| and run the following
steps in parallel:
1. Let |global| be [=this=]'s [=relevant global object=].
1. If |global|'s [=associated Document=] is not [=allowed to use=] the
[=policy-controlled feature=] named
"[=policy-controlled feature/bluetooth=]", [=queue a global task=] on
the [=Bluetooth task source=] given |global| to [=resolve=] |promise|
with `false`, and abort these steps.
1. If the user has configured the UA to return a particular answer from this function for the current origin, queue a task to resolve |promise| with the configured answer, and abort these steps.
getAvailability promise with false, the following can be used to
detect when Bluetooth is available again to show Bluetooth 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();
// Bluetooth is blocked, listen for change in PermissionStatus.
status.onchange = () => {
if (this.state !== 'denied') checkAvailability();
};
});
Navigator.
1. Let |bluetooth| be |navigator|'s associated
Bluetooth.
1. [=Fire an event=] named {{availabilitychanged}} using the {{ValueEvent}}
interface at |bluetooth| with its {{ValueEvent/value}} attribute
initialized to |newAvailability|.
[
Exposed=Window,
SecureContext
]
interface ValueEvent : Event {
constructor(DOMString type, optional ValueEventInit initDict = {});
readonly attribute any value;
};
dictionary ValueEventInit : EventInit {
any value = null;
};
{{ValueEvent}} instances are constructed as specified in
[[DOM#constructing-events]].
The value attribute must return the value
it was initialized to.
Issue: Such a generic event type belongs in [[HTML]] or [[DOM]], not here.
# Device Representation # {#device-representation}
The UA needs to track Bluetooth device properties at several levels: globally,
per origin, and per global object.
## Global Bluetooth device properties ## {#global-device-properties}
The physical Bluetooth device may be guaranteed to have some properties that the
UA may not have received. Those properties are described as optional here.
A Bluetooth device has the following properties. Optional
properties are not present, and sequence and map properties are empty,
unless/until described otherwise. Other properties have a default specified or
are specified when a device is introduced.
* A set of supported physical transports, including one or both of
BR/EDR and LE. This set will generally be filled based on the transports
over which the device was discovered and the Flags Data Type in the
Advertising Data or Extended Inquiry Response.
* One or more of several kinds of 48-bit address: a Public Bluetooth
Address, a (random) Static Address, and a resolvable or
non-resolvable Private Address.
* An optional 128-bit Identity Resolving Key.
* An optional partial or complete Bluetooth Device Name. A device has a
partial name when the Shortened Local Name AD data was received, but
the full name hasn't been read yet. The Bluetooth Device Name is
encoded as UTF-8 and converted to a DOMString using the utf-8 decode
without BOM algorithm.
* An optional ATT Bearer, over which all GATT communication happens. The
ATT Bearer is created by procedures described in "Connection
Establishment" under GAP Interoperability Requirements. It is
disconnected in ways [[BLUETOOTH42]] isn't entirely clear about.
* A list of advertised Service UUIDs from the Advertising Data or
Extended Inquiry Response.
* A hierarchy of GATT Attributes.
The UA SHOULD determine that two Bluetooth devices are the same Bluetooth device if and only if they have the
same Public Bluetooth Address, Static Address, Private
Address, or Identity Resolving Key, or if the Resolvable Private
Address Resolution Procedure succeeds using one device's IRK and the other's
Resolvable Private Address. However, because platform APIs don't document
how they determine device identity, the UA MAY use another procedure.
## BluetoothDevice ## {#bluetoothdevice-interface}
A {{BluetoothDevice}} instance represents a Bluetooth device for a
particular global object (or, equivalently, for a particular Realm
or {{Bluetooth}} instance).
| Internal Slot | Initial Value | Description (non-normative) |
|---|---|---|
| \[[context]] | <always set in prose> | The {{Bluetooth}} object that returned this {{BluetoothDevice}}. |
| \[[representedDevice]] | <always set in prose> | The Bluetooth device this object represents, or `null` if access has been revoked. |
| \[[gatt]] | a new {{BluetoothRemoteGATTServer}} instance with its {{BluetoothRemoteGATTServer/device}} attribute initialized to `this` and its {{BluetoothRemoteGATTServer/connected}} attribute initialized to `false`. | Does not change. |
| \[[allowedServices]] | <always set in prose> | This device's {{AllowedBluetoothDevice/allowedServices}} list for this origin or `"all"` if all services are allowed. For example, a UA may grant an origin access to all services on a {{referringDevice}} that advertised a URL on that origin. |
| \[[allowedManufacturerData]] | <always set in prose> | This device's {{AllowedBluetoothDevice/allowedManufacturerData}} list for this origin. |
| \[[watchAdvertisementsState]] | `'not-watching'` | An string enumeration describing the current state of a {{BluetoothDevice/watchAdvertisements()}} operation. The possible enumeration values are: * `'not-watching'` * `'pending-watch'` * `'watching'` |
BluetoothDevice representing a
Bluetooth device device inside a {{Bluetooth}} instance
context, the UA MUST run the following steps:
1. Let |storage|, a {{BluetoothPermissionStorage}}, be "bluetooth"'s
extra permission data for the current settings object.
1. Find the |allowedDevice| in |storage|.{{allowedDevices}} with
allowedDevice.{{[[device]]}} the same device
as device. If there is no such object, throw a {{SecurityError}}
and abort these steps.
1. If there is no key in context.{{Bluetooth/[[deviceInstanceMap]]}}
that is the same device as device, run the following
sub-steps:
1. Let result be a new instance of {{BluetoothDevice}}.
1. Initialize all of result's optional fields to
null.
1. Initialize result.{{[[context]]}} to
context.
1. Initialize result.{{[[representedDevice]]}} to
device.
1. Initialize result.id to
allowedDevice.{{AllowedBluetoothDevice/deviceId}},
and initialize
result.{{BluetoothDevice/[[allowedServices]]}}
to allowedDevice.{{allowedServices}}.
1. If device has a partial or complete Bluetooth Device
Name, set result.name to that string.
1. Initialize result.watchingAdvertisements to
`false`.
1. Add a mapping from device to result in
context.{{Bluetooth/[[deviceInstanceMap]]}}.
1. Return the value in context.{{Bluetooth/[[deviceInstanceMap]]}}
whose key is the same device as device.
gatt
attribute MUST perform the following steps:
1. If "bluetooth"'s extra permission data for `this`'s relevant
settings object has an {{AllowedBluetoothDevice}} |allowedDevice| in its
{{BluetoothPermissionStorage/allowedDevices}} list with
|allowedDevice|.{{AllowedBluetoothDevice/[[device]]}} the
same device as this.{{[[representedDevice]]}} and
|allowedDevice|.{{AllowedBluetoothDevice/mayUseGATT}} equal to
`true`, return this.{{[[gatt]]}}.
1. Otherwise, return `null`.
forget() method, when
invoked, MUST return a new promise |promise| and run the following steps:
1. Let |device| be the target {{BluetoothDevice}} object.
1. Let |storage| be the {{BluetoothPermissionStorage}} object in the current
script execution environment.
1. Remove |device| from storage with |storage|.
1. Resolve |promise|.
watchAdvertisements(|options|)
method, when invoked, MUST return a new promise |promise| and
run the following steps:
1. Let |global| be the [=relevant global object=] of [=this=].
1. If |options|.{{AbortSignal|signal}} is present, then perform
the following sub-steps:
1. If |options|.{{AbortSignal|signal}} is
[=AbortSignal/aborted=], then [=abort watchAdvertisements=] with `this`
and abort these steps.
1. [=AbortSignal/add|Add the following abort steps=] to
|options|.{{AbortSignal|signal}}:
1. [=Abort watchAdvertisements=] with `this`.
1. [=Reject=] |promise| with {{AbortError}}
1. If this.{{[[watchAdvertisementsState]]}} is:
this.{{[[watchAdvertisementsState]]}} to
`'pending-watch'`.
1. [=parallel queue/Enqueue the following steps=] to the [=watch
advertisements manager=], but abort when
this.{{[[watchAdvertisementsState]]}} becomes
`not-watching`:
1. Ensure that the UA is scanning for this device's
advertisements. The UA SHOULD NOT filter out "duplicate"
advertisements for the same device.
1. If the UA fails to enable scanning, [=queue a global task=]
on the [=Bluetooth task source=] given |global| to perform
the following steps, and abort these steps:
1. Set this.{{[[watchAdvertisementsState]]}} to
`'not-watching'`.
1. [=Reject=] |promise| with one of the following errors:
this.{{[[watchAdvertisementsState]]}} becomes
`not-watching`:
1. Set this.{{[[watchAdvertisementsState]]}} to
`watching`.
1. Set this.{{watchingAdvertisements}} to `true`.
1. [=Resolve=] |promise| with `undefined`.
: `'pending-watch'`
:: 1. [=Reject=] |promise| with {{InvalidStateError}}.
: `'watching'`
:: 1. [=Resolve=] |promise| with `undefined`.
this.{{[[watchAdvertisementsState]]}} to
`'not-watching'`.
1. Set |device|.{{watchingAdvertisements}} to `false`.
1. [=parallel queue/Enqueue the following steps=] to [=watch advertisements
manager=]:
1. If no more {{BluetoothDevice}}s in the whole UA have
{{watchingAdvertisements}} set to `true`, the UA SHOULD stop scanning
for advertisements. Otherwise, if no more {{BluetoothDevice}}s
representing the same device as `this` have {{watchingAdvertisements}}
set to `true`, the UA SHOULD reconfigure the scan to avoid receiving
reports for this device.
"visible", scanning operations for that [=document=] need to be
aborted.
Navigator.
1. Let |bluetooth| be |navigator|'s associated
Bluetooth.
1. If |visibilityState| is not "visible", then [=abort all
active watchAdvertisements=] operations on |bluetooth|.
Navigator.
1. Let |bluetooth| be |navigator|'s associated
Bluetooth.
1. Run [=abort all active watchAdvertisements=] operations on |bluetooth|.
this.txPower - this.rssi.
rssi is the power at which the advertisement was received, measured
in dBm. This is used to compute the path loss as this.txPower -
this.rssi.
manufacturerData maps unsigned short Company Identifier
Codes to {{DataView}}s.
serviceData maps {{UUID}}s to {{DataView}}s.
requestDevice dialog.
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});
});
The format of iBeacon advertisements was derived from How do iBeacons
work? by Adam Warski.
deviceObj.{{[[representedDevice]]}}, queue a
task on deviceObj's relevant settings object's
responsible event loop to do the following sub-steps:
1. If deviceObj.{{watchingAdvertisements}} is
`false`, abort these sub-steps.
1. Fire an `advertisementreceived` event for the advertising event at
|deviceObj|.
{
bubbles: true,
device: deviceObj,
uuids: [],
manufacturerData: new Map(),
serviceData: new Map()
}
1. If the received signal strength is available for any packet in |adv|,
set event.rssi to this signal strength in dBm.
1. For each AD structure in |adv|'s advertising packet and scan response,
select from the following steps depending on the AD type:
|uuid| in the listed UUIDs, if it is in
this.device.{{BluetoothDevice/[[allowedServices]]}}, then
append |uuid| to event.uuids.
event.name to the result.
|manufacturerCode|, if it is in
this.device.{{BluetoothDevice/[[allowedManufacturerData]]}},
and the manufacturer data is not a blocklisted manufacturer data
then add a mapping of |manufacturerCode| to an
{{ArrayBuffer}} containing the manufacturer-specific data to
event.manufacturerData.
event.txPower to the AD data.
|uuid| in the service data, if it is
in this.device.{{BluetoothDevice/[[allowedServices]]}},
then add a mapping of |uuid| to an {{ArrayBuffer}} containing the
service data to event.serviceData.
event.appearance to the AD data.
init.uuids is set, initialize
event.uuids to a new {{FrozenArray}} containing the
elements of init.uuids.map(
{{BluetoothUUID/getService()|BluetoothUUID.getService}}).
Otherwise initialize event.uuids to an empty
{{FrozenArray}}.
1. For each mapping in init.manufacturerData:
1. Let code be the key converted to an {{unsigned short}}.
1. Let value be the value.
1. If value is not a {{BufferSource}}, throw a {{TypeError}}.
1. Let bytes be a new read only ArrayBuffer containing
a copy of the bytes held by value.
1. Add a mapping from code to new
DataView(bytes) in
event.manufacturerData.{{BluetoothManufacturerDataMap/[[BackingMap]]}}.
1. For each mapping in init.serviceData:
1. Let key be the key.
1. Let service be the result of calling
BluetoothUUID.{{BluetoothUUID/getService()|getService}}(key).
1. Let value be the value.
1. If value is not a {{BufferSource}}, throw a {{TypeError}}.
1. Let bytes be a new read only ArrayBuffer containing
a copy of the bytes held by value.
1. Add a mapping from service to new
DataView(bytes) in
event.serviceData.{{BluetoothServiceDataMap/[[BackingMap]]}}.
1. Return event.
Instances of {{BluetoothManufacturerDataMap}} have a `[[BackingMap]]` slot because they are [=maplike=], which maps manufacturer codes to the manufacturer's data, converted to {{DataView}}s.
Instances of {{BluetoothServiceDataMap}} have a `[[BackingMap]]` slot because they are [=maplike=], which maps service UUIDs to the service's data, converted to {{DataView}}s.
A Service contains a collection of Included Services and Characteristics. The Included Services are references to other Services, and a single Service can be included by more than one other Service. Services are known as Primary Services if they appear directly under the GATT Server, and Secondary Services if they're only included by other Services, but Primary Services can also be included.
A Characteristic contains a value, which is an array of bytes, and a collection of Descriptors. Depending on the properties of the Characteristic, a GATT Client can read or write its value, or register to be notified when the value changes.
Finally, a Descriptor contains a value (again an array of bytes) that describes or configures its Characteristic.Promise<{{BluetoothRemoteGATTService}}>,
Promise<{{BluetoothRemoteGATTCharacteristic}}>, or
Promise<{{BluetoothRemoteGATTDescriptor}}> instance for each
{{Bluetooth}} instance.
serviceA.getCharacteristic(uuid1) function with an initially empty
Bluetooth cache, the UA uses the Discover Characteristics by UUID
procedure to fill the needed cache entries, and the UA ends the procedure early
because it only needs one Characteristic to fulfil the returned {{Promise}},
then the first Characteristic with UUID uuid1 inside
serviceA is known-present, and any subsequent Characteristics with
that UUID remain unknown. If the user later calls
serviceA.getCharacteristics(uuid1), the UA needs to resume or
restart the Discover Characteristics by UUID procedure. If it turns out
that serviceA only has one Characteristic with UUID
uuid1, then the subsequent Characteristics become known-absent.
Promise<BluetoothGATT*> instance in
|context|.{{Bluetooth/[[attributeInstanceMap]]}}, create a
BluetoothRemoteGATTService representing |entry|,
create a BluetoothRemoteGATTCharacteristic
representing |entry|, or create a
BluetoothRemoteGATTDescriptor representing |entry|,
depending on whether |entry| is a Service, Characteristic, or
Descriptor, and add a mapping from |entry| to the resulting {{Promise}}
in |context|.{{Bluetooth/[[attributeInstanceMap]]}}.
1. Append to |result| the Promise<BluetoothGATT*>
instance associated with |entry| in
|context|.{{Bluetooth/[[attributeInstanceMap]]}}.
1. [=Queue a global task=] on the [=Bluetooth task source=] given |global| to
[=resolve=] |promise| with the result of [=waiting for all=] elements of
|result|.
|obj|.{{[[representedDevice]]}}|obj|.{{[[representedService]]}}|obj|.{{[[representedCharacteristic]]}}|obj|.{{[[representedDescriptor]]}}(DOMString or unsigned int),("all" or Array<DOMString>),attributeattribute.{{BluetoothRemoteGATTService/device}}
attribute.{{BluetoothRemoteGATTCharacteristic/service}}.{{BluetoothRemoteGATTService/device}}
deviceObj.gatt.{{BluetoothRemoteGATTServer/connected}}
is `false`, return a promise rejected with with a {{NetworkError}}
and abort these steps.
1. If Represented(|attribute|) is `null`, return a promise rejected
with an {{InvalidStateError}} and abort these steps.
Note: This happens when a Service or Characteristic is removed from the
device or invalidated by a disconnection, and then its object is used again.
1. Query the Bluetooth cache in deviceObj for
entries that:
* are within Represented(|attribute|),
* have a type described by child type,
* have a UUID that is not blocklisted,
* if uuid is present, have a UUID of uuid,
* if allowedUuids is present and not `"all"`, have a UUID in
allowedUuids, and
* if the single flag is set, are the first of these.
Let promise be the result.
1. Upon fulfillment of promise with |result|, run the
following steps:
* If |result| is empty, throw a {{NotFoundError}}.
* Otherwise, if the single flag is set, returns the first (only)
element of |result|.
* Otherwise, return |result|.
x ===
y returns whether the objects represent the same
attribute, because of how the query the Bluetooth cache algorithm
creates and caches new objects.
this.device. It can be false while the UA is physically connected,
for example when there are other connected {{BluetoothRemoteGATTServer}}
instances for other global objects.
server.{{BluetoothRemoteGATTServer/disconnect()}}.
navigator.bluetooth.{{Bluetooth/[[deviceInstanceMap]]}}, this can't
happen at least until navigation releases the global object or closing the tab
or window destroys the browsing context.
| Internal Slot | Initial Value | Description (non-normative) |
|---|---|---|
| \[[activeAlgorithms]] | new {{Set}}() |
Contains a {{Promise}} corresponding to each algorithm using this server's connection. {{disconnect()}} empties this set so that the algorithm can tell whether its realm was ever disconnected while it was running. |
| \[[automatedGATTConnectionResponse]] | "not-expected" |
The simulated GATT connection response code for a GATT connection attempt. |
connect()
method, when invoked, MUST perform the following steps:
1. Let |global| be [=this=]'s [=relevant global object=].
1. If [=this=].{{BluetoothRemoteGATTServer/device}}.{{[[representedDevice]]}}
is `null`, return [=a promise rejected with=] a "{{NetworkError}}"
{{DOMException}}.
1. If the UA is currently using the Bluetooth system, it MAY return [=a promise
rejected with=] a "{{NetworkError}}" {{DOMException}}.
Issue(188): Implementations may be able to avoid this {{NetworkError}},
but for now sites need to serialize their use of this API
and/or give the user a way to retry failed operations.
1. Let |promise| be [=a new promise=].
1. If [=this=].{{BluetoothRemoteGATTServer/connected}} is `true`,
[=resolve=] |promise| with [=this=] and return |promise|.
1. Add |promise| to [=this=].{{[[activeAlgorithms]]}}.
1. Run the following steps [=in parallel=]:
1. If |global|'s [=Window/navigable=]'s
[=navigable/top-level traversable=]'s simulated Bluetooth adapter
is not empty, run the following steps:
1. [=Trigger a gatt connection attempted event=] given |global|'s
[=Window/navigable=] and [=this=].{{BluetoothRemoteGATTServer/device}}.
1. If [=this=].{{[[automatedGATTConnectionResponse]]}} is `"not-expected"`,
set it to `"expected"`.
1. If [=this=].{{[[automatedGATTConnectionResponse]]}} is `"expected"`,
wait for it to change.
1. Let |response| be [=this=].{{[[automatedGATTConnectionResponse]]}}.
1. Set [=this=].{{[[automatedGATTConnectionResponse]]}} to `"not-expected"`.
1. If |response| is not `0`, do the
following sub-steps:
1. Remove |promise| from [=this=].{{[[activeAlgorithms]]}}.
1. [=Queue a global task=] on the [=Bluetooth task source=] given
|global| to [=reject=] |promise| with a "{{NetworkError}}"
{{DOMException}} and abort these steps.
1. Otherwise, run the following steps:
1. If [=this=].{{BluetoothRemoteGATTServer/device}}.{{[[representedDevice]]}}
has no [=ATT Bearer=], do the following sub-steps:
1. Attempt to create an ATT Bearer using the procedures described in "Connection Establishment" under GAP Interoperability Requirements. Abort this attempt if |promise| is removed from [=this=].{{[[activeAlgorithms]]}}.
disconnect()
method, when invoked, MUST perform the following steps:
1. Clear this.{{[[activeAlgorithms]]}} to abort any active `connect()` calls.
1. If this.{{connected}} is false, abort these steps.
1. Clean up the disconnected device this.device.
1. Let device be this.device.{{[[representedDevice]]}}.
1. Garbage-collect the connection of |device|.
deviceObj in the whole UA:
1. If deviceObj.{{[[representedDevice]]}} is not the
same device as device, continue to the next
|deviceObj|.
1. If
deviceObj.gatt.{{BluetoothRemoteGATTServer/connected}}
is `true`, abort this algorithm.
1. If deviceObj.gatt.{{[[activeAlgorithms]]}}
contains the {{Promise}} of a call to {{connect()}}, abort this
algorithm.
1. Destroy device's ATT Bearer.
gattServer.connected is `true`, add
promise to
gattServer.{{BluetoothRemoteGATTServer/[[activeAlgorithms]]}}.
1. React to promise:
* If |promise| was fulfilled with value |result|, then:
1. If promise is in
gattServer.{{BluetoothRemoteGATTServer/[[activeAlgorithms]]}},
remove it and return |result|.
1. Otherwise, throw a {{NetworkError}}.
gattServer.{{BluetoothRemoteGATTServer/[[activeAlgorithms]]}},
remove it and throw |error|.
1. Otherwise, throw a {{NetworkError}}.
getPrimaryService(service)
method, when invoked, MUST perform the following steps:
1. If this.device.{{BluetoothDevice/[[allowedServices]]}} is not
`"all"` and service is not in
this.device.{{BluetoothDevice/[[allowedServices]]}}, return
a promise rejected with a {{SecurityError}} and abort these steps.
1. Return GetGATTChildren(attribute=this.device,service,this.device.{{BluetoothDevice/[[allowedServices]]}},getPrimaryServices(service)
method, when invoked, MUST perform the following steps:
1. If this.device.{{BluetoothDevice/[[allowedServices]]}} is not
`"all"`, and service is present and not in
this.device.{{BluetoothDevice/[[allowedServices]]}}, return
a promise rejected with a {{SecurityError}} and abort these steps.
1. Return GetGATTChildren(attribute=this.device,service,this.device.{{BluetoothDevice/[[allowedServices]]}},'0000180d-0000-1000-8000-00805f9b34fb' for the Heart Rate service.
isPrimary indicates whether the type of this service is primary or
secondary.
| Internal Slot | Initial Value | Description (non-normative) |
|---|---|---|
| \[[representedService]] | <always set in prose> | The Service this object represents, or `null` if the Service has been removed or otherwise invalidated. |
BluetoothRemoteGATTService representing a
Service service, the UA must run the following steps:
1. Let |global| be [=this=]'s [=relevant global object=].
1. Let |promise| be [=a new promise=].
1. Run the following steps in parallel:
1. Let |result| be a new instance of {{BluetoothRemoteGATTService}}
with its {{[[representedService]]}} slot initialized to |service|.
1. Get the BluetoothDevice representing the device in
which |service| appears, and let |device| be the result.
1. If the previous step threw an error, [=queue a global task=] on the
[=Bluetooth task source=] given |global| to [=reject=] |promise| with
that error and abort these steps.
1. Initialize |result|.{{BluetoothRemoteGATTService/device}} from |device|.
1. Initialize |result|.{{BluetoothRemoteGATTService/uuid}} from the UUID of
|service|.
1. If |service| is a Primary Service, initialize
|result|.{{BluetoothRemoteGATTService/isPrimary}} to `true`. Otherwise,
initialize |result|.{{BluetoothRemoteGATTService/isPrimary}} to
`false`.
1. [=Queue a global task=] on the [=Bluetooth task source=] given |global|
to [=resolve=] |promise| with |result|.
1. Return |promise|.
getCharacteristic(characteristic) method retrieves a
Characteristic inside this Service. When invoked, it MUST return
> GetGATTChildren(attribute=this,characteristic,undefined,
getCharacteristics(characteristic) method retrieves a
list of Characteristics inside this Service. When invoked, it MUST return
> GetGATTChildren(attribute=this,characteristic,undefined,
getIncludedService(service) method retrieves an
Included Service inside this Service. When invoked, it MUST return
> GetGATTChildren(attribute=this,service,undefined,
getIncludedServices(service) method retrieves a list of
Included Services inside this Service. When invoked, it MUST return
> GetGATTChildren(attribute=this,service,undefined,'00002a37-0000-1000-8000-00805f9b34fb' for the
Heart Rate Measurement characteristic.
properties holds the properties of this characteristic.
value is the currently cached characteristic value. This value gets
updated when the value of the characteristic is read or updated via a
notification or indication.
| Internal Slot | Initial Value | Description (non-normative) |
|---|---|---|
| \[[representedCharacteristic]] | <always set in prose> | The Characteristic this object represents, or `null` if the Characteristic has been removed or otherwise invalidated. |
| \[[automatedCharacteristicReadResponse]] | "not-expected" |
The simulated GATT characteristic response code for a GATT characteristic read attempt. |
| \[[automatedCharacteristicReadResponseData]] | Empty byte sequence | The simulated GATT characteristic response data for a GATT characteristic read attempt. |
| \[[automatedCharacteristicWriteResponse]] | "not-expected" |
The simulated GATT characteristic response code for a GATT characteristic write attempt. |
| \[[automatedCharacteristicSubscribeToNotificationsResponse]] | "not-expected" |
The simulated GATT characteristic response code for an attempt to subscribe to GATT characteristic notifications. |
| \[[automatedCharacteristicUnsubscribeFromNotificationsResponse]] | "not-expected" |
The simulated GATT characteristic response code for an attempt to unsubscribe from GATT characteristic notifications. |
BluetoothRemoteGATTCharacteristic
representing a Characteristic |characteristic|, the UA must
run the following steps:
1. Let |global| be [=this=]'s [=relevant global object=].
1. Let |promise| be [=a new promise=].
1. Run the following steps [=in parallel=]:
1. Let |result| be a new instance of
{{BluetoothRemoteGATTCharacteristic}} with its
{{[[representedCharacteristic]]}} slot initialized to |characteristic|.
1. Initialize |result|.{{BluetoothRemoteGATTCharacteristic/service}}< from the
{{BluetoothRemoteGATTService}} instance representing the Service in which
|characteristic| appears.
1. Initialize |result|.{{BluetoothRemoteGATTCharacteristic/uuid}} from the UUID of
|characteristic|.
1. Create a BluetoothCharacteristicProperties instance from the
Characteristic |characteristic|, and let |properties| be the result.
1. If the previous step returned an error, [=queue a global task=] on the
[=Bluetooth task source=] given |global| to [=reject=] |promise| with
that error and abort these steps.
1. Initialize |result|.{{BluetoothRemoteGATTCharacteristic/properties}} to
|properties|.
1. Initialize |result|.{{BluetoothRemoteGATTCharacteristic/value}} to
null. The UA MAY initialize
|result|.{{BluetoothRemoteGATTCharacteristic/value}} to a [=new=]
{{DataView}} wrapping a [=new=] {{ArrayBuffer}} containing the most
recently read value from |characteristic| if this value is available.
1. [=Queue a global task=] on the [=Bluetooth task source=] given |global|
to [=resolve=] |promise| with |result|.
1. Return |promise|.
getDescriptor(descriptor) method retrieves a
Descriptor inside this Characteristic. When invoked, it MUST return
> GetGATTChildren(attribute=this,descriptor,undefined,
getDescriptors(descriptor) method retrieves a list of
Descriptors inside this Characteristic. When invoked, it MUST return
> GetGATTChildren(attribute=this,descriptor,undefined,readValue()
method, when invoked, MUST run the following steps:
1. Let |global| be [=this=]'s [=relevant global object=].
1. Let |gatt| be [=this=].{{BluetoothRemoteGATTCharacteristic/service}}.{{BluetoothRemoteGATTService/device}}.{{BluetoothDevice/gatt}}.
1. If [=this=].{{BluetoothRemoteGATTCharacteristic/uuid}} is [=blocklisted
for reads=], return [=a promise rejected with=] a "{{SecurityError}}"
{{DOMException}} and abort these steps.
1. If |gatt|.{{BluetoothRemoteGATTServer/connected}}
is `false`, return [=a promise rejected with=] a "{{NetworkError}}"
{{DOMException}} and abort these steps.
1. Let |characteristic| be [=this=].{{[[representedCharacteristic]]}}.
1. If |characteristic| is `null`, return [=a promise rejected with=] an
"{{InvalidStateError}}" {{DOMException}} and abort these steps.
1. Return a |gatt|-[=connection-checking wrapper=] around [=a new promise=]
|promise| and run the following steps [=in parallel=]:
1. If the Read bit is not set in |characteristic|'s
properties, [=queue a global
task=] on the [=Bluetooth task source=] given |global| to [=reject=]
|promise| with a "{{NotSupportedError}}" {{DOMException}} and abort
these steps.
1. If |global|'s [=Window/navigable=]'s
[=navigable/top-level traversable=]'s simulated Bluetooth adapter
is not empty, run the following steps:
1. If [=this=].{{[[automatedCharacteristicReadResponse]]}} is not `"not-expected"`,
[=queue a global task=] on the [=Bluetooth task source=] given |global| to
[=reject=] |promise| with a "{{InvalidStateError}}" {{DOMException}} and abort
these steps.
1. [=Trigger a simulated characteristic event=] given |global|'s
[=Window/navigable=], [=this=].{{BluetoothRemoteGATTServer/device}},
|characteristic|, and `read`.
1. Set [=this=].{{[[automatedCharacteristicReadResponse]]}} to `"expected"`,
and wait for it to change.
1. Let |response| be [=this=].{{[[automatedCharacteristicReadResponse]]}}.
1. Set [=this=].{{[[automatedCharacteristicReadResponse]]}} to `"not-expected"`.
1. If |response| is not `0`, do the following sub-steps:
1. [=Queue a global task=] on the [=Bluetooth task source=] given
|global| to [=reject=] |promise| with a "{{NetworkError}}"
{{DOMException}} and abort these steps.
1. Otherwise, let |buffer| be a [=new=] {{ArrayBuffer}} containing
[=this=].{{[[automatedCharacteristicReadResponseData]]}}.
1. Otherwise, run the following steps:
1. If the UA is currently using the Bluetooth system, it MAY [=queue a
global task=] on the [=Bluetooth task source=] given |global| to
[=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and
abort these steps.
Issue(188): Implementations may be able to avoid this {{NetworkError}},
but for now sites need to serialize their use of this API and/or give
the user a way to retry failed operations.
1. Use any combination of the sub-procedures in the [=Characteristic Value
Read=] procedure to retrieve the value of |characteristic| and let |buffer|
be a [=new=] {{ArrayBuffer}} holding the retrieved value.
Handle errors as described in .
1. [=Queue a global task=] on the [=Bluetooth task source=] given |global|
to perform the following steps:
1. If |promise| is not in |gatt|.{{[[activeAlgorithms]]}}, [=reject=]
|promise| with a "{{NetworkError}}" {{DOMException}} and abort
these steps.
1. If the sub-procedures above returned an error, [=reject=] |promise|
with that error and abort these steps.
1. Assign a [=new=] {{DataView}} created with |buffer| to
[=this=].{{BluetoothRemoteGATTCharacteristic/value}}.
1. [=Fire an event=] named "{{characteristicvaluechanged}}" with its
{{Event/bubbles}} attribute initialized to `true` at [=this=].
1. [=Resolve=] |promise| with
[=this=].{{BluetoothRemoteGATTCharacteristic/value}}.
undefined.
writeValue(value) method, when invoked, MUST return
> WriteCharacteristicValue(
> this=this,value,
writeValueWithResponse(value) method, when invoked, MUST
return
> WriteCharacteristicValue(
> this=this,value,
writeValueWithoutResponse(value) method, when invoked,
MUST return
> WriteCharacteristicValue(
> this=this,value,
startNotifications() method, when invoked, MUST run the following
steps. See for details of receiving
notifications.
1. Let |global| be [=this=]'s [=relevant global object=].
1. Let |gatt| be [=this=].{{BluetoothRemoteGATTCharacteristic/service}}.{{BluetoothRemoteGATTService/device}}.{{BluetoothDevice/gatt}}.
1. If [=this=].{{BluetoothRemoteGATTCharacteristic/uuid}} is [=blocklisted for
reads=], return [=a promise rejected with=] a "{{SecurityError}}"
{{DOMException}}.
1. If |gatt|.{{BluetoothRemoteGATTServer/connected}} is `false`, return [=a
promise rejected with=] with a "{{NetworkError}}" {{DOMException}}.
1. Let |characteristic| be [=this=].{{[[representedCharacteristic]]}}.
1. If |characteristic| is `null`, return [=a promise rejected with=] an
"{{InvalidStateError}}" {{DOMException}}.
1. Return a |gatt|-[=connection-checking wrapper=] around [=a new promise=]
|promise| and run the following steps [=in parallel=].
1. If neither of the Notify or Indicate bits are set
in |characteristic|'s properties,
[=queue a global task=] on the [=Bluetooth task source=] given |global|
to [=reject=] |promise| with a {{NotSupportedError}} and abort these
steps.
1. If |characteristic|'s [=active notification context set=] contains
{{Navigator/bluetooth|navigator.bluetooth}}, [=queue a global task=]
on the [=Bluetooth task source=] given |global| to [=resolve=]
|promise| with [=this=] and abort these steps.
1. If |global|'s [=Window/navigable=]'s
[=navigable/top-level traversable=]'s simulated Bluetooth adapter
is not empty, run the following steps:
1. If [=this=].{{[[automatedCharacteristicSubscribeToNotificationsResponse]]}} is not `"not-expected"`,
[=queue a global task=] on the [=Bluetooth task source=] given |global| to
[=reject=] |promise| with a "{{InvalidStateError}}" {{DOMException}} and abort
these steps.
1. [=Trigger a simulated characteristic event=] given |global|'s
[=Window/navigable=], [=this=].{{BluetoothRemoteGATTServer/device}},
|characteristic|, and `subscribe-to-notifications`.
1. Set [=this=].{{[[automatedCharacteristicSubscribeToNotificationsResponse]]}} to `"expected"`,
and wait for it to change.
1. Let |response| be [=this=].{{[[automatedCharacteristicSubscribeToNotificationsResponse]]}}.
1. Set [=this=].{{[[automatedCharacteristicSubscribeToNotificationsResponse]]}} to `"not-expected"`.
1. If |response| is not `0`, do the following sub-steps:
1. [=Queue a global task=] on the [=Bluetooth task source=] given
|global| to [=reject=] |promise| with a "{{NetworkError}}"
{{DOMException}} and abort these steps.
1. Otherwise, let |success| to be `true`.
1. Otherwise, run the following steps:
1. If the UA is currently using the Bluetooth system, it MAY [=queue a
global task=] on the [=Bluetooth task source=] given |global| to
[=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and
abort these steps.
Issue(188): Implementations may be able to avoid this {{NetworkError}}, but
for now sites need to serialize their use of this API and/or give the user a
way to retry failed operations.
1. If the characteristic has a [=Client Characteristic Configuration=]
descriptor, use any of the [=Characteristic Descriptors=] procedures to
ensure that one of the Notification or Indication
bits in |characteristic|'s [=Client Characteristic Configuration=]
descriptor is set, matching the constraints in |characteristic|'s
properties. The UA SHOULD avoid
setting both bits, and MUST deduplicate
value-change events if both bits are
set. Handle errors as described in .
.then handler of the result promise.
stopNotifications() method, when invoked, MUST return a new
promise promise and run the following steps in parallel:
1. Let |characteristic| be this.{{[[representedCharacteristic]]}}.
1. If |characteristic| is `null`, return a promise rejected with an
{{InvalidStateError}} and abort these steps.
1. If characteristic's active notification context set
contains {{Navigator/bluetooth|navigator.bluetooth}}, remove it.
1. If |global|'s [=Window/navigable=]'s
[=navigable/top-level traversable=]'s simulated Bluetooth adapter
is not empty, run the following steps:
1. If [=this=].{{[[automatedCharacteristicUnsubscribeFromNotificationsResponse]]}} is not `"not-expected"`,
[=queue a global task=] on the [=Bluetooth task source=] given |global| to
[=reject=] |promise| with a "{{InvalidStateError}}" {{DOMException}} and abort
these steps.
1. [=Trigger a simulated characteristic event=] given |global|'s
[=Window/navigable=], [=this=].{{BluetoothRemoteGATTServer/device}},
|characteristic|, and `unsubscribe-from-notifications`.
1. Set [=this=].{{[[automatedCharacteristicUnsubscribeFromNotificationsResponse]]}} to `"expected"`,
and wait for it to change.
1. Let |response| be [=this=].{{[[automatedCharacteristicUnsubscribeFromNotificationsResponse]]}}.
1. Set [=this=].{{[[automatedCharacteristicUnsubscribeFromNotificationsResponse]]}} to `"not-expected"`.
1. If |response| is not `0`, do the following sub-steps:
1. [=Queue a global task=] on the [=Bluetooth task source=] given
|global| to [=reject=] |promise| with a "{{NetworkError}}"
{{DOMException}} and abort these steps..
1. Otherwise, run the following steps:
1. If characteristic's active notification context set became
empty and the characteristic has a Client Characteristic
Configuration descriptor, the UA SHOULD use any of the Characteristic
Descriptors procedures to clear the Notification and
Indication bits in characteristic's Client
Characteristic Configuration descriptor.
1. [=Queue a global task=] on the [=Bluetooth task source=] given [=this=]'s
[=relevant global object=] to [=resolve=] |promise| with [=this=].
BluetoothCharacteristicProperties instance from
the Characteristic |characteristic|, the UA MUST run the
following steps:
1. Let |propertiesObj| be a new instance of
{{BluetoothCharacteristicProperties}}.
1. Let |properties| be the [=characteristic properties=] of |characteristic|.
1. Initialize the attributes of |propertiesObj| from the corresponding
bits in |properties|:
| Attribute | Bit |
|---|---|
broadcast |
Broadcast |
read |
Read |
writeWithoutResponse |
Write Without Response |
write |
Write |
notify |
Notify |
indicate |
Indicate |
authenticatedSignedWrites |
Authenticated Signed Writes |
false. Otherwise, run the following steps:
1. Discover the
[=Characteristic Extended Properties=] descriptor for
|characteristic| and read its
value into |extendedProperties|.
Handle errors as described in .
Issue: [=Characteristic Extended Properties=] isn't clear whether
the extended properties are immutable for a given Characteristic.
If they are, the UA should be allowed to cache them.
1. If the previous step returned an error, return that error.
1. Initialize |propertiesObj|.{{BluetoothCharacteristicProperties/reliableWrite}}
from the Reliable Write bit of |extendedProperties|.
1. Initialize |propertiesObj|.{{BluetoothCharacteristicProperties/writableAuxiliaries}}
from the Writable Auxiliaries bit of |extendedProperties|.
1. Return |propertiesObj|.
'00002902-0000-1000-8000-00805f9b34fb' for the
Client Characteristic Configuration descriptor.
value is the currently cached descriptor value. This value gets
updated when the value of the descriptor is read.
| Internal Slot | Initial Value | Description (non-normative) |
|---|---|---|
| \[[representedDescriptor]] | <always set in prose> | The Descriptor this object represents, or `null` if the Descriptor has been removed or otherwise invalidated. |
| \[[automatedDescriptorReadResponse]] | "not-expected" |
The simulated GATT descriptor response code for a GATT descriptor read attempt. |
| \[[automatedDescriptorReadResponseData]] | Empty byte sequence | The simulated GATT descriptor response data for a GATT descriptor read attempt. |
| \[[automatedDescriptorWriteResponse]] | "not-expected" |
The simulated GATT descriptor response code for a GATT descriptor write attempt. |
BluetoothRemoteGATTDescriptor representing
a Descriptor descriptor,
the UA MUST run the following steps.
1. Let |promise| be [=a new promise=].
1. Run the following steps [=in parallel=]:
1. Let |result| be a new instance of {{BluetoothRemoteGATTDescriptor}}
with its {{[[representedDescriptor]]}} slot initialized to |descriptor|.
1. Initialize |result|.{{BluetoothRemoteGATTDescriptor/characteristic}}
from the {{BluetoothRemoteGATTCharacteristic}} instance representing
the Characteristic in which |descriptor| appears.
1. Initialize |result|.{{BluetoothRemoteGATTDescriptor/uuid}} from the UUID
of |descriptor|.
1. Initialize |result|.{{BluetoothRemoteGATTDescriptor/value}} to `null`.
The UA MAY initialize |result|.{{BluetoothRemoteGATTDescriptor/value}}
a [=new=] {{DataView}} wrapping a [=new=] {{ArrayBuffer}} containing
the most recently read value from |descriptor| if this value is
available.
1. [=Queue a global task=] on the [=Bluetooth task source=] given
[=this=]'s [=relevant global object=] to [=resolve=] |promise| with
|result|.
1. Return |promise|.
readValue() method, when invoked, MUST run the following steps:
1. Let |global| be [=this=]'s [=relevant global object=].
1. Let |gatt| be [=this=].{{BluetoothRemoteGATTDescriptor/characteristic}}.{{BluetoothRemoteGATTCharacteristic/service}}.{{BluetoothRemoteGATTService/device}}.{{BluetoothDevice/gatt}}.
1. If [=this=].{{BluetoothRemoteGATTDescriptor/uuid}} is [=blocklisted for
reads=], return [=a promise rejected with=] a "{{SecurityError}}"
{{DOMException}}.
1. If |gatt|.{{BluetoothRemoteGATTServer/connected}} is `false`, return [=a
promise rejected with=] a "{{NetworkError}}" {{DOMException}}.
1. Let |descriptor| be [=this=].{{[[representedDescriptor]]}}.
1. If |descriptor| is `null`, return [=a promise rejected with=] an
"{{InvalidStateError}}" {{DOMException}}.
1. Return a |gatt|-[=connection-checking wrapper=] around [=a new promise=]
|promise| and run the following steps [=in parallel=]:
1. If |global|'s [=Window/navigable=]'s
[=navigable/top-level traversable=]'s simulated Bluetooth adapter
is not empty, run the following steps:
1. If [=this=].{{[[automatedDescriptorReadResponse]]}} is not `"not-expected"`,
[=queue a global task=] on the [=Bluetooth task source=] given |global| to
[=reject=] |promise| with a "{{InvalidStateError}}" {{DOMException}} and abort
these steps.
1. [=Trigger a simulated descriptor event=] given |global|'s
[=Window/navigable=], [=this=].{{BluetoothRemoteGATTServer/device}},
|descriptor|, and `read`.
1. Set [=this=].{{[[automatedDescriptorReadResponse]]}} to `"expected"`,
and wait for it to change.
1. Let |response| be [=this=].{{[[automatedDescriptorReadResponse]]}}.
1. Set [=this=].{{[[automatedDescriptorReadResponse]]}} to `"not-expected"`.
1. If |response| is not `0`, do the following sub-steps:
1. [=Queue a global task=] on the [=Bluetooth task source=] given
|global| to [=reject=] |promise| with a "{{NetworkError}}"
{{DOMException}} and abort these steps.
1. Otherwise, let |buffer| be a [=new=] {{ArrayBuffer}} containing
[=this=].{{[[automatedDescriptorReadResponseData]]}}.
1. Otherwise, run the following steps:
1. If the UA is currently using the Bluetooth system, it MAY [=queue a
global task=] on the [=Bluetooth task source=] given |global| to
[=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and
abort these steps.
Issue(188): Implementations may be able to avoid this {{NetworkError}},
but for now sites need to serialize their use of this API
and/or give the user a way to retry failed operations.
1. Use either the [=Read Characteristic Descriptors=] or the [=Read Long
Characteristic Descriptors=] sub-procedure to retrieve the value of
|descriptor| and let |buffer| be a [=new=] {{ArrayBuffer}} holding the
retrieved value. Handle errors as described in
.
1. [=Queue a global task=] on the [=Bluetooth task source=] given |global|
to perform the following steps:
1. If |promise| is not in |gatt|.{{[[activeAlgorithms]]}}, [=reject=]
|promise| with a "{{NetworkError}}" {{DOMException}} and abort
these steps.
1. If the sub-procedure above returned an error, [=reject=] |promise|
with that error and abort these steps.
1. Assign a [=new=] {{DataView}} created with |buffer| to
[=this=].{{BluetoothRemoteGATTDescriptor/value}}.
1. [=Resolve=] |promise| with
[=this=].{{BluetoothRemoteGATTDescriptor/value}}.
writeValue(value) method, when invoked, MUST run the
following steps:
1. Let |global| be [=this=]'s [=relevant global object=].
1. Let |gatt| be [=this=].{{BluetoothRemoteGATTDescriptor/characteristic}}.{{BluetoothRemoteGATTCharacteristic/service}}.{{BluetoothRemoteGATTService/device}}.{{BluetoothDevice/gatt}}.
1. If [=this=].{{BluetoothRemoteGATTDescriptor/uuid}} is [=blocklisted for
writes=], return [=a promise rejected with=] a "{{SecurityError}}"
{{DOMException}}.
1. Let |bytes| be [=a copy of the bytes held=] by |value|.
1. If |bytes| is more than 512 bytes long (the maximum length of an attribute
value, per [=Long Attribute Values=]) return [=a promise rejected with=] an
"{{InvalidModificationError}}" {{DOMException}}.
1. If |gatt|.{{BluetoothRemoteGATTServer/connected}} is `false`, return [=a
promise rejected with=] a "{{NetworkError}}" {{DOMException}}.
1. Let |descriptor| be [=this=].{{[[representedDescriptor]]}}.
1. If |descriptor| is `null`, return [=a promise rejected with=] an
"{{InvalidStateError}}" {{DOMException}}.
1. Return a |gatt|-[=connection-checking wrapper=] around [=a new promise=]
|promise| and run the following steps [=in parallel=].
1. If |global|'s [=Window/navigable=]'s
[=navigable/top-level traversable=]'s simulated Bluetooth adapter
is not empty, run the following steps:
1. If [=this=].{{[[automatedDescriptorWriteResponse]]}} is not `"not-expected"`,
[=queue a global task=] on the [=Bluetooth task source=] given |global| to
[=reject=] |promise| with a "{{InvalidStateError}}" {{DOMException}} and abort
these steps.
1. [=Trigger a simulated descriptor event=] given |global|'s
[=Window/navigable=], [=this=].{{BluetoothRemoteGATTServer/device}},
|descriptor|, `write`, and |bytes|.
1. Set [=this=].{{[[automatedDescriptorWriteResponse]]}} to `"expected"`,
and wait for it to change.
1. Let |response| be [=this=].{{[[automatedDescriptorWriteResponse]]}}.
1. Set [=this=].{{[[automatedDescriptorWriteResponse]]}} to `"not-expected"`.
1. If |response| is not `0`, do the following sub-steps:
1. [=Queue a global task=] on the [=Bluetooth task source=] given
|global| to [=reject=] |promise| with a "{{NetworkError}}"
{{DOMException}} and abort these steps.
1. Otherwise, run the following steps:
1. If the UA is currently using the Bluetooth system, it MAY [=queue a
global task=] on the [=Bluetooth task source=] given |global| to
[=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and
abort these steps.
Issue(188): Implementations may be able to avoid this {{NetworkError}},
but for now sites need to serialize their use of this API
and/or give the user a way to retry failed operations.
1. Use either the [=Write Characteristic Descriptors=] or the [=Write Long
Characteristic Descriptors=] sub-procedure to write |bytes| to
|descriptor|. Handle errors as described in
.
1. [=Queue a global task=] on the [=Bluetooth task source=] given |global|
to perform the following steps:
1. If |promise| is not in |gatt|.{{[[activeAlgorithms]]}}, [=reject=]
|promise| with a "{{NetworkError}}" {{DOMException}} and abort
these steps.
1. If the sub-procedure above returned an error, [=reject=] |promise|
with that error and abort these steps.
1. Set [=this=].{{BluetoothRemoteGATTDescriptor/value}} to a [=new=]
{{DataView}} wrapping a [=new=] {{ArrayBuffer}} containing |bytes|.
1. [=Resolve=] |promise| with `undefined`.
advertisementreceived
availabilitychangedcharacteristicvaluechanged
gattserverdisconnected
serviceadded
servicechanged
serviceremoved
deviceObj.{{BluetoothDevice/[[representedDevice]]}}
is not the same device as device, abort these steps.
1. If !deviceObj.gatt.{{BluetoothRemoteGATTServer/connected}},
abort these steps.
1. Clean up the disconnected device |deviceObj|.
deviceObj.gatt.{{BluetoothRemoteGATTServer/connected}}
to `false`.
1. Clear deviceObj.gatt.{{[[activeAlgorithms]]}}.
1. Set deviceObj.gatt.{{[[automatedGATTConnectionResponse]]}} to
`"not-expected"`.
1. Let |context| be |deviceObj|.{{[[context]]}}.
1. Remove all entries from |context|.{{[[attributeInstanceMap]]}}
whose keys are inside |deviceObj|.{{[[representedDevice]]}}.
1. For each {{BluetoothRemoteGATTService}} |service| in |deviceObj|'s
realm, set |service|.{{[[representedService]]}} to
`null`.
1. For each {{BluetoothRemoteGATTCharacteristic}} |characteristic| in
|deviceObj|'s realm, do the following sub-steps:
1. Let |notificationContexts| be
|characteristic|.{{[[representedCharacteristic]]}}'s
active notification context set.
1. Remove |context| from |notificationContexts|.
1. If |notificationContexts| became empty and there is still an ATT
Bearer to |deviceObj|.{{[[representedDevice]]}} and
characteristic has a Client Characteristic
Configuration descriptor, the UA SHOULD use any of the
Characteristic Descriptors procedures to clear the
Notification and Indication bits in
characteristic's Client Characteristic Configuration
descriptor.
1. Set |characteristic|.{{[[representedCharacteristic]]}}
to `null`.
1. For each {{BluetoothRemoteGATTDescriptor}} |descriptor| in |deviceObj|'s
realm, set |descriptor|.{{[[representedDescriptor]]}} to
`null`.
1. Fire an event named {{gattserverdisconnected}} with its
{{Event/bubbles}} attribute initialized to `true` at
deviceObj.
characteristicObject
.service.device.gatt.{{BluetoothRemoteGATTServer/connected}}
is `false`, abort these sub-steps.
1. Set characteristicObject.value to a new
{{DataView}} wrapping a new {{ArrayBuffer}} holding the new value of the
Characteristic.
1. Fire an event named {{characteristicvaluechanged}} with its
bubbles attribute initialized to true at
characteristicObject.
|service|.{{[[representedService]]}} is in
|invalidatedAttributes|, set
|service|.{{[[representedService]]}} to `null`.
1. For each {{BluetoothRemoteGATTCharacteristic}} |characteristic| whose
relevant settings object is |settings|, if
|characteristic|.{{[[representedCharacteristic]]}} is in
|invalidatedAttributes|, set
|characteristic|.{{[[representedCharacteristic]]}} to
`null`.
1. For each {{BluetoothRemoteGATTDescriptor}} |descriptor| whose relevant
settings object is |settings|, if
|descriptor|.{{[[representedDescriptor]]}} is in
|invalidatedAttributes|, set
|descriptor|.{{[[representedDescriptor]]}} to `null`.
1. Let |global| be |settings|'
global object.
1. Remove every entry from
|global|.navigator.bluetooth.{{[[attributeInstanceMap]]}} that
represents an attribute that is in |invalidatedAttributes|.
1. Let changedServices be a set of Services, initially empty.
1. If the same Service appears in both removedAttributes and addedAttributes, remove it from both, and add it to changedServices.
1.For each Characteristic and Descriptor in removedAttributes or addedAttributes, remove it from its original list, and add its parent Service to changedServices.
If a Service in
addedAttributes would not have been returned from any previous
call to getPrimaryService, getPrimaryServices,
getIncludedService, or getIncludedServices if it
had existed at the time of the call, the UA MAY remove the Service
from addedAttributes.
deviceObj.{{BluetoothDevice/[[allowedServices]]}}
is `"all"` or contains the Service's UUID, fire an event
named {{serviceremoved}} with its bubbles attribute
initialized to true at the
{{BluetoothRemoteGATTService}} representing the Service.
1. Remove this {{BluetoothRemoteGATTService}} from the Bluetooth
tree.
1. For each Service in addedAttributes, if
deviceObj.{{BluetoothDevice/[[allowedServices]]}} is
`"all"` or contains the Service's UUID, add the
{{BluetoothRemoteGATTService}} representing this Service to the
Bluetooth tree and then fire an event named
{{serviceadded}} with its bubbles attribute initialized to
true at the {{BluetoothRemoteGATTService}}.
1. For each Service in changedServices, if
deviceObj.{{BluetoothDevice/[[allowedServices]]}} is
`"all"` or contains the Service's UUID, fire an event named
{{servicechanged}} with its bubbles attribute initialized
to true at the {{BluetoothRemoteGATTService}} representing
the Service.
Error
Response, the UA MUST perform the following steps:
1. If the procedure times out or the ATT Bearer (described in Profile
Fundamentals) is absent or terminated for any reason, return a
{{NetworkError}} from the step and abort these steps.
1. Take the following actions depending on the Error Code:
Invalid PDUInvalid OffsetAttribute Not FoundUnsupported Group TypeInvalid HandleInvalid Attribute Value LengthAttribute Not LongIf this error code is received without having used a "Long" sub-procedure, this may indicate a device bug. Return a {{NotSupportedError}} from the step.
Otherwise, retry the step without using a "Long" sub-procedure. If this is impossible due to the length of the value being written, return an {{InvalidModificationError}} from the step.
Insufficient AuthenticationInsufficient EncryptionInsufficient Encryption Key SizeInsufficient AuthorizationApplication ErrorRead Not PermittedWrite Not PermittedRequest Not SupportedPrepare Queue FullInsufficient ResourcesUnlikely Error/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.
That is, a valid UUID is lower-case and does not use the 16- or 32-bit
abbreviations defined by the Bluetooth standard. All UUIDs returned from
functions and attributes in this specification MUST be valid UUIDs. If a
function in this specification takes a parameter whose type is UUID or a
dictionary including a UUID attribute, and the argument passed in any
UUID slot is not a valid UUID, the function MUST return a
promise rejected with a TypeError and abort its other steps.
BluetoothUUID.canonicalUUID(alias) function to map
a 16- or 32-bit Bluetooth UUID alias to its 128-bit form.
/^[a-z0-9_-.]+$/.
BluetoothUUID.
canonicalUUID(alias) method, when invoked, MUST return
the 128-bit UUID represented by the 16- or 32-bit UUID alias
alias.
00000000-0000-1000-8000-00805f9b34fb" with the bits of the
alias. For example, canonicalUUID(0xDEADBEEF) returns
"deadbeef-0000-1000-8000-00805f9b34fb".
unsigned long, return
{{BluetoothUUID/canonicalUUID()|BluetoothUUID.canonicalUUID}}(name)
and abort these steps.
1. If name is a valid UUID, return name and abort
these steps.
1. If name is a valid name and maps to a valid UUID in
GATT assigned numbers, let alias be its assigned
number, and return
{{BluetoothUUID/canonicalUUID()|BluetoothUUID.canonicalUUID}}(alias).
1. Otherwise, throw a {{TypeError}}.
BluetoothUUID.
getService(name) method, when invoked, MUST return
ResolveUUIDName(name, GATT assigned
services).
The static BluetoothUUID.
getCharacteristic(name) method, when invoked, MUST
return ResolveUUIDName(name, GATT assigned
characteristics).
The static BluetoothUUID.
getDescriptor(name) method, when invoked, MUST return
ResolveUUIDName(name, GATT assigned
descriptors).
{{BluetoothUUID/getService()|BluetoothUUID.getService}}(" cycling_power")
returns "00001818-0000-1000-8000-00805f9b34fb".
{{BluetoothUUID/getService()|BluetoothUUID.getService}}("00001801-0000-1000-8000-00805f9b34fb") returns
"00001801-0000-1000-8000-00805f9b34fb".
{{BluetoothUUID/getService()|BluetoothUUID.getService}}("unknown-service") throws a {{TypeError}}.
{{BluetoothUUID/getCharacteristic()|BluetoothUUID.getCharacteristic}}("{{ieee_11073-20601_regulatory_certification_data_list|ieee_11073-20601_regulatory_certification_data_list}}")
returns "00002a2a-0000-1000-8000-00805f9b34fb".
{{BluetoothUUID/getDescriptor()|BluetoothUUID.getDescriptor}}("{{gatt.characteristic_presentation_format|gatt.characteristic_presentation_format}}")
returns "00002904-0000-1000-8000-00805f9b34fb".
'\n'.
1. Let result be an empty map.
1. For each line in lines, do the following sub-steps:
1. If line is empty or its first character is '#',
continue to the next line.
1. If line consists of a valid name, a space (U+0020), and
a valid UUID, let name be that name and let
uuid be that UUID.
1. Otherwise, return an error and abort these steps.
1. If name is already in result, return an error and
abort these steps.
1. Add a mapping in result from name to uuid.
1. Return result.
|prefixIndex| + 1
of |prefixData| as a hexadecimal number.
1. Let |mask| be the result of interpreting the characters at index |prefixIndex| and |prefixIndex| + 1
of |prefixMask| as a hexadecimal number.
1. Append |data| to |dataList|.
1. Append |mask| to |maskList|.
1. Set |prefixIndex| to `|prefixIndex| + 2`.
1. Let |result| be a new {{BluetoothDataFilterInit}} dictionary.
1. Set |result|[{{BluetoothDataFilterInit/dataPrefix}}] to an {{Uint8Array}} constructed with |dataList|.
1. Set |result|[{{BluetoothDataFilterInit/mask}}] to an {{Uint8Array}} constructed with |maskList|.
1. Return |result|.
'\n'.
1. Let result be an empty map.
1. For each line in lines, do the following sub-steps:
1. If line is empty or its first character is '#',
continue to the next line.
1. If line consists of just a valid UUID, let
uuid be that UUID and let token be
"exclude".
1. If line consists of a valid UUID, a space (U+0020), and
one of the tokens "exclude-reads" or
"exclude-writes", let uuid be that UUID and let
token be that token.
1. Otherwise, return an error and abort these steps.
1. If uuid is already in result, return an error and
abort these steps.
1. Add a mapping in result from uuid to token.
1. Return result.
exclude" in the GATT
blocklist.
A UUID is blocklisted for reads if either the GATT
blocklist's value is an error, or the UUID maps to either
"exclude" or "exclude-reads" in the GATT
blocklist.
A UUID is blocklisted for writes if either the GATT
blocklist's value is an error, or the UUID maps to either
"exclude" or "exclude-writes" in the GATT
blocklist.
bluetooth",
that controls whether the methods exposed by the {{Navigator/bluetooth}}
attribute on the {{Navigator}} object may be used.
The default allowlist for this feature is ["self"].
# Automated testing # {#automated-testing}
For the purposes of user-agent automation and application testing, this document defines extensions to the [[WebDriver-BiDi]] specification.
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.
Each [=navigable/top-level traversable=] may have a simulated Bluetooth adapter, that is a software defined Bluetooth adapter that has a set of discovered simulated Bluetooth devices and can assume roles like Central.
Each simulated Bluetooth adapter has a simulated Bluetooth device mapping, which is an ordered map of Bluetooth address {{strings}} to simulated Bluetooth devices.
Each simulated Bluetooth adapter has an adapter state that is a string enumeration describing the current state of the adapter. The possible enumeration values are:
* "powered-on"
* "powered-off"
* "absent"
Each simulated Bluetooth adapter has a low-energy supported state that is a boolean describing if the adapter supports Bluetooth Low Energy.
A simulated Bluetooth device is a software defined [=Bluetooth device=] that
behaves like a physical device, may be attached to a simulated Bluetooth adapter,
may have associated properties like Manufacturer Specific Data and Service UUIDs,
and has a simulated GATT service mapping, which is an ordered map of Bluetooth
UUID strings to simulated GATT services.
A simulated GATT service is a software defined [=Service=] that belongs to a
simulated Bluetooth device, has a property of UUID, is known-present in the
Bluetooth cache. and has a simulated GATT characteristic mapping, which
is an ordered map of Bluetooth UUID strings to simulated GATT characteristics.
A simulated GATT characteristic is a software defined [=Characteristic=] that belongs to a
simulated GATT service, has a property of UUID, a property of Characteristic Properties,
is known-present in the Bluetooth cache, and has a simulated GATT descriptor mapping, which
is an ordered map of Bluetooth UUID strings to simulated GATT descriptors.
Simulated GATT characteristic properties are software defined [=Characteristic Properties=] that belong to a
simulated GATT characteristic and are known-present in the Bluetooth cache.
A simulated GATT descriptor is a software defined [=Descriptor=] that belongs to a
simulated GATT characteristic, has a property of UUID, and is known-present in the Bluetooth cache.
Issue: CDDL snippetes use the "text" type instead of
"browsingContext.BrowsingContext" to allow indepedent programmatic
processing of CDDL snippets. Currently, other modules cannot be
referenced.
## Definitions ## {#bluetooth-bidi-definitions}
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
}
bluetooth.RequestDevice = textA bluetooth.RequestDevice is an identifier for a single device in a request device prompt. A device prompt is a [=tuple=] consisting of a device prompt id (a string) and a set of devices which is a [=/set=] of {{BluetoothDevice}} objects. It represents a prompt which allows a user to [=prompt the user to choose|choose=] a [=Bluetooth device=]. #### The bluetooth.RequestDeviceInfo Type #### {#bluetooth-requestdeviceinfo-type}
bluetooth.RequestDeviceInfo = {
id: bluetooth.RequestDevice,
name: text / null,
}
A bluetooth.RequestDeviceInfo represents a single device in a request device prompt.
bluetooth.RequestDevicePrompt = textA bluetooth.RequestDevicePrompt is an identifier for a single prompt. A remote end has a map of navigables to device prompts which is a [=/map=] whose keys are [=navigable ids=] and values are [=device prompts=].
bluetooth.ScanRecord = {
? name: text,
? uuids: [ * bluetooth.BluetoothUuid ],
? appearance: number,
? manufacturerData: [ * bluetooth.BluetoothManufacturerData ],
}
A `bluetooth.ScanRecord` represents data of the advertisement packet sent by a [=Bluetooth device=].
BluetoothManufacturerData that maps {{unsigned short}} Company Identifier Codes to base64 encoded manufacturer data [=byte sequences=].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 )#### The {^bluetooth.handleRequestDevicePrompt^} Command #### {#bluetooth-handlerequestdeviceprompt-command}
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,
)
{
"method": "bluetooth.handleRequestDevicePrompt",
"params": {
"context": "cxt-d03fdd81",
"prompt": "pmt-e0a234b",
"accept": true,
"device": "dvc-9b3b872"
}
}
bluetooth.SimulateAdapter = (
method: "bluetooth.simulateAdapter",
params: bluetooth.SimulateAdapterParameters,
)
bluetooth.SimulateAdapterParameters = {
context: text,
? leSupported: bool,
state: "absent" / "powered-off" / "powered-on"
}
{
"method": "bluetooth.simulateAdapter",
"params": {
"context": "cxt-d03fdd81",
"leSupported": true,
"state": "powered-on",
}
}
{
"method": "bluetooth.simulateAdapter",
"params": {
"context": "cxt-d03fdd81",
"state": "powered-off",
}
}
bluetooth.DisableSimulation = (
method: "bluetooth.disableSimulation",
params: bluetooth.DisableSimulationParameters,
)
bluetooth.DisableSimulationParameters = {
context: text
}
{
"method": "bluetooth.disableSimulation",
"params": {
"context": "cxt-d03fdd81"
}
}
bluetooth.SimulatePreconnectedPeripheral = (
method: "bluetooth.simulatePreconnectedPeripheral",
params: bluetooth.SimulatePreconnectedPeripheralParameters,
)
bluetooth.SimulatePreconnectedPeripheralParameters = {
context: text,
address: text,
name: text,
manufacturerData: [ * bluetooth.BluetoothManufacturerData ],
knownServiceUuids: [ * bluetooth.BluetoothUuid ]
}
{
"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",
],
}
}
bluetooth.SimulateAdvertisement = (
method: "bluetooth.simulateAdvertisement",
params: bluetooth.SimulateAdvertisementParameters,
)
bluetooth.SimulateAdvertisementParameters = {
context: text,
scanEntry: bluetooth.SimulateAdvertisementScanEntryParameters
}
bluetooth.SimulateAdvertisementScanEntryParameters = {
deviceAddress: text,
rssi: number,
scanRecord: bluetooth.ScanRecord
}
BluetoothDevice representing |simulatedDevice| inside |navigable|'s active window's associated Navigator's [=associated Bluetooth=].
1. If |simulatedDeviceInstance|.{{[[watchAdvertisementsState]]}} is `not-watching`, abort these sub-steps.
1. Fire an `advertisementreceived` event for the advertising event represented by |scanEntry|[{^bluetooth.SimulateAdvertisementScanEntryParameters/"scanRecord"^}], at |simulatedDeviceInstance|.
1. Return [=success=] with data `null`.
{
"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
}
}
}
}
bluetooth.SimulateGattConnectionResponse = (
method: "bluetooth.simulateGattConnectionResponse",
params: bluetooth.SimulateGattConnectionResponseParameters,
)
bluetooth.SimulateGattConnectionResponseParameters = {
context: text,
address: text,
code: uint
}
BluetoothDevice representing
|simulatedDevice| inside |navigable|'s active window's associated Navigator's
[=associated Bluetooth=].
1. If |simulatedDeviceInstance|.{{[[gatt]]}}.{{[[automatedGATTConnectionResponse]]}} is `"expected"`,
set |simulatedDeviceInstance|.{{[[gatt]]}}.{{[[automatedGATTConnectionResponse]]}} to |params|[{^bluetooth.SimulateGattConnectionResponseParameters/"code"^}].
1. Otherwise, return [=error=] with [=error code=] [=invalid element state=].
{
"method": "bluetooth.simulateGattConnectionResponse",
"params": {
"context": "cxt-d03fdd81",
"address": "09:09:09:09:09:09",
"code": 0
}
}
bluetooth.SimulateGattDisconnection = (
method: "bluetooth.simulateGattDisconnection",
params: bluetooth.SimulateGattDisconnectionParameters,
)
bluetooth.SimulateGattDisconnectionParameters = {
context: text,
address: text,
}
BluetoothDevice representing
|simulatedDevice| inside |navigable|'s active window's associated Navigator's
[=associated Bluetooth=].
1. If |simulatedDeviceInstance|.{{[[gatt]]}}.{{[[automatedGATTConnectionResponse]]}} is `"expected"`,
set |simulatedDeviceInstance|.{{[[gatt]]}}.{{[[automatedGATTConnectionResponse]]}} to `0x15`.
{
"method": "bluetooth.simulateGattDisconnection",
"params": {
"context": "cxt-d03fdd81",
"address": "09:09:09:09:09:09",
}
}
bluetooth.SimulateService = (
method: "bluetooth.simulateService",
params: bluetooth.SimulateServiceParameters,
)
bluetooth.SimulateServiceParameters = {
context: text,
address: text,
uuid: bluetooth.BluetoothUuid,
type: "add" / "remove",
}
BluetoothDevice representing
|simulatedDevice| inside |navigable|'s active window's associated Navigator's
[=associated Bluetooth=].
1. Let |serviceMapping| be |simulatedDevice|'s simulated GATT service mapping.
1. Let |uuid| be |params|[{^bluetooth.SimulateServiceParameters/"uuid"^}].
1. If |params|[{^bluetooth.SimulateServiceParameters/"type"^}] is {^bluetooth.SimulateServiceParameters/type/"add"^}:
1. If |serviceMapping|[|uuid|] [=map/exists=], return [=error=] with [=error code=] [=invalid element state=].
1. Let |simulatedGattService| be a new simulated GATT service.
1. Set |simulatedGattService|'s UUID to |uuid|.
1. Set |serviceMapping|[|uuid|] to |simulatedGattService|.
1. Create a BluetoothRemoteGATTService representing |simulatedGattService|
and add a mapping from |simulatedGattService| to the resulting {{Promise}} in
|simulatedDeviceInstance|.{{[[context]]}}.{{Bluetooth/[[attributeInstanceMap]]}}.
1. Return [=success=] with data `null`.
1. If |params|[{^bluetooth.SimulateServiceParameters/"type"^}] is {^bluetooth.SimulateServiceParameters/type/"remove"^}:
1. If |serviceMapping|[|uuid|] [=map/exists=], let |simulatedGattService| be |serviceMapping|[|uuid|].
1. Otherwise, return [=error=] with [=error code=] [=invalid element state=].
1. Remove |simulatedGattService| from |simulatedDeviceInstance|.{{[[context]]}}.{{Bluetooth/[[attributeInstanceMap]]}}.
1. Remove |uuid| from |serviceMapping|.
1. Return [=success=] with data `null`.
1. Return [=error=] with [=error code=] [=invalid argument=].
{
"method": "bluetooth.simulateService",
"params": {
"context": "cxt-d03fdd81",
"address": "09:09:09:09:09:09",
"uuid": "0000180d-0000-1000-8000-00805f9b34fb",
"type": "add"
}
}
{
"method": "bluetooth.simulateService",
"params": {
"context": "cxt-d03fdd81",
"address": "09:09:09:09:09:09",
"uuid": "0000180d-0000-1000-8000-00805f9b34fb",
"type": "remove"
}
}
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"
}
BluetoothDevice representing
|simulatedDevice| inside |navigable|'s active window's associated Navigator's
[=associated Bluetooth=].
1. Let |serviceMapping| be |simulatedDevice|'s simulated GATT service mapping.
1. Let |serviceUuid| be |params|[{^bluetooth.SimulateCharacteristicParameters/"serviceUuid"^}].
1. If |serviceMapping|[|serviceUuid|] [=map/exists=], let |simulatedService| be |serviceMapping|[|serviceUuid|].
1. Otherwise, return [=error=] with [=error code=] [=invalid argument=].
1. Let |characteristicMapping| be |simulatedService|'s simulated GATT characteristic mapping.
1. Let |characteristicUuid| be |params|[{^bluetooth.SimulateCharacteristicParameters/"characteristicUuid"^}].
1. If |params|[{^bluetooth.SimulateCharacteristicParameters/"type"^}] is {^bluetooth.SimulateCharacteristicParameters/type/"add"^}:
1. If |characteristicMapping|[|characteristicUuid|] [=map/exists=], return [=error=] with
[=error code=] [=invalid element state=].
1. If |params|[{^bluetooth.SimulateCharacteristicParameters/"characteristicProperties"^}] does not [=map/exist=], return [=error=] with [=error code=] [=invalid argument=].
1. Let |simulatedGattCharacteristicProperties| be new simulated GATT characteristic properties and run the following steps:
1. Let |properties| be |params|[{^bluetooth.SimulateCharacteristicParameters/"characteristicProperties"^}].
1. If |properties|[{^bluetooth.CharacteristicProperties/"broadcast"^}] [=map/exists=], set |simulatedGattCharacteristicProperties|'s `Broadcast` bit if
|properties|[{^bluetooth.CharacteristicProperties/"broadcast"^}] is `true`.
1. If |properties|[{^bluetooth.CharacteristicProperties/"read"^}] [=map/exists=], set |simulatedGattCharacteristicProperties|'s `Read` bit if
|properties|[{^bluetooth.CharacteristicProperties/"read"^}] is `true`.
1. If |properties|[{^bluetooth.CharacteristicProperties/"writeWithoutResponse"^}] [=map/exists=], set |simulatedGattCharacteristicProperties|'s `Write Without Response` bit if
|properties|[{^bluetooth.CharacteristicProperties/"writeWithoutResponse"^}] is `true`.
1. If |properties|[{^bluetooth.CharacteristicProperties/"write"^}] [=map/exists=], set |simulatedGattCharacteristicProperties|'s `Write` bit if
|properties|[{^bluetooth.CharacteristicProperties/"write"^}] is `true`.
1. If |properties|[{^bluetooth.CharacteristicProperties/"notify"^}] [=map/exists=], set |simulatedGattCharacteristicProperties|'s `Notify` bit if
|properties|[{^bluetooth.CharacteristicProperties/"notify"^}] is `true`.
1. If |properties|[{^bluetooth.CharacteristicProperties/"indicate"^}] [=map/exists=], set |simulatedGattCharacteristicProperties|'s `Indicate` bit if
|properties|[{^bluetooth.CharacteristicProperties/"indicate"^}] is `true`.
1. If |properties|[{^bluetooth.CharacteristicProperties/"authenticatedSignedWrites"^}] [=map/exists=], set |simulatedGattCharacteristicProperties|'s `Authenticated Signed Writes` bit if
|properties|[{^bluetooth.CharacteristicProperties/"authenticatedSignedWrites"^}] is `true`.
1. If |properties|[{^bluetooth.CharacteristicProperties/"extendedProperties"^}] [=map/exists=], set |simulatedGattCharacteristicProperties|'s `Extended Properties` bit if
|properties|[{^bluetooth.CharacteristicProperties/"extendedProperties"^}] is `true`.
1. Let |simulatedGattCharacteristic| be a new simulated GATT characteristic.
1. Set |simulatedGattCharacteristic|'s UUID to |characteristicUuid|.
1. Set |simulatedGattCharacteristic|'s Characteristic Properties to |simulatedGattCharacteristicProperties|.
1. Set |characteristicMapping|[|characteristicUuid|] to |simulatedGattCharacteristic|.
1. Create a BluetoothRemoteGATTCharacteristic representing |simulatedGattCharacteristic|
and add a mapping from |simulatedGattCharacteristic| to the resulting {{Promise}} in
|simulatedDeviceInstance|.{{[[context]]}}.{{Bluetooth/[[attributeInstanceMap]]}}.
1. Return [=success=] with data `null`.
1. If |params|[{^bluetooth.SimulateCharacteristicParameters/"type"^}] is {^bluetooth.SimulateCharacteristicParameters/type/"remove"^}:
1. If |params|[{^bluetooth.SimulateCharacteristicParameters/"characteristicProperties"^}] [=map/exists=], return [=error=] with [=error code=] [=invalid argument=].
1. If |characteristicMapping|[|characteristicUuid|] [=map/exists=], let |simulatedGattCharacteristic|
be |characteristicMapping|[|characteristicUuid|].
1. Otherwise, return [=error=] with [=error code=] [=invalid element state=].
1. Remove |simulatedGattCharacteristic| from |simulatedDeviceInstance|.{{[[context]]}}.{{Bluetooth/[[attributeInstanceMap]]}}.
1. Remove |characteristicUuid| from |characteristicMapping|.
1. Return [=success=] with data `null`.
1. Return [=error=] with [=error code=] [=invalid argument=].
{
"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"
}
}
{
"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"
}
}
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 ]
}
BluetoothDevice representing
|simulatedDevice| inside |navigable|'s active window's associated Navigator's
[=associated Bluetooth=].
1. Let |promise| be |simulatedDeviceInstance|.{{[[context]]}}.{{Bluetooth/[[attributeInstanceMap]]}}[|simulatedGattCharacteristic|].
1. Upon fulfillment of |promise| with |characteristic|, run the following steps:
1. If |params|[{^bluetooth.SimulateCharacteristicResponseParameters/"type"^}] is {^bluetooth.SimulateCharacteristicResponseParameters/type/"read"^}, run the following steps:
1. If |characteristic|.{{[[automatedCharacteristicReadResponse]]}} is `expected`,
set |characteristic|.{{[[automatedCharacteristicReadResponse]]}} to |params|[{^bluetooth.SimulateCharacteristicResponseParameters/"code"^}] and
|characteristic|.{{[[automatedCharacteristicReadResponseData]]}} to [=a copy of the bytes held=]
by |params|[{^bluetooth.SimulateCharacteristicResponseParameters/"data"^}].
1. Otherwise, return [=error=] with [=error code=] [=invalid element state=].
1. If |params|[{^bluetooth.SimulateCharacteristicResponseParameters/"type"^}] is {^bluetooth.SimulateCharacteristicResponseParameters/type/"write"^}, run the following steps:
1. If |characteristic|.{{[[automatedCharacteristicWriteResponse]]}} is `expected`,
set |characteristic|.{{[[automatedCharacteristicWriteResponse]]}} to |params|[{^bluetooth.SimulateCharacteristicResponseParameters/"code"^}].
1. Otherwise, return [=error=] with [=error code=] [=invalid element state=].
1. If |params|[{^bluetooth.SimulateCharacteristicResponseParameters/"type"^}] is {^bluetooth.SimulateCharacteristicResponseParameters/type/"subscribe-to-notifications"^}, run the following steps:
1. If |characteristic|.{{[[automatedCharacteristicSubscribeToNotificationsResponse]]}} is `expected`,
set |characteristic|.{{[[automatedCharacteristicSubscribeToNotificationsResponse]]}} to |params|[{^bluetooth.SimulateCharacteristicResponseParameters/"code"^}].
1. Otherwise, return [=error=] with [=error code=] [=invalid element state=].
1. If |params|[{^bluetooth.SimulateCharacteristicResponseParameters/"type"^}] is {^bluetooth.SimulateCharacteristicResponseParameters/type/"unsubscribe-from-notifications"^}, run the following steps:
1. If |characteristic|.{{[[automatedCharacteristicUnsubscribeFromNotificationsResponse]]}} is `expected`,
set |characteristic|.{{[[automatedCharacteristicUnsubscribeFromNotificationsResponse]]}} to |params|[{^bluetooth.SimulateCharacteristicResponseParameters/"code"^}].
1. Otherwise, return [=error=] with [=error code=] [=invalid element state=].
1. Otherwise, return [=error=] with [=error code=] [=invalid argument=].
{
"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]
}
}
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"
}
BluetoothDevice representing
|simulatedDevice| inside |navigable|'s active window's associated Navigator's
[=associated Bluetooth=].
1. Let |serviceMapping| be |simulatedDevice|'s simulated GATT service mapping.
1. Let |serviceUuid| be |params|[{^bluetooth.SimulateDescriptorParameters/"serviceUuid"^}].
1. If |serviceMapping|[|serviceUuid|] [=map/exists=], let |simulatedService| be |serviceMapping|[|serviceUuid|].
1. Otherwise, return [=error=] with [=error code=] [=invalid argument=].
1. Let |characteristicMapping| be |simulatedService|'s simulated GATT characteristic mapping.
1. Let |characteristicUuid| be |params|[{^bluetooth.SimulateDescriptorParameters/"characteristicUuid"^}].
1. If |characteristicMapping|[|characteristicUuid|] [=map/exists=], let |simulatedCharacteristic| be
|characteristicMapping|[|characteristicUuid|].
1. Otherwise, return [=error=] with [=error code=] [=invalid argument=].
1. Let |descriptorMapping| be |simulatedCharacteristic|'s simulated GATT descriptor mapping.
1. Let |descriptorUuid| be |params|[{^bluetooth.SimulateDescriptorParameters/"descriptorUuid"^}].
1. If |params|[{^bluetooth.SimulateDescriptorParameters/"type"^}] is {^bluetooth.SimulateDescriptorParameters/type/"add"^}:
1. If |descriptorMapping|[|descriptorUuid|] [=map/exists=], return [=error=] with
[=error code=] [=invalid element state=].
1. Let |simulatedGattDescriptor| be a new simulated GATT descriptor.
1. Set |simulatedGattDescriptor|'s UUID to |descriptorUuid|.
1. Set |descriptorMapping|[|descriptorUuid|] to |simulatedGattDescriptor|.
1. Create a BluetoothRemoteGATTDescriptor representing |simulatedGattDescriptor|
and add a mapping from |simulatedGattDescriptor| to the resulting {{Promise}} in
|simulatedDeviceInstance|.{{[[context]]}}.{{Bluetooth/[[attributeInstanceMap]]}}.
1. Return [=success=] with data `null`.
1. If |params|[{^bluetooth.SimulateDescriptorParameters/"type"^}] is {^bluetooth.SimulateDescriptorParameters/type/"remove"^}:
1. If |descriptorMapping|[|descriptorUuid|] [=map/exists=], let |simulatedGattDescriptor|
be |descriptorMapping|[|descriptorUuid|].
1. Otherwise, return [=error=] with [=error code=] [=invalid element state=].
1. Remove |simulatedGattDescriptor| from |simulatedDeviceInstance|.{{[[context]]}}.{{Bluetooth/[[attributeInstanceMap]]}}.
1. Remove |descriptorUuid| from |descriptorMapping|.
1. Return [=success=] with data `null`.
1. Return [=error=] with [=error code=] [=invalid argument=].
{
"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"
}
}
{
"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"
}
}
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 ]
}
BluetoothDevice representing
|simulatedDevice| inside |navigable|'s active window's associated Navigator's
[=associated Bluetooth=].
1. Let |promise| be |simulatedDeviceInstance|.{{[[context]]}}.{{Bluetooth/[[attributeInstanceMap]]}}[|simulatedDescriptor|].
1. Upon fulfillment of |promise| with |descriptor|, run the following steps:
1. If |params|[{^bluetooth.SimulateDescriptorResponseParameters/"type"^}] is {^bluetooth.SimulateDescriptorResponseParameters/type/"read"^}, run the following steps:
1. If |descriptor|.{{[[automatedDescriptorReadResponse]]}} is `expected`,
set |descriptor|.{{[[automatedDescriptorReadResponse]]}} to |params|[{^bluetooth.SimulateDescriptorResponseParameters/"code"^}] and
|descriptor|.{{[[automatedDescriptorReadResponseData]]}} to [=a copy of the bytes held=]
by |params|[{^bluetooth.SimulateDescriptorResponseParameters/"data"^}].
1. Otherwise, return [=error=] with [=error code=] [=invalid element state=].
1. If |params|[{^bluetooth.SimulateDescriptorResponseParameters/"type"^}] is {^bluetooth.SimulateDescriptorResponseParameters/type/"write"^}, run the following steps:
1. If |characteristic|.{{[[automatedDescriptorWriteResponse]]}} is `expected`,
set |characteristic|.{{[[automatedDescriptorWriteResponse]]}} to |params|[{^bluetooth.SimulateDescriptorResponseParameters/"code"^}].
1. Otherwise, return [=error=] with [=error code=] [=invalid element state=].
1. Otherwise, return [=error=] with [=error code=] [=invalid argument=].
{
"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]
}
}
BluetoothEvent = ( bluetooth.RequestDevicePromptUpdated // bluetooth.GattConnectionAttempted )#### The bluetooth.requestDevicePromptUpdated Event #### {#bluetooth-requestdevicepromptupdated-event}
bluetooth.RequestDevicePromptUpdated = (
method: "bluetooth.requestDevicePromptUpdated",
params: bluetooth.RequestDevicePromptUpdatedParameters
)
bluetooth.RequestDevicePromptUpdatedParameters = {
context: text,
prompt: bluetooth.RequestDevicePrompt,
devices: [* bluetooth.RequestDeviceInfo],
}
bluetooth.GattConnectionAttempted = (
method: "bluetooth.gattConnectionAttempted",
params: bluetooth.GattConnectionAttemptedParameters
)
bluetooth.GattConnectionAttemptedParameters = {
context: text,
address: text
}
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 ]
}
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 ]
}
{{Array.prototype.map}}.call(filter.services,
{{BluetoothUUID/getService()|BluetoothUUID.getService}}),
this MUST apply the {{Array.prototype.map}} algorithm defined in [[ECMAScript]]
with filter.services as its this parameter
and the algorithm defined in for
{{BluetoothUUID/getService()|BluetoothUUID.getService}} as its
callbackfn parameter, regardless of any modifications that have
been made to window, Array,
Array.prototype, Array.prototype.map,
Function, Function.prototype,
BluetoothUUID, BluetoothUUID.getService, or other
objects.
This specification uses a read-only type that is similar to WebIDL's
{{FrozenArray}}.
* A read only ArrayBuffer has {{ArrayBuffer}}'s values and interface,
except that attempting to write to its contents or transfer it has the same
effect as trying to write to a {{FrozenArray}}'s contents. This applies to
{{TypedArray}}s and {{DataView}}s wrapped around the {{ArrayBuffer}} too.